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