@inflexa-ai/harness 0.17.0 → 0.19.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 (249) hide show
  1. package/dist/agents/report-builder.d.ts +2 -0
  2. package/dist/agents/report-builder.js +2 -0
  3. package/dist/agents/report-session-agent.d.ts +56 -0
  4. package/dist/agents/report-session-agent.js +72 -0
  5. package/dist/app/report-session-runtime.d.ts +72 -0
  6. package/dist/app/report-session-runtime.js +157 -0
  7. package/dist/app/spawn-report-session.d.ts +85 -0
  8. package/dist/app/spawn-report-session.js +83 -0
  9. package/dist/contracts/chat-events.d.ts +9 -0
  10. package/dist/contracts/index.d.ts +6 -2
  11. package/dist/contracts/index.js +3 -1
  12. package/dist/contracts/organ-system.d.ts +49 -0
  13. package/dist/contracts/organ-system.js +67 -0
  14. package/dist/contracts/report-blocks.d.ts +564 -0
  15. package/dist/contracts/report-blocks.js +117 -0
  16. package/dist/contracts/report-reference.d.ts +459 -0
  17. package/dist/contracts/report-reference.js +256 -0
  18. package/dist/contracts/schemas/chat-events.d.ts +2 -0
  19. package/dist/contracts/schemas/chat-events.js +2 -0
  20. package/dist/contracts/severity.d.ts +15 -0
  21. package/dist/contracts/severity.js +10 -0
  22. package/dist/contracts/target-dossier.d.ts +8675 -12972
  23. package/dist/contracts/target-dossier.js +534 -591
  24. package/dist/data/safety-panel-schema.d.ts +18 -5
  25. package/dist/data/safety-panel-schema.js +4 -4
  26. package/dist/execution/artifact-metadata.js +1 -0
  27. package/dist/execution/report-runner.js +1 -3
  28. package/dist/execution/run-synthesis.js +2 -0
  29. package/dist/index.d.ts +6 -0
  30. package/dist/index.js +19 -0
  31. package/dist/lib/async-utils.d.ts +16 -4
  32. package/dist/lib/async-utils.js +20 -15
  33. package/dist/lib/host-concurrency.js +1 -0
  34. package/dist/loop/__fixtures__/scripted-provider.js +23 -2
  35. package/dist/loop/run-agent.js +58 -10
  36. package/dist/loop/tool-detail.d.ts +40 -0
  37. package/dist/loop/tool-detail.js +35 -3
  38. package/dist/loop/types.d.ts +13 -0
  39. package/dist/memory/thread-history.d.ts +10 -0
  40. package/dist/memory/thread-history.js +12 -1
  41. package/dist/memory/thread-store.d.ts +4 -4
  42. package/dist/memory/thread-store.js +18 -9
  43. package/dist/prompts/report-session.d.ts +1 -0
  44. package/dist/prompts/report-session.js +89 -0
  45. package/dist/prompts/target-assessment/briefs/executive-recommendation.d.ts +1 -1
  46. package/dist/prompts/target-assessment/briefs/executive-recommendation.js +5 -5
  47. package/dist/prompts/target-assessment/briefs/liability-bullets.d.ts +1 -1
  48. package/dist/prompts/target-assessment/briefs/liability-bullets.js +1 -1
  49. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.d.ts +1 -1
  50. package/dist/prompts/target-assessment/briefs/target-organ-liabilities.js +11 -3
  51. package/dist/prompts/target-assessment/investigation/adversarial-critique.d.ts +1 -0
  52. package/dist/prompts/target-assessment/investigation/adversarial-critique.js +67 -0
  53. package/dist/prompts/target-assessment/investigation/claim-reverification.d.ts +1 -0
  54. package/dist/prompts/target-assessment/investigation/claim-reverification.js +49 -0
  55. package/dist/prompts/target-assessment/investigation/index.d.ts +3 -0
  56. package/dist/prompts/target-assessment/investigation/index.js +3 -0
  57. package/dist/prompts/target-assessment/investigation/mechanism-proposal.d.ts +1 -0
  58. package/dist/prompts/target-assessment/investigation/mechanism-proposal.js +49 -0
  59. package/dist/prompts/target-assessment/tox-voice/index.d.ts +1 -1
  60. package/dist/prompts/target-assessment/tox-voice/vocabulary.d.ts +8 -5
  61. package/dist/prompts/target-assessment/tox-voice/vocabulary.js +9 -20
  62. package/dist/providers/__fixtures__/session.d.ts +5 -4
  63. package/dist/providers/__fixtures__/session.js +4 -4
  64. package/dist/providers/ai-sdk.d.ts +77 -0
  65. package/dist/providers/ai-sdk.js +149 -19
  66. package/dist/providers/errors.d.ts +14 -0
  67. package/dist/providers/errors.js +103 -18
  68. package/dist/providers/types.d.ts +16 -0
  69. package/dist/providers/types.js +10 -1
  70. package/dist/report-model/block-walk.d.ts +56 -0
  71. package/dist/report-model/block-walk.js +89 -0
  72. package/dist/report-model/draft-finish.d.ts +60 -0
  73. package/dist/report-model/draft-finish.js +56 -0
  74. package/dist/report-model/draft-operations.d.ts +125 -0
  75. package/dist/report-model/draft-operations.js +341 -0
  76. package/dist/report-model/draft-read.d.ts +59 -0
  77. package/dist/report-model/draft-read.js +109 -0
  78. package/dist/report-model/draft.d.ts +51 -0
  79. package/dist/report-model/draft.js +32 -0
  80. package/dist/report-model/fixture-resolver.d.ts +8 -0
  81. package/dist/report-model/fixture-resolver.js +252 -0
  82. package/dist/report-model/pin-snapshot.d.ts +30 -0
  83. package/dist/report-model/pin-snapshot.js +40 -0
  84. package/dist/report-model/reference-resolver.d.ts +113 -0
  85. package/dist/report-model/reference-resolver.js +60 -0
  86. package/dist/report-model/structural-validation.d.ts +22 -0
  87. package/dist/report-model/structural-validation.js +68 -0
  88. package/dist/report-model/validate.d.ts +72 -0
  89. package/dist/report-model/validate.js +95 -0
  90. package/dist/report-render/chart.d.ts +33 -0
  91. package/dist/report-render/chart.js +538 -0
  92. package/dist/report-render/page.d.ts +127 -0
  93. package/dist/report-render/page.js +307 -0
  94. package/dist/report-render/references.d.ts +32 -0
  95. package/dist/report-render/references.js +44 -0
  96. package/dist/report-render/render.d.ts +25 -0
  97. package/dist/report-render/render.js +125 -0
  98. package/dist/report-render/script-json.d.ts +13 -0
  99. package/dist/report-render/script-json.js +15 -0
  100. package/dist/report-render/types.d.ts +39 -0
  101. package/dist/report-render/types.js +10 -0
  102. package/dist/report-render/value-bridge.d.ts +62 -0
  103. package/dist/report-render/value-bridge.js +82 -0
  104. package/dist/report-render/views/chart-view.d.ts +11 -0
  105. package/dist/report-render/views/chart-view.js +23 -0
  106. package/dist/report-render/views/page-view.d.ts +20 -0
  107. package/dist/report-render/views/page-view.js +49 -0
  108. package/dist/report-render/views/prose.d.ts +28 -0
  109. package/dist/report-render/views/prose.js +75 -0
  110. package/dist/report-render/views/references-view.d.ts +20 -0
  111. package/dist/report-render/views/references-view.js +64 -0
  112. package/dist/report-render/views/values.d.ts +42 -0
  113. package/dist/report-render/views/values.js +43 -0
  114. package/dist/runtime/assemble.d.ts +15 -0
  115. package/dist/runtime/assemble.js +28 -6
  116. package/dist/runtime/boot.d.ts +3 -3
  117. package/dist/runtime/boot.js +15 -4
  118. package/dist/runtime/dbos.d.ts +4 -4
  119. package/dist/state/artifacts.d.ts +16 -0
  120. package/dist/state/artifacts.js +18 -0
  121. package/dist/state/init.js +54 -0
  122. package/dist/state/purge-analysis.js +2 -0
  123. package/dist/state/report-session-state.d.ts +112 -0
  124. package/dist/state/report-session-state.js +150 -0
  125. package/dist/state/report-versions.d.ts +129 -0
  126. package/dist/state/report-versions.js +167 -0
  127. package/dist/state/snapshot-parse.d.ts +34 -0
  128. package/dist/state/snapshot-parse.js +82 -0
  129. package/dist/tasks/data-profile.js +1 -0
  130. package/dist/tools/ad-hoc-router.d.ts +7 -2
  131. package/dist/tools/ad-hoc-router.js +2 -1
  132. package/dist/tools/bio/chembl.js +1 -0
  133. package/dist/tools/bio/comptox.js +1 -0
  134. package/dist/tools/bio/drug-gene-interactions.js +1 -0
  135. package/dist/tools/bio/gene-disease-evidence.js +1 -0
  136. package/dist/tools/bio/gene-preclinical-profile.js +1 -0
  137. package/dist/tools/bio/keys.d.ts +3 -2
  138. package/dist/tools/bio/keys.js +3 -2
  139. package/dist/tools/bio/lookup-annotation.js +1 -0
  140. package/dist/tools/bio/opentargets.d.ts +3 -3
  141. package/dist/tools/bio/opentargets.js +4 -3
  142. package/dist/tools/bio/pubchem.js +1 -0
  143. package/dist/tools/bio/search-clinical-trials.js +1 -0
  144. package/dist/tools/bio/search-faers.js +1 -0
  145. package/dist/tools/bio/search-geo-datasets.js +1 -0
  146. package/dist/tools/bio/search-interactions.js +1 -0
  147. package/dist/tools/bio/target-safety.d.ts +4 -6
  148. package/dist/tools/bio/target-safety.js +7 -6
  149. package/dist/tools/define-tool.d.ts +19 -12
  150. package/dist/tools/define-tool.js +4 -2
  151. package/dist/tools/display/show-user.js +1 -0
  152. package/dist/tools/iterate-report.js +2 -0
  153. package/dist/tools/lib/chembl-client.d.ts +2 -0
  154. package/dist/tools/lib/chembl-client.js +1 -0
  155. package/dist/tools/lib/monarch-client.d.ts +42 -0
  156. package/dist/tools/lib/monarch-client.js +100 -0
  157. package/dist/tools/report/build-report.js +1 -0
  158. package/dist/tools/report/mint-preview-url.js +1 -0
  159. package/dist/tools/report/preview-snapshot.js +1 -0
  160. package/dist/tools/report/submit-report.js +1 -0
  161. package/dist/tools/report/version-fs.js +4 -0
  162. package/dist/tools/report-authoring/authoring-tools.d.ts +241 -0
  163. package/dist/tools/report-authoring/authoring-tools.js +460 -0
  164. package/dist/tools/report-session/index.d.ts +8 -0
  165. package/dist/tools/report-session/index.js +8 -0
  166. package/dist/tools/report-session/preview-report.d.ts +90 -0
  167. package/dist/tools/report-session/preview-report.js +245 -0
  168. package/dist/tools/research/context7-docs.js +2 -0
  169. package/dist/tools/research/generate-analogy-report.js +1 -0
  170. package/dist/tools/research/generate-plan.js +7 -2
  171. package/dist/tools/research/inspect-data-profile.js +4 -0
  172. package/dist/tools/research/literature-reviewer.js +1 -0
  173. package/dist/tools/research/search-arxiv.js +1 -0
  174. package/dist/tools/research/search-github-repos.js +1 -0
  175. package/dist/tools/research/search-semantic-scholar.js +1 -0
  176. package/dist/tools/sandbox/list-available-packages.js +23 -0
  177. package/dist/tools/sandbox/list-available-refs.js +36 -0
  178. package/dist/tools/sandbox/report-blocker.js +1 -0
  179. package/dist/tools/sandbox/skills.js +2 -0
  180. package/dist/tools/workspace/file-stat.js +1 -0
  181. package/dist/tools/workspace/show-file.js +2 -0
  182. package/dist/tools/workspace/show-plan.js +1 -0
  183. package/dist/workflows/execute-analysis.d.ts +5 -1
  184. package/dist/workflows/execute-analysis.js +5 -5
  185. package/dist/workflows/execute-target-assessment.d.ts +13 -2
  186. package/dist/workflows/execute-target-assessment.js +78 -16
  187. package/dist/workflows/target-assessment/assemblers/index.d.ts +1 -1
  188. package/dist/workflows/target-assessment/assemblers/index.js +1 -1
  189. package/dist/workflows/target-assessment/assemblers/literature.d.ts +9 -27
  190. package/dist/workflows/target-assessment/assemblers/literature.js +32 -48
  191. package/dist/workflows/target-assessment/assemblers/orchestrator.d.ts +17 -9
  192. package/dist/workflows/target-assessment/assemblers/orchestrator.js +195 -85
  193. package/dist/workflows/target-assessment/assemblers/safety.d.ts +70 -48
  194. package/dist/workflows/target-assessment/assemblers/safety.js +125 -42
  195. package/dist/workflows/target-assessment/collectors/index.d.ts +5 -2
  196. package/dist/workflows/target-assessment/collectors/index.js +48 -5
  197. package/dist/workflows/target-assessment/coverage.d.ts +43 -27
  198. package/dist/workflows/target-assessment/coverage.js +33 -29
  199. package/dist/workflows/target-assessment/fanout/index.js +1 -0
  200. package/dist/workflows/target-assessment/investigation/index.d.ts +97 -0
  201. package/dist/workflows/target-assessment/investigation/index.js +419 -0
  202. package/dist/workflows/target-assessment/lib/approval-precedents.d.ts +26 -5
  203. package/dist/workflows/target-assessment/lib/approval-precedents.js +95 -29
  204. package/dist/workflows/target-assessment/lib/citation-validator.js +1 -1
  205. package/dist/workflows/target-assessment/lib/clinical-consequence-annotator.d.ts +2 -2
  206. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.d.ts +2 -3
  207. package/dist/workflows/target-assessment/lib/clinical-evidence-attribution.js +0 -4
  208. package/dist/workflows/target-assessment/lib/compute-derived.d.ts +6 -5
  209. package/dist/workflows/target-assessment/lib/compute-derived.js +14 -5
  210. package/dist/workflows/target-assessment/lib/family-complex-supplement.d.ts +5 -6
  211. package/dist/workflows/target-assessment/lib/family-complex-supplement.js +2 -3
  212. package/dist/workflows/target-assessment/lib/fda-label-safety.d.ts +72 -0
  213. package/dist/workflows/target-assessment/lib/fda-label-safety.js +166 -0
  214. package/dist/workflows/target-assessment/lib/heterodimer-filter.d.ts +0 -9
  215. package/dist/workflows/target-assessment/lib/heterodimer-filter.js +0 -10
  216. package/dist/workflows/target-assessment/lib/hpo-organ-map.d.ts +34 -0
  217. package/dist/workflows/target-assessment/lib/hpo-organ-map.js +63 -0
  218. package/dist/workflows/target-assessment/lib/impc-organ-map.d.ts +19 -0
  219. package/dist/workflows/target-assessment/lib/impc-organ-map.js +39 -0
  220. package/dist/workflows/target-assessment/lib/meddra-organ-map.d.ts +24 -14
  221. package/dist/workflows/target-assessment/lib/meddra-organ-map.js +59 -47
  222. package/dist/workflows/target-assessment/lib/recommendation-sanitizer.js +1 -1
  223. package/dist/workflows/target-assessment/lib/regulatory-actions.d.ts +2 -3
  224. package/dist/workflows/target-assessment/lib/regulatory-actions.js +33 -4
  225. package/dist/workflows/target-assessment/lib/safety-corroboration.d.ts +44 -0
  226. package/dist/workflows/target-assessment/lib/safety-corroboration.js +273 -0
  227. package/dist/workflows/target-assessment/lib/target-identity-filter.d.ts +5 -7
  228. package/dist/workflows/target-assessment/lib/target-identity-filter.js +5 -7
  229. package/dist/workflows/target-assessment/metrics.d.ts +1 -6
  230. package/dist/workflows/target-assessment/metrics.js +1 -13
  231. package/dist/workflows/target-assessment/phase4-assemble.d.ts +2147 -807
  232. package/dist/workflows/target-assessment/phase4-assemble.js +19 -19
  233. package/dist/workflows/target-assessment/phase5-persist.d.ts +20 -21
  234. package/dist/workflows/target-assessment/phase5-persist.js +56 -173
  235. package/dist/workflows/target-assessment/schemas.d.ts +46 -1
  236. package/dist/workflows/target-assessment/schemas.js +19 -0
  237. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.d.ts +1 -0
  238. package/dist/workflows/target-assessment/steps/fanout/polypharm-for-one-modulator-step.js +2 -0
  239. package/dist/workflows/target-assessment/steps/phase2-aggregate.d.ts +26 -62
  240. package/dist/workflows/target-assessment/steps/phase2-aggregate.js +0 -4
  241. package/dist/workflows/target-assessment/steps/phase3-aggregate.d.ts +27 -154
  242. package/dist/workflows/target-assessment/steps/phase3-aggregate.js +0 -6
  243. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.d.ts +0 -6
  244. package/dist/workflows/target-assessment/steps/synthesis/diagnostics.js +0 -18
  245. package/dist/workflows/target-assessment/sub-workflows/per-modulator-polypharm.d.ts +1 -0
  246. package/dist/workflows/target-assessment/synthesis/index.d.ts +52 -5
  247. package/dist/workflows/target-assessment/synthesis/index.js +4 -1
  248. package/dist/workflows/target-assessment/synthesis-probes.d.ts +3 -3
  249. package/package.json +4 -1
