@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
@@ -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 { ok } from "neverthrow";
23
43
  import { z } from "zod";
@@ -29,6 +49,7 @@ import { defineTool } from "./define-tool.js";
29
49
  import { previewDir } from "../workspace/paths.js";
30
50
  import { runReportIteration } from "../execution/report-runner.js";
31
51
  import { formatBytes, stageReportAssets } from "./lib/report-preflight.js";
52
+ import { createNoopLogger } from "../lib/console-logger.js";
32
53
  const REPORT_TOOL_ACCESS_TTL_SECONDS = 3600;
33
54
  const PREVIEW_META_FILE = "preview-meta.json";
34
55
  async function readPreviewMeta(metaPath) {
@@ -68,7 +89,7 @@ const SourceSchema = z.object({
68
89
  const StatItemSchema = z.object({
69
90
  label: z.string(),
70
91
  value: z.union([z.string(), z.number()]),
71
- unit: z.string().optional(),
92
+ unit: z.string().optional().describe("Unit rendered next to the value (e.g. '%', 'genes', 'kb')."),
72
93
  });
73
94
  const SortSpecSchema = z.object({
74
95
  column: z.string(),
@@ -76,97 +97,180 @@ const SortSpecSchema = z.object({
76
97
  });
77
98
  const ChartTypeSchema = z.enum(["bar", "line", "scatter", "histogram", "box", "heatmap", "pie"]);
78
99
  const ChartEncodingSchema = z.object({
79
- x: z.string().optional(),
80
- y: z.union([z.string(), z.array(z.string()).min(1)]).optional(),
81
- group: z.string().optional(),
82
- value: z.string().optional(),
100
+ x: z.string().optional().describe("Column on the x / category axis."),
101
+ y: z
102
+ .union([z.string(), z.array(z.string()).min(1)])
103
+ .optional()
104
+ .describe("Column on the y axis; an array plots one series per column."),
105
+ group: z.string().optional().describe("Column to split series (and colour) by."),
106
+ value: z.string().optional().describe("Value column for pie / heatmap-shaped charts."),
83
107
  });
84
108
  const INLINE_CHART_ROW_CAP = 500;
85
109
  const ChartInlineDataSchema = z.object({
86
- columns: z.array(z.string()).min(1),
110
+ columns: z.array(z.string()).min(1).describe("Column names of the inline rows, in render order."),
87
111
  rows: z
88
112
  .array(z.record(z.string(), z.union([z.string(), z.number(), z.null()])))
89
113
  .min(1)
90
- .max(INLINE_CHART_ROW_CAP),
114
+ .max(INLINE_CHART_ROW_CAP)
115
+ .describe("The pre-computed rows. Every value must come from a file you actually read — never fabricate, never estimate."),
91
116
  source: z
92
117
  .string()
93
118
  .min(1)
94
- .describe("Short free-text describing where the values came from (e.g. " +
95
- "'aggregated significant-gene counts from runs/r1/.../sig.csv, " +
96
- "runs/r2/.../sig.csv, runs/r3/.../sig.csv'). The builder renders " +
97
- "this as a footnote under the chart."),
119
+ .describe("Where the values came from — cite the real files (e.g. 'aggregated " +
120
+ "significant-gene counts from runs/r1/.../sig.csv, runs/r2/.../sig.csv'). " +
121
+ "The builder renders this verbatim as a footnote under the chart."),
98
122
  });
123
+ /**
124
+ * The editorial-emphasis channel every section carries. A fresh instance per
125
+ * section type — Zod inlines reused schemas rather than sharing a `$defs` ref.
126
+ */
127
+ const intentField = () => z
128
+ .string()
129
+ .describe("Why this section exists and what to emphasize (e.g. 'hero — headline " +
130
+ "finding', 'downplay if space is tight'). Emphasis, not layout — the " +
131
+ "builder picks the components, sizing and alternation.");
132
+ /** Free-text row transform — the reason to never pre-slice a CSV with `run_ephemeral`. */
133
+ const transformField = () => z
134
+ .string()
135
+ .optional()
136
+ .describe("Free-text row transform the builder applies client-side to the loaded " +
137
+ "asset — filter, derive, aggregate, sort (e.g. 'filter padj < 0.05 and " +
138
+ "abs(log2FoldChange) > 1', 'compute -log10(padj) as neg_log_padj', 'group " +
139
+ "by sample and sum count', 'sort by mean_count desc, take top 50'). Use " +
140
+ "this instead of running `run_ephemeral` to pre-slice the file. The text " +
141
+ "is rendered verbatim as a provenance footnote, so the data reads as " +
142
+ "processed, not fabricated.");
99
143
  const SectionSchema = z.discriminatedUnion("type", [
100
- z.object({
144
+ z
145
+ .object({
101
146
  type: z.literal("narrative"),
102
147
  title: z.string(),
103
- intent: z.string(),
104
- content: z.object({ prose: z.string().min(1) }),
105
- }),
106
- z.object({
148
+ intent: intentField(),
149
+ content: z.object({
150
+ prose: z.string().min(1).describe("Markdown prose you wrote. The builder never opens your data files to write it."),
151
+ }),
152
+ })
153
+ .describe("Prose you wrote — context, story, interpretation of the results."),
154
+ z
155
+ .object({
107
156
  type: z.literal("metrics"),
108
157
  title: z.string(),
109
- intent: z.string(),
110
- content: z.object({ stats: z.array(StatItemSchema).min(1) }),
111
- }),
112
- z.object({
158
+ intent: intentField(),
159
+ content: z.object({
160
+ stats: z.array(StatItemSchema).min(1).describe("Headline numbers you already derived — the builder does not compute them."),
161
+ }),
162
+ })
163
+ .describe("Labeled numbers you extracted — headline stats, at-a-glance summary."),
164
+ z
165
+ .object({
113
166
  type: z.literal("figure"),
114
167
  title: z.string(),
115
- intent: z.string(),
168
+ intent: intentField(),
116
169
  content: z.object({
117
- imageAsset: z.string().min(1),
170
+ imageAsset: z.string().min(1).describe("Staged asset name, not a path — a `sources[].as`, or the basename of a `sources[].path`."),
118
171
  caption: z.string().optional(),
119
172
  }),
120
- }),
121
- z.object({
173
+ })
174
+ .describe("A static image the analysis already produced (PNG/SVG). Prefer `chart` " +
175
+ "whenever the underlying data file exists — a chart is interactive, themed " +
176
+ "and re-encodable on iteration. Reach for `figure` only when the user asks " +
177
+ "for the existing image, the visual is genuinely image-only (e.g. a heatmap " +
178
+ "baked by an R script with no exportable data), or the image carries " +
179
+ "annotations the data does not."),
180
+ z
181
+ .object({
122
182
  type: z.literal("table"),
123
183
  title: z.string(),
124
- intent: z.string(),
184
+ intent: intentField(),
125
185
  content: z.object({
126
- dataAsset: z.string().min(1),
127
- columns: z.array(z.string()).optional(),
128
- topN: z.number().int().positive().optional(),
129
- sortBy: SortSpecSchema.optional(),
130
- transform: z.string().optional(),
186
+ dataAsset: z.string().min(1).describe("Staged asset name, not a path — `.csv`, `.tsv`, or `.json` (array of row objects)."),
187
+ columns: z.array(z.string()).optional().describe("Column subset to render, in order. Omit to render every column."),
188
+ topN: z.number().int().positive().optional().describe("Keep only the first N rows, applied after `sortBy` and `transform`."),
189
+ sortBy: SortSpecSchema.optional().describe("Sort applied before `topN`."),
190
+ transform: transformField(),
131
191
  caption: z.string().optional(),
132
192
  }),
133
- }),
134
- z.object({
193
+ })
194
+ .describe("A tabular asset rendered as a table. Pick a column subset and `topN` that fit the audience."),
195
+ z
196
+ .object({
135
197
  type: z.literal("chart"),
136
198
  title: z.string(),
137
- intent: z.string(),
199
+ intent: intentField(),
138
200
  content: z
139
201
  .object({
140
- dataAsset: z.string().min(1).optional(),
141
- data: ChartInlineDataSchema.optional(),
142
- chartType: ChartTypeSchema,
143
- encoding: ChartEncodingSchema,
144
- topN: z.number().int().positive().optional(),
145
- sortBy: SortSpecSchema.optional(),
146
- transform: z.string().optional(),
202
+ dataAsset: z
203
+ .string()
204
+ .min(1)
205
+ .optional()
206
+ .describe("Staged asset name, not a path — `.csv`, `.tsv`, or `.json` (an array " +
207
+ "of row objects, or a pre-built ECharts option object the builder " +
208
+ "hands straight to `setOption`)."),
209
+ data: ChartInlineDataSchema.optional().describe("Escape hatch: inline rows for a cross-file aggregate you computed " +
210
+ "yourself from analysis files you ACTUALLY read (e.g. significant-gene " +
211
+ "counts pulled from several runs). Prefer `dataAsset`. More than one " +
212
+ "inline-data chart per report is a smell — the aggregate belongs in a " +
213
+ "persisted derived CSV."),
214
+ chartType: ChartTypeSchema.describe("Editorial choice — bar vs scatter changes the story. Always required, " + "including for a pre-built ECharts option asset."),
215
+ encoding: ChartEncodingSchema.describe("Which column feeds which channel, resolved AFTER `transform` — it may " +
216
+ "name a column the transform derives. Always required: pass `{}` when " +
217
+ "`dataAsset` is a pre-built ECharts option object."),
218
+ topN: z.number().int().positive().optional().describe("Keep only the first N rows, applied after `sortBy` and `transform`."),
219
+ sortBy: SortSpecSchema.optional().describe("Sort applied before `topN`."),
220
+ transform: transformField(),
147
221
  caption: z.string().optional(),
148
222
  })
149
223
  .refine((v) => (v.dataAsset !== undefined) !== (v.data !== undefined), { message: "Chart content needs exactly one of `dataAsset` or `data`." })
150
224
  .refine((v) => !(v.data !== undefined && v.transform !== undefined), {
151
225
  message: "`transform` is for dataAsset only — inline `data` is already pre-computed.",
152
226
  }),
153
- }),
154
- z.object({
227
+ })
228
+ .describe("A tabular asset (or inline rows) rendered as an interactive ECharts plot. " +
229
+ "Default to this over `figure` whenever the data file exists. To show the " +
230
+ "same data as both a chart and a table, write two sections against the same " +
231
+ "`dataAsset` — the file is staged once."),
232
+ z
233
+ .object({
155
234
  type: z.literal("methods"),
156
235
  title: z.string(),
157
- intent: z.string(),
158
- content: z.object({ prose: z.string().min(1) }),
159
- }),
236
+ intent: intentField(),
237
+ content: z.object({
238
+ prose: z.string().min(1).describe("Markdown prose you wrote. The builder never opens your data files to write it."),
239
+ }),
240
+ })
241
+ .describe("Prose you wrote — what was done, how, and with which parameters."),
160
242
  ]);
161
- const ReportSchema = z.object({
162
- title: z.string(),
163
- audience: z.string(),
164
- styleGuidance: z.string().optional(),
165
- sources: z.array(SourceSchema).default([]),
166
- sections: z.array(SectionSchema).min(1),
243
+ /**
244
+ * The report brief the conversation agent composes: title/audience + sources +
245
+ * the section union. This is the ~12k-char schema that `plan_report` hands back
246
+ * just-in-time and that `submit_report` validates its `report` field against
247
+ * inside `execute`. Exported for `plan_report`, `submit_report`, and the
248
+ * brief-shape tests.
249
+ */
250
+ export const ReportBriefSchema = z.object({
251
+ title: z.string().describe("Report title. Persisted with the preview and reused as the card title on every later version."),
252
+ audience: z
253
+ .string()
254
+ .describe("Who reads this (e.g. 'wet-lab collaborators', 'a computational-biology PI'). The builder tunes tone, density and jargon to it."),
255
+ styleGuidance: z.string().optional().describe("Optional art direction for the builder (e.g. 'dense and print-ready', 'no hero imagery')."),
256
+ sources: z
257
+ .array(SourceSchema)
258
+ .default([])
259
+ .describe("Every file the report renders — CSV/TSV, PNG/SVG, JSON. A section's " +
260
+ "`imageAsset` / `dataAsset` must name one of these staged assets or the " +
261
+ "call fails. Do NOT list markdown or synthesis.json: those are inputs to " +
262
+ "the prose you write, not to the renderer."),
263
+ sections: z.array(SectionSchema).min(1).describe("The report's sections, in the order they are rendered."),
167
264
  });
168
- /** Exported for schema-validation tests — the underlying Zod schema. */
169
- export const iterateReportInputSchema = z
265
+ /**
266
+ * `submit_report`'s always-on input surface. The heavy brief rides in `report`
267
+ * as `unknown` — typed that way ON PURPOSE so the ~12k `ReportBriefSchema` does
268
+ * NOT ship on every conversation turn; `execute` validates it against
269
+ * `ReportBriefSchema` and returns issues as data. The iterate-mode + common
270
+ * fields stay fully typed (they are small and guide iteration). Exported for
271
+ * the envelope-validation tests.
272
+ */
273
+ export const submitReportInputSchema = z
170
274
  .object({
171
275
  previewId: z
172
276
  .string()
@@ -174,36 +278,169 @@ export const iterateReportInputSchema = z
174
278
  .max(64)
175
279
  .optional()
176
280
  .describe("Preview group ID. Omit for new reports (auto-generated). " + "Required for iterations — must match the existing preview."),
177
- baseVersion: z.number().int().min(1).optional().describe("Version to iterate from (1-based). Defaults to latest."),
281
+ baseVersion: z
282
+ .number()
283
+ .int()
284
+ .min(1)
285
+ .optional()
286
+ .describe("Version to branch from (1-based) — use when the user prefers an earlier version. Defaults to the latest."),
178
287
  format: z.enum(["html", "pdf"]).default("html").describe("Output format. Defaults to 'html' — only set explicitly for PDF."),
179
- report: ReportSchema.optional().describe("CREATION ONLY (v1). Mutually exclusive with modifications."),
288
+ report: z
289
+ .unknown()
290
+ .optional()
291
+ .describe("CREATION ONLY (v1): the composed report brief. Get its schema + " +
292
+ "authoring rules from `plan_report` first. Validated on submit — an " +
293
+ "invalid brief comes back as `{ ok: false, issues }`, not a thrown error. " +
294
+ "Mutually exclusive with `modifications`."),
180
295
  modifications: z
181
296
  .string()
182
297
  .optional()
183
- .describe("ITERATION ONLY (v2+). Natural-language modification instructions. " + "The report-builder agent receives only this text — be specific."),
298
+ .describe("ITERATION ONLY (v2+). Natural-language change instructions. The " +
299
+ "report-builder sees ONLY this text plus the previous version's template " +
300
+ "— name what changes and leave everything else alone."),
184
301
  sources: z
185
302
  .array(SourceSchema)
186
303
  .optional()
187
- .describe("Iteration only — additional assets to stage on top of the existing " + "assets/ dir. For creation, put sources inside `report.sources`."),
304
+ .describe("Iteration only — additional assets to stage on top of the existing " + "assets/ dir. For creation, put sources inside the brief's `sources`."),
188
305
  })
189
306
  .refine((data) => (data.report !== undefined) !== (data.modifications !== undefined), {
190
307
  message: "Exactly one of `report` or `modifications` must be provided.",
191
308
  })
192
- .refine((data) => !(data.report && data.sources), {
193
- message: "Top-level `sources` is for iteration mode. For creation, put sources inside `report.sources`.",
309
+ .refine((data) => !(data.report !== undefined && data.sources), {
310
+ message: "Top-level `sources` is for iteration mode. For creation, put sources inside the brief's `sources`.",
194
311
  });
195
- export function createIterateReportTool(deps) {
312
+ // ── plan_report: the just-in-time authoring contract ────────────────
313
+ /**
314
+ * The authoring rules `plan_report` hands back alongside the brief schema.
315
+ * These are the cross-field, when-to-use rules the schema's per-field
316
+ * `.describe()` text cannot carry on its own — pulled from the former
317
+ * `iterate_report` description and `prompts/report-builder.ts`. Guidance for
318
+ * the conversation agent composing the brief, NOT for the builder.
319
+ */
320
+ const REPORT_AUTHORING_RULES = `# Composing a report brief
321
+
322
+ The report-builder agent NEVER sees the analysis tree — it receives only the
323
+ brief you compose, so the brief must be complete. You write the prose, the
324
+ numbers, and the chart encodings; the builder only does layout and visual
325
+ treatment. Pass the composed brief as \`submit_report\`'s \`report\` field.
326
+
327
+ ## Create vs iterate
328
+ - CREATE (v1): pass \`report\` (this brief), omit \`previewId\`. Every CSV / image /
329
+ JSON the report renders goes in \`report.sources\` — pre-flight stages each into
330
+ the preview's assets/ dir and parses its columns, first 5 rows, and row count
331
+ into the brief for you.
332
+ - ITERATE (v2+): pass \`modifications\` (natural-language change instructions)
333
+ plus the existing \`previewId\`, and NEVER \`report\` — passing \`report\` builds a
334
+ fresh report and discards all prior work. New data files go in the top-level
335
+ \`sources\`. \`baseVersion\` branches from an earlier version instead of the latest.
336
+
337
+ ## Sources
338
+ - Every file the report renders — CSV/TSV, PNG/SVG, JSON — must be listed in
339
+ \`sources\`. A section's \`imageAsset\` / \`dataAsset\` must name one of these staged
340
+ assets (its \`as\`, or the basename of its \`path\`) or the call fails.
341
+ - Markdown is NOT a source. Keep summary.md / synthesis.json out of \`sources\`;
342
+ their content reaches the report as prose you write into \`narrative\` /
343
+ \`methods\` sections.
344
+
345
+ ## Do NOT reach for run_ephemeral first
346
+ - Not to peek at a CSV — pre-flight already parsed its columns and head rows.
347
+ - Not to filter, slice, rank, or derive columns from a single CSV about to be
348
+ rendered — \`chart.content.transform\` and \`table.content.transform\` do exactly
349
+ that client-side, and the transform text renders as a provenance footnote.
350
+ - run_ephemeral is only for computation no single section transform covers:
351
+ cross-file aggregation, statistics needing a real numerical library, or a
352
+ derived CSV the report then lists as a fresh source.
353
+
354
+ ## Choosing a section type
355
+ - narrative — prose you wrote: context, story, interpretation.
356
+ - methods — prose you wrote: what was done, how, with which parameters.
357
+ - metrics — labeled headline numbers you already derived (the builder does not
358
+ compute them).
359
+ - table — a tabular asset rendered as a table; pick a column subset + topN that
360
+ fit the audience.
361
+ - chart — a tabular asset (or inline rows) rendered as an interactive ECharts
362
+ plot. PREFER chart over figure whenever the data file exists — a chart is
363
+ interactive, themed, and re-encodable on iteration.
364
+ - figure — a static image the analysis already produced (PNG/SVG). Reach for it
365
+ only when the user asks for the existing image, the visual is genuinely
366
+ image-only, or the image carries annotations the data does not.
367
+
368
+ ## Inline chart data
369
+ \`chart.content.data\` is an escape hatch for a cross-file aggregate you computed
370
+ yourself from files you ACTUALLY read. Prefer \`dataAsset\`. Never fabricate or
371
+ estimate values — every inline value must come from a file you read, and cite
372
+ the real files in \`data.source\`. More than one inline-data chart per report is a
373
+ smell — persist the aggregate as a derived CSV instead.
374
+
375
+ ## Result
376
+ \`submit_report\` returns the preview id + version and emits a preview card.
377
+ Pre-flight and builder failures come back as an \`error\` string on the result,
378
+ not as a thrown error. An invalid brief comes back as \`{ ok: false, issues }\` —
379
+ fix the named fields and resubmit.`;
380
+ /** Precomputed once at module load — the brief schema `plan_report` returns as data. */
381
+ const REPORT_BRIEF_JSON_SCHEMA = z.toJSONSchema(ReportBriefSchema);
382
+ /**
383
+ * `plan_report` — the tiny, always-on trigger (no args). Its result delivers
384
+ * the brief schema + authoring rules just-in-time, so the ~12k contract enters
385
+ * context only on a report-building turn (and thereafter rides in the cached
386
+ * history prefix). Compose the brief from what it returns, then call
387
+ * `submit_report`. Pure logic, no deps — hence a module-scope leaf tool.
388
+ */
389
+ export const planReportTool = defineTool({
390
+ id: "plan_report",
391
+ description: "Start building or iterating a report. Returns the report-brief schema " +
392
+ "and the authoring rules as its result — the full contract you compose " +
393
+ "against — so it is not carried on every turn. Call this first, compose " +
394
+ "the brief it describes, then call `submit_report` with it. (Iterating an " +
395
+ "existing preview with `modifications` needs no brief — you may go " +
396
+ "straight to `submit_report`.)",
397
+ inputSchema: z.object({}),
398
+ executionMode: "inline",
399
+ execute: async () => ok({
400
+ schema: REPORT_BRIEF_JSON_SCHEMA,
401
+ rules: REPORT_AUTHORING_RULES,
402
+ }),
403
+ });
404
+ /**
405
+ * `submit_report` — takes the composed brief and drives the report iteration.
406
+ * On a valid brief its behaviour is byte-for-byte the former `iterate_report`:
407
+ * pre-flight staging, previewId/versioning, `runReportIteration`, preview
408
+ * publishing. The brief rides as `unknown` on the wire (keeping its ~12k schema
409
+ * off the always-on surface) and is validated against `ReportBriefSchema` here.
410
+ */
411
+ export function createReportSubmitTool(deps) {
196
412
  return defineTool({
197
- id: "iterate_report",
198
- description: "Create or iterate on an HTML/PDF report. Two modes — use exactly one:\n" +
199
- "• CREATION (v1): pass `report` with title/audience/sources/sections, " +
200
- "omit `previewId`. Pre-flight stages every source and hands the " +
201
- "report-builder agent a complete brief.\n" +
202
- "• ITERATION (v2+): pass `modifications` (natural language) with the " +
203
- "existing `previewId`. Optional top-level `sources` adds new assets.\n" +
204
- "Returns the preview ID and version. Emits a preview chat data part.",
205
- inputSchema: iterateReportInputSchema,
413
+ id: "submit_report",
414
+ description: "Submit a composed report brief to build or iterate an HTML/PDF report " +
415
+ "(the report-builder renders it; it never sees the analysis tree). Call " +
416
+ "`plan_report` FIRST to get the brief schema + authoring rules, compose " +
417
+ "the brief, then call this. CREATE: pass `report` (the composed brief), " +
418
+ "omit `previewId`. ITERATE: pass `modifications` + the existing " +
419
+ "`previewId`, and NEVER `report`. An invalid brief comes back as " +
420
+ "`{ ok: false, issues }` — fix the named fields and resubmit (see " +
421
+ "`plan_report` for the schema); pre-flight and builder failures come back " +
422
+ "as an `error` string. Returns the preview id + version and emits a " +
423
+ "preview card.",
424
+ inputSchema: submitReportInputSchema,
206
425
  execute: async (input, ctx) => {
426
+ // The brief rides as `unknown` so its ~12k schema stays off the
427
+ // always-on tool surface. Validate it here and return issues as DATA
428
+ // (the validate_plan trade) — never a thrown error.
429
+ let brief;
430
+ if (input.report !== undefined) {
431
+ const parsed = ReportBriefSchema.safeParse(input.report);
432
+ if (!parsed.success) {
433
+ return ok({
434
+ ok: false,
435
+ issues: parsed.error.issues.map((i) => ({
436
+ path: i.path.join(".") || "(root)",
437
+ message: i.message,
438
+ })),
439
+ hint: "The `report` brief did not match the schema. Call `plan_report` for the full schema, fix the fields named in `issues`, and resubmit.",
440
+ });
441
+ }
442
+ brief = parsed.data;
443
+ }
207
444
  const { resourceId } = scopeResource(ctx.session.scope);
208
445
  const analysisRoot = deps.resolveWorkspaceRoot(resourceId);
209
446
  // Zod fills the default at parse time but the static type keeps it
@@ -215,7 +452,7 @@ export function createIterateReportTool(deps) {
215
452
  const metaPathAbs = join(previewRootAbs, PREVIEW_META_FILE);
216
453
  // Iteration mode: recover the title from the creation-time meta file
217
454
  // so the data-report-preview part keeps the original title across versions.
218
- const existingMeta = input.report ? null : await readPreviewMeta(metaPathAbs);
455
+ const existingMeta = brief ? null : await readPreviewMeta(metaPathAbs);
219
456
  // Build the preview-publishing seam for this iteration. The managed
220
457
  // default mints a 1h scoped access grant so preview calls outlive the chat
221
458
  // turn; the local default returns an "unavailable" publisher.
@@ -239,7 +476,7 @@ export function createIterateReportTool(deps) {
239
476
  });
240
477
  }
241
478
  // ── Pre-flight: stage declared sources into assets/. ────────
242
- const sources = input.report?.sources ?? input.sources ?? [];
479
+ const sources = brief?.sources ?? input.sources ?? [];
243
480
  let staged = [];
244
481
  if (sources.length > 0) {
245
482
  const result = await stageReportAssets({
@@ -269,9 +506,9 @@ export function createIterateReportTool(deps) {
269
506
  }
270
507
  // Cross-check creation-mode briefs — every section asset reference
271
508
  // must be present in staged[].
272
- if (input.report) {
509
+ if (brief) {
273
510
  const stagedNames = new Set(staged.map((s) => s.name));
274
- const missing = collectMissingAssetRefs(input.report.sections, stagedNames);
511
+ const missing = collectMissingAssetRefs(brief.sections, stagedNames);
275
512
  if (missing.length > 0) {
276
513
  const reason = `section asset references not staged: ${missing.join(", ")} (pass them in report.sources)`;
277
514
  await ctx.emit({
@@ -292,7 +529,7 @@ export function createIterateReportTool(deps) {
292
529
  });
293
530
  }
294
531
  }
295
- const prompt = input.report ? buildCreationPrompt(input.report, format, staged) : buildModificationPrompt(input.modifications, format, staged);
532
+ const prompt = brief ? buildCreationPrompt(brief, format, staged) : buildModificationPrompt(input.modifications, format, staged);
296
533
  const result = await runReportIteration({
297
534
  provider: deps.provider,
298
535
  pool: deps.pool,
@@ -331,20 +568,21 @@ export function createIterateReportTool(deps) {
331
568
  });
332
569
  }
333
570
  // Persist title on creation so iteration runs can recover it.
334
- if (input.report) {
571
+ if (brief) {
335
572
  try {
336
573
  await mkdir(previewRootAbs, { recursive: true });
337
574
  await writePreviewMeta(metaPathAbs, {
338
- title: input.report.title,
339
- audience: input.report.audience,
575
+ title: brief.title,
576
+ audience: brief.audience,
340
577
  format,
341
578
  });
342
579
  }
343
580
  catch (err) {
344
- console.warn(`[iterate-report] failed to persist preview meta: ${err instanceof Error ? err.message : err}`);
581
+ const logger = (deps.logger ?? createNoopLogger()).named("submit-report");
582
+ logger.warn("failed to persist preview meta", logger.errorFields(err));
345
583
  }
346
584
  }
347
- const previewTitle = input.report?.title ?? existingMeta?.title ?? "Report";
585
+ const previewTitle = brief?.title ?? existingMeta?.title ?? "Report";
348
586
  await ctx.emit({
349
587
  type: "data-report-preview",
350
588
  data: {
@@ -468,7 +706,9 @@ export function stagedAssetsBlock(staged, opts = {}) {
468
706
  return out.join("\n");
469
707
  }
470
708
  function escapePipe(s) {
471
- return s.replace(/\|/g, "\\|");
709
+ // Escape backslashes first so a literal `\` in the cell cannot combine with
710
+ // the escape we add and re-expose an unescaped `|` that breaks the table.
711
+ return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|");
472
712
  }
473
713
  function formatSectionContent(s) {
474
714
  switch (s.type) {
@@ -3,6 +3,16 @@
3
3
  *
4
4
  * Used directly by target-assessment workflow steps and by tool wrappers.
5
5
  */
6
+ export interface ChemblCompound {
7
+ chemblId: string;
8
+ preferredCompoundName: string | null;
9
+ canonicalSmiles: string | null;
10
+ molecularWeight: number | null;
11
+ alogp: number | null;
12
+ molecularFormula: string | null;
13
+ }
14
+ /** How `searchCompounds` interprets its query string. */
15
+ export type CompoundSearchType = "target" | "compound" | "smiles";
6
16
  export interface ChemblTarget {
7
17
  targetChemblId: string;
8
18
  preferredName: string | null;
@@ -53,6 +63,8 @@ export interface ChemblPolypharmHit {
53
63
  standardUnits: string | null;
54
64
  pchemblValue: number | null;
55
65
  }
66
+ /** Search ChEMBL for compounds by target, compound name, or SMILES. */
67
+ export declare function searchCompounds(query: string, searchType: CompoundSearchType, limit?: number): Promise<ChemblCompound[]>;
56
68
  export declare function searchTargets(query: string, limit?: number): Promise<ChemblTarget[]>;
57
69
  export declare function getBioactivity(chemblId: string, type: "compound" | "target", options?: {
58
70
  activityType?: string;