toolroll 0.9.36 → 0.9.38

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 (175) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/dist/assignment-adapters.d.ts +12 -48
  3. package/dist/assignment-adapters.js +33 -43
  4. package/dist/browser/THIRD_PARTY_NOTICES.txt +3 -3
  5. package/dist/browser/workspace.css +1 -1
  6. package/dist/browser/workspace.js +26 -26
  7. package/dist/browser-crew.js +5 -0
  8. package/dist/browser-workspace.d.ts +2 -0
  9. package/dist/build-review.d.ts +1 -1
  10. package/dist/build-review.js +2 -1
  11. package/dist/chat-acceptance.d.ts +1 -1
  12. package/dist/chat-actions.d.ts +18 -12
  13. package/dist/chat-actions.js +15 -65
  14. package/dist/chat-ask.js +5 -5
  15. package/dist/chat-channel.js +20 -9
  16. package/dist/chat-decide.d.ts +18 -4
  17. package/dist/chat-decide.js +31 -11
  18. package/dist/chat-delivery-state.d.ts +12 -70
  19. package/dist/chat-delivery-state.js +17 -135
  20. package/dist/chat-delivery.js +58 -30
  21. package/dist/chat-flow.js +8 -6
  22. package/dist/chat-inbox.js +1 -1
  23. package/dist/coding-context.d.ts +2 -39
  24. package/dist/coding-context.js +2 -1
  25. package/dist/coding-handoff.d.ts +6 -25
  26. package/dist/coding-handoff.js +11 -5
  27. package/dist/coding-types.d.ts +2 -26
  28. package/dist/coding-workspace.d.ts +2 -0
  29. package/dist/coding-workspace.js +50 -14
  30. package/dist/contracts/acceptance-terms.d.ts +14 -0
  31. package/dist/contracts/acceptance-terms.js +16 -0
  32. package/dist/contracts/chat-actions.d.ts +328 -0
  33. package/dist/contracts/chat-actions.js +133 -0
  34. package/dist/contracts/chat-callback-rows.d.ts +59 -0
  35. package/dist/contracts/chat-callback-rows.js +46 -0
  36. package/dist/contracts/chat-content.d.ts +276 -0
  37. package/dist/contracts/chat-content.js +127 -0
  38. package/dist/contracts/chat-tables.d.ts +6 -0
  39. package/dist/contracts/chat-tables.js +136 -0
  40. package/dist/contracts/coding-context.d.ts +97 -0
  41. package/dist/contracts/coding-context.js +60 -0
  42. package/dist/contracts/coding-handoff.d.ts +44 -0
  43. package/dist/contracts/coding-handoff.js +47 -0
  44. package/dist/contracts/coding-workspace.d.ts +165 -0
  45. package/dist/contracts/coding-workspace.js +71 -0
  46. package/dist/contracts/contract.d.ts +7 -2
  47. package/dist/contracts/contract.js +21 -12
  48. package/dist/contracts/decision.d.ts +45 -0
  49. package/dist/contracts/decision.js +45 -0
  50. package/dist/contracts/discord-callback.d.ts +45 -0
  51. package/dist/contracts/discord-callback.js +34 -0
  52. package/dist/contracts/flow-card.d.ts +50 -0
  53. package/dist/contracts/flow-card.js +46 -0
  54. package/dist/contracts/flow-send.d.ts +119 -0
  55. package/dist/contracts/flow-send.js +112 -0
  56. package/dist/contracts/flow.d.ts +5 -0
  57. package/dist/contracts/flow.js +5 -0
  58. package/dist/contracts/gateway-tools.d.ts +374 -0
  59. package/dist/contracts/gateway-tools.js +94 -0
  60. package/dist/contracts/lead-context.d.ts +165 -0
  61. package/dist/contracts/lead-context.js +73 -0
  62. package/dist/contracts/lead-tools.d.ts +1905 -0
  63. package/dist/contracts/lead-tools.js +265 -0
  64. package/dist/contracts/memory-pass.d.ts +158 -0
  65. package/dist/contracts/memory-pass.js +102 -0
  66. package/dist/contracts/project-knowledge.d.ts +126 -0
  67. package/dist/contracts/project-knowledge.js +93 -0
  68. package/dist/contracts/project-memory.d.ts +117 -0
  69. package/dist/contracts/project-memory.js +51 -0
  70. package/dist/contracts/project-skills.d.ts +121 -0
  71. package/dist/contracts/project-skills.js +76 -0
  72. package/dist/contracts/review-findings.d.ts +46 -0
  73. package/dist/contracts/review-findings.js +48 -0
  74. package/dist/contracts/route.d.ts +484 -0
  75. package/dist/contracts/route.js +128 -0
  76. package/dist/contracts/scope.d.ts +105 -0
  77. package/dist/contracts/scope.js +114 -0
  78. package/dist/contracts/scout-report.d.ts +234 -0
  79. package/dist/contracts/scout-report.js +141 -0
  80. package/dist/contracts/slack-callback.d.ts +43 -0
  81. package/dist/contracts/slack-callback.js +35 -0
  82. package/dist/contracts/sort-answer.d.ts +83 -0
  83. package/dist/contracts/sort-answer.js +77 -0
  84. package/dist/contracts/stage-output.d.ts +127 -0
  85. package/dist/contracts/stage-output.js +166 -0
  86. package/dist/contracts/task-sizing.d.ts +24 -0
  87. package/dist/contracts/task-sizing.js +28 -0
  88. package/dist/contracts/teammate-turn.d.ts +43 -0
  89. package/dist/contracts/teammate-turn.js +51 -0
  90. package/dist/contracts/teams-callback.d.ts +14 -0
  91. package/dist/contracts/teams-callback.js +14 -0
  92. package/dist/contracts/telegram-callback.d.ts +120 -0
  93. package/dist/contracts/telegram-callback.js +97 -0
  94. package/dist/decision.d.ts +11 -20
  95. package/dist/decision.js +86 -167
  96. package/dist/desktop-update.d.ts +1 -1
  97. package/dist/discord-chat.js +31 -20
  98. package/dist/flow-engine.js +14 -17
  99. package/dist/flow-items.d.ts +4 -13
  100. package/dist/flow-items.js +1 -15
  101. package/dist/flow-send.d.ts +6 -37
  102. package/dist/flow-send.js +18 -14
  103. package/dist/flow-share.js +7 -1
  104. package/dist/flow-sort.d.ts +7 -26
  105. package/dist/flow-sort.js +25 -20
  106. package/dist/flow-steps.js +3 -2
  107. package/dist/flows.d.ts +2 -4
  108. package/dist/flows.js +9 -5
  109. package/dist/invoke.js +4 -4
  110. package/dist/lead-context.d.ts +4 -2
  111. package/dist/lead-context.js +24 -17
  112. package/dist/mate-progress.d.ts +26 -0
  113. package/dist/mate-progress.js +24 -0
  114. package/dist/mate-tools.d.ts +4 -0
  115. package/dist/mate-tools.js +108 -275
  116. package/dist/mate.js +11 -2
  117. package/dist/mcp-connect.d.ts +24 -4
  118. package/dist/mcp-connect.js +78 -15
  119. package/dist/mcp.js +84 -216
  120. package/dist/memory-pass.d.ts +17 -42
  121. package/dist/memory-pass.js +54 -26
  122. package/dist/phase-routing.d.ts +19 -72
  123. package/dist/phase-routing.js +34 -164
  124. package/dist/project-knowledge.d.ts +16 -35
  125. package/dist/project-knowledge.js +42 -21
  126. package/dist/project-memory.d.ts +2 -28
  127. package/dist/project-memory.js +28 -13
  128. package/dist/project-skills.d.ts +16 -38
  129. package/dist/project-skills.js +41 -32
  130. package/dist/project-tools.d.ts +1 -0
  131. package/dist/project-tools.js +4 -4
  132. package/dist/provider.d.ts +2 -0
  133. package/dist/provider.js +4 -16
  134. package/dist/result-shots.d.ts +1 -1
  135. package/dist/result-shots.js +2 -1
  136. package/dist/review-switch.d.ts +3 -8
  137. package/dist/reviewer.d.ts +8 -12
  138. package/dist/reviewer.js +11 -35
  139. package/dist/scope.d.ts +21 -43
  140. package/dist/scope.js +42 -128
  141. package/dist/scout-report.d.ts +9 -194
  142. package/dist/scout-report.js +113 -264
  143. package/dist/scout.js +15 -11
  144. package/dist/serve.js +23 -5
  145. package/dist/setup-guide.d.ts +2 -2
  146. package/dist/slack-chat.js +26 -20
  147. package/dist/slack-state.js +2 -1
  148. package/dist/store.d.ts +9 -32
  149. package/dist/store.js +18 -15
  150. package/dist/structured-output.js +2 -1
  151. package/dist/subscription-chat.d.ts +4 -0
  152. package/dist/subscription-chat.js +4 -1
  153. package/dist/task-review-brief.d.ts +1 -7
  154. package/dist/task-review-brief.js +6 -6
  155. package/dist/task-sizing.d.ts +3 -27
  156. package/dist/task-sizing.js +11 -26
  157. package/dist/task-text.d.ts +0 -5
  158. package/dist/task-text.js +1 -3
  159. package/dist/teammate-question.js +7 -5
  160. package/dist/teammate-work.js +9 -5
  161. package/dist/teammates.d.ts +13 -64
  162. package/dist/teammates.js +32 -33
  163. package/dist/teams-chat.js +20 -11
  164. package/dist/telegram-flow.d.ts +9 -0
  165. package/dist/telegram-flow.js +44 -8
  166. package/dist/telegram-mate.d.ts +4 -14
  167. package/dist/telegram-mate.js +9 -8
  168. package/dist/telegram-team.d.ts +2 -19
  169. package/dist/telegram.d.ts +1 -1
  170. package/dist/telegram.js +94 -68
  171. package/dist/text-limits.d.ts +142 -1
  172. package/dist/text-limits.js +144 -1
  173. package/dist/tools-ui.d.ts +2 -1
  174. package/dist/tools-ui.js +7 -4
  175. package/package.json +17 -12
