@inflexa-ai/harness 0.20.0 → 0.21.1

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 (133) hide show
  1. package/dist/agents/conversation-agent.d.ts +26 -14
  2. package/dist/agents/conversation-agent.js +12 -23
  3. package/dist/agents/report-session-agent.d.ts +31 -5
  4. package/dist/agents/report-session-agent.js +35 -9
  5. package/dist/app/chat-turn.js +3 -1
  6. package/dist/app/message-assembly.d.ts +15 -1
  7. package/dist/app/message-assembly.js +22 -8
  8. package/dist/app/report-session-runtime.d.ts +21 -4
  9. package/dist/app/report-session-runtime.js +53 -6
  10. package/dist/app/spawn-report-session.d.ts +174 -17
  11. package/dist/app/spawn-report-session.js +233 -21
  12. package/dist/contracts/chat-events.d.ts +7 -2
  13. package/dist/contracts/message.d.ts +10 -0
  14. package/dist/contracts/report-blocks.d.ts +743 -19
  15. package/dist/contracts/report-blocks.js +186 -11
  16. package/dist/contracts/report-reference.d.ts +88 -0
  17. package/dist/contracts/report-reference.js +48 -0
  18. package/dist/index.d.ts +9 -0
  19. package/dist/index.js +20 -0
  20. package/dist/lib/chrome.d.ts +26 -6
  21. package/dist/lib/chrome.js +99 -32
  22. package/dist/lib/eyes.d.ts +57 -0
  23. package/dist/lib/eyes.js +45 -0
  24. package/dist/lib/page-capture.d.ts +14 -0
  25. package/dist/lib/page-capture.js +56 -2
  26. package/dist/loop/run-agent.js +58 -23
  27. package/dist/loop/tool-detail.d.ts +19 -2
  28. package/dist/loop/tool-detail.js +35 -2
  29. package/dist/memory/content-to-cortex.d.ts +3 -2
  30. package/dist/memory/content-to-cortex.js +21 -12
  31. package/dist/memory/conversation-display-replay.js +14 -7
  32. package/dist/memory/thread-history.d.ts +60 -3
  33. package/dist/memory/thread-history.js +55 -16
  34. package/dist/memory/thread-store.d.ts +11 -1
  35. package/dist/memory/thread-store.js +12 -2
  36. package/dist/prompts/conversation.d.ts +1 -1
  37. package/dist/prompts/conversation.js +23 -6
  38. package/dist/prompts/report-session.d.ts +1 -1
  39. package/dist/prompts/report-session.js +190 -9
  40. package/dist/report-model/authoring-grammar.d.ts +463 -0
  41. package/dist/report-model/authoring-grammar.js +77 -0
  42. package/dist/report-model/block-walk.d.ts +57 -14
  43. package/dist/report-model/block-walk.js +116 -11
  44. package/dist/report-model/draft-finish.d.ts +22 -4
  45. package/dist/report-model/draft-finish.js +39 -7
  46. package/dist/report-model/draft-operations.d.ts +3 -0
  47. package/dist/report-model/draft-operations.js +19 -5
  48. package/dist/report-model/draft-read.d.ts +8 -5
  49. package/dist/report-model/draft-read.js +21 -5
  50. package/dist/report-model/fixture-resolver.js +15 -4
  51. package/dist/report-model/pin-snapshot.d.ts +24 -6
  52. package/dist/report-model/pin-snapshot.js +204 -6
  53. package/dist/report-model/production-resolver.js +15 -4
  54. package/dist/report-model/reference-resolver.d.ts +50 -1
  55. package/dist/report-model/reference-resolver.js +94 -1
  56. package/dist/report-model/reference-stamp.d.ts +45 -0
  57. package/dist/report-model/reference-stamp.js +173 -0
  58. package/dist/report-model/structural-validation.d.ts +4 -1
  59. package/dist/report-model/structural-validation.js +48 -3
  60. package/dist/report-model/validate.d.ts +1 -1
  61. package/dist/report-model/validate.js +1 -1
  62. package/dist/report-render/asset-lookup.d.ts +19 -0
  63. package/dist/report-render/asset-lookup.js +37 -0
  64. package/dist/report-render/assets.d.ts +45 -1
  65. package/dist/report-render/assets.js +65 -6
  66. package/dist/report-render/chart-presets.d.ts +102 -0
  67. package/dist/report-render/chart-presets.js +205 -0
  68. package/dist/report-render/chart.d.ts +112 -10
  69. package/dist/report-render/chart.js +1164 -41
  70. package/dist/report-render/design.d.ts +99 -0
  71. package/dist/report-render/design.js +289 -107
  72. package/dist/report-render/number-format.d.ts +117 -0
  73. package/dist/report-render/number-format.js +479 -0
  74. package/dist/report-render/page.d.ts +121 -5
  75. package/dist/report-render/page.js +689 -7
  76. package/dist/report-render/references.d.ts +70 -16
  77. package/dist/report-render/references.js +54 -22
  78. package/dist/report-render/render.d.ts +23 -5
  79. package/dist/report-render/render.js +81 -22
  80. package/dist/report-render/table-data.d.ts +92 -0
  81. package/dist/report-render/table-data.js +129 -0
  82. package/dist/report-render/types.d.ts +15 -0
  83. package/dist/report-render/value-bridge.js +6 -2
  84. package/dist/report-render/views/chart-view.d.ts +12 -5
  85. package/dist/report-render/views/chart-view.js +14 -8
  86. package/dist/report-render/views/page-view.d.ts +36 -15
  87. package/dist/report-render/views/page-view.js +45 -23
  88. package/dist/report-render/views/prose.d.ts +12 -2
  89. package/dist/report-render/views/prose.js +29 -7
  90. package/dist/report-render/views/references-view.d.ts +22 -8
  91. package/dist/report-render/views/references-view.js +101 -13
  92. package/dist/report-render/views/values.d.ts +128 -13
  93. package/dist/report-render/views/values.js +199 -18
  94. package/dist/runtime/assemble.d.ts +39 -5
  95. package/dist/runtime/assemble.js +47 -6
  96. package/dist/sandbox/client.d.ts +10 -1
  97. package/dist/sandbox/create-sandbox.d.ts +9 -4
  98. package/dist/sandbox/create-sandbox.js +22 -16
  99. package/dist/sandbox/docker-client.js +10 -7
  100. package/dist/sandbox/mount-plan.d.ts +45 -7
  101. package/dist/sandbox/mount-plan.js +43 -5
  102. package/dist/sandbox/types.d.ts +6 -0
  103. package/dist/state/init.js +38 -9
  104. package/dist/state/report-session-state.d.ts +42 -1
  105. package/dist/state/report-session-state.js +70 -2
  106. package/dist/state/report-versions.d.ts +22 -17
  107. package/dist/state/report-versions.js +48 -35
  108. package/dist/state/snapshot-parse.js +16 -1
  109. package/dist/tools/define-tool.d.ts +32 -0
  110. package/dist/tools/define-tool.js +4 -0
  111. package/dist/tools/detail-resolver.d.ts +7 -2
  112. package/dist/tools/detail-resolver.js +7 -2
  113. package/dist/tools/iterate-report.d.ts +1 -1
  114. package/dist/tools/report/preview-snapshot.js +1 -0
  115. package/dist/tools/report-authoring/authoring-tools.d.ts +15 -6
  116. package/dist/tools/report-authoring/authoring-tools.js +99 -18
  117. package/dist/tools/report-session/derive-table.d.ts +162 -0
  118. package/dist/tools/report-session/derive-table.js +459 -0
  119. package/dist/tools/report-session/examine-page.d.ts +48 -18
  120. package/dist/tools/report-session/examine-page.js +204 -24
  121. package/dist/tools/report-session/index.d.ts +2 -0
  122. package/dist/tools/report-session/index.js +2 -0
  123. package/dist/tools/report-session/list-artifacts.d.ts +97 -0
  124. package/dist/tools/report-session/list-artifacts.js +197 -0
  125. package/dist/tools/report-session/preview-report.d.ts +6 -0
  126. package/dist/tools/report-session/preview-report.js +180 -28
  127. package/dist/tools/report-session/record-version.d.ts +22 -10
  128. package/dist/tools/report-session/record-version.js +99 -14
  129. package/dist/tools/start-report-session.d.ts +115 -0
  130. package/dist/tools/start-report-session.js +179 -0
  131. package/dist/workspace/paths.d.ts +50 -0
  132. package/dist/workspace/paths.js +63 -0
  133. package/package.json +2 -1
