@inflexa-ai/harness 0.19.0 → 0.19.2

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 (101) hide show
  1. package/dist/agents/report-session-agent.d.ts +32 -9
  2. package/dist/agents/report-session-agent.js +22 -6
  3. package/dist/app/report-session-runtime.js +26 -0
  4. package/dist/app/spawn-report-session.d.ts +32 -5
  5. package/dist/app/spawn-report-session.js +19 -0
  6. package/dist/auth/local-run-authorizer.d.ts +3 -2
  7. package/dist/auth/local-run-authorizer.js +4 -2
  8. package/dist/config/environment-stores.d.ts +12 -7
  9. package/dist/config/resource-limits.d.ts +6 -10
  10. package/dist/config/resource-limits.js +6 -44
  11. package/dist/contracts/report-reference.d.ts +4 -0
  12. package/dist/contracts/report-reference.js +3 -0
  13. package/dist/execution/run-authorizer.d.ts +18 -0
  14. package/dist/execution/run-canceler.d.ts +67 -0
  15. package/dist/execution/run-canceler.js +130 -0
  16. package/dist/index.d.ts +7 -3
  17. package/dist/index.js +6 -1
  18. package/dist/lib/chrome.d.ts +14 -0
  19. package/dist/lib/chrome.js +7 -0
  20. package/dist/lib/page-capture.d.ts +47 -0
  21. package/dist/lib/page-capture.js +83 -0
  22. package/dist/loop/run-agent.js +104 -15
  23. package/dist/memory/ai-sdk-message-storage.d.ts +9 -4
  24. package/dist/memory/ai-sdk-message-storage.js +9 -4
  25. package/dist/memory/count-tokens.js +34 -5
  26. package/dist/prompts/report-session.d.ts +1 -1
  27. package/dist/prompts/report-session.js +21 -0
  28. package/dist/providers/ai-sdk.d.ts +81 -3
  29. package/dist/providers/ai-sdk.js +91 -10
  30. package/dist/providers/types.d.ts +17 -0
  31. package/dist/report-model/assert-rules.d.ts +44 -0
  32. package/dist/report-model/assert-rules.js +97 -0
  33. package/dist/report-model/draft-hash.d.ts +14 -0
  34. package/dist/report-model/draft-hash.js +36 -0
  35. package/dist/report-model/fixture-resolver.js +2 -71
  36. package/dist/report-model/production-resolver.d.ts +124 -0
  37. package/dist/report-model/production-resolver.js +801 -0
  38. package/dist/report-model/reference-resolver.d.ts +6 -0
  39. package/dist/report-model/validate.js +5 -0
  40. package/dist/report-render/assets.d.ts +42 -0
  41. package/dist/report-render/assets.js +61 -0
  42. package/dist/report-render/chart.js +31 -10
  43. package/dist/report-render/design.d.ts +120 -0
  44. package/dist/report-render/design.js +808 -0
  45. package/dist/report-render/page.d.ts +51 -111
  46. package/dist/report-render/page.js +158 -268
  47. package/dist/report-render/render.js +34 -11
  48. package/dist/report-render/views/chart-view.d.ts +6 -3
  49. package/dist/report-render/views/chart-view.js +10 -5
  50. package/dist/report-render/views/page-view.d.ts +19 -8
  51. package/dist/report-render/views/page-view.js +31 -11
  52. package/dist/report-render/views/prose.d.ts +1 -0
  53. package/dist/report-render/views/prose.js +7 -13
  54. package/dist/report-render/views/references-view.js +5 -5
  55. package/dist/report-render/views/values.d.ts +15 -6
  56. package/dist/report-render/views/values.js +29 -10
  57. package/dist/runtime/assemble.d.ts +38 -4
  58. package/dist/runtime/assemble.js +43 -3
  59. package/dist/sandbox/k8s-client.js +18 -0
  60. package/dist/sandbox/mount-plan.d.ts +7 -0
  61. package/dist/sandbox/mount-plan.js +7 -2
  62. package/dist/sandbox/types.d.ts +6 -0
  63. package/dist/state/analyses.d.ts +3 -26
  64. package/dist/state/analyses.js +6 -64
  65. package/dist/state/index.d.ts +2 -2
  66. package/dist/state/index.js +2 -2
  67. package/dist/state/init.js +23 -10
  68. package/dist/state/report-session-state.d.ts +27 -0
  69. package/dist/state/report-session-state.js +29 -2
  70. package/dist/state/runs.d.ts +10 -0
  71. package/dist/state/runs.js +21 -0
  72. package/dist/tasks/data-profile.d.ts +4 -0
  73. package/dist/tasks/data-profile.js +14 -0
  74. package/dist/tasks/extract-values-script.d.ts +64 -0
  75. package/dist/tasks/extract-values-script.js +180 -0
  76. package/dist/tasks/extract-values.d.ts +124 -0
  77. package/dist/tasks/extract-values.js +193 -0
  78. package/dist/tools/approval/gateway.d.ts +8 -1
  79. package/dist/tools/approval/gateway.js +9 -2
  80. package/dist/tools/approval/queries.d.ts +9 -2
  81. package/dist/tools/approval/queries.js +12 -4
  82. package/dist/tools/define-tool.d.ts +29 -0
  83. package/dist/tools/define-tool.js +26 -0
  84. package/dist/tools/report/preview-snapshot.d.ts +1 -1
  85. package/dist/tools/report/preview-snapshot.js +11 -50
  86. package/dist/tools/report-authoring/authoring-tools.d.ts +54 -4
  87. package/dist/tools/report-authoring/authoring-tools.js +1 -1
  88. package/dist/tools/report-session/examine-page.d.ts +90 -0
  89. package/dist/tools/report-session/examine-page.js +136 -0
  90. package/dist/tools/report-session/index.d.ts +3 -1
  91. package/dist/tools/report-session/index.js +3 -1
  92. package/dist/tools/report-session/preview-report.d.ts +31 -6
  93. package/dist/tools/report-session/preview-report.js +63 -16
  94. package/dist/tools/report-session/record-version.d.ts +102 -0
  95. package/dist/tools/report-session/record-version.js +139 -0
  96. package/dist/tools/sandbox/list-available-packages.js +2 -1
  97. package/dist/tools/sandbox/list-available-refs.d.ts +5 -1
  98. package/dist/tools/sandbox/list-available-refs.js +13 -6
  99. package/dist/workflows/sandbox-step.d.ts +4 -0
  100. package/dist/workflows/sandbox-step.js +21 -2
  101. package/package.json +14 -8
