@inflexa-ai/harness 0.46.2 → 0.47.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 (271) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/conversation-agent.d.ts +3 -8
  3. package/dist/agents/conversation-agent.js +24 -4
  4. package/dist/agents/report-session-agent.d.ts +11 -14
  5. package/dist/agents/report-session-agent.js +30 -17
  6. package/dist/agents/sandbox/shared.d.ts +18 -13
  7. package/dist/agents/sandbox/shared.js +42 -27
  8. package/dist/agents/sandbox/types.d.ts +1 -1
  9. package/dist/agents/sandbox-catalog.d.ts +2 -0
  10. package/dist/agents/sandbox-catalog.js +30 -1
  11. package/dist/app/synthesize-run.d.ts +10 -6
  12. package/dist/app/synthesize-run.js +4 -2
  13. package/dist/contracts/analogy-report.d.ts +46 -2
  14. package/dist/contracts/analogy-report.js +31 -25
  15. package/dist/contracts/chat-parts.d.ts +3 -14
  16. package/dist/contracts/report-reference.d.ts +2 -1
  17. package/dist/contracts/report-reference.js +2 -1
  18. package/dist/contracts/schemas/chat-parts.d.ts +0 -27
  19. package/dist/contracts/schemas/chat-parts.js +1 -3
  20. package/dist/execution/run-canceler.d.ts +2 -0
  21. package/dist/execution/run-canceler.js +9 -0
  22. package/dist/execution/run-synthesis.d.ts +36 -8
  23. package/dist/execution/run-synthesis.js +114 -32
  24. package/dist/index.d.ts +2 -5
  25. package/dist/index.js +3 -6
  26. package/dist/input-scan/enrich.d.ts +5 -10
  27. package/dist/input-scan/enrich.js +12 -8
  28. package/dist/lib/markdown-pdf.d.ts +27 -0
  29. package/dist/lib/markdown-pdf.js +236 -0
  30. package/dist/lib/metrics.d.ts +31 -0
  31. package/dist/lib/metrics.js +64 -0
  32. package/dist/lib/pdf-text.d.ts +23 -0
  33. package/dist/lib/pdf-text.js +34 -0
  34. package/dist/lib/regulatory-corpus/fetch-fda-guidance.js +3 -5
  35. package/dist/lib/regulatory-corpus/fetch-ich.js +3 -5
  36. package/dist/loop/run-loop.d.ts +5 -0
  37. package/dist/loop/run-loop.js +30 -2
  38. package/dist/loop/tool-name-repair.d.ts +11 -0
  39. package/dist/loop/tool-name-repair.js +23 -0
  40. package/dist/loop/types.d.ts +14 -0
  41. package/dist/memory/card-builders.d.ts +1 -1
  42. package/dist/prompts/SOUL.d.ts +6 -6
  43. package/dist/prompts/SOUL.js +22 -121
  44. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  45. package/dist/prompts/analogical-reasoner.js +35 -101
  46. package/dist/prompts/briefing.d.ts +20 -11
  47. package/dist/prompts/briefing.js +36 -19
  48. package/dist/prompts/conversation.d.ts +1 -1
  49. package/dist/prompts/conversation.js +103 -383
  50. package/dist/prompts/planner.d.ts +1 -5
  51. package/dist/prompts/planner.js +45 -128
  52. package/dist/prompts/report-session.d.ts +1 -1
  53. package/dist/prompts/report-session.js +26 -57
  54. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  55. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +3 -3
  56. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  57. package/dist/prompts/sandbox/cheminformatics-agent.js +12 -47
  58. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  59. package/dist/prompts/sandbox/chromatin-agent.js +4 -5
  60. package/dist/prompts/sandbox/data-profiler.js +11 -34
  61. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  62. package/dist/prompts/sandbox/dna-methylation-agent.js +3 -1
  63. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  64. package/dist/prompts/sandbox/drug-repurposing-agent.js +23 -73
  65. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  66. package/dist/prompts/sandbox/enrichment-agent.js +3 -1
  67. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  68. package/dist/prompts/sandbox/genomic-variant-agent.js +3 -4
  69. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  70. package/dist/prompts/sandbox/immune-profiling-agent.js +5 -4
  71. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  72. package/dist/prompts/sandbox/metabolomics-agent.js +3 -1
  73. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  74. package/dist/prompts/sandbox/microbiome-agent.js +3 -1
  75. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  76. package/dist/prompts/sandbox/multi-omics-integration-agent.js +3 -1
  77. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  78. package/dist/prompts/sandbox/multimodal-sc-agent.js +3 -1
  79. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  80. package/dist/prompts/sandbox/network-agent.js +3 -1
  81. package/dist/prompts/sandbox/pkpd-clinical-response-agent.d.ts +1 -1
  82. package/dist/prompts/sandbox/pkpd-clinical-response-agent.js +4 -7
  83. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  84. package/dist/prompts/sandbox/proteomics-agent.js +3 -1
  85. package/dist/prompts/sandbox/scientific-executor.d.ts +1 -1
  86. package/dist/prompts/sandbox/scientific-executor.js +0 -12
  87. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  88. package/dist/prompts/sandbox/single-cell-agent.js +3 -1
  89. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -1
  91. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/statistical-modeling-agent.js +4 -4
  93. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/translational-safety-agent.js +10 -29
  95. package/dist/prompts/sandbox-standards.d.ts +15 -18
  96. package/dist/prompts/sandbox-standards.js +96 -305
  97. package/dist/prompts/synthesis-agent.d.ts +1 -1
  98. package/dist/prompts/synthesis-agent.js +24 -90
  99. package/dist/provenance/collector.d.ts +2 -5
  100. package/dist/provenance/collector.js +2 -5
  101. package/dist/provenance/seam.d.ts +36 -1
  102. package/dist/provenance/workspace-reads.d.ts +36 -0
  103. package/dist/provenance/workspace-reads.js +33 -0
  104. package/dist/report-model/production-resolver.d.ts +22 -52
  105. package/dist/report-model/production-resolver.js +43 -112
  106. package/dist/report-render/chart-export.d.ts +9 -0
  107. package/dist/report-render/chart-export.js +1 -1
  108. package/dist/report-render/views/chart-view.d.ts +1 -0
  109. package/dist/report-render/views/chart-view.js +1 -0
  110. package/dist/report-render/views/lineage.d.ts +1 -0
  111. package/dist/report-render/views/page-view.d.ts +1 -0
  112. package/dist/report-render/views/page-view.js +1 -0
  113. package/dist/report-render/views/prose.d.ts +1 -0
  114. package/dist/report-render/views/prose.js +1 -0
  115. package/dist/report-render/views/references-view.d.ts +1 -0
  116. package/dist/report-render/views/values.d.ts +1 -0
  117. package/dist/report-render/views/values.js +1 -0
  118. package/dist/runtime/assemble.d.ts +20 -9
  119. package/dist/runtime/assemble.js +47 -38
  120. package/dist/sandbox/client.d.ts +5 -3
  121. package/dist/sandbox/create-sandbox.d.ts +7 -12
  122. package/dist/sandbox/create-sandbox.js +15 -37
  123. package/dist/sandbox/docker-client.d.ts +1 -3
  124. package/dist/sandbox/docker-client.js +3 -5
  125. package/dist/sandbox/exec-outcome.d.ts +11 -11
  126. package/dist/sandbox/exec-outcome.js +22 -22
  127. package/dist/sandbox/exec.d.ts +5 -0
  128. package/dist/sandbox/exec.js +9 -1
  129. package/dist/sandbox/identity.d.ts +4 -16
  130. package/dist/sandbox/identity.js +5 -21
  131. package/dist/sandbox/k8s-client.d.ts +0 -1
  132. package/dist/sandbox/k8s-client.js +8 -13
  133. package/dist/sandbox/lease-names.d.ts +6 -0
  134. package/dist/sandbox/lease-names.js +12 -0
  135. package/dist/sandbox/leases.d.ts +105 -0
  136. package/dist/sandbox/leases.js +519 -0
  137. package/dist/sandbox/liveness.d.ts +5 -2
  138. package/dist/sandbox/mount-plan.d.ts +10 -1
  139. package/dist/sandbox/mount-plan.js +7 -1
  140. package/dist/sandbox/reconciler.d.ts +58 -0
  141. package/dist/sandbox/reconciler.js +153 -0
  142. package/dist/sandbox/sandbox-step-translate.d.ts +1 -1
  143. package/dist/sandbox/types.d.ts +25 -18
  144. package/dist/sandbox/types.js +10 -4
  145. package/dist/schemas/plan-schemas.d.ts +0 -21
  146. package/dist/schemas/plan-schemas.js +2 -3
  147. package/dist/schemas/run-synthesis.d.ts +2 -2
  148. package/dist/schemas/run-synthesis.js +3 -3
  149. package/dist/schemas/validate-plan.d.ts +1 -14
  150. package/dist/schemas/validate-plan.js +5 -24
  151. package/dist/schemas/workflow-state.d.ts +3 -32
  152. package/dist/schemas/workflow-state.js +4 -13
  153. package/dist/state/index.d.ts +0 -1
  154. package/dist/state/index.js +0 -1
  155. package/dist/state/migrations/20261030120000_lease_the_sandboxes.d.ts +7 -0
  156. package/dist/state/migrations/20261030120000_lease_the_sandboxes.js +34 -0
  157. package/dist/state/migrations/20261030120100_drop_the_report_derivations.d.ts +5 -0
  158. package/dist/state/migrations/20261030120100_drop_the_report_derivations.js +9 -0
  159. package/dist/state/migrations/20261030120200_keep_the_report_derivations.d.ts +6 -0
  160. package/dist/state/migrations/20261030120200_keep_the_report_derivations.js +10 -0
  161. package/dist/state/migrations/index.js +6 -0
  162. package/dist/state/purge-analysis.d.ts +4 -2
  163. package/dist/state/purge-analysis.js +22 -11
  164. package/dist/state/sandbox-leases.d.ts +81 -0
  165. package/dist/state/sandbox-leases.js +247 -0
  166. package/dist/state/schema.d.ts +1 -13
  167. package/dist/state/schema.js +1 -6
  168. package/dist/state/step-executions.d.ts +0 -2
  169. package/dist/state/step-executions.js +1 -4
  170. package/dist/tasks/data-profile.d.ts +40 -2
  171. package/dist/tasks/data-profile.js +89 -87
  172. package/dist/tools/ad-hoc-router.d.ts +1 -6
  173. package/dist/tools/ad-hoc-router.js +5 -48
  174. package/dist/tools/bio/comptox.js +1 -1
  175. package/dist/tools/bio/drug-gene-interactions.js +1 -1
  176. package/dist/tools/bio/gene-disease-evidence.js +1 -1
  177. package/dist/tools/bio/keys.d.ts +1 -1
  178. package/dist/tools/bio/pubmed.js +31 -0
  179. package/dist/tools/bio/search-gene.js +17 -0
  180. package/dist/tools/bio/search-geo-datasets.js +13 -0
  181. package/dist/tools/bio/search-regulatory-approvals.js +1 -1
  182. package/dist/tools/define-tool.d.ts +25 -2
  183. package/dist/tools/define-tool.js +11 -1
  184. package/dist/tools/display/show-user.js +1 -1
  185. package/dist/tools/execute-analysis.d.ts +1 -3
  186. package/dist/tools/execute-analysis.js +5 -33
  187. package/dist/tools/report-session/index.d.ts +0 -1
  188. package/dist/tools/report-session/index.js +0 -1
  189. package/dist/tools/report-session/script-tools.d.ts +30 -0
  190. package/dist/tools/report-session/script-tools.js +177 -0
  191. package/dist/tools/research/generate-analogy-report.d.ts +16 -9
  192. package/dist/tools/research/generate-analogy-report.js +111 -49
  193. package/dist/tools/research/generate-plan.d.ts +102 -7
  194. package/dist/tools/research/generate-plan.js +74 -43
  195. package/dist/tools/research/index.d.ts +4 -3
  196. package/dist/tools/research/index.js +4 -3
  197. package/dist/tools/research/inspect-run.d.ts +0 -1
  198. package/dist/tools/research/inspect-run.js +5 -10
  199. package/dist/tools/research/search-arxiv.js +15 -0
  200. package/dist/tools/research/search-github-repos.js +13 -0
  201. package/dist/tools/research/search-semantic-scholar.js +16 -0
  202. package/dist/tools/research/synthesis-lookups.d.ts +21 -0
  203. package/dist/tools/research/synthesis-lookups.js +29 -0
  204. package/dist/tools/research/web-fetch.d.ts +1 -1
  205. package/dist/tools/research/web-fetch.js +14 -12
  206. package/dist/tools/sandbox/lease-tools.d.ts +32 -0
  207. package/dist/tools/sandbox/lease-tools.js +60 -0
  208. package/dist/tools/script/codemode-worker.d.ts +5 -0
  209. package/dist/tools/script/codemode-worker.js +5 -0
  210. package/dist/tools/script/file-tools.d.ts +100 -0
  211. package/dist/tools/script/file-tools.js +377 -0
  212. package/dist/tools/script/metrics.d.ts +21 -0
  213. package/dist/tools/script/metrics.js +38 -0
  214. package/dist/tools/script/quickjs.wasm +0 -0
  215. package/dist/tools/script/run-script.d.ts +76 -0
  216. package/dist/tools/script/run-script.js +326 -0
  217. package/dist/tools/script/scratch.d.ts +81 -0
  218. package/dist/tools/script/scratch.js +142 -0
  219. package/dist/tools/start-report-session.js +1 -1
  220. package/dist/tools/workspace/edit-file.js +2 -1
  221. package/dist/tools/workspace/execute-command.d.ts +9 -13
  222. package/dist/tools/workspace/execute-command.js +23 -19
  223. package/dist/tools/workspace/file-stat.js +1 -1
  224. package/dist/tools/workspace/grep.d.ts +5 -2
  225. package/dist/tools/workspace/grep.js +19 -10
  226. package/dist/tools/workspace/mutator.d.ts +48 -4
  227. package/dist/tools/workspace/mutator.js +43 -12
  228. package/dist/tools/workspace/read-file.d.ts +5 -2
  229. package/dist/tools/workspace/read-file.js +15 -11
  230. package/dist/tools/workspace/read-surface.d.ts +17 -0
  231. package/dist/tools/workspace/read-surface.js +17 -0
  232. package/dist/tools/workspace/result-bounds.d.ts +1 -0
  233. package/dist/tools/workspace/run-exec.d.ts +12 -13
  234. package/dist/tools/workspace/run-exec.js +13 -13
  235. package/dist/tools/workspace/scan-inputs.d.ts +2 -5
  236. package/dist/tools/workspace/scan-inputs.js +2 -3
  237. package/dist/tools/workspace/show-file.js +1 -1
  238. package/dist/tools/workspace/show-plan.js +4 -3
  239. package/dist/tools/workspace/write-file.js +2 -1
  240. package/dist/workflows/execute-analysis-scheduler.d.ts +0 -26
  241. package/dist/workflows/execute-analysis-scheduler.js +12 -32
  242. package/dist/workflows/execute-analysis.d.ts +17 -35
  243. package/dist/workflows/execute-analysis.js +13 -107
  244. package/dist/workflows/pause.js +29 -22
  245. package/dist/workflows/sandbox-step.d.ts +16 -31
  246. package/dist/workflows/sandbox-step.js +36 -71
  247. package/dist/workflows/test/sql-writes.d.ts +0 -1
  248. package/dist/workflows/test/sql-writes.js +0 -2
  249. package/dist/workspace/filesystem.d.ts +2 -2
  250. package/dist/workspace/filesystem.js +10 -5
  251. package/dist/workspace/paths.d.ts +19 -1
  252. package/dist/workspace/paths.js +22 -1
  253. package/package.json +6 -3
  254. package/dist/prompts/literature-reviewer.d.ts +0 -1
  255. package/dist/prompts/literature-reviewer.js +0 -114
  256. package/dist/sandbox/reaper.d.ts +0 -81
  257. package/dist/sandbox/reaper.js +0 -161
  258. package/dist/sandbox/step-sandbox.d.ts +0 -47
  259. package/dist/sandbox/step-sandbox.js +0 -53
  260. package/dist/state/active-sandboxes.d.ts +0 -33
  261. package/dist/state/active-sandboxes.js +0 -65
  262. package/dist/tasks/extract-values-script.d.ts +0 -64
  263. package/dist/tasks/extract-values-script.js +0 -180
  264. package/dist/tasks/extract-values.d.ts +0 -77
  265. package/dist/tasks/extract-values.js +0 -146
  266. package/dist/tools/report-session/derive-table.d.ts +0 -164
  267. package/dist/tools/report-session/derive-table.js +0 -419
  268. package/dist/tools/research/literature-reviewer.d.ts +0 -42
  269. package/dist/tools/research/literature-reviewer.js +0 -107
  270. package/dist/workflows/metrics.d.ts +0 -23
  271. package/dist/workflows/metrics.js +0 -38