@@ -1,16 +1,57 @@
1
1
  /**
2
- * The value markup: the metric card, the data table, the figure, and the inline citation.
2
+ * The value markup: the metric card, the data table, the figure, and the citation card.
3
+ *
4
+ * The citation card is the bibliography entry of its source. It carries the one link of the page body,
5
+ * which is a navigation to PubMed, thus the page loads nothing from a remote host.
3
6
  *
4
7
  * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
5
8
  * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
6
- * hostile source reaches the page as text.
9
+ * hostile source reaches the page as text. The `title` attribute takes the same escape as every other
10
+ * attribute value, thus the full digits cannot break out of their slot.
11
+ *
12
+ * A number reaches the page through the number format. A table cell takes the meaning that the binding
13
+ * declares for its column, and the column name answers for a column that declares none. A metric takes its
14
+ * label, because a value locator declares no column-wide meaning.
15
+ *
16
+ * A table card carries the grid mount and the download of the raw pinned bytes. The rows and the display of
17
+ * each column ride a data asset beside the page, and the page script builds the grid over them. Thus the
18
+ * markup carries no row, and `tableDisplay` gives the page what the server resolved for each column.
19
+ *
20
+ * Each card marks its binding in the shared ledger, thus every evidentiary block carries a marker and an
21
+ * appendix entry. A reference that two blocks share keeps one number and one entry.
7
22
  *
8
23
  * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
9
24
  * accents, which is the geometric identity of the report.
10
25
  */
11
26
  import type { CitationBlock, FigureBlock, MetricBlock, TableBlock } from "../../contracts/report-blocks.js";
12
- import type { ReferenceLedger } from "../references.js";
27
+ import { type ArtifactTableReference } from "../../contracts/report-reference.js";
28
+ import type { CitationRecord } from "../../report-model/reference-resolver.js";
29
+ import { type ReferenceLedger } from "../references.js";
30
+ import type { ColumnDisplay } from "../table-data.js";
13
31
  import type { RenderValue } from "../types.js";