@@ -16,10 +16,10 @@
16
16
  import { err, ok } from "neverthrow";
17
17
  import { renderChart } from "./views/chart-view.js";
18
18
  import { deriveChartOption } from "./chart.js";
19
- import { assemblePage, renderReferenceSection } from "./views/page-view.js";
19
+ import { assemblePage, renderBand, renderReferenceSection } from "./views/page-view.js";
20
20
  import { renderClaim, renderNav, renderSection, renderText } from "./views/prose.js";
21
21
  import { ReferenceLedger } from "./references.js";
22
- import { renderCitation, renderFigure, renderMetric, renderTable } from "./views/values.js";
22
+ import { renderCitation, renderFigure, renderMetric, renderMetricGrid, renderTable } from "./views/values.js";
23
23
  /**
24
24
  * Render a report document and its value map to one HTML string.
25
25
  *
@@ -30,14 +30,14 @@ export function renderReportPage(document, values) {
30
30
  const problems = [];
31
31
  const ledger = new ReferenceLedger();
32
32
  const content = [];
33
- for (const section of document.sections) {
34
- content.push(renderBlock(section, values, ledger, 0, problems));
33
+ for (const [index, section] of document.sections.entries()) {
34
+ content.push(renderBand(index, renderBlock(section, values, ledger, 0, problems)));
35
35
  }
36
36
  if (problems.length > 0) {
37
37
  return err(problems);
38
38
  }
39
39
  const nav = renderNav(document.sections);
40
- const references = renderReferenceSection(ledger);
40
+ const references = renderReferenceSection(ledger, document.sections.length);
41
41
  return ok(assemblePage(document.title, nav, content.join(""), references));
42
42
  }
43
43
  /**
@@ -106,14 +106,37 @@ function renderBlock(block, values, ledger, depth, problems) {
106
106
  }
107
107
  return renderChart(block, option.value);
108
108
  }
109
- case "section": {
110
- const parts = [];
111
- for (const child of block.blocks) {
112
- parts.push(renderBlock(child, values, ledger, depth + 1, problems));
113
- }
114
- return renderSection(block, depth, parts.join(""));
109
+ case "section":
110
+ return renderSection(block, depth, renderChildren(block.blocks, values, ledger, depth + 1, problems));
111
+ }
112
+ }
113
+ /**
114
+ * Render the children of one section, and group a consecutive run of metric siblings into one grid.
115
+ *
116
+ * A run of two or more metric blocks reads as one row of statistics, thus the grid holds the whole run. A
117
+ * lone metric stays one card, and no grid wraps it. A block of a different kind ends the run.
118
+ */
119
+ function renderChildren(blocks, values, ledger, depth, problems) {
120
+ const parts = [];
121
+ let index = 0;
122
+ while (index < blocks.length) {
123
+ const run = metricRunLength(blocks, index);
124
+ const rendered = [];
125
+ for (let offset = 0; offset < Math.max(run, 1); offset += 1) {
126
+ rendered.push(renderBlock(blocks[index + offset], values, ledger, depth, problems));
115
127
  }
128
+ parts.push(run > 1 ? renderMetricGrid(rendered.join("")) : rendered.join(""));
129
+ index += Math.max(run, 1);
130
+ }
131
+ return parts.join("");
132
+ }
133
+ /** The count of the metric blocks that start at `start`. A block of a different kind gives zero. */
134
+ function metricRunLength(blocks, start) {
135
+ let length = 0;
136
+ while (start + length < blocks.length && blocks[start + length].kind === "metric") {
137
+ length += 1;
116
138
  }
139
+ return length;
117
140
  }
118
141
  /** A `missing-value` problem that names the block and the shape that it needs. */