package/dist/scout.js CHANGED
@@ -20,7 +20,8 @@ import { auditOf } from "./provider.js";
20
20
  import { currentClaim, heartbeat } from "./claim.js";
21
21
  import { heartbeat as runnerHeartbeat } from "./runner.js";
22
22
  import { parseDecision } from "./decision.js";
23
- import { parseReport, REPORT_IMAGE_FILE, REPORT_LIMITS, SCOUT_OUTPUT_JSON_SCHEMA } from "./scout-report.js";
23
+ import { scoutHandbackSchema } from "./contracts/scout-report.js";
24
+ import { parseReport, REPORT_IMAGE_FILE, REPORT_LIMITS, REPORT_VERSION, SCOUT_OUTPUT_JSON_SCHEMA } from "./scout-report.js";
24
25
  import { TOKEN_ENVS as TELEGRAM_TOKEN_ENVS } from "./telegram.js";
25
26
  import { evidenceRoot, mailboxName, quarantineMailboxes, readMailbox, reportFileName, SCREENSHOT_BYTE_CAP, storeEvidence, validateScreenshotBytes, writeEvidenceFile, } from "./evidence.js";
26
27
  import { MARKER as LEASE_MARKER } from "./worktree.js";
@@ -29,7 +30,7 @@ import { proveTreeUntouched, snapshotIgnored } from "./tree-proof.js";
29
30
  import { redactSecretLines, scanForSecrets } from "./evidence.js";
30
31
  import { CLAUDE_LIMITS } from "./scope.js";
31
32
  import { catalogTool, projectToolsOf, toolLaunchFor } from "./project-tools.js";
32
- import { researchToolsOf } from "./mcp-connect.js";
33
+ import { grantOf, researchToolsOf } from "./mcp-connect.js";
33
34
  import { startScoutProxy } from "./scout-net.js";
34
35
  import * as browserCheck from "./scout-browser.js";
35
36
  import { invokeAgent } from "./invoke.js";
@@ -160,6 +161,7 @@ function scoutBrief(title, goal, outOfScope, mailbox, reportFile, answers, struc
160
161
  `\`${reportFile}\`:`,
161
162
  ]),
162
163
  "{",
164
+ ` "version": ${REPORT_VERSION},`,
163
165
  ' "title": "one line",',
164
166
  ' "summary": "one paragraph the operator reads first",',
165
167
  ' "report": "the report as markdown: what you found, the evidence, the risks",',