32
+ /**
33
+ * The attribute that marks the grid mount of a table card, and that names the block of its data.
34
+ *
35
+ * The page script reads the same attribute, thus the card and the boot cannot disagree over a rename.
36
+ */
37
+ export declare const GRID_MOUNT_ATTRIBUTE = "data-report-grid";
38
+ /**
39
+ * The class of the print note of a table card.
40
+ *
41
+ * The note is empty on the screen. The page script writes the bound of a truncated print form into it at
42
+ * print time, and it clears the text after. The page script reads the same class, thus the card and the
43
+ * boot cannot disagree over a rename.
44
+ */
45
+ export declare const GRID_NOTE_CLASS = "report-grid-note";
46
+ /**
47
+ * The class of the row count of a table card.
48
+ *
49
+ * The renderer writes the count of the resolved rows, and the page script writes it again after each filter.
50
+ * The page script reads the same class, thus the card and the boot cannot disagree over a rename.
51
+ */
52
+ export declare const GRID_COUNT_CLASS = "report-table-count";
53
+ /** The word that follows a row count, in the status line of the card and in the print note alike. */
54
+ export declare const GRID_ROWS_WORD = "rows";
14
55
  /** The scalar value that a metric renders from. */
15
56
  type ScalarValue = Extract<RenderValue, {
16
57
  type: "scalar";
@@ -23,8 +64,20 @@ type TableValue = Extract<RenderValue, {
23
64
  type FigureValue = Extract<RenderValue, {
24
65
  type: "figure";
25
66
  }>;
26
- /** Render a metric block as a stat card with the mono value and the mono label. */
27
- export declare function renderMetric(block: MetricBlock, value: ScalarValue): string;
67
+ /**
68
+ * Render a metric block as a stat card with the mono value and the mono label.
69
+ *
70
+ * The label names the value, thus it selects the number kind. The full digits ride the `title` attribute
71
+ * when the shown form hides one, and no attribute appears at any other time.
72
+ *
73
+ * A metric reads one cell and it has no column, thus no neighbor bounds a zero here. A zero under a
74
+ * p-value label reads as the near-zero form.
75
+ *
76
+ * The value binding joins the reference ladder, thus the label line carries the marker and the appendix
77
+ * names the cell. The marker sits on the label and never on the value, because the value line is the one
78
+ * figure that a reader takes from the card.
79
+ */
80
+ export declare function renderMetric(block: MetricBlock, ledger: ReferenceLedger, value: ScalarValue): string;
28
81
  /**
29
82
  * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
30
83
  * markup as one already-escaped string, thus `raw()` inserts it byte for byte. The grid carries its own
@@ -32,20 +85,82 @@ export declare function renderMetric(block: MetricBlock, value: ScalarValue): st
32
85
  */
33
86
  export declare function renderMetricGrid(cardsHtml: string): string;
34
87
  /**
35
- * Render a table block. The header holds one cell for each column, and the body holds one row for each
36
- * resolved row. A zero-row table renders the header alone. The title and the caption render only when the
37
- * block carries one. An absent cell renders as an empty cell, thus a ragged row keeps its shape.
88
+ * The column order of a table. The value columns win when they are present. The keys of the first row
89
+ * give the order otherwise, in first-appearance order.
90
+ *
91
+ * The header of the card and the payload of the data asset both read this order. Thus the position of a
92
+ * cell in an encoded row names the column of its header.
93
+ */
94
+ export declare function tableColumns(value: TableValue): string[];
95
+ /**
96
+ * The display of each column of a table, in the column order of the header.
97
+ *
98
+ * The renderer resolves the label, the number kind, the filter, and the bound of a stored zero one time for
99
+ * each column, and the data asset ships them. Thus the page formats a cell with no read of a declaration
100
+ * and no second pass over a column.
101
+ *
102
+ * The argument is the binding and not the block, because a chart binds the same whole-table reference. A
103
+ * chart that reads the shared payload then resolves its columns through this one helper.
104
+ *
105
+ * The filter reads the cells and not the kind. A column of gene names carries no number, thus it takes the
106
+ * text filter and a reader finds a gene by its name. A column where one cell parses takes the number filter.
107
+ *
108
+ * A probability column carries the smallest positive value of its own rows as the bound. A column with no
109
+ * positive value takes no bound, and its zero then reads as the near-zero form.
110
+ */
111
+ export declare function tableDisplay(binding: ArtifactTableReference, value: TableValue, columns: readonly string[]): ColumnDisplay[];
112
+ /**
113
+ * The status of a table card: the shown count against the pre-bound total of the artifact.
114
+ *
115
+ * A bound cut the rows, thus the shown count and the total differ and the line names both. A whole table
116
+ * shows every row that it holds, thus the line states the one count. The page script writes the same two
117
+ * forms after each filter, thus the first paint and every later one read alike.
38
118
  */
39
- export declare function renderTable(block: TableBlock, value: TableValue): string;
119
+ export declare function tableStatusText(shown: number, total: number): string;
120
+ /**
121
+ * Render a table block as the grid mount and the download of its raw bytes.
122
+ *
123
+ * The rows ride a data asset beside the page. A page that stamped 14,201 rows into its markup weighed
124
+ * megabytes, and each row was a copy of the column names. Thus the markup holds one empty mount, and the
125
+ * page script builds the grid over the payload that registers under the block id.
126
+ *
127
+ * The mount names its block in the mount attribute. A mount whose block the registry does not hold stays
128
+ * empty, thus the card keeps its title, its status, and its download, and the page throws nothing.
129
+ *
130
+ * The footer holds one status line and the download button. The status states the count of the resolved
131
+ * rows against the pre-bound total of the artifact, and the row bound of the binding beside it. The page
132
+ * script writes the count again after each filter, thus a reader of a narrowed table sees what the grid
133
+ * shows and what the artifact holds.
134
+ *
135
+ * The note under the status stays empty on the screen. A print of a table that passes the print bound writes
136
+ * the bound into it, thus the paper states what it shows and what it leaves out.
137
+ *
138
+ * The download names the staged copy of the pinned artifact. The link is relative and the browser saves
139
+ * the file, thus the page fetches nothing when it opens and the reader still gets the whole table.
140
+ *
141
+ * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
142
+ * appendix names the artifact. Every evidentiary block ledgers this way, and a card with no title still
143
+ * shows its marker on the same line.
144
+ */
145
+ export declare function renderTable(block: TableBlock, ledger: ReferenceLedger, rowCount: number, total?: number): string;
40
146
  /**
41
147
  * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
42
148
  * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
43
149
  * source and a hostile caption inside their slots.
150
+ *
151
+ * The whole-file binding joins the reference ladder, thus the caption line carries the marker and the
152
+ * appendix names the image. A figure with no caption still shows its marker on that line. The marker stays
153
+ * out of the `alt` text, because a screen reader reads the alt as the picture and not as a footnote.
44
154
  */
45
- export declare function renderFigure(block: FigureBlock, value: FigureValue): string;
155
+ export declare function renderFigure(block: FigureBlock, ledger: ReferenceLedger, value: FigureValue): string;
46
156
  /**
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.
157
+ * Render a citation block as a bibliography card. The binding joins the one reference ladder like a claim
158
+ * binding, thus one shared source keeps one bracket number. The card shows the marker, the short citation
159
+ * of the pinned record, the optional note, and the citation key.
160
+ *
161
+ * The short citation of a `pmid:` record carries the PubMed link of its identifier. A key of another
162
+ * identifier space shows the citation as text. A key with no pinned record shows the key and the note
163
+ * alone, because absence is a normal condition and no text stands in for the paper.
49
164
  */
50
- export declare function renderCitation(block: CitationBlock, ledger: ReferenceLedger): string;
165
+ export declare function renderCitation(block: CitationBlock, ledger: ReferenceLedger, record?: CitationRecord): string;
51
166
  export {};
@@ -1,19 +1,77 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "hono/jsx/jsx-runtime";
2
2
  /**
3
- * The value markup: the metric card, the data table, the figure, and the inline citation.
3
+ * The value markup: the metric card, the data table, the figure, and the citation card.
4
+ *
5
+ * The citation card is the bibliography entry of its source. It carries the one link of the page body,
6
+ * which is a navigation to PubMed, thus the page loads nothing from a remote host.
4
7
  *
5
8
  * The caller resolves each value, and it narrows the value to the shape that the block needs. The runtime
6
9
  * escapes every interpolated string, thus a hostile label, a hostile cell, a hostile caption, or a
7
- * hostile source reaches the page as text.
10
+ * hostile source reaches the page as text. The `title` attribute takes the same escape as every other
11
+ * attribute value, thus the full digits cannot break out of their slot.
12
+ *
13
+ * A number reaches the page through the number format. A table cell takes the meaning that the binding
14
+ * declares for its column, and the column name answers for a column that declares none. A metric takes its
15
+ * label, because a value locator declares no column-wide meaning.
16
+ *
17
+ * A table card carries the grid mount and the download of the raw pinned bytes. The rows and the display of
18
+ * each column ride a data asset beside the page, and the page script builds the grid over them. Thus the
19
+ * markup carries no row, and `tableDisplay` gives the page what the server resolved for each column.
20
+ *
21
+ * Each card marks its binding in the shared ledger, thus every evidentiary block carries a marker and an
22
+ * appendix entry. A reference that two blocks share keeps one number and one entry.
8
23
  *
9
24
  * Each data card carries the `corner-accents` class. Thus the card keeps square corners with the L-shaped
10
25
  * accents, which is the geometric identity of the report.
11
26
  */
12
27
  import { raw } from "hono/html";
28
+ import { declaredForColumn } from "../../contracts/report-reference.js";
29
+ import { stagedSource, tableSidecarName } from "../assets.js";
13
30
  import { Marker } from "./references-view.js";
14
- /** Render a metric block as a stat card with the mono value and the mono label. */
15
- export function renderMetric(block, value) {
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 })] }));
31
+ import { formatNumberCell, formatTableCell, holdsANumber, selectColumnKind, selectNumberKind, smallestPositiveValue } from "../number-format.js";
32
+ import { citationKeyOf } from "../references.js";
33
+ /**
34
+ * The attribute that marks the grid mount of a table card, and that names the block of its data.
35
+ *
36
+ * The page script reads the same attribute, thus the card and the boot cannot disagree over a rename.
37
+ */
38
+ export const GRID_MOUNT_ATTRIBUTE = "data-report-grid";
39
+ /**
40
+ * The class of the print note of a table card.
41
+ *
42
+ * The note is empty on the screen. The page script writes the bound of a truncated print form into it at
43
+ * print time, and it clears the text after. The page script reads the same class, thus the card and the
44
+ * boot cannot disagree over a rename.
45
+ */
46
+ export const GRID_NOTE_CLASS = "report-grid-note";
47
+ /**
48
+ * The class of the row count of a table card.
49
+ *
50
+ * The renderer writes the count of the resolved rows, and the page script writes it again after each filter.
51
+ * The page script reads the same class, thus the card and the boot cannot disagree over a rename.
52
+ */
53
+ export const GRID_COUNT_CLASS = "report-table-count";
54
+ /** The word that follows a row count, in the status line of the card and in the print note alike. */
55
+ export const GRID_ROWS_WORD = "rows";
56
+ /** The label of the download button of a table card, without the format of the file. */
57
+ const DOWNLOAD_LABEL = "Download";
58
+ /**
59
+ * Render a metric block as a stat card with the mono value and the mono label.
60
+ *
61
+ * The label names the value, thus it selects the number kind. The full digits ride the `title` attribute
62
+ * when the shown form hides one, and no attribute appears at any other time.
63
+ *
64
+ * A metric reads one cell and it has no column, thus no neighbor bounds a zero here. A zero under a
65
+ * p-value label reads as the near-zero form.
66
+ *
67
+ * The value binding joins the reference ladder, thus the label line carries the marker and the appendix
68
+ * names the cell. The marker sits on the label and never on the value, because the value line is the one
69
+ * figure that a reader takes from the card.
70
+ */
71
+ export function renderMetric(block, ledger, value) {
72
+ const n = ledger.mark(block.value);
73
+ const shown = formatNumberCell(value.value, selectNumberKind(block.label, value.value));
74
+ return String(_jsxs("div", { class: "stat-card corner-accents", children: [_jsx("div", { class: "stat-card-value", title: shown.full, children: shown.text }), _jsxs("div", { class: "stat-card-label", children: [block.label, _jsx(Marker, { n: n })] })] }));
17
75
  }