119
142
  function missingValue(blockId, expected) {
@@ -1,11 +1,14 @@
1
1
  /**
2
- * The chart container markup.
2
+ * The chart panel markup.
3
3
  *
4
4
  * The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
5
5
  * `<script type="application/json">` element MUST follow the container div with no element between them.
6
- * The title renders above the container, and the caption renders below.
6
+ *
7
+ * The panel is the window-chrome form: the three dots on the left, the title in the center, and the
8
+ * `CORTEX` badge on the right. It is the one component with rounded corners. The chart body carries a fixed
9
+ * height, because the chart runtime measures the container and a container with no height shows no chart.
7
10
  */
8
11
  import type { ChartBlock } from "../../contracts/report-blocks.js";
9
12
  import type { EchartOption } from "../chart.js";
10
- /** Render the container markup for a chart. */
13
+ /** Render the panel markup for a chart. */
11
14
  export declare function renderChart(block: ChartBlock, option: EchartOption): string;
@@ -1,21 +1,26 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
2
  /**
3
- * The chart container markup.
3
+ * The chart panel markup.
4
4
  *
5
5
  * The bootstrap in `page.ts` reads the option from the next element sibling of the container. Thus the
6
6
  * `<script type="application/json">` element MUST follow the container div with no element between them.
7
- * The title renders above the container, and the caption renders below.
7
+ *
8
+ * The panel is the window-chrome form: the three dots on the left, the title in the center, and the
9
+ * `CORTEX` badge on the right. It is the one component with rounded corners. The chart body carries a fixed
10
+ * height, because the chart runtime measures the container and a container with no height shows no chart.
8
11
  */
9
12
  import { raw } from "hono/html";
10
13
  import { scriptJson } from "../script-json.js";
11
- /** Render the container markup for a chart. */
14
+ /** The product badge of a chart panel. */
15
+ const PANEL_BADGE = "CORTEX";
16
+ /** Render the panel markup for a chart. */
12
17
  export function renderChart(block, option) {
13
18
  const containerId = chartContainerId(block.id);
14
19
  // The JSON goes to the page through `raw()`, thus `scriptJson` is the sole guard of this sink. It
15
20
  // replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
16
21
  // element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
17
22
  const json = scriptJson(option);
18
- return String(_jsxs(_Fragment, { children: [block.title !== undefined ? _jsx("p", { class: "chart-title", children: block.title }) : null, _jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) }), block.caption !== undefined ? _jsx("p", { class: "chart-caption", children: block.caption }) : null] }));
23
+ return String(_jsxs("div", { class: "report-chart", children: [_jsxs("div", { class: "window-chrome", children: [_jsxs("div", { class: "window-chrome-bar", children: [_jsxs("div", { class: "chrome-dots", children: [_jsx("span", { class: "chrome-dot dot-1" }), _jsx("span", { class: "chrome-dot dot-2" }), _jsx("span", { class: "chrome-dot dot-3" })] }), block.title !== undefined ? _jsx("span", { class: "window-chrome-title", children: block.title }) : null, _jsx("span", { class: "window-chrome-badge", children: PANEL_BADGE })] }), _jsxs("div", { class: "window-chrome-body", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container" }), _jsx("script", { type: "application/json", children: raw(json) })] })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
19
24
  }
20
25
  /** The container id derives from the block id, thus the id stays stable across renders. */
21
26
  function chartContainerId(blockId) {
@@ -1,17 +1,28 @@
1
1
  /**
2
- * The page assembly.
2
+ * The page assembly: the hero, the bands, the reference band, and the footer.
3
3
  *
4
- * The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
5
- * The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
6
- * navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
7
- * `raw()` inserts them byte for byte.
4
+ * The skeleton inlines the style rules in the head, and it puts the three scripts at the end of the body.
5
+ * The order of the three scripts is a contract. The theme registration runs before the chart bootstrap,
6
+ * thus each chart finds its theme. The fade-in observer also runs before the chart bootstrap, thus the
7
+ * bootstrap finds the reveal gate and it signals readiness after the first reveal pass. The navigation, the
8
+ * main content, and the reference frame arrive as already-escaped markup strings, thus `raw()` inserts them
9
+ * byte for byte.
10
+ *
11
+ * One band holds one top-level section. The band index drives the alternation of the background and of the
12
+ * texture, thus a caller that adds a band passes the next index.
8
13
  */
9
14
  import type { ReferenceLedger } from "../references.js";
10
15
  /**
11
- * Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
12
- * no empty reference frame.
16
+ * Wrap already-escaped markup in one full-bleed band. An even index gives the white background and the dot
17
+ * texture, and an odd index gives the slate background and the grid texture. The noise texture lies over
18
+ * both.
19
+ */
20
+ export declare function renderBand(index: number, inner: string): string;
21
+ /**
22
+ * Wrap the reference list in the final band. An empty ledger gives an empty string, thus the page shows no
23
+ * empty reference band. The index continues the band alternation of the sections.
13
24
  */
14
- export declare function renderReferenceSection(ledger: ReferenceLedger): string;
25
+ export declare function renderReferenceSection(ledger: ReferenceLedger, index: number): string;
15
26
  /**
16
27
  * Assemble the page from the title, the navigation, the main content, and the reference frame. The title
17
28
  * passes through the runtime as text, and the runtime escapes it. The reference frame is optional, thus
@@ -1,16 +1,26 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
2
  /**
3
- * The page assembly.
3
+ * The page assembly: the hero, the bands, the reference band, and the footer.
4
4
  *
5
- * The skeleton inlines the style rules in the head, and it puts the two scripts at the end of the body.
6
- * The theme registration runs before the chart bootstrap, thus each chart finds its theme. The
7
- * navigation, the main content, and the reference frame arrive as already-escaped markup strings, thus
8
- * `raw()` inserts them byte for byte.
5
+ * The skeleton inlines the style rules in the head, and it puts the three scripts at the end of the body.
6
+ * The order of the three scripts is a contract. The theme registration runs before the chart bootstrap,
7
+ * thus each chart finds its theme. The fade-in observer also runs before the chart bootstrap, thus the
8
+ * bootstrap finds the reveal gate and it signals readiness after the first reveal pass. The navigation, the
9
+ * main content, and the reference frame arrive as already-escaped markup strings, thus `raw()` inserts them
10
+ * byte for byte.
11
+ *
12
+ * One band holds one top-level section. The band index drives the alternation of the background and of the
13
+ * texture, thus a caller that adds a band passes the next index.
9
14
  */
10
15
  import { raw } from "hono/html";
11
- import { CDN_HEAD, CHART_BOOTSTRAP, ECHARTS_THEME, ECHARTS_THEME_NAME, PAGE_CSS } from "../page.js";
16
+ import { ASSET_HEAD, CHART_BOOTSTRAP, FADE_IN_OBSERVER } from "../page.js";
17
+ import { DESIGN_CSS, ECHARTS_THEME, ECHARTS_THEME_NAME } from "../design.js";
12
18
  import { renderReferenceList } from "./references-view.js";
13
19
  import { scriptJson } from "../script-json.js";
20
+ /** The constant eyebrow of the hero. The render reads no clock, thus the hero carries no date. */
21
+ const HERO_EYEBROW = "INFLEXA · ANALYSIS REPORT";
22
+ /** The constant note of the footer. */
23
+ const FOOTER_NOTE = "Powered by Inflexa Cortex";
14
24
  /**
15
25
  * The theme object as script-safe JSON. `scriptJson` replaces every `<` with `\u003c`, thus a later edit
16
26
  * that adds a `<` to the theme stays safe inside the script sink. The JSON parser reads `\u003c` as `<`, thus
@@ -28,15 +38,25 @@ const THEME_REGISTRATION = `(function () {
28
38
  echarts.registerTheme(${JSON.stringify(ECHARTS_THEME_NAME)}, ${THEME_JSON});
29
39
  })();`;
30
40
  /**
31
- * Wrap the reference list in one fixed frame. An empty ledger gives an empty string, thus the page shows
32
- * no empty reference frame.
41
+ * Wrap already-escaped markup in one full-bleed band. An even index gives the white background and the dot
42
+ * texture, and an odd index gives the slate background and the grid texture. The noise texture lies over
43
+ * both.
44
+ */
45
+ export function renderBand(index, inner) {
46
+ const surface = index % 2 === 0 ? "report-band-white texture-dots" : "report-band-slate texture-grid";
47
+ return String(_jsx("section", { class: `report-band ${surface} texture-noise`, children: _jsx("div", { class: "report-container fade-in", children: raw(inner) }) }));
48
+ }
49
+ /**
50
+ * Wrap the reference list in the final band. An empty ledger gives an empty string, thus the page shows no
51
+ * empty reference band. The index continues the band alternation of the sections.
33
52
  */
34
- export function renderReferenceSection(ledger) {
53
+ export function renderReferenceSection(ledger, index) {
35
54
  const list = renderReferenceList(ledger);
36
55
  if (list === "") {
37
56
  return "";
38
57
  }
39
- return String(_jsxs("section", { class: "report-references-section mt-16 pt-8 border-t border-slate-200", children: [_jsx("h2", { class: "text-xl font-semibold tracking-tight text-slate-900 mb-4", children: "References" }), raw(list)] }));
58
+ const heading = String(_jsx("h2", { class: "report-heading report-heading-2", children: "References" }));
59
+ return renderBand(index, heading + list);
40
60
  }
41
61
  /**
42
62
  * Assemble the page from the title, the navigation, the main content, and the reference frame. The title
@@ -45,5 +65,5 @@ export function renderReferenceSection(ledger) {
45
65
  */
46
66
  export function assemblePage(title, nav, content, references) {
47
67
  return ("<!doctype html>" +
48
- String(_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), raw(CDN_HEAD), _jsx("style", { children: raw(PAGE_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsxs("main", { class: "report-main mx-auto max-w-4xl px-6 py-10", children: [_jsx("h1", { class: "report-title text-4xl font-bold tracking-tight text-slate-900 mb-8", children: title }), raw(content), references.length > 0 ? raw(references) : null] }), _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(CHART_BOOTSTRAP) })] })] })));
68
+ String(_jsxs("html", { lang: "en", children: [_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: title }), raw(ASSET_HEAD), _jsx("style", { children: raw(DESIGN_CSS) })] }), _jsxs("body", { children: [raw(nav), _jsx("header", { class: "report-hero texture-noise", children: _jsxs("div", { class: "report-container", children: [_jsx("p", { class: "report-eyebrow", children: HERO_EYEBROW }), _jsx("h1", { class: "report-display", children: title })] }) }), _jsxs("main", { children: [raw(content), references.length > 0 ? raw(references) : null] }), _jsx("footer", { class: "report-footer", children: _jsxs("div", { class: "report-container report-footer-row", children: [_jsx("span", { class: "report-footer-title", children: title }), _jsx("span", { class: "report-footer-note", children: FOOTER_NOTE })] }) }), _jsx("script", { children: raw(THEME_REGISTRATION) }), _jsx("script", { children: raw(FADE_IN_OBSERVER) }), _jsx("script", { children: raw(CHART_BOOTSTRAP) })] })] })));
49
69
  }
