clinkx 0.3.0 → 1.0.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 (555) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +10 -0
  3. package/README.md +82 -26
  4. package/clinkx-workflows/dist/allowed-roots.d.ts +1 -0
  5. package/clinkx-workflows/dist/allowed-roots.js +5 -0
  6. package/clinkx-workflows/dist/allowed-roots.js.map +1 -0
  7. package/clinkx-workflows/dist/artifacts.d.ts +20 -1
  8. package/clinkx-workflows/dist/artifacts.js +38 -9
  9. package/clinkx-workflows/dist/artifacts.js.map +1 -1
  10. package/clinkx-workflows/dist/backend.d.ts +4 -0
  11. package/clinkx-workflows/dist/citation-check.d.ts +40 -0
  12. package/clinkx-workflows/dist/citation-check.js +348 -0
  13. package/clinkx-workflows/dist/citation-check.js.map +1 -0
  14. package/clinkx-workflows/dist/clink-client.d.ts +2 -1
  15. package/clinkx-workflows/dist/clink-client.js +17 -4
  16. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  17. package/clinkx-workflows/dist/config.d.ts +145 -16
  18. package/clinkx-workflows/dist/config.js +384 -26
  19. package/clinkx-workflows/dist/config.js.map +1 -1
  20. package/clinkx-workflows/dist/contained-file.d.ts +172 -0
  21. package/clinkx-workflows/dist/contained-file.js +483 -0
  22. package/clinkx-workflows/dist/contained-file.js.map +1 -0
  23. package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
  24. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
  25. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
  26. package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
  27. package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
  28. package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
  29. package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
  30. package/clinkx-workflows/dist/context-cache-key.js +91 -0
  31. package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
  32. package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
  33. package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
  34. package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
  35. package/clinkx-workflows/dist/context-preflight.d.ts +184 -0
  36. package/clinkx-workflows/dist/context-preflight.js +91 -0
  37. package/clinkx-workflows/dist/context-preflight.js.map +1 -0
  38. package/clinkx-workflows/dist/council-expander.d.ts +1 -1
  39. package/clinkx-workflows/dist/council-expander.js +2 -1
  40. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  41. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +165 -0
  42. package/clinkx-workflows/dist/dashboard/activity-contract.js +105 -0
  43. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
  44. package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
  45. package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
  46. package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
  47. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +86 -0
  48. package/clinkx-workflows/dist/dashboard/activity-reader.js +480 -0
  49. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +36 -0
  51. package/clinkx-workflows/dist/dashboard/api-routes.js +1047 -0
  52. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
  54. package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
  55. package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
  56. package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
  57. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
  58. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
  59. package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
  60. package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
  61. package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
  62. package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
  63. package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
  64. package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
  65. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
  66. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
  67. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
  68. package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
  69. package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
  70. package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
  71. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
  72. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
  73. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
  74. package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
  75. package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
  76. package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
  77. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +222 -0
  78. package/clinkx-workflows/dist/dashboard/context-run-reader.js +1178 -0
  79. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
  80. package/clinkx-workflows/dist/dashboard/contracts.d.ts +8936 -0
  81. package/clinkx-workflows/dist/dashboard/contracts.js +1157 -0
  82. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
  83. package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
  84. package/clinkx-workflows/dist/dashboard/generation.js +41 -0
  85. package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
  86. package/clinkx-workflows/dist/dashboard/index.d.ts +28 -0
  87. package/clinkx-workflows/dist/dashboard/index.js +16 -0
  88. package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
  89. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +101 -0
  90. package/clinkx-workflows/dist/dashboard/lazy-content.js +748 -0
  91. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
  92. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +303 -0
  93. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
  94. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
  95. package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
  96. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +581 -0
  97. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
  98. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
  99. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
  100. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
  101. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +28 -0
  102. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +110 -0
  103. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
  104. package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
  105. package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
  106. package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
  107. package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
  108. package/clinkx-workflows/dist/dashboard/request-admission.js +126 -0
  109. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
  110. package/clinkx-workflows/dist/dashboard/scanner.d.ts +75 -0
  111. package/clinkx-workflows/dist/dashboard/scanner.js +1110 -0
  112. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
  113. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +50 -0
  114. package/clinkx-workflows/dist/dashboard/server-factory.js +431 -0
  115. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
  116. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2611 -0
  117. package/clinkx-workflows/dist/dashboard/session-log-contract.js +480 -0
  118. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
  119. package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
  120. package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
  121. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
  122. package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
  123. package/clinkx-workflows/dist/dashboard/session-log-journal.js +522 -0
  124. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
  125. package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
  126. package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
  127. package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
  128. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +6 -0
  129. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +367 -0
  130. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
  131. package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
  132. package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
  133. package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
  134. package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
  135. package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
  136. package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
  137. package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
  138. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
  139. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
  140. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  141. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  142. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  143. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  144. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  145. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  146. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  147. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  148. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  149. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  150. package/clinkx-workflows/dist/dashboard-ui/assets/index-DOkMXTfB.js +25 -0
  151. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  152. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  153. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  154. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  155. package/clinkx-workflows/dist/dashboard-ui/assets/style-ChJsn0KD.css +1 -0
  156. package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
  157. package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
  158. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
  159. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
  160. package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
  161. package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
  162. package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
  163. package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
  164. package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
  165. package/clinkx-workflows/dist/delegate-validate.d.ts +78 -0
  166. package/clinkx-workflows/dist/delegate-validate.js +97 -0
  167. package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
  168. package/clinkx-workflows/dist/engine.d.ts +141 -0
  169. package/clinkx-workflows/dist/engine.js +882 -28
  170. package/clinkx-workflows/dist/engine.js.map +1 -1
  171. package/clinkx-workflows/dist/errors.d.ts +2 -0
  172. package/clinkx-workflows/dist/errors.js.map +1 -1
  173. package/clinkx-workflows/dist/fingerprint.d.ts +1 -1
  174. package/clinkx-workflows/dist/fingerprint.js +1 -1
  175. package/clinkx-workflows/dist/index.d.ts +19 -0
  176. package/clinkx-workflows/dist/index.js +15 -0
  177. package/clinkx-workflows/dist/index.js.map +1 -1
  178. package/clinkx-workflows/dist/loader.d.ts +9 -3
  179. package/clinkx-workflows/dist/loader.js +92 -21
  180. package/clinkx-workflows/dist/loader.js.map +1 -1
  181. package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
  182. package/clinkx-workflows/dist/plan-review/admission.js +290 -0
  183. package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
  184. package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
  185. package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
  186. package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
  187. package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
  188. package/clinkx-workflows/dist/plan-review/contract.js +289 -0
  189. package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
  190. package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
  191. package/clinkx-workflows/dist/plan-review/digest.js +241 -0
  192. package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
  193. package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
  194. package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
  195. package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
  196. package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
  197. package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
  198. package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
  199. package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
  200. package/clinkx-workflows/dist/plan-review/expander.js +243 -0
  201. package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
  202. package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
  203. package/clinkx-workflows/dist/plan-review/projector.js +437 -0
  204. package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
  205. package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
  206. package/clinkx-workflows/dist/plan-review/publication.js +182 -0
  207. package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
  208. package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
  209. package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
  210. package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
  211. package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
  212. package/clinkx-workflows/dist/plan-review/resume.js +122 -0
  213. package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
  214. package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
  215. package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
  216. package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
  217. package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
  218. package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
  219. package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
  220. package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
  221. package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
  222. package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
  223. package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
  224. package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
  225. package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
  226. package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
  227. package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
  228. package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
  229. package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
  230. package/clinkx-workflows/dist/post-processors/index.js +21 -0
  231. package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
  232. package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
  233. package/clinkx-workflows/dist/prompt-budget.js +7 -1
  234. package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
  235. package/clinkx-workflows/dist/reducers/index.js +2 -0
  236. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  237. package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
  238. package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
  239. package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
  240. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
  241. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
  242. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
  243. package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
  244. package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
  245. package/clinkx-workflows/dist/run-summary.d.ts +13 -0
  246. package/clinkx-workflows/dist/run-summary.js +29 -1
  247. package/clinkx-workflows/dist/run-summary.js.map +1 -1
  248. package/clinkx-workflows/dist/schema.d.ts +76 -21
  249. package/clinkx-workflows/dist/schema.js +10 -1
  250. package/clinkx-workflows/dist/schema.js.map +1 -1
  251. package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
  252. package/clinkx-workflows/dist/seed-terms.js +176 -0
  253. package/clinkx-workflows/dist/seed-terms.js.map +1 -0
  254. package/clinkx-workflows/dist/server.d.ts +5 -2
  255. package/clinkx-workflows/dist/server.js +82 -52
  256. package/clinkx-workflows/dist/server.js.map +1 -1
  257. package/clinkx-workflows/dist/shutdown.d.ts +18 -20
  258. package/clinkx-workflows/dist/shutdown.js +43 -61
  259. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  260. package/clinkx-workflows/dist/state-schema.d.ts +38 -0
  261. package/clinkx-workflows/dist/state.d.ts +170 -3
  262. package/clinkx-workflows/dist/state.js +1275 -28
  263. package/clinkx-workflows/dist/state.js.map +1 -1
  264. package/clinkx-workflows/dist/transport.d.ts +15 -0
  265. package/clinkx-workflows/dist/transport.js +19 -10
  266. package/clinkx-workflows/dist/transport.js.map +1 -1
  267. package/clinkx-workflows/dist/types.d.ts +14 -2
  268. package/clinkx-workflows/dist/validators/council.d.ts +318 -85
  269. package/clinkx-workflows/dist/validators/council.js +52 -13
  270. package/clinkx-workflows/dist/validators/council.js.map +1 -1
  271. package/clinkx-workflows/dist/validators/index.d.ts +1 -0
  272. package/clinkx-workflows/dist/validators/index.js +3 -0
  273. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  274. package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
  275. package/clinkx-workflows/dist/validators/plan-review.js +316 -0
  276. package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
  277. package/clinkx-workflows/dist/workflow-receipt.js +188 -4
  278. package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
  279. package/clinkx-workflows/dist/workflow-tools.d.ts +48 -3
  280. package/clinkx-workflows/dist/workflow-tools.js +1402 -59
  281. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  282. package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
  283. package/clinkx-workflows/dist/workspace-signature.js +388 -0
  284. package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
  285. package/clinkx-workflows/templates/council/answer-specialist.txt +2 -1
  286. package/clinkx-workflows/templates/council/code-review-chairman.txt +13 -0
  287. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +4 -1
  288. package/clinkx-workflows/templates/council/code-review-reviewer.txt +19 -3
  289. package/clinkx-workflows/templates/council/debug-debugger.txt +2 -1
  290. package/clinkx-workflows/templates/council/discover-analyst.txt +2 -1
  291. package/clinkx-workflows/templates/council/member.txt +2 -1
  292. package/clinkx-workflows/templates/council/plan-review-chairman.txt +68 -0
  293. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +51 -0
  294. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +60 -0
  295. package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
  296. package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
  297. package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
  298. package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
  299. package/clinkx-workflows/workflows/council-answer.yaml +97 -8
  300. package/clinkx-workflows/workflows/council-code-review.yaml +103 -8
  301. package/clinkx-workflows/workflows/council-debug.yaml +97 -8
  302. package/clinkx-workflows/workflows/council-default.yaml +97 -8
  303. package/clinkx-workflows/workflows/council-discover.yaml +97 -8
  304. package/clinkx-workflows/workflows/council-plan-review.yaml +250 -0
  305. package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
  306. package/conf/adapters/agy.json +49 -0
  307. package/conf/adapters/claude.json +5 -0
  308. package/conf/adapters/codex-app-server.json +44 -0
  309. package/conf/adapters/codex.json +7 -2
  310. package/conf/adapters/glm.json +5 -0
  311. package/conf/adapters/grok.json +60 -0
  312. package/conf/adapters/hapi/agy.json +52 -0
  313. package/conf/adapters/hapi/claude.json +5 -0
  314. package/conf/adapters/hapi/codex.json +7 -2
  315. package/conf/adapters/hapi/glm.json +5 -0
  316. package/dist/activity-journal.d.ts +42 -0
  317. package/dist/activity-journal.js +381 -0
  318. package/dist/activity-journal.js.map +1 -0
  319. package/dist/allowed-roots.d.ts +41 -0
  320. package/dist/allowed-roots.js +125 -3
  321. package/dist/allowed-roots.js.map +1 -1
  322. package/dist/codex/activity-session-log.d.ts +14 -0
  323. package/dist/codex/activity-session-log.js +206 -0
  324. package/dist/codex/activity-session-log.js.map +1 -0
  325. package/dist/codex/agent-tools.d.ts +23 -0
  326. package/dist/codex/agent-tools.js +1270 -0
  327. package/dist/codex/agent-tools.js.map +1 -0
  328. package/dist/codex/app-server-client.d.ts +76 -0
  329. package/dist/codex/app-server-client.js +582 -0
  330. package/dist/codex/app-server-client.js.map +1 -0
  331. package/dist/codex/app-server-registry.d.ts +389 -0
  332. package/dist/codex/app-server-registry.js +1407 -0
  333. package/dist/codex/app-server-registry.js.map +1 -0
  334. package/dist/codex/client-types.d.ts +109 -0
  335. package/dist/codex/client-types.js +2 -0
  336. package/dist/codex/client-types.js.map +1 -0
  337. package/dist/codex/protocol.d.ts +5375 -0
  338. package/dist/codex/protocol.js +614 -0
  339. package/dist/codex/protocol.js.map +1 -0
  340. package/dist/codex/session.d.ts +263 -0
  341. package/dist/codex/session.js +466 -0
  342. package/dist/codex/session.js.map +1 -0
  343. package/dist/config.d.ts +185 -19
  344. package/dist/config.js +82 -16
  345. package/dist/config.js.map +1 -1
  346. package/dist/context-builder/anchors.d.ts +1 -0
  347. package/dist/context-builder/anchors.js +3 -0
  348. package/dist/context-builder/anchors.js.map +1 -1
  349. package/dist/context-builder/bundle.d.ts +35 -2
  350. package/dist/context-builder/bundle.js +342 -2417
  351. package/dist/context-builder/bundle.js.map +1 -1
  352. package/dist/context-builder/codemap.d.ts +5 -0
  353. package/dist/context-builder/codemap.js +387 -13
  354. package/dist/context-builder/codemap.js.map +1 -1
  355. package/dist/context-builder/config.d.ts +1 -0
  356. package/dist/context-builder/config.js +12 -0
  357. package/dist/context-builder/config.js.map +1 -1
  358. package/dist/context-builder/context-run-catalog.d.ts +17 -0
  359. package/dist/context-builder/context-run-catalog.js +99 -0
  360. package/dist/context-builder/context-run-catalog.js.map +1 -0
  361. package/dist/context-builder/delegate-packet.d.ts +37 -0
  362. package/dist/context-builder/delegate-packet.js +359 -67
  363. package/dist/context-builder/delegate-packet.js.map +1 -1
  364. package/dist/context-builder/delegate-validation.d.ts +10 -1
  365. package/dist/context-builder/delegate-validation.js +153 -41
  366. package/dist/context-builder/delegate-validation.js.map +1 -1
  367. package/dist/context-builder/delegate.d.ts +2 -0
  368. package/dist/context-builder/delegate.js +1 -0
  369. package/dist/context-builder/delegate.js.map +1 -1
  370. package/dist/context-builder/diagnostics.d.ts +11 -2
  371. package/dist/context-builder/diagnostics.js +39 -6
  372. package/dist/context-builder/diagnostics.js.map +1 -1
  373. package/dist/context-builder/discovery.d.ts +29 -1
  374. package/dist/context-builder/discovery.js +475 -175
  375. package/dist/context-builder/discovery.js.map +1 -1
  376. package/dist/context-builder/globs.js +10 -10
  377. package/dist/context-builder/globs.js.map +1 -1
  378. package/dist/context-builder/handler.d.ts +26 -2
  379. package/dist/context-builder/handler.js +1158 -2416
  380. package/dist/context-builder/handler.js.map +1 -1
  381. package/dist/context-builder/lifecycle-contract.d.ts +424 -0
  382. package/dist/context-builder/lifecycle-contract.js +523 -0
  383. package/dist/context-builder/lifecycle-contract.js.map +1 -0
  384. package/dist/context-builder/lifecycle-journal.d.ts +62 -0
  385. package/dist/context-builder/lifecycle-journal.js +88 -0
  386. package/dist/context-builder/lifecycle-journal.js.map +1 -0
  387. package/dist/context-builder/lifecycle-observer.d.ts +102 -0
  388. package/dist/context-builder/lifecycle-observer.js +258 -0
  389. package/dist/context-builder/lifecycle-observer.js.map +1 -0
  390. package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
  391. package/dist/context-builder/lifecycle-reconcile.js +191 -0
  392. package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
  393. package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
  394. package/dist/context-builder/lifecycle-recovery.js +14 -0
  395. package/dist/context-builder/lifecycle-recovery.js.map +1 -0
  396. package/dist/context-builder/path-signals.js +9 -5
  397. package/dist/context-builder/path-signals.js.map +1 -1
  398. package/dist/context-builder/progress.d.ts +3 -1
  399. package/dist/context-builder/progress.js +23 -0
  400. package/dist/context-builder/progress.js.map +1 -1
  401. package/dist/context-builder/receipt.d.ts +6 -4
  402. package/dist/context-builder/receipt.js +12 -1
  403. package/dist/context-builder/receipt.js.map +1 -1
  404. package/dist/context-builder/refine-handler.d.ts +14 -0
  405. package/dist/context-builder/refine-handler.js +790 -0
  406. package/dist/context-builder/refine-handler.js.map +1 -0
  407. package/dist/context-builder/review-coverage.d.ts +84 -0
  408. package/dist/context-builder/review-coverage.js +437 -0
  409. package/dist/context-builder/review-coverage.js.map +1 -0
  410. package/dist/context-builder/schema.d.ts +239 -1
  411. package/dist/context-builder/schema.js +112 -6
  412. package/dist/context-builder/schema.js.map +1 -1
  413. package/dist/context-builder/secrets.d.ts +28 -0
  414. package/dist/context-builder/secrets.js +1473 -44
  415. package/dist/context-builder/secrets.js.map +1 -1
  416. package/dist/context-builder/selection.d.ts +7 -3
  417. package/dist/context-builder/selection.js +200 -738
  418. package/dist/context-builder/selection.js.map +1 -1
  419. package/dist/context-builder/session-log-identity.d.ts +1 -0
  420. package/dist/context-builder/session-log-identity.js +66 -0
  421. package/dist/context-builder/session-log-identity.js.map +1 -0
  422. package/dist/context-builder/slice-range-math.d.ts +16 -0
  423. package/dist/context-builder/slice-range-math.js +137 -0
  424. package/dist/context-builder/slice-range-math.js.map +1 -0
  425. package/dist/context-builder/status.d.ts +22 -1
  426. package/dist/context-builder/status.js +59 -4
  427. package/dist/context-builder/status.js.map +1 -1
  428. package/dist/context-builder/storage.d.ts +35 -2
  429. package/dist/context-builder/storage.js +271 -14
  430. package/dist/context-builder/storage.js.map +1 -1
  431. package/dist/context-builder/token-estimate.d.ts +13 -2
  432. package/dist/context-builder/token-estimate.js +77 -4
  433. package/dist/context-builder/token-estimate.js.map +1 -1
  434. package/dist/context-builder/versions.d.ts +29 -0
  435. package/dist/context-builder/versions.js +30 -0
  436. package/dist/context-builder/versions.js.map +1 -0
  437. package/dist/context-builder/workflow-runner.d.ts +69 -0
  438. package/dist/context-builder/workflow-runner.js +1051 -0
  439. package/dist/context-builder/workflow-runner.js.map +1 -0
  440. package/dist/context-builder/write-watchdog.d.ts +14 -0
  441. package/dist/context-builder/write-watchdog.js +38 -0
  442. package/dist/context-builder/write-watchdog.js.map +1 -0
  443. package/dist/dashboard-adapter-catalog.d.ts +9 -0
  444. package/dist/dashboard-adapter-catalog.js +46 -0
  445. package/dist/dashboard-adapter-catalog.js.map +1 -0
  446. package/dist/dashboard-cli.d.ts +79 -0
  447. package/dist/dashboard-cli.js +585 -0
  448. package/dist/dashboard-cli.js.map +1 -0
  449. package/dist/dashboard-companion/client.d.ts +36 -0
  450. package/dist/dashboard-companion/client.js +210 -0
  451. package/dist/dashboard-companion/client.js.map +1 -0
  452. package/dist/dashboard-companion/config.d.ts +8 -0
  453. package/dist/dashboard-companion/config.js +52 -0
  454. package/dist/dashboard-companion/config.js.map +1 -0
  455. package/dist/dashboard-companion/http-proxy.d.ts +10 -0
  456. package/dist/dashboard-companion/http-proxy.js +234 -0
  457. package/dist/dashboard-companion/http-proxy.js.map +1 -0
  458. package/dist/dashboard-companion/protocol.d.ts +80 -0
  459. package/dist/dashboard-companion/protocol.js +455 -0
  460. package/dist/dashboard-companion/protocol.js.map +1 -0
  461. package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
  462. package/dist/dashboard-companion/standalone-assets.js +72 -0
  463. package/dist/dashboard-companion/standalone-assets.js.map +1 -0
  464. package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
  465. package/dist/dashboard-companion/standalone-entry.js +17 -0
  466. package/dist/dashboard-companion/standalone-entry.js.map +1 -0
  467. package/dist/dashboard-gateway/config.d.ts +9 -0
  468. package/dist/dashboard-gateway/config.js +44 -0
  469. package/dist/dashboard-gateway/config.js.map +1 -0
  470. package/dist/dashboard-gateway/index.d.ts +11 -0
  471. package/dist/dashboard-gateway/index.js +755 -0
  472. package/dist/dashboard-gateway/index.js.map +1 -0
  473. package/dist/dashboard-gateway/main.d.ts +1 -0
  474. package/dist/dashboard-gateway/main.js +25 -0
  475. package/dist/dashboard-gateway/main.js.map +1 -0
  476. package/dist/dashboard-pairing-token.d.ts +1 -0
  477. package/dist/dashboard-pairing-token.js +5 -0
  478. package/dist/dashboard-pairing-token.js.map +1 -0
  479. package/dist/elaris/client.d.ts +73 -0
  480. package/dist/elaris/client.js +825 -0
  481. package/dist/elaris/client.js.map +1 -0
  482. package/dist/elaris/config.d.ts +18 -0
  483. package/dist/elaris/config.js +75 -0
  484. package/dist/elaris/config.js.map +1 -0
  485. package/dist/elaris/errors.d.ts +41 -0
  486. package/dist/elaris/errors.js +205 -0
  487. package/dist/elaris/errors.js.map +1 -0
  488. package/dist/elaris/github-repo.d.ts +18 -0
  489. package/dist/elaris/github-repo.js +89 -0
  490. package/dist/elaris/github-repo.js.map +1 -0
  491. package/dist/elaris/index.d.ts +10 -0
  492. package/dist/elaris/index.js +11 -0
  493. package/dist/elaris/index.js.map +1 -0
  494. package/dist/elaris/redact.d.ts +89 -0
  495. package/dist/elaris/redact.js +423 -0
  496. package/dist/elaris/redact.js.map +1 -0
  497. package/dist/elaris/run-id.d.ts +5 -0
  498. package/dist/elaris/run-id.js +20 -0
  499. package/dist/elaris/run-id.js.map +1 -0
  500. package/dist/elaris/status.d.ts +45 -0
  501. package/dist/elaris/status.js +122 -0
  502. package/dist/elaris/status.js.map +1 -0
  503. package/dist/elaris/tool-schemas.d.ts +314 -0
  504. package/dist/elaris/tool-schemas.js +208 -0
  505. package/dist/elaris/tool-schemas.js.map +1 -0
  506. package/dist/elaris/tools.d.ts +73 -0
  507. package/dist/elaris/tools.js +1065 -0
  508. package/dist/elaris/tools.js.map +1 -0
  509. package/dist/elaris/types.d.ts +1541 -0
  510. package/dist/elaris/types.js +252 -0
  511. package/dist/elaris/types.js.map +1 -0
  512. package/dist/errors.d.ts +16 -0
  513. package/dist/errors.js +19 -0
  514. package/dist/errors.js.map +1 -1
  515. package/dist/handler.d.ts +4 -0
  516. package/dist/handler.js +10 -1
  517. package/dist/handler.js.map +1 -1
  518. package/dist/index.d.ts +2 -1
  519. package/dist/index.js +13 -3
  520. package/dist/index.js.map +1 -1
  521. package/dist/local-clink-backend.d.ts +3 -0
  522. package/dist/local-clink-backend.js +4 -0
  523. package/dist/local-clink-backend.js.map +1 -1
  524. package/dist/package-info.d.ts +2 -0
  525. package/dist/package-info.js +18 -0
  526. package/dist/package-info.js.map +1 -0
  527. package/dist/parsers/codex-jsonl.js +48 -3
  528. package/dist/parsers/codex-jsonl.js.map +1 -1
  529. package/dist/parsers/extract.d.ts +10 -1
  530. package/dist/parsers/extract.js +11 -3
  531. package/dist/parsers/extract.js.map +1 -1
  532. package/dist/pipeline.d.ts +4 -0
  533. package/dist/pipeline.js +129 -8
  534. package/dist/pipeline.js.map +1 -1
  535. package/dist/run-dir.d.ts +24 -0
  536. package/dist/run-dir.js +201 -2
  537. package/dist/run-dir.js.map +1 -1
  538. package/dist/runner.d.ts +13 -0
  539. package/dist/runner.js +184 -15
  540. package/dist/runner.js.map +1 -1
  541. package/dist/safety.d.ts +4 -4
  542. package/dist/safety.js +4 -4
  543. package/dist/schema.d.ts +7 -7
  544. package/dist/server.d.ts +1 -0
  545. package/dist/server.js +359 -17
  546. package/dist/server.js.map +1 -1
  547. package/dist/session-log-stream.d.ts +18 -0
  548. package/dist/session-log-stream.js +319 -0
  549. package/dist/session-log-stream.js.map +1 -0
  550. package/package.json +46 -5
  551. package/conf/adapters/gemini.json +0 -44
  552. package/conf/adapters/hapi/gemini.json +0 -47
  553. package/dist/parsers/gemini-json.d.ts +0 -2
  554. package/dist/parsers/gemini-json.js +0 -72
  555. package/dist/parsers/gemini-json.js.map +0 -1