18
76
  /**
19
77
  * Wrap a run of stat cards in one responsive grid. The caller renders each card first, and it passes the
@@ -26,8 +84,11 @@ export function renderMetricGrid(cardsHtml) {
26
84
  /**
27
85
  * The column order of a table. The value columns win when they are present. The keys of the first row
28
86
  * give the order otherwise, in first-appearance order.
87
+ *
88
+ * The header of the card and the payload of the data asset both read this order. Thus the position of a
89
+ * cell in an encoded row names the column of its header.
29
90
  */
30
- function tableColumns(value) {
91
+ export function tableColumns(value) {
31
92
  if (value.columns !== undefined) {
32
93
  return value.columns;
33
94
  }
@@ -35,28 +96,148 @@ function tableColumns(value) {
35
96
  return first !== undefined ? Object.keys(first) : [];
36
97
  }
37
98
  /**
38
- * Render a table block. The header holds one cell for each column, and the body holds one row for each
39
- * resolved row. A zero-row table renders the header alone. The title and the caption render only when the
40
- * block carries one. An absent cell renders as an empty cell, thus a ragged row keeps its shape.
99
+ * The header label of one column.
100
+ *
101
+ * A declared label names what the column measures. A column with no label prettifies, thus an underscore
102
+ * reads as a space. The page compares the label against the raw name, and it puts the raw name on the
103
+ * header hover where the two differ.
104
+ */
105
+ function columnLabel(column, labels) {
106
+ return declaredForColumn(labels, column) ?? column.replaceAll("_", " ");
107
+ }
108
+ /**
109
+ * The display of each column of a table, in the column order of the header.
110
+ *
111
+ * The renderer resolves the label, the number kind, the filter, and the bound of a stored zero one time for
112
+ * each column, and the data asset ships them. Thus the page formats a cell with no read of a declaration
113
+ * and no second pass over a column.
114
+ *
115
+ * The argument is the binding and not the block, because a chart binds the same whole-table reference. A
116
+ * chart that reads the shared payload then resolves its columns through this one helper.
117
+ *
118
+ * The filter reads the cells and not the kind. A column of gene names carries no number, thus it takes the
119
+ * text filter and a reader finds a gene by its name. A column where one cell parses takes the number filter.
120
+ *
121
+ * A probability column carries the smallest positive value of its own rows as the bound. A column with no
122
+ * positive value takes no bound, and its zero then reads as the near-zero form.
123
+ */
124
+ export function tableDisplay(binding, value, columns) {
125
+ return columns.map((column) => {
126
+ // One pass over the column serves the filter and the bound alike, thus a wide table reads no column
127
+ // twice.
128
+ const cells = value.rows.map((row) => row[column]);
129
+ const kind = selectColumnKind(column, declaredForColumn(binding.columnMeanings, column));
130
+ const label = columnLabel(column, binding.columnLabels);
131
+ const filter = holdsANumber(cells) ? "number" : "text";
132
+ if (kind !== "scientific") {
133
+ return { label, kind, filter };
134
+ }
135
+ const bound = smallestPositiveValue(cells);
136
+ return bound === undefined ? { label, kind, filter } : { label, kind, filter, bound };
137
+ });
138
+ }
139
+ /**
140
+ * The label of the download button: the word and the format of the pinned file, for example `Download CSV`.
141
+ *
142
+ * The extension of the path names the format, thus the label states what the reader gets. A path with no
143
+ * extension gives the word alone, because a made-up format would be a claim about bytes that nobody read.
144
+ */
145
+ function downloadLabel(path) {
146
+ const base = path.slice(path.lastIndexOf("/") + 1);
147
+ const dot = base.lastIndexOf(".");
148
+ const format = dot > 0 ? base.slice(dot + 1).toUpperCase() : "";
149
+ return format === "" ? DOWNLOAD_LABEL : `${DOWNLOAD_LABEL} ${format}`;
150
+ }
151
+ /**
152
+ * The row bound of a binding as text, for example `top 20 by padj`, or `undefined` where the binding carries
153
+ * none.
154
+ *
155
+ * The bound is what the resolution kept, thus the status line states it beside the count and a reader never
156
+ * reads a cut table as the whole artifact. The rank direction names the end that the bound kept.
41
157
  */
42
- export function renderTable(block, value) {
43
- const columns = tableColumns(value);
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] }));
158
+ function boundText(binding) {
159
+ const bound = binding.rowBound;
160
+ if (bound === undefined) {
161
+ return undefined;
162
+ }
163
+ const end = bound.order === "asc" ? "lowest" : "top";
164
+ const count = formatTableCell(bound.count, "compact-scientific");
165
+ return `${end} ${count} by ${columnLabel(bound.column, binding.columnLabels)}`;
166
+ }
167
+ /**
168
+ * The status of a table card: the shown count against the pre-bound total of the artifact.
169
+ *
170
+ * A bound cut the rows, thus the shown count and the total differ and the line names both. A whole table
171
+ * shows every row that it holds, thus the line states the one count. The page script writes the same two
172
+ * forms after each filter, thus the first paint and every later one read alike.
173
+ */
174
+ export function tableStatusText(shown, total) {
175
+ const whole = `${formatTableCell(total, "compact-scientific")} ${GRID_ROWS_WORD}`;
176
+ return shown === total ? whole : `${formatTableCell(shown, "compact-scientific")} of ${whole}`;
177
+ }
178
+ /**
179
+ * Render a table block as the grid mount and the download of its raw bytes.
180
+ *
181
+ * The rows ride a data asset beside the page. A page that stamped 14,201 rows into its markup weighed
182
+ * megabytes, and each row was a copy of the column names. Thus the markup holds one empty mount, and the
183
+ * page script builds the grid over the payload that registers under the block id.
184
+ *
185
+ * The mount names its block in the mount attribute. A mount whose block the registry does not hold stays
186
+ * empty, thus the card keeps its title, its status, and its download, and the page throws nothing.
187
+ *
188
+ * The footer holds one status line and the download button. The status states the count of the resolved
189
+ * rows against the pre-bound total of the artifact, and the row bound of the binding beside it. The page
190
+ * script writes the count again after each filter, thus a reader of a narrowed table sees what the grid
191
+ * shows and what the artifact holds.
192
+ *
193
+ * The note under the status stays empty on the screen. A print of a table that passes the print bound writes
194
+ * the bound into it, thus the paper states what it shows and what it leaves out.
195
+ *
196
+ * The download names the staged copy of the pinned artifact. The link is relative and the browser saves
197
+ * the file, thus the page fetches nothing when it opens and the reader still gets the whole table.
198
+ *
199
+ * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
200
+ * appendix names the artifact. Every evidentiary block ledgers this way, and a card with no title still
201
+ * shows its marker on the same line.
202
+ */
203
+ export function renderTable(block, ledger, rowCount, total) {
204
+ const binding = block.binding;
205
+ const n = ledger.mark(binding);
206
+ const download = tableSidecarName(binding.hash, binding.path);
207
+ const bound = boundText(binding);
208
+ return String(_jsxs("div", { class: "report-table", children: [_jsxs("div", { class: "report-table-title", children: [block.title, _jsx(Marker, { n: n })] }), _jsxs("div", { class: "corner-accents", children: [_jsx("div", { class: "report-grid", [GRID_MOUNT_ATTRIBUTE]: block.id }), _jsxs("div", { class: "report-table-footer", children: [_jsxs("div", { class: "report-table-footer-row", children: [_jsxs("span", { class: "report-table-status", children: [_jsx("span", { class: GRID_COUNT_CLASS, children: tableStatusText(rowCount, total ?? rowCount) }), bound !== undefined ? _jsx("span", { class: "report-table-bound", children: bound }) : null] }), _jsx("a", { class: "report-table-download", href: stagedSource(download), download: download, children: downloadLabel(binding.path) })] }), _jsx("div", { class: GRID_NOTE_CLASS })] })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
45
209
  }
46
210
  /**
47
211
  * Render a figure block as a corner-accent card. The source rides the `src` attribute, and the caption
48
212
  * renders below the image. The caption also fills the `alt` attribute, thus the escape keeps a hostile
49
213
  * source and a hostile caption inside their slots.
214
+ *
215
+ * The whole-file binding joins the reference ladder, thus the caption line carries the marker and the
216
+ * appendix names the image. A figure with no caption still shows its marker on that line. The marker stays
217
+ * out of the `alt` text, because a screen reader reads the alt as the picture and not as a footnote.
50
218
  */
51
- export function renderFigure(block, value) {
219
+ export function renderFigure(block, ledger, value) {
220
+ const n = ledger.mark(block.binding);
52
221
  const caption = block.caption;
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] }));
222
+ return String(_jsxs("figure", { class: "report-figure corner-accents", children: [_jsx("img", { src: value.src, alt: caption !== undefined ? caption : "", class: "report-figure-image" }), _jsxs("figcaption", { class: "report-caption", children: [caption, _jsx(Marker, { n: n })] })] }));
54
223
  }
55
224
  /**
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.
225
+ * The PubMed address of one identifier. The address is deterministic, and an anchor loads nothing, thus a
226
+ * page with such a link still stands alone.
227
+ */
228
+ const PUBMED_BASE = "https://pubmed.ncbi.nlm.nih.gov/";
229
+ /**
230
+ * Render a citation block as a bibliography card. The binding joins the one reference ladder like a claim
231
+ * binding, thus one shared source keeps one bracket number. The card shows the marker, the short citation
232
+ * of the pinned record, the optional note, and the citation key.
233
+ *
234
+ * The short citation of a `pmid:` record carries the PubMed link of its identifier. A key of another
235
+ * identifier space shows the citation as text. A key with no pinned record shows the key and the note
236
+ * alone, because absence is a normal condition and no text stands in for the paper.
58
237
  */
59
- export function renderCitation(block, ledger) {
238
+ export function renderCitation(block, ledger, record) {
60
239
  const n = ledger.mark(block.binding);
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] }));
240
+ const binding = block.binding;
241
+ const key = citationKeyOf(binding);
242
+ return String(_jsxs("div", { class: "report-citation corner-accents", children: [_jsx(Marker, { n: n }), record !== undefined ? (_jsxs(_Fragment, { children: [" ", binding.idKind === "pmid" ? (_jsx("a", { class: "report-citation-source", href: `${PUBMED_BASE}${encodeURIComponent(binding.id)}/`, children: record.citation })) : (_jsx("span", { class: "report-citation-source", children: record.citation }))] })) : null, block.note !== undefined ? (_jsxs(_Fragment, { children: [" ", _jsx("span", { class: "report-citation-note", children: block.note })] })) : null, " ", _jsx("span", { class: "report-citation-key", children: key })] }));
62
243
  }
@@ -20,6 +20,8 @@ import { type ReportSessionRuntime } from "../app/report-session-runtime.js";
20
20
  import type { UsageRecorder } from "../billing/usage-recorder.js";
21
21
  import type { ResourcePolicy } from "../config/resource-limits.js";
22
22
  import type { AgentDefinition } from "../loop/types.js";
23
+ import { type ChromeConfig } from "../lib/chrome.js";
24
+ import { type AcquireEyes } from "../lib/eyes.js";
23
25
  import type { ThreadType } from "../memory/thread-store.js";
24
26
  import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
25
27
  import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
@@ -61,12 +63,22 @@ export interface RegisteredWorkflows {
61
63
  readonly extractValues: (input: ExtractValuesWorkflowInput) => Promise<ExtractValuesResult>;
62
64
  }
63
65
  /**
64
- * Conversation-agent deps minus the workflow callable, the resource policy, and
65
- * the usage recorder — `assembleCoreRuntime` supplies those itself so a caller
66
- * cannot wire a stale callable, a policy that diverges from the one the
67
- * workflows see, or a recorder that only half the agent tree reports to.
66
+ * Conversation-agent deps minus the workflow callable, the resource policy, the
67
+ * usage recorder, the report-session anchor, and the eyes.
68
+ * `assembleCoreRuntime` supplies each of those itself. Thus a caller cannot wire
69
+ * one of these five values:
70
+ *
71
+ * - a stale callable
72
+ * - a policy that diverges from the one the workflows see
73
+ * - a recorder that only half the agent tree reports to
74
+ * - an anchor over a different session runtime
75
+ * - a seam that only half the agent tree looks through
76
+ *
77
+ * The assembly resolves the eyes one time, from `CoreRuntimeDeps.eyes` and the
78
+ * chrome config. A caller that bound the eyes here would give the conversation
79
+ * agent a seam that the report agent never sees.
68
80
  */
69
- export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver">;
81
+ export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver" | "anchorReportSession" | "eyes">;
70
82
  /**
71
83
  * What binds one reference resolver: the analysis whose pinned artifacts it reads, and the auth of the tool
72
84
  * call that reads them. A resolver serves one analysis, thus the report tools build one for each call.
@@ -120,6 +132,15 @@ export interface CoreRuntimeDeps {
120
132
  * materializes them to disk and binds its own lookup here.
121
133
  */
122
134
  readonly resolveReportPageAsset?: ResolvePageAsset;
135
+ /**
136
+ * The eyes of a report session — where a browser comes from for one look at the rendered
137
+ * page. A host with no standing sidecar starts a browser for one look. Thus it binds its own
138
+ * realization here, and the harness keeps every page behavior. Absent, the assembly wraps a
139
+ * chrome config that names a browser into the static realization. Thus a composition that
140
+ * names a standing sidecar changes nothing. A composition with neither route has no eyes, and
141
+ * the eyes tool of a report session then reports that condition.
142
+ */
143
+ readonly eyes?: AcquireEyes;
123
144
  }