@@ -5,13 +5,13 @@
5
5
  * The realization holds three layers. Layer one is identity: membership through `snapshotEntry`, and a
6
6
  * streamed hash compare with `computeSha256File`. A hash mismatch fails before any parse. Layer two is
7
7
  * the host fast path: an in-process parse of a CSV, a TSV, a JSON, or a parquet file at or under the cap.
8
- * Layer three is the fall-through: an over-cap file or a host parse fault goes to the extraction arm. On any
9
- * doubt the read falls through, thus correctness never depends on the host parser. An extension that names
10
- * no tabular format refuses here, because the host decides the format for both arms.
8
+ * Layer three is the fall-through: an over-cap file or a host parse fault fails as `extraction-unavailable`.
9
+ * On any doubt the read falls through, thus the host parser never gives a partial or guessed answer. An
10
+ * extension that names no tabular format refuses the same way.
11
11
  *
12
12
  * A read of the bytes is the expensive part, thus an optional `ArtifactReadCache` outlives one resolver. It
13
- * holds the streamed hash and the extracted rows against a stat signature. Two tool calls of one analysis
14
- * share one cache, and each call still binds its own extraction arm with its own auth.
13
+ * holds the streamed hash and the parsed rows against a stat signature. Two tool calls of one analysis
14
+ * share one cache.
15
15
  *
