@inflexa-ai/harness 0.1.1 → 0.3.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 (330) hide show
  1. package/README.md +48 -0
  2. package/dist/agents/conversation-agent.d.ts +15 -11
  3. package/dist/agents/conversation-agent.js +31 -34
  4. package/dist/agents/report-builder.js +2 -1
  5. package/dist/agents/sandbox/bulk-transcriptomics-agent.js +1 -1
  6. package/dist/agents/sandbox/cheminformatics-agent.js +1 -19
  7. package/dist/agents/sandbox/chromatin-agent.js +1 -10
  8. package/dist/agents/sandbox/dna-methylation-agent.js +1 -10
  9. package/dist/agents/sandbox/drug-repurposing-agent.js +4 -14
  10. package/dist/agents/sandbox/enrichment-agent.js +1 -10
  11. package/dist/agents/sandbox/ephemeral-executor.js +1 -10
  12. package/dist/agents/sandbox/genomic-variant-agent.js +1 -3
  13. package/dist/agents/sandbox/immune-profiling-agent.js +1 -13
  14. package/dist/agents/sandbox/metabolomics-agent.js +1 -1
  15. package/dist/agents/sandbox/microbiome-agent.js +1 -1
  16. package/dist/agents/sandbox/multi-omics-integration-agent.js +1 -12
  17. package/dist/agents/sandbox/multimodal-sc-agent.js +1 -1
  18. package/dist/agents/sandbox/network-agent.js +1 -10
  19. package/dist/agents/sandbox/pkpd-clinical-response-agent.js +2 -8
  20. package/dist/agents/sandbox/proteomics-agent.js +1 -1
  21. package/dist/agents/sandbox/scientific-executor.js +1 -10
  22. package/dist/agents/sandbox/shared.d.ts +20 -6
  23. package/dist/agents/sandbox/shared.js +50 -39
  24. package/dist/agents/sandbox/single-cell-agent.js +1 -1
  25. package/dist/agents/sandbox/spatial-omics-agent.js +1 -1
  26. package/dist/agents/sandbox/statistical-modeling-agent.js +1 -1
  27. package/dist/agents/sandbox/translational-safety-agent.js +3 -9
  28. package/dist/agents/sandbox/types.d.ts +1 -1
  29. package/dist/agents/system-prompt.d.ts +37 -11
  30. package/dist/agents/system-prompt.js +35 -12
  31. package/dist/app/chat-turn.d.ts +3 -0
  32. package/dist/app/chat-turn.js +3 -1
  33. package/dist/app/data-profile-orientation.d.ts +34 -0
  34. package/dist/app/data-profile-orientation.js +117 -0
  35. package/dist/app/synthesize-run.d.ts +3 -0
  36. package/dist/app/synthesize-run.js +3 -1
  37. package/dist/contracts/schemas/chat-parts.d.ts +9 -9
  38. package/dist/execution/artifact-metadata.d.ts +3 -0
  39. package/dist/execution/artifact-metadata.js +10 -3
  40. package/dist/execution/artifact-registration.d.ts +2 -1
  41. package/dist/execution/artifact-registration.js +8 -2
  42. package/dist/execution/ephemeral-runner.d.ts +16 -0
  43. package/dist/execution/ephemeral-runner.js +31 -5
  44. package/dist/execution/post-step-pipeline.d.ts +3 -0
  45. package/dist/execution/post-step-pipeline.js +24 -4
  46. package/dist/execution/reconcile-manifest.d.ts +7 -0
  47. package/dist/execution/reconcile-manifest.js +19 -5
  48. package/dist/execution/report-runner.d.ts +3 -0
  49. package/dist/execution/report-runner.js +4 -4
  50. package/dist/execution/run-synthesis.js +20 -19
  51. package/dist/execution/step-summary.d.ts +3 -0
  52. package/dist/execution/step-summary.js +6 -3
  53. package/dist/index.d.ts +9 -2
  54. package/dist/index.js +24 -5
  55. package/dist/lib/chrome.d.ts +4 -1
  56. package/dist/lib/chrome.js +10 -7
  57. package/dist/lib/console-logger.d.ts +19 -0
  58. package/dist/lib/console-logger.js +62 -0
  59. package/dist/lib/logger.d.ts +88 -0
  60. package/dist/lib/logger.js +37 -0
  61. package/dist/lib/otel.d.ts +2 -1
  62. package/dist/lib/otel.js +8 -3
  63. package/dist/loop/__fixtures__/scripted-provider.d.ts +2 -2
  64. package/dist/loop/__fixtures__/scripted-provider.js +2 -1
  65. package/dist/loop/metrics.d.ts +33 -1
  66. package/dist/loop/metrics.js +56 -2
  67. package/dist/loop/run-agent.d.ts +13 -1
  68. package/dist/loop/run-agent.js +19 -4
  69. package/dist/memory/card-builders.d.ts +8 -1
  70. package/dist/memory/card-builders.js +20 -3
  71. package/dist/memory/reconstruct-cards.js +4 -3
  72. package/dist/memory/thread-history.d.ts +27 -2
  73. package/dist/memory/thread-history.js +45 -5
  74. package/dist/memory/working-memory.d.ts +93 -10
  75. package/dist/memory/working-memory.js +220 -84
  76. package/dist/prompts/SOUL.d.ts +22 -9
  77. package/dist/prompts/SOUL.js +56 -37
  78. package/dist/prompts/analogical-reasoner.d.ts +1 -1
  79. package/dist/prompts/analogical-reasoner.js +2 -2
  80. package/dist/prompts/briefing.d.ts +100 -0
  81. package/dist/prompts/briefing.js +166 -0
  82. package/dist/prompts/conversation.d.ts +1 -1
  83. package/dist/prompts/conversation.js +94 -440
  84. package/dist/prompts/literature-reviewer.d.ts +1 -1
  85. package/dist/prompts/literature-reviewer.js +10 -10
  86. package/dist/prompts/planner.js +28 -59
  87. package/dist/prompts/report-builder.d.ts +1 -1
  88. package/dist/prompts/report-builder.js +71 -24
  89. package/dist/prompts/sandbox/bulk-transcriptomics-agent.d.ts +1 -1
  90. package/dist/prompts/sandbox/bulk-transcriptomics-agent.js +2 -7
  91. package/dist/prompts/sandbox/cheminformatics-agent.d.ts +1 -1
  92. package/dist/prompts/sandbox/cheminformatics-agent.js +11 -15
  93. package/dist/prompts/sandbox/chromatin-agent.d.ts +1 -1
  94. package/dist/prompts/sandbox/chromatin-agent.js +3 -7
  95. package/dist/prompts/sandbox/data-profiler.d.ts +1 -1
  96. package/dist/prompts/sandbox/data-profiler.js +13 -8
  97. package/dist/prompts/sandbox/dna-methylation-agent.d.ts +1 -1
  98. package/dist/prompts/sandbox/dna-methylation-agent.js +2 -7
  99. package/dist/prompts/sandbox/drug-repurposing-agent.d.ts +1 -1
  100. package/dist/prompts/sandbox/drug-repurposing-agent.js +15 -16
  101. package/dist/prompts/sandbox/enrichment-agent.d.ts +1 -1
  102. package/dist/prompts/sandbox/enrichment-agent.js +2 -9
  103. package/dist/prompts/sandbox/genomic-variant-agent.d.ts +1 -1
  104. package/dist/prompts/sandbox/genomic-variant-agent.js +2 -6
  105. package/dist/prompts/sandbox/immune-profiling-agent.d.ts +1 -1
  106. package/dist/prompts/sandbox/immune-profiling-agent.js +3 -8
  107. package/dist/prompts/sandbox/metabolomics-agent.d.ts +1 -1
  108. package/dist/prompts/sandbox/metabolomics-agent.js +1 -7
  109. package/dist/prompts/sandbox/microbiome-agent.d.ts +1 -1
  110. package/dist/prompts/sandbox/microbiome-agent.js +2 -7
  111. package/dist/prompts/sandbox/multi-omics-integration-agent.d.ts +1 -1
  112. package/dist/prompts/sandbox/multi-omics-integration-agent.js +2 -8
  113. package/dist/prompts/sandbox/multimodal-sc-agent.d.ts +1 -1
  114. package/dist/prompts/sandbox/multimodal-sc-agent.js +2 -7
  115. package/dist/prompts/sandbox/network-agent.d.ts +1 -1
  116. package/dist/prompts/sandbox/network-agent.js +2 -7
  117. package/dist/prompts/sandbox/proteomics-agent.d.ts +1 -1
  118. package/dist/prompts/sandbox/proteomics-agent.js +2 -9
  119. package/dist/prompts/sandbox/single-cell-agent.d.ts +1 -1
  120. package/dist/prompts/sandbox/single-cell-agent.js +2 -9
  121. package/dist/prompts/sandbox/spatial-omics-agent.d.ts +1 -1
  122. package/dist/prompts/sandbox/spatial-omics-agent.js +3 -6
  123. package/dist/prompts/sandbox/statistical-modeling-agent.d.ts +1 -1
  124. package/dist/prompts/sandbox/statistical-modeling-agent.js +2 -7
  125. package/dist/prompts/sandbox/translational-safety-agent.d.ts +1 -1
  126. package/dist/prompts/sandbox/translational-safety-agent.js +13 -14
  127. package/dist/prompts/sandbox-standards.d.ts +22 -14
  128. package/dist/prompts/sandbox-standards.js +91 -71
  129. package/dist/prompts/synthesis-agent.d.ts +1 -1
  130. package/dist/prompts/synthesis-agent.js +33 -131
  131. package/dist/provenance/collector.js +1 -1
  132. package/dist/providers/ai-sdk.js +31 -4
  133. package/dist/providers/prompt-cache.d.ts +74 -0
  134. package/dist/providers/prompt-cache.js +77 -0
  135. package/dist/providers/types.d.ts +33 -0
  136. package/dist/reference-data/catalog.d.ts +131 -0
  137. package/dist/reference-data/catalog.js +348 -0
  138. package/dist/reference-data/receipt.d.ts +41 -0
  139. package/dist/reference-data/receipt.js +40 -0
  140. package/dist/runtime/boot.d.ts +2 -2
  141. package/dist/runtime/boot.js +1 -1
  142. package/dist/runtime/connection-budget.d.ts +2 -2
  143. package/dist/runtime/connection-budget.js +5 -3
  144. package/dist/runtime/dbos.d.ts +7 -7
  145. package/dist/runtime/dbos.js +14 -11
  146. package/dist/runtime/shutdown.d.ts +2 -2
  147. package/dist/runtime/shutdown.js +4 -3
  148. package/dist/sandbox/create-sandbox.d.ts +2 -2
  149. package/dist/sandbox/docker-client.d.ts +2 -2
  150. package/dist/sandbox/docker-client.js +4 -1
  151. package/dist/sandbox/k8s-client.d.ts +3 -0
  152. package/dist/sandbox/k8s-client.js +5 -4
  153. package/dist/sandbox/notification-sweep.d.ts +3 -3
  154. package/dist/sandbox/notification-sweep.js +4 -2
  155. package/dist/sandbox/reaper.d.ts +3 -3
  156. package/dist/sandbox/reaper.js +7 -4
  157. package/dist/sandbox/watchdog.d.ts +3 -3
  158. package/dist/sandbox/watchdog.js +8 -2
  159. package/dist/schemas/data-profile-schemas.js +5 -3
  160. package/dist/schemas/plan-schemas.d.ts +21 -21
  161. package/dist/schemas/plan-schemas.js +9 -3
  162. package/dist/schemas/run-synthesis.d.ts +8 -4
  163. package/dist/schemas/run-synthesis.js +92 -56
  164. package/dist/schemas/validate-plan.js +1 -1
  165. package/dist/schemas/workflow-state.d.ts +7 -1
  166. package/dist/schemas/workflow-state.js +33 -19
  167. package/dist/state/artifacts.d.ts +13 -0
  168. package/dist/state/artifacts.js +27 -0
  169. package/dist/state/data-profile.d.ts +64 -5
  170. package/dist/state/index.d.ts +3 -3
  171. package/dist/state/index.js +1 -1
  172. package/dist/state/init.d.ts +2 -1
  173. package/dist/state/init.js +5 -6
  174. package/dist/tasks/data-profile.d.ts +25 -2
  175. package/dist/tasks/data-profile.js +95 -39
  176. package/dist/tools/bio/chembl.d.ts +38 -0
  177. package/dist/tools/bio/chembl.js +138 -0
  178. package/dist/tools/bio/comptox.d.ts +50 -0
  179. package/dist/tools/bio/comptox.js +149 -0
  180. package/dist/tools/bio/get-impc-ko-profile.js +9 -6
  181. package/dist/tools/bio/index.d.ts +5 -17
  182. package/dist/tools/bio/index.js +5 -17
  183. package/dist/tools/bio/keys.d.ts +14 -9
  184. package/dist/tools/bio/keys.js +16 -16
  185. package/dist/tools/bio/opentargets.d.ts +38 -0
  186. package/dist/tools/bio/opentargets.js +89 -0
  187. package/dist/tools/bio/pubchem.d.ts +29 -0
  188. package/dist/tools/bio/pubchem.js +92 -0
  189. package/dist/tools/bio/pubmed.d.ts +51 -0
  190. package/dist/tools/bio/pubmed.js +100 -0
  191. package/dist/tools/bio/search-bgee-expression.js +12 -6
  192. package/dist/tools/bio/search-clinical-trials.js +16 -5
  193. package/dist/tools/bio/search-dgidb.d.ts +11 -1
  194. package/dist/tools/bio/search-dgidb.js +92 -78
  195. package/dist/tools/bio/search-faers.js +8 -4
  196. package/dist/tools/bio/search-geo-datasets.js +20 -6
  197. package/dist/tools/bio/search-gwas-catalog.d.ts +1 -1
  198. package/dist/tools/bio/search-pharmgkb.js +10 -4
  199. package/dist/tools/display/index.d.ts +1 -0
  200. package/dist/tools/display/index.js +1 -0
  201. package/dist/tools/display/normalize-echart-spec.d.ts +61 -0
  202. package/dist/tools/display/normalize-echart-spec.js +211 -0
  203. package/dist/tools/display/show-user.d.ts +6 -1
  204. package/dist/tools/display/show-user.js +25 -12
  205. package/dist/tools/execute-plan.js +5 -2
  206. package/dist/tools/iterate-report.d.ts +171 -118
  207. package/dist/tools/iterate-report.js +336 -96
  208. package/dist/tools/lib/chembl-client.d.ts +12 -0
  209. package/dist/tools/lib/chembl-client.js +127 -0
  210. package/dist/tools/lib/ctx-ops.d.ts +224 -0
  211. package/dist/tools/lib/ctx-ops.js +450 -0
  212. package/dist/tools/lib/iuphar-client.js +10 -1
  213. package/dist/tools/lib/openfda-client.js +11 -3
  214. package/dist/tools/lib/pathway-client.js +10 -4
  215. package/dist/tools/lib/pubchem-ops.d.ts +74 -0
  216. package/dist/tools/lib/pubchem-ops.js +224 -0
  217. package/dist/tools/memory/update-working-memory.d.ts +45 -7
  218. package/dist/tools/memory/update-working-memory.js +111 -41
  219. package/dist/tools/report/index.d.ts +3 -2
  220. package/dist/tools/report/index.js +3 -2
  221. package/dist/tools/research/generate-analogy-report.js +25 -12
  222. package/dist/tools/research/generate-plan.d.ts +12 -5
  223. package/dist/tools/research/generate-plan.js +168 -37
  224. package/dist/tools/research/index.d.ts +1 -0
  225. package/dist/tools/research/index.js +1 -0
  226. package/dist/tools/research/inspect-data-profile.d.ts +85 -0
  227. package/dist/tools/research/inspect-data-profile.js +161 -0
  228. package/dist/tools/research/literature-reviewer.d.ts +3 -0
  229. package/dist/tools/research/literature-reviewer.js +5 -6
  230. package/dist/tools/sandbox/list-available-packages.d.ts +79 -5
  231. package/dist/tools/sandbox/list-available-packages.js +152 -10
  232. package/dist/tools/sandbox/list-available-refs.d.ts +141 -5
  233. package/dist/tools/sandbox/list-available-refs.js +647 -134
  234. package/dist/tools/sandbox/report-blocker.d.ts +39 -9
  235. package/dist/tools/sandbox/report-blocker.js +43 -20
  236. package/dist/tools/workspace/execute-command.d.ts +3 -0
  237. package/dist/tools/workspace/execute-command.js +3 -1
  238. package/dist/tools/workspace/read-file.js +4 -5
  239. package/dist/tools/workspace/run-exec.d.ts +4 -4
  240. package/dist/tools/workspace/run-exec.js +4 -4
  241. package/dist/tools/workspace/show-file.js +10 -6
  242. package/dist/tools/workspace/show-plan.js +12 -6
  243. package/dist/tools/workspace/workspace-search.d.ts +1 -1
  244. package/dist/tools/workspace/workspace-search.js +11 -5
  245. package/dist/workflows/execute-analysis.d.ts +46 -4
  246. package/dist/workflows/execute-analysis.js +118 -13
  247. package/dist/workflows/execute-target-assessment.d.ts +3 -0
  248. package/dist/workflows/execute-target-assessment.js +19 -15
  249. package/dist/workflows/sandbox-step.d.ts +9 -0
  250. package/dist/workflows/sandbox-step.js +39 -17
  251. package/dist/workflows/target-assessment/lib/citation-validator.js +2 -2
  252. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.js +2 -1
  253. package/dist/workflows/target-assessment/lib/llm-step.d.ts +3 -0
  254. package/dist/workflows/target-assessment/lib/llm-step.js +3 -1
  255. package/dist/workflows/target-assessment/lib/structured-llm.d.ts +3 -0
  256. package/dist/workflows/target-assessment/lib/structured-llm.js +1 -0
  257. package/dist/workflows/target-assessment/phase5-persist.d.ts +3 -0
  258. package/dist/workflows/target-assessment/phase5-persist.js +8 -3
  259. package/dist/workflows/target-assessment/progress.d.ts +2 -1
  260. package/dist/workflows/target-assessment/progress.js +4 -3
  261. package/dist/workspace/filesystem.js +163 -74
  262. package/dist/workspace/paths.d.ts +2 -2
  263. package/dist/workspace/paths.js +2 -2
  264. package/package.json +95 -96
  265. package/dist/execution/filesystem-artifact-registry.d.ts +0 -57
  266. package/dist/execution/filesystem-artifact-registry.js +0 -92
  267. package/dist/lib/identifiers.d.ts +0 -8
  268. package/dist/lib/identifiers.js +0 -10
  269. package/dist/lib/mime-detect.d.ts +0 -17
  270. package/dist/lib/mime-detect.js +0 -29
  271. package/dist/lib/streamed-download.d.ts +0 -28
  272. package/dist/lib/streamed-download.js +0 -111
  273. package/dist/lib/trace-context.d.ts +0 -11
  274. package/dist/lib/trace-context.js +0 -49
  275. package/dist/providers/llm-capabilities.d.ts +0 -33
  276. package/dist/providers/llm-capabilities.js +0 -58
  277. package/dist/schemas/render-step-prompt.d.ts +0 -32
  278. package/dist/schemas/render-step-prompt.js +0 -76
  279. package/dist/tools/bio/find-approval-precedent.d.ts +0 -32
  280. package/dist/tools/bio/find-approval-precedent.js +0 -72
  281. package/dist/tools/bio/get-article-details.d.ts +0 -11
  282. package/dist/tools/bio/get-article-details.js +0 -20
  283. package/dist/tools/bio/get-article-full-text.d.ts +0 -19
  284. package/dist/tools/bio/get-article-full-text.js +0 -32
  285. package/dist/tools/bio/get-bioactivity.d.ts +0 -11
  286. package/dist/tools/bio/get-bioactivity.js +0 -23
  287. package/dist/tools/bio/get-drug-info.d.ts +0 -9
  288. package/dist/tools/bio/get-drug-info.js +0 -22
  289. package/dist/tools/bio/get-mechanism.d.ts +0 -8
  290. package/dist/tools/bio/get-mechanism.js +0 -18
  291. package/dist/tools/bio/get-pubchem-assays.d.ts +0 -22
  292. package/dist/tools/bio/get-pubchem-assays.js +0 -107
  293. package/dist/tools/bio/get-pubchem-cross-refs.d.ts +0 -15
  294. package/dist/tools/bio/get-pubchem-cross-refs.js +0 -68
  295. package/dist/tools/bio/get-target-safety.d.ts +0 -18
  296. package/dist/tools/bio/get-target-safety.js +0 -26
  297. package/dist/tools/bio/search-compounds.d.ts +0 -22
  298. package/dist/tools/bio/search-compounds.js +0 -160
  299. package/dist/tools/bio/search-ctx-chemical.d.ts +0 -50
  300. package/dist/tools/bio/search-ctx-chemical.js +0 -130
  301. package/dist/tools/bio/search-ctx-exposure.d.ts +0 -55
  302. package/dist/tools/bio/search-ctx-exposure.js +0 -176
  303. package/dist/tools/bio/search-ctx-hazard.d.ts +0 -111
  304. package/dist/tools/bio/search-ctx-hazard.js +0 -178
  305. package/dist/tools/bio/search-opentargets.d.ts +0 -11
  306. package/dist/tools/bio/search-opentargets.js +0 -25
  307. package/dist/tools/bio/search-pubchem-compound.d.ts +0 -26
  308. package/dist/tools/bio/search-pubchem-compound.js +0 -110
  309. package/dist/tools/bio/search-pubmed.d.ts +0 -17
  310. package/dist/tools/bio/search-pubmed.js +0 -31
  311. package/dist/tools/bio/search-targets.d.ts +0 -9
  312. package/dist/tools/bio/search-targets.js +0 -20
  313. package/dist/tools/bio/search-toxcast.d.ts +0 -36
  314. package/dist/tools/bio/search-toxcast.js +0 -141
  315. package/dist/workflows/register-workflows.d.ts +0 -51
  316. package/dist/workflows/register-workflows.js +0 -48
  317. package/dist/workflows/resume-execute-analysis.d.ts +0 -70
  318. package/dist/workflows/resume-execute-analysis.js +0 -80
  319. package/dist/workflows/target-assessment/correction-loop.d.ts +0 -45
  320. package/dist/workflows/target-assessment/correction-loop.js +0 -62
  321. package/dist/workflows/target-assessment/steps/phase0-resolve.d.ts +0 -71
  322. package/dist/workflows/target-assessment/steps/phase0-resolve.js +0 -21
  323. package/dist/workflows/target-assessment/steps/phase1-aggregate.d.ts +0 -1018
  324. package/dist/workflows/target-assessment/steps/phase1-aggregate.js +0 -25
  325. package/dist/workspace/mount-strategy.d.ts +0 -50
  326. package/dist/workspace/mount-strategy.js +0 -67
  327. package/dist/workspace/provenance-collector.d.ts +0 -34
  328. package/dist/workspace/provenance-collector.js +0 -16
  329. package/dist/workspace/workspace-paths.d.ts +0 -26
  330. package/dist/workspace/workspace-paths.js +0 -31