124
145
  /**
125
146
  * The refusal a thread type carries no agent yet. `report` is a valid
@@ -172,4 +193,17 @@ export interface CoreRuntime {
172
193
  readonly workflows: RegisteredWorkflows;
173
194
  readonly citationResolver: CitationResolver;
174
195
  }
196
+ /**
197
+ * The eyes of the composition, or none.
198
+ *
199
+ * A seam that the embedder bound answers first. Otherwise a chrome config that names a browser
200
+ * becomes the static realization. Thus a composition with a standing sidecar keeps its endpoint,
201
+ * and it changes nothing. A config that names no browser gives no eyes at all, and the eyes tool
202
+ * then gives the no-browser outcome.
203
+ *
204
+ * `createStaticEyes` refuses a config with no endpoint, thus the gate on the config runs before
205
+ * the construction. The resolution stays apart from `assembleCoreRuntime`, the same as the thread
206
+ * resolver above. Thus a test drives the precedence with no DBOS registration.
207
+ */
208
+ export declare function resolveCompositionEyes(seam: AcquireEyes | undefined, chrome: ChromeConfig): AcquireEyes | undefined;
175
209
  export declare function assembleCoreRuntime(deps: CoreRuntimeDeps): CoreRuntime;
@@ -19,6 +19,8 @@ import { createConversationAgent } from "../agents/conversation-agent.js";
19
19
  import { createReportSessionAgent } from "../agents/report-session-agent.js";