16
16
  * The realization calls the shared assert functions in `assert-rules.ts`, and it mirrors the locator walk
17
17
  * and the reason choices of `fixture-resolver.ts`. Thus one semantics exists, and the fixture stays the
@@ -22,7 +22,6 @@ import { readFile, stat } from "node:fs/promises";
22
22
  import { extname } from "node:path";
23
23
  import { parquetReadObjects } from "hyparquet";
24
24
  import { z } from "zod";
25
- import { isWorkflowCancellation } from "../runtime/dbos.js";
26
25
  import { allWithConcurrency } from "../lib/async-utils.js";
27
26
  import { computeSha256File } from "../lib/fs-helpers.js";
28
27
  import { parseJsonAs } from "../lib/parse-json.js";
@@ -92,6 +91,10 @@ export function createArtifactReadStore() {
92
91
  },
93
92
  };
94
93
  }
94
+ /** The cells of a fall-through. No reader outside the host exists, thus the reference fails as `extraction-unavailable`. */
95
+ function fallThroughCells(path, hostDetail) {
96
+ return { kind: "unavailable", detail: `the host read of the file at ${path} stopped because ${hostDetail}, and no other reader exists` };
97
+ }
95
98
  /** Build an `Err` that carries the unresolved reference. The `detail` key is present only when there is a detail to carry. */