@@ -25,10 +25,12 @@ import { z } from "zod";
25
25
  import { writeFileWithinRoot } from "../lib/fs-helpers.js";
26
26
  import { RunSynthesisSchema } from "../schemas/run-synthesis.js";
27
27
  import { synthesisAgentPrompt } from "../prompts/synthesis-agent.js";
28
+ import { composeSystemPrompt } from "../agents/system-prompt.js";
28
29
  import { runDir } from "../workspace/paths.js";
29
30
  import { runToTerminal } from "../loop/run-to-terminal.js";
30
31
  import { passthroughStep } from "../loop/run-step.js";
31
32
  import { defineTool } from "../tools/define-tool.js";
33
+ import { createReportBlockerToolFor } from "../tools/sandbox/report-blocker.js";
32
34
  import { createLiteratureReviewerTool } from "../tools/research/literature-reviewer.js";
33
35
  // ── Agent identity / budgets ────────────────────────────────────────
34
36
  const AGENT_ID = "run-synthesizer";
@@ -125,13 +127,14 @@ function fullyValidate(candidate, ctx) {
125
127
  function buildSubmitTool(holder, ctx) {
126
128
  return defineTool({
127
129
  id: "submit_synthesis",
128
- description: "Submit the final synthesis. Re-validates payload against the schema " +
129
- "and semantic checks (stepId refs, theme→finding refs, keyReferences " +
130
- "PMIDs cited, numeric PMIDs). On success returns {accepted: true} — " +
131
- "STOP after this. On rejection returns {accepted: false, issues} — " +
132
- "fix the specific fields at each issue path and call again, or switch " +
133
- "to report_blocker if the synthesis cannot be made valid.",
134
- inputSchema: z.object({ synthesis: z.unknown() }),
130
+ description: "Submit the final synthesis. Re-validates the payload against the " +
131
+ "semantic checks the arg schema cannot express (stepId refs, " +
132
+ "theme→finding refs, keyReferences PMIDs cited, numeric PMIDs). On " +
133
+ "success returns {accepted: true} — STOP after this. On rejection " +
134
+ "returns {accepted: false, issues} — fix the specific fields at each " +
135
+ "issue path and call again, or switch to report_blocker if the " +
136
+ "synthesis cannot be made valid.",
137
+ inputSchema: z.object({ synthesis: RunSynthesisSchema }),
135
138
  execute: async (input) => {
136
139
  if (holder.outcome !== null) {
137
140
  return ok({
@@ -155,18 +158,16 @@ function buildSubmitTool(holder, ctx) {
155
158
  });
156
159
  }
157
160
  function buildBlockerTool(holder) {
158
- return defineTool({
159
- id: "report_blocker",
160
- description: "Terminal. Use when the run produced no synthesizable content (all " +
161
- "summaries empty, contradictory to the point of incoherence, or no " +
162
- "findings worth surfacing). Pass a short reason. Stop after calling.",
163
- inputSchema: z.object({ reason: z.string().min(1) }),
164
- execute: async (input) => {
165
- if (holder.outcome === null) {
166
- holder.outcome = { kind: "blocker", reason: input.reason };
167
- }
168
- return ok({ recorded: true });
161
+ return createReportBlockerToolFor({
162
+ record: (outcome) => {
163
+ if (holder.outcome === null)
164
+ holder.outcome = outcome;
169
165
  },
166
+ blockedWhen: "Ends run synthesis with no synthesis written. Use it when the run " +
167
+ "produced no synthesizable content (all step summaries empty, " +
168
+ "contradictory to the point of incoherence, or no findings worth " +
169
+ "surfacing) — not as an escape from a synthesis you could fix and " +
170
+ "submit.",
170
171
  });
171
172
  }
172
173
  /**
@@ -207,7 +208,7 @@ export async function generateRunSynthesis(input) {
207
208
  const tools = [submitTool, blockerTool, reviewer];
208
209
  const agent = {
209
210
  id: AGENT_ID,
210
- systemPrompt: synthesisAgentPrompt,
211
+ systemPrompt: composeSystemPrompt(synthesisAgentPrompt),
211
212
  model: input.model,
212
213
  tools,
213
214
  maxIterations: SYNTHESIZER_MAX_ITERATIONS,
@@ -29,7 +29,10 @@ import type { LoopMessage } from "../loop/types.js";
29
29
  import type { AgentChat } from "../providers/types.js";
30
30
  import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
31
31
  import { type StepSummary } from "../schemas/step-summary.js";
32
+ import type { Logger } from "../lib/logger.js";
32
33
  export interface GenerateStepSummaryOptions {
34
+ /** Operational logging seam; omitted falls back to no-op. */
35
+ readonly logger?: Logger;
33
36
  readonly provider: AgentChat;
34
37
  readonly session: AgentSession;
35
38
  readonly modelId: string;
@@ -28,9 +28,11 @@ import { forSubAgent } from "../auth/types.js";
28
28
  import { finalText, runAgent } from "../loop/run-agent.js";
29
29
  import { passthroughStep } from "../loop/run-step.js";
30
30
  import { stepSummaryPrompt } from "../prompts/execute-analysis/step-summary.js";
31
+ import { composeSystemPrompt } from "../agents/system-prompt.js";
31
32
  import { createReadFileTool } from "../tools/workspace/read-file.js";
32
33
  import { StepSummarySchema } from "../schemas/step-summary.js";
33
34
  import { incrementSummaryNullCount } from "./step-summary-metrics.js";
35
+ import { createNoopLogger } from "../lib/console-logger.js";
34
36
  const SYSTEM_PROMPT = `You are a sandbox agent writing a post-step interpretive summary of work you just completed.
35
37
 
36
38
  Your prior conversation — tool calls, code output, intermediate results — is in the message history above. Use it for narrative and intent.
@@ -68,11 +70,12 @@ function sanitizeTranscript(messages) {
68
70
  * without `summary.md` in that case.
69
71
  */
70
72
  export async function generateStepSummary(opts) {
73
+ const logger = (opts.logger ?? createNoopLogger()).named("step-summary").with({ runId: opts.runId, stepId: opts.stepId, agentId: opts.agentId });
71
74
  const transcript = sanitizeTranscript(opts.messages);
72
75
  const userPrompt = stepSummaryPrompt(opts.artifactPaths.join("\n"));
73
76
  const writer = {
74
77
  id: SUMMARY_AGENT_ID,
75
- systemPrompt: SYSTEM_PROMPT,
78
+ systemPrompt: composeSystemPrompt(SYSTEM_PROMPT),
76
79
  model: opts.modelId,
77
80
  tools: [createReadFileTool(opts.workspaceFs, opts.workingDir)],
78
81
  maxIterations: opts.maxIterations ?? DEFAULT_MAX_ITERATIONS,
@@ -88,13 +91,13 @@ export async function generateStepSummary(opts) {
88
91
  });
89
92
  }
90
93
  catch (err) {
91
- console.warn(`[step-summary] Summary loop failed runId=${opts.runId} stepId=${opts.stepId} agentId=${opts.agentId}: ${err instanceof Error ? err.message : err}`);
94
+ logger.warn("summary loop failed", logger.errorFields(err));
92
95
  incrementSummaryNullCount(opts.agentId, "throw");
93
96
  return undefined;
94
97
  }
95
98
  const markdown = finalText(result.messages);
96
99
  if (!markdown || markdown.trim().length === 0) {
97
- console.warn(`[step-summary] Empty markdown runId=${opts.runId} stepId=${opts.stepId} agentId=${opts.agentId}`);
100
+ logger.warn("empty markdown");
98
101
  incrementSummaryNullCount(opts.agentId, "empty");
99
102
  return undefined;
100
103
  }
package/dist/index.d.ts CHANGED
@@ -39,7 +39,8 @@ export { createConfiguredAiSdkProvider } from "./providers/ai-sdk.js";
39
39
  export type { AiSdkProviderConfig, ConfiguredAiSdkProviderDeps } from "./providers/ai-sdk.js";
40
40
  export { createEmbeddingProvider } from "./providers/embedding.js";
41
41
  export type { EmbeddingProviderDeps } from "./providers/embedding.js";
42
- export type { AgentChat, ChatProvider, EmbeddingProvider, ChatRequest, ChatResponse, ChatStreamEvent, ModelMessage } from "./providers/types.js";
42
+ export type { AgentChat, ChatProvider, EmbeddingProvider, ChatRequest, ChatResponse, ChatStreamEvent, ChatUsage, ModelMessage, PromptCachePolicy, } from "./providers/types.js";
43
+ export { DEFAULT_PROMPT_CACHE, promptCacheProviderOptions } from "./providers/prompt-cache.js";
43
44
  export { toProviderError, isProviderError } from "./providers/errors.js";
44
45
  export type { ProviderError } from "./providers/errors.js";
45
46
  export { createStreamingChat } from "./providers/streaming-chat.js";
@@ -94,7 +95,6 @@ export type { PlanStep } from "./workflows/execute-analysis-scheduler.js";
94
95
  export { validatePlan } from "./schemas/validate-plan.js";
95
96
  export type { ValidatePlanOptions } from "./schemas/validate-plan.js";
96
97
  export type { ValidationResult } from "./schemas/validate-plan.js";
97
- export { renderStepPrompt } from "./schemas/render-step-prompt.js";
98
98
  export { upsertPlan, loadPlan } from "./state/plans.js";
99
99
  export type { UpsertPlanInput } from "./state/plans.js";
100
100
  export { insertRun, queryActiveRun, updateRunStatus, queryRun, queryRunsByAnalysis, RunDedupCollisionError } from "./state/runs.js";
@@ -102,8 +102,15 @@ export type { InsertRunInput } from "./state/runs.js";
102
102
  export { queryStepsByRun } from "./state/step-executions.js";
103
103
  export type { CortexRunRow, StepExecutionRow, RunStatus } from "./state/schema.js";
104
104
  export type { DbError } from "./lib/db-result.js";
105
+ export { REFERENCE_DATA_CATALOG, REFERENCE_DATA_CATALOG_VERSION, ReferenceArtifactPathSchema, ReferenceArtifactSchema, ReferenceDataCatalogSchema, ReferenceDatasetSchema, ReferenceSha256Schema, UnknownReferenceDatasetError, referenceArtifactKey, resolveReferenceInstallPlan, } from "./reference-data/catalog.js";
106
+ export type { ReferenceArtifact, ReferenceDataCatalog, ReferenceDataset, ReferenceInstallPlan, ReferenceInstallPlanDataset, ReferenceIntegrity, } from "./reference-data/catalog.js";
107
+ export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt } from "./reference-data/receipt.js";
108
+ export type { ReferenceInstallReceipt, ReferenceReceiptArtifact } from "./reference-data/receipt.js";
105
109
  export { queryActiveSandboxes } from "./state/active-sandboxes.js";
106
110
  export { runStepDir } from "./workspace/paths.js";
111
+ export type { LogFields, LogLevel, Logger } from "./lib/logger.js";
112
+ export { defaultErrorFields } from "./lib/logger.js";
113
+ export { createConsoleLogger, createNoopLogger } from "./lib/console-logger.js";
107
114
  export { registerSandboxReaper } from "./sandbox/reaper.js";
108
115
  export type { RegisterReaperDeps } from "./sandbox/reaper.js";
109
116
  export { registerWatchdog } from "./sandbox/watchdog.js";
package/dist/index.js CHANGED
@@ -43,6 +43,14 @@ export { createAnthropicProvider } from "./providers/anthropic.js";
43
43
  // above is a convenience over this union's `anthropic` arm.
44
44
  export { createConfiguredAiSdkProvider } from "./providers/ai-sdk.js";
45
45
  export { createEmbeddingProvider } from "./providers/embedding.js";
46
+ // Prompt caching. `PromptCachePolicy` is the harness's vendor-neutral cache
47
+ // directive; a composition root sets it per run via `RunAgentOptions.promptCache`
48
+ // (defaulting to `DEFAULT_PROMPT_CACHE` — 5m — when omitted, since an agent loop
49
+ // re-sends its prefix every iteration and breaks even immediately). Hosts whose
50
+ // endpoint ignores cache directives — notably the Claude Max OAuth path — pass
51
+ // `"off"`; the `cortex.harness.agent.cache_*_tokens` metrics show which case a
52
+ // deployment is actually in.
53
+ export { DEFAULT_PROMPT_CACHE, promptCacheProviderOptions } from "./providers/prompt-cache.js";
46
54
  // Provider error channel. `ProviderError` is the value `chat`/`embed` fail
47
55
  // with; `toProviderError` is its sole constructor. Exposed so an embedder
48
56
  // realizing its own `ChatProvider`/`EmbeddingProvider` fails with the exact
@@ -129,25 +137,36 @@ export { registerSandboxStep } from "./workflows/sandbox-step.js";
129
137
  // `SandboxAgentDeps`, then selects the per-step agent by id from this record;
130
138
  // `SANDBOX_AGENT_META` is the planner-facing meta the same ids key.
131
139
  export { createSandboxAgents, SANDBOX_AGENT_META } from "./agents/sandbox/index.js";
132
- // Plan schema, structural validation, and per-step prompt rendering — the gates
133
- // a plan passes before it can trigger a run, plus the prompt each step's agent
134
- // receives. `PlanStep` is the minimal `{ id, depends_on }` element type that
140
+ // Plan schema and structural validation — the gates a plan passes before it can
141
+ // trigger a run. `PlanStep` is the minimal `{ id, depends_on }` element type that
135
142
  // `ExecuteAnalysisInput.steps` is typed against; a parsed `AnalysisPlan`'s
136
- // richer `AnalysisStep[]` is assignable to it.
143
+ // richer `AnalysisStep[]` is assignable to it, and is also what
144
+ // `ExecuteAnalysisInput.planStepById` carries. There is no prompt renderer here:
145
+ // an embedder supplies the plan's step DATA and the harness composes each step's
146
+ // seed at dispatch, when the step's dependencies have actually produced something.
137
147
  export { AnalysisPlanSchema } from "./schemas/workflow-state.js";
138
148
  export { validatePlan } from "./schemas/validate-plan.js";
139
- export { renderStepPrompt } from "./schemas/render-step-prompt.js";
140
149
  // Plan + run + step ledgers around a trigger. `upsertPlan` takes a
141
150
  // caller-derived id and inserts-if-absent (the deterministic-id intake path);
142
151
  // the run-state calls mirror the chat trigger's reserve → dedup → status flow.
143
152
  export { upsertPlan, loadPlan } from "./state/plans.js";
144
153
  export { insertRun, queryActiveRun, updateRunStatus, queryRun, queryRunsByAnalysis, RunDedupCollisionError } from "./state/runs.js";
145
154
  export { queryStepsByRun } from "./state/step-executions.js";
155
+ // Host-neutral reference-data catalog and shared installation receipts. Each
156
+ // artifact names the third-party upstream it is fetched from; embedders own all
157
+ // transfer/storage behavior but never choose a different source.
158
+ export { REFERENCE_DATA_CATALOG, REFERENCE_DATA_CATALOG_VERSION, ReferenceArtifactPathSchema, ReferenceArtifactSchema, ReferenceDataCatalogSchema, ReferenceDatasetSchema, ReferenceSha256Schema, UnknownReferenceDatasetError, referenceArtifactKey, resolveReferenceInstallPlan, } from "./reference-data/catalog.js";
159
+ export { REFERENCE_INSTALL_RECEIPT_VERSION, ReferenceInstallReceiptSchema, parseReferenceInstallReceipt } from "./reference-data/receipt.js";
146
160
  // Backs `WatchdogDeps.queryActiveSandboxes` when the embedder wires the watchdog.
147
161
  export { queryActiveSandboxes } from "./state/active-sandboxes.js";
148
162
  // Workspace-tree path convention — the root-relative step directory each
149
163
  // sandbox step confines its artifact writes to (join onto the resolved root).
150
164
  export { runStepDir } from "./workspace/paths.js";
165
+ // `Logger.errorFields` is on the interface so a realization can defer to its
166
+ // sink's native error handling (pino's `err` serializer, OTel's `exception.*`);
167
+ // `defaultErrorFields` is the shipped mapping for one with no opinion.
168
+ export { defaultErrorFields } from "./lib/logger.js";
169
+ export { createConsoleLogger, createNoopLogger } from "./lib/console-logger.js";
151
170
  // Sandbox-hygiene scheduled workflows. Reaper reclaims a dead host's orphaned
152
171
  // containers; watchdog converts a dead sandbox into a prompt step failure
153
172
  // instead of a deadline-long `DBOS.recv` hang; the sweep prunes stale
@@ -7,9 +7,12 @@
7
7
  * semaphore so the sidecar isn't overwhelmed by concurrent reports.
8
8
  */
9
9
  import type { Browser, BrowserContext, Page } from "puppeteer-core";
10
+ import type { Logger } from "./logger.js";
10
11
  export interface ChromeConfig {
12
+ /** Operational logging seam; omitted falls back to no-op. */
13
+ readonly logger?: Logger;
11
14
  readonly browserUrl?: string;
12
15
  readonly maxPages?: number;
13
16
  }
14
- export declare function getBrowser(browserUrl?: string): Promise<Browser>;
17
+ export declare function getBrowser(browserUrl?: string, injected?: Logger): Promise<Browser>;
15
18
  export declare function withPage<T>(cfg: ChromeConfig, fn: (page: Page, context: BrowserContext) => Promise<T>): Promise<T>;
@@ -7,6 +7,7 @@
7
7
  * semaphore so the sidecar isn't overwhelmed by concurrent reports.
8
8
  */
9
9
  import puppeteer from "puppeteer-core";
10
+ import { createNoopLogger } from "./console-logger.js";
10
11
  let browser;
11
12
  let connecting;
12
13
  let semaphore;
@@ -41,7 +42,8 @@ function getSemaphore(maxPages) {
41
42
  }
42
43
  return semaphore;
43
44
  }
44
- export async function getBrowser(browserUrl) {
45
+ export async function getBrowser(browserUrl, injected) {
46
+ const logger = (injected ?? createNoopLogger()).named("chrome");
45
47
  if (browser && browser.connected)
46
48
  return browser;
47
49
  if (connecting)
@@ -50,21 +52,21 @@ export async function getBrowser(browserUrl) {
50
52
  if (!browserURL) {
51
53
  throw new Error("CHROME_BROWSER_URL is not set — Cortex requires the chrome sidecar to be reachable");
52
54
  }
53
- console.log(`[chrome] connecting to browser at ${browserURL}`);
55
+ logger.info("connecting to browser", { browserURL });
54
56
  connecting = puppeteer
55
57
  .connect({ browserURL })
56
58
  .then((b) => {
57
59
  browser = b;
58
60
  b.on("disconnected", () => {
59
- console.log("[chrome] browser disconnected; will reconnect on next request");
61
+ logger.info("browser disconnected; will reconnect on next request");
60
62
  if (browser === b)
61
63
  browser = undefined;
62
64
  });
63
- console.log(`[chrome] connected (wsEndpoint=${b.wsEndpoint()})`);
65
+ logger.info("connected", { wsEndpoint: b.wsEndpoint() });
64
66
  return b;
65
67
  })
66
68
  .catch((err) => {
67
- console.error("[chrome] failed to connect to browser:", err);
69
+ logger.error("failed to connect to browser", logger.errorFields(err));
68
70
  throw err;
69
71
  })
70
72
  .finally(() => {
@@ -76,7 +78,7 @@ export async function withPage(cfg, fn) {
76
78
  const release = await getSemaphore(cfg.maxPages).acquire();
77
79
  let context;
78
80
  try {
79
- const b = await getBrowser(cfg.browserUrl);
81
+ const b = await getBrowser(cfg.browserUrl, cfg.logger);
80
82
  context = await b.createBrowserContext();
81
83
  const page = await context.newPage();
82
84
  return await fn(page, context);
@@ -87,7 +89,8 @@ export async function withPage(cfg, fn) {
87
89
  await context.close();
88
90
  }
89
91
  catch (err) {
90
- console.error("[chrome] error closing browser context:", err);
92
+ const logger = (cfg.logger ?? createNoopLogger()).named("chrome");
93
+ logger.error("error closing browser context", logger.errorFields(err));
91
94
  }
92
95
  }
93
96
  release();
@@ -0,0 +1,19 @@
1
+ /**
2
+ * The trivial `Logger` realizations: one that writes to `console`, and the
3
+ * silent fallback the harness substitutes when an embedder wires nothing.
4
+ *
5
+ * `createConsoleLogger` is offered, never defaulted. A host whose UI owns
6
+ * stdout (an alternate-screen TUI, for instance) discards console output, so
7
+ * defaulting to it would quietly reintroduce the invisible-diagnostics failure
8
+ * the seam exists to prevent — the default is `createNoopLogger`, which is at
9
+ * least honest about saying nothing.
10
+ */
11
+ import { type Logger } from "./logger.js";
12
+ /** Build a `Logger` that writes each record to the matching `console` method. */
13
+ export declare function createConsoleLogger(): Logger;
14
+ /**
15
+ * Build a `Logger` that discards every record. This is what the harness falls
16
+ * back to when an embedder injects no logger, so internal call sites can log
17
+ * unconditionally instead of threading `?.` through every diagnostic.
18
+ */
19
+ export declare function createNoopLogger(): Logger;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * The trivial `Logger` realizations: one that writes to `console`, and the
3
+ * silent fallback the harness substitutes when an embedder wires nothing.
4
+ *
5
+ * `createConsoleLogger` is offered, never defaulted. A host whose UI owns
6
+ * stdout (an alternate-screen TUI, for instance) discards console output, so
7
+ * defaulting to it would quietly reintroduce the invisible-diagnostics failure
8
+ * the seam exists to prevent — the default is `createNoopLogger`, which is at
9
+ * least honest about saying nothing.
10
+ */
11
+ import { defaultErrorFields } from "./logger.js";
12
+ /** Renders accumulated namespace segments as the message's `[a.b]` prefix. */
13
+ function prefixed(names, msg) {
14
+ return names.length > 0 ? `[${names.join(".")}] ${msg}` : msg;
15
+ }
16
+ /**
17
+ * `with()` accumulates into `bindings` and `named()` into `names`; each record
18
+ * merges call-site fields over the bindings, so a narrower call-site key
19
+ * deliberately wins over inherited context.
20
+ */
21
+ function makeConsoleLogger(names, bindings) {
22
+ const emit = (level) => (msg, fields) => {
23
+ // The one sanctioned console site in the harness — writing to console is
24
+ // this function's entire contract. `no-console` exempts this file by path
25
+ // in eslint.config.js rather than by an inline disable here.
26
+ console[level](prefixed(names, msg), { ...bindings, ...fields });
27
+ };
28
+ return {
29
+ debug: emit("debug"),
30
+ info: emit("info"),
31
+ warn: emit("warn"),
32
+ error: emit("error"),
33
+ with: (extra) => makeConsoleLogger(names, { ...bindings, ...extra }),
34
+ named: (name) => makeConsoleLogger([...names, name], bindings),
35
+ // console renders whatever it is handed; there is no native error
36
+ // serializer here to defer to, so the shipped default is the right shape.
37
+ errorFields: defaultErrorFields,
38
+ };
39
+ }
40
+ /** Build a `Logger` that writes each record to the matching `console` method. */
41
+ export function createConsoleLogger() {
42
+ return makeConsoleLogger([], {});
43
+ }
44
+ /**
45
+ * Build a `Logger` that discards every record. This is what the harness falls
46
+ * back to when an embedder injects no logger, so internal call sites can log
47
+ * unconditionally instead of threading `?.` through every diagnostic.
48
+ */
49
+ export function createNoopLogger() {
50
+ const noop = {
51
+ debug: () => { },
52
+ info: () => { },
53
+ warn: () => { },
54
+ error: () => { },
55
+ with: () => noop,
56
+ named: () => noop,
57
+ // Still the real mapping, not `{}`: callers spread the result into fields
58
+ // they may pass elsewhere, so it must stay honest even when nothing is emitted.
59
+ errorFields: defaultErrorFields,
60
+ };
61
+ return noop;
62
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * The harness's operational logging seam.
3
+ *
4
+ * The harness ships as a published package embedded by hosts it does not
5
+ * control, so it declares the shape it logs through and lets the embedder bind
6
+ * a realization at its composition root — the same inversion the capability
7
+ * seams use. Naming a concrete logger here would push that library onto every
8
+ * consumer.
9
+ *
10
+ * The alternative that reads as obvious — take the embedder's logger type
11
+ * directly — was rejected for exactly that reason. `@opentelemetry/api-logs`
12
+ * was the other candidate, being the nearest thing to a neutral standard, but
13
+ * it is still pre-1.0 and would force consumers into an OTel SDK to read a log
14
+ * line. JS has no `log/slog` equivalent to conform to, so the harness defines
15
+ * its own minimum and adapts at the edges.
16
+ *
17
+ * Diagnostics MUST go through here, never `console`: an embedder whose UI owns
18
+ * stdout discards console output entirely, which is silent data loss precisely
19
+ * when something has gone wrong. `createConsoleLogger` (`./console-logger.ts`)
20
+ * is the one sanctioned console site.
21
+ */
22
+ /** Severity of a log record, ascending. */
23
+ export type LogLevel = "debug" | "info" | "warn" | "error";
24
+ /**
25
+ * Structured key/value context attached to a record. Identifiers ride here
26
+ * rather than interpolated into the message so records stay queryable and any
27
+ * field-name redaction the host configures can actually match them.
28
+ */
29
+ export type LogFields = Record<string, unknown>;
30
+ /**
31
+ * Operational logger. Message-first (`error(msg, fields?)`) — the convention
32
+ * `slog`, winston, and `console` share. Object-first loggers exist (pino,
33
+ * bunyan) and an embedder using one adapts at its composition root; shaping
34
+ * this contract to match one of them would model the published surface on a
35
+ * single consumer's choice.
36
+ */
37
+ export interface Logger {
38
+ debug(msg: string, fields?: LogFields): void;
39
+ info(msg: string, fields?: LogFields): void;
40
+ warn(msg: string, fields?: LogFields): void;
41
+ error(msg: string, fields?: LogFields): void;
42
+ /**
43
+ * Bind `fields` onto every record the returned logger emits — `slog`'s
44
+ * `With`. Lets a workflow body fix `{ runId, stepId }` once instead of
45
+ * repeating it at each call site.
46
+ */
47
+ with(fields: LogFields): Logger;
48
+ /**
49
+ * Bind a namespace onto every record the returned logger emits, rendered as
50
+ * a bracketed prefix on the message: `named("boot")` turns
51
+ * `info("harness booted")` into `[boot] harness booted`.
52
+ *
53
+ * The namespace is a first-class part of a record, so it is declared once at
54
+ * the seam rather than hand-typed into every message string — hand-typing is
55
+ * what lets one module drift to `[Boot]` or drop the tag entirely. Nesting
56
+ * composes with a dot (`named("post-step").named("reconcile")` →
57
+ * `[post-step.reconcile]`), matching the tag convention already in use.
58
+ */
59
+ named(name: string): Logger;
60
+ /**
61
+ * Normalize a thrown value into fields to spread onto a record:
62
+ * `logger.error("step failure", { ...logger.errorFields(err), stepId })`.
63
+ *
64
+ * Every catch block would otherwise hand-roll
65
+ * `err instanceof Error ? err.message : String(err)` — repetition that
66
+ * reliably drifts, and that silently drops the stack from whichever site
67
+ * forgot it.
68
+ *
69
+ * It lives on the interface rather than as a free function because how an
70
+ * error is best represented is a property of the SINK, not of the harness: a
71
+ * pino-backed realization may defer to pino's `err` serializer, an
72
+ * OTel-backed one to the `exception.*` semantic conventions. Owning the
73
+ * mapping here would foreclose both. `defaultErrorFields` is the shipped
74
+ * implementation — a realization with no opinion just references it.
75
+ */
76
+ errorFields(err: unknown): LogFields;
77
+ }
78
+ /**
79
+ * The default `Logger.errorFields`: `err` carries the message, `stack` rides as
80
+ * its own field when there is one.
81
+ *
82
+ * A raw `Error` is deliberately not passed through as a field value: it is
83
+ * assignable to `unknown` and so type-checks, but `JSON.stringify(new Error())`
84
+ * is `{}` — a sink that serializes to JSON would drop the message entirely,
85
+ * which is the silent-loss failure this seam exists to prevent. Normalizing to
86
+ * strings means any sink gets something printable regardless of its serializer.
87
+ */
88
+ export declare function defaultErrorFields(err: unknown): LogFields;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * The harness's operational logging seam.
3
+ *
4
+ * The harness ships as a published package embedded by hosts it does not
5
+ * control, so it declares the shape it logs through and lets the embedder bind
6
+ * a realization at its composition root — the same inversion the capability
7
+ * seams use. Naming a concrete logger here would push that library onto every
8
+ * consumer.
9
+ *
10
+ * The alternative that reads as obvious — take the embedder's logger type
11
+ * directly — was rejected for exactly that reason. `@opentelemetry/api-logs`
12
+ * was the other candidate, being the nearest thing to a neutral standard, but
13
+ * it is still pre-1.0 and would force consumers into an OTel SDK to read a log
14
+ * line. JS has no `log/slog` equivalent to conform to, so the harness defines
15
+ * its own minimum and adapts at the edges.
16
+ *
17
+ * Diagnostics MUST go through here, never `console`: an embedder whose UI owns
18
+ * stdout discards console output entirely, which is silent data loss precisely
19
+ * when something has gone wrong. `createConsoleLogger` (`./console-logger.ts`)
20
+ * is the one sanctioned console site.
21
+ */
22
+ /**
23
+ * The default `Logger.errorFields`: `err` carries the message, `stack` rides as
24
+ * its own field when there is one.
25
+ *
26
+ * A raw `Error` is deliberately not passed through as a field value: it is
27
+ * assignable to `unknown` and so type-checks, but `JSON.stringify(new Error())`
28
+ * is `{}` — a sink that serializes to JSON would drop the message entirely,
29
+ * which is the silent-loss failure this seam exists to prevent. Normalizing to
30
+ * strings means any sink gets something printable regardless of its serializer.
31
+ */
32
+ export function defaultErrorFields(err) {
33
+ if (err instanceof Error) {
34
+ return err.stack ? { err: err.message, stack: err.stack } : { err: err.message };
35
+ }
36
+ return { err: String(err) };
37
+ }
@@ -15,13 +15,14 @@
15
15
  * so inbound/outbound trace-context propagation across those paths is
16
16
  * not currently wired.
17
17
  */
18
+ import type { Logger } from "./logger.js";
18
19
  /**
19
20
  * Initialize the OpenTelemetry SDK. Must be called before any code that
20
21
  * calls trace.getTracer() or metrics.getMeter().
21
22
  *
22
23
  * Safe to call multiple times — only the first call has effect.
23
24
  */
24
- export declare function initOtel(): void;
25
+ export declare function initOtel(injected?: Logger): void;
25
26
  /**
26
27
  * Flush and shut down the OTel exporters. Called by the graceful-shutdown
27
28
  * sequence so in-flight batch spans/metrics make it to the collector
package/dist/lib/otel.js CHANGED
@@ -16,6 +16,7 @@
16
16
  * not currently wired.
17
17
  */
18
18
  import { propagation, metrics, trace } from "@opentelemetry/api";
19
+ import { createNoopLogger } from "./console-logger.js";
19
20
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
20
21
  import { MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
21
22
  import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-http";
@@ -33,10 +34,11 @@ let registeredMeterProvider;
33
34
  *
34
35
  * Safe to call multiple times — only the first call has effect.
35
36
  */
36
- export function initOtel() {
37
+ export function initOtel(injected) {
37
38
  if (initialized)
38
39
  return;
39
40
  initialized = true;
41
+ const logger = (injected ?? createNoopLogger()).named("otel");
40
42
  // ── W3C Trace Context propagation ──────────────────────────────────
41
43
  propagation.setGlobalPropagator(new W3CTraceContextPropagator());
42
44
  const endpoint = process.env.OTEL_EXPORTER_OTLP_ENDPOINT;
@@ -65,8 +67,11 @@ export function initOtel() {
65
67
  const testSpan = testTracer.startSpan("init-check");
66
68
  const ctx = testSpan.spanContext();
67
69
  const isNoop = ctx.spanId === "0000000000000000";
68
- console.log(`[otel] TracerProvider registered: spanId=${ctx.spanId}, noop=${isNoop}, ` +
69
- `endpoint=${endpoint ?? "(none)"}, processors=${spanProcessors.length}`);
70
+ // A `console.log` here is what forced the CLI embedder to pass
71
+ // `initTelemetry: () => {}` — its TUI owns stdout, so the banner corrupted
72
+ // the screen and the whole of the harness's traces + metrics were switched
73
+ // off to avoid it. At debug through the injected seam it costs nothing.
74
+ logger.debug("TracerProvider registered", { spanId: ctx.spanId, noop: isNoop, endpoint: endpoint ?? null, processors: spanProcessors.length });
70
75
  testSpan.end();
71
76
  }
72
77
  // ── Metrics ────────────────────────────────────────────────────────
@@ -1,4 +1,4 @@
1
- import type { ChatResponse, ChatProvider, ChatRequest } from "../../providers/types.js";
1
+ import type { ChatResponse, ChatProvider, ChatRequest, ChatUsage } from "../../providers/types.js";
2
2
  import type { AgentSession as Session } from "../../auth/types.js";
3
3
  export type TextBlock = {
4
4
  type: "text";
@@ -20,7 +20,7 @@ export type ToolUseBlock = {
20
20
  input: unknown;
21
21
  };
22
22
  export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock;
23
- export declare function makeMessage(content: ContentBlock[], finishReason: ChatResponse["finishReason"] | "tool_use" | "end_turn" | "max_tokens" | "refusal"): ChatResponse;
23
+ export declare function makeMessage(content: ContentBlock[], finishReason: ChatResponse["finishReason"] | "tool_use" | "end_turn" | "max_tokens" | "refusal", usage?: ChatUsage): ChatResponse;
24
24
  export declare function textBlock(text: string): TextBlock;
25
25
  export declare function thinkingBlock(thinking: string, signature: string): ThinkingBlock;
26
26
  export declare function toolUseBlock(id: string, name: string, input: unknown): ToolUseBlock;
@@ -13,10 +13,11 @@ function mapFinishReason(finishReason) {
13
13
  return finishReason;
14
14
  }
15
15
  }
16
- export function makeMessage(content, finishReason) {
16
+ export function makeMessage(content, finishReason, usage) {
17
17
  return {
18
18
  message: { role: "assistant", content },
19
19
  finishReason: mapFinishReason(finishReason),
20
+ usage,
20
21
  };
21
22
  }
22
23
  export function textBlock(text) {