@inflexa-ai/harness 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/README.md +48 -0
  2. package/dist/agents/conversation-agent.d.ts +15 -11
  3. package/dist/agents/conversation-agent.js +31 -34
  4. package/dist/agents/report-builder.js +2 -1
  5. package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
  6. package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
  7. package/dist/agents/sandbox/chromatin-agent.js +1 -10
  8. package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
  9. package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
  10. package/dist/agents/sandbox/enrichment-agent.js +1 -10
  11. package/dist/agents/sandbox/ephemeral-executor.js +1 -10
  12. package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
  13. package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
  14. package/dist/agents/sandbox/metabolomics-agent.js +1 -1
  15. package/dist/agents/sandbox/microbiome-agent.js +1 -1
  16. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
  17. package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
  18. package/dist/agents/sandbox/network-agent.js +1 -10
  19. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
  20. package/dist/agents/sandbox/proteomics-agent.js +1 -1
  21. package/dist/agents/sandbox/scientific-executor.js +1 -10
  22. package/dist/agents/sandbox/shared.d.ts +20 -6
  23. package/dist/agents/sandbox/shared.js +50 -39
  24. package/dist/agents/sandbox/single-cell-agent.js +1 -1
  25. package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
  26. package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
  27. package/dist/agents/sandbox/translational-safety-agent.js +3 -9
  28. package/dist/agents/sandbox/types.d.ts +1 -1
  29. package/dist/agents/system-prompt.d.ts +37 -11
  30. package/dist/agents/system-prompt.js +35 -12
  31. package/dist/app/chat-turn.d.ts +3 -0
  32. package/dist/app/chat-turn.js +3 -1
  33. package/dist/app/data-profile-orientation.d.ts +34 -0
  34. package/dist/app/data-profile-orientation.js +117 -0
  35. package/dist/app/synthesize-run.d.ts +3 -0
  36. package/dist/app/synthesize-run.js +3 -1
  37. package/dist/contracts/schemas/chat-parts.d.ts +9 -9
  38. package/dist/execution/artifact-metadata.d.ts +3 -0
  39. package/dist/execution/artifact-metadata.js +10 -3
  40. package/dist/execution/artifact-registration.d.ts +2 -1
  41. package/dist/execution/artifact-registration.js +8 -2
  42. package/dist/execution/ephemeral-runner.d.ts +16 -0
  43. package/dist/execution/ephemeral-runner.js +31 -5
  44. package/dist/execution/post-step-pipeline.d.ts +3 -0
  45. package/dist/execution/post-step-pipeline.js +24 -4
  46. package/dist/execution/reconcile-manifest.d.ts +7 -0
  47. package/dist/execution/reconcile-manifest.js +19 -5
  48. package/dist/execution/report-runner.d.ts +3 -0
  49. package/dist/execution/report-runner.js +4 -4
  50. package/dist/execution/run-synthesis.js +20 -19
  51. package/dist/execution/step-summary.d.ts +3 -0
  52. package/dist/execution/step-summary.js +6 -3
  53. package/dist/index.d.ts +9 -2
  54. package/dist/index.js +24 -5
  55. package/dist/lib/chrome.d.ts +4 -1
  56. package/dist/lib/chrome.js +10 -7
  57. package/dist/lib/console-logger.d.ts +19 -0
  58. package/dist/lib/console-logger.js +62 -0
  59. package/dist/lib/logger.d.ts +88 -0
  60. package/dist/lib/logger.js +37 -0
  61. package/dist/lib/otel.d.ts +2 -1
  62. package/dist/lib/otel.js +8 -3
  63. package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
  64. package/dist/loop/__fixtures__/scripted-provider.js +2 -1
  65. package/dist/loop/metrics.d.ts +33 -1
  66. package/dist/loop/metrics.js +56 -2
  67. package/dist/loop/run-agent.d.ts +13 -1
  68. package/dist/loop/run-agent.js +19 -4
  69. package/dist/memory/card-builders.d.ts +8 -1
  70. package/dist/memory/card-builders.js +20 -3
  71. package/dist/memory/reconstruct-cards.js +4 -3
  72. package/dist/memory/thread-history.d.ts +27 -2
  73. package/dist/memory/thread-history.js +45 -5
  74. package/dist/memory/working-memory.d.ts +93 -10
  75. package/dist/memory/working-memory.js +220 -84
  76. package/dist/prompts/SOUL.d.ts +22 -9
  77. package/dist/prompts/SOUL.js +56 -37
  78. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  79. package/dist/prompts/analogical-reasoner.js +2 -2
  80. package/dist/prompts/briefing.d.ts +100 -0
  81. package/dist/prompts/briefing.js +166 -0
  82. package/dist/prompts/conversation.d.ts +1 -1
  83. package/dist/prompts/conversation.js +94 -440
  84. package/dist/prompts/literature-reviewer.d.ts +1 -1
  85. package/dist/prompts/literature-reviewer.js +10 -10
  86. package/dist/prompts/planner.js +28 -59
  87. package/dist/prompts/report-builder.d.ts +1 -1
  88. package/dist/prompts/report-builder.js +71 -24
  89. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
  91. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
  93. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/chromatin-agent.js +3 -7
  95. package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
  96. package/dist/prompts/sandbox/data-profiler.js +13 -8
  97. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  98. package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
  99. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  100. package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
  101. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  102. package/dist/prompts/sandbox/enrichment-agent.js +2 -9
  103. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  104. package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
  105. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  106. package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
  107. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  108. package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
  109. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  110. package/dist/prompts/sandbox/microbiome-agent.js +2 -7
  111. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  112. package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
  113. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  114. package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
  115. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  116. package/dist/prompts/sandbox/network-agent.js +2 -7
  117. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  118. package/dist/prompts/sandbox/proteomics-agent.js +2 -9
  119. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  120. package/dist/prompts/sandbox/single-cell-agent.js +2 -9
  121. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  122. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
  123. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  124. package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
  125. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  126. package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
  127. package/dist/prompts/sandbox-standards.d.ts +22 -14
  128. package/dist/prompts/sandbox-standards.js +91 -71
  129. package/dist/prompts/synthesis-agent.d.ts +1 -1
  130. package/dist/prompts/synthesis-agent.js +33 -131
  131. package/dist/provenance/collector.js +1 -1
  132. package/dist/providers/ai-sdk.js +31 -4
  133. package/dist/providers/prompt-cache.d.ts +74 -0
  134. package/dist/providers/prompt-cache.js +77 -0
  135. package/dist/providers/types.d.ts +33 -0
  136. package/dist/reference-data/catalog.d.ts +131 -0
  137. package/dist/reference-data/catalog.js +348 -0
  138. package/dist/reference-data/receipt.d.ts +41 -0
  139. package/dist/reference-data/receipt.js +40 -0
  140. package/dist/runtime/boot.d.ts +2 -2
  141. package/dist/runtime/boot.js +1 -1
  142. package/dist/runtime/connection-budget.d.ts +2 -2
  143. package/dist/runtime/connection-budget.js +5 -3
  144. package/dist/runtime/dbos.d.ts +7 -7
  145. package/dist/runtime/dbos.js +14 -11
  146. package/dist/runtime/shutdown.d.ts +2 -2
  147. package/dist/runtime/shutdown.js +4 -3
  148. package/dist/sandbox/create-sandbox.d.ts +2 -2
  149. package/dist/sandbox/docker-client.d.ts +2 -2
  150. package/dist/sandbox/docker-client.js +4 -1
  151. package/dist/sandbox/k8s-client.d.ts +3 -0
  152. package/dist/sandbox/k8s-client.js +5 -4
  153. package/dist/sandbox/notification-sweep.d.ts +3 -3
  154. package/dist/sandbox/notification-sweep.js +4 -2
  155. package/dist/sandbox/reaper.d.ts +3 -3
  156. package/dist/sandbox/reaper.js +7 -4
  157. package/dist/sandbox/watchdog.d.ts +3 -3
  158. package/dist/sandbox/watchdog.js +8 -2
  159. package/dist/schemas/data-profile-schemas.js +5 -3
  160. package/dist/schemas/plan-schemas.d.ts +21 -21
  161. package/dist/schemas/plan-schemas.js +9 -3
  162. package/dist/schemas/run-synthesis.d.ts +8 -4
  163. package/dist/schemas/run-synthesis.js +92 -56
  164. package/dist/schemas/validate-plan.js +1 -1
  165. package/dist/schemas/workflow-state.d.ts +7 -1
  166. package/dist/schemas/workflow-state.js +33 -19
  167. package/dist/state/artifacts.d.ts +13 -0
  168. package/dist/state/artifacts.js +27 -0
  169. package/dist/state/data-profile.d.ts +64 -5
  170. package/dist/state/index.d.ts +3 -3
  171. package/dist/state/index.js +1 -1
  172. package/dist/state/init.d.ts +2 -1
  173. package/dist/state/init.js +5 -6
  174. package/dist/tasks/data-profile.d.ts +25 -2
  175. package/dist/tasks/data-profile.js +95 -39
  176. package/dist/tools/bio/chembl.d.ts +38 -0
  177. package/dist/tools/bio/chembl.js +138 -0
  178. package/dist/tools/bio/comptox.d.ts +50 -0
  179. package/dist/tools/bio/comptox.js +149 -0
  180. package/dist/tools/bio/get-impc-ko-profile.js +9 -6
  181. package/dist/tools/bio/index.d.ts +5 -17
  182. package/dist/tools/bio/index.js +5 -17
  183. package/dist/tools/bio/keys.d.ts +14 -9
  184. package/dist/tools/bio/keys.js +16 -16
  185. package/dist/tools/bio/opentargets.d.ts +38 -0
  186. package/dist/tools/bio/opentargets.js +89 -0
  187. package/dist/tools/bio/pubchem.d.ts +29 -0
  188. package/dist/tools/bio/pubchem.js +92 -0
  189. package/dist/tools/bio/pubmed.d.ts +51 -0
  190. package/dist/tools/bio/pubmed.js +100 -0
  191. package/dist/tools/bio/search-bgee-expression.js +12 -6
  192. package/dist/tools/bio/search-clinical-trials.js +16 -5
  193. package/dist/tools/bio/search-dgidb.d.ts +11 -1
  194. package/dist/tools/bio/search-dgidb.js +92 -78
  195. package/dist/tools/bio/search-faers.js +8 -4
  196. package/dist/tools/bio/search-geo-datasets.js +20 -6
  197. package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
  198. package/dist/tools/bio/search-pharmgkb.js +10 -4
  199. package/dist/tools/display/index.d.ts +1 -0
  200. package/dist/tools/display/index.js +1 -0
  201. package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
  202. package/dist/tools/display/normalize-echart-spec.js +211 -0
  203. package/dist/tools/display/show-user.d.ts +6 -1
  204. package/dist/tools/display/show-user.js +25 -12
  205. package/dist/tools/execute-plan.js +5 -2
  206. package/dist/tools/iterate-report.d.ts +171 -118
  207. package/dist/tools/iterate-report.js +336 -96
  208. package/dist/tools/lib/chembl-client.d.ts +12 -0
  209. package/dist/tools/lib/chembl-client.js +127 -0
  210. package/dist/tools/lib/ctx-ops.d.ts +224 -0
  211. package/dist/tools/lib/ctx-ops.js +450 -0
  212. package/dist/tools/lib/iuphar-client.js +10 -1
  213. package/dist/tools/lib/openfda-client.js +11 -3
  214. package/dist/tools/lib/pathway-client.js +10 -4
  215. package/dist/tools/lib/pubchem-ops.d.ts +74 -0
  216. package/dist/tools/lib/pubchem-ops.js +224 -0
  217. package/dist/tools/memory/update-working-memory.d.ts +45 -7
  218. package/dist/tools/memory/update-working-memory.js +111 -41
  219. package/dist/tools/report/index.d.ts +3 -2
  220. package/dist/tools/report/index.js +3 -2
  221. package/dist/tools/research/generate-analogy-report.js +25 -12
  222. package/dist/tools/research/generate-plan.d.ts +12 -5
  223. package/dist/tools/research/generate-plan.js +168 -37
  224. package/dist/tools/research/index.d.ts +1 -0
  225. package/dist/tools/research/index.js +1 -0
  226. package/dist/tools/research/inspect-data-profile.d.ts +85 -0
  227. package/dist/tools/research/inspect-data-profile.js +161 -0
  228. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  229. package/dist/tools/research/literature-reviewer.js +5 -6
  230. package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
  231. package/dist/tools/sandbox/list-available-packages.js +152 -10
  232. package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
  233. package/dist/tools/sandbox/list-available-refs.js +647 -134
  234. package/dist/tools/sandbox/report-blocker.d.ts +39 -9
  235. package/dist/tools/sandbox/report-blocker.js +43 -20
  236. package/dist/tools/workspace/execute-command.d.ts +3 -0
  237. package/dist/tools/workspace/execute-command.js +3 -1
  238. package/dist/tools/workspace/read-file.js +4 -5
  239. package/dist/tools/workspace/run-exec.d.ts +4 -4
  240. package/dist/tools/workspace/run-exec.js +4 -4
  241. package/dist/tools/workspace/show-file.js +10 -6
  242. package/dist/tools/workspace/show-plan.js +12 -6
  243. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  244. package/dist/tools/workspace/workspace-search.js +11 -5
  245. package/dist/workflows/execute-analysis.d.ts +46 -4
  246. package/dist/workflows/execute-analysis.js +118 -13
  247. package/dist/workflows/execute-target-assessment.d.ts +3 -0
  248. package/dist/workflows/execute-target-assessment.js +19 -15
  249. package/dist/workflows/sandbox-step.d.ts +9 -0
  250. package/dist/workflows/sandbox-step.js +39 -17
  251. package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
  252. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
  253. package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
  254. package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
  255. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
  256. package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
  257. package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
  258. package/dist/workflows/target-assessment/phase5-persist.js +8 -3
  259. package/dist/workflows/target-assessment/progress.d.ts +2 -1
  260. package/dist/workflows/target-assessment/progress.js +4 -3
  261. package/dist/workspace/filesystem.js +163 -74
  262. package/dist/workspace/paths.d.ts +2 -2
  263. package/dist/workspace/paths.js +2 -2
  264. package/package.json +95 -96
  265. package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
  266. package/dist/execution/filesystem-artifact-registry.js +0 -92
  267. package/dist/lib/identifiers.d.ts +0 -8
  268. package/dist/lib/identifiers.js +0 -10
  269. package/dist/lib/mime-detect.d.ts +0 -17
  270. package/dist/lib/mime-detect.js +0 -29
  271. package/dist/lib/streamed-download.d.ts +0 -28
  272. package/dist/lib/streamed-download.js +0 -111
  273. package/dist/lib/trace-context.d.ts +0 -11
  274. package/dist/lib/trace-context.js +0 -49
  275. package/dist/providers/llm-capabilities.d.ts +0 -33
  276. package/dist/providers/llm-capabilities.js +0 -58
  277. package/dist/schemas/render-step-prompt.d.ts +0 -32
  278. package/dist/schemas/render-step-prompt.js +0 -76
  279. package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
  280. package/dist/tools/bio/find-approval-precedent.js +0 -72
  281. package/dist/tools/bio/get-article-details.d.ts +0 -11
  282. package/dist/tools/bio/get-article-details.js +0 -20
  283. package/dist/tools/bio/get-article-full-text.d.ts +0 -19
  284. package/dist/tools/bio/get-article-full-text.js +0 -32
  285. package/dist/tools/bio/get-bioactivity.d.ts +0 -11
  286. package/dist/tools/bio/get-bioactivity.js +0 -23
  287. package/dist/tools/bio/get-drug-info.d.ts +0 -9
  288. package/dist/tools/bio/get-drug-info.js +0 -22
  289. package/dist/tools/bio/get-mechanism.d.ts +0 -8
  290. package/dist/tools/bio/get-mechanism.js +0 -18
  291. package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
  292. package/dist/tools/bio/get-pubchem-assays.js +0 -107
  293. package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
  294. package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
  295. package/dist/tools/bio/get-target-safety.d.ts +0 -18
  296. package/dist/tools/bio/get-target-safety.js +0 -26
  297. package/dist/tools/bio/search-compounds.d.ts +0 -22
  298. package/dist/tools/bio/search-compounds.js +0 -160
  299. package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
  300. package/dist/tools/bio/search-ctx-chemical.js +0 -130
  301. package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
  302. package/dist/tools/bio/search-ctx-exposure.js +0 -176
  303. package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
  304. package/dist/tools/bio/search-ctx-hazard.js +0 -178
  305. package/dist/tools/bio/search-opentargets.d.ts +0 -11
  306. package/dist/tools/bio/search-opentargets.js +0 -25
  307. package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
  308. package/dist/tools/bio/search-pubchem-compound.js +0 -110
  309. package/dist/tools/bio/search-pubmed.d.ts +0 -17
  310. package/dist/tools/bio/search-pubmed.js +0 -31
  311. package/dist/tools/bio/search-targets.d.ts +0 -9
  312. package/dist/tools/bio/search-targets.js +0 -20
  313. package/dist/tools/bio/search-toxcast.d.ts +0 -36
  314. package/dist/tools/bio/search-toxcast.js +0 -141
  315. package/dist/workflows/register-workflows.d.ts +0 -51
  316. package/dist/workflows/register-workflows.js +0 -48
  317. package/dist/workflows/resume-execute-analysis.d.ts +0 -70
  318. package/dist/workflows/resume-execute-analysis.js +0 -80
  319. package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
  320. package/dist/workflows/target-assessment/correction-loop.js +0 -62
  321. package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
  322. package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
  323. package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
  324. package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
  325. package/dist/workspace/mount-strategy.d.ts +0 -50
  326. package/dist/workspace/mount-strategy.js +0 -67
  327. package/dist/workspace/provenance-collector.d.ts +0 -34
  328. package/dist/workspace/provenance-collector.js +0 -16
  329. package/dist/workspace/workspace-paths.d.ts +0 -26
  330. package/dist/workspace/workspace-paths.js +0 -31