@@ -183,7 +185,7 @@ function connectedResearch(store, taskRef, runId) {
183
185
  const repo = store.refById(taskRef)?.repo ?? null;
184
186
  if (repo === null)
185
187
  return NO_RESEARCH;
186
- return researchToolsOf(projectToolsOf(store, repo), new Set(toolLaunchFor(store, runId).tools.map(one => one.spec.name)));
188
+ return researchToolsOf(projectToolsOf(store, repo), new Set(toolLaunchFor(store, runId).tools.map(one => one.spec.name)), tool => grantOf(repo, tool));
187
189
  }
188
190
  catch {
189
191
  return NO_RESEARCH;
@@ -325,6 +327,7 @@ async function scoutWith(store, request, outputDir) {
325
327
  allowedTools: proxy === null ? [] : [...(browser === null ? SCOUT_RESEARCH_TOOLS : SCOUT_ALLOWED_TOOLS), ...research.allowed],
326
328
  ...(browser === null ? {} : { extraMcpServers: browser }),
327
329
  readOnlyTools: research.reads,
330
+ ...(research.withheld === undefined ? {} : { researchWithheld: research.withheld }),
328
331
  skipPermissions: false,
329
332
  resumeSession: null,
330
333
  ...(structured ? { jsonSchema: SCOUT_OUTPUT_JSON_SCHEMA } : {}),
@@ -484,7 +487,7 @@ async function scoutWith(store, request, outputDir) {
484
487
  ok: false,
485
488
  kind: "malformed",
486
489
  reason: "malformed-report",
487
- message: `the scout concluded, but the payload is not a report: ${parsed.problems.map(problem => problem.reason).join(", ")}`,
490
+ message: `the scout concluded, but the payload is not a report: ${parsed.problems.map(problem => problem.message).join("; ")}`,
488
491
  problems: parsed.problems,
489
492
  };
490
493
  }
@@ -544,7 +547,7 @@ async function scoutWith(store, request, outputDir) {
544
547
  // the deliverable, and a task whose deliverable does not exist is not
545
548
  // done.
546
549
  try {
547
- const content = Buffer.from(JSON.stringify(report, null, 2), "utf8");
550
+ const content = Buffer.from(JSON.stringify({ version: REPORT_VERSION, ...report }, null, 2), "utf8");
548
551
  const key = writeEvidenceFile(root, request.runId, "report.json", content);
549
552
  return {
550
553
  ok: true,
@@ -630,17 +633,18 @@ function structuredHandback(structuredOutput) {
630
633
  value = JSON.parse(structuredOutput);
631
634
  }
632
635
  catch {
633
- return { kind: "invalid", problems: [{ reason: "structured-not-json", message: "the structured output is not JSON" }] };
636
+ return { kind: "invalid", problems: [{ reason: "structured-not-json", message: "payload: the structured output is not JSON" }] };
634
637
  }
635
638
  const body = typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
636
- const kind = body["kind"];
637
- const inner = kind === "report" ? body["report"] : kind === "question" ? body["decision"] : undefined;
638
- if (kind !== "report" && kind !== "question") {
639
- return { kind: "invalid", problems: [{ reason: "structured-kind", message: 'the structured output must say kind "report" or "question"' }] };
639
+ const read = scoutHandbackSchema.shape.kind.safeParse(body["kind"]);
640
+ if (!read.success) {
641
+ return { kind: "invalid", problems: [{ reason: "structured-kind", message: `kind: must be ${scoutHandbackSchema.shape.kind.options.map(one => JSON.stringify(one)).join(" or ")}` }] };
640
642
  }
643
+ const kind = read.data;
644
+ const inner = kind === "report" ? body["report"] : body["decision"];
641
645
  if (inner === undefined || inner === null) {
642
646
  const field = kind === "report" ? "report" : "decision";
643
- return { kind: "invalid", problems: [{ reason: `structured-missing-${field}`, message: `kind "${kind}" needs its ${field}` }] };
647
+ return { kind: "invalid", problems: [{ reason: `structured-missing-${field}`, message: `${field}: required for kind "${kind}"` }] };
644
648
  }
645
649
  return { kind, raw: JSON.stringify(inner) };
646
650
  }
package/dist/serve.js CHANGED
@@ -55,6 +55,7 @@ import { addFlowTriggerTo, checkFlowTriggerNow, HOOK_PATH, pressFlowButton, rece
55
55
  import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, FLOW_HREF, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
56
56
  import { chooseFlowCard } from "./flow-send.js";
57
57
  import { FlowContractError, FLOW_TEMPLATES, validateFlowDefinition, withZoneNames } from "./flows.js";
58
+ import { stageReferenceProblems } from "./contracts/stage-output.js";
58
59
  import { TOOL_CATALOG, addToolTo, catalogTool, discoverTools, localAppOf, projectToolsOf, removeToolFrom, secretsSetFor, setToolSecret, splitCommandLine, testToolOf } from "./project-tools.js";
59
60
  import { changeLearning, learningView } from "./project-learning.js";
60
61
  import { applySavedKnowledge, changeKnowledge, knowledgeView, knowledgeVersion, readKnowledgeSnapshot } from "./project-knowledge.js";
@@ -723,7 +724,7 @@ export function createDecisionServer(options) {
723
724
  tools: tools.map(tool => ({ tool, secretsSet: secretsSetFor(repo, tool.spec, toolHome) })),
724
725
  // A service that connects by signing in (or an app's own Connect) is offered only that way.
725
726
  catalog: TOOL_CATALOG.filter(one => !names.has(one.name) && oneClickOf(one.name) === null && localConnectOf(one.name) === null),
726
- connections: connectionsOf(store, repo), kit, wanted,
727
+ connections: connectionsOf(store, repo, toolHome), kit, wanted,
727
728
  found: discoverTools(repo, codex, toolHome).filter(one => !names.has(one.spec.name)),
728
729
  };
729
730
  };
@@ -4539,14 +4540,25 @@ export function createDecisionServer(options) {
4539
4540
  clearTimeout(previous.expiry);
4540
4541
  const live = { steps: [], done: false, ok: false, listeners: previous?.listeners ?? new Set() };
4541
4542
  liveTurns.set(thread, live);
4542
- const current = () => live.steps.at(-1) ?? (live.steps.push({ tools: [], text: "" }), live.steps.at(-1));
4543
+ const current = () => live.steps.at(-1) ?? (live.steps.push({ tools: [], toolCalls: [], text: "" }), live.steps.at(-1));
4543
4544
  return event => {
4544
4545
  if (live.done)
4545
4546
  return;
4546
4547
  if (event.kind === "step")
4547
- live.steps.push({ tools: [], text: "" });
4548
- else if (event.kind === "tool")
4548
+ live.steps.push({ tools: [], toolCalls: [], text: "" });
4549
+ else if (event.kind === "tool") {
4549
4550
  current().tools.push(event.label);
4551
+ current().toolCalls.push({ id: event.id, label: event.label, state: "running" });
4552
+ }
4553
+ else if (event.kind === "tool-result") {
4554
+ for (const step of live.steps) {
4555
+ const index = step.toolCalls.findIndex(tool => tool.id === event.id);
4556
+ if (index !== -1) {
4557
+ step.toolCalls[index] = { ...step.toolCalls[index], ...event.outcome };
4558
+ break;
4559
+ }
4560
+ }
4561
+ }
4550
4562
  else if (event.kind === "text")
4551
4563
  current().text = event.text;
4552
4564
  for (const listener of live.listeners)
@@ -7186,6 +7198,10 @@ export function createDecisionServer(options) {
7186
7198
  try {
7187
7199
  drawn = JSON.parse(body.get("definition") ?? "null");
7188
7200
  saved = validateFlowDefinition(drawn);
7201
+ // A {{stage.…}} this save adds must be one its zone hands on; ones the saved flow had stay.
7202
+ const references = stageReferenceProblems(saved, flowDefinitionOf(flow));
7203
+ if (references.length > 0)
7204
+ throw new FlowContractError(references.map(one => one.line));
7189
7205
  }
7190
7206
  catch (error) {
7191
7207
  return answer(400, { ok: false, said: error instanceof SyntaxError ? "That flow couldn't be read." : error instanceof FlowContractError ? withZoneNames(error.lines, drawn) : error instanceof Error ? error.message : "That flow isn't valid." });
@@ -7834,7 +7850,9 @@ export function createDecisionServer(options) {
7834
7850
  const origin = consoleOrigin(request.headers.host);
7835
7851
  if (origin === null)
7836
7852
  return back("Connect tools from this computer (localhost) or from your https address.");
7837
- const started = await startConnect({ service: service.id, repo: target, by: who.name, origin, kit: also === null ? kit : null, template }, options.connectFetch ?? fetch);
7853
+ // Reconnect read-only (from a connected service's Tools entry): the same sign-in, asking only to read.
7854
+ const readOnly = also === null && body.get("access") === "read";
7855
+ const started = await startConnect({ service: service.id, repo: target, by: who.name, origin, kit: also === null ? kit : null, template, readOnly }, options.connectFetch ?? fetch);
7838
7856
  if (!started.ok)
7839
7857
  return back(started.said);
7840
7858
  for (const [key, visit] of connectVisits)
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
15
15
  } | null;
16
16
  /** Browser installation may write only this known file inside its project. */
17
17
  export declare function previewProjectInstructions(repo: string): {
18
- content?: never;
19
18
  ok: false;
20
19
  message: string;
21
20
  plan?: never;
21
+ content?: never;
22
22
  fingerprint?: never;
23
23
  installed?: never;
24
24
  } | {
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
30
30
  installed: boolean;
31
31
  };
32
32
  export declare function addProjectInstructions(repo: string, fingerprint: string): {
33
- content?: never;
34
33
  ok: false;
35
34
  message: string;
36
35
  plan?: never;
36
+ content?: never;
37
37
  fingerprint?: never;
38
38
  installed?: never;
39
39
  } | import("./skills.js").InstallResult;
@@ -11,6 +11,9 @@ import { MATE_MESSAGE_MAX_CHARS } from "./mate.js";
11
11
  import { renderReply } from "./reply-shape.js";
12
12
  import {} from "./mate-doors.js";
13
13
  import { SlackState, slackHash, } from "./slack-state.js";
14
+ import { ChatDeliveryError, partContent } from "./chat-delivery-state.js";
15
+ import { readChatPart } from "./contracts/chat-content.js";
16
+ import { readSlackBlockActions, readSlackButton, SLACK_LINK_ACTION } from "./contracts/slack-callback.js";
14
17
  import { object, slackId, slackTs, slackMember, SlackError, uploadSlackBytes, } from "./slack-api.js";
15
18
  import { resultImageFileName, safeResultImageCaption, verifyResultImage, } from "./chat-evidence.js";
16
19
  import {} from "./store.js";
@@ -70,25 +73,23 @@ export function receiveSlack(state, identity, type, raw, now) {
70
73
  id = slackHash(`${identity.installation}:event:${body.event_id}`);
71
74
  }
72
75
  else if (type === "interactive" && body.type === "block_actions") {
73
- const actions = Array.isArray(body.actions) ? body.actions : [];
74
- if (actions.length !== 1)
76
+ const interaction = readSlackBlockActions(body);
77
+ if (!interaction.ok || interaction.value.container.type !== "message")
75
78
  return false;
76
- const action = object(actions[0]), container = object(body.container);
77
- if (
78
- // toolroll_* since the rename; standing_orders_* buttons on older messages still work.
79
- !/^(?:toolroll|standing_orders)_(confirm|dismiss|yes|cancel|flow_approve|flow_edit|flow_send_back|flow_choose_[0-3]|flow_note_(?:yes|no)|question_choice|question_words)$/.test(String(action.action_id)) ||
80
- typeof action.value !== "string" ||
81
- !/^[a-f0-9]{32}$/.test(action.value) ||
82
- !slackTs(action.action_ts) ||
83
- container.type !== "message")
79
+ const { actions, container, user } = interaction.value;
80
+ // A link button opens its page itself: nothing to do, and nothing to say.
81
+ if (actions.length === 1 && SLACK_LINK_ACTION.test(actions[0].action_id ?? ""))
84
82
  return false;
85
- member = object(body.user).id;
83
+ member = user.id;
86
84
  channel = container.channel_id;
87
85
  ts = container.message_ts;
88
- thread = object(body.message).thread_ts ?? ts;
86
+ thread = interaction.value.message?.thread_ts ?? ts;
89
87
  kind = "action";
90
- payload = { token: action.value };
91
- id = slackHash(`${identity.installation}:action:${member}:${ts}:${action.value}:${action.action_ts}`);
88
+ // A button that isn't one Toolroll made is answered with why, once the tap is proved to be the paired person's below.
89
+ const button = readSlackButton(interaction.value);
90
+ payload = button.ok ? { token: button.value.value } : { problem: button.issues.map(issue => issue.line).join("; ") };
91
+ const tapped = actions[0];
92
+ id = slackHash(`${identity.installation}:action:${member}:${ts}:${tapped?.value ?? ""}:${tapped?.action_ts ?? ""}`);
92
93
  }
93
94
  else
94
95
  return false;
@@ -101,9 +102,10 @@ export function receiveSlack(state, identity, type, raw, now) {
101
102
  // A channel or private group is a room: accepted only while it follows a
102
103
  // conversation, or for the `/team` words that make it follow one.
103
104
  const isRoom = !slackId(channel, "D");
105
+ const words = "text" in payload ? payload.text ?? "" : "";
104
106
  // v89: a channel that feeds a flow takes anyone's message (as a card, with no say over anything); "flow 12" connects one.
105
- const inbox = isRoom && kind === "message" ? channelInbox(state.store, "slack", identity.installation, String(channel), String(payload.text ?? "")) : { watched: false, command: false };
106
- const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(String(payload.text ?? "")) !== null) || inbox.watched || inbox.command);
107
+ const inbox = isRoom && kind === "message" ? channelInbox(state.store, "slack", identity.installation, String(channel), words) : { watched: false, command: false };
108
+ const roomish = isRoom && (state.room(identity.installation, String(channel)) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
107
109
  if (isRoom && !roomish)
108
110
  return false;
109
111
  const binding = state.bindingFor(identity.installation, member);
@@ -124,7 +126,7 @@ export function receiveSlack(state, identity, type, raw, now) {
124
126
  member,
125
127
  ts,
126
128
  thread,
127
- payload: JSON.stringify(payload),
129
+ payload,
128
130
  created: now.toISOString(),
129
131
  });
130
132
  }
@@ -227,7 +229,7 @@ export async function deliverSlackPart(options) {
227
229
  const repos = await access(options, binding, session?.ceilingDigest);
228
230
  if (event.kind === "notice" && options.canNotify?.() === false)
229
231
  return false;
230
- const content = JSON.parse(row.payload);
232
+ const content = partContent(row.payload);
231
233
  const destination = content.channel ?? binding.channel;
232
234
  let text = content.text, buttons = [];
233
235
  if (content.task &&
@@ -435,9 +437,13 @@ export async function deliverSlackPart(options) {
435
437
  return true;
436
438
  }
437
439
  catch (error) {
440
+ if (error instanceof ChatDeliveryError && error.permanent) {
441
+ state.prepare("UPDATE chat_part SET state='dropped',next_at=NULL,problem=? WHERE id=?").run(error.message, row.id);
442
+ return true;
443
+ }
438
444
  // No permission to upload files here: one plain line instead of the result's screenshots.
439
- const content = JSON.parse(row.payload);
440
- if (content.image && content.shot && error instanceof SlackError && ["missing_scope", "no_permission"].includes(error.code)) {
445
+ const saved = readChatPart(row.payload), content = saved.ok ? saved.value : null;
446
+ if (content !== null && content.image && content.shot && error instanceof SlackError && ["missing_scope", "no_permission"].includes(error.code)) {
441
447
  refuseResultShots(state, store, row, content, "Slack", nowOf(options));
442
448
  return true;
443
449
  }
@@ -1,5 +1,6 @@
1
1
  /** Slack compatibility names for the shared durable transport state. */
2
- import { ChatState, chatSchema, chatTables } from "./chat-delivery-state.js";
2
+ import { ChatState } from "./chat-delivery-state.js";
3
+ import { chatSchema, chatTables } from "./contracts/chat-tables.js";
3
4
  export { chatHash as slackHash } from "./chat-delivery-state.js";
4
5
  export const SLACK_SCHEMA = chatSchema("slack"), SLACK_TABLES = chatTables("slack");
5
6
  export class SlackState extends ChatState {
package/dist/store.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { DecisionOption } from "./contracts/decision.js";
2
+ import type { FlowCardChange, FlowCardFields } from "./contracts/flow-card.js";
1
3
  import { type Actor } from "./actor.js";
2
4
  import { type CriterionMatrixRow, type CriterionJudgementWord } from "./proof.js";
3
5
  import { type ExecutionProfile, type ChainEntry, type UnattendedPermissionMode, type AcceptanceCriterion } from "./scope.js";
@@ -764,24 +766,9 @@ export type FlowCardSource = {
764
766
  binding: number;
765
767
  };
766
768
  };
767
- export type FlowCardRow = {
768
- id: number;
769
- flow: number;
770
- title: string;
771
- description: string | null;
772
- stage: string;
773
- entry: number;
774
- state: "active" | "done" | "cancelled";
775
- task: string | null;
776
- primaryTask: string | null;
777
- note: string | null;
778
- waiting: string | null;
779
- outputs: Record<string, string>;
780
- createdBy: string;
781
- createdAt: string;
782
- updatedAt: string;
769
+ /** A flow card as read (src/contracts/flow-card.ts), and where it came from. */
770
+ export type FlowCardRow = FlowCardFields & {
783
771
  source: FlowCardSource | null;
784
- owner: string | null;
785
772
  };
786
773
  /** One line of a card's discussion: a comment (and whom it @mentioned), or who became its owner. */
787
774
  export type FlowCommentRow = {
@@ -1361,13 +1348,8 @@ export type PushPair = {
1361
1348
  lastError: string | null;
1362
1349
  acceptedAt: string | null;
1363
1350
  };
1364
- /** One option of a decision. `reversible` is a field so a scheduler can refuse to auto-apply. */
1365
- export type DecisionOption = {
1366
- id: string;
1367
- label: string;
1368
- consequence: string;
1369
- reversible: boolean;
1370
- };
1351
+ /** One option of a decision (src/contracts/decision.ts). `reversible` is a field so a scheduler can refuse to auto-apply. */
1352
+ export type { DecisionOption };
1371
1353
  /** The judgement call an agent refused to guess at (§7). Identity = its run. */
1372
1354
  export type Decision = {
1373
1355
  id: number;
@@ -6389,13 +6371,9 @@ export declare class Store {
6389
6371
  }, now: Date): boolean;
6390
6372
  /** What a card's current zone is doing: its task, the card's main work, what it waits on, what zones reported. */
6391
6373
  /** Read and written in one transaction, so a pass changing one field never writes back another pass's older task or waiting. */
6392
- updateFlowCard(id: number, change: {
6393
- task?: string | null;
6394
- primaryTask?: string;
6395
- waiting?: string | null;
6396
- outputs?: Record<string, string>;
6397
- state?: "done" | "cancelled";
6398
- }, now: Date): void;
6374
+ /** Outputs are written only when they change (src/contracts/stage-output.ts), never over a row a newer Toolroll wrote;
6375
+ * a zone's attachment stays only while its output does. */
6376
+ updateFlowCard(id: number, change: FlowCardChange, now: Date): void;
6399
6377
  flowEvents(card: number): {
6400
6378
  fromStage: string | null;
6401
6379
  toStage: string;
@@ -8216,4 +8194,3 @@ export type VerifiedAuthor = string & {
8216
8194
  readonly __verifiedAuthor: unique symbol;
8217
8195
  };
8218
8196
  export declare function verifiedAuthor(name: string): VerifiedAuthor;
8219
- export {};
package/dist/store.js CHANGED
@@ -1,5 +1,4 @@
1
- import { chatSchema, chatTables } from "./chat-delivery-state.js";
2
- import { SLACK_SCHEMA, SLACK_TABLES } from "./slack-state.js";
1
+ import { chatSchema, chatTables } from "./contracts/chat-tables.js";
3
2
  import { assessmentFromSavedEvidence, verificationEvidence } from "./verification-evidence.js";
4
3
  import { LEARNING_SCHEMA, queueLearning } from "./project-learning.js";
5
4
  import { SKILLS_SCHEMA } from "./project-skills.js";
@@ -8,6 +7,7 @@ import { MEMORY_SCHEMA } from "./project-memory.js";
8
7
  import { MODELS_SCHEMA } from "./model-catalog.js";
9
8
  import { validateTaskText } from "./task-text.js";
10
9
  import { flowDefinitionForStore, flowDefinitionFromStore } from "./flows.js";
10
+ import { cardOutputsForStore, cardOutputsFromStore } from "./contracts/stage-output.js";
11
11
  import { chatControlHref, chatResultHref } from "./chat-controls.js";
12
12
  import { actorLabel, currentActor, leadSecretMatches, mintLeadToken, parseLeadToken } from "./actor.js";
13
13
  import { scanForSecrets } from "./evidence.js";
@@ -687,18 +687,11 @@ function readFlowStepRunRow(row) {
687
687
  durationMs: number("duration_ms"), exitCode: number("exit_code"), result: optional("result"), log: optional("log"), decisionJson: optional("decision_json") };
688
688
  }
689
689
  function readFlowCardRow(row) {
690
- let outputs = {};
691
- try {
692
- const parsed = JSON.parse(String(row["outputs_json"] ?? "{}"));
693
- if (parsed !== null && typeof parsed === "object")
694
- outputs = Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === "string"));
695
- }
696
- catch {
697
- outputs = {};
698
- }
690
+ // What finished zones handed on (src/contracts/stage-output.ts): a row saved before it carried a version reads as it always did.
691
+ const { outputs, attached } = cardOutputsFromStore(String(row["outputs_json"] ?? "{}"));
699
692
  const optional = (key) => row[key] === null || row[key] === undefined ? null : String(row[key]);
700
693
  return { id: Number(row["id"]), flow: Number(row["flow"]), title: String(row["title"]), description: optional("description"), stage: String(row["stage"]), entry: Number(row["entry"]),
701
- state: String(row["state"]), task: optional("task"), primaryTask: optional("primary_task"), note: optional("note"), waiting: optional("waiting"), outputs,
694
+ state: String(row["state"]), task: optional("task"), primaryTask: optional("primary_task"), note: optional("note"), waiting: optional("waiting"), outputs, attached,
702
695
  createdBy: String(row["created_by"]), createdAt: String(row["created_at"]), updatedAt: String(row["updated_at"]), source: readFlowCardSource(row["source_json"]), owner: optional("owner") };
703
696
  }
704
697
  function readFlowCardSource(value) {
@@ -3950,7 +3943,7 @@ function initializeStore(db, file) {
3950
3943
  throw new Error(`${file}: shared action history has an unknown shape; refusing to recreate it`);
3951
3944
  }
3952
3945
  if (preflight !== null && Math.abs(preflight) >= 67) {
3953
- for (const table of SLACK_TABLES)
3946
+ for (const table of chatTables("slack"))
3954
3947
  if (!tableExists(db, table))
3955
3948
  throw new Error(`${file}: Slack history is missing; refusing to recreate receipts`);
3956
3949
  }
@@ -3971,7 +3964,7 @@ function initializeStore(db, file) {
3971
3964
  db.exec(MEMORY_SCHEMA);
3972
3965
  db.exec(MODELS_SCHEMA);
3973
3966
  db.exec(SKILLS_SCHEMA);
3974
- db.exec(SLACK_SCHEMA);
3967
+ db.exec(chatSchema("slack"));
3975
3968
  db.exec(chatSchema("discord"));
3976
3969
  db.exec(chatSchema("teams"));
3977
3970
  db.exec(TEAM_SCHEMA);
@@ -19362,12 +19355,22 @@ export class Store {
19362
19355
  }
19363
19356
  /** What a card's current zone is doing: its task, the card's main work, what it waits on, what zones reported. */
19364
19357
  /** Read and written in one transaction, so a pass changing one field never writes back another pass's older task or waiting. */
19358
+ /** Outputs are written only when they change (src/contracts/stage-output.ts), never over a row a newer Toolroll wrote;
19359
+ * a zone's attachment stays only while its output does. */
19365
19360
  updateFlowCard(id, change, now) {
19366
19361
  this.transact(() => {
19367
19362
  const card = this.getFlowCard(id);
19368
19363
  if (card === null)
19369
19364
  return;
19370
- this.db.prepare("UPDATE flow_card SET task = ?, primary_task = ?, waiting = ?, outputs_json = ?, state = ?, updated_at = ? WHERE id = ?").run(change.task === undefined ? card.task : change.task, change.primaryTask ?? card.primaryTask, change.waiting === undefined ? card.waiting : change.waiting, JSON.stringify(change.outputs ?? card.outputs), change.state ?? card.state, now.toISOString(), id);
19365
+ this.db.prepare("UPDATE flow_card SET task = ?, primary_task = ?, waiting = ?, state = ?, updated_at = ? WHERE id = ?").run(change.task === undefined ? card.task : change.task, change.primaryTask ?? card.primaryTask, change.waiting === undefined ? card.waiting : change.waiting, change.state ?? card.state, now.toISOString(), id);
19366
+ if (change.outputs === undefined && change.attached === undefined)
19367
+ return;
19368
+ const saved = this.db.prepare("SELECT outputs_json FROM flow_card WHERE id = ?").get(id);
19369
+ if (cardOutputsFromStore(String(saved?.["outputs_json"] ?? "{}")).newer)
19370
+ return;
19371
+ const outputs = change.outputs ?? card.outputs;
19372
+ const attached = { ...Object.fromEntries(Object.entries(card.attached).filter(([zone]) => outputs[zone] === card.outputs[zone])), ...change.attached };
19373
+ this.db.prepare("UPDATE flow_card SET outputs_json = ? WHERE id = ?").run(cardOutputsForStore(outputs, attached), id);
19371
19374
  });
19372
19375
  }
19373
19376
  flowEvents(card) {
@@ -8,12 +8,13 @@
8
8
  * subject to the phase's strict parser.
9
9
  */
10
10
  import { redactSecretLines, scanForSecrets, storeEvidence } from "./evidence.js";
11
+ import { TEXT_LIMITS } from "./text-limits.js";
11
12
  export const STRUCTURED_REPAIR_ATTEMPTS = 2;
12
13
  export const STRUCTURED_REPAIR_MAX_TURNS = 4;
13
14
  export const STRUCTURED_REPAIR_TIMEOUT_MS = 5 * 60_000;
14
15
  /** Native parsing counts UTF-16 code units. A shorter writing target keeps
15
16
  * notes concise without imposing a second, stricter transport contract. */
16
- export const REVIEW_OUTPUT_LIMITS = { comments: 40, criteria: 12, note: 500, path: 300 };
17
+ export const REVIEW_OUTPUT_LIMITS = { comments: 40, criteria: 12, note: TEXT_LIMITS.reviewNote, path: TEXT_LIMITS.reviewPath };
17
18
  export const REVIEW_NOTE_CODE_POINTS = Math.floor(REVIEW_OUTPUT_LIMITS.note / 2);
18
19
  export const REVIEW_NOTE_GUIDANCE = `Keep each comment and criterion note to one concise finding and its evidence. Aim for ${REVIEW_NOTE_CODE_POINTS} characters or fewer; the hard limit is ${REVIEW_OUTPUT_LIMITS.note} UTF-16 units. Preserve the conclusion and any needed provenance; omit repeated rubric text.`;
19
20
  /** Strip only unambiguous, whole-payload wrappers. */
@@ -9,6 +9,10 @@ export type SubscriptionMateRequest = {
9
9
  dataDocument: string;
10
10
  history: readonly MateHistoryMessage[];
11
11
  tools: readonly MateToolSchema[];
12
+ /** A structured answer of another shape (the memory pass's verdict): this JSON Schema is the harness's
13
+ * `--json-schema` (Codex's `--output-schema`) in place of the mate envelope, and its JSON text comes back as the
14
+ * answer's text, unread, for the caller's own schema to parse. */
15
+ outputSchema?: Record<string, unknown>;
12
16
  timeoutMs: number;
13
17
  /** The turn's own deadline: aborting it ends the harness's whole process group. */
14
18
  signal?: AbortSignal;
@@ -191,7 +191,7 @@ export async function performSubscriptionMateRequest(request, runner = run) {
191
191
  try {
192
192
  const prompt = composeSubscriptionMatePrompt(request);
193
193
  const streaming = request.provider !== "codex-subscription" && request.onText !== undefined;
194
- const schema = outputSchema(request.tools);
194
+ const schema = request.outputSchema ?? outputSchema(request.tools);
195
195
  let command;
196
196
  let args;
197
197
  if (request.provider === "codex-subscription") {
@@ -271,6 +271,9 @@ export async function performSubscriptionMateRequest(request, runner = run) {
271
271
  const output = request.provider === "codex-subscription" ? codexOutput(result.stdout) : claudeOutput(streaming ? lastResultLine(result.stdout) : result.stdout);
272
272
  if (output.text === null)
273
273
  return { ok: false, problem: "malformed-reply" };
274
+ if (request.outputSchema !== undefined) {
275
+ return { ok: true, answer: { text: output.text, calls: [], tokensIn: output.tokensIn, tokensOut: output.tokensOut, reportedCostMicrousd: null } };
276
+ }
274
277
  return parseSubscriptionMateAnswer(output.text, output);
275
278
  }
276
279
  finally {
@@ -1,15 +1,9 @@
1
1
  /** A read-only packet of saved result facts. Never opens a worktree or emits logs, patches or saved shell commands. */
2
2
  import type { Store } from './store.js';
3
3
  import { type AssignmentAccess } from './assignment.js';
4
- import { type FindingSeverity } from './review-switch.js';
4
+ import { type BuildFinding as Finding, type FindingSeverity } from './contracts/review-findings.js';
5
5
  declare const MISSING: 'not recorded';
6
6
  type Missing = typeof MISSING;
7
- type Finding = {
8
- severity: FindingSeverity;
9
- file: string;
10
- line: number;
11
- scenario: string;
12
- };
13
7
  export type ReviewBrief = {
14
8
  header: {
15
9
  task: string;
@@ -2,13 +2,13 @@ import { assignmentChecksForRun, assignmentOf } from './assignment.js';
2
2
  import { assignmentPresentationOf } from './assignment-presentation.js';
3
3
  import { readVerifiedArtifact, readVerifiedProofForRun, reportShotsOf, scanForSecrets } from './evidence.js';
4
4
  import { SCREENSHOT_CAPTURE, structuredHandoffView, terminalDiffView } from './result-evidence-readers.js';
5
+ import { FINDING_SEVERITIES } from './contracts/review-findings.js';
5
6
  import { buildReviewOf } from './review-switch.js';
6
7
  import { parseReport } from './scout-report.js';
7
8
  import { sanitizeTranscriptLine } from './live.js';
8
9
  import { redactSecretAssignments } from './builder.js';
9
10
  import { parseExecutionPlanDocument } from './plan.js';
10
11
  const MISSING = 'not recorded';
11
- const SEVERITIES = ['HIGH', 'MEDIUM', 'LOW'];
12
12
  /** Redact before shortening so a clipped credential cannot escape recognition. Apply to every data string in both formats. */
13
13
  function safeText(value, secrets, cap = 300) {
14
14
  if (typeof value !== 'string' || !value.trim())
@@ -65,11 +65,11 @@ export function taskReviewBrief(store, task, root, now, access, options = {}) {
65
65
  ...Object.entries(progress?.suites ?? {}).filter(([, suite]) => suite.state === 'failed').map(([name, suite]) => `${name}: ${suite.failed} failed`),
66
66
  ])] : [];
67
67
  const review = run === null ? null : buildReviewOf(store, run.id);
68
- const findings = review === null ? [] : [...review.high, ...review.followUps].filter(one => one && SEVERITIES.includes(one.severity));
69
- const counts = Object.fromEntries(SEVERITIES.map(severity => [severity, findings.filter(one => one.severity === severity).length]));
70
- const shown = SEVERITIES.flatMap(severity => severity === 'LOW' && !options.all ? [] : findings.filter(one => one.severity === severity))
68
+ const findings = review === null ? [] : [...review.high, ...review.followUps].filter(one => one && FINDING_SEVERITIES.includes(one.severity));
69
+ const counts = Object.fromEntries(FINDING_SEVERITIES.map(severity => [severity, findings.filter(one => one.severity === severity).length]));
70
+ const shown = FINDING_SEVERITIES.flatMap(severity => severity === 'LOW' && !options.all ? [] : findings.filter(one => one.severity === severity))
71
71
  .slice(0, 12).map(one => ({ severity: one.severity, file: safe(one.file), line: Number.isSafeInteger(one.line) ? one.line : 0, scenario: safe(one.scenario) }));
72
- const omitted = Object.fromEntries(SEVERITIES.map(severity => [severity, counts[severity] - shown.filter(one => one.severity === severity).length]));
72
+ const omitted = Object.fromEntries(FINDING_SEVERITIES.map(severity => [severity, counts[severity] - shown.filter(one => one.severity === severity).length]));
73
73
  const proof = run === null ? null : readVerifiedProofForRun(store, root, run.id);
74
74
  const shots = artifacts.filter(one => one.kind === 'screenshot');
75
75
  const reportShots = report?.ok && run !== null ? reportShotsOf(artifacts, root, run.id, report.report.images) : [];
@@ -152,7 +152,7 @@ export function renderReviewBrief(brief) {
152
152
  else {
153
153
  lines.push(`Automatic review: ${brief.review.state}${brief.review.reason === MISSING ? '' : ` · ${brief.review.reason}`}`);
154
154
  lines.push(...brief.review.findings.map(one => ` ${one.severity} ${one.file}:${one.line} — ${one.scenario}`));
155
- for (const severity of SEVERITIES)
155
+ for (const severity of FINDING_SEVERITIES)
156
156
  if (brief.review.omitted[severity])
157
157
  lines.push(` ${severity}: ${brief.review.omitted[severity]} more (${brief.review.counts[severity]} total)`);
158
158
  if (Object.values(brief.review.counts).every(count => count === 0) && brief.review.state === 'reviewed')
@@ -1,5 +1,6 @@
1
+ import { type SizeAnswer } from "./contracts/task-sizing.js";
1
2
  import { run, type ExecResult } from "./exec.js";
2
- import { type TaskSize, type TaskSizing } from "./phase-routing.js";
3
+ import { type TaskSizing } from "./phase-routing.js";
3
4
  import type { Store } from "./store.js";
4
5
  /** The classifier's whole budget, from asking to answer. */
5
6
  export declare const SIZING_BUDGET_MS = 5000;
@@ -11,11 +12,7 @@ export type SizingInput = {
11
12
  outOfScope?: string | null | undefined;
12
13
  touches?: readonly string[] | undefined;
13
14
  };
14
- export type SizeAnswer = {
15
- size: TaskSize;
16
- risky: boolean;
17
- reason: string;
18
- };
15
+ export type { SizeAnswer } from "./contracts/task-sizing.js";
19
16
  /** One classifier: null (or a throw) is "no answer"; the signal ends at the budget. */
20
17
  export type Sizer = (input: SizingInput, signal: AbortSignal) => Promise<SizeAnswer | null>;
21
18
  /** Structural or broad work — the description signal planning has always read. */
@@ -27,26 +24,6 @@ export declare function planningSignals(input: SizingInput): boolean;
27
24
  export declare function heuristicSizing(input: SizingInput, why?: string | null): TaskSizing;
28
25
  /** What the classifier reads: the task as data, bounded, keys blanked. */
29
26
  export declare function sizingText(input: SizingInput): string;
30
- export declare const SIZING_SCHEMA: {
31
- readonly type: "object";
32
- readonly properties: {
33
- readonly size: {
34
- readonly type: "string";
35
- readonly enum: readonly ["small", "medium", "large"];
36
- };
37
- readonly risky: {
38
- readonly type: "boolean";
39
- };
40
- readonly reason: {
41
- readonly type: "string";
42
- readonly maxLength: 160;
43
- };
44
- };
45
- readonly required: readonly ["size", "risky", "reason"];
46
- readonly additionalProperties: false;
47
- };
48
- /** Read an answer back: only the three sizes, a boolean, and a short reason. */
49
- export declare function readSizeAnswer(value: unknown): SizeAnswer | null;
50
27
  type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
51
28
  /** A small Claude model on this computer's sign-in: no tools, no MCP servers, no repository, credential keys stripped.
52
29
  * The abort ends it: a late answer never keeps a command (or its process) waiting. */
@@ -75,4 +52,3 @@ export declare function filingSizerInstalled(): boolean;
75
52
  export declare function refineFiledSizing(store: Store, taskId: string, input: SizingInput, followPlanning: boolean, clock?: () => Date): Promise<void> | null;
76
53
  /** Wait for every classification still in flight (each is bounded by the budget). */
77
54
  export declare function settleSizings(): Promise<void>;
78
- export {};