@inflexa-ai/harness 0.42.2 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +2 -2
  2. package/dist/agents/sandbox/shared.d.ts +1 -6
  3. package/dist/agents/sandbox/shared.js +0 -15
  4. package/dist/app/chat-turn.js +13 -1
  5. package/dist/config/environment-stores.d.ts +4 -2
  6. package/dist/contracts/chat-events.d.ts +9 -1
  7. package/dist/contracts/chat-frame.d.ts +3 -3
  8. package/dist/contracts/chat-frame.js +8 -3
  9. package/dist/contracts/chat-parts.d.ts +4 -1
  10. package/dist/contracts/data-profile.d.ts +4 -6
  11. package/dist/contracts/index.d.ts +1 -1
  12. package/dist/contracts/report-blocks.d.ts +1364 -75
  13. package/dist/contracts/report-blocks.js +221 -29
  14. package/dist/contracts/report-reference.js +1 -1
  15. package/dist/contracts/schemas/chat-events.d.ts +13 -0
  16. package/dist/contracts/schemas/chat-events.js +5 -0
  17. package/dist/contracts/schemas/chat-parts.d.ts +2 -2
  18. package/dist/contracts/schemas/index.d.ts +1 -1
  19. package/dist/contracts/schemas/index.js +1 -1
  20. package/dist/execution/input-signature.d.ts +7 -13
  21. package/dist/execution/input-signature.js +0 -0
  22. package/dist/execution/run-canceler.d.ts +3 -2
  23. package/dist/execution/run-canceler.js +3 -2
  24. package/dist/execution/run-event-stream.d.ts +16 -2
  25. package/dist/execution/run-event-stream.js +54 -3
  26. package/dist/index.d.ts +1 -8
  27. package/dist/index.js +5 -16
  28. package/dist/input-scan/enrich.d.ts +0 -2
  29. package/dist/input-scan/enrich.js +0 -1
  30. package/dist/lib/metrics.js +6 -7
  31. package/dist/lib/otel-spans.d.ts +4 -9
  32. package/dist/lib/otel-spans.js +3 -8
  33. package/dist/loop/run-agent.d.ts +4 -2
  34. package/dist/loop/run-agent.js +38 -23
  35. package/dist/memory/thread-history.d.ts +1 -1
  36. package/dist/prompts/report-session.d.ts +1 -1
  37. package/dist/prompts/report-session.js +17 -0
  38. package/dist/provenance/exec-frame.d.ts +1 -1
  39. package/dist/report-model/authoring-grammar.d.ts +416 -17
  40. package/dist/report-model/authoring-grammar.js +25 -4
  41. package/dist/report-model/block-walk.d.ts +20 -4
  42. package/dist/report-model/block-walk.js +64 -7
  43. package/dist/report-model/draft-finish.d.ts +6 -2
  44. package/dist/report-model/draft-finish.js +6 -1
  45. package/dist/report-model/production-resolver.d.ts +8 -0
  46. package/dist/report-model/production-resolver.js +1 -1
  47. package/dist/report-model/validate.d.ts +20 -10
  48. package/dist/report-model/validate.js +29 -13
  49. package/dist/report-render/chart-export.d.ts +52 -0
  50. package/dist/report-render/chart-export.js +192 -0
  51. package/dist/report-render/chart-presets.d.ts +46 -47
  52. package/dist/report-render/chart-presets.js +31 -101
  53. package/dist/report-render/chart-renderers.d.ts +163 -0
  54. package/dist/report-render/chart-renderers.js +1055 -0
  55. package/dist/report-render/chart-toolbox.d.ts +124 -0
  56. package/dist/report-render/chart-toolbox.js +595 -0
  57. package/dist/report-render/chart.d.ts +60 -27
  58. package/dist/report-render/chart.js +2323 -524
  59. package/dist/report-render/design.d.ts +476 -16
  60. package/dist/report-render/design.js +390 -56
  61. package/dist/report-render/figures/common.d.ts +321 -0
  62. package/dist/report-render/figures/common.js +759 -0
  63. package/dist/report-render/figures/dendrogram.d.ts +36 -0
  64. package/dist/report-render/figures/dendrogram.js +130 -0
  65. package/dist/report-render/figures/dense.d.ts +111 -0
  66. package/dist/report-render/figures/dense.js +204 -0
  67. package/dist/report-render/figures/dotplot.d.ts +28 -0
  68. package/dist/report-render/figures/dotplot.js +234 -0
  69. package/dist/report-render/figures/embedding.d.ts +43 -0
  70. package/dist/report-render/figures/embedding.js +360 -0
  71. package/dist/report-render/figures/equal-units.d.ts +101 -0
  72. package/dist/report-render/figures/equal-units.js +215 -0
  73. package/dist/report-render/figures/forest.d.ts +20 -0
  74. package/dist/report-render/figures/forest.js +398 -0
  75. package/dist/report-render/figures/gsea.d.ts +30 -0
  76. package/dist/report-render/figures/gsea.js +391 -0
  77. package/dist/report-render/figures/heatmap.d.ts +40 -0
  78. package/dist/report-render/figures/heatmap.js +395 -0
  79. package/dist/report-render/figures/index.d.ts +135 -0
  80. package/dist/report-render/figures/index.js +100 -0
  81. package/dist/report-render/figures/km.d.ts +16 -0
  82. package/dist/report-render/figures/km.js +406 -0
  83. package/dist/report-render/figures/label-room.d.ts +130 -0
  84. package/dist/report-render/figures/label-room.js +341 -0
  85. package/dist/report-render/figures/locuszoom.d.ts +50 -0
  86. package/dist/report-render/figures/locuszoom.js +675 -0
  87. package/dist/report-render/figures/lollipop.d.ts +16 -0
  88. package/dist/report-render/figures/lollipop.js +247 -0
  89. package/dist/report-render/figures/ma.d.ts +20 -0
  90. package/dist/report-render/figures/ma.js +114 -0
  91. package/dist/report-render/figures/manhattan.d.ts +60 -0
  92. package/dist/report-render/figures/manhattan.js +343 -0
  93. package/dist/report-render/figures/oncoprint.d.ts +41 -0
  94. package/dist/report-render/figures/oncoprint.js +420 -0
  95. package/dist/report-render/figures/pca.d.ts +37 -0
  96. package/dist/report-render/figures/pca.js +241 -0
  97. package/dist/report-render/figures/qq.d.ts +30 -0
  98. package/dist/report-render/figures/qq.js +182 -0
  99. package/dist/report-render/figures/roc.d.ts +17 -0
  100. package/dist/report-render/figures/roc.js +116 -0
  101. package/dist/report-render/figures/sankey.d.ts +48 -0
  102. package/dist/report-render/figures/sankey.js +353 -0
  103. package/dist/report-render/figures/upset.d.ts +60 -0
  104. package/dist/report-render/figures/upset.js +316 -0
  105. package/dist/report-render/figures/volcano.d.ts +25 -0
  106. package/dist/report-render/figures/volcano.js +166 -0
  107. package/dist/report-render/hybrid-svg.d.ts +75 -0
  108. package/dist/report-render/hybrid-svg.js +504 -0
  109. package/dist/report-render/number-format.d.ts +19 -0
  110. package/dist/report-render/number-format.js +25 -1
  111. package/dist/report-render/page.d.ts +41 -4
  112. package/dist/report-render/page.js +247 -12
  113. package/dist/report-render/render.d.ts +13 -4
  114. package/dist/report-render/render.js +37 -6
  115. package/dist/report-render/types.d.ts +23 -2
  116. package/dist/report-render/value-bridge.d.ts +38 -3
  117. package/dist/report-render/value-bridge.js +137 -6
  118. package/dist/report-render/views/chart-view.d.ts +42 -6
  119. package/dist/report-render/views/chart-view.js +70 -9
  120. package/dist/report-render/views/page-view.js +9 -10
  121. package/dist/runtime/assemble.js +2 -2
  122. package/dist/sandbox/client.d.ts +23 -32
  123. package/dist/sandbox/client.js +3 -11
  124. package/dist/sandbox/create-sandbox.d.ts +9 -24
  125. package/dist/sandbox/create-sandbox.js +15 -30
  126. package/dist/sandbox/docker-client.d.ts +12 -22
  127. package/dist/sandbox/docker-client.js +29 -43
  128. package/dist/sandbox/exec.d.ts +58 -0
  129. package/dist/sandbox/exec.js +197 -0
  130. package/dist/sandbox/identity.d.ts +5 -5
  131. package/dist/sandbox/identity.js +7 -10
  132. package/dist/sandbox/k8s-client.d.ts +4 -10
  133. package/dist/sandbox/k8s-client.js +5 -12
  134. package/dist/sandbox/liveness.d.ts +5 -12
  135. package/dist/sandbox/liveness.js +5 -12
  136. package/dist/sandbox/notification-sweep.d.ts +4 -7
  137. package/dist/sandbox/notification-sweep.js +4 -7
  138. package/dist/sandbox/reaper.d.ts +3 -3
  139. package/dist/sandbox/reaper.js +3 -3
  140. package/dist/sandbox/sandbox-error.d.ts +10 -11
  141. package/dist/sandbox/sandbox-error.js +10 -11
  142. package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
  143. package/dist/sandbox/sandbox-step-translate.js +1 -1
  144. package/dist/sandbox/types.d.ts +31 -99
  145. package/dist/sandbox/types.js +17 -84
  146. package/dist/state/active-sandboxes.d.ts +7 -32
  147. package/dist/state/active-sandboxes.js +11 -46
  148. package/dist/state/index.d.ts +1 -2
  149. package/dist/state/index.js +1 -1
  150. package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.d.ts +7 -0
  151. package/dist/state/migrations/20261005120000_drop_the_active_exec_tracking.js +12 -0
  152. package/dist/state/migrations/index.js +2 -0
  153. package/dist/state/schema.d.ts +1 -5
  154. package/dist/state/schema.js +1 -6
  155. package/dist/state/step-executions.js +1 -2
  156. package/dist/tasks/data-profile.js +7 -20
  157. package/dist/tasks/derive-table-exec.d.ts +4 -14
  158. package/dist/tasks/derive-table-exec.js +9 -24
  159. package/dist/tasks/extract-values.d.ts +2 -2
  160. package/dist/tasks/extract-values.js +4 -7
  161. package/dist/tools/define-tool.d.ts +56 -13
  162. package/dist/tools/define-tool.js +28 -1
  163. package/dist/tools/execute-analysis.js +3 -3
  164. package/dist/tools/report-session/derive-table.d.ts +8 -9
  165. package/dist/tools/report-session/derive-table.js +3 -4
  166. package/dist/tools/report-session/preview-report.js +4 -49
  167. package/dist/tools/sandbox/list-available-packages.d.ts +1 -1
  168. package/dist/tools/sandbox/list-available-packages.js +5 -4
  169. package/dist/tools/workspace/edit-file.d.ts +5 -4
  170. package/dist/tools/workspace/edit-file.js +10 -13
  171. package/dist/tools/workspace/execute-command.d.ts +27 -42
  172. package/dist/tools/workspace/execute-command.js +42 -38
  173. package/dist/tools/workspace/index.d.ts +5 -6
  174. package/dist/tools/workspace/index.js +5 -6
  175. package/dist/tools/workspace/mutator.d.ts +43 -8
  176. package/dist/tools/workspace/mutator.js +45 -34
  177. package/dist/tools/workspace/run-exec.d.ts +3 -12
  178. package/dist/tools/workspace/run-exec.js +7 -24
  179. package/dist/tools/workspace/scan-inputs.d.ts +0 -3
  180. package/dist/tools/workspace/scan-inputs.js +0 -1
  181. package/dist/tools/workspace/write-file.d.ts +12 -3
  182. package/dist/tools/workspace/write-file.js +17 -13
  183. package/dist/workflows/sandbox-step.d.ts +7 -11
  184. package/dist/workflows/sandbox-step.js +24 -27
  185. package/package.json +3 -1
  186. package/dist/sandbox/await-exec.d.ts +0 -143
  187. package/dist/sandbox/await-exec.js +0 -417
  188. package/dist/sandbox/deliver-exec-event.d.ts +0 -25
  189. package/dist/sandbox/deliver-exec-event.js +0 -29
  190. package/dist/sandbox/digest.d.ts +0 -14
  191. package/dist/sandbox/digest.js +0 -17
  192. package/dist/sandbox/exec-id.d.ts +0 -12
  193. package/dist/sandbox/exec-id.js +0 -20
  194. package/dist/sandbox/hmac.d.ts +0 -62
  195. package/dist/sandbox/hmac.js +0 -72
  196. package/dist/sandbox/submit-exec.d.ts +0 -38
  197. package/dist/sandbox/submit-exec.js +0 -80
  198. package/dist/sandbox/watchdog.d.ts +0 -71
  199. package/dist/sandbox/watchdog.js +0 -161