@@ -0,0 +1,460 @@
1
+ /**
2
+ * The authoring tool surface over the draft operations.
3
+ *
4
+ * The factory takes a session-state gateway, and it holds no draft. Each tool reads the thread id from the
5
+ * scope of the call, loads the state of that thread, applies the pure operation, and persists the new
6
+ * document. Thus two threads never share one draft, and a landed document outlives a host restart.
7
+ *
8
+ * Each tool maps its flat input onto a core operation, and it applies the pure operation. A landed
9
+ * document persists before the tool reports `applied: true`. A refused operation returns typed data in the
10
+ * ok channel, and the error channel stays for an unexpected failure.
11
+ *
12
+ * A call with no report thread in its scope, a thread with no stored state, and a gateway fault each
13
+ * refuse as typed data too. That refusal sits beside the core refusal, and it names the absent thread
14
+ * scope or the absent state.
15
+ *
16
+ * The primitive admits a flat object input only. Thus a destination is four optional fields, and not a
17
+ * nested union. A conflict between the flat fields refuses before the core runs.
18
+ */
19
+ import { err, ok } from "neverthrow";
20
+ import { z } from "zod";
21
+ import { isSafeId } from "../../workspace/paths.js";
22
+ import { addBlock, changeBlock, locate, moveBlock, removeBlock, setTitle, } from "../../report-model/draft-operations.js";
23
+ import { buildOutline, childOutline, readBlock } from "../../report-model/draft-read.js";
24
+ import { finishDraft } from "../../report-model/draft-finish.js";
25
+ import { DraftBlockSchema } from "../../report-model/draft.js";
26
+ import { defineTool } from "../define-tool.js";
27
+ /**
28
+ * The block payload.
29
+ *
30
+ * The published JSON Schema carries the whole draft grammar, because a tool is self-describing at attach
31
+ * time and the schema is the only place where the model learns the shape of the eight block kinds. A bare
32
+ * `z.unknown()` emits the empty schema, which tells the model nothing and leaves it to discover each
33
+ * required field through one refusal at a time.
34
+ *
35
+ * The `z.unknown()` member keeps the runtime parse permissive. Thus a malformed payload still reaches the
36
+ * core, which parses it with the same grammar and refuses `malformed-block` as typed data in the ok
37
+ * channel. A strict input schema would turn that designed refusal into a hard input-validation error from
38
+ * the loop.
39
+ */
40
+ const blockPayload = z.union([DraftBlockSchema, z.unknown()]);
41
+ /**
42
+ * A flat field that the model can leave out.
43
+ *
44
+ * Nullish, and not optional. A model that must choose one of four destination fields routinely sends
45
+ * `null` for the three it does not use, and strict function calling requires every declared key to be
46
+ * present. Under `.optional()` each of those nulls fails the input parse, and the block never lands.
47
+ */
48
+ const absentable = (schema) => schema.nullish();
49
+ /** The flat destination fields. An anchor is `before` or `after`, and a place is `start` or `end`. */
50
+ const addBlockInput = z.object({
51
+ block: blockPayload,
52
+ parentId: absentable(z.string()),
53
+ place: absentable(z.enum(["start", "end"])),
54
+ before: absentable(z.string()),
55
+ after: absentable(z.string()),
56
+ });
57
+ /**
58
+ * The change payload is a title for a section target, or a block for an atom target, and exactly one of
59
+ * the two. Both fields are absentable, because a section retitle names no block. Zod 4 treats a bare
60
+ * `z.unknown()` as a required key, thus an optional marker is what makes the retitle call representable.
61
+ */
62
+ const changeBlockInput = z.object({
63
+ targetId: z.string(),
64
+ title: absentable(z.string()),
65
+ block: blockPayload.optional(),
66
+ });
67
+ const removeBlockInput = z.object({
68
+ targetId: z.string(),
69
+ });
70
+ const moveBlockInput = z.object({
71
+ targetId: z.string(),
72
+ parentId: absentable(z.string()),
73
+ place: absentable(z.enum(["start", "end"])),
74
+ before: absentable(z.string()),
75
+ after: absentable(z.string()),
76
+ });
77
+ const readOutlineInput = z.object({});
78
+ /** The read names its target with the same field name as each mutation, thus one id key serves them all. */
79
+ const readBlockInput = z.object({
80
+ targetId: z.string(),
81
+ });
82
+ const setTitleInput = z.object({
83
+ title: z.string().min(1),
84
+ });
85
+ const finishDraftInput = z.object({});
86
+ /**
87
+ * Resolve the report thread of a call, and load its state through the gateway.
88
+ *
89
+ * The report thread and its analysis ride on the analysis scope. The thread id becomes one segment of a
90
+ * session directory, thus the safe-id check sits here beside the shape check: a scope whose id carries a
91
+ * separator or a traversal segment names no thread that a tool can write under.
92
+ *
93
+ * The stored analysis of the thread must match the scope. A mismatch reads one analysis's draft and writes
94
+ * into another analysis's workspace, thus the resolution refuses it with a permanent reason that names the
95
+ * two ids. A scope of a different kind, an unsafe id, an absent thread, a wrong thread type, and a gateway
96
+ * fault each refuse as a typed `SessionRefusal` too.
97
+ *
98
+ * The authoring tools and the preview tool share this one resolution, thus the two surfaces accept the
99
+ * same thread id and map a load outcome onto a refusal the same way.
100
+ */
101
+ export async function openReportThread(gateway, scope) {
102
+ if (scope.kind !== "analysis" || scope.threadId === undefined || !isSafeId(scope.threadId)) {
103
+ return err({ reason: "no-thread-scope", detail: "the scope of the call names no usable report thread id" });
104
+ }
105
+ const { threadId, analysisId } = scope;
106
+ const loaded = await gateway.load(threadId);
107
+ if (loaded.outcome === "absent") {
108
+ return err({ reason: "absent-state", detail: `no report session exists for the thread ${threadId}, and this condition is permanent` });
109
+ }
110
+ if (loaded.outcome === "wrong-type") {
111
+ return err({ reason: "wrong-thread-type", detail: `${loaded.detail}, and this condition is permanent` });
112
+ }
113
+ if (loaded.outcome === "failed") {
114
+ return err({ reason: "state-unavailable", detail: loaded.detail });
115
+ }
116
+ if (loaded.analysisId !== analysisId) {
117
+ return err({
118
+ reason: "scope-analysis-mismatch",
119
+ detail: `the scope names the analysis ${analysisId}, but the thread ${threadId} belongs to the analysis ${loaded.analysisId}`,
120
+ });
121
+ }
122
+ return ok({ threadId, analysisId, state: loaded.state, token: loaded.token });
123
+ }
124
+ /** Read an absentable field. An explicit `null` and an omitted key both mean that the model gave no value. */
125
+ function given(value) {
126
+ return value ?? undefined;
127
+ }
128
+ /**
129
+ * Map the flat destination fields onto the core destination.
130
+ *
131
+ * Two anchors, or an anchor beside a place, name two places at one time. Thus the map refuses with
132
+ * `conflicting-destination`, and the detail names the conflict. An anchor implies its own parent, thus a
133
+ * place beside an anchor is a contradiction and not a refinement.
134
+ *
135
+ * The reason is not `unknown-target`, which means that no block holds a named id. A conflict is a fault in
136
+ * the shape of the call, and the repair is to drop one field. An agent that reads `unknown-target` instead
137
+ * goes back to the outline for a better id, finds the ids were already correct, and sends the same
138
+ * conflicting pair again.
139
+ */
140
+ function toDestination(input) {
141
+ const parentId = given(input.parentId);
142
+ const place = given(input.place);
143
+ const before = given(input.before);
144
+ const after = given(input.after);
145
+ if (before !== undefined && after !== undefined) {
146
+ return err({ reason: "conflicting-destination", detail: "a destination names `before` or `after`, not both" });
147
+ }
148
+ const hasAnchor = before !== undefined || after !== undefined;
149
+ if (hasAnchor && place !== undefined) {
150
+ return err({ reason: "conflicting-destination", detail: "a destination names an anchor or a `place`, not both" });
151
+ }
152
+ let resolvedPlace;
153
+ if (before !== undefined) {
154
+ resolvedPlace = { before };
155
+ }
156
+ else if (after !== undefined) {
157
+ resolvedPlace = { after };
158
+ }
159
+ else {
160
+ resolvedPlace = place;
161
+ }
162
+ return ok({ parentId, place: resolvedPlace });
163
+ }
164
+ /**
165
+ * Map the flat change fields onto the core change operation.
166
+ *
167
+ * A change names a title or a block, and exactly one of the two. Neither field and both fields each name
168
+ * an ambiguous change. Thus the map refuses with `payload-kind-mismatch`.
169
+ */
170
+ function toChangeOperation(input) {
171
+ const title = given(input.title);
172
+ const block = given(input.block);
173
+ if (title !== undefined && block !== undefined) {
174
+ return err({ reason: "payload-kind-mismatch", detail: "a change names a `title` or a `block`, not both" });
175
+ }
176
+ if (title !== undefined) {
177
+ return ok({ targetId: input.targetId, title });
178
+ }
179
+ if (block !== undefined) {
180
+ return ok({ targetId: input.targetId, block: decodeBlockPayload(block) });
181
+ }
182
+ return err({ reason: "payload-kind-mismatch", detail: "a change names a `title` or a `block`" });
183
+ }
184
+ /**
185
+ * Give the block payload as the core expects it, and decode a double-encoded one.
186
+ *
187
+ * A model routinely sends a nested object as a `JSON.stringify` string. The loop repairs that at its own
188
+ * input boundary, but only for a schema that rejects the string, and this payload accepts any value so
189
+ * that a malformed block can come back as a typed refusal. Thus the decode happens here. A string that is
190
+ * not JSON passes through as it is, and the core refuses it as a malformed block.
191
+ */
192
+ function decodeBlockPayload(block) {
193
+ if (typeof block !== "string") {
194
+ return block;
195
+ }
196
+ try {
197
+ return JSON.parse(block);
198
+ }
199
+ catch {
200
+ return block;
201
+ }
202
+ }
203
+ /** Read the `kind` of an untyped block payload, or `undefined` when the payload names none. */
204
+ function readKind(block) {
205
+ return readStringField(block, "kind");
206
+ }
207
+ /** Read the `id` of an untyped block payload, or `undefined` when the payload names none. */
208
+ function readId(block) {
209
+ return readStringField(block, "id");
210
+ }
211
+ function readStringField(block, field) {
212
+ if (block !== null && typeof block === "object") {
213
+ const value = block[field];
214
+ if (typeof value === "string") {
215
+ return value;
216
+ }
217
+ }
218
+ return undefined;
219
+ }
220
+ /**
221
+ * The id of the section that holds a block, or `undefined` when the block sits at the root.
222
+ *
223
+ * The two cases are not distinguishable from the value alone, thus a caller that needs to tell "the root"
224
+ * from "no such block" tests the block itself first.
225
+ */
226
+ function holderIdOf(document, blockId) {
227
+ return locate(document, blockId)?.parent?.id;
228
+ }
229
+ /** Read the child order of one container out of a document. */
230
+ function containerIn(document, parentId) {
231
+ if (parentId === undefined) {
232
+ return { children: childOutline(document.sections, 0) };
233
+ }
234
+ const located = locate(document, parentId);
235
+ // A parent id reaches here only from a landed operation, which already resolved it to a section. The
236
+ // empty fall-back keeps the read total rather than asserting that.
237
+ const children = located !== undefined && located.block.kind === "section" ? located.block.blocks : [];
238
+ return { parentId, children: childOutline(children, located?.path.length ?? 0) };
239
+ }
240
+ /**
241
+ * Every tool of this surface runs inline.
242
+ *
243
+ * A tool loads the state of its thread, applies the pure operation, and persists the result in one body.
244
+ * The reported outline must agree with the row that the body just wrote. A step-mode tool goes through
245
+ * `DBOS.runStep`, which caches the result of a step and replays it without a fresh load. A replay could
246
+ * then report an outline that the current row no longer holds. Inline mode reads and writes the row in one
247
+ * lifetime, thus the outline never drifts from the state.
248
+ */
249
+ const AUTHORING_EXECUTION_MODE = "inline";
250
+ /**
251
+ * Make the eight authoring tools over a session-state gateway.
252
+ *
253
+ * Each tool reads the thread id from the scope of the call, and it loads the state of that thread through
254
+ * the gateway. Thus one factory serves every thread, and two threads never share one draft. A mutation
255
+ * persists the new document before it reports `applied: true`, thus a reported landing is never lost.
256
+ */
257
+ export function createReportAuthoringTools(gateway) {
258
+ /**
259
+ * Resolve the thread of the call, and load its state.
260
+ *
261
+ * The shared `openReportThread` holds the one resolution. A scope of a different kind, an unsafe or
262
+ * absent thread id, an absent row, and a gateway fault each refuse before the call reaches a core
263
+ * operation.
264
+ */
265
+ const openThread = (ctx) => openReportThread(gateway, ctx.session.scope);
266
+ /**
267
+ * Persist a landing, and report the containers that the operation changed.
268
+ *
269
+ * `holders` names them by id, and it reads the previous document and the next document, because a
270
+ * removed block has a holder only in the first and an added block has one only in the second. The child
271
+ * order comes out of `next` in either case. A persist fault refuses with `state-unavailable`, thus the
272
+ * tool reports `applied: true` only after the row holds the new document.
273
+ *
274
+ * The persist is a compare-and-swap against the token that the load read. A concurrent turn that landed
275
+ * first turns the persist into a conflict, and the tool refuses with `stale-state`.
276
+ */
277
+ const land = async (threadId, token, previous, result, holders) => {
278
+ if (result.isErr()) {
279
+ return { applied: false, refusal: result.error };
280
+ }
281
+ const next = result.value;
282
+ const persisted = await gateway.persist(threadId, next, token);
283
+ if (persisted.outcome === "conflict") {
284
+ return {
285
+ applied: false,
286
+ refusal: { reason: "stale-state", detail: "another turn changed the report since this turn read it, thus read the state again" },
287
+ };
288
+ }
289
+ if (persisted.outcome === "failed") {
290
+ return { applied: false, refusal: { reason: "state-unavailable", detail: persisted.detail } };
291
+ }
292
+ // A move inside one container names the same holder two times.
293
+ const unique = [...new Set(holders(previous, next))];
294
+ return { applied: true, changed: unique.map((parentId) => containerIn(next, parentId)) };
295
+ };
296
+ const refuse = (refusal) => ({ applied: false, refusal });
297
+ const add_block = defineTool({
298
+ id: "add_block",
299
+ description: "Add one block to the draft. The `block` schema gives the eight kinds and the fields of each one. " +
300
+ "You choose the `id` of the block, and it must be unique in the draft. " +
301
+ "Name the destination with `parentId` and one of `place`, `before`, or `after`. " +
302
+ "The root admits a section only, and an atom needs a section as its parent.",
303
+ inputSchema: addBlockInput,
304
+ executionMode: AUTHORING_EXECUTION_MODE,
305
+ describeCall: (input) => {
306
+ const kind = readKind(decodeBlockPayload(input.block));
307
+ return kind !== undefined ? `add ${kind}` : "add a block";
308
+ },
309
+ execute: async (input, ctx) => {
310
+ const opened = await openThread(ctx);
311
+ if (opened.isErr()) {
312
+ return ok(refuse(opened.error));
313
+ }
314
+ const { threadId, state, token } = opened.value;
315
+ const destination = toDestination(input);
316
+ if (destination.isErr()) {
317
+ return ok(refuse(destination.error));
318
+ }
319
+ const block = decodeBlockPayload(input.block);
320
+ const addedId = readId(block);
321
+ return ok(await land(threadId, token, state.document, addBlock(state.document, { block, destination: destination.value }, state.snapshot), (_previous, next) => [addedId === undefined ? undefined : holderIdOf(next, addedId)]));
322
+ },
323
+ });
324
+ const change_block = defineTool({
325
+ id: "change_block",
326
+ description: "Change one block by its id. Give a `title` to retitle a section, or a `block` to replace an atom, and never both. " +
327
+ "The change keeps the id of the target, thus the `id` of the payload does not matter. A kind change is permitted.",
328
+ inputSchema: changeBlockInput,
329
+ executionMode: AUTHORING_EXECUTION_MODE,
330
+ describeCall: (input) => `change ${input.targetId}`,
331
+ execute: async (input, ctx) => {
332
+ const opened = await openThread(ctx);
333
+ if (opened.isErr()) {
334
+ return ok(refuse(opened.error));
335
+ }
336
+ const { threadId, state, token } = opened.value;
337
+ const operation = toChangeOperation(input);
338
+ if (operation.isErr()) {
339
+ return ok(refuse(operation.error));
340
+ }
341
+ return ok(await land(threadId, token, state.document, changeBlock(state.document, operation.value, state.snapshot), (_previous, next) => [
342
+ holderIdOf(next, input.targetId),
343
+ ]));
344
+ },
345
+ });
346
+ const remove_block = defineTool({
347
+ id: "remove_block",
348
+ description: "Remove one block by its id. A removed section takes its whole subtree with it, and the id is free for a later add.",
349
+ inputSchema: removeBlockInput,
350
+ executionMode: AUTHORING_EXECUTION_MODE,
351
+ describeCall: (input) => `remove ${input.targetId}`,
352
+ execute: async (input, ctx) => {
353
+ const opened = await openThread(ctx);
354
+ if (opened.isErr()) {
355
+ return ok(refuse(opened.error));
356
+ }
357
+ const { threadId, state, token } = opened.value;
358
+ return ok(await land(threadId, token, state.document, removeBlock(state.document, { targetId: input.targetId }, state.snapshot), (previous) => [
359
+ holderIdOf(previous, input.targetId),
360
+ ]));
361
+ },
362
+ });
363
+ const move_block = defineTool({
364
+ id: "move_block",
365
+ description: "Move one block by its id to a new destination. Name the destination with `parentId` and one of `place`, `before`, or `after`. " +
366
+ "A section cannot move into its own subtree.",
367
+ inputSchema: moveBlockInput,
368
+ executionMode: AUTHORING_EXECUTION_MODE,
369
+ describeCall: (input) => `move ${input.targetId}`,
370
+ execute: async (input, ctx) => {
371
+ const opened = await openThread(ctx);
372
+ if (opened.isErr()) {
373
+ return ok(refuse(opened.error));
374
+ }
375
+ const { threadId, state, token } = opened.value;
376
+ const destination = toDestination(input);
377
+ if (destination.isErr()) {
378
+ return ok(refuse(destination.error));
379
+ }
380
+ return ok(await land(threadId, token, state.document, moveBlock(state.document, { targetId: input.targetId, destination: destination.value }, state.snapshot), (previous, next) => [holderIdOf(previous, input.targetId), holderIdOf(next, input.targetId)]));
381
+ },
382
+ });
383
+ const set_title = defineTool({
384
+ id: "set_title",
385
+ description: "Set the title of the report document. A draft starts with no title, and a document needs one. " +
386
+ "The title names the whole report, and it is not the title of a section.",
387
+ inputSchema: setTitleInput,
388
+ executionMode: AUTHORING_EXECUTION_MODE,
389
+ describeCall: (input) => `title the report ${input.title}`,
390
+ // The title sits on the document, thus no container changes its child order.
391
+ execute: async (input, ctx) => {
392
+ const opened = await openThread(ctx);
393
+ if (opened.isErr()) {
394
+ return ok(refuse(opened.error));
395
+ }
396
+ const { threadId, state, token } = opened.value;
397
+ return ok(await land(threadId, token, state.document, ok(setTitle(state.document, input.title)), () => []));
398
+ },
399
+ });
400
+ const read_outline = defineTool({
401
+ id: "read_outline",
402
+ description: "Read the outline of the draft. Each entry gives the id, the kind, the nesting depth, and a short label. " +
403
+ "A label that ends with the character … is clipped. Use the outline as the primary view of the draft, " +
404
+ "and read one block when the label is not enough.",
405
+ inputSchema: readOutlineInput,
406
+ executionMode: AUTHORING_EXECUTION_MODE,
407
+ // The input carries no field, thus a hook can only restate the name of the tool.
408
+ describeCall: "none",
409
+ execute: async (_input, ctx) => {
410
+ const opened = await openThread(ctx);
411
+ if (opened.isErr()) {
412
+ return ok({ refused: opened.error });
413
+ }
414
+ return ok({ outline: buildOutline(opened.value.state.document) });
415
+ },
416
+ });
417
+ const read_block = defineTool({
418
+ id: "read_block",
419
+ description: "Read one block by its id. An atom gives the full block with its bindings. A section gives its title and the id of each child, " +
420
+ "because the outline already names every block under it. Use it when the outline label is not enough.",
421
+ inputSchema: readBlockInput,
422
+ executionMode: AUTHORING_EXECUTION_MODE,
423
+ describeCall: (input) => `read ${input.targetId}`,
424
+ execute: async (input, ctx) => {
425
+ const opened = await openThread(ctx);
426
+ if (opened.isErr()) {
427
+ return ok({ refused: opened.error });
428
+ }
429
+ const block = readBlock(opened.value.state.document, input.targetId);
430
+ const result = block !== undefined ? { found: true, block } : { found: false };
431
+ return ok(result);
432
+ },
433
+ });
434
+ const finish_draft = defineTool({
435
+ id: "finish_draft",
436
+ description: "Finish the draft. The result gives each completeness gap, or the valid report document, and each advisory warning. " +
437
+ "Use it to make sure that the draft passes the whole document schema, the id rule, and the structural tier.",
438
+ inputSchema: finishDraftInput,
439
+ executionMode: AUTHORING_EXECUTION_MODE,
440
+ // The input carries no field, thus a hook can only restate the name of the tool.
441
+ describeCall: "none",
442
+ execute: async (_input, ctx) => {
443
+ const opened = await openThread(ctx);
444
+ if (opened.isErr()) {
445
+ return ok({ refused: opened.error });
446
+ }
447
+ return ok(finishDraft(opened.value.state.document, opened.value.state.snapshot));
448
+ },
449
+ });
450
+ return {
451
+ add_block,
452
+ change_block,
453
+ remove_block,
454
+ move_block,
455
+ set_title,
456
+ read_outline,
457
+ read_block,
458
+ finish_draft,
459
+ };
460
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The report-session tool barrel.
3
+ *
4
+ * These tools serve a `report` thread. The runtime constructs them over the session-state gateway and the
5
+ * render seams, thus each tool binds the per-session state to the thread of the call. The barrel exports
6
+ * the factories and their public types for the composition root.
7
+ */
8
+ export { createPreviewReportTool, type PreviewReportInput, type PreviewReportResult, type PreviewReportToolDeps } from "./preview-report.js";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * The report-session tool barrel.
3
+ *
4
+ * These tools serve a `report` thread. The runtime constructs them over the session-state gateway and the
5
+ * render seams, thus each tool binds the per-session state to the thread of the call. The barrel exports
6
+ * the factories and their public types for the composition root.
7
+ */
8
+ export { createPreviewReportTool } from "./preview-report.js";
@@ -0,0 +1,90 @@
1
+ /**
2
+ * The render-and-preview tool of a report session.
3
+ *
4
+ * The tool renders the current draft of a thread. It runs the finish first, because a mid-composition
5
+ * draft is not a `ReportDocument`. A gap list returns as data, and no render runs. On a pass the tool
6
+ * resolves each reference through the injected resolver, bridges the resolved values into the render model,
7
+ * and renders the page.
8
+ *
9
+ * The page and its staged image assets land in `report-sessions/{threadId}/` under the workspace root. That
10
+ * namespace belongs to this path alone, thus the tool never writes under the old `previews/` or `reports/`
11
+ * trees. The result carries the absolute page path, thus a local host shows the page with no seam.
12
+ *
13
+ * The result carries no access grant. `PreviewPublisher` authorizes the URL space
14
+ * `previews/{analysisId}/{previewId}` (`contracts/content-url.ts`), and that space cannot name a page of
15
+ * this tree. A hosted view of a session page is a later capability, with a URL space of its own.
16
+ *
17
+ * Each degraded condition is a typed outcome in the ok channel: a session refusal, a gap list, a resolver
18
+ * absence, an unresolved reference, a bridge mismatch, a render problem, a figure that escapes the
19
+ * workspace root, and a write failure. The tool never throws for one of them. The filesystem speaks the
20
+ * throw protocol, and the workspace-root seam signals an unresolvable resource the same way. Thus the
21
+ * write runs through the `tryFs` glue, which turns a genuine fault into the ok-channel outcome and lets a
22
+ * control-flow exception propagate.
23
+ */
24
+ import { z } from "zod";
25
+ import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
26
+ import { type Logger } from "../../lib/logger.js";
27
+ import { type FinishGap } from "../../report-model/draft-finish.js";
28
+ import type { ReferenceResolver } from "../../report-model/reference-resolver.js";
29
+ import { type ResolutionFailure } from "../../report-model/validate.js";
30
+ import { type BridgeMismatch } from "../../report-render/value-bridge.js";
31
+ import type { RenderProblem } from "../../report-render/types.js";
32
+ import { type Tool } from "../define-tool.js";
33
+ import { type ReportSessionStateGateway, type SessionRefusal } from "../report-authoring/authoring-tools.js";
34
+ /** The empty input. The tool renders the current draft of the thread, thus it needs no field. */
35
+ declare const previewReportInput: z.ZodObject<{}, z.core.$strip>;
36
+ export type PreviewReportInput = z.infer<typeof previewReportInput>;
37
+ /**
38
+ * The typed outcome of the preview tool. Each arm is ok-channel data, thus the tool never throws for a
39
+ * degraded condition. `rendered` carries the absolute page path.
40
+ */
41
+ export type PreviewReportResult = {
42
+ outcome: "refused";
43
+ refusal: SessionRefusal;
44
+ } | {
45
+ outcome: "gaps";
46
+ gaps: FinishGap[];
47
+ } | {
48
+ outcome: "resolver-unavailable";
49
+ } | {
50
+ outcome: "unresolved-references";
51
+ unresolved: ResolutionFailure[];
52
+ } | {
53
+ outcome: "bridge-mismatch";
54
+ mismatches: BridgeMismatch[];
55
+ } | {
56
+ outcome: "render-problems";
57
+ problems: RenderProblem[];
58
+ } | {
59
+ outcome: "figure-out-of-scope";
60
+ blockId: string;
61
+ path: string;
62
+ } | {
63
+ outcome: "write-failed";
64
+ detail: string;
65
+ } | {
66
+ outcome: "rendered";
67
+ pagePath: string;
68
+ };
69
+ /**
70
+ * The construction deps of the preview tool.
71
+ *
72
+ * `resolveWorkspaceRoot` maps the analysis of the call onto its workspace root, thus one singleton tool
73
+ * serves every analysis and it resolves the root per call from the scope. `resolver` is optional, because a
74
+ * resolver realization can be absent.
75
+ */
76
+ export interface PreviewReportToolDeps {
77
+ readonly gateway: ReportSessionStateGateway;
78
+ readonly resolver?: ReferenceResolver;
79
+ readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
80
+ readonly logger?: Logger;
81
+ }
82
+ /**
83
+ * Make the render-and-preview tool over the session-state gateway and the render seams.
84
+ *
85
+ * The tool reads the thread id from the scope of the call, and it loads the state through the gateway. Thus
86
+ * one factory serves every thread. The tool holds no per-session value, and it resolves the root of the
87
+ * analysis of the call.
88
+ */
89
+ export declare function createPreviewReportTool(deps: PreviewReportToolDeps): Tool<PreviewReportInput, PreviewReportResult>;
90
+ export {};