@@ -24,5 +24,6 @@ export declare function renderSection(section: SectionBlock, depth: number, chil
24
24
  /**
25
25
  * Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
26
26
  * numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
27
+ * The `report-sidebar` id is the hook of the layout shift on a large viewport.
27
28
  */
28
29
  export declare function renderNav(topSections: SectionBlock[]): string;
@@ -8,8 +8,8 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
8
8
  */
9
9
  import { raw } from "hono/html";
10
10
  import { Marker } from "./references-view.js";
11
- /** The paragraph class of a text block and a claim block. */
12
- const PARAGRAPH_CLASS = "report-paragraph mb-4 leading-relaxed text-slate-600";
11
+ /** The paragraph class of a text block and a claim block. The measure caps the line inside the wide band. */
12
+ const PARAGRAPH_CLASS = "report-prose";
13
13
  /**
14
14
  * Split prose into paragraphs on a blank line. A run of blank lines makes one split. An empty part drops
15
15
  * out, thus a leading or a trailing blank line adds no empty paragraph.
@@ -45,16 +45,9 @@ export function renderClaim(block, ledger) {
45
45
  const last = paragraphs.length - 1;
46
46
  return String(_jsx(_Fragment, { children: paragraphs.map((paragraph, index) => (_jsxs("p", { class: PARAGRAPH_CLASS, children: [paragraph, index === last ? markers : null] }))) }));
47
47
  }
48
- /** The heading class of a section, by heading level. */
48
+ /** The heading class of a section, by heading level. A heading uses the sans family, never the mono family. */
49
49
  function headingClass(level) {
50
- switch (level) {
51
- case 2:
52
- return "text-3xl font-semibold tracking-tight text-slate-900 mb-4";
53
- case 3:
54
- return "text-2xl font-semibold tracking-tight text-slate-900 mb-3";
55
- default:
56
- return "text-xl font-semibold tracking-tight text-slate-900 mb-2";
57
- }
50
+ return `report-heading report-heading-${level}`;
58
51
  }
59
52
  /**
60
53
  * Render a section wrapper around its rendered children. The heading level is `h2` at depth 0, `h3` at
@@ -64,12 +57,13 @@ function headingClass(level) {
64
57
  export function renderSection(section, depth, childrenHtml) {
65
58
  const level = Math.min(depth + 2, 4);
66
59
  const Heading = `h${level}`;
67
- return String(_jsxs("section", { id: section.id, class: "report-section mb-10", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
60
+ return String(_jsxs("section", { id: section.id, class: "report-section", children: [_jsx(Heading, { class: headingClass(level), children: section.title }), raw(childrenHtml)] }));
68
61
  }
69
62
  /**
70
63
  * Render the fixed left navigation. Each anchor targets one top-level section by its block id, with a
71
64
  * numeric badge for its position. The brand row stays, and the mobile toggle and the icon slot drop out.
65
+ * The `report-sidebar` id is the hook of the layout shift on a large viewport.
72
66
  */
73
67
  export function renderNav(topSections) {
74
- return String(_jsxs("aside", { id: "report-sidebar", class: "fixed top-0 left-0 z-40 h-screen w-60 flex flex-col border-r border-slate-200 bg-white/95 backdrop-blur", "aria-label": "Report navigation", children: [_jsx("div", { class: "flex items-center gap-2 px-5 py-5 border-b border-slate-200", children: _jsx("span", { class: "font-mono text-sm font-semibold tracking-wider text-primary-500", children: "inflexa" }) }), _jsx("nav", { class: "flex-1 overflow-y-auto py-3", "aria-label": "Report sections", children: topSections.map((section, index) => (_jsxs("a", { href: `#${section.id}`, "data-sidebar-link": "", class: "sidebar-link flex items-center gap-3 px-5 py-2 text-sm text-slate-500 hover:text-slate-900 hover:bg-slate-50 transition-colors", children: [_jsx("span", { class: "font-mono text-[10px] font-semibold tracking-wider text-slate-300 shrink-0 w-4 text-right", children: index + 1 }), _jsx("span", { class: "truncate", children: section.title })] }))) })] }));
68
+ return String(_jsxs("aside", { id: "report-sidebar", class: "report-nav", "aria-label": "Report navigation", children: [_jsx("div", { class: "report-nav-brand", children: _jsx("span", { class: "report-nav-brand-name", children: "inflexa" }) }), _jsx("nav", { class: "report-nav-list", "aria-label": "Report sections", children: topSections.map((section, index) => (_jsxs("a", { href: `#${section.id}`, class: "report-nav-link", children: [_jsx("span", { class: "report-nav-index", children: index + 1 }), _jsx("span", { class: "report-nav-label", children: section.title })] }))) })] }));
75
69
  }
@@ -4,19 +4,19 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "hono/jsx/jsx-
4
4
  * an integer.
5
5
  */
6
6
  export function Marker({ n }) {
7
- return (_jsx("sup", { class: "report-marker font-mono text-[10px] text-primary-500", children: _jsx("a", { href: `#ref-${n}`, children: n }) }));
7
+ return (_jsx("sup", { class: "report-marker", children: _jsx("a", { href: `#ref-${n}`, children: n }) }));
8
8
  }
9
9
  /** The kind label of one reference entry. */
10
10
  function RefKind({ children }) {
11
- return _jsx("span", { class: "report-ref-kind font-mono text-[11px] uppercase tracking-wider text-slate-400", children: children });
11
+ return _jsx("span", { class: "report-ref-kind", children: children });
12
12
  }
13
13
  /** A path in a monospace code span. */
14
14
  function PathCode({ children }) {
15
- return _jsx("code", { class: "font-mono text-primary-700", children: children });
15
+ return _jsx("code", { class: "report-ref-path", children: children });
16
16
  }
17
17
  /** A plain detail such as a locator or an external id. */
18
18
  function Detail({ children }) {
19
- return _jsx("span", { class: "text-slate-500", children: children });
19
+ return _jsx("span", { class: "report-ref-detail", children: children });
20
20
  }
21
21
  /**
22
22
  * Describe a locator as plain text: the column and the one row selector. The refine of the schema admits
@@ -60,5 +60,5 @@ export function renderReferenceList(ledger) {
60
60
  if (entries.length === 0) {
61
61
  return "";
62
62
  }
63
- return String(_jsx("ol", { class: "report-references list-decimal pl-6", children: entries.map((reference, index) => (_jsx("li", { id: `ref-${index + 1}`, class: "report-ref-item mb-2 text-sm text-slate-600", children: _jsx(ReferenceEntry, { reference: reference }) }))) }));
63
+ return String(_jsx("ol", { class: "report-references", children: entries.map((reference, index) => (_jsx("li", { id: `ref-${index + 1}`, class: "report-ref-item", children: _jsx(ReferenceEntry, { reference: reference }) }))) }));
64
64
  }
@@ -4,6 +4,9 @@
4
4
  * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
5
5
  * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
6
6
  * hostile source reaches the page as text.
7
+ *
8
+ * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
9
+ * accents, which is the geometric identity of the report.
7
10
  */
8
11
  import type { CitationBlock, FigureBlock, MetricBlock, TableBlock } from "../../contracts/report-blocks.js";
9
12
  import type { ReferenceLedger } from "../references.js";
@@ -20,8 +23,14 @@ type TableValue = Extract<RenderValue, {
20
23
  type FigureValue = Extract<RenderValue, {
21
24
  type: "figure";
22
25
  }>;
23
- /** Render a metric block as a stat card with the label and the scalar value. */
26
+ /** Render a metric block as a stat card with the mono value and the mono label. */
24
27
  export declare function renderMetric(block: MetricBlock, value: ScalarValue): string;
28
+ /**
29
+ * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
30
+ * markup as one already-escaped string, thus `raw()` inserts it byte for byte. The grid carries its own
31
+ * stagger, thus the run reveals after the band around it.
32
+ */
33
+ export declare function renderMetricGrid(cardsHtml: string): string;
25
34
  /**
26
35
  * Render a table block. The header holds one cell for each column, and the body holds one row for each
27
36
  * resolved row. A zero-row table renders the header alone. The title and the caption render only when the
@@ -29,14 +38,14 @@ export declare function renderMetric(block: MetricBlock, value: ScalarValue): st
29
38
  */
30
39
  export declare function renderTable(block: TableBlock, value: TableValue): string;
31
40
  /**
32
- * Render a figure block. The source rides the `src` attribute, and the caption renders below the image.
33
- * The caption also fills the `alt` attribute, thus the escape keeps a hostile source and a hostile caption
34
- * inside their slots.
41
+ * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
42
+ * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
43
+ * source and a hostile caption inside their slots.
35
44
  */
36
45
  export declare function renderFigure(block: FigureBlock, value: FigureValue): string;
37
46
  /**
38
- * Render a citation block as a short line. The binding joins the ledger like a claim binding, thus one
39
- * shared source keeps one number. The optional note renders after the marker.
47
+ * Render a citation block as a card in the reference form. The binding joins the ledger like a claim
48
+ * binding, thus one shared source keeps one number. The optional note renders after the marker.
40
49
  */
41
50
  export declare function renderCitation(block: CitationBlock, ledger: ReferenceLedger): string;
42
51
  export {};
@@ -1,8 +1,27 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
+ /**
3
+ * The value markup: the metric card, the data table, the figure, and the inline citation.
4
+ *
5
+ * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
6
+ * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
7
+ * hostile source reaches the page as text.
8
+ *
9
+ * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
10
+ * accents, which is the geometric identity of the report.
11
+ */
12
+ import { raw } from "hono/html";
2
13
  import { Marker } from "./references-view.js";
3
- /** Render a metric block as a stat card with the label and the scalar value. */
14
+ /** Render a metric block as a stat card with the mono value and the mono label. */
4
15
  export function renderMetric(block, value) {
5
- return String(_jsxs("div", { class: "corner-accents border border-slate-200 bg-white p-6", children: [_jsx("div", { class: "font-mono text-3xl font-bold", style: "color: #576dea;", children: value.value }), _jsx("div", { class: "font-mono text-xs font-medium text-slate-500 mt-2 uppercase tracking-wider", children: block.label })] }));
16
+ return String(_jsxs("div", { class: "stat-card corner-accents", children: [_jsx("div", { class: "stat-card-value", children: value.value }), _jsx("div", { class: "stat-card-label", children: block.label })] }));
17
+ }
18
+ /**
19
+ * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
20
+ * markup as one already-escaped string, thus `raw()` inserts it byte for byte. The grid carries its own
21
+ * stagger, thus the run reveals after the band around it.
22
+ */
23
+ export function renderMetricGrid(cardsHtml) {
24
+ return String(_jsx("div", { class: "report-metric-grid fade-in", "data-delay": "100", children: raw(cardsHtml) }));
6
25
  }
7
26
  /**
8
27
  * The column order of a table. The value columns win when they are present. The keys of the first row
@@ -22,22 +41,22 @@ function tableColumns(value) {
22
41
  */
23
42
  export function renderTable(block, value) {
24
43
  const columns = tableColumns(value);
25
- return String(_jsxs("div", { class: "report-table mb-8", children: [block.title !== undefined ? (_jsx("div", { class: "font-mono text-xs font-semibold uppercase tracking-widest text-primary-500 mb-3", children: block.title })) : null, _jsx("div", { class: "corner-accents border border-slate-200 bg-white overflow-hidden", children: _jsx("div", { class: "overflow-x-auto", children: _jsxs("table", { class: "w-full text-sm", children: [_jsx("thead", { children: _jsx("tr", { class: "border-b border-slate-200 bg-slate-50", children: columns.map((column) => (_jsx("th", { class: "px-4 py-3 text-left font-mono text-[11px] font-semibold text-slate-500 uppercase tracking-wider", children: column }))) }) }), _jsx("tbody", { children: value.rows.map((row) => (_jsx("tr", { class: "report-row border-b border-slate-100 last:border-b-0", children: columns.map((column) => (_jsx("td", { class: "px-4 py-2.5 text-slate-700", children: row[column] }))) }))) })] }) }) }), block.caption !== undefined ? _jsx("p", { class: "text-sm text-slate-400 mt-2", children: block.caption }) : null] }));
44
+ return String(_jsxs("div", { class: "report-table", children: [block.title !== undefined ? _jsx("div", { class: "report-table-title", children: block.title }) : null, _jsx("div", { class: "corner-accents", children: _jsx("div", { class: "data-table-scroll", children: _jsxs("table", { class: "data-table", children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column) => (_jsx("th", { children: column }))) }) }), _jsx("tbody", { children: value.rows.map((row) => (_jsx("tr", { class: "report-row", children: columns.map((column) => (_jsx("td", { children: row[column] }))) }))) })] }) }) }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
26
45
  }