@@ -13,6 +13,7 @@
13
13
  * one call reports each fault at one time.
14
14
  */
15
15
  import { err, ok } from "neverthrow";
16
+ import { statisticSlot, treeSlot } from "../report-model/block-walk.js";
16
17
  /**
17
18
  * Map the resolved values of the blocks onto a `RenderValues` map.
18
19
  *
@@ -40,16 +41,24 @@ export function bridgeValues(resolutions, figureSrc) {
40
41
  values[resolution.blockId] = { type: "scalar", value: value.value };
41
42
  break;
42
43
  }
43
- case "table":
44
- case "chart": {
44
+ case "table": {
45
45
  const value = resolution.resolved;
46
46
  if (value.type !== "table") {
47
- mismatches.push(mismatch(resolution.blockId, resolution.kind, "table", value.type));
47
+ mismatches.push(mismatch(resolution.blockId, "table", "table", value.type));
48
48
  break;
49
49
  }
50
50
  values[resolution.blockId] = tableValue(value);
51
51
  break;
52
52
  }
53
+ case "chart": {
54
+ const chart = chartValue(resolution);
55
+ if (chart.isErr()) {
56
+ mismatches.push(...chart.error);
57
+ break;
58
+ }
59
+ values[resolution.blockId] = chart.value;
60
+ break;
61
+ }
53
62
  case "figure": {
54
63
  const value = resolution.resolved;
55
64
  if (value.type !== "file") {
@@ -66,6 +75,125 @@ export function bridgeValues(resolutions, figureSrc) {
66
75
  }
67
76
  return ok(values);
68
77
  }
78
+ /**
79
+ * Pair each block with the resolved value of its binding, in document order.
80
+ *
81
+ * A value-bearing kind carries the resolved value of its binding, looked up by the block id from the map
82
+ * that the resolution pass filled. A chart also carries the value of its track, of each tree, and of each statistic, looked
83
+ * up by the block id and the slot. The label of a statistic comes from the block, because the render value
84
+ * prints it. A no-value kind carries none. Where a reference sits in a block is the knowledge of
85
+ * `block-walk.ts`, thus this walk reads the slots that the walk names and never a binding field.
86
+ *
87
+ * The switch is exhaustive over the eight block kinds. A ninth kind reaches the end with no return, and
88
+ * the declared return type fails the build. Thus the walk cannot drop a kind in silence. A value-bearing
89
+ * block reaches here only when its reference resolved, because an unresolved reference short-circuits
90
+ * before this walk.
91
+ */
92
+ export function collectResolutions(blocks, resolvedByBlock, resolvedBySlot) {
93
+ const resolutions = [];
94
+ const visit = (block) => {
95
+ switch (block.kind) {
96
+ case "section":
97
+ resolutions.push({ blockId: block.id, kind: "section" });
98
+ for (const child of block.blocks) {
99
+ visit(child);
100
+ }
101
+ return;
102
+ case "text":
103
+ case "claim":
104
+ case "citation":
105
+ resolutions.push({ blockId: block.id, kind: block.kind });
106
+ return;
107
+ case "metric":
108
+ case "table":
109
+ case "figure": {
110
+ const resolved = resolvedByBlock.get(block.id);
111
+ if (resolved !== undefined) {
112
+ resolutions.push({ blockId: block.id, kind: block.kind, resolved });
113
+ }
114
+ return;
115
+ }
116
+ case "chart": {
117
+ const resolved = resolvedByBlock.get(block.id);
118
+ if (resolved === undefined) {
119
+ return;
120
+ }
121
+ const slots = resolvedBySlot.get(block.id);
122
+ const track = slots?.get("track");
123
+ const treeX = slots?.get(treeSlot("x"));
124
+ const treeY = slots?.get(treeSlot("y"));
125
+ const statistics = (block.statistics ?? []).flatMap((statistic, index) => {
126
+ const value = slots?.get(statisticSlot(index));
127
+ return value === undefined ? [] : [{ label: statistic.label, resolved: value }];
128
+ });
129
+ resolutions.push({
130
+ blockId: block.id,
131
+ kind: "chart",
132
+ resolved,
133
+ ...(track !== undefined ? { track } : {}),
134
+ ...(treeX !== undefined || treeY !== undefined
135
+ ? { trees: { ...(treeX !== undefined ? { x: treeX } : {}), ...(treeY !== undefined ? { y: treeY } : {}) } }
136
+ : {}),
137
+ ...(statistics.length > 0 ? { statistics } : {}),
138
+ });
139
+ return;
140
+ }
141
+ }
142
+ };
143
+ for (const block of blocks) {
144
+ visit(block);
145
+ }
146
+ return resolutions;
147
+ }
148
+ /**
149
+ * Map the resolutions of one chart onto its render value: the table of the binding, the rows of the track and
150
+ * of each tree, and the value of each statistic in block order. The binding, the track, and each tree resolve
151
+ * to a table, and each statistic resolves to a scalar. Each part of another type is one mismatch that names
152
+ * its slot.
153
+ */
154
+ function chartValue(resolution) {
155
+ const mismatches = [];
156
+ const binding = resolution.resolved;
157
+ if (binding.type !== "table") {
158
+ mismatches.push(mismatch(resolution.blockId, "chart", "table", binding.type));
159
+ }
160
+ const track = resolution.track;
161
+ if (track !== undefined && track.type !== "table") {
162
+ mismatches.push(mismatch(resolution.blockId, "chart", "table", track.type, "track"));
163
+ }
164
+ const trees = {};
165
+ for (const axis of ["x", "y"]) {
166
+ const tree = resolution.trees?.[axis];
167
+ if (tree === undefined)
168
+ continue;
169
+ if (tree.type !== "table") {
170
+ mismatches.push(mismatch(resolution.blockId, "chart", "table", tree.type, treeSlot(axis)));
171
+ continue;
172
+ }
173
+ trees[axis] = secondTable(tree);
174
+ }
175
+ const statistics = [];
176
+ for (const [index, statistic] of (resolution.statistics ?? []).entries()) {
177
+ if (statistic.resolved.type !== "scalar") {
178
+ mismatches.push(mismatch(resolution.blockId, "chart", "scalar", statistic.resolved.type, statisticSlot(index)));
179
+ continue;
180
+ }
181
+ statistics.push({ label: statistic.label, value: statistic.resolved.value });
182
+ }
183
+ if (mismatches.length > 0 || binding.type !== "table") {
184
+ return err(mismatches);
185
+ }
186
+ return ok({
187
+ ...tableValue(binding),
188
+ ...(statistics.length > 0 ? { statistics } : {}),
189
+ ...(track !== undefined && track.type === "table" ? { track: secondTable(track) } : {}),
190
+ ...(trees.x !== undefined || trees.y !== undefined ? { trees } : {}),
191
+ });
192
+ }
193
+ /** The rows of a second table of a chart, a track or a tree, with its column order where the resolution gives one. */
194
+ function secondTable(value) {
195
+ return value.columns !== undefined ? { rows: value.rows, columns: value.columns } : { rows: value.rows };
196
+ }
69
197
  /**
70
198
  * Map a resolved table onto a render table. The explicit column order carries through when it is present,
71
199
  * and it stays absent otherwise, thus the renderer keeps its first-row order as the default.
@@ -80,7 +208,10 @@ function tableValue(value) {
80
208
  }
81
209
  return { type: "table", rows: value.rows, ...total };
82
210
  }
83
- /** Make one mismatch that names the block, its kind, the resolved type that it needs, and the type that arrived. */
84
- function mismatch(blockId, blockKind, expected, actual) {
85
- return { blockId, blockKind, expected, actual };
211
+ /**
212
+ * Make one mismatch that names the block, its kind, the resolved type that it needs, and the type that
213
+ * arrived. A mismatch of a chart track, tree, or statistic names its slot.
214
+ */
215
+ function mismatch(blockId, blockKind, expected, actual, slot) {
216
+ return { blockId, blockKind, ...(slot !== undefined ? { slot } : {}), expected, actual };
86
217
  }
@@ -7,19 +7,55 @@
7
7
  * The card is the square corner-accent form of the table and the figure, with the mono title line over it. A
8
8
  * report is a document, thus the card wears no application-window costume. The chart body carries a fixed
9
9
  * height, because the chart runtime measures the container and a container with no height shows no chart.
10
+ * A faceted chart grows its body one row of height for each row of panels, and a figure of many rows states
11
+ * its own height. A figure that draws a block of a fixed size, for example a square, narrows the body to the
12
+ * block and centers it. Each export entry states the height of its export, which grows with a taller body.
13
+ *
14
+ * The option of the page carries the toolbox of the chart, and the download control of the toolbox opens the
15
+ * download menu of the card. The runtime draws that control on the canvas and binds a mouse click alone, thus the
16
+ * title line also carries a download button that the keyboard reaches and that opens the same menu. The menu
17
+ * follows the option script, and it holds the two SVG entries and the three PNG entries. An SVG entry links a
18
+ * staged file, or it names the hybrid file that the page builds for a dense chart. The page draws each PNG on
19
+ * click.
10
20
  */
11
21
  import type { ChartBlock } from "../../contracts/report-blocks.js";
12
22
  import type { EchartOption } from "../chart.js";
13
23
  import type { ReferenceLedger } from "../references.js";
14
24
  import { type ViewOptions } from "./lineage.js";
25
+ /**
26
+ * What the card carries beside the option: the staged names of the two SVG files, and the box of the chart
27
+ * body. A chart past the export bound carries no SVG name. The page then builds the hybrid SVG of a chart that
28
+ * holds a point layer, and the menu states that the PNG serves each other such chart.
29
+ * `bodyPx` is the height of the body, and `widthPx` is the width of a figure that draws a block of a fixed
30
+ * size.
31
+ */
32
+ export interface ChartCard {
33
+ readonly svg?: {
34
+ readonly single: string;
35
+ readonly double: string;
36
+ };
37
+ readonly bodyPx: number;
38
+ readonly widthPx?: number;
39
+ }
40
+ /**
41
+ * The note of a chart past the export bound that holds no point layer. The page draws its PNG, and no SVG
42
+ * serves it.
43
+ */
44
+ export declare const PNG_ONLY_NOTE = "The chart holds too many points for an SVG file. Use a PNG.";
45
+ /** The note that the menu of a dense chart shows when the page cannot build its hybrid SVG file. */
46
+ export declare const HYBRID_FAULT_NOTE = "The page could not build the SVG file. Use a PNG.";
47
+ /** The text of the download control in the title line of the card. */
48
+ export declare const DOWNLOAD_CONTROL_LABEL = "Download";
15
49
  /**
16
50
  * Render the card markup for a chart.
17
51
  *
18
- * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
19
- * appendix names the artifact that the chart plots. A chart with no title still shows its marker on the
20
- * same line.
52
+ * Each reference of the chart joins the reference ladder: the whole-table binding, the track, the tree of `x`,
53
+ * the tree of `y`, and each statistic, in that order. Thus the title line carries one marker for each, and the
54
+ * appendix names the artifact that the chart plots, each second table that it draws, and each cell that it
55
+ * prints. A chart with
56
+ * no title still shows its markers on the same line.
21
57
  *
22
- * `view.lineage` states that the page carries a provenance document. The card then stamps its keys, and the
23
- * marker carries the control that opens the chain.
58
+ * `view.lineage` states that the page carries a provenance document. The card then stamps the keys of its
59
+ * references, and each marker carries the control that opens the chain of its own key.
24
60
  */
25
- export declare function renderChart(block: ChartBlock, ledger: ReferenceLedger, option: EchartOption, view?: ViewOptions): string;
61
+ export declare function renderChart(block: ChartBlock, ledger: ReferenceLedger, option: EchartOption, view?: ViewOptions, card?: ChartCard): string;
@@ -8,30 +8,91 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
8
8
  * The card is the square corner-accent form of the table and the figure, with the mono title line over it. A
9
9
  * report is a document, thus the card wears no application-window costume. The chart body carries a fixed
10
10
  * height, because the chart runtime measures the container and a container with no height shows no chart.
11
+ * A faceted chart grows its body one row of height for each row of panels, and a figure of many rows states
12
+ * its own height. A figure that draws a block of a fixed size, for example a square, narrows the body to the
13
+ * block and centers it. Each export entry states the height of its export, which grows with a taller body.
14
+ *
15
+ * The option of the page carries the toolbox of the chart, and the download control of the toolbox opens the
16
+ * download menu of the card. The runtime draws that control on the canvas and binds a mouse click alone, thus the
17
+ * title line also carries a download button that the keyboard reaches and that opens the same menu. The menu
18
+ * follows the option script, and it holds the two SVG entries and the three PNG entries. An SVG entry links a
19
+ * staged file, or it names the hybrid file that the page builds for a dense chart. The page draws each PNG on
20
+ * click.
11
21
  */
12
22
  import { raw } from "hono/html";
23
+ import { kebabFilename } from "../../tools/display/normalize-echart-spec.js";
24
+ import { stagedSource } from "../assets.js";
25
+ import { chartMenuControlId, chartMenuId, MENU_CONTROL_ATTRIBUTE, MENU_FAULT_CLASS, pageChartOption } from "../chart-toolbox.js";
26
+ import { CHART_BODY_PX, CHART_EXPORT_SIZES, exportSizeFor } from "../design.js";
27
+ import { holdsPointLayer } from "../hybrid-svg.js";
13
28
  import { blockMark } from "./block-mark.js";
14
29
  import { DEFAULT_VIEW_OPTIONS, lineagePlace, lineageStamp } from "./lineage.js";
15
30
  import { Marker } from "./references-view.js";
16
31
  import { scriptJson } from "../script-json.js";
32
+ /** The card of a chart with the default body and no SVG file. */
33
+ const PLAIN_CARD = { bodyPx: CHART_BODY_PX };
34
+ /**
35
+ * The note of a chart past the export bound that holds no point layer. The page draws its PNG, and no SVG
36
+ * serves it.
37
+ */
38
+ export const PNG_ONLY_NOTE = "The chart holds too many points for an SVG file. Use a PNG.";
39
+ /** The note that the menu of a dense chart shows when the page cannot build its hybrid SVG file. */
40
+ export const HYBRID_FAULT_NOTE = "The page could not build the SVG file. Use a PNG.";
41
+ /** The name of the download menu for a reader who hears the page instead of seeing it. */
42
+ const MENU_LABEL = "Download the chart";
43
+ /** The text of the download control in the title line of the card. */
44
+ export const DOWNLOAD_CONTROL_LABEL = "Download";
45
+ /** The two SVG entries of the menu, in the order that the menu shows them. */
46
+ const SVG_EXPORTS = ["single", "double"];
47
+ /** The three PNG entries, in the order that the menu shows them, with the suffix of each file name. */
48
+ const PNG_EXPORTS = [
49
+ { kind: "single", suffix: `${CHART_EXPORT_SIZES.single.widthMm}mm` },
50
+ { kind: "double", suffix: `${CHART_EXPORT_SIZES.double.widthMm}mm` },
51
+ { kind: "slide", suffix: "slide" },
52
+ ];
17
53
  /**
18
54
  * Render the card markup for a chart.
19
55
  *
20
- * The whole-table binding joins the reference ladder, thus the title line carries the marker and the
21
- * appendix names the artifact that the chart plots. A chart with no title still shows its marker on the
22
- * same line.
56
+ * Each reference of the chart joins the reference ladder: the whole-table binding, the track, the tree of `x`,
57
+ * the tree of `y`, and each statistic, in that order. Thus the title line carries one marker for each, and the
58
+ * appendix names the artifact that the chart plots, each second table that it draws, and each cell that it
59
+ * prints. A chart with
60
+ * no title still shows its markers on the same line.
23
61
  *
24
- * `view.lineage` states that the page carries a provenance document. The card then stamps its keys, and the
25
- * marker carries the control that opens the chain.
62
+ * `view.lineage` states that the page carries a provenance document. The card then stamps the keys of its
63
+ * references, and each marker carries the control that opens the chain of its own key.
26
64
  */
27
- export function renderChart(block, ledger, option, view = DEFAULT_VIEW_OPTIONS) {
28
- const n = ledger.mark(block.binding);
65
+ export function renderChart(block, ledger, option, view = DEFAULT_VIEW_OPTIONS, card = PLAIN_CARD) {
66
+ const references = [
67
+ block.binding,
68
+ ...(block.track !== undefined ? [block.track.binding] : []),
69
+ ...(block.trees?.x !== undefined ? [block.trees.x.binding] : []),
70
+ ...(block.trees?.y !== undefined ? [block.trees.y.binding] : []),
71
+ ...(block.statistics ?? []).map((statistic) => statistic.value),
72
+ ];
73
+ const markers = references.map((reference, index) => _jsx(Marker, { n: ledger.mark(reference), lineage: lineagePlace(view.lineage, reference, index) }));
29
74
  const containerId = chartContainerId(block.id);
30
75
  // The JSON goes to the page through `raw()`, thus `scriptJson` is the sole guard of this sink. It
31
76
  // replaces every `<` with `\u003c`, thus a `</script` sequence in a string cell cannot close the
32
77
  // element early. The JSON parser reads `\u003c` as `<`, thus the option value stays exact.
33
- const json = scriptJson(option);
34
- return String(_jsxs("div", { class: "report-chart", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, [block.binding]), children: [_jsxs("div", { class: "report-chart-title", children: [block.title, _jsx(Marker, { n: n, lineage: lineagePlace(view.lineage, block.binding) })] }), _jsxs("div", { class: "report-chart-card corner-accents", 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] }));
78
+ const json = scriptJson(pageChartOption(option, block.chartType));
79
+ const bodyStyle = [
80
+ ...(card.bodyPx !== CHART_BODY_PX ? [`height: ${card.bodyPx}px`] : []),
81
+ ...(card.widthPx !== undefined ? [`max-width: ${card.widthPx}px; margin-inline: auto`] : []),
82
+ ].join("; ");
83
+ const stem = kebabFilename(block.title ?? block.id);
84
+ const hybrid = card.svg === undefined && holdsPointLayer(option);
85
+ return String(_jsxs("div", { class: "report-chart", ...blockMark(block.id), ...lineageStamp(view.lineage, block.id, references), children: [_jsxs("div", { class: "report-chart-title", children: [block.title, markers, _jsx("button", { type: "button", id: chartMenuControlId(containerId), class: "report-chart-download", "aria-haspopup": "menu", "aria-expanded": "false", "aria-controls": chartMenuId(containerId), [MENU_CONTROL_ATTRIBUTE]: containerId, children: DOWNLOAD_CONTROL_LABEL })] }), _jsxs("div", { class: "report-chart-card corner-accents", children: [_jsx("div", { id: containerId, "data-echarts-id": block.id, class: "chart-container", ...(bodyStyle !== "" ? { style: bodyStyle } : {}) }), _jsx("script", { type: "application/json", children: raw(json) }), _jsxs("div", { id: chartMenuId(containerId), class: "report-chart-menu", role: "menu", "aria-label": MENU_LABEL, children: [SVG_EXPORTS.map((kind) => {
86
+ const size = exportSizeFor(CHART_EXPORT_SIZES[kind], card.bodyPx);
87
+ const file = `${stem}-${size.widthMm}mm.svg`;
88
+ const label = `SVG · ${size.label}`;
89
+ if (card.svg !== undefined) {
90
+ return (_jsx("a", { class: "report-chart-menu-item", role: "menuitem", tabindex: -1, href: stagedSource(card.svg[kind]), download: file, children: label }));
91
+ }
92
+ if (!hybrid)
93
+ return null;
94
+ return (_jsx("button", { type: "button", class: "report-chart-menu-item", role: "menuitem", tabindex: -1, "data-hybrid": kind, "data-chart": containerId, "data-height": String(size.heightPx), "data-height-mm": String(size.heightMm), "data-file": file, children: label }));
95
+ }), card.svg === undefined && !hybrid ? (_jsx("span", { class: "report-chart-menu-note", role: "menuitem", "aria-disabled": "true", children: PNG_ONLY_NOTE })) : null, hybrid ? (_jsx("span", { class: `report-chart-menu-note ${MENU_FAULT_CLASS}`, role: "menuitem", "aria-disabled": "true", tabindex: -1, children: HYBRID_FAULT_NOTE })) : null, _jsx("div", { class: "report-chart-menu-rule", role: "separator" }), PNG_EXPORTS.map((png) => (_jsxs("button", { type: "button", class: "report-chart-menu-item", role: "menuitem", tabindex: -1, "data-export": png.kind, "data-chart": containerId, "data-height": String(exportSizeFor(CHART_EXPORT_SIZES[png.kind], card.bodyPx).heightPx), "data-file": `${stem}-${png.suffix}.png`, children: ["PNG \u00B7 ", CHART_EXPORT_SIZES[png.kind].label] })))] })] }), block.caption !== undefined ? _jsx("p", { class: "report-caption", children: block.caption }) : null] }));
35
96
  }
36
97
  /** The container id derives from the block id, thus the id stays stable across renders. */
37
98
  function chartContainerId(blockId) {
@@ -20,7 +20,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
20
20
  import { raw } from "hono/html";
21
21
  import { ASSET_HEAD, CHART_BOOTSTRAP, FADE_IN_OBSERVER, GRID_ASSET_HEAD, GRID_BOOTSTRAP, LINEAGE_ASSET_HEAD, LINEAGE_POPOVER, SECTION_SPY, TABLE_DATA_DECODER, } from "../page.js";
22
22
  import { stagedSource } from "../assets.js";
23
- import { DESIGN_CSS, ECHARTS_THEME, ECHARTS_THEME_NAME } from "../design.js";
23
+ import { CHART_THEMES, chartTheme, DESIGN_CSS } from "../design.js";
24
24
  import { renderReferences } from "./references-view.js";
25
25
  import { scriptJson } from "../script-json.js";
26
26
  /** The constant eyebrow of the hero. The render reads no clock, thus the hero carries no date. */
@@ -30,20 +30,19 @@ const FOOTER_NOTE = "Powered by Inflexa";
30
30
  /** The title of the one appendix. It holds each artifact and each paper that the report names. */
31
31
  const REFERENCES_TITLE = "References";
32
32
  /**
33
- * The theme object as script-safe JSON. `scriptJson` replaces every `<` with `\u003c`, thus a later edit
34
- * that adds a `<` to the theme stays safe inside the script sink. The JSON parser reads `\u003c` as `<`, thus
35
- * the theme value stays exact.
36
- */
37
- const THEME_JSON = scriptJson(ECHARTS_THEME);
38
- /**
39
- * The registration script for the ECharts theme. It runs before the chart bootstrap, thus each chart
40
- * reads the registered theme by its name. The guard skips the call when the ECharts runtime did not load.
33
+ * The registration script for the ECharts themes: the page theme, and the two export themes that the PNG
34
+ * download reads. It runs before the chart bootstrap, thus each chart reads the registered theme by its
35
+ * name. The guard skips the calls when the ECharts runtime did not load.
36
+ *
37
+ * Each theme rides as script-safe JSON. `scriptJson` replaces every `<` with `\u003c`, thus a later edit that
38
+ * adds a `<` to the theme stays safe inside the script sink. The JSON parser reads `\u003c` as `<`, thus the
39
+ * theme value stays exact.
41
40
  */
42
41
  const THEME_REGISTRATION = `(function () {
43
42
  if (typeof echarts === "undefined") {
44
43
  return;
45
44
  }
46
- echarts.registerTheme(${JSON.stringify(ECHARTS_THEME_NAME)}, ${THEME_JSON});
45
+ ${CHART_THEMES.map((theme) => ` echarts.registerTheme(${JSON.stringify(theme.name)}, ${scriptJson(chartTheme(theme.textPx))});`).join("\n")}
47
46
  })();`;
48
47
  /**
49
48
  * Wrap already-escaped markup in one full-bleed band. An even index gives the white background and the dot
@@ -103,8 +103,8 @@ export function assembleCoreRuntime(deps) {
103
103
  runAuthorizer: wf.dataProfile.runAuthorizer,
104
104
  ...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
105
105
  });
106
- // A session derivation runs its container here and not in the turn. The await of an exec is a
107
- // workflow-body call under the callback transport, thus the tool starts this workflow and awaits it.
106
+ // A session derivation runs its container here and not in the turn. An exec is a durable step of a
107
+ // workflow, thus the tool starts this workflow and awaits it.
108
108
  const deriveTableExec = registerDeriveTableExecWorkflow({
109
109
  sandboxClient: wf.dataProfile.sandboxClient,
110
110
  ...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
@@ -9,26 +9,18 @@
9
9
  * - `client.ts` (here) — the interface and the per-method types.
10
10
  * - `create-sandbox.ts` — the factory + per-backend `createSandbox` /
11
11
  * `teardown` / `isAlive` implementations.
12
- * - `submit-exec.ts` + `await-exec.ts` — `submitExec` and the workflow-
13
- * body recv loop, which are backend-agnostic (HTTP + DBOS messages).
12
+ * - `exec.ts` — the submit and the poll loop of one exec, which are
13
+ * backend-agnostic (HTTP only).
14
14
  *
15
15
  * Lifetime separation (CONTEXT.md "Sandbox exec"):
16
16
  * - The **sandbox machine** lifetime is `createSandbox → ... → teardown`.
17
17
  * Many execs may fire against the same machine.
18
- * - The **exec** lifetime is one `submitExec → awaitExec`.
19
- *
20
- * **Every caller of `awaitExec` runs inside a DBOS workflow body.** `DBOS.recv`
21
- * and `DBOS.writeStream` are body-only, thus the callback transport cannot
22
- * settle anywhere else, and the poll transport reaches for `DBOS.runStep` and
23
- * `DBOS.sleepms` defaults. A caller that starts from a live chat turn registers
24
- * its own workflow and starts it: `tasks/extract-values.ts` and
25
- * `tasks/derive-table-exec.ts` are the two worked examples. No type carries this
26
- * rule yet, thus a new caller must read this paragraph.
18
+ * - The **exec** lifetime is one `exec` call: one durable step.
27
19
  */
28
20
  import type { ResultAsync } from "neverthrow";
29
21
  import type { SpawnSession } from "../auth/types.js";
30
22
  import type { SandboxError } from "./sandbox-error.js";
31
- import type { ExecEmit, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxSpec, SubmitExecBody, ToolchainSource } from "./types.js";
23
+ import type { ExecEmit, ExecRequest, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SandboxSpec, ToolchainSource } from "./types.js";
32
24
  export interface SandboxClient {
33
25
  /**
34
26
  * The declared owner of the sandbox toolchain, as the client was composed
@@ -42,7 +34,7 @@ export interface SandboxClient {
42
34
  /**
43
35
  * DBOS step (`sandbox.create`) — the spawn half of the two-step create
44
36
  * (see the harness-sandbox-exec spec). Launches the sandbox-base container/Job under the
45
- * pre-minted `identity` (name + HMAC secret checkpointed by `sandbox.mint`),
37
+ * pre-minted `identity` (the name checkpointed by `sandbox.mint`),
46
38
  * stamps the labels, waits for `/health`, records the live handle in
47
39
  * the active-sandbox registry, and returns the in-memory `SandboxRef`. A
48
40
  * recovery re-run whose machine already exists (the crash window between
@@ -55,30 +47,29 @@ export interface SandboxClient {
55
47
  */
56
48
  createSandbox(session: SpawnSession, spec: SandboxSpec, identity: SandboxIdentity): ResultAsync<SandboxRef, SandboxError>;
57
49
  /**
58
- * DBOS step (`sandbox.submit-exec.${execId}`). POSTs the command to
59
- * sandbox-server's `/exec` and returns after the HTTP 202 ack. Does NOT
60
- * wait for command completion. Replay-safe: the cached step output is
61
- * returned on subsequent invocations; any duplicate POST that reaches
62
- * sandbox-server during the in-flight window is deduped server-side
63
- * (PR #3 change 4) on `execId`.
64
- */
65
- submitExec(ref: SandboxRef, body: SubmitExecBody): Promise<void>;
66
- /**
67
- * Awaits a submitted exec's terminal result under the client's transport.
68
- * Poll (default) loops durable, signed `GET /exec/{execId}?since={cursor}`
69
- * steps against the sandbox in `ref`; callback loops
70
- * `DBOS.recv("exec-event:${execId}", T)` with a signed pull as its
71
- * recovery backstop. Both HMAC-verify every body against
72
- * `ref.callbackSecret`, forward progress events via `emit`, and are
73
- * bounded by `deadline` (absolute unix-ms timestamp).
50
+ * DBOS step (`sandbox.exec`). Submits the command to the sandbox in `ref`,
51
+ * polls `GET /exec/{execId}?since={cursor}` until the exec is
52
+ * terminal, forwards each progress event through `emit`, and returns the
53
+ * result. `deadline` is an absolute unix-ms timestamp.
54
+ *
55
+ * The exec id is the id of the workflow and the function id of the step
56
+ * that the exec runs in. Called from a workflow body, the exec is its own
57
+ * step. Called inside a step (a tool call of the agent loop), it runs
58
+ * inline in that step, thus ONE exec for each step: a second exec in the
59
+ * same step gets the same id, and the sandbox gives it the result of the
60
+ * first. Outside a workflow it throws.
61
+ *
62
+ * A recovered step submits again under the same id. The sandbox dedups the
63
+ * submit on the id, thus the poll attaches to the exec that already ran or
64
+ * runs, and `emit` sees its events again from the start.
74
65
  */
75
- awaitExec(ref: SandboxRef, execId: string, emit: ExecEmit, deadline: number): Promise<ExecResult>;
66
+ exec(ref: SandboxRef, request: ExecRequest, emit: ExecEmit, deadline: number): Promise<ExecResult>;
76
67
  /**
77
68
  * Per-sandbox-machine liveness. `alive: false` only when observably dead
78
69
  * (terminal pod phase, missing container); `oomKilled` marks a death the
79
70
  * backend attributes to the machine's memory limit. Transient API errors
80
- * throw, so callers can decide whether to retry — silently lying about
81
- * dead sandboxes would race the synthetic-complete path.
71
+ * throw, so callers can decide whether to retry — a false `dead` verdict
72
+ * would fail an exec that still runs.
82
73
  */
83
74
  isAlive(ref: SandboxRef): Promise<SandboxLiveness>;
84
75
  /**
@@ -9,20 +9,12 @@
9
9
  * - `client.ts` (here) — the interface and the per-method types.
10
10
  * - `create-sandbox.ts` — the factory + per-backend `createSandbox` /
11
11
  * `teardown` / `isAlive` implementations.
12
- * - `submit-exec.ts` + `await-exec.ts` — `submitExec` and the workflow-
13
- * body recv loop, which are backend-agnostic (HTTP + DBOS messages).
12
+ * - `exec.ts` — the submit and the poll loop of one exec, which are
13
+ * backend-agnostic (HTTP only).
14
14
  *
15
15
  * Lifetime separation (CONTEXT.md "Sandbox exec"):
16
16
  * - The **sandbox machine** lifetime is `createSandbox → ... → teardown`.
17
17
  * Many execs may fire against the same machine.
18
- * - The **exec** lifetime is one `submitExec → awaitExec`.
19
- *
20
- * **Every caller of `awaitExec` runs inside a DBOS workflow body.** `DBOS.recv`
21
- * and `DBOS.writeStream` are body-only, thus the callback transport cannot
22
- * settle anywhere else, and the poll transport reaches for `DBOS.runStep` and
23
- * `DBOS.sleepms` defaults. A caller that starts from a live chat turn registers
24
- * its own workflow and starts it: `tasks/extract-values.ts` and
25
- * `tasks/derive-table-exec.ts` are the two worked examples. No type carries this
26
- * rule yet, thus a new caller must read this paragraph.
18
+ * - The **exec** lifetime is one `exec` call: one durable step.
27
19
  */
28
20
  export {};
@@ -8,8 +8,8 @@
8
8
  *
9
9
  * Registry write/clear callbacks are wired here so the per-backend
10
10
  * `createSandbox` / `teardown` implementations stay free of state-layer
11
- * coupling (they just call the closures we hand them). `submitExec` and
12
- * `awaitExec` are backend-agnostic — they only need the SandboxRef.
11
+ * coupling (they just call the closures we hand them). `exec` is
12
+ * backend-agnostic — it only needs the SandboxRef.
13
13
  */
14
14
  import type { V1Toleration } from "@kubernetes/client-node";
15
15
  import type { Pool } from "pg";
@@ -17,11 +17,10 @@ import type { Logger } from "../lib/logger.js";
17
17
  import { type ResourceLimits } from "../config/resource-limits.js";
18
18
  import { type ResolveWorkspaceRoot } from "../workspace/paths.js";
19
19
  import { clearSandboxRef } from "../state/index.js";
20
- import { type AwaitExecOptions } from "./await-exec.js";
21
20
  import type { SandboxClient } from "./client.js";
21
+ import { type ExecDeps } from "./exec.js";
22
22
  import { type MountPlanCoords } from "./mount-plan.js";
23
- import { type SubmitExecDeps } from "./submit-exec.js";
24
- import { type FarmSource, type ResolveSandboxLabels, type SandboxTransport, type ToolchainSource } from "./types.js";
23
+ import type { FarmSource, ResolveSandboxLabels, ToolchainSource } from "./types.js";
25
24
  /**
26
25
  * Narrow config slice the sandbox factory reads off `Env` — the backend
27
26
  * default and the K8s namespace fallback. Composition roots map their
@@ -36,14 +35,6 @@ export interface CreateSandboxClientConfig {
36
35
  pool: Pool;
37
36
  /** Backend default + namespace fallback source. */
38
37
  env: SandboxBackendConfig;
39
- /** Cortex base URL injected into sandbox-server's env for callbacks. */
40
- cortexBaseUrl: string;
41
- /**
42
- * Result transport for every sandbox this client creates. Threaded to the
43
- * container as `SANDBOX_TRANSPORT` and to `awaitExec`'s loop selection.
44
- * Defaults to `poll`.
45
- */
46
- transport?: SandboxTransport;
47
38
  /** Default sandbox-base image when the workflow doesn't override per-step. */
48
39
  image: string;
49
40
  /**
@@ -154,18 +145,12 @@ export interface CreateSandboxClientConfig {
154
145
  * unset.
155
146
  */
156
147
  logger?: Logger;
157
- /** Dependency seams (fetch, durable step/sleep, clock, recv, warn sink) forwarded to submit/await. */
158
- submitDeps?: SubmitExecDeps;
159
- awaitOptions?: AwaitExecOptions;
148
+ /**
149
+ * The effects of each exec (fetch, clock, sleep, cancel read, warn sink).
150
+ * An `isAlive` here wins over the backend inspect that the client wires.
151
+ */
152
+ execDeps?: ExecDeps;
160
153
  }
161
- /**
162
- * Assemble `awaitExec`'s options: the liveness probe self-wires from the
163
- * backend ops (the poll loop's escalation always has its arbiter under the
164
- * client), explicit seam injections in `base` win over the self-wired probe,
165
- * and the transport is client-owned — never overridable through the seam bag.
166
- * Exported for tests.
167
- */
168
- export declare function composeAwaitOptions(base: AwaitExecOptions | undefined, transport: SandboxTransport, isAlive: NonNullable<AwaitExecOptions["isAlive"]>): AwaitExecOptions;
169
154
  /**
170
155
  * Backend-agnostic pre-creation of the writable tree under the analysis's
171
156
  * resolved workspace root. On Docker that root is the host dir the container