@@ -0,0 +1,211 @@
1
+ /**
2
+ * ECharts layout normalization — the renderer-side invariant behind `show_user(kind: "echart")`.
3
+ *
4
+ * The layout discipline (no duplicate title, bottom legend, grid margins that fit the axis labels,
5
+ * every category label shown, a downloadable chart) is applied HERE, deterministically, rather than
6
+ * being a checklist the model must remember on every chart it composes. A rule a model must remember
7
+ * is a rule it can forget.
8
+ *
9
+ * Two invariants govern every rule below:
10
+ *
11
+ * 1. **Defaults, not overrides.** Every property is filled in only when the author left it unset —
12
+ * an explicit, sensible authored `grid`/`legend`/`rotate`/filename is respected. The two forced
13
+ * fields are `title` (deleted: the `show_user` `title` param is the card heading, so an in-spec
14
+ * title is a genuine duplicate render) and `axisLabel.interval` (pinned to `0`: ECharts' default
15
+ * `"auto"` SILENTLY DROPS labels, which is the exact bug this rule exists to prevent).
16
+ * 2. **Never turn a valid chart invalid.** Nothing here is derived from data that may be absent: a
17
+ * property is only injected where it is meaningful (a `grid` only for a cartesian chart), and an
18
+ * unknown category count yields the safe layout rather than a guessed one.
19
+ *
20
+ * Pure: the input spec is never mutated. Modified branches are copy-on-write; untouched branches are
21
+ * shared by reference. `normalize(normalize(x))` deep-equals `normalize(x)`.
22
+ */
23
+ /** Max length of a derived `saveAsImage` filename. */
24
+ const MAX_FILENAME_LEN = 64;
25
+ /** Fallback download filename when no heading is available to derive one from. */
26
+ const DEFAULT_FILENAME = "chart";
27
+ function isRecord(value) {
28
+ return typeof value === "object" && value !== null && !Array.isArray(value);
29
+ }
30
+ /** The axis entries of an `xAxis`/`yAxis` option, which ECharts accepts as one object or an array. */
31
+ function axisEntries(axis) {
32
+ if (Array.isArray(axis))
33
+ return axis.filter(isRecord);
34
+ return isRecord(axis) ? [axis] : [];
35
+ }
36
+ /**
37
+ * Kebab-case download filename: `"HMGCR — VST Expression"` → `"hmgcr-vst-expression"`.
38
+ * Diacritics are folded, every other non-alphanumeric run collapses to a single dash.
39
+ */
40
+ export function kebabFilename(source) {
41
+ const slug = (source ?? "")
42
+ .normalize("NFKD")
43
+ .replace(/[\u0300-\u036f]/g, "")
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9]+/g, "-")
46
+ .slice(0, MAX_FILENAME_LEN)
47
+ .replace(/^-+|-+$/g, "");
48
+ return slug.length > 0 ? slug : DEFAULT_FILENAME;
49
+ }
50
+ /** Declared series count. `0` = none declared, which is also "nothing a legend could label". */
51
+ function seriesCount(spec) {
52
+ const series = spec.series;
53
+ if (Array.isArray(series))
54
+ return series.length;
55
+ return isRecord(series) ? 1 : 0;
56
+ }
57
+ /** True when any series maps its categories to dataset COLUMNS rather than rows. */
58
+ function laysOutByRow(spec) {
59
+ const series = Array.isArray(spec.series) ? spec.series : [spec.series];
60
+ return series.some((s) => isRecord(s) && s.seriesLayoutBy === "row");
61
+ }
62
+ /** Header rows to discount from a `dataset.source` array, per ECharts' `sourceHeader` rules. */
63
+ function headerRowCount(dataset, source) {
64
+ const declared = dataset.sourceHeader;
65
+ if (declared === true)
66
+ return 1;
67
+ if (declared === false)
68
+ return 0;
69
+ if (typeof declared === "number" && Number.isFinite(declared)) {
70
+ return Math.min(Math.max(Math.trunc(declared), 0), source.length);
71
+ }
72
+ // `"auto"` (the default): a leading row of all-strings in front of further rows is dimension names.
73
+ const first = source[0];
74
+ return source.length > 1 && Array.isArray(first) && first.length > 0 && first.every((cell) => typeof cell === "string") ? 1 : 0;
75
+ }
76
+ /**
77
+ * Category count on the x axis, or `null` when the spec cannot tell us.
78
+ *
79
+ * `null` is the honest answer for a CSV-backed chart (`show_user`'s `dataPath`): the rows are loaded
80
+ * by the host at render time and deliberately never pulled through the model's context window, so
81
+ * they are NOT in the spec. Guessing a rotation for an axis of unknown length is worse than not
82
+ * rotating — an unrotated axis is also exactly what the other `null` cases (a value axis: scatter,
83
+ * time series) want. The count is used ONLY to pick a label rotation; it never touches the data, so
84
+ * an off-by-one at a bucket boundary costs at most a rotation, never a rendering.
85
+ */
86
+ export function categoryCount(spec) {
87
+ for (const axis of axisEntries(spec.xAxis)) {
88
+ // An explicit category list is unambiguous — prefer it over any dataset inference.
89
+ if (Array.isArray(axis.data))
90
+ return axis.data.length;
91
+ }
92
+ // An array of datasets, an object-of-columns `source`, or a row-major layout: do not guess.
93
+ const dataset = spec.dataset;
94
+ if (!isRecord(dataset) || laysOutByRow(spec))
95
+ return null;
96
+ const source = dataset.source;
97
+ if (!Array.isArray(source) || source.length === 0)
98
+ return null;
99
+ return Math.max(0, source.length - headerRowCount(dataset, source));
100
+ }
101
+ /** The label rotation for `count` categories, or `null` for "leave the labels horizontal". */
102
+ function rotationFor(count) {
103
+ if (count === null || count <= 10)
104
+ return null;
105
+ // >20 categories: ECharts' own escape hatch is a `dataZoom` slider, but that rewrites the chart's
106
+ // interaction model and its vertical budget — a 90° turn is the mutation that cannot surprise.
107
+ return count <= 20 ? 45 : 90;
108
+ }
109
+ /** True when the legend (authored or defaulted) sits below the chart and consumes bottom margin. */
110
+ function isBottomLegend(legend) {
111
+ const entries = Array.isArray(legend) ? legend.filter(isRecord) : isRecord(legend) ? [legend] : [];
112
+ return entries.some((entry) => entry.show !== false && entry.bottom !== undefined);
113
+ }
114
+ /** True when a component option carries any explicit placement — adding another would fight it. */
115
+ function hasPlacement(option) {
116
+ return option.left !== undefined || option.right !== undefined || option.top !== undefined || option.bottom !== undefined;
117
+ }
118
+ /**
119
+ * Apply the ECharts layout invariant to an author-composed spec.
120
+ *
121
+ * - `title` is deleted (always) — the `show_user` `title` param is the visible card heading, and
122
+ * `title.subtext` is the documented misuse for stats that belong in the tooltip.
123
+ * - `legend` defaults to `{ bottom: 0 }` for a multi-series chart and `{ show: false }` for a
124
+ * single-series one (nothing to disambiguate); an authored legend is left alone.
125
+ * - `grid` (cartesian charts only) gets `top` 8% — 12% when a `graphic` annotation shares the canvas
126
+ * — `bottom` 20% / 25% (rotated labels) / 30% (rotated labels above a bottom legend), `left` 10%,
127
+ * `right` 5%. Only unset keys are filled.
128
+ * - `axisLabel.interval` is pinned to `0` on every axis; the x axis is rotated 45° (11–20 categories)
129
+ * or 90° (>20) unless the author chose a rotation.
130
+ * - `toolbox.feature.saveAsImage` is injected with `type: "png"` and a kebab-case filename derived
131
+ * from the `show_user` title (falling back to the stripped in-spec title, then `"chart"`).
132
+ */
133
+ export function normalizeEchartSpec(spec, opts = {}) {
134
+ const { title: specTitle, ...out } = spec;
135
+ // ── Legend ──────────────────────────────────────────────────────────
136
+ const legendAuthored = out.legend !== undefined && out.legend !== null;
137
+ if (!legendAuthored) {
138
+ const count = seriesCount(out);
139
+ if (count >= 2)
140
+ out.legend = { bottom: 0 };
141
+ // A single series has nothing to disambiguate: hide it explicitly rather than omit it, so a
142
+ // themed legend component cannot reintroduce an orphan swatch. An undeclared series count
143
+ // (0) gets no legend option at all — there is nothing to show or hide.
144
+ else if (count === 1)
145
+ out.legend = { show: false };
146
+ }
147
+ const bottomLegend = isBottomLegend(out.legend);
148
+ // ── Axis labels ─────────────────────────────────────────────────────
149
+ const rotate = rotationFor(categoryCount(out));
150
+ let rotated = false;
151
+ // Rotation is an x-axis rule: turning a horizontal bar chart's y-axis category labels on their
152
+ // side would be actively wrong, so only `interval` crosses to the y axis (where it is inert on a
153
+ // value axis and prevents silent label-skipping on a category one).
154
+ const normalizeAxis = (axis, isX) => {
155
+ const axisLabel = isRecord(axis.axisLabel) ? { ...axis.axisLabel } : {};
156
+ axisLabel.interval = 0;
157
+ if (isX && rotate !== null && axisLabel.rotate === undefined)
158
+ axisLabel.rotate = rotate;
159
+ if (isX && typeof axisLabel.rotate === "number" && axisLabel.rotate !== 0)
160
+ rotated = true;
161
+ return { ...axis, axisLabel };
162
+ };
163
+ for (const key of ["xAxis", "yAxis"]) {
164
+ const axis = out[key];
165
+ // A malformed entry is passed through rather than dropped — normalization rewrites layout,
166
+ // never the shape of the spec it was handed.
167
+ if (Array.isArray(axis))
168
+ out[key] = axis.map((entry) => (isRecord(entry) ? normalizeAxis(entry, key === "xAxis") : entry));
169
+ else if (isRecord(axis))
170
+ out[key] = normalizeAxis(axis, key === "xAxis");
171
+ }
172
+ // ── Grid ────────────────────────────────────────────────────────────
173
+ const cartesian = out.xAxis !== undefined || out.yAxis !== undefined;
174
+ const grid = out.grid;
175
+ // An array of grids, or a grid the author sized explicitly (`width`/`height`): a margin added on
176
+ // top of an explicit box could over-constrain it. Non-cartesian charts (pie, sunburst, graph)
177
+ // have no grid to lay out.
178
+ const gridNormalizable = cartesian && !Array.isArray(grid) && (grid === undefined || (isRecord(grid) && grid.width === undefined && grid.height === undefined));
179
+ if (gridNormalizable) {
180
+ const authored = isRecord(grid) ? grid : {};
181
+ out.grid = {
182
+ ...authored,
183
+ ...(authored.top === undefined ? { top: out.graphic !== undefined ? "12%" : "8%" } : {}),
184
+ ...(authored.bottom === undefined ? { bottom: rotated ? (bottomLegend ? "30%" : "25%") : "20%" } : {}),
185
+ ...(authored.left === undefined ? { left: "10%" } : {}),
186
+ ...(authored.right === undefined ? { right: "5%" } : {}),
187
+ };
188
+ }
189
+ // ── Toolbox ─────────────────────────────────────────────────────────
190
+ if (!Array.isArray(out.toolbox)) {
191
+ const authored = isRecord(out.toolbox) ? out.toolbox : {};
192
+ const feature = isRecord(authored.feature) ? authored.feature : {};
193
+ const saveAsImage = isRecord(feature.saveAsImage) ? feature.saveAsImage : {};
194
+ // The in-spec title is stripped, but it is still the author's own description of the chart —
195
+ // a better filename seed than the generic fallback when no card title was given.
196
+ const specTitleText = isRecord(specTitle) && typeof specTitle.text === "string" ? specTitle.text : undefined;
197
+ out.toolbox = {
198
+ ...authored,
199
+ ...(hasPlacement(authored) ? {} : { right: 0, top: 0 }),
200
+ feature: {
201
+ ...feature,
202
+ saveAsImage: {
203
+ ...saveAsImage,
204
+ ...(saveAsImage.type === undefined ? { type: "png" } : {}),
205
+ ...(saveAsImage.name === undefined ? { name: kebabFilename(opts.title ?? specTitleText) } : {}),
206
+ },
207
+ },
208
+ };
209
+ }
210
+ return out;
211
+ }
@@ -9,6 +9,11 @@
9
9
  * downstream reconciliation concern of the emit pipeline (change 3), not a