27
46
  /**
28
- * Render a figure block. The source rides the `src` attribute, and the caption renders below the image.
29
- * The caption also fills the `alt` attribute, thus the escape keeps a hostile source and a hostile caption
30
- * inside their slots.
47
+ * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
48
+ * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
49
+ * source and a hostile caption inside their slots.
31
50
  */
32
51
  export function renderFigure(block, value) {
33
52
  const caption = block.caption;
34
- return String(_jsxs("figure", { class: "report-figure mb-8", children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "max-w-full h-auto border border-slate-200" }), caption !== undefined ? _jsx("figcaption", { class: "text-sm text-slate-400 mt-2", children: caption }) : null] }));
53
+ return String(_jsxs("figure", { class: "report-figure corner-accents", children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "report-figure-image" }), caption !== undefined ? _jsx("figcaption", { class: "report-caption", children: caption }) : null] }));
35
54
  }
36
55
  /**
37
- * Render a citation block as a short line. The binding joins the ledger like a claim binding, thus one
38
- * shared source keeps one number. The optional note renders after the marker.
56
+ * Render a citation block as a card in the reference form. The binding joins the ledger like a claim
57
+ * binding, thus one shared source keeps one number. The optional note renders after the marker.
39
58
  */
40
59
  export function renderCitation(block, ledger) {
41
60
  const n = ledger.mark(block.binding);
42
- return String(_jsxs("p", { class: "report-citation text-sm text-slate-500", children: [_jsx(Marker, { n: n }), block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "text-slate-600", children: block.note })] })) : null] }));
61
+ return String(_jsxs("div", { class: "report-citation corner-accents", children: [_jsx(Marker, { n: n }), block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "report-citation-note", children: block.note })] })) : null] }));
43
62
  }
