@inflexa-ai/harness 0.22.1 → 0.24.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 (73) hide show
  1. package/README.md +1 -2
  2. package/dist/agents/conversation-agent.d.ts +0 -9
  3. package/dist/app/chat-turn.js +1 -0
  4. package/dist/app/message-assembly.d.ts +3 -0
  5. package/dist/app/message-assembly.js +17 -0
  6. package/dist/contracts/chat-parts.d.ts +38 -26
  7. package/dist/contracts/content-url.d.ts +7 -41
  8. package/dist/contracts/content-url.js +7 -48
  9. package/dist/contracts/index.d.ts +2 -2
  10. package/dist/contracts/index.js +1 -1
  11. package/dist/contracts/message.d.ts +1 -1
  12. package/dist/contracts/message.js +1 -1
  13. package/dist/contracts/part-registry.js +2 -2
  14. package/dist/contracts/schemas/chat-parts.d.ts +16 -44
  15. package/dist/contracts/schemas/chat-parts.js +15 -18
  16. package/dist/contracts/schemas/index.d.ts +1 -1
  17. package/dist/contracts/schemas/index.js +1 -1
  18. package/dist/execution/run-authorizer.d.ts +1 -1
  19. package/dist/execution/run-authorizer.js +1 -1
  20. package/dist/index.d.ts +1 -3
  21. package/dist/index.js +0 -2
  22. package/dist/lib/zod-issues.d.ts +2 -3
  23. package/dist/lib/zod-issues.js +2 -3
  24. package/dist/loop/__fixtures__/scripted-provider.js +3 -1
  25. package/dist/loop/run-agent.d.ts +5 -0
  26. package/dist/loop/run-agent.js +50 -2
  27. package/dist/loop/run-to-terminal.d.ts +1 -1
  28. package/dist/loop/run-to-terminal.js +1 -1
  29. package/dist/memory/card-builders.d.ts +0 -27
  30. package/dist/memory/card-builders.js +0 -75
  31. package/dist/memory/conversation-display-backfill.d.ts +2 -4
  32. package/dist/memory/conversation-display-backfill.js +3 -9
  33. package/dist/memory/conversation-display-storage.d.ts +3 -3
  34. package/dist/memory/conversation-display-storage.js +3 -3
  35. package/dist/memory/reconstruct-cards.d.ts +5 -7
  36. package/dist/memory/reconstruct-cards.js +6 -20
  37. package/dist/memory/tool-call-integrity.d.ts +46 -0
  38. package/dist/memory/tool-call-integrity.js +92 -0
  39. package/dist/runtime/boot.js +2 -12
  40. package/dist/tools/report-session/preview-report.d.ts +2 -1
  41. package/dist/tools/report-session/preview-report.js +9 -1
  42. package/dist/tools/start-report-session.js +17 -7
  43. package/dist/workspace/paths.d.ts +0 -21
  44. package/dist/workspace/paths.js +0 -37
  45. package/package.json +1 -3
  46. package/dist/agents/report-builder.d.ts +0 -34
  47. package/dist/agents/report-builder.js +0 -34
  48. package/dist/execution/report-render.d.ts +0 -39
  49. package/dist/execution/report-render.js +0 -98
  50. package/dist/execution/report-runner.d.ts +0 -111
  51. package/dist/execution/report-runner.js +0 -230
  52. package/dist/prompts/report-builder.d.ts +0 -1
  53. package/dist/prompts/report-builder.js +0 -174
  54. package/dist/tools/iterate-report.d.ts +0 -227
  55. package/dist/tools/iterate-report.js +0 -883
  56. package/dist/tools/lib/preview-lock.d.ts +0 -20
  57. package/dist/tools/lib/preview-lock.js +0 -35
  58. package/dist/tools/lib/report-preflight.d.ts +0 -39
  59. package/dist/tools/lib/report-preflight.js +0 -283
  60. package/dist/tools/report/build-report.d.ts +0 -13
  61. package/dist/tools/report/build-report.js +0 -27
  62. package/dist/tools/report/index.d.ts +0 -15
  63. package/dist/tools/report/index.js +0 -15
  64. package/dist/tools/report/mint-preview-url.d.ts +0 -26
  65. package/dist/tools/report/mint-preview-url.js +0 -47
  66. package/dist/tools/report/preview-publisher.d.ts +0 -53
  67. package/dist/tools/report/preview-publisher.js +0 -45
  68. package/dist/tools/report/preview-snapshot.d.ts +0 -34
  69. package/dist/tools/report/preview-snapshot.js +0 -98
  70. package/dist/tools/report/submit-report.d.ts +0 -26
  71. package/dist/tools/report/submit-report.js +0 -148
  72. package/dist/tools/report/version-fs.d.ts +0 -15
  73. package/dist/tools/report/version-fs.js +0 -165