20
20
  import { createReportSessionRuntime } from "../app/report-session-runtime.js";
21
21
  import { createNoopUsageRecorder } from "../billing/noop-usage-recorder.js";
22
+ import { hasBrowserUrl } from "../lib/chrome.js";
23
+ import { createStaticEyes } from "../lib/eyes.js";
22
24
  import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
23
25
  import { registerSandboxStep } from "../workflows/sandbox-step.js";
24
26
  import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
@@ -52,6 +54,25 @@ export function createThreadAgentResolver(registry) {
52
54
  },
53
55
  };
54
56
  }
57
+ /**
58
+ * The eyes of the composition, or none.
59
+ *
60
+ * A seam that the embedder bound answers first. Otherwise a chrome config that names a browser
61
+ * becomes the static realization. Thus a composition with a standing sidecar keeps its endpoint,
62
+ * and it changes nothing. A config that names no browser gives no eyes at all, and the eyes tool
63
+ * then gives the no-browser outcome.
64
+ *
65
+ * `createStaticEyes` refuses a config with no endpoint, thus the gate on the config runs before
66
+ * the construction. The resolution stays apart from `assembleCoreRuntime`, the same as the thread
67
+ * resolver above. Thus a test drives the precedence with no DBOS registration.
68
+ */
69
+ export function resolveCompositionEyes(seam, chrome) {
70
+ if (seam !== undefined)
71
+ return seam;
72
+ if (hasBrowserUrl(chrome))
73
+ return createStaticEyes(chrome);
74
+ return undefined;
75
+ }
55
76
  export function assembleCoreRuntime(deps) {
56
77
  const { conversation, workflows: wf, resourcePolicy } = deps;
57
78
  const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
@@ -74,19 +95,32 @@ export function assembleCoreRuntime(deps) {
74
95
  runAuthorizer: wf.dataProfile.runAuthorizer,
75
96
  ...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
76
97
  });