@@ -25,9 +25,12 @@ import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalys
25
25
  import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
26
26
  import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
27
27
  import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
28
+ import { type ExtractValuesResult, type ExtractValuesWorkflowInput } from "../tasks/extract-values.js";
28
29
  import { type CitationResolverConfig } from "../citations/resolve.js";
29
30
  import type { CitationResolver } from "../citations/types.js";
31
+ import type { AuthContext } from "../auth/types.js";
30
32
  import type { ReferenceResolver } from "../report-model/reference-resolver.js";
33
+ import type { ResolvePageAsset } from "../tools/report-session/index.js";
31
34
  /** Registered child sandbox-step callable the parent's child dispatch closes over. */
32
35
  export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxStepResult>;
33
36
  /**
@@ -55,6 +58,7 @@ export interface RegisteredWorkflows {
55
58
  readonly sandboxStep: SandboxStepCallable;
56
59
  readonly executeTargetAssessment: (input: ExecuteTargetAssessmentInput) => Promise<ExecuteTargetAssessmentResult>;
57
60
  readonly dataProfile: (input: DataProfileWorkflowInput) => Promise<void>;
61
+ readonly extractValues: (input: ExtractValuesWorkflowInput) => Promise<ExtractValuesResult>;
58
62
  }
59
63
  /**
60
64
  * Conversation-agent deps minus the workflow callable, the resource policy, and
@@ -63,6 +67,19 @@ export interface RegisteredWorkflows {
63
67
  * workflows see, or a recorder that only half the agent tree reports to.
64
68
  */
