agent-bober 0.17.1 → 0.18.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 (355) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +158 -5
  4. package/dist/chat/answerer.d.ts +17 -0
  5. package/dist/chat/answerer.d.ts.map +1 -0
  6. package/dist/chat/answerer.js +52 -0
  7. package/dist/chat/answerer.js.map +1 -0
  8. package/dist/chat/approval-cursor.d.ts +25 -0
  9. package/dist/chat/approval-cursor.d.ts.map +1 -0
  10. package/dist/chat/approval-cursor.js +61 -0
  11. package/dist/chat/approval-cursor.js.map +1 -0
  12. package/dist/chat/approval-reader.d.ts +11 -0
  13. package/dist/chat/approval-reader.d.ts.map +1 -0
  14. package/dist/chat/approval-reader.js +20 -0
  15. package/dist/chat/approval-reader.js.map +1 -0
  16. package/dist/chat/careful-sidecar.d.ts +15 -0
  17. package/dist/chat/careful-sidecar.d.ts.map +1 -0
  18. package/dist/chat/careful-sidecar.js +39 -0
  19. package/dist/chat/careful-sidecar.js.map +1 -0
  20. package/dist/chat/chat-session.d.ts +129 -0
  21. package/dist/chat/chat-session.d.ts.map +1 -0
  22. package/dist/chat/chat-session.js +470 -0
  23. package/dist/chat/chat-session.js.map +1 -0
  24. package/dist/chat/completion-tailer.d.ts +27 -0
  25. package/dist/chat/completion-tailer.d.ts.map +1 -0
  26. package/dist/chat/completion-tailer.js +187 -0
  27. package/dist/chat/completion-tailer.js.map +1 -0
  28. package/dist/chat/conversation-store.d.ts +22 -0
  29. package/dist/chat/conversation-store.d.ts.map +1 -0
  30. package/dist/chat/conversation-store.js +66 -0
  31. package/dist/chat/conversation-store.js.map +1 -0
  32. package/dist/chat/cursor-store.d.ts +23 -0
  33. package/dist/chat/cursor-store.d.ts.map +1 -0
  34. package/dist/chat/cursor-store.js +42 -0
  35. package/dist/chat/cursor-store.js.map +1 -0
  36. package/dist/chat/pid-sidecar.d.ts +22 -0
  37. package/dist/chat/pid-sidecar.d.ts.map +1 -0
  38. package/dist/chat/pid-sidecar.js +42 -0
  39. package/dist/chat/pid-sidecar.js.map +1 -0
  40. package/dist/chat/roster-reader.d.ts +16 -0
  41. package/dist/chat/roster-reader.d.ts.map +1 -0
  42. package/dist/chat/roster-reader.js +39 -0
  43. package/dist/chat/roster-reader.js.map +1 -0
  44. package/dist/chat/run-spawner.d.ts +71 -0
  45. package/dist/chat/run-spawner.d.ts.map +1 -0
  46. package/dist/chat/run-spawner.js +121 -0
  47. package/dist/chat/run-spawner.js.map +1 -0
  48. package/dist/chat/slash-commands.d.ts +36 -0
  49. package/dist/chat/slash-commands.d.ts.map +1 -0
  50. package/dist/chat/slash-commands.js +133 -0
  51. package/dist/chat/slash-commands.js.map +1 -0
  52. package/dist/chat/steer-cleanup.d.ts +12 -0
  53. package/dist/chat/steer-cleanup.d.ts.map +1 -0
  54. package/dist/chat/steer-cleanup.js +60 -0
  55. package/dist/chat/steer-cleanup.js.map +1 -0
  56. package/dist/chat/turn-classifier.d.ts +43 -0
  57. package/dist/chat/turn-classifier.d.ts.map +1 -0
  58. package/dist/chat/turn-classifier.js +150 -0
  59. package/dist/chat/turn-classifier.js.map +1 -0
  60. package/dist/cli/commands/blackboard.d.ts +33 -0
  61. package/dist/cli/commands/blackboard.d.ts.map +1 -0
  62. package/dist/cli/commands/blackboard.js +114 -0
  63. package/dist/cli/commands/blackboard.js.map +1 -0
  64. package/dist/cli/commands/chat.d.ts +13 -0
  65. package/dist/cli/commands/chat.d.ts.map +1 -0
  66. package/dist/cli/commands/chat.js +43 -0
  67. package/dist/cli/commands/chat.js.map +1 -0
  68. package/dist/cli/commands/evolve.d.ts +18 -0
  69. package/dist/cli/commands/evolve.d.ts.map +1 -0
  70. package/dist/cli/commands/evolve.js +72 -0
  71. package/dist/cli/commands/evolve.js.map +1 -0
  72. package/dist/cli/commands/facts.d.ts +15 -0
  73. package/dist/cli/commands/facts.d.ts.map +1 -0
  74. package/dist/cli/commands/facts.js +213 -0
  75. package/dist/cli/commands/facts.js.map +1 -0
  76. package/dist/cli/commands/medical.d.ts +24 -0
  77. package/dist/cli/commands/medical.d.ts.map +1 -0
  78. package/dist/cli/commands/medical.js +132 -0
  79. package/dist/cli/commands/medical.js.map +1 -0
  80. package/dist/cli/commands/memory.d.ts +2 -1
  81. package/dist/cli/commands/memory.d.ts.map +1 -1
  82. package/dist/cli/commands/memory.js +94 -7
  83. package/dist/cli/commands/memory.js.map +1 -1
  84. package/dist/cli/commands/plan.d.ts.map +1 -1
  85. package/dist/cli/commands/plan.js +29 -3
  86. package/dist/cli/commands/plan.js.map +1 -1
  87. package/dist/cli/commands/replay.d.ts +17 -0
  88. package/dist/cli/commands/replay.d.ts.map +1 -0
  89. package/dist/cli/commands/replay.js +261 -0
  90. package/dist/cli/commands/replay.js.map +1 -0
  91. package/dist/cli/commands/run.d.ts +9 -0
  92. package/dist/cli/commands/run.d.ts.map +1 -1
  93. package/dist/cli/commands/run.js +27 -1
  94. package/dist/cli/commands/run.js.map +1 -1
  95. package/dist/cli/commands/sprint.d.ts.map +1 -1
  96. package/dist/cli/commands/sprint.js +17 -3
  97. package/dist/cli/commands/sprint.js.map +1 -1
  98. package/dist/cli/index.js +12 -0
  99. package/dist/cli/index.js.map +1 -1
  100. package/dist/config/defaults.d.ts.map +1 -1
  101. package/dist/config/defaults.js +8 -0
  102. package/dist/config/defaults.js.map +1 -1
  103. package/dist/config/role-providers.d.ts +1 -1
  104. package/dist/config/role-providers.d.ts.map +1 -1
  105. package/dist/config/role-providers.js +6 -1
  106. package/dist/config/role-providers.js.map +1 -1
  107. package/dist/config/schema.d.ts +467 -222
  108. package/dist/config/schema.d.ts.map +1 -1
  109. package/dist/config/schema.js +25 -0
  110. package/dist/config/schema.js.map +1 -1
  111. package/dist/contracts/eval-result.d.ts +84 -84
  112. package/dist/contracts/spec.d.ts +28 -28
  113. package/dist/contracts/sprint-contract.d.ts +4 -4
  114. package/dist/fleet/critic-deep.d.ts +53 -0
  115. package/dist/fleet/critic-deep.d.ts.map +1 -0
  116. package/dist/fleet/critic-deep.js +199 -0
  117. package/dist/fleet/critic-deep.js.map +1 -0
  118. package/dist/fleet/decomposer-deep-constants.d.ts +4 -0
  119. package/dist/fleet/decomposer-deep-constants.d.ts.map +1 -0
  120. package/dist/fleet/decomposer-deep-constants.js +14 -0
  121. package/dist/fleet/decomposer-deep-constants.js.map +1 -0
  122. package/dist/fleet/decomposer-deep-types.d.ts +8 -0
  123. package/dist/fleet/decomposer-deep-types.d.ts.map +1 -0
  124. package/dist/fleet/decomposer-deep-types.js +9 -0
  125. package/dist/fleet/decomposer-deep-types.js.map +1 -0
  126. package/dist/fleet/decomposer-deep.d.ts +51 -0
  127. package/dist/fleet/decomposer-deep.d.ts.map +1 -0
  128. package/dist/fleet/decomposer-deep.js +261 -0
  129. package/dist/fleet/decomposer-deep.js.map +1 -0
  130. package/dist/fleet/decomposer.d.ts +22 -0
  131. package/dist/fleet/decomposer.d.ts.map +1 -0
  132. package/dist/fleet/decomposer.js +144 -0
  133. package/dist/fleet/decomposer.js.map +1 -0
  134. package/dist/fleet/index.d.ts +93 -0
  135. package/dist/fleet/index.d.ts.map +1 -1
  136. package/dist/fleet/index.js +230 -2
  137. package/dist/fleet/index.js.map +1 -1
  138. package/dist/fleet/manifest-write.d.ts +30 -0
  139. package/dist/fleet/manifest-write.d.ts.map +1 -0
  140. package/dist/fleet/manifest-write.js +90 -0
  141. package/dist/fleet/manifest-write.js.map +1 -0
  142. package/dist/fleet/manifest.d.ts +6 -6
  143. package/dist/fleet/shared-blackboard.d.ts +51 -0
  144. package/dist/fleet/shared-blackboard.d.ts.map +1 -0
  145. package/dist/fleet/shared-blackboard.js +79 -0
  146. package/dist/fleet/shared-blackboard.js.map +1 -0
  147. package/dist/fleet/synthesis.d.ts +16 -0
  148. package/dist/fleet/synthesis.d.ts.map +1 -0
  149. package/dist/fleet/synthesis.js +22 -0
  150. package/dist/fleet/synthesis.js.map +1 -0
  151. package/dist/fleet/tier-policy.d.ts +18 -0
  152. package/dist/fleet/tier-policy.d.ts.map +1 -0
  153. package/dist/fleet/tier-policy.js +53 -0
  154. package/dist/fleet/tier-policy.js.map +1 -0
  155. package/dist/fleet/tool-role-guard.d.ts +24 -0
  156. package/dist/fleet/tool-role-guard.d.ts.map +1 -0
  157. package/dist/fleet/tool-role-guard.js +55 -0
  158. package/dist/fleet/tool-role-guard.js.map +1 -0
  159. package/dist/incident/resolution-verify.d.ts +4 -4
  160. package/dist/incident/types.d.ts +8 -8
  161. package/dist/mcp/run-manager.d.ts +9 -1
  162. package/dist/mcp/run-manager.d.ts.map +1 -1
  163. package/dist/mcp/run-manager.js.map +1 -1
  164. package/dist/medical/adapters/apple-health.d.ts +32 -0
  165. package/dist/medical/adapters/apple-health.d.ts.map +1 -0
  166. package/dist/medical/adapters/apple-health.js +100 -0
  167. package/dist/medical/adapters/apple-health.js.map +1 -0
  168. package/dist/medical/audit.d.ts +31 -0
  169. package/dist/medical/audit.d.ts.map +1 -0
  170. package/dist/medical/audit.js +56 -0
  171. package/dist/medical/audit.js.map +1 -0
  172. package/dist/medical/consent.d.ts +37 -0
  173. package/dist/medical/consent.d.ts.map +1 -0
  174. package/dist/medical/consent.js +73 -0
  175. package/dist/medical/consent.js.map +1 -0
  176. package/dist/medical/disclaimer.d.ts +11 -0
  177. package/dist/medical/disclaimer.d.ts.map +1 -0
  178. package/dist/medical/disclaimer.js +21 -0
  179. package/dist/medical/disclaimer.js.map +1 -0
  180. package/dist/medical/egress.d.ts +30 -0
  181. package/dist/medical/egress.d.ts.map +1 -0
  182. package/dist/medical/egress.js +50 -0
  183. package/dist/medical/egress.js.map +1 -0
  184. package/dist/medical/engine.d.ts +73 -0
  185. package/dist/medical/engine.d.ts.map +1 -0
  186. package/dist/medical/engine.js +333 -0
  187. package/dist/medical/engine.js.map +1 -0
  188. package/dist/medical/guardrails.d.ts +47 -0
  189. package/dist/medical/guardrails.d.ts.map +1 -0
  190. package/dist/medical/guardrails.js +92 -0
  191. package/dist/medical/guardrails.js.map +1 -0
  192. package/dist/medical/health-store.d.ts +80 -0
  193. package/dist/medical/health-store.d.ts.map +1 -0
  194. package/dist/medical/health-store.js +204 -0
  195. package/dist/medical/health-store.js.map +1 -0
  196. package/dist/medical/inference.d.ts +22 -0
  197. package/dist/medical/inference.d.ts.map +1 -0
  198. package/dist/medical/inference.js +39 -0
  199. package/dist/medical/inference.js.map +1 -0
  200. package/dist/medical/ingestion.d.ts +33 -0
  201. package/dist/medical/ingestion.d.ts.map +1 -0
  202. package/dist/medical/ingestion.js +52 -0
  203. package/dist/medical/ingestion.js.map +1 -0
  204. package/dist/medical/numerics.d.ts +66 -0
  205. package/dist/medical/numerics.d.ts.map +1 -0
  206. package/dist/medical/numerics.js +216 -0
  207. package/dist/medical/numerics.js.map +1 -0
  208. package/dist/medical/red-flag.d.ts +42 -0
  209. package/dist/medical/red-flag.d.ts.map +1 -0
  210. package/dist/medical/red-flag.js +161 -0
  211. package/dist/medical/red-flag.js.map +1 -0
  212. package/dist/medical/refusal.d.ts +46 -0
  213. package/dist/medical/refusal.d.ts.map +1 -0
  214. package/dist/medical/refusal.js +131 -0
  215. package/dist/medical/refusal.js.map +1 -0
  216. package/dist/medical/retrieval/grounding-critic.d.ts +34 -0
  217. package/dist/medical/retrieval/grounding-critic.d.ts.map +1 -0
  218. package/dist/medical/retrieval/grounding-critic.js +153 -0
  219. package/dist/medical/retrieval/grounding-critic.js.map +1 -0
  220. package/dist/medical/retrieval/literature.d.ts +78 -0
  221. package/dist/medical/retrieval/literature.d.ts.map +1 -0
  222. package/dist/medical/retrieval/literature.js +292 -0
  223. package/dist/medical/retrieval/literature.js.map +1 -0
  224. package/dist/medical/retrieval/medline-source.d.ts +72 -0
  225. package/dist/medical/retrieval/medline-source.d.ts.map +1 -0
  226. package/dist/medical/retrieval/medline-source.js +120 -0
  227. package/dist/medical/retrieval/medline-source.js.map +1 -0
  228. package/dist/medical/team.d.ts +15 -0
  229. package/dist/medical/team.d.ts.map +1 -0
  230. package/dist/medical/team.js +44 -0
  231. package/dist/medical/team.js.map +1 -0
  232. package/dist/medical/types.d.ts +175 -0
  233. package/dist/medical/types.d.ts.map +1 -0
  234. package/dist/medical/types.js +8 -0
  235. package/dist/medical/types.js.map +1 -0
  236. package/dist/medical/whoop/whoop-client.d.ts +114 -0
  237. package/dist/medical/whoop/whoop-client.d.ts.map +1 -0
  238. package/dist/medical/whoop/whoop-client.js +226 -0
  239. package/dist/medical/whoop/whoop-client.js.map +1 -0
  240. package/dist/medical/whoop/whoop-sync.d.ts +25 -0
  241. package/dist/medical/whoop/whoop-sync.d.ts.map +1 -0
  242. package/dist/medical/whoop/whoop-sync.js +97 -0
  243. package/dist/medical/whoop/whoop-sync.js.map +1 -0
  244. package/dist/medical/whoop/whoop-token.d.ts +44 -0
  245. package/dist/medical/whoop/whoop-token.d.ts.map +1 -0
  246. package/dist/medical/whoop/whoop-token.js +66 -0
  247. package/dist/medical/whoop/whoop-token.js.map +1 -0
  248. package/dist/orchestrator/context-handoff.d.ts +60 -60
  249. package/dist/orchestrator/contract-materialization.d.ts +33 -0
  250. package/dist/orchestrator/contract-materialization.d.ts.map +1 -0
  251. package/dist/orchestrator/contract-materialization.js +113 -0
  252. package/dist/orchestrator/contract-materialization.js.map +1 -0
  253. package/dist/orchestrator/memory/distill.d.ts +4 -1
  254. package/dist/orchestrator/memory/distill.d.ts.map +1 -1
  255. package/dist/orchestrator/memory/distill.js +41 -1
  256. package/dist/orchestrator/memory/distill.js.map +1 -1
  257. package/dist/orchestrator/memory/fact-detector.d.ts +61 -0
  258. package/dist/orchestrator/memory/fact-detector.d.ts.map +1 -0
  259. package/dist/orchestrator/memory/fact-detector.js +153 -0
  260. package/dist/orchestrator/memory/fact-detector.js.map +1 -0
  261. package/dist/orchestrator/memory/fact-judge.d.ts +19 -0
  262. package/dist/orchestrator/memory/fact-judge.d.ts.map +1 -0
  263. package/dist/orchestrator/memory/fact-judge.js +121 -0
  264. package/dist/orchestrator/memory/fact-judge.js.map +1 -0
  265. package/dist/orchestrator/memory/fact-retrieve.d.ts +52 -0
  266. package/dist/orchestrator/memory/fact-retrieve.d.ts.map +1 -0
  267. package/dist/orchestrator/memory/fact-retrieve.js +117 -0
  268. package/dist/orchestrator/memory/fact-retrieve.js.map +1 -0
  269. package/dist/orchestrator/memory/hygiene.d.ts +66 -0
  270. package/dist/orchestrator/memory/hygiene.d.ts.map +1 -0
  271. package/dist/orchestrator/memory/hygiene.js +153 -0
  272. package/dist/orchestrator/memory/hygiene.js.map +1 -0
  273. package/dist/orchestrator/memory/reconcile.d.ts +47 -0
  274. package/dist/orchestrator/memory/reconcile.d.ts.map +1 -0
  275. package/dist/orchestrator/memory/reconcile.js +116 -0
  276. package/dist/orchestrator/memory/reconcile.js.map +1 -0
  277. package/dist/orchestrator/memory/retrieve.d.ts +5 -3
  278. package/dist/orchestrator/memory/retrieve.d.ts.map +1 -1
  279. package/dist/orchestrator/memory/retrieve.js +9 -6
  280. package/dist/orchestrator/memory/retrieve.js.map +1 -1
  281. package/dist/orchestrator/pipeline.d.ts +19 -3
  282. package/dist/orchestrator/pipeline.d.ts.map +1 -1
  283. package/dist/orchestrator/pipeline.js +63 -52
  284. package/dist/orchestrator/pipeline.js.map +1 -1
  285. package/dist/orchestrator/planner-agent.d.ts.map +1 -1
  286. package/dist/orchestrator/planner-agent.js +16 -1
  287. package/dist/orchestrator/planner-agent.js.map +1 -1
  288. package/dist/orchestrator/selfimprove/eval-guards.d.ts +51 -0
  289. package/dist/orchestrator/selfimprove/eval-guards.d.ts.map +1 -0
  290. package/dist/orchestrator/selfimprove/eval-guards.js +120 -0
  291. package/dist/orchestrator/selfimprove/eval-guards.js.map +1 -0
  292. package/dist/orchestrator/selfimprove/gepa.d.ts +74 -0
  293. package/dist/orchestrator/selfimprove/gepa.d.ts.map +1 -0
  294. package/dist/orchestrator/selfimprove/gepa.js +227 -0
  295. package/dist/orchestrator/selfimprove/gepa.js.map +1 -0
  296. package/dist/orchestrator/selfimprove/replay-harness.d.ts +65 -0
  297. package/dist/orchestrator/selfimprove/replay-harness.d.ts.map +1 -0
  298. package/dist/orchestrator/selfimprove/replay-harness.js +134 -0
  299. package/dist/orchestrator/selfimprove/replay-harness.js.map +1 -0
  300. package/dist/orchestrator/selfimprove/replay-store.d.ts +45 -0
  301. package/dist/orchestrator/selfimprove/replay-store.d.ts.map +1 -0
  302. package/dist/orchestrator/selfimprove/replay-store.js +119 -0
  303. package/dist/orchestrator/selfimprove/replay-store.js.map +1 -0
  304. package/dist/orchestrator/selfimprove/replay-types.d.ts +32 -0
  305. package/dist/orchestrator/selfimprove/replay-types.d.ts.map +1 -0
  306. package/dist/orchestrator/selfimprove/replay-types.js +15 -0
  307. package/dist/orchestrator/selfimprove/replay-types.js.map +1 -0
  308. package/dist/orchestrator/workflow/engine.d.ts +3 -1
  309. package/dist/orchestrator/workflow/engine.d.ts.map +1 -1
  310. package/dist/orchestrator/workflow/selector.d.ts +18 -0
  311. package/dist/orchestrator/workflow/selector.d.ts.map +1 -1
  312. package/dist/orchestrator/workflow/selector.js +41 -0
  313. package/dist/orchestrator/workflow/selector.js.map +1 -1
  314. package/dist/orchestrator/workflow/ts-engine.d.ts +3 -1
  315. package/dist/orchestrator/workflow/ts-engine.d.ts.map +1 -1
  316. package/dist/orchestrator/workflow/ts-engine.js +2 -2
  317. package/dist/orchestrator/workflow/ts-engine.js.map +1 -1
  318. package/dist/orchestrator/workflow/workflow-engine.d.ts +3 -1
  319. package/dist/orchestrator/workflow/workflow-engine.d.ts.map +1 -1
  320. package/dist/orchestrator/workflow/workflow-engine.js +3 -3
  321. package/dist/orchestrator/workflow/workflow-engine.js.map +1 -1
  322. package/dist/state/facts.d.ts +110 -0
  323. package/dist/state/facts.d.ts.map +1 -0
  324. package/dist/state/facts.js +208 -0
  325. package/dist/state/facts.js.map +1 -0
  326. package/dist/state/guidance.d.ts +37 -0
  327. package/dist/state/guidance.d.ts.map +1 -0
  328. package/dist/state/guidance.js +144 -0
  329. package/dist/state/guidance.js.map +1 -0
  330. package/dist/state/history.d.ts +2 -2
  331. package/dist/state/index.d.ts +1 -1
  332. package/dist/state/index.d.ts.map +1 -1
  333. package/dist/state/index.js +1 -1
  334. package/dist/state/index.js.map +1 -1
  335. package/dist/state/memory.d.ts +43 -11
  336. package/dist/state/memory.d.ts.map +1 -1
  337. package/dist/state/memory.js +93 -15
  338. package/dist/state/memory.js.map +1 -1
  339. package/dist/state/pause.d.ts +47 -0
  340. package/dist/state/pause.d.ts.map +1 -0
  341. package/dist/state/pause.js +146 -0
  342. package/dist/state/pause.js.map +1 -0
  343. package/dist/state/sprint-state.d.ts +8 -0
  344. package/dist/state/sprint-state.d.ts.map +1 -1
  345. package/dist/state/sprint-state.js +32 -1
  346. package/dist/state/sprint-state.js.map +1 -1
  347. package/dist/teams/registry.d.ts +16 -0
  348. package/dist/teams/registry.d.ts.map +1 -0
  349. package/dist/teams/registry.js +53 -0
  350. package/dist/teams/registry.js.map +1 -0
  351. package/dist/teams/types.d.ts +25 -0
  352. package/dist/teams/types.d.ts.map +1 -0
  353. package/dist/teams/types.js +2 -0
  354. package/dist/teams/types.js.map +1 -0
  355. package/package.json +3 -1