@@ -63,28 +63,165 @@
63
63
  * workflow engine treats as a transient failure eligible for retry.
64
64
  */
65
65
  import { stat as fsStat } from "node:fs/promises";
66
- import { isAbsolute, join, resolve } from "node:path";
66
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
67
+ import { basename, isAbsolute, join, resolve, sep } from "node:path";
67
68
  import { callClink } from "./clink-client.js";
68
- import { getArtifactsDir, getChildMaxConcurrent, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
69
+ import { getArtifactsDir, getChildMaxConcurrent, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
70
+ import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
71
+ import { CouncilPlanReviewErrorSchema, } from "./plan-review/publication.js";
72
+ import { PlanReviewStageError } from "./plan-review/stage-error.js";
69
73
  import { CallExecutionError, InvalidCallError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
70
74
  import { analyzeOutputFidelity, enforceOutputFidelity } from "./fidelity.js";
71
75
  import { validateDynamicPaths } from "./path-validation.js";
72
76
  import { estimatePromptBudget, enforcePromptBudget, createPathContributor } from "./prompt-budget.js";
73
77
  import { DispatchQueue } from "./queue.js";
74
78
  import { renderCallPrompt } from "./template.js";
75
- import { ArtifactStore } from "./artifacts.js";
79
+ import { ArtifactStore, OutputSizeError, SchemaValidationError } from "./artifacts.js";
80
+ import { projectPlanReviewSemanticBody, renderPlanSnapshotBlock, } from "./plan-review/expander.js";
81
+ import { filterExcludedPlanPaths, foldPlanReviewExcludeGlobs, } from "./plan-review/context-policy.js";
76
82
  import { getReducer } from "./reducers/index.js";
83
+ import { getPostProcessor } from "./post-processors/index.js";
84
+ import { addPathWithBasename, buildAllowlistFromDiff } from "./citation-check.js";
77
85
  import { logger } from "./logger.js";
86
+ import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
87
+ import { WorkflowDelegateValidationError } from "./delegate-validate.js";
88
+ import { contextSeedSourceValidationError, resolveSeedSource, synthesizeSeedTerms, } from "./seed-terms.js";
89
+ /**
90
+ * Declarations for variables the ENGINE derives at runtime (not declared in the
91
+ * workflow YAML or accepted as caller input). These are merged into the
92
+ * declaration map passed to the template renderer so that templates may
93
+ * reference them ({{context_available}}) without `template.ts` rejecting them as
94
+ * "Unknown variable", while genuinely-unknown placeholders are still rejected.
95
+ *
96
+ * The VALUES are produced by the engine (e.g. {@link deriveContextAvailable},
97
+ * which always overwrites any caller-supplied `context_available` before stages
98
+ * run), so listing them here does NOT widen the public caller-input surface.
99
+ */
100
+ const ENGINE_DERIVED_VARIABLE_DECLS = {
101
+ context_available: {
102
+ type: "string",
103
+ required: false,
104
+ description: "Engine-derived: whether repository context is attached to the run",
105
+ },
106
+ context_quality: {
107
+ type: "string",
108
+ required: false,
109
+ description: "Engine-derived: effective context coverage quality",
110
+ },
111
+ };
78
112
  /** Default parent-side timeout for a single tools/call RPC (10 minutes). */
79
113
  const DEFAULT_PARENT_CALL_TIMEOUT_MS = 600_000;
80
114
  /** Buffer added to per-call timeout to account for child-side teardown latency. */
81
115
  const CALL_TIMEOUT_BUFFER_MS = 5_000;
116
+ const preflightFallbackAggregate = {
117
+ curated_count: 0,
118
+ fallback_count: 0,
119
+ };
120
+ function orderedUnique(values) {
121
+ const seen = new Set();
122
+ const result = [];
123
+ for (const value of values) {
124
+ if (seen.has(value)) {
125
+ continue;
126
+ }
127
+ seen.add(value);
128
+ result.push(value);
129
+ }
130
+ return result;
131
+ }
132
+ /**
133
+ * Maps a runner's {@link PreflightContextResult} to the persisted {@link WorkflowContext}.
134
+ * Single source of truth for this mapping: the transient `receipt` field is dropped, and
135
+ * optional `delegate` metadata is spread only when present.
136
+ */
137
+ export function mapPreflightResultToWorkflowContext(result, contextMode, lightCaller) {
138
+ // Mirror the engine's bundle-attachment decision exactly: bundle artifacts are
139
+ // appended to context_bundle_files IFF files_selected > 0 (see ~line 1090). On a
140
+ // zero-selection run we proceed WITHOUT a bundle, so the reported copied_paths
141
+ // must be empty too — otherwise the receipt would imply a bundle was attached.
142
+ const copied_paths = result.metrics.files_selected > 0 ? result.copied_paths : [];
143
+ return {
144
+ context_builder: {
145
+ source_run_id: result.source_run_id,
146
+ source_artifact_dir: result.source_artifact_dir,
147
+ cache: result.cache,
148
+ schema_version: result.schema_version,
149
+ status: result.status,
150
+ ...(contextMode !== undefined ? { context_mode: contextMode } : {}),
151
+ copied_paths,
152
+ ...(result.coverage !== undefined ? { coverage: result.coverage } : {}),
153
+ metrics: result.metrics,
154
+ warning_count: result.warning_count,
155
+ omission_count: result.omission_count,
156
+ ...(result.delegate !== undefined ? { delegate: result.delegate } : {}),
157
+ },
158
+ ...(lightCaller !== undefined ? { light_caller: lightCaller } : {}),
159
+ };
160
+ }
161
+ export function classifyPreflightPartial(metrics) {
162
+ if (metrics.truncated ||
163
+ metrics.scan_cap_hit ||
164
+ metrics.search_cap_hit ||
165
+ metrics.discovery_byte_cap_hit ||
166
+ metrics.discovery_candidate_cap_hit ||
167
+ metrics.candidate_cap_hit) {
168
+ return { severity: "degraded", partial_reason: "cap_or_truncation" };
169
+ }
170
+ return { severity: "benign", partial_reason: "omissions_only" };
171
+ }
172
+ function recordPreflightFallbackAggregate(input) {
173
+ if (input.delegateEnabled) {
174
+ if (input.fallbackUsed) {
175
+ preflightFallbackAggregate.fallback_count += 1;
176
+ }
177
+ else {
178
+ preflightFallbackAggregate.curated_count += 1;
179
+ }
180
+ }
181
+ const total = preflightFallbackAggregate.curated_count +
182
+ preflightFallbackAggregate.fallback_count;
183
+ return {
184
+ curated_count: preflightFallbackAggregate.curated_count,
185
+ fallback_count: preflightFallbackAggregate.fallback_count,
186
+ total_delegate_count: total,
187
+ fallback_ratio: total === 0
188
+ ? 0
189
+ : preflightFallbackAggregate.fallback_count / total,
190
+ };
191
+ }
192
+ function firstReceiptWarningCode(result) {
193
+ const warnings = result.receipt["warnings"];
194
+ if (!Array.isArray(warnings)) {
195
+ return null;
196
+ }
197
+ for (const warning of warnings) {
198
+ if (warning !== null && typeof warning === "object") {
199
+ const code = warning["code"];
200
+ if (typeof code === "string" && code.trim() !== "") {
201
+ return code;
202
+ }
203
+ }
204
+ }
205
+ return null;
206
+ }
207
+ function receiptDelegateFailureReason(result) {
208
+ const delegate = result.receipt["delegate"];
209
+ if (delegate === null || typeof delegate !== "object") {
210
+ return null;
211
+ }
212
+ const failureReason = delegate["failure_reason"];
213
+ return typeof failureReason === "string" && failureReason.trim() !== ""
214
+ ? failureReason
215
+ : null;
216
+ }
82
217
  export class WorkflowEngine {
83
218
  definition;
84
219
  session;
85
220
  variables;
221
+ effectiveVariables;
86
222
  validatorRegistry;
87
223
  runId;
224
+ sessionLogGeneration;
88
225
  runDir;
89
226
  signal;
90
227
  queueDepth;
@@ -95,14 +232,29 @@ export class WorkflowEngine {
95
232
  resumeCompletedCalls;
96
233
  resumePreparedInputs;
97
234
  resumeFailedCalls;
235
+ resumeNextRetryCounts;
98
236
  contextProducingCalls = new Set();
237
+ planReviewDeliveredAliases = new Map();
99
238
  artifactStore;
239
+ contextBuilderRunner;
240
+ contextDefault;
241
+ defaultCurator;
242
+ commitPreflightContext;
243
+ restoredWorkflowContext;
244
+ requireContextPreflight;
245
+ initialContextPolicy;
246
+ planReviewContextPolicy;
247
+ planReviewCallBindings;
248
+ commitPlanReviewContextProvenance;
249
+ terminalResultPolicy;
100
250
  constructor(options) {
101
251
  this.definition = options.definition;
102
252
  this.session = options.session;
103
253
  this.variables = options.variables;
254
+ this.effectiveVariables = { ...options.variables };
104
255
  this.validatorRegistry = options.validatorRegistry;
105
256
  this.runId = options.runId;
257
+ this.sessionLogGeneration = options.sessionLogGeneration;
106
258
  this.runDir = options.runDir;
107
259
  this.signal = options.signal;
108
260
  this.queueDepth = Math.min(options.maxParallel ?? getMaxParallel(), options.childMaxConcurrent ?? getChildMaxConcurrent());
@@ -113,7 +265,25 @@ export class WorkflowEngine {
113
265
  this.resumeCompletedCalls = options.resume?.completedCalls ?? new Map();
114
266
  this.resumePreparedInputs = options.resume?.preparedInputs ?? new Map();
115
267
  this.resumeFailedCalls = options.resume?.failedCalls ?? new Map();
268
+ this.resumeNextRetryCounts = options.resume?.nextRetryCounts ?? new Map();
116
269
  this.artifactStore = new ArtifactStore(options.runDir, options.validatorRegistry);
270
+ this.contextBuilderRunner = options.contextBuilderRunner;
271
+ this.contextDefault = options.contextDefault ?? getWorkflowsContextDefault();
272
+ this.defaultCurator = options.defaultCurator ?? getWorkflowsContextDefaultCurator();
273
+ this.commitPreflightContext = options.commitPreflightContext;
274
+ this.restoredWorkflowContext = options.restoredWorkflowContext;
275
+ this.requireContextPreflight = options.requireContextPreflight ?? false;
276
+ this.initialContextPolicy = options.initialContextPolicy;
277
+ this.planReviewContextPolicy = options.planReviewContextPolicy;
278
+ this.planReviewCallBindings = options.planReviewCallBindings === undefined
279
+ ? new Map()
280
+ : validatePlanReviewCallBindings(options.definition, options.planReviewCallBindings);
281
+ validatePlanReviewPreparedInputPolicy(this.resumePreparedInputs, this.planReviewCallBindings, {
282
+ snapshotText: options.planReviewSnapshotText,
283
+ contextPolicy: options.planReviewContextPolicy,
284
+ });
285
+ this.commitPlanReviewContextProvenance = options.commitPlanReviewContextProvenance;
286
+ this.terminalResultPolicy = options.terminalResultPolicy ?? { kind: "final_call" };
117
287
  this.validateFinalStage();
118
288
  this.validateAdvertisedCliNames();
119
289
  this.contextProducingCalls = collectContextProducingCalls(this.definition);
@@ -137,7 +307,7 @@ export class WorkflowEngine {
137
307
  state: "running",
138
308
  message: `Workflow ${this.definition.name} is running`,
139
309
  });
140
- await this.validateInputPathsExist();
310
+ await this.prepareContextPreflight(deadlineController.signal);
141
311
  for (let stageIndex = 0; stageIndex < this.definition.stages.length; stageIndex += 1) {
142
312
  this.throwIfAborted(deadlineController.signal);
143
313
  const stage = this.definition.stages[stageIndex];
@@ -172,11 +342,7 @@ export class WorkflowEngine {
172
342
  throw new WorkflowError(`Stage ${stage.id} failed and on_failure=abort`);
173
343
  }
174
344
  }
175
- const finalStage = stages[stages.length - 1];
176
- const finalCall = finalStage?.calls[0];
177
- if (finalCall == null || !finalCall.ok || finalCall.outputText == null) {
178
- throw new WorkflowError("Final stage did not produce a successful terminal call result");
179
- }
345
+ const outputText = await this.resolveTerminalResult(stages);
180
346
  const completedAt = new Date().toISOString();
181
347
  await this.emitRunStateChange({
182
348
  runId: this.runId,
@@ -193,7 +359,7 @@ export class WorkflowEngine {
193
359
  message: `Workflow ${this.definition.name} succeeded`,
194
360
  });
195
361
  return {
196
- outputText: finalCall.outputText,
362
+ outputText,
197
363
  stages,
198
364
  artifactsDir: this.artifactStore.artifactsDir,
199
365
  };
@@ -219,6 +385,18 @@ export class WorkflowEngine {
219
385
  });
220
386
  }
221
387
  else {
388
+ const planReviewError = error instanceof PlanReviewStageError
389
+ ? CouncilPlanReviewErrorSchema.parse({
390
+ schema_version: "council-plan-review-error-v1",
391
+ run_id: this.runId,
392
+ execution_status: "failed",
393
+ stage: error.stage,
394
+ error: {
395
+ reason: error.reason,
396
+ detail: message.slice(0, 2_048),
397
+ },
398
+ })
399
+ : undefined;
222
400
  await this.emitRunStateChange({
223
401
  runId: this.runId,
224
402
  workflowName: this.definition.name,
@@ -227,6 +405,7 @@ export class WorkflowEngine {
227
405
  completedAt,
228
406
  error: message,
229
407
  failureClass: classifyFailureClass(error),
408
+ planReviewError,
230
409
  });
231
410
  await this.emitProgress({
232
411
  runId: this.runId,
@@ -242,6 +421,42 @@ export class WorkflowEngine {
242
421
  deadlineController.dispose();
243
422
  }
244
423
  }
424
+ /**
425
+ * Complete the workflow context gate without dispatching a provider call.
426
+ * Resume uses this before backend construction when workflow_context is absent.
427
+ */
428
+ async prepareContextPreflight(signal) {
429
+ // Protect engine-derived variables from caller overrides before every
430
+ // branch (runner absent, off, zero-selection, or legacy result).
431
+ if (this.restoredWorkflowContext === undefined) {
432
+ this.setEffectiveVariable("context_quality", "not_evaluated");
433
+ }
434
+ try {
435
+ await this.runContextBuilderPreflight(signal);
436
+ }
437
+ catch (err) {
438
+ if (this.planReviewContextPolicy !== undefined &&
439
+ !(err instanceof PlanReviewStageError) &&
440
+ !(err instanceof WorkflowCancellationError)) {
441
+ throw new PlanReviewStageError("context_preflight", err instanceof Error ? err.message : String(err), "internal_error");
442
+ }
443
+ throw err;
444
+ }
445
+ this.enforcePlanReviewContextExclusions();
446
+ this.deriveContextAvailable();
447
+ await this.validateInputPathsExist();
448
+ }
449
+ async resolveTerminalResult(stages) {
450
+ if (this.terminalResultPolicy.kind === "plan_review_artifacts") {
451
+ return await this.terminalResultPolicy.project();
452
+ }
453
+ const finalStage = stages[stages.length - 1];
454
+ const finalCall = finalStage?.calls[0];
455
+ if (finalCall == null || !finalCall.ok || finalCall.outputText == null) {
456
+ throw new WorkflowError("Final stage did not produce a successful terminal call result");
457
+ }
458
+ return finalCall.outputText;
459
+ }
245
460
  async executeParallelStage(stage, stageIndex, signal) {
246
461
  const queue = new DispatchQueue(this.queueDepth);
247
462
  const settled = await Promise.allSettled(stage.calls.map((call, callIndex) => queue.run(async () => this.executeSingleCall(stage, call, stageIndex, callIndex, signal))));
@@ -282,6 +497,15 @@ export class WorkflowEngine {
282
497
  if (resumedCall != null) {
283
498
  return resumedCall;
284
499
  }
500
+ const resumedFailure = this.resumeFailedCalls.get(callKey);
501
+ if (resumedFailure?.terminal === true) {
502
+ return {
503
+ stageId: resumedFailure.stageId,
504
+ callId: resumedFailure.callId,
505
+ ok: false,
506
+ error: resumedFailure.error,
507
+ };
508
+ }
285
509
  // Handle built-in reducers — no LLM call, no retry loop
286
510
  if (call.reducer != null) {
287
511
  const reducer = getReducer(call.reducer);
@@ -407,21 +631,29 @@ export class WorkflowEngine {
407
631
  const retryMode = stage.retry.mode;
408
632
  const backoffSeconds = stage.retry.backoff_seconds;
409
633
  const maxBackoffSeconds = stage.retry.max_backoff_seconds;
634
+ // Resume never replenishes an already-consumed provider attempt.
635
+ const initialRetryCount = this.resumeNextRetryCounts.get(callKey) ?? 0;
410
636
  // Emit call prepared ONCE (input is stable across retries)
411
637
  await this.emitCallPrepared({
412
638
  runId: this.runId,
413
639
  workflowName: this.definition.name,
414
640
  stageId: stage.id,
415
641
  callId: call.id,
416
- attempt: 1,
417
- retryCount: 0,
642
+ attempt: initialRetryCount + 1,
643
+ retryCount: initialRetryCount,
418
644
  contextProducing,
419
645
  input: structuredClone(input),
420
646
  });
421
- let lastError = undefined;
647
+ let lastError = initialRetryCount > maxRetries
648
+ ? new Error("Retry budget exhausted before resume")
649
+ : undefined;
422
650
  let lastFailureClass = "permanent";
423
- let lastRetryCount = 0;
424
- for (let retryCount = 0; retryCount <= maxRetries; retryCount += 1) {
651
+ let lastRetryCount = initialRetryCount;
652
+ // F4: set in the catch below for the NEXT attempt only; always derived from
653
+ // the original `input.prompt`, so it never stacks. In-memory only — a
654
+ // resumed retry has no lastError and simply gets no suffix.
655
+ let correctiveSuffix;
656
+ for (let retryCount = initialRetryCount; retryCount <= maxRetries; retryCount += 1) {
425
657
  const attempt = retryCount + 1;
426
658
  lastRetryCount = retryCount;
427
659
  this.throwIfAborted(signal);
@@ -450,7 +682,13 @@ export class WorkflowEngine {
450
682
  : `Call ${callKey} is retrying (attempt ${String(attempt)})`,
451
683
  });
452
684
  try {
453
- const result = await this.invokeCall(stage, call, input, signal);
685
+ const attemptInput = correctiveSuffix == null
686
+ ? input
687
+ : {
688
+ ...input,
689
+ prompt: `${typeof input["prompt"] === "string" ? input["prompt"] : ""}${correctiveSuffix}`,
690
+ };
691
+ const result = await this.invokeCall(stage, call, attemptInput, signal, attempt);
454
692
  const fidelity = analyzeOutputFidelity(result.outputText, result.debugMetadata);
455
693
  enforceOutputFidelity(fidelity, {
456
694
  stageId: stage.id,
@@ -534,6 +772,10 @@ export class WorkflowEngine {
534
772
  // Determine if this error is retryable
535
773
  const canRetry = retryCount < maxRetries && isRetryable(error, failureClass, retryMode);
536
774
  if (canRetry) {
775
+ // F4: corrective suffix rides only on the retry that was already
776
+ // decided above — it never changes retry eligibility. Unconditional
777
+ // reassignment clears a stale suffix after a non-JSON failure.
778
+ correctiveSuffix = this.buildCorrectiveRetrySuffix(stage, call, input, error);
537
779
  // Compute backoff with jitter
538
780
  const delayMs = computeRetryDelay(retryCount, backoffSeconds, maxBackoffSeconds);
539
781
  const message = error instanceof Error ? error.message : String(error);
@@ -608,11 +850,85 @@ export class WorkflowEngine {
608
850
  error: message,
609
851
  };
610
852
  }
611
- async invokeCall(stage, call, input, signal) {
853
+ /**
854
+ * F4: bounded corrective suffix for the next already-scheduled retry after
855
+ * a JSON/schema-shaped failure. Returns undefined (no suffix) unless ALL of:
856
+ * - the call declares a JSON contract (mirrors the expect_json derivation
857
+ * in buildCallInput);
858
+ * - the run is NOT typed plan review (identity: the plan-review-receipts
859
+ * workflow name on the
860
+ * normalized definition, plus the plan-review runtime signals as
861
+ * defense-in-depth — plan-review dispatch bytes are pinned);
862
+ * - the failure is JSON/schema-shaped (SchemaValidationError,
863
+ * OutputSizeError as the second correctable class, or a child
864
+ * invalid_json_output rejection);
865
+ * - appending stays inside the same prompt-budget ceiling the dispatch-time
866
+ * gate enforces (best-effort re-estimate; skip with a warn on overflow or
867
+ * estimator failure — never a new failure mode).
868
+ * The suffix is always derived from the ORIGINAL prompt, so it can never
869
+ * stack across attempts, and it is never persisted to state.
870
+ */
871
+ buildCorrectiveRetrySuffix(stage, call, input, error) {
872
+ if (call.response_contract !== "json" && call.validator_schema_id == null) {
873
+ return undefined;
874
+ }
875
+ if (this.definition.name === "plan-review-receipts" ||
876
+ this.planReviewContextPolicy !== undefined ||
877
+ call.plan_review_snapshot_text !== undefined) {
878
+ return undefined;
879
+ }
880
+ const detail = correctiveFailureDetail(error);
881
+ if (detail == null) {
882
+ return undefined;
883
+ }
884
+ const suffix = buildCorrectiveSuffixText(detail, error instanceof OutputSizeError ? CORRECTIVE_SIZE_SUFFIX_HEADER : CORRECTIVE_SUFFIX_HEADER);
885
+ try {
886
+ const basePrompt = typeof input["prompt"] === "string" ? input["prompt"] : "";
887
+ const files = Array.isArray(input["absolute_file_paths"])
888
+ ? input["absolute_file_paths"].filter((path) => typeof path === "string")
889
+ : [];
890
+ const images = Array.isArray(input["images"])
891
+ ? input["images"].filter((path) => typeof path === "string")
892
+ : [];
893
+ const estimate = estimatePromptBudget({
894
+ promptText: `${basePrompt}${suffix}`,
895
+ contextText: typeof input["context"] === "string" ? input["context"] : undefined,
896
+ fileContributors: files.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
897
+ imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
898
+ maxBytes: this.maxPromptBytes,
899
+ reserveBytes: this.promptReserveBytes,
900
+ });
901
+ if (estimate.exceeded) {
902
+ logger.warn({ run_id: this.runId, stage_id: stage.id, call_id: call.id }, "corrective retry suffix skipped: appending would exceed the prompt budget");
903
+ return undefined;
904
+ }
905
+ }
906
+ catch (budgetError) {
907
+ logger.warn({
908
+ run_id: this.runId,
909
+ stage_id: stage.id,
910
+ call_id: call.id,
911
+ err: budgetError instanceof Error ? budgetError.message : String(budgetError),
912
+ }, "corrective retry suffix skipped: prompt budget estimate failed");
913
+ return undefined;
914
+ }
915
+ return suffix;
916
+ }
917
+ async invokeCall(stage, call, input, signal, attempt) {
612
918
  const timeoutMs = call.timeout_seconds != null
613
919
  ? Math.max((call.timeout_seconds * 1000) + CALL_TIMEOUT_BUFFER_MS, DEFAULT_PARENT_CALL_TIMEOUT_MS)
614
920
  : undefined;
615
- return await callClink(this.session, input, signal, { stageId: stage.id, callId: call.id }, timeoutMs);
921
+ const sessionLogOwner = this.sessionLogGeneration !== undefined && this.session.supportsSessionLogOwner === true
922
+ ? {
923
+ family: "workflow",
924
+ runId: this.runId,
925
+ runGeneration: this.sessionLogGeneration,
926
+ stageId: stage.id,
927
+ callId: call.id,
928
+ attempt,
929
+ }
930
+ : undefined;
931
+ return await callClink(this.session, input, signal, { stageId: stage.id, callId: call.id }, timeoutMs, sessionLogOwner);
616
932
  }
617
933
  maybeWriteArtifact(stage, call, stageIndex, callIndex, outputText) {
618
934
  const requiresValidation = call.validator_schema_id != null;
@@ -624,19 +940,78 @@ export class WorkflowEngine {
624
940
  throw new WorkflowError(`Validated call ${stage.id}.${call.id} must declare validator_schema_id`);
625
941
  }
626
942
  const validated = this.artifactStore.validateJsonOutput(stage.id, call.id, validatorSchemaId, outputText);
943
+ if (call.plan_review_anonymous_aliases != null) {
944
+ const parsed = validated.parsed;
945
+ const allowed = this.planReviewDeliveredAliases.get(`${stage.id}.${call.id}`) ?? new Set();
946
+ const assessmentIds = (parsed.peer_assessments ?? []).map((entry) => entry.candidate_id);
947
+ const rankingIds = (parsed.ranking ?? []).map((entry) => entry.candidate_id);
948
+ const exactAliasSet = (ids) => {
949
+ if (!ids.every((candidate) => typeof candidate === "string" && allowed.has(candidate)))
950
+ return false;
951
+ const unique = new Set(ids);
952
+ return ids.length === allowed.size && unique.size === allowed.size &&
953
+ [...allowed].every((candidate) => unique.has(candidate));
954
+ };
955
+ if (!exactAliasSet(assessmentIds) || !exactAliasSet(rankingIds)) {
956
+ throw new WorkflowError(`Validation failed for ${stage.id}.${call.id}: peer_assessments and ranking must each contain every bound peer alias exactly once`);
957
+ }
958
+ }
959
+ // Optional post-processing: a call may declare `post_processor: <name>` to
960
+ // transform the validated value before it is persisted. Unknown names are a
961
+ // load-time validation error (loader.ts:validatePostProcessors); we fail
962
+ // fast here too as defense-in-depth, since silently writing the
963
+ // un-transformed output through could ship un-scrubbed data (e.g. an
964
+ // un-run citation_check). The transform runs on the parsed data; the
965
+ // artifact is re-serialized so the persisted bytes reflect it.
966
+ let persisted = validated;
967
+ const postProcessorName = call.post_processor;
968
+ if (postProcessorName != null) {
969
+ const postProcessor = getPostProcessor(postProcessorName);
970
+ if (postProcessor == null) {
971
+ throw new WorkflowError(`Call ${stage.id}.${call.id} declares post_processor ` +
972
+ `"${postProcessorName}" which is not registered`);
973
+ }
974
+ const allowlist = this.buildPostProcessorAllowlist();
975
+ const processed = postProcessor(validated.parsed, { allowlist, schemaId: validatorSchemaId });
976
+ const serialized = `${JSON.stringify(processed, null, 2)}\n`;
977
+ persisted = {
978
+ parsed: processed,
979
+ serialized,
980
+ sizeBytes: Buffer.byteLength(serialized, "utf-8"),
981
+ };
982
+ }
627
983
  return this.artifactStore.writeCallArtifact({
628
984
  stage,
629
985
  call,
630
986
  stageIndex,
631
987
  callIndex,
632
- validated,
988
+ validated: persisted,
633
989
  });
634
990
  }
635
- resolveDynamicPaths(variableName, context) {
636
- if (variableName == null) {
991
+ resolveDynamicPaths(variableNames, context) {
992
+ if (variableNames == null) {
637
993
  return [];
638
994
  }
639
- const rawValue = this.variables[variableName];
995
+ if (typeof variableNames === "string") {
996
+ const values = this.resolveDynamicPathVariable(variableNames, context);
997
+ validateDynamicPaths(values, this.allowedRoots, context);
998
+ return values;
999
+ }
1000
+ const values = [];
1001
+ const seen = new Set();
1002
+ for (const variableName of variableNames) {
1003
+ for (const value of this.resolveDynamicPathVariable(variableName, context)) {
1004
+ if (!seen.has(value)) {
1005
+ seen.add(value);
1006
+ values.push(value);
1007
+ }
1008
+ }
1009
+ }
1010
+ validateDynamicPaths(values, this.allowedRoots, context);
1011
+ return values;
1012
+ }
1013
+ resolveDynamicPathVariable(variableName, context) {
1014
+ const rawValue = this.effectiveVariables[variableName];
640
1015
  if (!Array.isArray(rawValue)) {
641
1016
  throw new WorkflowError(`Variable "${variableName}" for ${context} must be a string[]`);
642
1017
  }
@@ -647,16 +1022,390 @@ export class WorkflowEngine {
647
1022
  }
648
1023
  return isAbsolute(value) ? value : resolve(workspaceRoot, value);
649
1024
  });
650
- validateDynamicPaths(values, this.allowedRoots, context);
651
1025
  return values;
652
1026
  }
1027
+ setEffectiveVariable(name, value) {
1028
+ this.effectiveVariables[name] = value;
1029
+ }
1030
+ /**
1031
+ * Derive the engine-computed `context_available` template variable after
1032
+ * context-builder preflight has had a chance to append to
1033
+ * `context_bundle_files`. True when the union of the effective
1034
+ * `context_files` and `context_bundle_files` path lists is non-empty.
1035
+ * Stored as the STRING "true"/"false" since the template engine stringifies
1036
+ * variable values. Engine-derived; setting it is expected.
1037
+ */
1038
+ deriveContextAvailable() {
1039
+ const contextFiles = this.readStringArrayVariable("context_files");
1040
+ const bundleFiles = this.readStringArrayVariable("context_bundle_files");
1041
+ const available = contextFiles.length > 0 || bundleFiles.length > 0;
1042
+ this.setEffectiveVariable("context_available", available ? "true" : "false");
1043
+ }
1044
+ /**
1045
+ * Build the citation allowlist for a post-processor run: the union of files
1046
+ * parsed from the `diff_context` variable, the resolved `context_files`, and
1047
+ * the resolved `context_bundle_files`. Both full paths and basenames are
1048
+ * stored so citation matching is lenient.
1049
+ */
1050
+ buildPostProcessorAllowlist() {
1051
+ const diffRaw = this.effectiveVariables["diff_context"];
1052
+ const allowlist = buildAllowlistFromDiff(typeof diffRaw === "string" ? diffRaw : "");
1053
+ for (const name of ["context_files", "context_bundle_files"]) {
1054
+ for (const path of this.resolvePathListLenient(name)) {
1055
+ addPathWithBasename(allowlist, path);
1056
+ }
1057
+ }
1058
+ return allowlist;
1059
+ }
1060
+ /**
1061
+ * Resolve a path-list variable to absolute paths without enforcing allowed
1062
+ * roots (the post-processor allowlist is advisory, not a security boundary).
1063
+ * Both the raw and resolved forms are returned so basename matching works
1064
+ * regardless of how a citation spells the file.
1065
+ */
1066
+ resolvePathListLenient(name) {
1067
+ const value = this.effectiveVariables[name];
1068
+ if (!Array.isArray(value)) {
1069
+ return [];
1070
+ }
1071
+ const workspaceRoot = resolveWorkspaceRoot();
1072
+ const out = [];
1073
+ for (const entry of value) {
1074
+ if (typeof entry !== "string") {
1075
+ continue;
1076
+ }
1077
+ out.push(entry);
1078
+ out.push(isAbsolute(entry) ? entry : resolve(workspaceRoot, entry));
1079
+ }
1080
+ return out;
1081
+ }
1082
+ async runContextBuilderPreflight(signal) {
1083
+ if (this.restoredWorkflowContext !== undefined) {
1084
+ return;
1085
+ }
1086
+ const policy = this.resolveContextPolicy();
1087
+ if (policy.effectiveMode === "off" || policy.effectiveMode === "disabled") {
1088
+ return;
1089
+ }
1090
+ const runner = this.contextBuilderRunner;
1091
+ if (runner == null) {
1092
+ if (this.requireContextPreflight) {
1093
+ throw new WorkflowError(`Workflow context preflight is required in ${policy.effectiveMode} mode but no runner is available`);
1094
+ }
1095
+ return;
1096
+ }
1097
+ // A-7(a): strip force-excluded plan anchors from the caller's focus BEFORE
1098
+ // anything reads them, so the curator is never even asked to select the plan.
1099
+ this.enforcePlanReviewContextExclusions();
1100
+ const focusPaths = this.readStringArrayVariable("cb_focus_paths");
1101
+ const callerSearchTerms = this.readStringArrayVariable("cb_search_terms");
1102
+ this.throwIfAborted(signal);
1103
+ // Optional budget overrides: 6 flat number vars folded into a single budget
1104
+ // object containing ONLY the keys actually supplied. When none are present
1105
+ // the object is undefined and omitted via conditional spread, keeping the
1106
+ // no-budget input byte-identical to the historical shape (cache equality).
1107
+ const budgetEntries = [
1108
+ ["max_estimated_tokens", this.readOptionalNumberVariable("cb_budget_max_estimated_tokens")],
1109
+ ["max_files", this.readOptionalNumberVariable("cb_budget_max_files")],
1110
+ ["max_scan_files", this.readOptionalNumberVariable("cb_budget_max_scan_files")],
1111
+ ["max_bytes_per_file", this.readOptionalNumberVariable("cb_budget_max_bytes_per_file")],
1112
+ ["max_total_bytes", this.readOptionalNumberVariable("cb_budget_max_total_bytes")],
1113
+ ["max_search_results", this.readOptionalNumberVariable("cb_budget_max_search_results")],
1114
+ ];
1115
+ const budgetObject = {};
1116
+ for (const [key, value] of budgetEntries) {
1117
+ if (value !== undefined) {
1118
+ budgetObject[key] = value;
1119
+ }
1120
+ }
1121
+ const budget = Object.keys(budgetObject).length > 0 ? budgetObject : undefined;
1122
+ const contextBudgetAdapter = this.readOptionalStringVariable("cb_budget_adapter");
1123
+ const hasCallerSearchTerms = callerSearchTerms.length > 0;
1124
+ const shouldSynthesizeSeedTerms = focusPaths.length === 0 && !hasCallerSearchTerms;
1125
+ // Match mapPreflightContextInput's nonblank rule: a whitespace-only cb_task
1126
+ // must fall through to user_request, else seed synthesis yields no terms and
1127
+ // allowRootWideDiscovery runs a broad scan with no search terms.
1128
+ const cbTaskSeed = this.readOptionalStringVariable("cb_task");
1129
+ const seedText = resolveSeedSource(cbTaskSeed, this.readOptionalStringVariable("user_request"));
1130
+ const seedSourceError = contextSeedSourceValidationError(seedText);
1131
+ if (shouldSynthesizeSeedTerms && seedSourceError != null) {
1132
+ throw new McpError(ErrorCode.InvalidParams, seedSourceError);
1133
+ }
1134
+ const synthesizedSearchTerms = shouldSynthesizeSeedTerms
1135
+ ? synthesizeSeedTerms(seedText)
1136
+ : [];
1137
+ const searchTerms = shouldSynthesizeSeedTerms ? synthesizedSearchTerms : callerSearchTerms;
1138
+ const input = {
1139
+ runId: this.runId,
1140
+ runDir: this.runDir,
1141
+ cb_task: this.readOptionalStringVariable("cb_task") ?? "",
1142
+ user_request: this.readOptionalStringVariable("user_request") ?? "",
1143
+ roots: this.readStringArrayVariable("cb_roots"),
1144
+ focus_paths: focusPaths,
1145
+ include_globs: this.readStringArrayVariable("cb_include_globs"),
1146
+ // A-7(a): the enforced plan exclusions are folded in HERE — before the
1147
+ // delegate packet is built — so the default-on curator+finalizer never
1148
+ // sees the plan as a candidate. Caller `cb_exclude_globs:[]` cannot drop
1149
+ // them; the fold is unconditional.
1150
+ exclude_globs: this.planReviewContextPolicy == null
1151
+ ? this.readStringArrayVariable("cb_exclude_globs")
1152
+ : foldPlanReviewExcludeGlobs(this.planReviewContextPolicy, this.readStringArrayVariable("cb_exclude_globs")),
1153
+ search_terms: searchTerms,
1154
+ profile: this.readOptionalStringVariable("cb_profile"),
1155
+ allow_secrets: this.readOptionalBooleanVariable("cb_allow_secrets"),
1156
+ context_mode: policy.effectiveMode,
1157
+ ...(shouldSynthesizeSeedTerms ? { allowRootWideDiscovery: true } : {}),
1158
+ ...(this.definition.settings.include_selected_context_artifact === true
1159
+ ? { includeSelectedContext: true }
1160
+ : {}),
1161
+ ...(contextBudgetAdapter == null || contextBudgetAdapter.trim() === ""
1162
+ ? {}
1163
+ : { context_budget_adapter: contextBudgetAdapter }),
1164
+ ...(budget == null ? {} : { budget }),
1165
+ ...(policy.effectiveDelegate == null ? {} : { delegate: policy.effectiveDelegate }),
1166
+ };
1167
+ // Light-caller provenance: capture how anchors were derived. `root_wide_used`
1168
+ // records the REALIZED `input.allowRootWideDiscovery` flag actually passed to
1169
+ // the runner, while `anchor_source` reflects whether seed synthesis ran. Today
1170
+ // both flow from the same `shouldSynthesizeSeedTerms` decision.
1171
+ const lightCaller = {
1172
+ anchor_source: shouldSynthesizeSeedTerms ? "synthesized" : "caller",
1173
+ root_wide_used: input.allowRootWideDiscovery === true,
1174
+ synthesized_search_terms: shouldSynthesizeSeedTerms ? synthesizedSearchTerms : [],
1175
+ };
1176
+ let result;
1177
+ try {
1178
+ result = await runner.runOrReuse(input, { signal });
1179
+ }
1180
+ catch (error) {
1181
+ // The injected runner lives in the root package, which clinkx-workflows
1182
+ // must not import. We therefore cannot catch the root CancellationError by
1183
+ // type; instead, normalize at the engine boundary using the signal: if the
1184
+ // signal aborted, surface it as a WorkflowCancellationError (preserving any
1185
+ // existing abort reason). A genuine non-abort failure rethrows unchanged.
1186
+ this.throwIfAborted(signal);
1187
+ throw error;
1188
+ }
1189
+ this.throwIfAborted(signal);
1190
+ const effectiveCoverageStatus = result.metrics.files_selected > 0
1191
+ ? result.coverage?.status ?? "not_evaluated"
1192
+ : "not_evaluated";
1193
+ this.setEffectiveVariable("context_quality", effectiveCoverageStatus);
1194
+ const delegateInput = policy.effectiveDelegate?.enabled === true ? policy.effectiveDelegate : undefined;
1195
+ const fallbackUsed = result.delegate?.fallback_used === true;
1196
+ const fallbackAggregate = recordPreflightFallbackAggregate({
1197
+ delegateEnabled: result.delegate?.enabled === true,
1198
+ fallbackUsed,
1199
+ });
1200
+ logger.info({
1201
+ run_id: this.runId,
1202
+ workflow: this.definition.name,
1203
+ source_run_id: result.source_run_id,
1204
+ mode: policy.effectiveMode,
1205
+ delegate_enabled: delegateInput != null,
1206
+ fallback_used: fallbackUsed,
1207
+ curator_ms: result.observability?.curator_ms ?? null,
1208
+ finalizer_ms: result.observability?.finalizer_ms ?? null,
1209
+ files_seen: result.metrics.files_seen,
1210
+ bytes_read: result.metrics.bytes_read,
1211
+ scan_cap_hit: result.metrics.scan_cap_hit,
1212
+ candidate_pool_size: result.observability?.candidate_pool_size ?? 0,
1213
+ delegate_packet_size: result.observability?.delegate_packet_size ?? 0,
1214
+ files_selected: result.metrics.files_selected,
1215
+ cache: {
1216
+ hit: result.cache.hit,
1217
+ write: result.cache.write,
1218
+ key_prefix: result.cache.key_prefix,
1219
+ },
1220
+ curator: delegateInput == null
1221
+ ? null
1222
+ : {
1223
+ adapter: delegateInput.curator.cli_name,
1224
+ role: delegateInput.curator.role,
1225
+ },
1226
+ finalizer: delegateInput == null
1227
+ ? null
1228
+ : {
1229
+ adapter: delegateInput.finalizer.cli_name,
1230
+ role: delegateInput.finalizer.role,
1231
+ },
1232
+ failure_reason: receiptDelegateFailureReason(result),
1233
+ warning_code: firstReceiptWarningCode(result),
1234
+ budget_caps: result.observability?.budget_caps ?? null,
1235
+ profile: input.profile ?? null,
1236
+ context_budget_adapter: input.context_budget_adapter ?? null,
1237
+ root_source: input.roots.length > 0 ? "explicit" : "fallback",
1238
+ seed_term_count: searchTerms.length,
1239
+ fallback_aggregate: fallbackAggregate,
1240
+ }, "context-builder preflight summary");
1241
+ if (result.metrics.files_selected === 0) {
1242
+ if (this.definition.settings.fail_on_empty_context_selection === true) {
1243
+ throw new WorkflowError("Context-builder preflight selected no files");
1244
+ }
1245
+ logger.warn({
1246
+ run_id: this.runId,
1247
+ source_run_id: result.source_run_id,
1248
+ files_selected: 0,
1249
+ }, "context-builder preflight selected no files; proceeding without bundle");
1250
+ }
1251
+ if (effectiveCoverageStatus === "degraded") {
1252
+ if (this.definition.settings.fail_on_degraded_context_coverage === true) {
1253
+ throw new WorkflowError("Context-builder preflight coverage is degraded");
1254
+ }
1255
+ logger.warn({
1256
+ run_id: this.runId,
1257
+ source_run_id: result.source_run_id,
1258
+ coverage_status: effectiveCoverageStatus,
1259
+ missing_required: result.coverage?.missing_required ?? [],
1260
+ }, "context-builder preflight coverage degraded; proceeding");
1261
+ }
1262
+ if (result.status === "partial") {
1263
+ const partial = classifyPreflightPartial(result.metrics);
1264
+ const payload = {
1265
+ run_id: this.runId,
1266
+ source_run_id: result.source_run_id,
1267
+ files_selected: result.metrics.files_selected,
1268
+ warning_count: result.warning_count,
1269
+ omission_count: result.omission_count,
1270
+ partial_reason: partial.partial_reason,
1271
+ };
1272
+ if (partial.severity === "degraded") {
1273
+ logger.warn(payload, "context-builder preflight returned partial context; proceeding");
1274
+ }
1275
+ else {
1276
+ logger.info(payload, "context-builder preflight returned partial context; proceeding");
1277
+ }
1278
+ }
1279
+ // Only attach the generated bundle artifacts when the preflight actually
1280
+ // selected files. On a zero-selection run we proceed WITHOUT a bundle
1281
+ // (matching the warn log above): the runner always emits two copied paths
1282
+ // (bundle.md + manifest.json) even for an empty selection, so appending
1283
+ // them would hand downstream seats an empty bundle that looks intentional.
1284
+ if (result.metrics.files_selected > 0) {
1285
+ const existingBundleFiles = this.readStringArrayVariable("context_bundle_files");
1286
+ this.setEffectiveVariable("context_bundle_files", orderedUnique([...existingBundleFiles, ...result.copied_paths]));
1287
+ }
1288
+ // A-7(b): post-filter the FINALIZED selection. This is not redundant with
1289
+ // the pre-dispatch fold — caller `cb_include_globs`/focus anchors can
1290
+ // re-admit exactly what the fold excluded, and this is the point where the
1291
+ // exclusion has to survive them. Runs BEFORE commitPreflightContext so the
1292
+ // persisted inputs snapshot never records an excluded path.
1293
+ const removed = this.enforcePlanReviewContextExclusions();
1294
+ if (this.planReviewContextPolicy != null) {
1295
+ await this.commitPlanReviewContextProvenance?.({
1296
+ enforced: true,
1297
+ ...(policy.effectiveMode != null ? { context_mode: policy.effectiveMode } : {}),
1298
+ copied_paths: [...this.readStringArrayVariable("context_bundle_files")],
1299
+ filtered_out_paths: removed,
1300
+ });
1301
+ }
1302
+ await this.commitPreflightContext?.({
1303
+ variables: { ...this.effectiveVariables },
1304
+ workflow_context: this.buildWorkflowContext(result, policy.effectiveMode, lightCaller),
1305
+ });
1306
+ }
1307
+ /**
1308
+ * Strip every force-excluded plan path from the effective context path
1309
+ * variables (A-7). Idempotent and safe to call on any path — no policy means
1310
+ * no-op, so non-plan-review runs are byte-unchanged.
1311
+ *
1312
+ * Returns the paths that were removed, for provenance.
1313
+ */
1314
+ enforcePlanReviewContextExclusions() {
1315
+ const planPolicy = this.planReviewContextPolicy;
1316
+ if (planPolicy == null) {
1317
+ return [];
1318
+ }
1319
+ const removedAll = [];
1320
+ for (const name of ["context_files", "context_bundle_files", "cb_focus_paths"]) {
1321
+ const values = this.readStringArrayVariable(name);
1322
+ if (values.length === 0)
1323
+ continue;
1324
+ const { kept, removed } = filterExcludedPlanPaths(planPolicy, values, resolveWorkspaceRoot());
1325
+ if (removed.length === 0)
1326
+ continue;
1327
+ removedAll.push(...removed);
1328
+ this.setEffectiveVariable(name, kept);
1329
+ logger.warn({ run_id: this.runId, variable: name, removed }, "plan-review context policy stripped excluded plan paths");
1330
+ }
1331
+ return removedAll;
1332
+ }
1333
+ buildWorkflowContext(result, contextMode, lightCaller) {
1334
+ return mapPreflightResultToWorkflowContext(result, contextMode, lightCaller);
1335
+ }
1336
+ resolveContextPolicy() {
1337
+ if (this.initialContextPolicy !== undefined) {
1338
+ return this.initialContextPolicy;
1339
+ }
1340
+ try {
1341
+ return resolveWorkflowContextMode(this.effectiveVariables["cb_context_mode"], this.contextDefault, true, this.effectiveVariables["cb_delegate"], this.defaultCurator);
1342
+ }
1343
+ catch (error) {
1344
+ if (error instanceof WorkflowContextModeResolverError ||
1345
+ error instanceof WorkflowDelegateValidationError) {
1346
+ throw new WorkflowError(error.message);
1347
+ }
1348
+ throw error;
1349
+ }
1350
+ }
1351
+ readOptionalStringVariable(name) {
1352
+ const value = this.effectiveVariables[name];
1353
+ if (value === undefined) {
1354
+ return undefined;
1355
+ }
1356
+ if (typeof value !== "string") {
1357
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string`);
1358
+ }
1359
+ return value;
1360
+ }
1361
+ readOptionalNumberVariable(name) {
1362
+ const value = this.effectiveVariables[name];
1363
+ if (value === undefined) {
1364
+ return undefined;
1365
+ }
1366
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1367
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a finite number`);
1368
+ }
1369
+ return value;
1370
+ }
1371
+ readOptionalBooleanVariable(name) {
1372
+ const value = this.effectiveVariables[name];
1373
+ if (value === undefined) {
1374
+ return undefined;
1375
+ }
1376
+ if (typeof value === "boolean") {
1377
+ return value;
1378
+ }
1379
+ if (value === "true" || value === "1") {
1380
+ return true;
1381
+ }
1382
+ if (value === "false" || value === "0") {
1383
+ return false;
1384
+ }
1385
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a boolean`);
1386
+ }
1387
+ readStringArrayVariable(name) {
1388
+ const value = this.effectiveVariables[name];
1389
+ if (value === undefined) {
1390
+ return [];
1391
+ }
1392
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
1393
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string[]`);
1394
+ }
1395
+ return value;
1396
+ }
653
1397
  createDeadlineController() {
654
1398
  const controller = new AbortController();
655
1399
  const timeout = setTimeout(() => {
656
1400
  controller.abort(new Error(`Workflow timeout exceeded after ${String(this.definition.settings.timeout_seconds)} seconds`));
657
1401
  }, this.definition.settings.timeout_seconds * 1000);
658
1402
  if (this.signal != null) {
659
- this.signal.addEventListener("abort", () => controller.abort(new WorkflowCancellationError()), { once: true });
1403
+ if (this.signal.aborted) {
1404
+ controller.abort(new WorkflowCancellationError());
1405
+ }
1406
+ else {
1407
+ this.signal.addEventListener("abort", () => controller.abort(new WorkflowCancellationError()), { once: true });
1408
+ }
660
1409
  }
661
1410
  return {
662
1411
  signal: controller.signal,
@@ -709,18 +1458,67 @@ export class WorkflowEngine {
709
1458
  join(this.runDir, "artifacts"),
710
1459
  ];
711
1460
  }
1461
+ recordPlanReviewDeliveredAliases(callKey, call, absoluteFilePaths) {
1462
+ if (call.plan_review_anonymous_aliases == null)
1463
+ return;
1464
+ const configured = new Set(Object.values(call.plan_review_anonymous_aliases));
1465
+ const delivered = new Set();
1466
+ const anonymousRoot = resolve(this.artifactStore.artifactsDir, "anonymous");
1467
+ if (Array.isArray(absoluteFilePaths)) {
1468
+ for (const path of absoluteFilePaths) {
1469
+ if (typeof path !== "string")
1470
+ continue;
1471
+ const absolute = resolve(path);
1472
+ if (!absolute.startsWith(anonymousRoot + sep))
1473
+ continue;
1474
+ const alias = basename(path, ".json");
1475
+ if (configured.has(alias))
1476
+ delivered.add(alias);
1477
+ }
1478
+ }
1479
+ this.planReviewDeliveredAliases.set(callKey, delivered);
1480
+ }
712
1481
  buildCallInput(stage, call, callKey, callIndex) {
713
1482
  const resumedInput = this.resumePreparedInputs.get(callKey);
714
1483
  if (resumedInput != null) {
715
- return structuredClone(resumedInput);
1484
+ const input = structuredClone(resumedInput);
1485
+ this.recordPlanReviewDeliveredAliases(callKey, call, input["absolute_file_paths"]);
1486
+ const binding = this.planReviewCallBindings.get(callKey);
1487
+ if (binding !== undefined) {
1488
+ input["cli_name"] = binding.cli_name;
1489
+ }
1490
+ return input;
716
1491
  }
717
- const prompt = renderCallPrompt(call.prompt, this.definition.variables, this.variables, {
1492
+ const renderedPrompt = renderCallPrompt(call.prompt, { ...this.definition.variables, ...ENGINE_DERIVED_VARIABLE_DECLS }, this.effectiveVariables, {
718
1493
  workflow: { run_id: this.runId },
719
1494
  stage: { id: stage.id },
720
1495
  call: { id: call.id },
721
1496
  });
1497
+ /**
1498
+ * A-6/D9: append the verified plan snapshot AFTER rendering, never before.
1499
+ *
1500
+ * The snapshot must not be an input to `renderCallPrompt`. A plan is
1501
+ * untrusted text that may legitimately contain `{{...}}` (plans in this
1502
+ * repo do), and `PLACEHOLDER_RE` scans the WHOLE string it is given: a
1503
+ * `{{call.id}}` inside the plan would be silently substituted — handing the
1504
+ * actor bytes that are NOT the verified snapshot, which is a D9 violation
1505
+ * requiring no tampering at all — and an undeclared `{{user_request}}`
1506
+ * would throw and brick an honest run. Appending after rendering is what
1507
+ * makes the snapshot reach the actor VERBATIM.
1508
+ *
1509
+ * This lands before `estimatePromptBudget` below on purpose: the snapshot
1510
+ * is real dispatched bytes (up to 512 KiB) and must be billed as such, or
1511
+ * the authoritative gate would under-count by the largest term in it.
1512
+ */
1513
+ const prompt = call.plan_review_snapshot_text === undefined
1514
+ ? renderedPrompt
1515
+ : `${renderedPrompt}${renderPlanSnapshotBlock(call.plan_review_snapshot_text)}`;
722
1516
  const contextArtifacts = call.anonymize_context === true
723
- ? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex)
1517
+ ? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex, call.plan_review_semantic_context === true
1518
+ ? projectPlanReviewSemanticBody
1519
+ : undefined, call.plan_review_semantic_context === true
1520
+ ? `${stage.id}-${call.id}`
1521
+ : undefined, call.plan_review_anonymous_aliases)
724
1522
  : this.artifactStore.buildContext(this.definition, call.context_from);
725
1523
  const staticFiles = call.absolute_file_paths ?? [];
726
1524
  const dynamicFiles = this.resolveDynamicPaths(call.absolute_file_paths_from, `${stage.id}.${call.id}.absolute_file_paths_from`);
@@ -731,6 +1529,7 @@ export class WorkflowEngine {
731
1529
  ...staticFiles,
732
1530
  ...dynamicFiles,
733
1531
  ];
1532
+ this.recordPlanReviewDeliveredAliases(callKey, call, contextArtifacts.absoluteFilePaths);
734
1533
  const images = [...staticImages, ...dynamicImages];
735
1534
  const estimate = estimatePromptBudget({
736
1535
  promptText: prompt,
@@ -834,7 +1633,9 @@ export class WorkflowEngine {
834
1633
  }
835
1634
  }
836
1635
  for (const failedCall of this.resumeFailedCalls.values()) {
837
- this.artifactStore.seedFailedCall(failedCall.stageId, failedCall.callId, failedCall.error);
1636
+ if (failedCall.terminal) {
1637
+ this.artifactStore.seedFailedCall(failedCall.stageId, failedCall.callId, failedCall.error);
1638
+ }
838
1639
  }
839
1640
  }
840
1641
  /**
@@ -994,8 +1795,61 @@ function classifyFailureClass(error) {
994
1795
  if (error instanceof WorkflowCancellationError) {
995
1796
  return "permanent";
996
1797
  }
1798
+ // Cross-package classification marker: errors thrown by the injected root
1799
+ // package (e.g. a context-builder preflight delegate timeout) cannot be
1800
+ // narrowed by instanceof here, so honor a well-formed `failureClass`
1801
+ // property when present. Anything else keeps the permanent default.
1802
+ const marker = crossPackageFailureClass(error);
1803
+ if (marker != null) {
1804
+ return marker;
1805
+ }
997
1806
  return "permanent";
998
1807
  }
1808
+ function crossPackageFailureClass(error) {
1809
+ if (error == null || typeof error !== "object") {
1810
+ return undefined;
1811
+ }
1812
+ const value = error.failureClass;
1813
+ return value === "transient" || value === "permanent" ? value : undefined;
1814
+ }
1815
+ /**
1816
+ * Bounded corrective suffix appended to the NEXT attempt's prompt after a
1817
+ * JSON/schema-shaped failure (SchemaValidationError, or OutputSizeError as
1818
+ * the second correctable class) that is already being retried. Total suffix
1819
+ * length is capped at CORRECTIVE_SUFFIX_MAX_CHARS; the failure detail is
1820
+ * truncated to fit. Never persisted — resume replays the original input.
1821
+ */
1822
+ const CORRECTIVE_SUFFIX_MAX_CHARS = 1_500;
1823
+ const CORRECTIVE_SUFFIX_HEADER = "\n\n---\nCORRECTION: Your previous response was rejected because it was not schema-valid. Failure: ";
1824
+ const CORRECTIVE_SIZE_SUFFIX_HEADER = "\n\n---\nCORRECTION: Your previous response was rejected because the pretty-printed JSON exceeded the validator byte cap. Shrink the deliverable first. Failure: ";
1825
+ const CORRECTIVE_SUFFIX_FOOTER = "\nReturn ONLY a single JSON object conforming to the declared contract — no prose, no markdown, " +
1826
+ "no code fences, and nothing before or after the JSON object.";
1827
+ /**
1828
+ * Detail text for a JSON/schema-shaped failure, or undefined when the failure
1829
+ * is not one a corrective re-prompt could fix (transport death, timeout, …).
1830
+ */
1831
+ function correctiveFailureDetail(error) {
1832
+ if (error instanceof SchemaValidationError) {
1833
+ return error.message;
1834
+ }
1835
+ if (error instanceof OutputSizeError) {
1836
+ return error.message;
1837
+ }
1838
+ if (error instanceof CallExecutionError &&
1839
+ error.childDebugMetadata?.rejection_reason === "invalid_json_output") {
1840
+ return "output did not contain valid JSON";
1841
+ }
1842
+ return undefined;
1843
+ }
1844
+ function buildCorrectiveSuffixText(detail, header = CORRECTIVE_SUFFIX_HEADER) {
1845
+ const budget = CORRECTIVE_SUFFIX_MAX_CHARS -
1846
+ header.length -
1847
+ CORRECTIVE_SUFFIX_FOOTER.length;
1848
+ const bounded = detail.length > budget
1849
+ ? `${detail.slice(0, Math.max(0, budget - 1))}…`
1850
+ : detail;
1851
+ return `${header}${bounded}${CORRECTIVE_SUFFIX_FOOTER}`;
1852
+ }
999
1853
  /**
1000
1854
  * Determine whether an error is retryable based on the retry mode (5-1, 5-2).
1001
1855
  *