@@ -1,230 +0,0 @@
1
- /**
2
- * Report runner — orchestrates an iterative report build entirely in the
3
- * Cortex Node process. No sandbox, no Python.
4
- *
5
- * Lifecycle:
6
- * 1. Resolve version (latest + 1, or baseVersion + 1)
7
- * 2. Prepare version dir (mkdir, copy v{base}/report.html.j2, symlink ../assets)
8
- * 3. Construct the 4 custom report tools + the in-process versionFs surface;
9
- * all of them close over the iteration's preview-dir state and a single
10
- * `outcome: ReportOutcome | undefined` variable (the closure-state
11
- * closure-state capture — see design.md Decision #9, mirrors
12
- * `harness/tools/research/generate-plan.ts`'s
13
- * `PlannerOutcome` capture).
14
- * 4. Build the `report-builder` AgentDefinition with that tool roster and
15
- * drive it via `runAgent` + `passthroughStep`.
16
- * 5. Verify the closure-captured `outcome` is `{ ok: true, ... }` (else
17
- * classify the failure, roll back the version dir, return a structured
18
- * failure).
19
- * 6. Return success or a structured failure.
20
- *
21
- * Concurrency: per-previewId serialization via `withPreviewLock` so two
22
- * iterations on the same preview don't race on shared assets/.
23
- *
24
- * Billing: the report-builder's LLM calls flow through the `ChatProvider`
25
- * seam with `forSubAgent(session, REPORT_BUILDER_AGENT_ID)` — the provider's
26
- * lazy `resolveBilling` fires at the call site. No ALS, no fetch-patch
27
- * (Decision #6).
28
- *
29
- * Preview surface: the `preview_snapshot` and `mint_preview_url` tools mint
30
- * a content-server URL through the injected `PreviewPublisher` seam. The
31
- * managed publisher owns the short-lived host credential; the local default
32
- * returns "unavailable" so the report still builds without a hosted preview.
33
- */
34
- import { copyFile, mkdir, readdir, rm, stat, symlink } from "node:fs/promises";
35
- import { join } from "node:path";
36
- import { forSubAgent } from "../auth/types.js";
37
- import { runToTerminal } from "../loop/run-to-terminal.js";
38
- import { passthroughStep } from "../loop/run-step.js";
39
- import { previewVersionDir, previewDir, latestPreviewVersion } from "../workspace/paths.js";
40
- import { withPreviewLock } from "../tools/lib/preview-lock.js";
41
- import { createBuildReportTool, createSubmitReportTool, createMintPreviewUrlTool, createPreviewSnapshotTool, createVersionFsTools, } from "../tools/report/index.js";
42
- import { createReportBuilderAgent, REPORT_BUILDER_AGENT_ID, REPORT_BUILDER_SKILLS } from "../agents/report-builder.js";
43
- import { createSkillTools } from "../tools/sandbox/skills.js";
44
- import { createNoopLogger } from "../lib/console-logger.js";
45
- const REPORT_AGENT_MAX_STEPS = 75;
46
- export async function runReportIteration(deps, opts) {
47
- const logger = (deps.logger ?? createNoopLogger()).named("report-runner").with({ previewId: opts.previewId });
48
- const startTime = performance.now();
49
- return withPreviewLock(opts.previewId, async () => {
50
- // ── Resolve version directories ───────────────────────────────
51
- const previewRootAbs = join(opts.workspaceRoot, previewDir(opts.previewId));
52
- let existingVersions = [];
53
- try {
54
- existingVersions = (await readdir(previewRootAbs, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => d.name);
55
- }
56
- catch {
57
- /* dir doesn't exist yet */
58
- }
59
- const latestVer = latestPreviewVersion(existingVersions);
60
- const baseVer = opts.baseVersion ?? latestVer;
61
- const newVersion = Math.max(latestVer, baseVer) + 1;
62
- const versionDirRel = previewVersionDir(opts.previewId, newVersion);
63
- const versionDirAbs = join(opts.workspaceRoot, versionDirRel);
64
- const assetsDirAbs = join(previewRootAbs, "assets");
65
- // ── Prepare directories ───────────────────────────────────────
66
- try {
67
- await mkdir(versionDirAbs, { recursive: true });
68
- await mkdir(assetsDirAbs, { recursive: true });
69
- if (baseVer > 0) {
70
- const baseTemplate = join(opts.workspaceRoot, previewVersionDir(opts.previewId, baseVer), "report.html.j2");
71
- try {
72
- await copyFile(baseTemplate, join(versionDirAbs, "report.html.j2"));
73
- }
74
- catch (err) {
75
- const code = err.code;
76
- if (code !== "ENOENT")
77
- throw err;
78
- // Base version has no template — agent will author from scratch.
79
- }
80
- }
81
- // Symlink assets/ → ../assets so templates can reference assets/foo.csv
82
- // and the content-server resolves them through the version-dir prefix.
83
- const assetsSymlink = join(versionDirAbs, "assets");
84
- try {
85
- await symlink("../assets", assetsSymlink, "dir");
86
- }
87
- catch (err) {
88
- const code = err.code;
89
- if (code !== "EEXIST")
90
- throw err;
91
- }
92
- }
93
- catch (err) {
94
- const msg = err instanceof Error ? err.message : String(err);
95
- return failure(opts.previewId, newVersion, `prepareVersionDir failed: ${msg}`, "internal", startTime);
96
- }
97
- // ── Closure-captured terminal-tool state (Decision #9) ─────────
98
- let outcome;
99
- const urlCell = { url: undefined, expiresAt: undefined };
100
- const templatesDir = join(deps.templatesDir, "report-html");
101
- const buildReportTool = createBuildReportTool({
102
- versionDir: versionDirAbs,
103
- templatesDir,
104
- });
105
- const submitReportTool = createSubmitReportTool({
106
- versionDir: versionDirAbs,
107
- assetsDir: assetsDirAbs,
108
- setOutcome: (v) => {
109
- outcome = v;
110
- },
111
- });
112
- const tools = [
113
- buildReportTool,
114
- submitReportTool,
115
- createMintPreviewUrlTool({
116
- resourceId: opts.resourceId,
117
- previewId: opts.previewId,
118
- currentVersion: newVersion,
119
- previews: opts.previews,
120
- urlCell,
121
- }),
122
- createPreviewSnapshotTool({
123
- resourceId: opts.resourceId,
124
- previewId: opts.previewId,
125
- currentVersion: newVersion,
126
- previews: opts.previews,
127
- urlCell,
128
- chrome: deps.chrome,
129
- logger,
130
- }),
131
- ...createVersionFsTools({ versionDir: versionDirAbs }),
132
- ...Object.values(createSkillTools({ skillsDir: deps.skillsDir, skills: [...REPORT_BUILDER_SKILLS] })),
133
- ];
134
- const agent = createReportBuilderAgent({ model: deps.model, tools });
135
- // The agent's runaway-guard takes precedence over the per-iteration cap.
136
- const cappedAgent = {
137
- ...agent,
138
- maxIterations: Math.min(agent.maxIterations, REPORT_AGENT_MAX_STEPS),
139
- };
140
- const childSession = forSubAgent(opts.session, REPORT_BUILDER_AGENT_ID);
141
- let agentError;
142
- try {
143
- await runToTerminal(cappedAgent, [{ role: "user", content: opts.prompt }], childSession, {
144
- provider: deps.provider,
145
- signal: opts.signal,
146
- emit: opts.emit,
147
- runStep: passthroughStep,
148
- resolved: () => outcome !== undefined,
149
- usageRecorder: deps.usageRecorder,
150
- // Fold the builder's calls into the turn total the root loop reports.
151
- turnUsage: opts.turnUsage,
152
- invocationId: opts.invocationId,
153
- }, {
154
- tools: [buildReportTool, submitReportTool],
155
- nudge: "You stopped without calling submit_report, so this report version " +
156
- "was not recorded. Build the report if needed, then call " +
157
- "submit_report now to finalize it.",
158
- });
159
- }
160
- catch (err) {
161
- agentError = err instanceof Error ? err.message : String(err);
162
- }
163
- // ── Phantom-success guard ──────────────────────────────────────
164
- // submit-report claims ok but index.html doesn't exist or is empty.
165
- let phantomSuccess = false;
166
- if (outcome?.ok === true && !agentError) {
167
- try {
168
- const indexStat = await stat(join(versionDirAbs, "index.html"));
169
- if (!indexStat.isFile() || indexStat.size === 0)
170
- phantomSuccess = true;
171
- }
172
- catch {
173
- phantomSuccess = true;
174
- }
175
- }
176
- if (!outcome || agentError || phantomSuccess) {
177
- logger.warn("report.agent.finish", { version: newVersion, submitted: outcome?.ok === true, phantom: phantomSuccess, agentError });
178
- const { reason, errorKind } = classifyFailure({
179
- phantomSuccess,
180
- agentError,
181
- outcome,
182
- });
183
- // Roll back the version dir (assets are shared — leave them alone).
184
- try {
185
- await rm(versionDirAbs, { recursive: true, force: true });
186
- }
187
- catch (err) {
188
- logger.error("rollback failed", { versionDirAbs, ...logger.errorFields(err) });
189
- }
190
- return failure(opts.previewId, newVersion, reason, errorKind, startTime);
191
- }
192
- const durationMs = Math.round(performance.now() - startTime);
193
- return {
194
- ok: true,
195
- previewId: opts.previewId,
196
- version: newVersion,
197
- previewPath: `v${newVersion}/index.html`,
198
- durationMs,
199
- notes: outcome.notes,
200
- };
201
- });
202
- }
203
- function failure(previewId, version, reason, errorKind, startTime) {
204
- return {
205
- ok: false,
206
- previewId,
207
- version,
208
- reason,
209
- errorKind,
210
- durationMs: Math.round(performance.now() - startTime),
211
- };
212
- }
213
- function classifyFailure(s) {
214
- if (s.phantomSuccess) {
215
- return {
216
- reason: "submit_report claimed ok but index.html is missing or empty",
217
- errorKind: "build",
218
- };
219
- }
220
- if (s.agentError) {
221
- return {
222
- reason: s.agentError,
223
- errorKind: /timeout/i.test(s.agentError) ? "timeout" : "submit",
224
- };
225
- }
226
- return {
227
- reason: "agent did not call submit_report — terminal outcome was never recorded",
228
- errorKind: "submit",
229
- };
230
- }
@@ -1 +0,0 @@
1
- export declare const reportBuilderPrompt = "# Report Builder\n\nYou build polished HTML reports from a structured brief using Jinja2 templating with the Inflexa Design Blueprint. The conversation agent has already done the analysis discovery, picked the data, and composed the prose \u2014 your job is to lay it out using the design system. Reports are light-themed, full-width, Space Grotesk + IBM Plex Mono typography with corner-accent cards and section textures.\n\n## CRITICAL \u2014 Read This First\n\n**Every iteration MUST terminate by calling `submit_report` and receiving `ok: true`. That is the only signal the runner acts on.**\n\nA response that ends without a successful `submit_report` is a **failure** \u2014 the runner rolls back the version directory. `build_report` returning `ok: true` does NOT ship the report. `preview_snapshot` returning a screenshot does NOT ship the report. Only `submit_report` does.\n\nThe brief you receive is **complete**:\n- Every asset you might need is already staged in `assets/` (the brief lists name, kind, size, columns, head rows, row count for each).\n- Every section's content is provided \u2014 narrative prose, metric values, asset names, chart specs.\n- You **cannot see the analysis tree.** There is no discovery to do, no files to find, no columns to peek at. `workspace_search`, `list_files`, and `file_stat` are disabled.\n\nIf a section references something not in the brief or in `assets/`, that's a real failure \u2014 stop and emit one short final message naming what's missing. Do NOT attempt to find it or fabricate substitutes.\n\n## Workflow\n\n1. **Read the brief.** Identify the title, audience, sections, staged assets, and any iteration-specific instructions.\n2. **For iterations only:** read the existing `report.html.j2` first. Apply only the requested changes \u2014 surgical edits, no rewrites.\n3. **Author** `report.html.j2` using the design system. Reference staged assets directly: `<img src=\"assets/foo.png\">`, ECharts loading `assets/data.csv`, etc.\n4. **Build** with `build_report`. On Jinja error, fix and rebuild. Cap yourself at 3 build attempts on a fresh template \u2014 if it still won't render, stop with a final message naming the Jinja error rather than grinding through dozens of attempts.\n5. **Verify** with one `preview_snapshot` after build is green. Console errors that aren't fatal (warnings, missing favicons) are acceptable \u2014 submit with notes.\n6. **Submit** with `submit_report`. Pass `notes` for caveats the user should know.\n\nYou may NOT use `execute_command` \u2014 there is no shell. You may NOT call `python` or any build script \u2014 `build_report` is the only build path.\n\n## Templating model\n\n**Always use relative paths.** Your workspace is rooted at the version directory \u2014 `write_file`, `read_file`, `edit_file`, and `mkdir` resolve every path against this root. Pass `report.html.j2`, `assets/foo.csv`, `index.html` \u2014 never an absolute path like `/previews/{resourceId}/{previewId}/v{N}/report.html.j2`. An absolute path is rejected with `out_of_scope` naming the path and nothing is written; drop the leading slash and retry.\n\nWorking directory: `report.html.j2` (you author this), `assets/` (a symlink to the shared assets dir for this preview), `index.html` (output of build_report).\n\nBase templates and components live at `/templates/report-html/` (read-only) and are resolved by the Nunjucks renderer when `build_report` runs. Reference them via short paths:\n\n```jinja2\n{% extends \"base.html.j2\" %}\n{% include \"components/stat-card.html.j2\" %}\n```\n\nDo NOT prepend `templates/` \u2014 the renderer's loader path handles that.\n\nThe ECharts theme is inlined at render time, registered as `inflexa` before `inflexa-theme-ready` fires. Wire chart initialization with `document.addEventListener('inflexa-theme-ready', () => { ... })`.\n\n## Components and design system\n\nUse `skill_search` and `skill_read` on `report-html` for:\n- Component selection rules (when to use stat-card vs insight-box vs data-table)\n- Component invocation snippets (`{% set %}` + `{% include %}` blocks)\n- Section composition rules (alternation, textures, fade-in)\n- Inline patterns (block progress bars, terminal cards, gene spans)\n- Anti-patterns\n\n**Every report MUST include a sidebar.** Override `{% block sidebar %}` with one `sidebar_items` entry per top-level `<section id=\"\u2026\">`. The base template auto-shifts the page right on lg+ and tracks the active section via IntersectionObserver \u2014 id mismatches break navigation silently. See `SKILL.md` \"Required component invocations\" for the snippet.\n\nFor the full design-token reference (colors, spacing, typography), `skill_read(\"report-html\", \"references/design-system.md\")`.\n\n## Reading the brief\n\nThe brief is **markdown, not JSON**. The conversation agent's section objects\nare flattened into labeled lines before they reach you \u2014 there is no nested\n`content` object to traverse and no field paths to dereference. Read the labels.\n\nA creation brief is: title / audience / style header lines, a **Staged Assets**\ntable, then a **Sections** block. An iteration brief is the requested changes in\nprose, plus a **Newly Staged Assets** table when new assets came with it.\n\n### Staged Assets\n\nA table with columns `name | kind | size | rows | columns`. `kind` is one of\n`csv`, `tsv`, `image`, `json`, `other` \u2014 trust it over your own guess from the\nextension. Each CSV/TSV asset is followed by a `### Head: <name>` table of its\nfirst rows. Reference every asset as `assets/<name>`.\n\nFor a `json` asset, the `columns` cell tells you the shape:\n- **filled** \u2192 a top-level array of row objects. Treat it like a CSV: iterate rows, keys are columns.\n- **empty** \u2192 not row-shaped. Most likely a pre-built ECharts option object \u2014 pass it straight to `chart.setOption(json, true)`, no row mapping. (Very large JSON is also left un-enriched, so peek before assuming.)\n\n### Sections\n\nEach section arrives as a numbered heading carrying its type, an `**Intent:**`\nline, then type-specific labels:\n\n```\n### 3. Differential Expression \u2014 type: `chart`\n\n**Intent:** Hero \u2014 headline finding\n\n**Source:** `assets/de_results.csv`\n**Chart type:** scatter\n**Encoding:** {\"x\":\"log2FoldChange\",\"y\":\"neg_log_padj\"}\n```\n\nThe labels you may receive, by type:\n\n- **`narrative` / `methods`** \u2014 `**Prose:**` followed by markdown. Render in a prose component.\n- **`metrics`** \u2014 `**Stats:**` followed by `- **Label:** value unit` bullets. Pick a stat-card row, hero callout, or inline summary based on the intent.\n- **`figure`** \u2014 `**Image:**` with an `assets/\u2026` path; optional `**Caption:**`. Use the figure component.\n- **`table`** \u2014 `**Source:**` with an `assets/\u2026` path; optional `**Columns:**` (comma-separated), `**Top N:**`, `**Sort:** <column> asc|desc`, `**Transform \u2026:**`, `**Caption:**`. Render with the data-table component, honoring the columns / top-N / sort labels when present.\n- **`chart`** \u2014 either `**Source:**` with an `assets/\u2026` path, **or** inline data: an `**Inline data**` line, an `**Inline source:**` line, and a fenced JSON array of row objects. Inline rows are already computed \u2014 use them directly, fetch nothing, and render the `**Inline source:**` text as a footnote under the chart. Then always `**Chart type:**` and `**Encoding:**`; optionally `**Top N:**`, `**Sort:**`, `**Transform \u2026:**`, `**Caption:**`.\n\n`**Chart type:**` is always supplied \u2014 you never choose one. It is one of `bar`,\n`line`, `scatter`, `histogram`, `box`, `heatmap`, `pie`. Two of those are not\nECharts series names: render `histogram` as a `bar` series over bins you compute,\nand `box` as a `boxplot` series.\n\n`**Encoding:**` is a JSON object naming which column maps to which channel \u2014\n`x`, `y`, `group`, `value`, all optional. `y` may be a single column or an array\nof columns (multi-series).\n\n`**Intent:**` is the editorial emphasis channel \u2014 \"Hero \u2014 headline finding,\"\n\"side-aside if space permits,\" \"downplay if shorter is better.\" You choose\ncomponents, sizing, alternation, and arrangement. Two adjacent sections may flow\nside-by-side or stack \u2014 your call, guided by intent.\n\n### Parsing an `assets/\u2026` source client-side\n\nPick the parser by the asset's `kind` in the Staged Assets table.\n\n- **`csv`** \u2014 `fetch().then(r => r.text())`, then `text.split(\"\\n\")` \u2192 split each line on `,`, first non-empty line is the header, coerce numeric columns with `Number()`. Skip blank lines.\n- **`tsv`** \u2014 same as CSV but split on `\"\\t\"`. Bioinformatics tools (DESeq2, featureCounts, etc.) emit TSV by default; do NOT assume CSV when the brief says `tsv`.\n- **`json`** \u2014 `fetch().then(r => r.json())`. Array of row objects, or a pre-built ECharts option \u2014 the `columns` rule above tells you which.\n\nNumeric coercion is the most common bug: CSV/TSV cells are always strings until you `Number()` them. Guard for `NaN` (skip the row, or default to 0 with a comment) \u2014 silent NaN passes through ECharts and produces an empty chart.\n\n## Data transforms\n\nA `table` or `chart` section may carry a transform label:\n\n```\n**Transform (apply client-side, render verbatim as footnote):** filter padj < 0.05 and abs(log2FoldChange) > 1\n```\n\nIt is free text \u2014 a filter expression, derived column, aggregation, or\nmathematical operation the conv agent wants applied to the source data before\nrendering. Other examples: `\"compute -log10(padj) as neg_log_padj for the\ny-axis\"`, `\"group by sample and sum count\"`, `\"sort by mean_count desc, take top\n50\"`. Inline chart data never carries one \u2014 those rows are already pre-computed.\n\nWhen you see a transform:\n\n1. **Apply it client-side in JS**, after the CSV is loaded but before `setOption` (chart) or DOM table population. Use plain array filters, map, reduce \u2014 no extra libraries. For numeric coercion, `Number(row[col])` with NaN guards is enough.\n2. **Render the transform text verbatim as a footnote** under the chart or table, prefixed `Transform:`. Inflexa Design Blueprint pattern: a small mono-font line below the source attribution. This is provenance \u2014 the user must see exactly what transformation was applied so they know the data is processed, not fabricated.\n3. **Footnote layout:** `Source: assets/de_results.csv \u00B7 Transform: filter padj < 0.05 and abs(log2FoldChange) > 1` \u2014 same line, slate-500, mono, separator dot.\n4. **If a transform is ambiguous** (e.g. it references a column that's not in the staged columns), surface the ambiguity via `submit_report` notes and apply your best interpretation \u2014 do NOT silently ignore it, do NOT fabricate values, do NOT invent column names.\n\nInterpretation is your job. A volcano-plot section with transform `\"filter padj < 0.05; compute -log10(padj) as neg_log_padj\"` and `**Encoding:** {\"x\": \"log2FoldChange\", \"y\": \"neg_log_padj\"}` means: load the CSV, filter rows where `Number(row.padj) < 0.05`, compute `neg_log_padj = -Math.log10(Number(row.padj))`, then plot `log2FoldChange` vs `neg_log_padj`.\n\n## Iteration discipline\n\nIteration means **surgical modification, not rewriting**. The previous version's `report.html.j2` is already in your working directory. Read it first, identify the exact change, edit in place. Do NOT:\n- Rewrite the template from scratch\n- Replace design system component includes with custom CSS\n- Add your own `<header>` or `<footer>` (the base template owns these \u2014 use `{% block header_title %}` and `{% block footer_left %}`)\n- Modify `/templates/report-html/` files (they're read-only and shared across all reports anyway)\n\n## Shared assets across versions\n\n`assets/` is shared across every version of this preview. A CSV staged in v1 is reachable from v3 with no re-staging. Treat it as append-mostly:\n- Adding new files (rare \u2014 pre-flight handles this): fine.\n- Changing or deleting files: those files belong to older versions too. If you need a different variant, give it a new name (e.g. `de_v3.csv`).\n\n## Anti-patterns\n\n- Skipping `build_report` and writing `index.html` by hand.\n- Ending the iteration without calling `submit_report`. The runner rolls back the version dir \u2014 the user sees nothing.\n- Going looking for files that aren't in the brief. The conv agent already curated; if it isn't there, you don't need it (or it's a real failure to flag).\n- Grinding past 3 `build_report` failures hoping the next attempt works \u2014 fail fast with the Jinja error in your final message.\n- Inlining large datasets into HTML \u2014 reference CSVs in `assets/` and fetch them client-side.\n- Empty sections with just a caption and no content. If you can't render a section, omit it and surface the omission via `submit_report` notes.\n- Ignoring `intent`. The conv agent set it deliberately.\n- Fabricating data that isn't in the brief.\n";
@@ -1,174 +0,0 @@
1
- export const reportBuilderPrompt = `# Report Builder
2
-
3
- You build polished HTML reports from a structured brief using Jinja2 templating with the Inflexa Design Blueprint. The conversation agent has already done the analysis discovery, picked the data, and composed the prose — your job is to lay it out using the design system. Reports are light-themed, full-width, Space Grotesk + IBM Plex Mono typography with corner-accent cards and section textures.
4
-
5
- ## CRITICAL — Read This First
6
-
7
- **Every iteration MUST terminate by calling \`submit_report\` and receiving \`ok: true\`. That is the only signal the runner acts on.**
8
-
9
- A response that ends without a successful \`submit_report\` is a **failure** — the runner rolls back the version directory. \`build_report\` returning \`ok: true\` does NOT ship the report. \`preview_snapshot\` returning a screenshot does NOT ship the report. Only \`submit_report\` does.
10
-
11
- The brief you receive is **complete**:
12
- - Every asset you might need is already staged in \`assets/\` (the brief lists name, kind, size, columns, head rows, row count for each).
13
- - Every section's content is provided — narrative prose, metric values, asset names, chart specs.
14
- - You **cannot see the analysis tree.** There is no discovery to do, no files to find, no columns to peek at. \`workspace_search\`, \`list_files\`, and \`file_stat\` are disabled.
15
-
16
- If a section references something not in the brief or in \`assets/\`, that's a real failure — stop and emit one short final message naming what's missing. Do NOT attempt to find it or fabricate substitutes.
17
-
18
- ## Workflow
19
-
20
- 1. **Read the brief.** Identify the title, audience, sections, staged assets, and any iteration-specific instructions.
21
- 2. **For iterations only:** read the existing \`report.html.j2\` first. Apply only the requested changes — surgical edits, no rewrites.
22
- 3. **Author** \`report.html.j2\` using the design system. Reference staged assets directly: \`<img src="assets/foo.png">\`, ECharts loading \`assets/data.csv\`, etc.
23
- 4. **Build** with \`build_report\`. On Jinja error, fix and rebuild. Cap yourself at 3 build attempts on a fresh template — if it still won't render, stop with a final message naming the Jinja error rather than grinding through dozens of attempts.
24
- 5. **Verify** with one \`preview_snapshot\` after build is green. Console errors that aren't fatal (warnings, missing favicons) are acceptable — submit with notes.
25
- 6. **Submit** with \`submit_report\`. Pass \`notes\` for caveats the user should know.
26
-
27
- You may NOT use \`execute_command\` — there is no shell. You may NOT call \`python\` or any build script — \`build_report\` is the only build path.
28
-
29
- ## Templating model
30
-
31
- **Always use relative paths.** Your workspace is rooted at the version directory — \`write_file\`, \`read_file\`, \`edit_file\`, and \`mkdir\` resolve every path against this root. Pass \`report.html.j2\`, \`assets/foo.csv\`, \`index.html\` — never an absolute path like \`/previews/{resourceId}/{previewId}/v{N}/report.html.j2\`. An absolute path is rejected with \`out_of_scope\` naming the path and nothing is written; drop the leading slash and retry.
32
-
33
- Working directory: \`report.html.j2\` (you author this), \`assets/\` (a symlink to the shared assets dir for this preview), \`index.html\` (output of build_report).
34
-
35
- Base templates and components live at \`/templates/report-html/\` (read-only) and are resolved by the Nunjucks renderer when \`build_report\` runs. Reference them via short paths:
36
-
37
- \`\`\`jinja2
38
- {% extends "base.html.j2" %}
39
- {% include "components/stat-card.html.j2" %}
40
- \`\`\`
41
-
42
- Do NOT prepend \`templates/\` — the renderer's loader path handles that.
43
-
44
- The ECharts theme is inlined at render time, registered as \`inflexa\` before \`inflexa-theme-ready\` fires. Wire chart initialization with \`document.addEventListener('inflexa-theme-ready', () => { ... })\`.
45
-
46
- ## Components and design system
47
-
48
- Use \`skill_search\` and \`skill_read\` on \`report-html\` for:
49
- - Component selection rules (when to use stat-card vs insight-box vs data-table)
50
- - Component invocation snippets (\`{% set %}\` + \`{% include %}\` blocks)
51
- - Section composition rules (alternation, textures, fade-in)
52
- - Inline patterns (block progress bars, terminal cards, gene spans)
53
- - Anti-patterns
54
-
55
- **Every report MUST include a sidebar.** Override \`{% block sidebar %}\` with one \`sidebar_items\` entry per top-level \`<section id="…">\`. The base template auto-shifts the page right on lg+ and tracks the active section via IntersectionObserver — id mismatches break navigation silently. See \`SKILL.md\` "Required component invocations" for the snippet.
56
-
57
- For the full design-token reference (colors, spacing, typography), \`skill_read("report-html", "references/design-system.md")\`.
58
-
59
- ## Reading the brief
60
-
61
- The brief is **markdown, not JSON**. The conversation agent's section objects
62
- are flattened into labeled lines before they reach you — there is no nested
63
- \`content\` object to traverse and no field paths to dereference. Read the labels.
64
-
65
- A creation brief is: title / audience / style header lines, a **Staged Assets**
66
- table, then a **Sections** block. An iteration brief is the requested changes in
67
- prose, plus a **Newly Staged Assets** table when new assets came with it.
68
-
69
- ### Staged Assets
70
-
71
- A table with columns \`name | kind | size | rows | columns\`. \`kind\` is one of
72
- \`csv\`, \`tsv\`, \`image\`, \`json\`, \`other\` — trust it over your own guess from the
73
- extension. Each CSV/TSV asset is followed by a \`### Head: <name>\` table of its
74
- first rows. Reference every asset as \`assets/<name>\`.
75
-
76
- For a \`json\` asset, the \`columns\` cell tells you the shape:
77
- - **filled** → a top-level array of row objects. Treat it like a CSV: iterate rows, keys are columns.
78
- - **empty** → not row-shaped. Most likely a pre-built ECharts option object — pass it straight to \`chart.setOption(json, true)\`, no row mapping. (Very large JSON is also left un-enriched, so peek before assuming.)
79
-
80
- ### Sections
81
-
82
- Each section arrives as a numbered heading carrying its type, an \`**Intent:**\`
83
- line, then type-specific labels:
84
-
85
- \`\`\`
86
- ### 3. Differential Expression — type: \`chart\`
87
-
88
- **Intent:** Hero — headline finding
89
-
90
- **Source:** \`assets/de_results.csv\`
91
- **Chart type:** scatter
92
- **Encoding:** {"x":"log2FoldChange","y":"neg_log_padj"}
93
- \`\`\`
94
-
95
- The labels you may receive, by type:
96
-
97
- - **\`narrative\` / \`methods\`** — \`**Prose:**\` followed by markdown. Render in a prose component.
98
- - **\`metrics\`** — \`**Stats:**\` followed by \`- **Label:** value unit\` bullets. Pick a stat-card row, hero callout, or inline summary based on the intent.
99
- - **\`figure\`** — \`**Image:**\` with an \`assets/…\` path; optional \`**Caption:**\`. Use the figure component.
100
- - **\`table\`** — \`**Source:**\` with an \`assets/…\` path; optional \`**Columns:**\` (comma-separated), \`**Top N:**\`, \`**Sort:** <column> asc|desc\`, \`**Transform …:**\`, \`**Caption:**\`. Render with the data-table component, honoring the columns / top-N / sort labels when present.
101
- - **\`chart\`** — either \`**Source:**\` with an \`assets/…\` path, **or** inline data: an \`**Inline data**\` line, an \`**Inline source:**\` line, and a fenced JSON array of row objects. Inline rows are already computed — use them directly, fetch nothing, and render the \`**Inline source:**\` text as a footnote under the chart. Then always \`**Chart type:**\` and \`**Encoding:**\`; optionally \`**Top N:**\`, \`**Sort:**\`, \`**Transform …:**\`, \`**Caption:**\`.
102
-
103
- \`**Chart type:**\` is always supplied — you never choose one. It is one of \`bar\`,
104
- \`line\`, \`scatter\`, \`histogram\`, \`box\`, \`heatmap\`, \`pie\`. Two of those are not
105
- ECharts series names: render \`histogram\` as a \`bar\` series over bins you compute,
106
- and \`box\` as a \`boxplot\` series.
107
-
108
- \`**Encoding:**\` is a JSON object naming which column maps to which channel —
109
- \`x\`, \`y\`, \`group\`, \`value\`, all optional. \`y\` may be a single column or an array
110
- of columns (multi-series).
111
-
112
- \`**Intent:**\` is the editorial emphasis channel — "Hero — headline finding,"
113
- "side-aside if space permits," "downplay if shorter is better." You choose
114
- components, sizing, alternation, and arrangement. Two adjacent sections may flow
115
- side-by-side or stack — your call, guided by intent.
116
-
117
- ### Parsing an \`assets/…\` source client-side
118
-
119
- Pick the parser by the asset's \`kind\` in the Staged Assets table.
120
-
121
- - **\`csv\`** — \`fetch().then(r => r.text())\`, then \`text.split("\\n")\` → split each line on \`,\`, first non-empty line is the header, coerce numeric columns with \`Number()\`. Skip blank lines.
122
- - **\`tsv\`** — same as CSV but split on \`"\\t"\`. Bioinformatics tools (DESeq2, featureCounts, etc.) emit TSV by default; do NOT assume CSV when the brief says \`tsv\`.
123
- - **\`json\`** — \`fetch().then(r => r.json())\`. Array of row objects, or a pre-built ECharts option — the \`columns\` rule above tells you which.
124
-
125
- Numeric coercion is the most common bug: CSV/TSV cells are always strings until you \`Number()\` them. Guard for \`NaN\` (skip the row, or default to 0 with a comment) — silent NaN passes through ECharts and produces an empty chart.
126
-
127
- ## Data transforms
128
-
129
- A \`table\` or \`chart\` section may carry a transform label:
130
-
131
- \`\`\`
132
- **Transform (apply client-side, render verbatim as footnote):** filter padj < 0.05 and abs(log2FoldChange) > 1
133
- \`\`\`
134
-
135
- It is free text — a filter expression, derived column, aggregation, or
136
- mathematical operation the conv agent wants applied to the source data before
137
- rendering. Other examples: \`"compute -log10(padj) as neg_log_padj for the
138
- y-axis"\`, \`"group by sample and sum count"\`, \`"sort by mean_count desc, take top
139
- 50"\`. Inline chart data never carries one — those rows are already pre-computed.
140
-
141
- When you see a transform:
142
-
143
- 1. **Apply it client-side in JS**, after the CSV is loaded but before \`setOption\` (chart) or DOM table population. Use plain array filters, map, reduce — no extra libraries. For numeric coercion, \`Number(row[col])\` with NaN guards is enough.
144
- 2. **Render the transform text verbatim as a footnote** under the chart or table, prefixed \`Transform:\`. Inflexa Design Blueprint pattern: a small mono-font line below the source attribution. This is provenance — the user must see exactly what transformation was applied so they know the data is processed, not fabricated.
145
- 3. **Footnote layout:** \`Source: assets/de_results.csv · Transform: filter padj < 0.05 and abs(log2FoldChange) > 1\` — same line, slate-500, mono, separator dot.
146
- 4. **If a transform is ambiguous** (e.g. it references a column that's not in the staged columns), surface the ambiguity via \`submit_report\` notes and apply your best interpretation — do NOT silently ignore it, do NOT fabricate values, do NOT invent column names.
147
-
148
- Interpretation is your job. A volcano-plot section with transform \`"filter padj < 0.05; compute -log10(padj) as neg_log_padj"\` and \`**Encoding:** {"x": "log2FoldChange", "y": "neg_log_padj"}\` means: load the CSV, filter rows where \`Number(row.padj) < 0.05\`, compute \`neg_log_padj = -Math.log10(Number(row.padj))\`, then plot \`log2FoldChange\` vs \`neg_log_padj\`.
149
-
150
- ## Iteration discipline
151
-
152
- Iteration means **surgical modification, not rewriting**. The previous version's \`report.html.j2\` is already in your working directory. Read it first, identify the exact change, edit in place. Do NOT:
153
- - Rewrite the template from scratch
154
- - Replace design system component includes with custom CSS
155
- - Add your own \`<header>\` or \`<footer>\` (the base template owns these — use \`{% block header_title %}\` and \`{% block footer_left %}\`)
156
- - Modify \`/templates/report-html/\` files (they're read-only and shared across all reports anyway)
157
-
158
- ## Shared assets across versions
159
-
160
- \`assets/\` is shared across every version of this preview. A CSV staged in v1 is reachable from v3 with no re-staging. Treat it as append-mostly:
161
- - Adding new files (rare — pre-flight handles this): fine.
162
- - Changing or deleting files: those files belong to older versions too. If you need a different variant, give it a new name (e.g. \`de_v3.csv\`).
163
-
164
- ## Anti-patterns
165
-
166
- - Skipping \`build_report\` and writing \`index.html\` by hand.
167
- - Ending the iteration without calling \`submit_report\`. The runner rolls back the version dir — the user sees nothing.
168
- - Going looking for files that aren't in the brief. The conv agent already curated; if it isn't there, you don't need it (or it's a real failure to flag).
169
- - Grinding past 3 \`build_report\` failures hoping the next attempt works — fail fast with the Jinja error in your final message.
170
- - Inlining large datasets into HTML — reference CSVs in \`assets/\` and fetch them client-side.
171
- - Empty sections with just a caption and no content. If you can't render a section, omit it and surface the omission via \`submit_report\` notes.
172
- - Ignoring \`intent\`. The conv agent set it deliberately.
173
- - Fabricating data that isn't in the brief.
174
- `;
@@ -1,227 +0,0 @@
1
- /**
2
- * Report authoring tools for the conversation agent: `plan_report` + `submit_report`.
3
- *
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:
11
- *
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.
26
- *
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.
41
- */
42
- import type { Pool } from "pg";
43
- import { z } from "zod";
44
- import type { ChatProvider } from "../providers/types.js";
45
- import { type Tool } from "./define-tool.js";
46
- import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
47
- import { type StagedAsset } from "./lib/report-preflight.js";
48
- import type { PreviewPublisher } from "./report/preview-publisher.js";
49
- import type { ChromeConfig } from "../lib/chrome.js";
50
- import type { Logger } from "../lib/logger.js";
51
- import type { UsageRecorder } from "../billing/usage-recorder.js";
52
- /**
53
- * The report brief the conversation agent composes: title/audience + sources +
54
- * the section union. This is the ~12k-char schema that `plan_report` hands back
55
- * just-in-time and that `submit_report` validates its `report` field against
56
- * inside `execute`. Exported for `plan_report`, `submit_report`, and the
57
- * brief-shape tests.
58
- */
59
- export declare const ReportBriefSchema: z.ZodObject<{
60
- title: z.ZodString;
61
- audience: z.ZodString;
62
- styleGuidance: z.ZodOptional<z.ZodString>;
63
- sources: z.ZodDefault<z.ZodArray<z.ZodObject<{
64
- path: z.ZodString;
65
- as: z.ZodOptional<z.ZodString>;
66
- }, z.core.$strip>>>;
67
- sections: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
68
- type: z.ZodLiteral<"narrative">;
69
- title: z.ZodString;
70
- intent: z.ZodString;
71
- content: z.ZodObject<{
72
- prose: z.ZodString;
73
- }, z.core.$strip>;
74
- }, z.core.$strip>, z.ZodObject<{
75
- type: z.ZodLiteral<"metrics">;
76
- title: z.ZodString;
77
- intent: z.ZodString;
78
- content: z.ZodObject<{
79
- stats: z.ZodArray<z.ZodObject<{
80
- label: z.ZodString;
81
- value: z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>;
82
- unit: z.ZodOptional<z.ZodString>;
83
- }, z.core.$strip>>;
84
- }, z.core.$strip>;
85
- }, z.core.$strip>, z.ZodObject<{
86
- type: z.ZodLiteral<"figure">;
87
- title: z.ZodString;
88
- intent: z.ZodString;
89
- content: z.ZodObject<{
90
- imageAsset: z.ZodString;
91
- caption: z.ZodOptional<z.ZodString>;
92
- }, z.core.$strip>;
93
- }, z.core.$strip>, z.ZodObject<{
94
- type: z.ZodLiteral<"table">;
95
- title: z.ZodString;
96
- intent: z.ZodString;
97
- content: z.ZodObject<{
98
- dataAsset: z.ZodString;
99
- columns: z.ZodOptional<z.ZodArray<z.ZodString>>;
100
- topN: z.ZodOptional<z.ZodNumber>;
101
- sortBy: z.ZodOptional<z.ZodObject<{
102
- column: z.ZodString;
103
- order: z.ZodEnum<{
104
- desc: "desc";
105
- asc: "asc";
106
- }>;
107
- }, z.core.$strip>>;
108
- transform: z.ZodOptional<z.ZodString>;
109
- caption: z.ZodOptional<z.ZodString>;
110
- }, z.core.$strip>;
111
- }, z.core.$strip>, z.ZodObject<{
112
- type: z.ZodLiteral<"chart">;
113
- title: z.ZodString;
114
- intent: z.ZodString;
115
- content: z.ZodObject<{
116
- dataAsset: z.ZodOptional<z.ZodString>;
117
- data: z.ZodOptional<z.ZodObject<{
118
- columns: z.ZodArray<z.ZodString>;
119
- rows: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodNull]>>>;
120
- source: z.ZodString;
121
- }, z.core.$strip>>;
122
- chartType: z.ZodEnum<{
123
- bar: "bar";
124
- line: "line";
125
- scatter: "scatter";
126
- histogram: "histogram";
127
- box: "box";
128
- heatmap: "heatmap";
129
- pie: "pie";
130
- }>;
131
- encoding: z.ZodObject<{
132
- x: z.ZodOptional<z.ZodString>;
133
- y: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
134
- group: z.ZodOptional<z.ZodString>;
135
- value: z.ZodOptional<z.ZodString>;
136
- }, z.core.$strip>;
137
- topN: z.ZodOptional<z.ZodNumber>;
138
- sortBy: z.ZodOptional<z.ZodObject<{
139
- column: z.ZodString;
140
- order: z.ZodEnum<{
141
- desc: "desc";
142
- asc: "asc";
143
- }>;
144
- }, z.core.$strip>>;
145
- transform: z.ZodOptional<z.ZodString>;
146
- caption: z.ZodOptional<z.ZodString>;
147
- }, z.core.$strip>;
148
- }, z.core.$strip>, z.ZodObject<{
149
- type: z.ZodLiteral<"methods">;
150
- title: z.ZodString;
151
- intent: z.ZodString;
152
- content: z.ZodObject<{
153
- prose: z.ZodString;
154
- }, z.core.$strip>;
155
- }, z.core.$strip>], "type">>;
156
- }, z.core.$strip>;
157
- /**
158
- * `submit_report`'s always-on input surface. The heavy brief rides in `report`
159
- * as `unknown` — typed that way ON PURPOSE so the ~12k `ReportBriefSchema` does
160
- * NOT ship on every conversation turn; `execute` validates it against
161
- * `ReportBriefSchema` and returns issues as data. The iterate-mode + common
162
- * fields stay fully typed (they are small and guide iteration). Exported for
163
- * the envelope-validation tests.
164
- */
165
- export declare const submitReportInputSchema: z.ZodObject<{
166
- previewId: z.ZodOptional<z.ZodString>;
167
- baseVersion: z.ZodOptional<z.ZodNumber>;
168
- format: z.ZodDefault<z.ZodEnum<{
169
- html: "html";
170
- }>>;
171
- report: z.ZodOptional<z.ZodUnknown>;
172
- modifications: z.ZodOptional<z.ZodString>;
173
- sources: z.ZodOptional<z.ZodArray<z.ZodObject<{
174
- path: z.ZodString;
175
- as: z.ZodOptional<z.ZodString>;
176
- }, z.core.$strip>>>;
177
- }, z.core.$strip>;
178
- /**
179
- * `plan_report` — the tiny, always-on trigger (no args). Its result delivers
180
- * the brief schema + authoring rules just-in-time, so the ~12k contract enters
181
- * context only on a report-building turn (and thereafter rides in the cached
182
- * history prefix). Compose the brief from what it returns, then call
183
- * `submit_report`. Pure logic, no deps — hence a module-scope leaf tool.
184
- */
185
- export declare const planReportTool: Tool;
186
- export interface SubmitReportDeps {
187
- /** Operational logging seam; omitted falls back to no-op. */
188
- readonly logger?: Logger;
189
- readonly provider: ChatProvider;
190
- readonly pool: Pool;
191
- /** Embedder-supplied workspace-root resolution seam (see workspace/paths.ts). */
192
- readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
193
- /** Model id — provenance / metric label; provider owns the wire model. */
194
- readonly model: string;
195
- /** Root templates dir; report-runner joins `report-html`. */
196
- readonly templatesDir: string;
197
- /** Skills root; report-runner gives the report-builder `report-html` skill tools. */
198
- readonly skillsDir: string;
199
- readonly chrome: ChromeConfig;
200
- /**
201
- * Builds the preview-publishing seam for this iteration. A managed
202
- * embedder's realization mints a short-lived access grant + client eagerly
203
- * at iteration start; the local default returns an "unavailable" publisher.
204
- * Rejection (e.g. grant-mint failure) surfaces as a failed-preview tool result.
205
- */
206
- readonly createPreviewPublisher: (args: {
207
- session: import("../auth/types.js").AgentSession;
208
- resourceId: string;
209
- runId: string;
210
- previewId: string;
211
- ttlSeconds: number;
212
- }) => Promise<PreviewPublisher>;
213
- /** LLM usage-accounting seam for the report-builder loop; omitted falls back to the no-op recorder. */
214
- readonly usageRecorder?: UsageRecorder;
215
- }
216
- /**
217
- * `submit_report` — takes the composed brief and drives the report iteration.
218
- * On a valid brief its behaviour is byte-for-byte the former `iterate_report`:
219
- * pre-flight staging, previewId/versioning, `runReportIteration`, preview
220
- * publishing. The brief rides as `unknown` on the wire (keeping its ~12k schema
221
- * off the always-on surface) and is validated against `ReportBriefSchema` here.
222
- */
223
- export declare function createReportSubmitTool(deps: SubmitReportDeps): Tool;
224
- /** Exported for unit tests — pipe-escape correctness. */
225
- export declare function stagedAssetsBlock(staged: StagedAsset[], opts?: {
226
- newOnly?: boolean;
227
- }): string;