10
10
  * per-request `Set` smuggled through an ambient context bag.
11
11
  *
12
+ * An `echart` spec's layout is normalized (`normalize-echart-spec.ts`) inside
13
+ * `buildPresentationCardData`, the one construction site both this tool and the
14
+ * reconstruct-on-read path share — the model authors the data, the renderer owns
15
+ * the layout.
16
+ *
12
17
  * For stored plans, use `show_plan`. For existing files, use `show_file`.
13
18
  */
14
19
  type ShowUserOutput = {
@@ -18,7 +23,7 @@ type ShowUserOutput = {
18
23
  id: string;
19
24
  };
20
25
  export declare const showUserTool: import("../define-tool.js").Tool<{
21
- kind: "code" | "echart" | "markdown" | "svg" | "table";
26
+ kind: "code" | "markdown" | "echart" | "svg" | "table";
22
27
  title?: string | undefined;
23
28
  spec?: Record<string, unknown> | undefined;
24
29
  dataPath?: string | undefined;
@@ -9,6 +9,11 @@
9
9
  * downstream reconciliation concern of the emit pipeline (change 3), not a
10
10
  * per-request `Set` smuggled through an ambient context bag.
11
11
  *
12
+ * An `echart` spec's layout is normalized (`normalize-echart-spec.ts`) inside
13
+ * `buildPresentationCardData`, the one construction site both this tool and the
14
+ * reconstruct-on-read path share — the model authors the data, the renderer owns
15
+ * the layout.
16
+ *
12
17
  * For stored plans, use `show_plan`. For existing files, use `show_file`.
13
18
  */
14
19
  import { ok } from "neverthrow";
@@ -22,12 +27,18 @@ import { validatePath } from "../lib/path-validation.js";
22
27
  // LLM picks the right ones based on `kind`.
23
28
  const ShowUserInputSchema = z.object({
24
29
  kind: z.enum(["echart", "markdown", "code", "svg", "table"]).describe("The type of agent-synthesized content to display"),
25
- title: z.string().optional().describe("Title shown above the content"),
26
- spec: z.record(z.string(), z.unknown()).optional().describe("Full ECharts option spec as JSON (kind=echart)"),
30
+ title: z
31
+ .string()
32
+ .optional()
33
+ .describe("Card heading rendered above the content. For an echart it is the chart's only title, and it seeds the PNG download filename."),
34
+ spec: z
35
+ .record(z.string(), z.unknown())
36
+ .optional()
37
+ .describe("Full ECharts option JSON (kind=echart). Author the data, series, `encode`, and tooltip — and OMIT `title`: the card heading is the `title` param, so an in-spec title is stripped (put stats in `tooltip.formatter`, never in `title.subtext`). Layout is normalized for you at render time (legend placement, grid margins, `axisLabel.interval`/rotation, and a PNG `saveAsImage` toolbox), so do not hand-tune it."),
27
38
  dataPath: z
28
39
  .string()
29
40
  .optional()
30
- .describe("echart only: analysis-rooted CSV artifact path (e.g. 'runs/run-abc/step-2/output/de-summary.csv') the host loads as the ECharts `dataset.source` at render time. Author `encode`/dimensions against the CSV's column names and omit `dataset.source` from the spec — the data is never pulled through the context window."),
41
+ .describe("echart only: analysis-rooted CSV artifact path (e.g. 'runs/run-abc/step-2/output/de-summary.csv') the host loads as the ECharts `dataset.source` at render time. Author `encode`/dimensions against the CSV's column names (read only its header) and omit `dataset.source` from the spec — the rows are never pulled through the context window. If the data is not chart-ready (needs aggregation, filtering, reshaping), do that in a sandbox step that writes a chart-ready CSV. Inline `dataset.source` is only for a handful of numbers you just computed in conversation that exist nowhere as an artifact."),
31
42
  body: z.string().optional().describe("Markdown content to render (kind=markdown)"),
32
43
  code: z.string().optional().describe("Code content (kind=code)"),
33
44
  language: z.string().optional().describe("Language identifier, e.g. r, python, bash (kind=code)"),
@@ -38,15 +49,17 @@ const ShowUserInputSchema = z.object({
38
49
  });
39
50
  export const showUserTool = defineTool({
40
51
  id: "show_user",
41
- description: "Display agent-synthesized content to the user: ECharts visualizations, " +
42
- "markdown, code blocks, SVG diagrams, or tables. " +
43
- "Use for content you are INVENTING (a chart you just built, a code snippet you are proposing, " +
44
- "a markdown synthesis, a table you constructed). " +
45
- "For an echart backed by a chart-ready CSV artifact a step already wrote, pass `dataPath` (analysis-rooted) " +
46
- "and author `encode`/dimensions by column name with no `dataset.source` — the host loads the CSV as the " +
47
- "dataset at render time, so you never inline the rows. " +
48
- "For stored plans, use `show_plan`. For existing analysis files (images, CSVs, etc.), use `show_file`. " +
49
- "Call once per distinct content item; an identical repeat call resolves to the same card.",
52
+ description: "Display content you are INVENTING — a chart you composed, a code snippet you are proposing, a markdown " +
53
+ "synthesis, an SVG diagram, a table you built. The content is inlined on the wire. " +
54
+ "Pick this tool by what you are referencing, not by how the output looks: " +
55
+ "NOT for an existing analysis file — never read an artifact and paste its bytes here, reference it with `show_file` " +
56
+ "(images, CSVs, PDFs, notebooks). NOT for a stored plan — use `show_plan`. " +
57
+ "NEVER put markdown image syntax pointing at a workspace file in a `markdown` body: `![](runs/.../volcano.png)` " +
58
+ "renders as a broken image, because the chat UI cannot resolve workspace-relative paths. To interleave figures " +
59
+ "with prose (a write-up, a narrative summary), emit a SEQUENCE of cards in display order — `show_user(markdown)` " +
60
+ "for a prose section, then `show_file` for the figures that follow it, and so on — instead of one monolithic card. " +
61
+ "One card per call, rendered in call order; an identical repeat call resolves to the same card, so compose the " +
62
+ "finished content before calling rather than re-rendering a draft.",
50
63
  inputSchema: ShowUserInputSchema,
51
64
  execute: async (input, ctx) => {
52
65
  // `dataPath` is a reference the host resolves at render time; validate its
@@ -32,7 +32,6 @@ import { buildRunCardData } from "../memory/card-builders.js";
32
32
  import { unwrapOrThrow } from "../lib/result.js";
33
33
  import { validatePlan } from "../schemas/validate-plan.js";
34
34
  import { AnalysisPlanSchema } from "../schemas/workflow-state.js";
35
- import { renderStepPrompt } from "../schemas/render-step-prompt.js";
36
35
  import { RunDedupCollisionError, insertRun, loadPlan, queryActiveRun, updateRunStatus } from "../state/index.js";
37
36
  import { defineTool } from "./define-tool.js";
38
37
  const planIdSchema = z.string().regex(/^pln-[a-f0-9]{8}$/, "planId must be a pln-<8hex> value");
@@ -177,7 +176,11 @@ export function createExecutePlanTool(deps) {
177
176
  id: s.id,
178
177
  depends_on: s.depends_on ?? [],
179
178
  })),
180
- promptByStepId: Object.fromEntries(plan.steps.map((s) => [s.id, renderStepPrompt(s)])),
179
+ // The step DATA, not a rendered prompt: each step's seed is composed
180
+ // by the scheduler at dispatch, when its dependencies' results exist.
181
+ // A prompt frozen here — before the first step has run — could never
182
+ // mention them.
183
+ planStepById: Object.fromEntries(plan.steps.map((s) => [s.id, s])),
181
184
  agentByStepId: Object.fromEntries(plan.steps.map((s) => [s.id, s.agent ?? "unknown"])),
182
185
  resourcesByStepId: Object.fromEntries(plan.steps.map((s) => {
183
186
  if (!s.resources) {
@@ -1,23 +1,43 @@
1
1
  /**
2
- * iterate_report — creates or iterates on a report.
2
+ * Report authoring tools for the conversation agent: `plan_report` + `submit_report`.
3
3
  *
4
- * Two modes:
5
- * - Creation (v1): pass `report` with title/audience/sources/sections.
6
- * Pre-flight stages every source into the preview's assets/ dir,
7
- * enriches the brief with kind/size/columns/headRows/rowCount, and
8
- * hands the report-builder agent a complete brief — no discovery, no
9
- * staging in the LLM loop.
10
- * - Iteration (v2+): pass `modifications` (natural language) with the
11
- * existing `previewId`. Optional top-level `sources` adds new assets
12
- * on top of the existing assets/ dir.
4
+ * WHY TWO TOOLS. The report brief (title/audience/sources plus the section
5
+ * union — narrative/metrics/figure/table/chart, each with its own
6
+ * encoding/transform/asset sub-fields) is a ~12k-char JSON schema. Every
7
+ * registered tool's schema ships on EVERY conversation turn, so parking that
8
+ * brief on an always-on tool would tax every turn for a tool used only when a
9
+ * report is actually built. We split the single former tool in two and deliver
10
+ * the heavy schema just-in-time:
13
11
  *
14
- * Emits a `data-report-preview` chat data part on success or `data-report-preview-failed`
15
- * on pre-flight or builder failure. The hosted preview surface is reached
16
- * through an injected `PreviewPublisher` seam (managed default mints a
17
- * short-lived run-authorization grant; local default returns "unavailable").
12
+ * - `plan_report` — a tiny, always-on trigger (no-arg). Its `execute` RETURNS
13
+ * the brief schema (`z.toJSONSchema(ReportBriefSchema)`) plus the authoring
14
+ * rules as its tool RESULT, so the full contract enters context only on a
15
+ * report-building turn — and thereafter rides in the cached history prefix,
16
+ * not at full price on every request.
17
+ * - `submit_report` — takes the composed brief and does the real work
18
+ * (pre-flight staging, previewId/versioning, `runReportIteration`, preview
19
+ * publishing). It accepts the brief as `unknown` so the 12k schema stays off
20
+ * the always-on surface, then validates it against `ReportBriefSchema` INSIDE
21
+ * `execute`, returning `{ ok: false, issues }` as DATA the model can fix (not
22
+ * a thrown error). That is the same trade `validate_plan` makes: a runtime
23
+ * contract in exchange for keeping the schema out of every request. The small
24
+ * iterate-mode + common fields (modifications / previewId / baseVersion /
25
+ * sources / format) stay fully typed — they cost little and guide iteration.
18
26
  *
19
- * The 4 custom report tools the builder drives are constructed inside
20
- * `runReportIteration` — see design.md Decision #9.
27
+ * On a valid brief, `submit_report`'s behaviour is identical to the former
28
+ * `iterate_report`: two modes (Create v1 via `report`; Iterate v2+ via
29
+ * `modifications` + `previewId`), pre-flight stages every source into the
30
+ * preview's assets/ dir, and it emits a `data-report-preview` chat data part on
31
+ * success or `data-report-preview-failed` on pre-flight/builder failure. The
32
+ * hosted preview surface is reached through an injected `PreviewPublisher` seam
33
+ * (managed default mints a short-lived run-authorization grant; local default
34
+ * returns "unavailable"). The 4 custom report tools the builder drives are
35
+ * constructed inside `runReportIteration` — see design.md Decision #9.
36
+ *
37
+ * Naming note: the report-builder SUB-agent has its OWN same-id `submit_report`
38
+ * terminal tool (`tools/report/submit-report.ts`). The two never share a tool
39
+ * roster — this one is the conversation agent's brief-submission entry point;
40
+ * that one is the builder's finalize gate.
21
41
  */
22
42
  import type { Pool } from "pg";
23
43
  import { z } from "zod";
@@ -27,119 +47,145 @@ import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
27
47
  import { type StagedAsset } from "./lib/report-preflight.js";
28
48
  import type { PreviewPublisher } from "./report/preview-publisher.js";
29
49
  import type { ChromeConfig } from "../lib/chrome.js";
30
- /** Exported for schema-validation tests — the underlying Zod schema. */
31
- export declare const iterateReportInputSchema: z.ZodObject<{
50
+ import type { Logger } from "../lib/logger.js";
51
+ /**
52
+ * The report brief the conversation agent composes: title/audience + sources +
53
+ * the section union. This is the ~12k-char schema that `plan_report` hands back
54
+ * just-in-time and that `submit_report` validates its `report` field against
55
+ * inside `execute`. Exported for `plan_report`, `submit_report`, and the
56
+ * brief-shape tests.
57
+ */
58
+ export declare const ReportBriefSchema: z.ZodObject<{
59
+ title: z.ZodString;
60
+ audience: z.ZodString;
61
+ styleGuidance: z.ZodOptional<z.ZodString>;
62
+ sources: z.ZodDefault<z.ZodArray<z.ZodObject<{
63
+ path: z.ZodString;
64
+ as: z.ZodOptional<z.ZodString>;
65
+ }, z.core.$strip>>>;
66
+ sections: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
67
+ type: z.ZodLiteral<"narrative">;
68
+ title: z.ZodString;
69
+ intent: z.ZodString;
70
+ content: z.ZodObject<{
71
+ prose: z.ZodString;
72
+ }, z.core.$strip>;
73
+ }, z.core.$strip>, z.ZodObject<{
74
+ type: z.ZodLiteral<"metrics">;
75
+ title: z.ZodString;
76
+ intent: z.ZodString;
77
+ content: z.ZodObject<{
78
+ stats: z.ZodArray<z.ZodObject<{
79
+ label: z.ZodString;
80
+ value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
81
+ unit: z.ZodOptional<z.ZodString>;
82
+ }, z.core.$strip>>;
83
+ }, z.core.$strip>;
84
+ }, z.core.$strip>, z.ZodObject<{
85
+ type: z.ZodLiteral<"figure">;
86
+ title: z.ZodString;
87
+ intent: z.ZodString;
88
+ content: z.ZodObject<{
89
+ imageAsset: z.ZodString;
90
+ caption: z.ZodOptional<z.ZodString>;
91
+ }, z.core.$strip>;
92
+ }, z.core.$strip>, z.ZodObject<{
93
+ type: z.ZodLiteral<"table">;
94
+ title: z.ZodString;
95
+ intent: z.ZodString;
96
+ content: z.ZodObject<{
97
+ dataAsset: z.ZodString;
98
+ columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
99
+ topN: z.ZodOptional<z.ZodNumber>;
100
+ sortBy: z.ZodOptional<z.ZodObject<{
101
+ column: z.ZodString;
102
+ order: z.ZodEnum<{
103
+ desc: "desc";
104
+ asc: "asc";
105
+ }>;
106
+ }, z.core.$strip>>;
107
+ transform: z.ZodOptional<z.ZodString>;
108
+ caption: z.ZodOptional<z.ZodString>;
109
+ }, z.core.$strip>;
110
+ }, z.core.$strip>, z.ZodObject<{
111
+ type: z.ZodLiteral<"chart">;
112
+ title: z.ZodString;
113
+ intent: z.ZodString;
114
+ content: z.ZodObject<{
115
+ dataAsset: z.ZodOptional<z.ZodString>;
116
+ data: z.ZodOptional<z.ZodObject<{
117
+ columns: z.ZodArray<z.ZodString>;
118
+ rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodNull]>>>;
119
+ source: z.ZodString;
120
+ }, z.core.$strip>>;
121
+ chartType: z.ZodEnum<{
122
+ line: "line";
123
+ bar: "bar";
124
+ scatter: "scatter";
125
+ histogram: "histogram";
126
+ box: "box";
127
+ heatmap: "heatmap";
128
+ pie: "pie";
129
+ }>;
130
+ encoding: z.ZodObject<{
131
+ x: z.ZodOptional<z.ZodString>;
132
+ y: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
133
+ group: z.ZodOptional<z.ZodString>;
134
+ value: z.ZodOptional<z.ZodString>;
135
+ }, z.core.$strip>;
136
+ topN: z.ZodOptional<z.ZodNumber>;
137
+ sortBy: z.ZodOptional<z.ZodObject<{
138
+ column: z.ZodString;
139
+ order: z.ZodEnum<{
140
+ desc: "desc";
141
+ asc: "asc";
142
+ }>;
143
+ }, z.core.$strip>>;
144
+ transform: z.ZodOptional<z.ZodString>;
145
+ caption: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strip>;
147
+ }, z.core.$strip>, z.ZodObject<{
148
+ type: z.ZodLiteral<"methods">;
149
+ title: z.ZodString;
150
+ intent: z.ZodString;
151
+ content: z.ZodObject<{
152
+ prose: z.ZodString;
153
+ }, z.core.$strip>;
154
+ }, z.core.$strip>], "type">>;
155
+ }, z.core.$strip>;
156
+ /**
157
+ * `submit_report`'s always-on input surface. The heavy brief rides in `report`
158
+ * as `unknown` — typed that way ON PURPOSE so the ~12k `ReportBriefSchema` does
159
+ * NOT ship on every conversation turn; `execute` validates it against
160
+ * `ReportBriefSchema` and returns issues as data. The iterate-mode + common
161
+ * fields stay fully typed (they are small and guide iteration). Exported for
162
+ * the envelope-validation tests.
163
+ */
164
+ export declare const submitReportInputSchema: z.ZodObject<{
32
165
  previewId: z.ZodOptional<z.ZodString>;
33
166
  baseVersion: z.ZodOptional<z.ZodNumber>;
34
167
  format: z.ZodDefault<z.ZodEnum<{
35
168
  html: "html";
36
169
  pdf: "pdf";
37
170
  }>>;
38
- report: z.ZodOptional<z.ZodObject<{
39
- title: z.ZodString;
40
- audience: z.ZodString;
41
- styleGuidance: z.ZodOptional<z.ZodString>;
42
- sources: z.ZodDefault<z.ZodArray<z.ZodObject<{
43
- path: z.ZodString;
44
- as: z.ZodOptional<z.ZodString>;
45
- }, z.core.$strip>>>;
46
- sections: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
47
- type: z.ZodLiteral<"narrative">;
48
- title: z.ZodString;
49
- intent: z.ZodString;
50
- content: z.ZodObject<{
51
- prose: z.ZodString;
52
- }, z.core.$strip>;
53
- }, z.core.$strip>, z.ZodObject<{
54
- type: z.ZodLiteral<"metrics">;
55
- title: z.ZodString;
56
- intent: z.ZodString;
57
- content: z.ZodObject<{
58
- stats: z.ZodArray<z.ZodObject<{
59
- label: z.ZodString;
60
- value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
61
- unit: z.ZodOptional<z.ZodString>;
62
- }, z.core.$strip>>;
63
- }, z.core.$strip>;
64
- }, z.core.$strip>, z.ZodObject<{
65
- type: z.ZodLiteral<"figure">;
66
- title: z.ZodString;
67
- intent: z.ZodString;
68
- content: z.ZodObject<{
69
- imageAsset: z.ZodString;
70
- caption: z.ZodOptional<z.ZodString>;
71
- }, z.core.$strip>;
72
- }, z.core.$strip>, z.ZodObject<{
73
- type: z.ZodLiteral<"table">;
74
- title: z.ZodString;
75
- intent: z.ZodString;
76
- content: z.ZodObject<{
77
- dataAsset: z.ZodString;
78
- columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
79
- topN: z.ZodOptional<z.ZodNumber>;
80
- sortBy: z.ZodOptional<z.ZodObject<{
81
- column: z.ZodString;
82
- order: z.ZodEnum<{
83
- desc: "desc";
84
- asc: "asc";
85
- }>;
86
- }, z.core.$strip>>;
87
- transform: z.ZodOptional<z.ZodString>;
88
- caption: z.ZodOptional<z.ZodString>;
89
- }, z.core.$strip>;
90
- }, z.core.$strip>, z.ZodObject<{
91
- type: z.ZodLiteral<"chart">;
92
- title: z.ZodString;
93
- intent: z.ZodString;
94
- content: z.ZodObject<{
95
- dataAsset: z.ZodOptional<z.ZodString>;
96
- data: z.ZodOptional<z.ZodObject<{
97
- columns: z.ZodArray<z.ZodString>;
98
- rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodNull]>>>;
99
- source: z.ZodString;
100
- }, z.core.$strip>>;
101
- chartType: z.ZodEnum<{
102
- line: "line";
103
- bar: "bar";
104
- scatter: "scatter";
105
- histogram: "histogram";
106
- box: "box";
107
- heatmap: "heatmap";
108
- pie: "pie";
109
- }>;
110
- encoding: z.ZodObject<{
111
- x: z.ZodOptional<z.ZodString>;
112
- y: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
113
- group: z.ZodOptional<z.ZodString>;
114
- value: z.ZodOptional<z.ZodString>;
115
- }, z.core.$strip>;
116
- topN: z.ZodOptional<z.ZodNumber>;
117
- sortBy: z.ZodOptional<z.ZodObject<{
118
- column: z.ZodString;
119
- order: z.ZodEnum<{
120
- desc: "desc";
121
- asc: "asc";
122
- }>;
123
- }, z.core.$strip>>;
124
- transform: z.ZodOptional<z.ZodString>;
125
- caption: z.ZodOptional<z.ZodString>;
126
- }, z.core.$strip>;
127
- }, z.core.$strip>, z.ZodObject<{
128
- type: z.ZodLiteral<"methods">;
129
- title: z.ZodString;
130
- intent: z.ZodString;
131
- content: z.ZodObject<{
132
- prose: z.ZodString;
133
- }, z.core.$strip>;
134
- }, z.core.$strip>], "type">>;
135
- }, z.core.$strip>>;
171
+ report: z.ZodOptional<z.ZodUnknown>;
136
172
  modifications: z.ZodOptional<z.ZodString>;
137
173
  sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
138
174
  path: z.ZodString;
139
175
  as: z.ZodOptional<z.ZodString>;
140
176
  }, z.core.$strip>>>;
141
177
  }, z.core.$strip>;
142
- export interface IterateReportDeps {
178
+ /**
179
+ * `plan_report` — the tiny, always-on trigger (no args). Its result delivers
180
+ * the brief schema + authoring rules just-in-time, so the ~12k contract enters
181
+ * context only on a report-building turn (and thereafter rides in the cached
182
+ * history prefix). Compose the brief from what it returns, then call
183
+ * `submit_report`. Pure logic, no deps — hence a module-scope leaf tool.
184
+ */
185
+ export declare const planReportTool: Tool;
186
+ export interface SubmitReportDeps {
187
+ /** Operational logging seam; omitted falls back to no-op. */
188
+ readonly logger?: Logger;
143
189
  readonly provider: ChatProvider;
144
190
  readonly pool: Pool;
145
191
  /** Embedder-supplied workspace-root resolution seam (see workspace/paths.ts). */
@@ -165,7 +211,14 @@ export interface IterateReportDeps {
165
211
  ttlSeconds: number;
166
212
  }) => Promise<PreviewPublisher>;
167
213
  }
168
- export declare function createIterateReportTool(deps: IterateReportDeps): Tool;
214
+ /**
215
+ * `submit_report` — takes the composed brief and drives the report iteration.
216
+ * On a valid brief its behaviour is byte-for-byte the former `iterate_report`:
217
+ * pre-flight staging, previewId/versioning, `runReportIteration`, preview
218
+ * publishing. The brief rides as `unknown` on the wire (keeping its ~12k schema
219
+ * off the always-on surface) and is validated against `ReportBriefSchema` here.
220
+ */
221
+ export declare function createReportSubmitTool(deps: SubmitReportDeps): Tool;
169
222
  /** Exported for unit tests — pipe-escape correctness. */
170
223
  export declare function stagedAssetsBlock(staged: StagedAsset[], opts?: {
171
224
  newOnly?: boolean;