96
99
  function fail(reference, reason, detail) {
97
100
  const failure = detail !== undefined ? { reference, reason, detail } : { reference, reason };
@@ -104,10 +107,9 @@ function describeReference(reference) {
104
107
  /**
105
108
  * Map a file extension to the format that the host arm reads. An unknown extension gives back `undefined`.
106
109
  *
107
- * This is the one format decision of the report path. The extraction request carries the answer, thus the
108
- * sandbox arm never repeats the mapping and the two arms hold no second table between them.
110
+ * This is the one format decision of the report path.
109
111
  */
110
- function detectFormat(path) {
112
+ export function detectFormat(path) {
111
113
  switch (extname(path).toLowerCase()) {
112
114
  case ".csv":
113
115
  return "csv";
@@ -130,8 +132,7 @@ function detectFormat(path) {
130
132
  * where the caller detects the loss.
131
133
  *
132
134
  * A non-finite number is not a cell either. A parquet float column holds a `NaN` or an `Infinity`, thus the
133
- * coercion drops it and the row omits the key. The extraction script (`tasks/extract-values-script.ts`)
134
- * drops the same value to `None`, thus the host arm and the sandbox arm read the same bytes the same way.
135
+ * coercion drops it and the row omits the key.
135
136
  */
136
137
  export function coerceCell(value) {
137
138
  if (value === null || value === undefined) {
@@ -339,10 +340,7 @@ async function parseParquet(bytes) {
339
340
  }
340
341
  /**
341
342
  * Read the cells of one artifact through the host fast path. An over-cap file and a parse fault each give
342
- * back a fall-through, thus the arm reads them out of process.
343
- *
344
- * An extension that names no tabular format stops here. The arm reads a file by a decided format, and no
345
- * format is decided, thus the arm has nothing to obey and the read is unavailable.
343
+ * back a fall-through. An extension that names no tabular format stops here.
346
344
  */
347
345
  async function hostReadCells(cap, path, identity) {
348
346
  const format = detectFormat(path);
@@ -350,26 +348,33 @@ async function hostReadCells(cap, path, identity) {
350
348
  return { kind: "unsupported-format", detail: `the file at ${path} has no supported tabular format, thus no reader is decided for it` };
351
349
  }
352
350
  if (identity.size > cap) {
353
- return { kind: "fall-through", format, detail: `the file size of ${identity.size} bytes is over the ${cap}-byte cap` };
351
+ return { kind: "fall-through", detail: `the file size of ${identity.size} bytes is over the ${cap}-byte cap` };
354
352
  }
355
353
  let bytes;
356
354
  try {
357
355
  bytes = await readFile(identity.absolute);
358
356
  }
359
357
  catch {
360
- return { kind: "fall-through", format, detail: "the file could not be read" };
361
- }
362
- if (format === "parquet") {
363
- const rows = await parseParquet(bytes);
364
- return rows === undefined ? { kind: "fall-through", format, detail: "the parquet read did not succeed" } : { kind: "rows", rows };
358
+ return { kind: "fall-through", detail: "the file could not be read" };
365
359
  }
360
+ const rows = await parseTable(format, bytes);
361
+ return rows === undefined ? { kind: "fall-through", detail: PARSE_FAULT_DETAIL[format] } : { kind: "rows", rows };
362
+ }
363
+ /** The detail of a parse fault, for each format. */
364
+ const PARSE_FAULT_DETAIL = {
365
+ csv: "the delimited parse did not succeed",
366
+ tsv: "the delimited parse did not succeed",
367
+ json: "the JSON parse did not succeed",
368
+ parquet: "the parquet read did not succeed",
369
+ };
370
+ /** Parse the bytes of one table in a decided format. A parse fault gives back `undefined`. */
371
+ export async function parseTable(format, bytes) {
372
+ if (format === "parquet")
373
+ return parseParquet(bytes);
366
374
  const text = bytes.toString("utf8");
367
- if (format === "json") {
368
- const rows = parseJsonTable(text);
369
- return rows === undefined ? { kind: "fall-through", format, detail: "the JSON parse did not succeed" } : { kind: "rows", rows };
370
- }
371
- const rows = parseDelimited(text, format === "tsv" ? "\t" : ",");
372
- return rows === undefined ? { kind: "fall-through", format, detail: "the delimited parse did not succeed" } : { kind: "rows", rows };
375
+ if (format === "json")
376
+ return parseJsonTable(text);
377
+ return parseDelimited(text, format === "tsv" ? "\t" : ",");
373
378
  }
374
379
  /**
375
380
  * Compute the identity of one artifact: membership, containment, and the on-disk hash.
@@ -417,58 +422,6 @@ async function computeIdentity(workspaceRoot, analysisId, path, snapshot, readCa
417
422
  readCache?.set(resolved.absolute, { signature, hash: onDiskHash });
418
423
  return { kind: "ready", onDiskHash, fileType: entry.fileType, absolute: resolved.absolute, size, signature };
419
424
  }
420
- /**
421
- * Resolve each fall-through artifact through the extraction arm. One call covers the whole batch. An
422
- * absent arm gives back `unavailable`, and the detail names the absent arm. An arm that gives no answer
423
- * for a path gives back `unreadable`.
424
- */
425
- async function extractFallThrough(arm, requests) {
426
- const out = new Map();
427
- if (requests.length === 0) {
428
- return out;
429
- }
430
- if (arm === undefined) {
431
- for (const request of requests) {
432
- out.set(request.path, {
433
- kind: "unavailable",
434
- detail: `the extraction arm is not wired, and the file at ${request.path} needs it because ${request.hostDetail}`,
435
- });
436
- }
437
- return out;
438
- }
439
- let answered;
440
- try {
441
- answered = await arm.extract(requests.map((request) => ({ path: request.path, hash: request.hash, format: request.format })));
442
- }
443
- catch (cause) {
444
- // The arm speaks the throw protocol for a genuine infrastructure fault. This boundary turns the
445
- // throw into a value, thus each reference on the batch reads a failure as data. A cancel of the
446
- // workflow is not a fault of the read, thus it leaves to DBOS with no change.
447
- if (isWorkflowCancellation(cause))
448
- throw cause;
449
- for (const request of requests) {
450
- out.set(request.path, { kind: "unreadable", detail: `the extraction arm did not read the file at ${request.path}` });
451
- }
452
- return out;
453
- }
454
- if (answered.isErr()) {
455
- for (const request of requests) {
456
- out.set(request.path, {
457
- kind: "unavailable",
458
- detail: `the host refused the sandbox (${answered.error.reason}), thus the file at ${request.path} was not read`,
459
- });
460
- }
461
- return out;
462
- }
463
- const answers = answered.value;
464
- for (const request of requests) {
465
- const answer = answers.get(request.path);
466
- out.set(request.path, answer === undefined
467
- ? { kind: "unreadable", detail: `the extraction arm did not read the file at ${request.path}` }
468
- : { kind: "rows", rows: answer.rows });
469
- }
470
- return out;
471
- }
472
425
  /**
473
426
  * Give back the rows that an earlier pass read for these exact bytes, or `undefined` when nothing shared
474
427
  * them. The signature compare is the whole guard: a file that changed on disk holds a different signature,
@@ -479,8 +432,8 @@ function sharedRows(readCache, identity) {
479
432
  return held !== undefined && held.signature === identity.signature && held.rows !== undefined ? held.rows : undefined;
480
433
  }
481
434
  /**
482
- * Share the rows of one clean read, thus the next pass over the same bytes starts no second container and
483
- * parses nothing again. A failed read is transient, thus only rows enter the cache.
435
+ * Share the rows of one clean read, thus the next pass over the same bytes parses nothing again. A failed
436
+ * read is transient, thus only rows enter the cache.
484
437
  */
485
438
  function shareRows(readCache, identity, rows) {
486
439
  readCache?.set(identity.absolute, { signature: identity.signature, hash: identity.onDiskHash, rows });
@@ -500,7 +453,7 @@ function accessesOf(reference) {
500
453
  }
501
454
  }
502
455
  /**
503
- * Make the production resolver over the workspace root and the optional extraction arm.
456
+ * Make the production resolver over the workspace root.
504
457
  *
505
458
  * The resolver holds a per-pass cache. `prepare` groups the references by artifact, reads each file one
506
459
  * time, and fills the cache. `resolve` then answers from the cache. A `resolve` with no prior `prepare`
@@ -508,7 +461,7 @@ function accessesOf(reference) {
508
461
  * artifact on both paths, and never one read for each reference.
509
462
  */
510
463
  export function createProductionResolver(deps) {
511
- const { workspaceRoot, analysisId, extractionArm, readCache } = deps;
464
+ const { workspaceRoot, analysisId, readCache } = deps;
512
465
  const cap = deps.cap ?? DEFAULT_CAP_BYTES;
513
466
  // The per-pass caches. `prepare` clears and refills them, thus a later pass reads the artifacts again.
514
467
  const idCache = new Map();
@@ -528,8 +481,8 @@ export function createProductionResolver(deps) {
528
481
  }
529
482
  /**
530
483
  * Give back the cells of one path, from the cache after a prepare, or by a fresh read. A fresh read
531
- * runs the host fast path, and it resolves a fall-through through the arm as a batch of one. The
532
- * outcome enters the cache, thus a second reference at one path opens no second batch.
484
+ * runs the host fast path. The outcome enters the cache, thus a second reference at one path reads
485
+ * nothing again.
533
486
  */
534
487
  async function getCells(path, identity) {
535
488
  const cached = cellsCache.get(path);
@@ -549,18 +502,9 @@ export function createProductionResolver(deps) {
549
502
  cellsCache.set(path, parsed);
550
503
  return parsed;
551
504
  }
552
- if (read.kind === "unsupported-format") {
553
- const unavailable = { kind: "unavailable", detail: read.detail };
554
- cellsCache.set(path, unavailable);
555
- return unavailable;
556
- }
557
- const resolved = await extractFallThrough(extractionArm, [{ path, hash: identity.onDiskHash, format: read.format, hostDetail: read.detail }]);
558
- const cells = resolved.get(path) ?? { kind: "unreadable", detail: `the extraction arm did not read the file at ${path}` };
559
- if (cells.kind === "rows") {
560
- shareRows(readCache, identity, cells.rows);
561
- }
562
- cellsCache.set(path, cells);
563
- return cells;
505
+ const unavailable = read.kind === "unsupported-format" ? { kind: "unavailable", detail: read.detail } : fallThroughCells(path, read.detail);
506
+ cellsCache.set(path, unavailable);
507
+ return unavailable;
564
508
  }
565
509
  /**
566
510
  * The identity gate that a cell-reading reference passes before a read of cells.
@@ -788,7 +732,7 @@ export function createProductionResolver(deps) {
788
732
  }
789
733
  }
790
734
  // An earlier pass over these exact bytes already paid for the rows. Take them, thus the second pass
791
- // of one analysis parses nothing again and starts no second container.
735
+ // of one analysis parses nothing again.
792
736
  const pending = [];
793
737
  for (const target of cellTargets) {
794
738
  const shared = sharedRows(readCache, target.identity);
@@ -800,10 +744,6 @@ export function createProductionResolver(deps) {
800
744
  }
801
745
  }
802
746
  const reads = await allWithConcurrency(pending.map((target) => async () => ({ target, read: await hostReadCells(cap, target.path, target.identity) })), PREPARE_CONCURRENCY);
803
- // Split the clean reads from the fall-throughs, then resolve every fall-through in one arm call. An
804
- // unsupported format reaches no arm, because no reader is decided for it.
805
- const fallThrough = [];
806
- const identityByPath = new Map();
807
747
  for (const { target, read } of reads) {
808
748
  if (read.kind === "rows") {
809
749
  cellsCache.set(target.path, { kind: "rows", rows: read.rows });
@@ -813,16 +753,7 @@ export function createProductionResolver(deps) {
813
753
  cellsCache.set(target.path, { kind: "unavailable", detail: read.detail });
814
754
  }
815
755
  else {
816
- identityByPath.set(target.path, target.identity);
817
- fallThrough.push({ path: target.path, hash: target.identity.onDiskHash, format: read.format, hostDetail: read.detail });
818
- }
819
- }
820
- const resolved = await extractFallThrough(extractionArm, fallThrough);
821
- for (const [path, cells] of resolved) {
822
- cellsCache.set(path, cells);
823
- const identity = identityByPath.get(path);
824
- if (cells.kind === "rows" && identity !== undefined) {
825
- shareRows(readCache, identity, cells.rows);
756
+ cellsCache.set(target.path, fallThroughCells(target.path, read.detail));
826
757
  }
827
758
  }
828
759
  }
@@ -46,6 +46,15 @@ export declare function rendererApi(api: Echarts.CustomSeriesRenderItemAPI): Ren
46
46
  * behind.
47
47
  */
48
48
  export declare function renderChartSvg(echarts: ChartRuntime, option: EchartOption, size: ChartExportSize): Result<string, string>;
49
+ /**
50
+ * The count of coordinates that one option draws, summed over its series.
51
+ *
52
+ * The count bounds the export. A cloud of many thousands of points gives an SVG of many megabytes, two times,
53
+ * thus the page builds the SVG of such a chart on a click, with the points as one raster layer. An empty slot draws nothing, thus it counts nothing. A radar
54
+ * polygon draws one coordinate for each indicator, and a violin outline draws two vertices for each grid
55
+ * point, thus each counts every coordinate that it draws.
56
+ */
57
+ export declare function plottedPoints(option: EchartOption): number;
49
58
  /**
50
59
  * The two SVG files of one chart, or `undefined` for a chart past the export bound.
51
60
  *
@@ -138,7 +138,7 @@ function finishedSvg(svg, size) {
138
138
  * polygon draws one coordinate for each indicator, and a violin outline draws two vertices for each grid
139
139
  * point, thus each counts every coordinate that it draws.
140
140
  */
141
- function plottedPoints(option) {
141
+ export function plottedPoints(option) {
142
142
  const series = option.series;
143
143
  if (!Array.isArray(series))
144
144
  return 0;
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The chart card markup.
3
4
  *
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /** @jsxImportSource hono/jsx */
2
3
  /**
3
4
  * The chart card markup.
4
5
  *
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The lineage keys of a grounded block, and the control that opens the chain of one key.
3
4
  *
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The page assembly: the hero, the bands, the appendix band, and the footer.
3
4
  *
@@ -1,4 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /** @jsxImportSource hono/jsx */
2
3
  /**
3
4
  * The page assembly: the hero, the bands, the appendix band, and the footer.
4
5
  *
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The prose markup: the text block, the claim block, the section wrapper, and the page navigation.
3
4
  *
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /** @jsxImportSource hono/jsx */
2
3
  /**
3
4
  * The prose markup: the text block, the claim block, the section wrapper, and the page navigation.
4
5
  *
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The reference markup: the one evidence marker, and the one appendix list under it.
3
4
  *
@@ -1,3 +1,4 @@
1
+ /** @jsxImportSource hono/jsx */
1
2
  /**
2
3
  * The value markup: the metric card, the data table, the figure, and the citation card.
3
4
  *
@@ -1,4 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "hono/jsx/jsx-runtime";
2
+ /** @jsxImportSource hono/jsx */
2
3
  /**
3
4
  * The value markup: the metric card, the data table, the figure, and the citation card.
4
5
  *
@@ -19,6 +19,7 @@ import { type ConversationAgentDeps } from "../agents/conversation-agent.js";
19
19
  import { type ApprovePlanInput, type ApprovePlanRefusal, type PlanApproval } from "../app/approve-plan.js";
20
20
  import { type ChatTurns } from "../app/chat-turn-workflow.js";
21
21
  import { type WorkSurface } from "../app/work.js";
22
+ import { type RunCanceler } from "../execution/run-canceler.js";
22
23
  import { type ReportSessionRuntime } from "../app/report-session-runtime.js";
23
24
  import type { UsageRecorder } from "../billing/usage-recorder.js";
24
25
  import type { ResourcePolicy } from "../config/resource-limits.js";
@@ -27,6 +28,7 @@ import { type ChromeConfig } from "../lib/chrome.js";
27
28
  import { type AcquireEyes } from "../lib/eyes.js";
28
29
  import type { ThreadType } from "../memory/thread-store.js";
29
30
  import { type ExecuteAnalysisDeps, type ExecuteAnalysisInput, type ExecuteAnalysisResult } from "../workflows/execute-analysis.js";
31
+ import type { SandboxClient } from "../sandbox/client.js";
30
32
  import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } from "../workflows/sandbox-step.js";
31
33
  import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
32
34
  import { type CitationResolverConfig } from "../citations/resolve.js";
@@ -56,12 +58,19 @@ export type SandboxStepCallable = (input: SandboxStepInput) => Promise<SandboxSt
56
58
  * the same memory that the conversation agent writes.
57
59
  */
58
60
  export type CoreWorkflowDeps = {
59
- readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver" | "toolOutputStore" | "runAuthorizer">;
60
- readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "literatureReviewer" | "toolOutputStore" | "workingMemory"> & {
61
- /** API keys for the bio/chem tools of the literature reviewer of the synthesis. */
61
+ readonly sandboxStep: Omit<SandboxStepDeps, "usageRecorder" | "citationResolver" | "toolOutputStore" | "runAuthorizer" | "leases">;
62
+ readonly buildExecuteAnalysis: (sandboxStep: SandboxStepCallable) => Omit<ExecuteAnalysisDeps, "usageRecorder" | "synthesisLookups" | "toolOutputStore" | "workingMemory" | "workspaceFs"> & {
63
+ /** API keys for the bio/chem tools of the lookups of the synthesis. */
62
64
  readonly bioKeys: BioToolKeys;
63
65
  };
64
- readonly dataProfile: Omit<DataProfileDeps, "usageRecorder" | "toolOutputStore">;
66
+ readonly dataProfile: Omit<DataProfileDeps, "usageRecorder" | "toolOutputStore" | "leases">;
67
+ };
68
+ /** The sandboxes of the runtime. The assembly builds the one sandbox module over the client. */
69
+ export type CoreSandboxDeps = {
70
+ /** The backend client that the embedder made. Only the sandbox module calls it. */
71
+ readonly client: SandboxClient;
72
+ /** How long a lease can go unused before it loses its machine. Absent: 15 minutes. */
73
+ readonly idlePeriodMs?: number;
65
74
  };
66
75
  /** The registered, callable workflow handles. */
67
76
  export type RegisteredWorkflows = {
@@ -86,7 +95,7 @@ export type RegisteredWorkflows = {
86
95
  * chrome config. A caller that bound the eyes here would give the conversation
87
96
  * agent a seam that the report agent never sees.
88
97
  */
89
- export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "resourcePolicy" | "usageRecorder" | "citationResolver" | "anchorReportSession" | "eyes" | "toolOutputStore" | "registerSubAgent">;
98
+ export type ConversationAssemblyDeps = Omit<ConversationAgentDeps, "executeAnalysisWorkflow" | "usageRecorder" | "citationResolver" | "anchorReportSession" | "eyes" | "toolOutputStore" | "registerSubAgent">;
90
99
  /**
91
100
  * What binds one reference resolver: the analysis whose pinned artifacts it reads, and the identity and the
92
101
  * auth of the tool call that reads them. A resolver serves one analysis, thus the report tools build one for
@@ -105,9 +114,9 @@ export type MakeReportReferenceResolver = (scope: ReportResolverScope) => Promis
105
114
  export type CoreRuntimeDeps = {
106
115
  readonly conversation: ConversationAssemblyDeps;
107
116
  readonly workflows: CoreWorkflowDeps;
117
+ readonly sandbox: CoreSandboxDeps;
108
118
  /**
109
- * Host resource policy — per-step ceilings and machine budget. One supply
110
- * point for planner/routing guidance and workflow-input budget snapshots.
119
+ * Host resource policy — the per-step ceilings and the budget of the sandbox lease grant.
111
120
  */
112
121
  readonly resourcePolicy?: ResourcePolicy;
113
122
  /**
@@ -121,7 +130,7 @@ export type CoreRuntimeDeps = {
121
130
  readonly citationResolverConfig?: CitationResolverConfig;
122
131
  /**
123
132
  * The byte cap on the in-process host read of a report reference. A file over the
124
- * cap falls through to the extraction arm. Absent, the resolver uses its 16 MiB
133
+ * cap falls through, and its reference fails as `extraction-unavailable`. Absent, the resolver uses its 16 MiB
125
134
  * default. The embedder tunes it here, thus a host changes it with no harness change.
126
135
  */
127
136
  readonly reportHostReadCapBytes?: number;
@@ -129,7 +138,7 @@ export type CoreRuntimeDeps = {
129
138
  * The report reference resolver of an embedder. A managed host reads its pinned
130
139
  * artifacts from its own store, thus it binds a realization of the seam here and the
131
140
  * report tools resolve through it. Absent, the harness wires the production resolver
132
- * over the workspace tree and the extraction arm, which is the whole OSS path. A bound
141
+ * over the workspace tree, which is the whole OSS path. A bound
133
142
  * factory replaces that wiring, thus `reportHostReadCapBytes` then governs nothing.
134
143
  */
135
144
  readonly makeReportReferenceResolver?: MakeReportReferenceResolver;
@@ -263,6 +272,8 @@ export type CoreRuntime = {
263
272
  * ends one work in each state, and `resume` and `hold` reach the waits of one work. Only a client resumes paused work.
264
273
  */
265
274
  readonly work: WorkSurface;
275
+ /** The run canceler of `work`. It also ends the sandbox leases of the canceled workflows at once. */
276
+ readonly runCanceler: RunCanceler;
266
277
  /**
267
278
  * Approve a stored plan by its id and start its run with no model call. It runs over the deps that
268
279
  * reach the `execute_analysis` tool of the conversation agent, thus the approval and the tool launch
@@ -28,9 +28,11 @@ import { workPause } from "../workflows/pause.js";
28
28
  import { hasBrowserUrl } from "../lib/chrome.js";
29
29
  import { createStaticEyes } from "../lib/eyes.js";
30
30
  import { registerExecuteAnalysis } from "../workflows/execute-analysis.js";
31
+ import { createSandboxLeases } from "../sandbox/leases.js";
32
+ import { createNoopLogger } from "../lib/console-logger.js";
33
+ import { dbosClientFor } from "../execution/dbos-workflow-purger.js";
31
34
  import { registerSandboxStep } from "../workflows/sandbox-step.js";
32
35
  import { registerDataProfileWorkflow, stopDataProfile } from "../tasks/data-profile.js";
33
- import { createExtractionArm, runExtraction } from "../tasks/extract-values.js";
34
36
  import { createCitationResolver } from "../citations/resolve.js";
35
37
  import { createThreadStore } from "../memory/thread-store.js";
36
38
  import { createWorkingMemory } from "../memory/working-memory.js";
@@ -38,7 +40,7 @@ import { createArtifactReadStore, createProductionResolver } from "../report-mod
38
40
  import { createReportVersionStore } from "../state/report-versions.js";
39
41
  import { createToolOutputStore } from "../state/tool-outputs.js";
40
42
  import { createAskGateway } from "../tools/approval/gateway.js";
41
- import { createLiteratureReviewerTool } from "../tools/research/literature-reviewer.js";
43
+ import { createSynthesisLookupTools } from "../tools/research/synthesis-lookups.js";
42
44
  const _assertDomainError = true;
43
45
  /**
44
46
  * Wrap a type→agent registry as the resolution surface. Held apart from
@@ -83,7 +85,7 @@ export function resolveCompositionEyes(seam, chrome) {
83
85
  return undefined;
84
86
  }
85
87
  export function assembleCoreRuntime(deps) {
86
- const { conversation, workflows: wf, resourcePolicy } = deps;
88
+ const { conversation, workflows: wf } = deps;
87
89
  const usageRecorder = deps.usageRecorder ?? createNoopUsageRecorder();
88
90
  const citationResolver = createCitationResolver({
89
91
  ...deps.citationResolverConfig,
@@ -102,8 +104,21 @@ export function assembleCoreRuntime(deps) {
102
104
  logger: conversation.logger,
103
105
  }).afterFailure;
104
106
  const registerPausingSubAgent = (name, body) => registerSubAgent(name, body, subAgentPauses);
107
+ // One sandbox module for each runtime: one budget, one lease workflow, and one reconciler.
108
+ const leases = createSandboxLeases({
109
+ pool: conversation.pool,
110
+ client: deps.sandbox.client,
111
+ resourcePolicy: deps.resourcePolicy,
112
+ resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
113
+ runAuthorizer: conversation.runAuthorizer,
114
+ idlePeriodMs: deps.sandbox.idlePeriodMs,
115
+ pauseRetryDelaysMs,
116
+ logger: conversation.logger,
117
+ dbosClient: () => dbosClientFor(conversation.pool, conversation.logger ?? createNoopLogger()),
118
+ });
105
119
  const sandboxStep = registerSandboxStep({
106
120
  ...wf.sandboxStep,
121
+ leases,
107
122
  citationResolver,
108
123
  usageRecorder,
109
124
  toolOutputStore,
@@ -111,24 +126,18 @@ export function assembleCoreRuntime(deps) {
111
126
  runAuthorizer: conversation.runAuthorizer,
112
127
  });
113
128
  const { bioKeys: synthesisBioKeys, ...executeAnalysisDeps } = wf.buildExecuteAnalysis(sandboxStep);
114
- const literatureReviewer = createLiteratureReviewerTool({
115
- provider: executeAnalysisDeps.provider,
116
- model: executeAnalysisDeps.synthesisModel,
117
- bioKeys: synthesisBioKeys,
118
- citationResolver,
119
- usageRecorder,
120
- toolOutputStore,
121
- logger: executeAnalysisDeps.logger,
122
- }, registerPausingSubAgent);
129
+ const synthesisLookups = createSynthesisLookupTools({ bioKeys: synthesisBioKeys, citationResolver, logger: executeAnalysisDeps.logger });
123
130
  const executeAnalysis = registerExecuteAnalysis({
124
131
  ...executeAnalysisDeps,
125
- literatureReviewer,
132
+ synthesisLookups,
133
+ scriptRuntime: conversation.scriptRuntime,
134
+ workspaceFs: conversation.workspaceFs,
126
135
  usageRecorder,
127
136
  toolOutputStore,
128
137
  workingMemory: createWorkingMemory(conversation.pool),
129
138
  pauseRetryDelaysMs,
130
139
  });
131
- const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, usageRecorder, toolOutputStore, pauseRetryDelaysMs });
140
+ const dataProfile = registerDataProfileWorkflow({ ...wf.dataProfile, leases, usageRecorder, toolOutputStore, pauseRetryDelaysMs });
132
141
  // The session runtime binds the per-session state to the thread behind the
133
142
  // tool boundary, thus one instance serves every report thread. It comes before
134
143
  // the conversation agent, because `start_report_session` takes its anchor
@@ -147,7 +156,6 @@ export function assembleCoreRuntime(deps) {
147
156
  ...conversation,
148
157
  executeAnalysisWorkflow: executeAnalysis,
149
158
  anchorReportSession: reportSession.ensureSessionState.bind(reportSession),
150
- resourcePolicy,
151
159
  usageRecorder,
152
160
  toolOutputStore,
153
161
  citationResolver,
@@ -164,21 +172,16 @@ export function assembleCoreRuntime(deps) {
164
172
  const reportThreads = createThreadStore(conversation.pool);
165
173
  // The read cache of the production report path, one for each recent analysis. A preview
166
174
  // and the record that follows it are two tool calls over one draft, thus they meet the
167
- // same artifacts. The store carries the streamed hash and the extracted rows between
168
- // them, and each resolver still binds its own extraction arm below. Thus the auth of a
169
- // call never outlives the call, and one unchanged file costs one hash and one container.
175
+ // same artifacts. The store carries the streamed hash and the parsed rows between them,
176
+ // thus one unchanged file costs one hash and one parse.
170
177
  const reportArtifactReads = createArtifactReadStore();
171
178
  // The reference resolver of the report path, and its production wiring. A resolver reads
172
179
  // the pinned artifacts of one analysis, thus the factory binds one analysis and one caller
173
- // for each tool call. The extraction arm falls through to an extraction pass on the sandbox
174
- // rail of the profile for an over-cap file or a host parse fault. The pass runs inside the
175
- // step of the tool call. The cap gates the host read, and the resolver defaults it to 16 MiB.
180
+ // for each tool call. The cap gates the host read, and the resolver defaults it to 16 MiB.
176
181
  const makeProductionReportResolver = async (scope) => {
177
- const arm = createExtractionArm((requests) => runExtraction({ sandboxClient: wf.dataProfile.sandboxClient, logger: wf.dataProfile.logger }, scope, requests));
178
182
  return createProductionResolver({
179
183
  workspaceRoot: await conversation.resolveWorkspaceRoot(scope.analysisId),
180
184
  analysisId: scope.analysisId,
181
- extractionArm: arm,
182
185
  readCache: reportArtifactReads.forAnalysis(scope.analysisId),
183
186
  cap: deps.reportHostReadCapBytes,
184
187
  });
@@ -192,13 +195,9 @@ export function assembleCoreRuntime(deps) {
192
195
  gateway: reportSession.gateway,
193
196
  resolveWorkspaceRoot: conversation.resolveWorkspaceRoot,
194
197
  store: reportVersionStore,
198
+ derivations: reportSession.derivations,
195
199
  threads: reportThreads,
196
200
  chrome: conversation.chrome,
197
- // The derivation tool draws the rail of the value tier: the same sandbox client. Thus a
198
- // session derivation and an extraction run on one substrate, and neither one mints a run
199
- // of the analysis.
200
- derivations: reportSession.derivations,
201
- sandboxClient: wf.dataProfile.sandboxClient,
202
201
  makeResolver: makeReportResolver,
203
202
  eyes,
204
203
  resolvePageAsset: deps.resolveReportPageAsset,
@@ -207,6 +206,7 @@ export function assembleCoreRuntime(deps) {
207
206
  provenance: deps.provenance,
208
207
  logger: conversation.logger,
209
208
  toolOutputStore,
209
+ scriptRuntime: conversation.scriptRuntime,
210
210
  });
211
211
  // The single registration point for every typed agent. A future thread type's
212
212
  // agent plugs in here at assembly with no embedder change. `conversation` is
@@ -232,8 +232,18 @@ export function assembleCoreRuntime(deps) {
232
232
  credentialFor: deps.credentialFor,
233
233
  pauseRetryDelaysMs,
234
234
  });
235
+ const runCanceler = createRunCanceler({
236
+ pool: conversation.pool,
237
+ runCharge: executeAnalysisDeps.runCharge,
238
+ runAuthorizer: conversation.runAuthorizer,
239
+ // The end of a canceled run goes where the run engine emits the rest of the run.
240
+ provenance: executeAnalysisDeps.provenance,
241
+ logger: conversation.logger,
242
+ releaseLeasesOf: (workflowIds) => leases.releaseOwnedBy(workflowIds),
243
+ });
235
244
  return {
236
245
  agents: agentResolver,
246
+ runCanceler,
237
247
  askGateway,
238
248
  chatTurns,
239
249
  work: createWork({
@@ -241,15 +251,15 @@ export function assembleCoreRuntime(deps) {
241
251
  runAuthorizer: conversation.runAuthorizer,
242
252
  logger: conversation.logger,
243
253
  stopTurn: (turnId) => chatTurns.stop(turnId),
244
- runCanceler: createRunCanceler({
245
- pool: conversation.pool,
246
- runCharge: executeAnalysisDeps.runCharge,
247
- runAuthorizer: conversation.runAuthorizer,
248
- // The end of a canceled run goes where the run engine emits the rest of the run.
249
- provenance: executeAnalysisDeps.provenance,
250
- logger: conversation.logger,
251
- }),
252
- stopProfile: (analysisId, workflowId) => stopDataProfile({ pool: conversation.pool, logger: conversation.logger, notifyDataProfileSettled: wf.dataProfile.notifyDataProfileSettled }, analysisId, workflowId),
254
+ runCanceler,
255
+ stopProfile: async (analysisId, workflowId) => {
256
+ await stopDataProfile({ pool: conversation.pool, logger: conversation.logger, notifyDataProfileSettled: wf.dataProfile.notifyDataProfileSettled }, analysisId, workflowId);
257
+ // A canceled body runs no teardown, thus the lease of the profile ends here and not at its owner check.
258
+ await leases.releaseOwnedBy([workflowId]).catch((failure) => {
259
+ const logger = conversation.logger ?? createNoopLogger();
260
+ logger.error("the sandbox lease of a stopped profile did not end", { analysisId, ...logger.errorFields(failure) });
261
+ });
262
+ },
253
263
  }),
254
264
  reportSession: { ensureSessionState: reportSession.ensureSessionState.bind(reportSession) },
255
265
  workflows: {
@@ -263,7 +273,6 @@ export function assembleCoreRuntime(deps) {
263
273
  executeAnalysisWorkflow: executeAnalysis,
264
274
  runAuthorizer: conversation.runAuthorizer,
265
275
  runLauncher: conversation.runLauncher,
266
- resourcePolicy,
267
276
  extendAnalysisFarm: conversation.extendAnalysisFarm,
268
277
  logger: conversation.logger,
269
278
  provenance: deps.provenance,