@@ -0,0 +1,292 @@
1
+ import { MedlineSource } from "./medline-source.js";
2
+ import { getGroundingVerdict, GROUNDING_MAX_LLM_CALLS } from "./grounding-critic.js";
3
+ // ── LiteratureRetriever ──────────────────────────────────────────────
4
+ /**
5
+ * Orchestrates literature retrieval behind the literature-retrieval egress gate.
6
+ *
7
+ * SYNCHRONOUS short-circuit: if the axis is off, return {disabled} — NO network
8
+ * attempt and NO MedlineSource method is called. This is the zero-egress proof.
9
+ */
10
+ export class LiteratureRetriever {
11
+ egress;
12
+ source;
13
+ constructor(egress, source = new MedlineSource(egress)) {
14
+ this.egress = egress;
15
+ this.source = source;
16
+ }
17
+ /**
18
+ * Retrieve literature passages for the given query.
19
+ *
20
+ * When literature-retrieval is OFF: returns { kind: "disabled" } IMMEDIATELY,
21
+ * before MedlineSource is consulted — guarantees zero outbound bytes.
22
+ *
23
+ * When literature-retrieval is ON: delegates to MedlineSource.fetchPassages.
24
+ * Any throw from the source is caught and maps to abstain{source-error} (belt-and-braces;
25
+ * MedlineSource.fetchPassages itself is already fail-closed).
26
+ */
27
+ async retrieve(query) {
28
+ // isAllowed check MUST precede source call — proves zero-egress when axis is off.
29
+ if (!this.egress.isAllowed("literature-retrieval")) {
30
+ return { kind: "disabled" };
31
+ }
32
+ try {
33
+ return await this.source.fetchPassages(query);
34
+ }
35
+ catch {
36
+ return { kind: "abstain", reason: "source-error" };
37
+ }
38
+ }
39
+ }
40
+ // ── synthesize ───────────────────────────────────────────────────────
41
+ const SYNTHESIS_MODEL = "ollama/llama3";
42
+ const SYNTHESIS_MAX_TOKENS = 512;
43
+ /**
44
+ * Build the system prompt that pins the model to the retrieved passages.
45
+ * The model is instructed to reply with exactly "ABSTAIN" if the passages
46
+ * do not support the query — synthesize treats that as an abstain signal.
47
+ */
48
+ function buildSynthesisSystem(passages) {
49
+ const passageBlock = passages
50
+ .map((p, i) => `[${i + 1}] ${p.title}\n${p.text}\nSource: ${p.url}`)
51
+ .join("\n\n");
52
+ return ("You are a medical information assistant. Answer the user's question using ONLY " +
53
+ "the following retrieved passages from MedlinePlus. " +
54
+ "If the passages do not contain information that supports a specific answer, " +
55
+ "reply with exactly the single word: ABSTAIN\n\n" +
56
+ "Retrieved passages:\n" +
57
+ passageBlock);
58
+ }
59
+ /**
60
+ * Derive citations from grounded passages that were actually used in synthesis.
61
+ * For simplicity (all passages are pinned in the system prompt), all passages
62
+ * become citations when the model produces a non-abstained answer.
63
+ * This ensures citations.length >= 1 for every non-abstained answer.
64
+ */
65
+ function passagesToCitations(passages) {
66
+ return passages.map((p) => ({
67
+ title: p.title,
68
+ url: p.url,
69
+ source: "medlineplus",
70
+ }));
71
+ }
72
+ /**
73
+ * synthesize — single LLM call that grounds the answer in retrieved passages.
74
+ *
75
+ * Rules (non-negotiable, enforced here):
76
+ * - disabled/abstain outcome => abstained MedicalAnswer, citations: [], no clinical assertion.
77
+ * - grounded outcome => one llm.chat call; if the model abstains or returns empty => abstained.
78
+ * - Every non-abstained answer carries >= 1 citation derived from the passages.
79
+ * - LLM error (e.g. Ollama unreachable) => abstained + "model unavailable" footer, NO cloud fallback.
80
+ * - Never emits an uncited clinical claim.
81
+ *
82
+ * @param query - The original user question.
83
+ * @param outcome - The RetrievalOutcome from LiteratureRetriever.retrieve.
84
+ * @param llm - Injectable LLMClient (tests pass {chat: vi.fn()}).
85
+ * @param footer - DisclaimerComposer footer string included in every answer.
86
+ * @param model - Model identifier to use for synthesis (default: SYNTHESIS_MODEL).
87
+ */
88
+ export async function synthesize(query, outcome, llm, footer, model = SYNTHESIS_MODEL) {
89
+ // ── Abstain immediately on disabled / no-passages ────────────────
90
+ if (outcome.kind === "disabled" || outcome.kind === "abstain") {
91
+ return {
92
+ body: "I cannot ground an answer in retrieved literature. " +
93
+ "For evidence-based guidance, please consult a licensed healthcare professional.",
94
+ abstained: true,
95
+ citations: [],
96
+ disclaimerFooter: footer,
97
+ shortCircuit: false,
98
+ };
99
+ }
100
+ // outcome.kind === "grounded"
101
+ const passages = outcome.passages;
102
+ if (passages.length === 0) {
103
+ return {
104
+ body: "No supporting passages were retrieved. " +
105
+ "For evidence-based guidance, please consult a licensed healthcare professional.",
106
+ abstained: true,
107
+ citations: [],
108
+ disclaimerFooter: footer,
109
+ shortCircuit: false,
110
+ };
111
+ }
112
+ // ── Single LLM call ──────────────────────────────────────────────
113
+ let responseText;
114
+ try {
115
+ const response = await llm.chat({
116
+ model,
117
+ system: buildSynthesisSystem(passages),
118
+ messages: [{ role: "user", content: query }],
119
+ maxTokens: SYNTHESIS_MAX_TOKENS,
120
+ });
121
+ responseText = response.text.trim();
122
+ }
123
+ catch {
124
+ // Local model unavailable — abstain, NO cloud fallback.
125
+ return {
126
+ body: "I cannot ground an answer at this time (model unavailable). " +
127
+ "For evidence-based guidance, please consult a licensed healthcare professional.",
128
+ abstained: true,
129
+ citations: [],
130
+ disclaimerFooter: footer,
131
+ shortCircuit: false,
132
+ };
133
+ }
134
+ // ── Abstain if the model said so or returned empty ───────────────
135
+ if (!responseText || responseText.toUpperCase() === "ABSTAIN") {
136
+ return {
137
+ body: "The retrieved passages do not sufficiently support a specific answer to your question. " +
138
+ "For evidence-based guidance, please consult a licensed healthcare professional.",
139
+ abstained: true,
140
+ citations: [],
141
+ disclaimerFooter: footer,
142
+ shortCircuit: false,
143
+ };
144
+ }
145
+ // ── Non-abstained: attach citations (>= 1 guaranteed by passages.length > 0) ─
146
+ const citations = passagesToCitations(passages);
147
+ return {
148
+ body: responseText,
149
+ abstained: false,
150
+ citations,
151
+ disclaimerFooter: footer,
152
+ shortCircuit: false,
153
+ };
154
+ }
155
+ // ── synthesizeGrounded ────────────────────────────────────────────────
156
+ /**
157
+ * Worst-case LLM call budget for the grounded gate:
158
+ * 1 synth + GROUNDING_MAX_LLM_CALLS critic + 1 re-synth + GROUNDING_MAX_LLM_CALLS re-critic
159
+ * Computed from the imported constant so it tracks future changes to the critic budget.
160
+ */
161
+ export const GROUNDED_GATE_MAX_LLM_CALLS = 1 + GROUNDING_MAX_LLM_CALLS + 1 + GROUNDING_MAX_LLM_CALLS; // = 6 today (1+2+1+2)
162
+ /**
163
+ * Canned abstain answer used throughout the gate when the answer cannot be
164
+ * sufficiently grounded in the retrieved literature.
165
+ */
166
+ function abstainAnswer(footer) {
167
+ return {
168
+ body: "I cannot provide a sufficiently-supported answer grounded in the retrieved literature. " +
169
+ "For evidence-based guidance, please consult a licensed healthcare professional.",
170
+ abstained: true,
171
+ citations: [],
172
+ disclaimerFooter: footer,
173
+ shortCircuit: false,
174
+ };
175
+ }
176
+ /**
177
+ * Re-synthesize with critic feedback appended to the system prompt.
178
+ * A near-copy of the grounded branch inside synthesize, with one extra instruction line.
179
+ * Module-private — only synthesizeGrounded calls this.
180
+ */
181
+ async function synthesizeWithFeedback(query, outcome, llm, footer, feedback, model = SYNTHESIS_MODEL) {
182
+ const passages = outcome.passages;
183
+ if (passages.length === 0)
184
+ return abstainAnswer(footer); // mirrors synthesize:120-130
185
+ const system = buildSynthesisSystem(passages) +
186
+ `\n\nAddress this reviewer feedback while staying grounded ONLY in the passages: ${feedback}`;
187
+ let responseText;
188
+ try {
189
+ const response = await llm.chat({
190
+ model,
191
+ system,
192
+ messages: [{ role: "user", content: query }],
193
+ maxTokens: SYNTHESIS_MAX_TOKENS,
194
+ });
195
+ responseText = response.text.trim();
196
+ }
197
+ catch {
198
+ return abstainAnswer(footer);
199
+ }
200
+ if (!responseText || responseText.toUpperCase() === "ABSTAIN")
201
+ return abstainAnswer(footer);
202
+ return {
203
+ body: responseText,
204
+ abstained: false,
205
+ citations: passagesToCitations(passages),
206
+ disclaimerFooter: footer,
207
+ shortCircuit: false,
208
+ };
209
+ }
210
+ /**
211
+ * synthesizeGrounded — fail-closed gate: synthesize → critic → one re-synth → abstain.
212
+ *
213
+ * For non-grounded outcomes, delegates to synthesize (disabled/abstain handled there).
214
+ * For grounded outcomes:
215
+ * 1. synthesize → if abstained, return it
216
+ * 2. getGroundingVerdict → if approve, return answer
217
+ * 3. synthesizeWithFeedback (one re-synth with critic feedback)
218
+ * 4. getGroundingVerdict again → if approve, return re-synth answer
219
+ * 5. else → return abstainAnswer (fail-closed)
220
+ *
221
+ * Any thrown error from synthesize or the critic maps to abstainAnswer (fail-closed).
222
+ * Bounded by GROUNDED_GATE_MAX_LLM_CALLS (= 6 worst case today).
223
+ *
224
+ * Returns { answer, verdict } where verdict is one of:
225
+ * 'approve' — gate returned an approved answer
226
+ * 'reject-abstained' — gate abstained after a critic reject (second reject)
227
+ * 'error-abstained' — gate abstained due to a thrown error or model unavailable
228
+ *
229
+ * @param model - Model identifier to use (default: SYNTHESIS_MODEL for back-compat).
230
+ */
231
+ export async function synthesizeGrounded(query, outcome, llm, footer, model = SYNTHESIS_MODEL) {
232
+ // Non-grounded outcomes are already handled by synthesize (disabled/abstain → abstained).
233
+ if (outcome.kind !== "grounded") {
234
+ const answer = await synthesize(query, outcome, llm, footer, model);
235
+ return { answer, verdict: "error-abstained" };
236
+ }
237
+ // 1) First synthesis.
238
+ let answer;
239
+ try {
240
+ answer = await synthesize(query, outcome, llm, footer, model);
241
+ }
242
+ catch {
243
+ return { answer: abstainAnswer(footer), verdict: "error-abstained" };
244
+ }
245
+ if (answer.abstained)
246
+ return { answer, verdict: "error-abstained" }; // synthesize abstained (empty/ABSTAIN/no-passages/model-unavailable)
247
+ // 2) First critique. getGroundingVerdict PROPAGATES transport errors → wrap.
248
+ let verdict;
249
+ try {
250
+ verdict = await getGroundingVerdict({
251
+ llm,
252
+ model,
253
+ question: query,
254
+ answerBody: answer.body,
255
+ passages: outcome.passages,
256
+ });
257
+ }
258
+ catch {
259
+ return { answer: abstainAnswer(footer), verdict: "error-abstained" };
260
+ }
261
+ if (verdict.verdict === "approve")
262
+ return { answer, verdict: "approve" };
263
+ // 3) ONE re-synthesis with critic feedback appended to the system prompt.
264
+ let answer2;
265
+ try {
266
+ answer2 = await synthesizeWithFeedback(query, outcome, llm, footer, verdict.feedback, model);
267
+ }
268
+ catch {
269
+ return { answer: abstainAnswer(footer), verdict: "error-abstained" };
270
+ }
271
+ if (answer2.abstained)
272
+ return { answer: answer2, verdict: "error-abstained" };
273
+ // 4) Re-critique.
274
+ let verdict2;
275
+ try {
276
+ verdict2 = await getGroundingVerdict({
277
+ llm,
278
+ model,
279
+ question: query,
280
+ answerBody: answer2.body,
281
+ passages: outcome.passages,
282
+ });
283
+ }
284
+ catch {
285
+ return { answer: abstainAnswer(footer), verdict: "error-abstained" };
286
+ }
287
+ // Approved on re-critique => 'approve'. Not approved => 'reject-abstained' (critic ran and rejected).
288
+ return verdict2.verdict === "approve"
289
+ ? { answer: answer2, verdict: "approve" }
290
+ : { answer: abstainAnswer(footer), verdict: "reject-abstained" };
291
+ }
292
+ //# sourceMappingURL=literature.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"literature.js","sourceRoot":"","sources":["../../../src/medical/retrieval/literature.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /** MedlineSource — the ONLY medical file allowed network imports (ADR-6 exception). S7 adds the real call. */
2
+ import type { EgressGuard } from "../egress.js";
3
+ /**
4
+ * A single retrieved passage from MedlinePlus.
5
+ * title + url form the citation; text is the passage body for synthesis grounding.
6
+ */
7
+ export interface Passage {
8
+ title: string;
9
+ url: string;
10
+ text: string;
11
+ source: "medlineplus";
12
+ }
13
+ /**
14
+ * Discriminated union for retrieval outcomes.
15
+ * disabled — the egress axis is off; no attempt was made.
16
+ * abstain — the axis is on but the source could not produce passages (error or empty).
17
+ * grounded — passages retrieved from MedlinePlus (Sprint 7).
18
+ */
19
+ export type RetrievalOutcome = {
20
+ kind: "disabled";
21
+ } | {
22
+ kind: "abstain";
23
+ reason: string;
24
+ } | {
25
+ kind: "grounded";
26
+ passages: Passage[];
27
+ };
28
+ /**
29
+ * Injectable transport type — a minimal fetch-like function.
30
+ * Tests pass a fake returning recorded fixture data; production defaults to the global fetch.
31
+ * Using a plain duck-typed return rather than the global Response to stay testable
32
+ * without the global fetch in test files (global fetch is banned in other medical files).
33
+ * No AbortSignal in the signature — tests inject synchronous fakes that don't need it.
34
+ */
35
+ export type FetchLike = (url: string) => Promise<{
36
+ ok: boolean;
37
+ status: number;
38
+ json(): Promise<unknown>;
39
+ }>;
40
+ /**
41
+ * MedlinePlus retrieval source.
42
+ *
43
+ * This is the ONE file the ESLint no-restricted-imports exception permits to hold
44
+ * network imports (src/medical/retrieval/medline-source.ts — ADR-6).
45
+ *
46
+ * assertAllowed("literature-retrieval") is called BEFORE any fetch attempt — this
47
+ * is the runtime defense-in-depth that backs the static ESLint boundary.
48
+ *
49
+ * The injectable fetchImpl transport means tests NEVER reach the live MedlinePlus
50
+ * endpoint — they pass a fake FetchLike returning committed fixture data.
51
+ *
52
+ * bober: single source (MedlinePlus/NIH, no-auth); add PubMed in a future sprint
53
+ * if multi-source support is needed — keep each source in its own file.
54
+ */
55
+ export declare class MedlineSource {
56
+ private readonly egress;
57
+ private readonly fetchImpl;
58
+ constructor(egress: EgressGuard, fetchImpl?: FetchLike);
59
+ /**
60
+ * Fetch passages from MedlinePlus for the given query.
61
+ *
62
+ * Order:
63
+ * 1. assertAllowed — throws (and is caught below) if the axis is off.
64
+ * 2. fetchImpl(url) — injectable transport; default = global fetch.
65
+ * 3. Parse response into Passage[].
66
+ * 4. Return grounded | abstain{no-passages} | abstain{source-error}.
67
+ *
68
+ * NEVER throws out of this method — all errors map to abstain{source-error}.
69
+ */
70
+ fetchPassages(query: string): Promise<RetrievalOutcome>;
71
+ }
72
+ //# sourceMappingURL=medline-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medline-source.d.ts","sourceRoot":"","sources":["../../../src/medical/retrieval/medline-source.ts"],"names":[],"mappings":"AAAA,8GAA8G;AAG9G,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,CAAC;CACvB;AAED;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,EAAE,OAAO,EAAE,CAAA;CAAE,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,MAAM,SAAS,GAAG,CACtB,GAAG,EAAE,MAAM,KACR,OAAO,CAAC;IAAE,EAAE,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAAA;CAAE,CAAC,CAAC;AAqExE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,aAAa;IAEtB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAGvB,OAAO,CAAC,QAAQ,CAAC,SAAS;gBAHT,MAAM,EAAE,WAAW,EAGnB,SAAS,GAAE,SAA8B;IAG5D;;;;;;;;;;OAUG;IACG,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;CAuB9D"}
@@ -0,0 +1,120 @@
1
+ // ── MedlinePlus Web Service URL builder ─────────────────────────────
2
+ // bober: MedlinePlus Web Service (health-topics JSON, no-auth) — https://wsearch.nlm.nih.gov/ws/query
3
+ // Returns nlmSearchResult.list.document[] with content fields (title, FullSummary, link).
4
+ // CI uses the committed fixture at __fixtures__/medlineplus-sample.json; live endpoint skipped by default.
5
+ const MEDLINEPLUS_BASE = "https://wsearch.nlm.nih.gov/ws/query";
6
+ function buildMedlineUrl(query) {
7
+ // Build query string manually — URLSearchParams is not declared as a global in the ESLint config.
8
+ const encodedTerm = encodeURIComponent(query);
9
+ return `${MEDLINEPLUS_BASE}?db=healthTopics&term=${encodedTerm}&rettype=brief&retmax=5&retformat=json`;
10
+ }
11
+ // ── MedlinePlus response parser ──────────────────────────────────────
12
+ /**
13
+ * Parse the MedlinePlus Web Service JSON response into Passage[].
14
+ * Accepts unknown shape; returns [] on any structural mismatch (fail-closed).
15
+ */
16
+ function parseMedline(raw) {
17
+ if (!raw || typeof raw !== "object")
18
+ return [];
19
+ const result = raw;
20
+ const nlmResult = result["nlmSearchResult"];
21
+ if (!nlmResult)
22
+ return [];
23
+ const list = nlmResult["list"];
24
+ if (!list)
25
+ return [];
26
+ const documents = list["document"];
27
+ if (!Array.isArray(documents))
28
+ return [];
29
+ const passages = [];
30
+ for (const doc of documents) {
31
+ if (!doc || typeof doc !== "object")
32
+ continue;
33
+ const d = doc;
34
+ // title: from content[] where name === "title"
35
+ let title = "";
36
+ let text = "";
37
+ let url = "";
38
+ const url_ = d["@url"];
39
+ if (typeof url_ === "string")
40
+ url = url_;
41
+ const contents = d["content"];
42
+ if (Array.isArray(contents)) {
43
+ for (const item of contents) {
44
+ if (!item || typeof item !== "object")
45
+ continue;
46
+ const c = item;
47
+ const name = c["@name"];
48
+ const value = c["#text"];
49
+ if (name === "title" && typeof value === "string")
50
+ title = value;
51
+ if (name === "FullSummary" && typeof value === "string")
52
+ text = value;
53
+ }
54
+ }
55
+ if (title && url) {
56
+ passages.push({ title, url, text: text || title, source: "medlineplus" });
57
+ }
58
+ }
59
+ return passages;
60
+ }
61
+ // ── MedlineSource class ──────────────────────────────────────────────
62
+ /**
63
+ * MedlinePlus retrieval source.
64
+ *
65
+ * This is the ONE file the ESLint no-restricted-imports exception permits to hold
66
+ * network imports (src/medical/retrieval/medline-source.ts — ADR-6).
67
+ *
68
+ * assertAllowed("literature-retrieval") is called BEFORE any fetch attempt — this
69
+ * is the runtime defense-in-depth that backs the static ESLint boundary.
70
+ *
71
+ * The injectable fetchImpl transport means tests NEVER reach the live MedlinePlus
72
+ * endpoint — they pass a fake FetchLike returning committed fixture data.
73
+ *
74
+ * bober: single source (MedlinePlus/NIH, no-auth); add PubMed in a future sprint
75
+ * if multi-source support is needed — keep each source in its own file.
76
+ */
77
+ export class MedlineSource {
78
+ egress;
79
+ fetchImpl;
80
+ constructor(egress,
81
+ // bober: global fetch is the default ONLY in this file (ESLint exception);
82
+ // tests inject a FetchLike returning fixture data so CI stays offline.
83
+ fetchImpl = fetch) {
84
+ this.egress = egress;
85
+ this.fetchImpl = fetchImpl;
86
+ }
87
+ /**
88
+ * Fetch passages from MedlinePlus for the given query.
89
+ *
90
+ * Order:
91
+ * 1. assertAllowed — throws (and is caught below) if the axis is off.
92
+ * 2. fetchImpl(url) — injectable transport; default = global fetch.
93
+ * 3. Parse response into Passage[].
94
+ * 4. Return grounded | abstain{no-passages} | abstain{source-error}.
95
+ *
96
+ * NEVER throws out of this method — all errors map to abstain{source-error}.
97
+ */
98
+ async fetchPassages(query) {
99
+ try {
100
+ // MUST be first — runtime defense-in-depth over the ESLint boundary.
101
+ this.egress.assertAllowed("literature-retrieval");
102
+ const url = buildMedlineUrl(query);
103
+ const res = await this.fetchImpl(url);
104
+ if (!res.ok) {
105
+ return { kind: "abstain", reason: "source-error" };
106
+ }
107
+ const json = await res.json();
108
+ const passages = parseMedline(json);
109
+ return passages.length > 0
110
+ ? { kind: "grounded", passages }
111
+ : { kind: "abstain", reason: "no-passages" };
112
+ }
113
+ catch {
114
+ // Includes: assertAllowed throws (axis off), network error, parse error.
115
+ // NEVER fail-open — abstain with source-error.
116
+ return { kind: "abstain", reason: "source-error" };
117
+ }
118
+ }
119
+ }
120
+ //# sourceMappingURL=medline-source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"medline-source.js","sourceRoot":"","sources":["../../../src/medical/retrieval/medline-source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Medical team builder (Phase 6, Sprint 1).
3
+ *
4
+ * buildMedicalTeam returns the built-in 'medical' Team with pipelineShape
5
+ * 'medical-sop' and a concrete (stub) GuardrailSet in the guardrails slot.
6
+ * Real GuardrailSet.evaluate logic lands in S3.
7
+ */
8
+ import type { BoberConfig } from "../config/schema.js";
9
+ import type { Team } from "../teams/types.js";
10
+ /**
11
+ * Builds the built-in 'medical' Team.
12
+ * Registered in loadTeam (registry.ts) before the config.teams lookup.
13
+ */
14
+ export declare function buildMedicalTeam(config: BoberConfig): Team;
15
+ //# sourceMappingURL=team.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.d.ts","sourceRoot":"","sources":["../../src/medical/team.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD,OAAO,KAAK,EAAQ,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAmCpD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAU1D"}
@@ -0,0 +1,44 @@
1
+ import { resolveRoleProviders } from "../config/role-providers.js";
2
+ import { MedicalGuardrails } from "./guardrails.js";
3
+ // ── Role descriptors ────────────────────────────────────────────────
4
+ /**
5
+ * The 7 role descriptors reused for the medical team.
6
+ * Mirrors DEFAULT_ROLES in registry.ts — kept separate to avoid a circular
7
+ * import (registry.ts → medical/team.ts → registry.ts).
8
+ * bober: inline copy; if roles diverge between teams, extract to a shared module.
9
+ */
10
+ const MEDICAL_ROLES = [
11
+ { name: "planner", displayName: "Planner" },
12
+ { name: "researcher", displayName: "Researcher" },
13
+ { name: "chat", displayName: "Chat" },
14
+ { name: "curator", displayName: "Curator" },
15
+ { name: "generator", displayName: "Generator" },
16
+ { name: "evaluator", displayName: "Evaluator" },
17
+ { name: "codeReview", displayName: "Code Reviewer" },
18
+ ];
19
+ // ── Built-in medical guardrails (real impl, Sprint 3) ───────────────
20
+ /**
21
+ * Builds the real medical GuardrailSet (Sprint 3 and beyond).
22
+ * Wraps RedFlagDetector for emergency escalation; allows benign prompts.
23
+ * The stub allow-all from Sprint 1–2 is replaced by MedicalGuardrails.
24
+ */
25
+ function buildMedicalGuardrails() {
26
+ return new MedicalGuardrails();
27
+ }
28
+ // ── buildMedicalTeam ────────────────────────────────────────────────
29
+ /**
30
+ * Builds the built-in 'medical' Team.
31
+ * Registered in loadTeam (registry.ts) before the config.teams lookup.
32
+ */
33
+ export function buildMedicalTeam(config) {
34
+ return {
35
+ id: "medical",
36
+ displayName: "Medical team",
37
+ memoryNamespace: "medical",
38
+ providers: resolveRoleProviders(config),
39
+ pipelineShape: "medical-sop",
40
+ roles: MEDICAL_ROLES,
41
+ guardrails: buildMedicalGuardrails(),
42
+ };
43
+ }
44
+ //# sourceMappingURL=team.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"team.js","sourceRoot":"","sources":["../../src/medical/team.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEpD,uEAAuE;AAEvE;;;;;GAKG;AACH,MAAM,aAAa,GAAW;IAC5B,EAAE,IAAI,EAAE,SAAS,EAAK,WAAW,EAAE,SAAS,EAAE;IAC9C,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE;IACjD,EAAE,IAAI,EAAE,MAAM,EAAQ,WAAW,EAAE,MAAM,EAAE;IAC3C,EAAE,IAAI,EAAE,SAAS,EAAK,WAAW,EAAE,SAAS,EAAE;IAC9C,EAAE,IAAI,EAAE,WAAW,EAAG,WAAW,EAAE,WAAW,EAAE;IAChD,EAAE,IAAI,EAAE,WAAW,EAAG,WAAW,EAAE,WAAW,EAAE;IAChD,EAAE,IAAI,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE;CACrD,CAAC;AAEF,uEAAuE;AAEvE;;;;GAIG;AACH,SAAS,sBAAsB;IAC7B,OAAO,IAAI,iBAAiB,EAAE,CAAC;AACjC,CAAC;AAED,uEAAuE;AAEvE;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,MAAmB;IAClD,OAAO;QACL,EAAE,EAAE,SAAS;QACb,WAAW,EAAE,cAAc;QAC3B,eAAe,EAAE,SAAS;QAC1B,SAAS,EAAE,oBAAoB,CAAC,MAAM,CAAC;QACvC,aAAa,EAAE,aAAa;QAC5B,KAAK,EAAE,aAAa;QACpB,UAAU,EAAE,sBAAsB,EAAE;KACrC,CAAC;AACJ,CAAC"}