98
+ // The session runtime binds the per-session state to the thread behind the
99
+ // tool boundary, thus one instance serves every report thread. It comes before
100
+ // the conversation agent, because `start_report_session` takes its anchor
101
+ // operation and pins the snapshot of a new session at the spawn.
102
+ // The runtime takes the same root resolver as the session tools, because the pin reads the citation
103
+ // evidence out of the run tree of the analysis.
104
+ const reportSession = createReportSessionRuntime({
105
+ pool: conversation.pool,
106
+ resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
107
+ ...(conversation.logger ? { logger: conversation.logger } : {}),
108
+ });
109
+ // The eyes of the composition resolve one time here. Thus the agent that looks at a page and
110
+ // the tool that starts a session read one answer. No tool reads the precedence again.
111
+ const eyes = resolveCompositionEyes(deps.eyes, conversation.chrome);
77
112
  const conversationAgent = createConversationAgent({
78
113
  ...conversation,
79
114
  executeAnalysisWorkflow: executeAnalysis,
115
+ anchorReportSession: reportSession.ensureSessionState,
80
116
  resourcePolicy,
81
117
  usageRecorder,
82
118
  citationResolver,
119
+ ...(eyes ? { eyes } : {}),
83
120
  });
84
- // The report agent is a singleton over the conversation deps. The session
85
- // runtime binds the per-session state to the thread behind the tool boundary,
86
- // thus one definition serves every report thread. Its preview tool writes the
87
- // page into the analysis tree and returns the path, thus the page write reaches
88
- // no host seam.
89
- const reportSession = createReportSessionRuntime({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
121
+ // The report agent is a singleton over the conversation deps, the same way the
122
+ // session runtime is. Its preview tool writes the page into the analysis tree
123
+ // and returns the path, thus the page write reaches no host seam.
90
124
  const reportVersionStore = createReportVersionStore({ pool: conversation.pool, ...(conversation.logger ? { logger: conversation.logger } : {}) });
91
125
  const reportThreads = createThreadStore(conversation.pool);
92
126
  // The read cache of the production report path, one for each recent analysis. A preview
@@ -121,7 +155,14 @@ export function assembleCoreRuntime(deps) {
121
155
  store: reportVersionStore,
122
156
  threads: reportThreads,
123
157
  chrome: conversation.chrome,
158
+ // The derivation tool draws the rails of the value tier: the same sandbox client
159
+ // and the same run authorizer. Thus a session derivation and an extraction run on
160
+ // one substrate, and neither one mints a run of the analysis.
161
+ derivations: reportSession.derivations,
162
+ sandboxClient: wf.dataProfile.sandboxClient,
163
+ runAuthorizer: conversation.runAuthorizer,
124
164
  makeResolver: makeReportResolver,
165
+ ...(eyes ? { eyes } : {}),
125
166
  ...(deps.resolveReportPageAsset ? { resolvePageAsset: deps.resolveReportPageAsset } : {}),
126
167
  ...(conversation.logger ? { logger: conversation.logger } : {}),
127
168
  });
@@ -20,8 +20,17 @@
20
20
  * `awaitExec` runs in the workflow body (not as a DBOS step) because
21
21
  * `DBOS.recv` and `DBOS.writeStream` are body-only.
22
22
  */
23
- import type { CreateSandboxMeta, ExecEmit, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SubmitExecBody } from "./types.js";
23
+ import type { CreateSandboxMeta, ExecEmit, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxTransport, SubmitExecBody } from "./types.js";
24
24
  export interface SandboxClient {
25
+ /**
26
+ * The result transport this client awaits under, fixed at construction. A caller that runs outside a
27
+ * workflow body reads it: `callback` awaits through `DBOS.recv`, which is body-only, thus such a caller
28
+ * must refuse before it starts a container it can never await.
29
+ *
30
+ * Optional, because a hand-written realization states nothing here. Absent means unknown, and a caller
31
+ * treats it as the poll default rather than refusing every composition that omits it.
32
+ */
33
+ readonly transport?: SandboxTransport;
25
34
  /**
26
35
  * DBOS step (`sandbox.create`) — the spawn half of the two-step create
27
36
  * (see the harness-sandbox-exec spec). Launches the sandbox-base container/Job under the