65
69
  export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver">;
70
+ /**
71
+ * What binds one reference resolver: the analysis whose pinned artifacts it reads, and the auth of the tool
72
+ * call that reads them. A resolver serves one analysis, thus the report tools build one for each call.
73
+ */
74
+ export interface ReportResolverScope {
75
+ readonly analysisId: string;
76
+ readonly auth: AuthContext;
77
+ }
78
+ /**
79
+ * The factory that gives the reference resolver of one report tool call. The harness wires the production
80
+ * realization, and an embedder can bind its own at its composition root.
81
+ */
82
+ export type MakeReportReferenceResolver = (scope: ReportResolverScope) => ReferenceResolver;
66
83
  export interface CoreRuntimeDeps {
67
84
  readonly conversation: ConversationAssemblyDeps;
68
85
  readonly workflows: CoreWorkflowDeps;
@@ -81,11 +98,28 @@ export interface CoreRuntimeDeps {
81
98
  /** Harness-owned citation capability configuration; no ambient lookup occurs. */
82
99
  readonly citationResolverConfig?: CitationResolverConfig;
83
100
  /**
84
- * Reference-resolution seam for the report preview. Absent by default, thus the
85
- * preview tool degrades as data until an embedder wires a realization. The
86
- * realization itself stays out of the harness.
101
+ * The byte cap on the in-process host read of a report reference. A file over the
102
+ * cap falls through to the extraction arm. Absent, the resolver uses its 16 MiB
103
+ * default. The embedder tunes it here, thus a host changes it with no harness change.
104
+ */
105
+ readonly reportHostReadCapBytes?: number;
106
+ /**
107
+ * The report reference resolver of an embedder. A managed host reads its pinned
108
+ * artifacts from its own store, thus it binds a realization of the seam here and the
109
+ * report tools resolve through it. Absent, the harness wires the production resolver
110
+ * over the workspace tree and the extraction arm, which is the whole OSS path. A bound
111
+ * factory replaces that wiring, thus `reportHostReadCapBytes` then governs nothing.
112
+ */
113
+ readonly makeReportReferenceResolver?: MakeReportReferenceResolver;
114
+ /**
115
+ * The page-asset lookup of the report preview. The preview tool stages the chart
116
+ * runtime and the fonts beside the page, and it names each source by its module
117
+ * specifier. Absent, the tool resolves each specifier against the installation of the
118
+ * harness, which is the npm path and the whole OSS path. An embedder that ships the
119
+ * bytes packed, for example a compiled single-file binary with no `node_modules` tree,
120
+ * materializes them to disk and binds its own lookup here.
87
121
  */
88
- readonly reportReferenceResolver?: ReferenceResolver;
122
+ readonly resolveReportPageAsset?: ResolvePageAsset;
89
123
  }
90
124
  /**
91
125
  * The refusal a thread type carries no agent yet. `report` is a valid
@@ -23,7 +23,11 @@ import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
23
23
  import { registerSandboxStep } from "../workflows/sandbox-step.js";
24
24
  import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
25
25
  import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
26
+ import { bindExtractionTrigger, createExtractionArm, registerExtractValuesWorkflow, } from "../tasks/extract-values.js";
26
27
  import { createCitationResolver } from "../citations/resolve.js";
28
+ import { createThreadStore } from "../memory/thread-store.js";
29
+ import { createArtifactReadStore, createProductionResolver } from "../report-model/production-resolver.js";
30
+ import { createReportVersionStore } from "../state/report-versions.js";
27
31
  const _assertDomainError = true;
28
32
  /**
29
33
  * Wrap a type→agent registry as the resolution surface. Held apart from
@@ -62,6 +66,14 @@ export function assembleCoreRuntime(deps) {
62
66
  const executeAnalysis = registerExecuteAnalysis({ ...wf.buildExecuteAnalysis(sandboxStep), citationResolver, usageRecorder });
63
67
  const executeTargetAssessment = registerExecuteTargetAssessment({ ...wf.executeTargetAssessment, usageRecorder });
64
68
  const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder });
69
+ // The extraction workflow shares the profile's sandbox and authorization rails, thus it draws the same
70
+ // three seams from the profile deps. The report resolver factory binds the extraction arm over this
71
+ // callable, thus a fall-through report reference reads its file out of process on the profile rails.
72
+ const extractValues = registerExtractValuesWorkflow({
73
+ sandboxClient: wf.dataProfile.sandboxClient,
74
+ runAuthorizer: wf.dataProfile.runAuthorizer,
75
+ ...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
76
+ });
65
77
  const conversationAgent = createConversationAgent({
66
78
  ...conversation,
67
79
  executeAnalysisWorkflow: executeAnalysis,
@@ -73,9 +85,32 @@ export function assembleCoreRuntime(deps) {
73
85
  // runtime binds the per-session state to the thread behind the tool boundary,
74
86
  // thus one definition serves every report thread. Its preview tool writes the
75
87
  // page into the analysis tree and returns the path, thus the page write reaches
76
- // no host seam. The optional reference resolver is the one seam it can use, and
77
- // it is absent by default.
88
+ // no host seam.
78
89
  const reportSession = createReportSessionRuntime({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
90
+ const reportVersionStore = createReportVersionStore({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
91
+ const reportThreads = createThreadStore(conversation.pool);
92
+ // The read cache of the production report path, one for each recent analysis. A preview
93
+ // and the record that follows it are two tool calls over one draft, thus they meet the
94
+ // same artifacts. The store carries the streamed hash and the extracted rows between
95
+ // them, and each resolver still binds its own extraction arm below. Thus the auth of a
96
+ // call never outlives the call, and one unchanged file costs one hash and one container.
97
+ const reportArtifactReads = createArtifactReadStore();
98
+ // The reference resolver of the report path, and its production wiring. A resolver reads
99
+ // the pinned artifacts of one analysis, thus the factory binds one analysis and one auth
100
+ // for each tool call. The extraction arm falls through to the registered extract-values
101
+ // callable for an over-cap file or a host parse fault. The cap gates the host read, and
102
+ // the resolver defaults it to 16 MiB.
103
+ const makeProductionReportResolver = (scope) => {
104
+ const arm = createExtractionArm(bindExtractionTrigger({ runAuthorizer: conversation.runAuthorizer, workflow: extractValues }, { auth: scope.auth, analysisId: scope.analysisId }));
105
+ return createProductionResolver({
106
+ workspaceRoot: conversation.resolveWorkspaceRoot(scope.analysisId),
107
+ analysisId: scope.analysisId,
108
+ extractionArm: arm,
109
+ readCache: reportArtifactReads.forAnalysis(scope.analysisId),
110
+ ...(deps.reportHostReadCapBytes !== undefined ? { cap: deps.reportHostReadCapBytes } : {}),
111
+ });
112
+ };
113
+ const makeReportResolver = deps.makeReportReferenceResolver ?? makeProductionReportResolver;
79
114
  const reportAgent = createReportSessionAgent({
80
115
  model: conversation.model,
81
116
  pool: conversation.pool,
@@ -83,7 +118,11 @@ export function assembleCoreRuntime(deps) {
83
118
  workspaceFs: conversation.workspaceFs,
84
119
  gateway: reportSession.gateway,
85
120
  resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
86
- ...(deps.reportReferenceResolver ? { resolver: deps.reportReferenceResolver } : {}),
121
+ store: reportVersionStore,
122
+ threads: reportThreads,
123
+ chrome: conversation.chrome,
124
+ makeResolver: makeReportResolver,
125
+ ...(deps.resolveReportPageAsset ? { resolvePageAsset: deps.resolveReportPageAsset } : {}),
87
126
  ...(conversation.logger ? { logger: conversation.logger } : {}),
88
127
  });
89
128
  // The single registration point for every typed agent. A future thread type's
@@ -105,6 +144,7 @@ export function assembleCoreRuntime(deps) {
105
144
  sandboxStep,
106
145
  executeTargetAssessment,
107
146
  dataProfile,
147
+ extractValues,
108
148
  },
109
149
  citationResolver,
110
150
  };