clinkx 0.3.0 → 1.1.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 (669) hide show
  1. package/CHANGELOG.md +119 -0
  2. package/LICENSE +10 -0
  3. package/README.md +154 -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/chairman-knobs.d.ts +24 -0
  12. package/clinkx-workflows/dist/chairman-knobs.js +27 -0
  13. package/clinkx-workflows/dist/chairman-knobs.js.map +1 -0
  14. package/clinkx-workflows/dist/citation-check.d.ts +40 -0
  15. package/clinkx-workflows/dist/citation-check.js +348 -0
  16. package/clinkx-workflows/dist/citation-check.js.map +1 -0
  17. package/clinkx-workflows/dist/clink-client.d.ts +12 -1
  18. package/clinkx-workflows/dist/clink-client.js +56 -5
  19. package/clinkx-workflows/dist/clink-client.js.map +1 -1
  20. package/clinkx-workflows/dist/config.d.ts +173 -16
  21. package/clinkx-workflows/dist/config.js +417 -26
  22. package/clinkx-workflows/dist/config.js.map +1 -1
  23. package/clinkx-workflows/dist/contained-file.d.ts +172 -0
  24. package/clinkx-workflows/dist/contained-file.js +483 -0
  25. package/clinkx-workflows/dist/contained-file.js.map +1 -0
  26. package/clinkx-workflows/dist/context-builder-lifecycle-contract.d.ts +416 -0
  27. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js +328 -0
  28. package/clinkx-workflows/dist/context-builder-lifecycle-contract.js.map +1 -0
  29. package/clinkx-workflows/dist/context-builder-lifecycle-router.d.ts +24 -0
  30. package/clinkx-workflows/dist/context-builder-lifecycle-router.js +39 -0
  31. package/clinkx-workflows/dist/context-builder-lifecycle-router.js.map +1 -0
  32. package/clinkx-workflows/dist/context-cache-key.d.ts +53 -0
  33. package/clinkx-workflows/dist/context-cache-key.js +91 -0
  34. package/clinkx-workflows/dist/context-cache-key.js.map +1 -0
  35. package/clinkx-workflows/dist/context-mode-resolver.d.ts +27 -0
  36. package/clinkx-workflows/dist/context-mode-resolver.js +126 -0
  37. package/clinkx-workflows/dist/context-mode-resolver.js.map +1 -0
  38. package/clinkx-workflows/dist/context-preflight.d.ts +189 -0
  39. package/clinkx-workflows/dist/context-preflight.js +91 -0
  40. package/clinkx-workflows/dist/context-preflight.js.map +1 -0
  41. package/clinkx-workflows/dist/council-execution-policy.d.ts +68 -0
  42. package/clinkx-workflows/dist/council-execution-policy.js +110 -0
  43. package/clinkx-workflows/dist/council-execution-policy.js.map +1 -0
  44. package/clinkx-workflows/dist/council-expander.d.ts +10 -9
  45. package/clinkx-workflows/dist/council-expander.js +20 -10
  46. package/clinkx-workflows/dist/council-expander.js.map +1 -1
  47. package/clinkx-workflows/dist/dashboard/activity-contract.d.ts +5718 -0
  48. package/clinkx-workflows/dist/dashboard/activity-contract.js +157 -0
  49. package/clinkx-workflows/dist/dashboard/activity-contract.js.map +1 -0
  50. package/clinkx-workflows/dist/dashboard/activity-lease.d.ts +25 -0
  51. package/clinkx-workflows/dist/dashboard/activity-lease.js +34 -0
  52. package/clinkx-workflows/dist/dashboard/activity-lease.js.map +1 -0
  53. package/clinkx-workflows/dist/dashboard/activity-reader.d.ts +90 -0
  54. package/clinkx-workflows/dist/dashboard/activity-reader.js +493 -0
  55. package/clinkx-workflows/dist/dashboard/activity-reader.js.map +1 -0
  56. package/clinkx-workflows/dist/dashboard/api-routes.d.ts +40 -0
  57. package/clinkx-workflows/dist/dashboard/api-routes.js +1155 -0
  58. package/clinkx-workflows/dist/dashboard/api-routes.js.map +1 -0
  59. package/clinkx-workflows/dist/dashboard/artifact-index.d.ts +37 -0
  60. package/clinkx-workflows/dist/dashboard/artifact-index.js +145 -0
  61. package/clinkx-workflows/dist/dashboard/artifact-index.js.map +1 -0
  62. package/clinkx-workflows/dist/dashboard/auth/challenge-store.d.ts +31 -0
  63. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js +65 -0
  64. package/clinkx-workflows/dist/dashboard/auth/challenge-store.js.map +1 -0
  65. package/clinkx-workflows/dist/dashboard/auth/controller.d.ts +26 -0
  66. package/clinkx-workflows/dist/dashboard/auth/controller.js +596 -0
  67. package/clinkx-workflows/dist/dashboard/auth/controller.js.map +1 -0
  68. package/clinkx-workflows/dist/dashboard/auth/credential-store.d.ts +60 -0
  69. package/clinkx-workflows/dist/dashboard/auth/credential-store.js +776 -0
  70. package/clinkx-workflows/dist/dashboard/auth/credential-store.js.map +1 -0
  71. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.d.ts +9 -0
  72. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js +44 -0
  73. package/clinkx-workflows/dist/dashboard/auth/enrollment-gate.js.map +1 -0
  74. package/clinkx-workflows/dist/dashboard/auth/session-store.d.ts +17 -0
  75. package/clinkx-workflows/dist/dashboard/auth/session-store.js +77 -0
  76. package/clinkx-workflows/dist/dashboard/auth/session-store.js.map +1 -0
  77. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.d.ts +25 -0
  78. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js +71 -0
  79. package/clinkx-workflows/dist/dashboard/auth/webauthn-service.js.map +1 -0
  80. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.d.ts +3865 -0
  81. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js +69 -0
  82. package/clinkx-workflows/dist/dashboard/context-builder-invocation-contract.js.map +1 -0
  83. package/clinkx-workflows/dist/dashboard/context-run-liveness.d.ts +37 -0
  84. package/clinkx-workflows/dist/dashboard/context-run-liveness.js +49 -0
  85. package/clinkx-workflows/dist/dashboard/context-run-liveness.js.map +1 -0
  86. package/clinkx-workflows/dist/dashboard/context-run-reader.d.ts +256 -0
  87. package/clinkx-workflows/dist/dashboard/context-run-reader.js +1301 -0
  88. package/clinkx-workflows/dist/dashboard/context-run-reader.js.map +1 -0
  89. package/clinkx-workflows/dist/dashboard/contracts.d.ts +28483 -0
  90. package/clinkx-workflows/dist/dashboard/contracts.js +1284 -0
  91. package/clinkx-workflows/dist/dashboard/contracts.js.map +1 -0
  92. package/clinkx-workflows/dist/dashboard/generation.d.ts +14 -0
  93. package/clinkx-workflows/dist/dashboard/generation.js +41 -0
  94. package/clinkx-workflows/dist/dashboard/generation.js.map +1 -0
  95. package/clinkx-workflows/dist/dashboard/index.d.ts +32 -0
  96. package/clinkx-workflows/dist/dashboard/index.js +20 -0
  97. package/clinkx-workflows/dist/dashboard/index.js.map +1 -0
  98. package/clinkx-workflows/dist/dashboard/invocation-contract.d.ts +786 -0
  99. package/clinkx-workflows/dist/dashboard/invocation-contract.js +201 -0
  100. package/clinkx-workflows/dist/dashboard/invocation-contract.js.map +1 -0
  101. package/clinkx-workflows/dist/dashboard/lazy-content.d.ts +114 -0
  102. package/clinkx-workflows/dist/dashboard/lazy-content.js +799 -0
  103. package/clinkx-workflows/dist/dashboard/lazy-content.js.map +1 -0
  104. package/clinkx-workflows/dist/dashboard/native-transcript/contract.d.ts +90 -0
  105. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js +31 -0
  106. package/clinkx-workflows/dist/dashboard/native-transcript/contract.js.map +1 -0
  107. package/clinkx-workflows/dist/dashboard/native-transcript/projection.d.ts +12 -0
  108. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js +286 -0
  109. package/clinkx-workflows/dist/dashboard/native-transcript/projection.js.map +1 -0
  110. package/clinkx-workflows/dist/dashboard/progress-report-contract.d.ts +446 -0
  111. package/clinkx-workflows/dist/dashboard/progress-report-contract.js +221 -0
  112. package/clinkx-workflows/dist/dashboard/progress-report-contract.js.map +1 -0
  113. package/clinkx-workflows/dist/dashboard/progress-report-reader.d.ts +50 -0
  114. package/clinkx-workflows/dist/dashboard/progress-report-reader.js +529 -0
  115. package/clinkx-workflows/dist/dashboard/progress-report-reader.js.map +1 -0
  116. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.d.ts +89 -0
  117. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js +71 -0
  118. package/clinkx-workflows/dist/dashboard/progress-report-status-overlay.js.map +1 -0
  119. package/clinkx-workflows/dist/dashboard/progress-report-status-store.d.ts +29 -0
  120. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js +111 -0
  121. package/clinkx-workflows/dist/dashboard/progress-report-status-store.js.map +1 -0
  122. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.d.ts +194 -0
  123. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js +89 -0
  124. package/clinkx-workflows/dist/dashboard/prompt-builder-library-contract.js.map +1 -0
  125. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.d.ts +26 -0
  126. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js +102 -0
  127. package/clinkx-workflows/dist/dashboard/prompt-builder-library-store.js.map +1 -0
  128. package/clinkx-workflows/dist/dashboard/read-model.d.ts +86 -0
  129. package/clinkx-workflows/dist/dashboard/read-model.js +440 -0
  130. package/clinkx-workflows/dist/dashboard/read-model.js.map +1 -0
  131. package/clinkx-workflows/dist/dashboard/request-admission.d.ts +31 -0
  132. package/clinkx-workflows/dist/dashboard/request-admission.js +130 -0
  133. package/clinkx-workflows/dist/dashboard/request-admission.js.map +1 -0
  134. package/clinkx-workflows/dist/dashboard/scanner.d.ts +78 -0
  135. package/clinkx-workflows/dist/dashboard/scanner.js +1154 -0
  136. package/clinkx-workflows/dist/dashboard/scanner.js.map +1 -0
  137. package/clinkx-workflows/dist/dashboard/server-factory.d.ts +63 -0
  138. package/clinkx-workflows/dist/dashboard/server-factory.js +483 -0
  139. package/clinkx-workflows/dist/dashboard/server-factory.js.map +1 -0
  140. package/clinkx-workflows/dist/dashboard/session-log-contract.d.ts +2778 -0
  141. package/clinkx-workflows/dist/dashboard/session-log-contract.js +506 -0
  142. package/clinkx-workflows/dist/dashboard/session-log-contract.js.map +1 -0
  143. package/clinkx-workflows/dist/dashboard/session-log-identity.d.ts +21 -0
  144. package/clinkx-workflows/dist/dashboard/session-log-identity.js +101 -0
  145. package/clinkx-workflows/dist/dashboard/session-log-identity.js.map +1 -0
  146. package/clinkx-workflows/dist/dashboard/session-log-journal.d.ts +43 -0
  147. package/clinkx-workflows/dist/dashboard/session-log-journal.js +529 -0
  148. package/clinkx-workflows/dist/dashboard/session-log-journal.js.map +1 -0
  149. package/clinkx-workflows/dist/dashboard/session-log-reader.d.ts +61 -0
  150. package/clinkx-workflows/dist/dashboard/session-log-reader.js +378 -0
  151. package/clinkx-workflows/dist/dashboard/session-log-reader.js.map +1 -0
  152. package/clinkx-workflows/dist/dashboard/session-log-redaction.d.ts +14 -0
  153. package/clinkx-workflows/dist/dashboard/session-log-redaction.js +1101 -0
  154. package/clinkx-workflows/dist/dashboard/session-log-redaction.js.map +1 -0
  155. package/clinkx-workflows/dist/dashboard/session-log-retention.d.ts +23 -0
  156. package/clinkx-workflows/dist/dashboard/session-log-retention.js +212 -0
  157. package/clinkx-workflows/dist/dashboard/session-log-retention.js.map +1 -0
  158. package/clinkx-workflows/dist/dashboard/static-responses.d.ts +10 -0
  159. package/clinkx-workflows/dist/dashboard/static-responses.js +94 -0
  160. package/clinkx-workflows/dist/dashboard/static-responses.js.map +1 -0
  161. package/clinkx-workflows/dist/dashboard/worker-lock-observer.d.ts +28 -0
  162. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js +241 -0
  163. package/clinkx-workflows/dist/dashboard/worker-lock-observer.js.map +1 -0
  164. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
  165. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
  166. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
  167. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
  168. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
  169. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
  170. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
  171. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
  172. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
  173. package/clinkx-workflows/dist/dashboard-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
  174. package/clinkx-workflows/dist/dashboard-ui/assets/index-JPHQVtn9.js +106 -0
  175. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-6-qcROiO.woff +0 -0
  176. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-400-normal-V6pRDFza.woff2 +0 -0
  177. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-BWZEU5yA.woff2 +0 -0
  178. package/clinkx-workflows/dist/dashboard-ui/assets/jetbrains-mono-latin-500-normal-CJOVTJB7.woff +0 -0
  179. package/clinkx-workflows/dist/dashboard-ui/assets/style-DOMHk-j6.css +1 -0
  180. package/clinkx-workflows/dist/dashboard-ui/contracts/council-plan-review-input-v1.json +158 -0
  181. package/clinkx-workflows/dist/dashboard-ui/index.html +15 -0
  182. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Mono-OFL.txt +93 -0
  183. package/clinkx-workflows/dist/dashboard-ui/licenses/IBM-Plex-Sans-OFL.txt +93 -0
  184. package/clinkx-workflows/dist/dashboard-ui/licenses/JetBrains-Mono-OFL.txt +93 -0
  185. package/clinkx-workflows/dist/dashboard-ui/licenses/NOTICE.md +32 -0
  186. package/clinkx-workflows/dist/dashboard-ui/theme-init.js +8 -0
  187. package/clinkx-workflows/dist/definition-normalizer.d.ts +19 -0
  188. package/clinkx-workflows/dist/definition-normalizer.js.map +1 -1
  189. package/clinkx-workflows/dist/delegate-finalizer-policy.d.ts +26 -0
  190. package/clinkx-workflows/dist/delegate-finalizer-policy.js +72 -0
  191. package/clinkx-workflows/dist/delegate-finalizer-policy.js.map +1 -0
  192. package/clinkx-workflows/dist/delegate-signature.d.ts +14 -0
  193. package/clinkx-workflows/dist/delegate-signature.js +14 -0
  194. package/clinkx-workflows/dist/delegate-signature.js.map +1 -0
  195. package/clinkx-workflows/dist/delegate-validate.d.ts +108 -0
  196. package/clinkx-workflows/dist/delegate-validate.js +148 -0
  197. package/clinkx-workflows/dist/delegate-validate.js.map +1 -0
  198. package/clinkx-workflows/dist/engine.d.ts +251 -0
  199. package/clinkx-workflows/dist/engine.js +1342 -92
  200. package/clinkx-workflows/dist/engine.js.map +1 -1
  201. package/clinkx-workflows/dist/errors.d.ts +24 -2
  202. package/clinkx-workflows/dist/errors.js +30 -2
  203. package/clinkx-workflows/dist/errors.js.map +1 -1
  204. package/clinkx-workflows/dist/fidelity.d.ts +47 -1
  205. package/clinkx-workflows/dist/fidelity.js +54 -1
  206. package/clinkx-workflows/dist/fidelity.js.map +1 -1
  207. package/clinkx-workflows/dist/fingerprint.d.ts +25 -7
  208. package/clinkx-workflows/dist/fingerprint.js +33 -10
  209. package/clinkx-workflows/dist/fingerprint.js.map +1 -1
  210. package/clinkx-workflows/dist/index.d.ts +19 -0
  211. package/clinkx-workflows/dist/index.js +68 -1
  212. package/clinkx-workflows/dist/index.js.map +1 -1
  213. package/clinkx-workflows/dist/loader.d.ts +9 -3
  214. package/clinkx-workflows/dist/loader.js +92 -21
  215. package/clinkx-workflows/dist/loader.js.map +1 -1
  216. package/clinkx-workflows/dist/logger.d.ts +1 -1
  217. package/clinkx-workflows/dist/logger.js +17 -3
  218. package/clinkx-workflows/dist/logger.js.map +1 -1
  219. package/clinkx-workflows/dist/monotonic-clock.d.ts +19 -0
  220. package/clinkx-workflows/dist/monotonic-clock.js +22 -0
  221. package/clinkx-workflows/dist/monotonic-clock.js.map +1 -0
  222. package/clinkx-workflows/dist/plan-review/admission.d.ts +160 -0
  223. package/clinkx-workflows/dist/plan-review/admission.js +290 -0
  224. package/clinkx-workflows/dist/plan-review/admission.js.map +1 -0
  225. package/clinkx-workflows/dist/plan-review/context-policy.d.ts +55 -0
  226. package/clinkx-workflows/dist/plan-review/context-policy.js +146 -0
  227. package/clinkx-workflows/dist/plan-review/context-policy.js.map +1 -0
  228. package/clinkx-workflows/dist/plan-review/contract.d.ts +763 -0
  229. package/clinkx-workflows/dist/plan-review/contract.js +289 -0
  230. package/clinkx-workflows/dist/plan-review/contract.js.map +1 -0
  231. package/clinkx-workflows/dist/plan-review/digest.d.ts +68 -0
  232. package/clinkx-workflows/dist/plan-review/digest.js +241 -0
  233. package/clinkx-workflows/dist/plan-review/digest.js.map +1 -0
  234. package/clinkx-workflows/dist/plan-review/discovery.d.ts +45 -0
  235. package/clinkx-workflows/dist/plan-review/discovery.js +36 -0
  236. package/clinkx-workflows/dist/plan-review/discovery.js.map +1 -0
  237. package/clinkx-workflows/dist/plan-review/execution-contract.d.ts +298 -0
  238. package/clinkx-workflows/dist/plan-review/execution-contract.js +194 -0
  239. package/clinkx-workflows/dist/plan-review/execution-contract.js.map +1 -0
  240. package/clinkx-workflows/dist/plan-review/expander.d.ts +48 -0
  241. package/clinkx-workflows/dist/plan-review/expander.js +243 -0
  242. package/clinkx-workflows/dist/plan-review/expander.js.map +1 -0
  243. package/clinkx-workflows/dist/plan-review/projector.d.ts +126 -0
  244. package/clinkx-workflows/dist/plan-review/projector.js +437 -0
  245. package/clinkx-workflows/dist/plan-review/projector.js.map +1 -0
  246. package/clinkx-workflows/dist/plan-review/publication.d.ts +95 -0
  247. package/clinkx-workflows/dist/plan-review/publication.js +182 -0
  248. package/clinkx-workflows/dist/plan-review/publication.js.map +1 -0
  249. package/clinkx-workflows/dist/plan-review/ranking-reducer.d.ts +34 -0
  250. package/clinkx-workflows/dist/plan-review/ranking-reducer.js +139 -0
  251. package/clinkx-workflows/dist/plan-review/ranking-reducer.js.map +1 -0
  252. package/clinkx-workflows/dist/plan-review/resume.d.ts +38 -0
  253. package/clinkx-workflows/dist/plan-review/resume.js +122 -0
  254. package/clinkx-workflows/dist/plan-review/resume.js.map +1 -0
  255. package/clinkx-workflows/dist/plan-review/rubric.d.ts +7 -0
  256. package/clinkx-workflows/dist/plan-review/rubric.js +24 -0
  257. package/clinkx-workflows/dist/plan-review/rubric.js.map +1 -0
  258. package/clinkx-workflows/dist/plan-review/run-state.d.ts +398 -0
  259. package/clinkx-workflows/dist/plan-review/run-state.js +183 -0
  260. package/clinkx-workflows/dist/plan-review/run-state.js.map +1 -0
  261. package/clinkx-workflows/dist/plan-review/runtime-policy.d.ts +74 -0
  262. package/clinkx-workflows/dist/plan-review/runtime-policy.js +186 -0
  263. package/clinkx-workflows/dist/plan-review/runtime-policy.js.map +1 -0
  264. package/clinkx-workflows/dist/plan-review/snapshot.d.ts +81 -0
  265. package/clinkx-workflows/dist/plan-review/snapshot.js +170 -0
  266. package/clinkx-workflows/dist/plan-review/snapshot.js.map +1 -0
  267. package/clinkx-workflows/dist/plan-review/stage-error.d.ts +19 -0
  268. package/clinkx-workflows/dist/plan-review/stage-error.js +20 -0
  269. package/clinkx-workflows/dist/plan-review/stage-error.js.map +1 -0
  270. package/clinkx-workflows/dist/post-processors/index.d.ts +20 -0
  271. package/clinkx-workflows/dist/post-processors/index.js +21 -0
  272. package/clinkx-workflows/dist/post-processors/index.js.map +1 -0
  273. package/clinkx-workflows/dist/prompt-budget.d.ts +7 -0
  274. package/clinkx-workflows/dist/prompt-budget.js +7 -1
  275. package/clinkx-workflows/dist/prompt-budget.js.map +1 -1
  276. package/clinkx-workflows/dist/reducers/index.js +2 -0
  277. package/clinkx-workflows/dist/reducers/index.js.map +1 -1
  278. package/clinkx-workflows/dist/roster-contract.d.ts +889 -0
  279. package/clinkx-workflows/dist/roster-contract.js +180 -0
  280. package/clinkx-workflows/dist/roster-contract.js.map +1 -0
  281. package/clinkx-workflows/dist/rosters.d.ts +14 -0
  282. package/clinkx-workflows/dist/rosters.js +169 -0
  283. package/clinkx-workflows/dist/rosters.js.map +1 -0
  284. package/clinkx-workflows/dist/run-health.d.ts +7 -0
  285. package/clinkx-workflows/dist/run-health.js +25 -0
  286. package/clinkx-workflows/dist/run-health.js.map +1 -0
  287. package/clinkx-workflows/dist/run-lock-heartbeat.d.ts +2 -0
  288. package/clinkx-workflows/dist/run-lock-heartbeat.js +3 -0
  289. package/clinkx-workflows/dist/run-lock-heartbeat.js.map +1 -0
  290. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.d.ts +34 -0
  291. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js +74 -0
  292. package/clinkx-workflows/dist/run-summary/cards/council-plan-review.js.map +1 -0
  293. package/clinkx-workflows/dist/run-summary/cards/index.js +5 -0
  294. package/clinkx-workflows/dist/run-summary/cards/index.js.map +1 -1
  295. package/clinkx-workflows/dist/run-summary.d.ts +13 -0
  296. package/clinkx-workflows/dist/run-summary.js +29 -1
  297. package/clinkx-workflows/dist/run-summary.js.map +1 -1
  298. package/clinkx-workflows/dist/schema.d.ts +140 -21
  299. package/clinkx-workflows/dist/schema.js +34 -1
  300. package/clinkx-workflows/dist/schema.js.map +1 -1
  301. package/clinkx-workflows/dist/seed-terms.d.ts +30 -0
  302. package/clinkx-workflows/dist/seed-terms.js +176 -0
  303. package/clinkx-workflows/dist/seed-terms.js.map +1 -0
  304. package/clinkx-workflows/dist/server.d.ts +5 -2
  305. package/clinkx-workflows/dist/server.js +84 -52
  306. package/clinkx-workflows/dist/server.js.map +1 -1
  307. package/clinkx-workflows/dist/shutdown.d.ts +29 -18
  308. package/clinkx-workflows/dist/shutdown.js +90 -64
  309. package/clinkx-workflows/dist/shutdown.js.map +1 -1
  310. package/clinkx-workflows/dist/state-schema.d.ts +61 -0
  311. package/clinkx-workflows/dist/state.d.ts +175 -3
  312. package/clinkx-workflows/dist/state.js +1398 -44
  313. package/clinkx-workflows/dist/state.js.map +1 -1
  314. package/clinkx-workflows/dist/stdio-watchdog.d.ts +52 -0
  315. package/clinkx-workflows/dist/stdio-watchdog.js +212 -0
  316. package/clinkx-workflows/dist/stdio-watchdog.js.map +1 -0
  317. package/clinkx-workflows/dist/transport.d.ts +15 -0
  318. package/clinkx-workflows/dist/transport.js +19 -10
  319. package/clinkx-workflows/dist/transport.js.map +1 -1
  320. package/clinkx-workflows/dist/types.d.ts +20 -2
  321. package/clinkx-workflows/dist/validators/council.d.ts +334 -101
  322. package/clinkx-workflows/dist/validators/council.js +52 -13
  323. package/clinkx-workflows/dist/validators/council.js.map +1 -1
  324. package/clinkx-workflows/dist/validators/index.d.ts +1 -0
  325. package/clinkx-workflows/dist/validators/index.js +3 -0
  326. package/clinkx-workflows/dist/validators/index.js.map +1 -1
  327. package/clinkx-workflows/dist/validators/plan-review.d.ts +398 -0
  328. package/clinkx-workflows/dist/validators/plan-review.js +316 -0
  329. package/clinkx-workflows/dist/validators/plan-review.js.map +1 -0
  330. package/clinkx-workflows/dist/workflow-receipt.js +188 -4
  331. package/clinkx-workflows/dist/workflow-receipt.js.map +1 -1
  332. package/clinkx-workflows/dist/workflow-tools.d.ts +118 -3
  333. package/clinkx-workflows/dist/workflow-tools.js +2110 -175
  334. package/clinkx-workflows/dist/workflow-tools.js.map +1 -1
  335. package/clinkx-workflows/dist/workspace-signature.d.ts +16 -0
  336. package/clinkx-workflows/dist/workspace-signature.js +388 -0
  337. package/clinkx-workflows/dist/workspace-signature.js.map +1 -0
  338. package/clinkx-workflows/templates/council/answer-chairman.txt +3 -0
  339. package/clinkx-workflows/templates/council/answer-cross-reviewer.txt +3 -0
  340. package/clinkx-workflows/templates/council/answer-specialist.txt +5 -1
  341. package/clinkx-workflows/templates/council/chairman.txt +3 -0
  342. package/clinkx-workflows/templates/council/code-review-chairman.txt +16 -0
  343. package/clinkx-workflows/templates/council/code-review-cross-reviewer.txt +7 -1
  344. package/clinkx-workflows/templates/council/code-review-reviewer.txt +22 -3
  345. package/clinkx-workflows/templates/council/debug-chairman.txt +3 -0
  346. package/clinkx-workflows/templates/council/debug-cross-reviewer.txt +3 -0
  347. package/clinkx-workflows/templates/council/debug-debugger.txt +5 -1
  348. package/clinkx-workflows/templates/council/discover-analyst.txt +5 -1
  349. package/clinkx-workflows/templates/council/discover-chairman.txt +3 -0
  350. package/clinkx-workflows/templates/council/discover-cross-reviewer.txt +3 -0
  351. package/clinkx-workflows/templates/council/member.txt +5 -1
  352. package/clinkx-workflows/templates/council/plan-review-chairman.txt +71 -0
  353. package/clinkx-workflows/templates/council/plan-review-cross-reviewer.txt +54 -0
  354. package/clinkx-workflows/templates/council/plan-review-reviewer.txt +63 -0
  355. package/clinkx-workflows/templates/council/reviewer.txt +3 -0
  356. package/clinkx-workflows/templates/plan-review/chairman.txt +48 -0
  357. package/clinkx-workflows/templates/plan-review/cross-review.txt +52 -0
  358. package/clinkx-workflows/templates/plan-review/ranking.txt +16 -0
  359. package/clinkx-workflows/templates/plan-review/reviewer.txt +57 -0
  360. package/clinkx-workflows/workflows/council-answer.yaml +100 -8
  361. package/clinkx-workflows/workflows/council-code-review.yaml +106 -8
  362. package/clinkx-workflows/workflows/council-debug.yaml +100 -8
  363. package/clinkx-workflows/workflows/council-default.yaml +104 -8
  364. package/clinkx-workflows/workflows/council-discover.yaml +100 -8
  365. package/clinkx-workflows/workflows/council-plan-review.yaml +253 -0
  366. package/clinkx-workflows/workflows/plan-review-receipts.yaml +230 -0
  367. package/conf/adapters/agy.json +56 -0
  368. package/conf/adapters/claude.json +12 -0
  369. package/conf/adapters/codex-app-server.json +44 -0
  370. package/conf/adapters/codex.json +14 -2
  371. package/conf/adapters/glm.json +24 -3
  372. package/conf/adapters/grok.json +67 -0
  373. package/conf/adapters/hapi/agy.json +59 -0
  374. package/conf/adapters/hapi/claude.json +12 -0
  375. package/conf/adapters/hapi/codex.json +14 -2
  376. package/conf/adapters/hapi/glm.json +25 -4
  377. package/conf/rosters/default-council.json +21 -0
  378. package/dist/activity-journal.d.ts +56 -0
  379. package/dist/activity-journal.js +706 -0
  380. package/dist/activity-journal.js.map +1 -0
  381. package/dist/allowed-roots.d.ts +41 -0
  382. package/dist/allowed-roots.js +125 -3
  383. package/dist/allowed-roots.js.map +1 -1
  384. package/dist/codex/activity-session-log.d.ts +14 -0
  385. package/dist/codex/activity-session-log.js +206 -0
  386. package/dist/codex/activity-session-log.js.map +1 -0
  387. package/dist/codex/agent-tools.d.ts +23 -0
  388. package/dist/codex/agent-tools.js +1306 -0
  389. package/dist/codex/agent-tools.js.map +1 -0
  390. package/dist/codex/app-server-client.d.ts +79 -0
  391. package/dist/codex/app-server-client.js +593 -0
  392. package/dist/codex/app-server-client.js.map +1 -0
  393. package/dist/codex/app-server-registry.d.ts +407 -0
  394. package/dist/codex/app-server-registry.js +1493 -0
  395. package/dist/codex/app-server-registry.js.map +1 -0
  396. package/dist/codex/client-types.d.ts +115 -0
  397. package/dist/codex/client-types.js +2 -0
  398. package/dist/codex/client-types.js.map +1 -0
  399. package/dist/codex/protocol.d.ts +5375 -0
  400. package/dist/codex/protocol.js +614 -0
  401. package/dist/codex/protocol.js.map +1 -0
  402. package/dist/codex/session.d.ts +263 -0
  403. package/dist/codex/session.js +466 -0
  404. package/dist/codex/session.js.map +1 -0
  405. package/dist/config.d.ts +386 -23
  406. package/dist/config.js +184 -15
  407. package/dist/config.js.map +1 -1
  408. package/dist/context-builder/anchors.d.ts +1 -0
  409. package/dist/context-builder/anchors.js +3 -0
  410. package/dist/context-builder/anchors.js.map +1 -1
  411. package/dist/context-builder/bundle.d.ts +48 -2
  412. package/dist/context-builder/bundle.js +388 -2417
  413. package/dist/context-builder/bundle.js.map +1 -1
  414. package/dist/context-builder/codemap.d.ts +5 -0
  415. package/dist/context-builder/codemap.js +387 -13
  416. package/dist/context-builder/codemap.js.map +1 -1
  417. package/dist/context-builder/config.d.ts +1 -0
  418. package/dist/context-builder/config.js +12 -0
  419. package/dist/context-builder/config.js.map +1 -1
  420. package/dist/context-builder/context-run-catalog.d.ts +17 -0
  421. package/dist/context-builder/context-run-catalog.js +99 -0
  422. package/dist/context-builder/context-run-catalog.js.map +1 -0
  423. package/dist/context-builder/delegate-finalizer-effective.d.ts +33 -0
  424. package/dist/context-builder/delegate-finalizer-effective.js +82 -0
  425. package/dist/context-builder/delegate-finalizer-effective.js.map +1 -0
  426. package/dist/context-builder/delegate-packet.d.ts +55 -0
  427. package/dist/context-builder/delegate-packet.js +430 -74
  428. package/dist/context-builder/delegate-packet.js.map +1 -1
  429. package/dist/context-builder/delegate-reconstruction.d.ts +29 -0
  430. package/dist/context-builder/delegate-reconstruction.js +110 -41
  431. package/dist/context-builder/delegate-reconstruction.js.map +1 -1
  432. package/dist/context-builder/delegate-validation.d.ts +41 -1
  433. package/dist/context-builder/delegate-validation.js +237 -48
  434. package/dist/context-builder/delegate-validation.js.map +1 -1
  435. package/dist/context-builder/delegate.d.ts +11 -0
  436. package/dist/context-builder/delegate.js +210 -58
  437. package/dist/context-builder/delegate.js.map +1 -1
  438. package/dist/context-builder/diagnostics.d.ts +11 -2
  439. package/dist/context-builder/diagnostics.js +39 -6
  440. package/dist/context-builder/diagnostics.js.map +1 -1
  441. package/dist/context-builder/discovery-progress.d.ts +25 -0
  442. package/dist/context-builder/discovery-progress.js +64 -0
  443. package/dist/context-builder/discovery-progress.js.map +1 -0
  444. package/dist/context-builder/discovery-watchdog.d.ts +15 -0
  445. package/dist/context-builder/discovery-watchdog.js +39 -0
  446. package/dist/context-builder/discovery-watchdog.js.map +1 -0
  447. package/dist/context-builder/discovery.d.ts +36 -1
  448. package/dist/context-builder/discovery.js +584 -172
  449. package/dist/context-builder/discovery.js.map +1 -1
  450. package/dist/context-builder/external-gaps.d.ts +73 -0
  451. package/dist/context-builder/external-gaps.js +248 -0
  452. package/dist/context-builder/external-gaps.js.map +1 -0
  453. package/dist/context-builder/globs.d.ts +1 -0
  454. package/dist/context-builder/globs.js +25 -10
  455. package/dist/context-builder/globs.js.map +1 -1
  456. package/dist/context-builder/handler.d.ts +67 -2
  457. package/dist/context-builder/handler.js +1810 -2440
  458. package/dist/context-builder/handler.js.map +1 -1
  459. package/dist/context-builder/invocation-sidecar.d.ts +41 -0
  460. package/dist/context-builder/invocation-sidecar.js +189 -0
  461. package/dist/context-builder/invocation-sidecar.js.map +1 -0
  462. package/dist/context-builder/lifecycle-contract.d.ts +424 -0
  463. package/dist/context-builder/lifecycle-contract.js +523 -0
  464. package/dist/context-builder/lifecycle-contract.js.map +1 -0
  465. package/dist/context-builder/lifecycle-journal.d.ts +62 -0
  466. package/dist/context-builder/lifecycle-journal.js +88 -0
  467. package/dist/context-builder/lifecycle-journal.js.map +1 -0
  468. package/dist/context-builder/lifecycle-observer.d.ts +102 -0
  469. package/dist/context-builder/lifecycle-observer.js +258 -0
  470. package/dist/context-builder/lifecycle-observer.js.map +1 -0
  471. package/dist/context-builder/lifecycle-reconcile.d.ts +32 -0
  472. package/dist/context-builder/lifecycle-reconcile.js +191 -0
  473. package/dist/context-builder/lifecycle-reconcile.js.map +1 -0
  474. package/dist/context-builder/lifecycle-recovery.d.ts +2 -0
  475. package/dist/context-builder/lifecycle-recovery.js +14 -0
  476. package/dist/context-builder/lifecycle-recovery.js.map +1 -0
  477. package/dist/context-builder/mutation-lock.d.ts +32 -0
  478. package/dist/context-builder/mutation-lock.js +321 -0
  479. package/dist/context-builder/mutation-lock.js.map +1 -0
  480. package/dist/context-builder/path-signals.js +9 -5
  481. package/dist/context-builder/path-signals.js.map +1 -1
  482. package/dist/context-builder/progress.d.ts +3 -1
  483. package/dist/context-builder/progress.js +23 -0
  484. package/dist/context-builder/progress.js.map +1 -1
  485. package/dist/context-builder/receipt.d.ts +6 -4
  486. package/dist/context-builder/receipt.js +12 -1
  487. package/dist/context-builder/receipt.js.map +1 -1
  488. package/dist/context-builder/refine-handler.d.ts +14 -0
  489. package/dist/context-builder/refine-handler.js +790 -0
  490. package/dist/context-builder/refine-handler.js.map +1 -0
  491. package/dist/context-builder/review-coverage.d.ts +116 -0
  492. package/dist/context-builder/review-coverage.js +544 -0
  493. package/dist/context-builder/review-coverage.js.map +1 -0
  494. package/dist/context-builder/schema.d.ts +315 -2
  495. package/dist/context-builder/schema.js +133 -7
  496. package/dist/context-builder/schema.js.map +1 -1
  497. package/dist/context-builder/secrets.d.ts +59 -0
  498. package/dist/context-builder/secrets.js +2255 -44
  499. package/dist/context-builder/secrets.js.map +1 -1
  500. package/dist/context-builder/selection.d.ts +7 -3
  501. package/dist/context-builder/selection.js +200 -738
  502. package/dist/context-builder/selection.js.map +1 -1
  503. package/dist/context-builder/session-log-identity.d.ts +1 -0
  504. package/dist/context-builder/session-log-identity.js +66 -0
  505. package/dist/context-builder/session-log-identity.js.map +1 -0
  506. package/dist/context-builder/slice-range-math.d.ts +16 -0
  507. package/dist/context-builder/slice-range-math.js +137 -0
  508. package/dist/context-builder/slice-range-math.js.map +1 -0
  509. package/dist/context-builder/status.d.ts +23 -2
  510. package/dist/context-builder/status.js +60 -4
  511. package/dist/context-builder/status.js.map +1 -1
  512. package/dist/context-builder/storage.d.ts +59 -2
  513. package/dist/context-builder/storage.js +500 -168
  514. package/dist/context-builder/storage.js.map +1 -1
  515. package/dist/context-builder/token-estimate.d.ts +13 -2
  516. package/dist/context-builder/token-estimate.js +77 -4
  517. package/dist/context-builder/token-estimate.js.map +1 -1
  518. package/dist/context-builder/versions.d.ts +44 -0
  519. package/dist/context-builder/versions.js +45 -0
  520. package/dist/context-builder/versions.js.map +1 -0
  521. package/dist/context-builder/workflow-runner.d.ts +75 -0
  522. package/dist/context-builder/workflow-runner.js +1241 -0
  523. package/dist/context-builder/workflow-runner.js.map +1 -0
  524. package/dist/context-builder/write-watchdog.d.ts +14 -0
  525. package/dist/context-builder/write-watchdog.js +38 -0
  526. package/dist/context-builder/write-watchdog.js.map +1 -0
  527. package/dist/dashboard-adapter-catalog.d.ts +9 -0
  528. package/dist/dashboard-adapter-catalog.js +53 -0
  529. package/dist/dashboard-adapter-catalog.js.map +1 -0
  530. package/dist/dashboard-cli.d.ts +81 -0
  531. package/dist/dashboard-cli.js +623 -0
  532. package/dist/dashboard-cli.js.map +1 -0
  533. package/dist/dashboard-companion/client.d.ts +37 -0
  534. package/dist/dashboard-companion/client.js +229 -0
  535. package/dist/dashboard-companion/client.js.map +1 -0
  536. package/dist/dashboard-companion/config.d.ts +8 -0
  537. package/dist/dashboard-companion/config.js +52 -0
  538. package/dist/dashboard-companion/config.js.map +1 -0
  539. package/dist/dashboard-companion/http-proxy.d.ts +10 -0
  540. package/dist/dashboard-companion/http-proxy.js +234 -0
  541. package/dist/dashboard-companion/http-proxy.js.map +1 -0
  542. package/dist/dashboard-companion/protocol.d.ts +80 -0
  543. package/dist/dashboard-companion/protocol.js +458 -0
  544. package/dist/dashboard-companion/protocol.js.map +1 -0
  545. package/dist/dashboard-companion/standalone-assets.d.ts +12 -0
  546. package/dist/dashboard-companion/standalone-assets.js +72 -0
  547. package/dist/dashboard-companion/standalone-assets.js.map +1 -0
  548. package/dist/dashboard-companion/standalone-entry.d.ts +1 -0
  549. package/dist/dashboard-companion/standalone-entry.js +17 -0
  550. package/dist/dashboard-companion/standalone-entry.js.map +1 -0
  551. package/dist/dashboard-gateway/config.d.ts +9 -0
  552. package/dist/dashboard-gateway/config.js +44 -0
  553. package/dist/dashboard-gateway/config.js.map +1 -0
  554. package/dist/dashboard-gateway/index.d.ts +12 -0
  555. package/dist/dashboard-gateway/index.js +786 -0
  556. package/dist/dashboard-gateway/index.js.map +1 -0
  557. package/dist/dashboard-gateway/main.d.ts +1 -0
  558. package/dist/dashboard-gateway/main.js +25 -0
  559. package/dist/dashboard-gateway/main.js.map +1 -0
  560. package/dist/dashboard-pairing-token.d.ts +1 -0
  561. package/dist/dashboard-pairing-token.js +5 -0
  562. package/dist/dashboard-pairing-token.js.map +1 -0
  563. package/dist/elaris/client.d.ts +73 -0
  564. package/dist/elaris/client.js +825 -0
  565. package/dist/elaris/client.js.map +1 -0
  566. package/dist/elaris/config.d.ts +18 -0
  567. package/dist/elaris/config.js +75 -0
  568. package/dist/elaris/config.js.map +1 -0
  569. package/dist/elaris/errors.d.ts +41 -0
  570. package/dist/elaris/errors.js +205 -0
  571. package/dist/elaris/errors.js.map +1 -0
  572. package/dist/elaris/github-repo.d.ts +18 -0
  573. package/dist/elaris/github-repo.js +89 -0
  574. package/dist/elaris/github-repo.js.map +1 -0
  575. package/dist/elaris/index.d.ts +10 -0
  576. package/dist/elaris/index.js +11 -0
  577. package/dist/elaris/index.js.map +1 -0
  578. package/dist/elaris/redact.d.ts +89 -0
  579. package/dist/elaris/redact.js +423 -0
  580. package/dist/elaris/redact.js.map +1 -0
  581. package/dist/elaris/run-id.d.ts +5 -0
  582. package/dist/elaris/run-id.js +20 -0
  583. package/dist/elaris/run-id.js.map +1 -0
  584. package/dist/elaris/status.d.ts +45 -0
  585. package/dist/elaris/status.js +122 -0
  586. package/dist/elaris/status.js.map +1 -0
  587. package/dist/elaris/tool-schemas.d.ts +314 -0
  588. package/dist/elaris/tool-schemas.js +208 -0
  589. package/dist/elaris/tool-schemas.js.map +1 -0
  590. package/dist/elaris/tools.d.ts +73 -0
  591. package/dist/elaris/tools.js +1065 -0
  592. package/dist/elaris/tools.js.map +1 -0
  593. package/dist/elaris/types.d.ts +1541 -0
  594. package/dist/elaris/types.js +252 -0
  595. package/dist/elaris/types.js.map +1 -0
  596. package/dist/errors.d.ts +16 -0
  597. package/dist/errors.js +19 -0
  598. package/dist/errors.js.map +1 -1
  599. package/dist/handler.d.ts +4 -0
  600. package/dist/handler.js +15 -2
  601. package/dist/handler.js.map +1 -1
  602. package/dist/index.d.ts +20 -2
  603. package/dist/index.js +154 -6
  604. package/dist/index.js.map +1 -1
  605. package/dist/local-clink-backend.d.ts +3 -0
  606. package/dist/local-clink-backend.js +4 -0
  607. package/dist/local-clink-backend.js.map +1 -1
  608. package/dist/logger.d.ts +1 -1
  609. package/dist/logger.js +18 -3
  610. package/dist/logger.js.map +1 -1
  611. package/dist/package-info.d.ts +2 -0
  612. package/dist/package-info.js +18 -0
  613. package/dist/package-info.js.map +1 -0
  614. package/dist/parsers/agy-stream-json.d.ts +14 -0
  615. package/dist/parsers/agy-stream-json.js +172 -0
  616. package/dist/parsers/agy-stream-json.js.map +1 -0
  617. package/dist/parsers/codex-jsonl.js +48 -3
  618. package/dist/parsers/codex-jsonl.js.map +1 -1
  619. package/dist/parsers/extract.d.ts +10 -1
  620. package/dist/parsers/extract.js +16 -4
  621. package/dist/parsers/extract.js.map +1 -1
  622. package/dist/parsers/observed-invocation.d.ts +20 -0
  623. package/dist/parsers/observed-invocation.js +42 -0
  624. package/dist/parsers/observed-invocation.js.map +1 -0
  625. package/dist/pipeline.d.ts +17 -0
  626. package/dist/pipeline.js +181 -10
  627. package/dist/pipeline.js.map +1 -1
  628. package/dist/progress-report/lock.d.ts +74 -0
  629. package/dist/progress-report/lock.js +343 -0
  630. package/dist/progress-report/lock.js.map +1 -0
  631. package/dist/progress-report/schema.d.ts +808 -0
  632. package/dist/progress-report/schema.js +119 -0
  633. package/dist/progress-report/schema.js.map +1 -0
  634. package/dist/progress-report/store.d.ts +66 -0
  635. package/dist/progress-report/store.js +766 -0
  636. package/dist/progress-report/store.js.map +1 -0
  637. package/dist/progress-report/tools.d.ts +20 -0
  638. package/dist/progress-report/tools.js +141 -0
  639. package/dist/progress-report/tools.js.map +1 -0
  640. package/dist/prompt-mode.d.ts +1 -1
  641. package/dist/prompt-mode.js +7 -2
  642. package/dist/prompt-mode.js.map +1 -1
  643. package/dist/run-dir.d.ts +24 -0
  644. package/dist/run-dir.js +201 -2
  645. package/dist/run-dir.js.map +1 -1
  646. package/dist/runner.d.ts +20 -0
  647. package/dist/runner.js +192 -16
  648. package/dist/runner.js.map +1 -1
  649. package/dist/safety.d.ts +54 -4
  650. package/dist/safety.js +186 -5
  651. package/dist/safety.js.map +1 -1
  652. package/dist/schema.d.ts +15 -7
  653. package/dist/schema.js +20 -0
  654. package/dist/schema.js.map +1 -1
  655. package/dist/server.d.ts +7 -1
  656. package/dist/server.js +451 -23
  657. package/dist/server.js.map +1 -1
  658. package/dist/session-log-stream.d.ts +18 -0
  659. package/dist/session-log-stream.js +566 -0
  660. package/dist/session-log-stream.js.map +1 -0
  661. package/dist/stdio-activity.d.ts +28 -0
  662. package/dist/stdio-activity.js +72 -0
  663. package/dist/stdio-activity.js.map +1 -0
  664. package/package.json +47 -5
  665. package/conf/adapters/gemini.json +0 -44
  666. package/conf/adapters/hapi/gemini.json +0 -47
  667. package/dist/parsers/gemini-json.d.ts +0 -2
  668. package/dist/parsers/gemini-json.js +0 -72
  669. package/dist/parsers/gemini-json.js.map +0 -1
@@ -62,29 +62,190 @@
62
62
  * isError: true with termination_reason: "wall" or "idle", which the
63
63
  * workflow engine treats as a transient failure eligible for retry.
64
64
  */
65
+ import { closeSync, fstatSync, openSync, readSync } from "node:fs";
65
66
  import { stat as fsStat } from "node:fs/promises";
66
- import { isAbsolute, join, resolve } from "node:path";
67
- import { callClink } from "./clink-client.js";
68
- import { getArtifactsDir, getChildMaxConcurrent, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
69
- import { CallExecutionError, InvalidCallError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
70
- import { analyzeOutputFidelity, enforceOutputFidelity } from "./fidelity.js";
67
+ import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
68
+ import { basename, isAbsolute, join, resolve, sep } from "node:path";
69
+ import { callClink, extractObservedInvocation } from "./clink-client.js";
70
+ import { getArtifactsDir, getChildMaxConcurrent, getWorkflowsContextDefault, getWorkflowsContextDefaultCurator, getMaxParallel, getMaxPromptBytes, getStateDir, resolveWorkspaceRoot, } from "./config.js";
71
+ import { validatePlanReviewCallBindings, validatePlanReviewPreparedInputPolicy, } from "./plan-review/runtime-policy.js";
72
+ import { CouncilPlanReviewErrorSchema, } from "./plan-review/publication.js";
73
+ import { PlanReviewStageError } from "./plan-review/stage-error.js";
74
+ import { CallExecutionError, InvalidCallError, WorkflowDispatchDeadlineError, WorkflowCancellationError, WorkflowError, } from "./errors.js";
75
+ import { analyzeOutputFidelity, enforceOutputFidelity, enforceTypedResultFidelity, TypedResultFidelityError, } from "./fidelity.js";
76
+ import { monotonicNowMs } from "./monotonic-clock.js";
71
77
  import { validateDynamicPaths } from "./path-validation.js";
72
- import { estimatePromptBudget, enforcePromptBudget, createPathContributor } from "./prompt-budget.js";
78
+ import { DEFAULT_RESERVE_BYTES, estimatePromptBudget, enforcePromptBudget, createPathContributor, } from "./prompt-budget.js";
79
+ import { buildCouncilDeadlineGuidance, COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES, isCouncilExecutionWorkflow, shouldVetoWallTimeoutRetry, } from "./council-execution-policy.js";
73
80
  import { DispatchQueue } from "./queue.js";
74
81
  import { renderCallPrompt } from "./template.js";
75
- import { ArtifactStore } from "./artifacts.js";
82
+ import { ArtifactStore, OutputSizeError, SchemaValidationError } from "./artifacts.js";
83
+ import { projectPlanReviewSemanticBody, renderPlanSnapshotBlock, } from "./plan-review/expander.js";
84
+ import { filterExcludedPlanPaths, foldPlanReviewExcludeGlobs, } from "./plan-review/context-policy.js";
76
85
  import { getReducer } from "./reducers/index.js";
86
+ import { getPostProcessor } from "./post-processors/index.js";
87
+ import { addPathWithBasename, buildAllowlistFromDiff } from "./citation-check.js";
77
88
  import { logger } from "./logger.js";
89
+ import { resolveWorkflowContextMode, WorkflowContextModeResolverError, } from "./context-mode-resolver.js";
90
+ import { WorkflowDelegateValidationError } from "./delegate-validate.js";
91
+ import { isDelegateExternalGapDegrade } from "./delegate-signature.js";
92
+ import { contextSeedSourceValidationError, resolveSeedSource, synthesizeSeedTerms, } from "./seed-terms.js";
93
+ /**
94
+ * Declarations for variables the ENGINE derives at runtime (not declared in the
95
+ * workflow YAML or accepted as caller input). These are merged into the
96
+ * declaration map passed to the template renderer so that templates may
97
+ * reference them ({{context_available}}) without `template.ts` rejecting them as
98
+ * "Unknown variable", while genuinely-unknown placeholders are still rejected.
99
+ *
100
+ * The VALUES are produced by the engine (e.g. {@link deriveContextAvailable},
101
+ * which always overwrites any caller-supplied `context_available` before stages
102
+ * run), so listing them here does NOT widen the public caller-input surface.
103
+ */
104
+ const ENGINE_DERIVED_VARIABLE_DECLS = {
105
+ context_available: {
106
+ type: "string",
107
+ required: false,
108
+ description: "Engine-derived: whether repository context is attached to the run",
109
+ },
110
+ context_quality: {
111
+ type: "string",
112
+ required: false,
113
+ description: "Engine-derived: effective context coverage quality",
114
+ },
115
+ };
78
116
  /** Default parent-side timeout for a single tools/call RPC (10 minutes). */
79
117
  const DEFAULT_PARENT_CALL_TIMEOUT_MS = 600_000;
80
118
  /** Buffer added to per-call timeout to account for child-side teardown latency. */
81
119
  const CALL_TIMEOUT_BUFFER_MS = 5_000;
120
+ const preflightFallbackAggregate = {
121
+ curated_count: 0,
122
+ fallback_count: 0,
123
+ };
124
+ function orderedUnique(values) {
125
+ const seen = new Set();
126
+ const result = [];
127
+ for (const value of values) {
128
+ if (seen.has(value)) {
129
+ continue;
130
+ }
131
+ seen.add(value);
132
+ result.push(value);
133
+ }
134
+ return result;
135
+ }
136
+ /**
137
+ * Maps a runner's {@link PreflightContextResult} to the persisted {@link WorkflowContext}.
138
+ * Single source of truth for this mapping: the transient `receipt` field is dropped, and
139
+ * optional `delegate` metadata is spread only when present.
140
+ */
141
+ export function mapPreflightResultToWorkflowContext(result, contextMode, lightCaller) {
142
+ // Mirror the engine's bundle-attachment decision exactly: bundle artifacts are
143
+ // appended to context_bundle_files IFF files_selected > 0 (see ~line 1090). On a
144
+ // zero-selection run we proceed WITHOUT a bundle, so the reported copied_paths
145
+ // must be empty too — otherwise the receipt would imply a bundle was attached.
146
+ const copied_paths = result.metrics.files_selected > 0 ? result.copied_paths : [];
147
+ return {
148
+ context_builder: {
149
+ source_run_id: result.source_run_id,
150
+ source_artifact_dir: result.source_artifact_dir,
151
+ cache: result.cache,
152
+ schema_version: result.schema_version,
153
+ status: result.status,
154
+ ...(contextMode !== undefined ? { context_mode: contextMode } : {}),
155
+ copied_paths,
156
+ ...(result.coverage !== undefined ? { coverage: result.coverage } : {}),
157
+ metrics: result.metrics,
158
+ warning_count: result.warning_count,
159
+ omission_count: result.omission_count,
160
+ ...(result.delegate !== undefined ? { delegate: result.delegate } : {}),
161
+ },
162
+ ...(lightCaller !== undefined ? { light_caller: lightCaller } : {}),
163
+ };
164
+ }
165
+ export function classifyPreflightPartial(metrics) {
166
+ if (metrics.truncated ||
167
+ metrics.scan_cap_hit ||
168
+ metrics.search_cap_hit ||
169
+ metrics.discovery_byte_cap_hit ||
170
+ metrics.discovery_candidate_cap_hit ||
171
+ metrics.candidate_cap_hit) {
172
+ return { severity: "degraded", partial_reason: "cap_or_truncation" };
173
+ }
174
+ return { severity: "benign", partial_reason: "omissions_only" };
175
+ }
176
+ function recordPreflightFallbackAggregate(input) {
177
+ if (input.delegateEnabled) {
178
+ if (input.fallbackUsed) {
179
+ preflightFallbackAggregate.fallback_count += 1;
180
+ }
181
+ else {
182
+ preflightFallbackAggregate.curated_count += 1;
183
+ }
184
+ }
185
+ const total = preflightFallbackAggregate.curated_count +
186
+ preflightFallbackAggregate.fallback_count;
187
+ return {
188
+ curated_count: preflightFallbackAggregate.curated_count,
189
+ fallback_count: preflightFallbackAggregate.fallback_count,
190
+ total_delegate_count: total,
191
+ fallback_ratio: total === 0
192
+ ? 0
193
+ : preflightFallbackAggregate.fallback_count / total,
194
+ };
195
+ }
196
+ function firstReceiptWarningCode(result) {
197
+ const warnings = result.receipt["warnings"];
198
+ if (!Array.isArray(warnings)) {
199
+ return null;
200
+ }
201
+ for (const warning of warnings) {
202
+ if (warning !== null && typeof warning === "object") {
203
+ const code = warning["code"];
204
+ if (typeof code === "string" && code.trim() !== "") {
205
+ return code;
206
+ }
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ function receiptDelegateFailureReason(result) {
212
+ const delegate = result.receipt["delegate"];
213
+ if (delegate === null || typeof delegate !== "object") {
214
+ return null;
215
+ }
216
+ const failureReason = delegate["failure_reason"];
217
+ return typeof failureReason === "string" && failureReason.trim() !== ""
218
+ ? failureReason
219
+ : null;
220
+ }
221
+ /**
222
+ * The allowed roots a workflow engine derives for itself when the caller supplies
223
+ * none. Exported so admission-time path checks can validate against the SAME set
224
+ * the engine will enforce at dispatch, rather than re-deriving a near-copy.
225
+ *
226
+ * Note this set is engine-owned: `CLINKX_ALLOWED_ROOTS` is not consulted and
227
+ * cannot widen it.
228
+ */
229
+ export function defaultWorkflowAllowedRoots(runDir) {
230
+ const workspaceRoot = resolveWorkspaceRoot();
231
+ const stateDir = getStateDir();
232
+ const configuredArtifactsDir = getArtifactsDir();
233
+ return [
234
+ workspaceRoot,
235
+ stateDir,
236
+ configuredArtifactsDir,
237
+ runDir,
238
+ join(runDir, "artifacts"),
239
+ ];
240
+ }
82
241
  export class WorkflowEngine {
83
242
  definition;
84
243
  session;
85
244
  variables;
245
+ effectiveVariables;
86
246
  validatorRegistry;
87
247
  runId;
248
+ sessionLogGeneration;
88
249
  runDir;
89
250
  signal;
90
251
  queueDepth;
@@ -95,14 +256,44 @@ export class WorkflowEngine {
95
256
  resumeCompletedCalls;
96
257
  resumePreparedInputs;
97
258
  resumeFailedCalls;
259
+ resumeNextRetryCounts;
98
260
  contextProducingCalls = new Set();
261
+ planReviewDeliveredAliases = new Map();
99
262
  artifactStore;
263
+ contextBuilderRunner;
264
+ contextDefault;
265
+ defaultCurator;
266
+ commitPreflightContext;
267
+ restoredWorkflowContext;
268
+ requireContextPreflight;
269
+ initialContextPolicy;
270
+ planReviewContextPolicy;
271
+ planReviewCallBindings;
272
+ commitPlanReviewContextProvenance;
273
+ terminalResultPolicy;
274
+ deriveInvocationAudit;
275
+ /**
276
+ * Workflow budget expiry on the MONOTONIC clock (`monotonicNowMs()`), not on
277
+ * the wall clock. Never compare it to `Date.now()`; read it only through
278
+ * `workflowRemainingMs()`.
279
+ */
280
+ workflowDeadlineAtMonoMs;
281
+ /**
282
+ * `stage.id`.`call.id` keys already written to a terminal state by
283
+ * `emitTerminalCallAbort`. A parallel stage still surfaces that throw through
284
+ * `materializeUnexpectedFailure`, whose emit carries no startedAt/durationMs
285
+ * and attempt 1 / retryCount 0 — persisted unconditionally, it would erase the
286
+ * terminal entry's timing. Bounded by the run's call count.
287
+ */
288
+ terminalizedCallKeys = new Set();
100
289
  constructor(options) {
101
290
  this.definition = options.definition;
102
291
  this.session = options.session;
103
292
  this.variables = options.variables;
293
+ this.effectiveVariables = { ...options.variables };
104
294
  this.validatorRegistry = options.validatorRegistry;
105
295
  this.runId = options.runId;
296
+ this.sessionLogGeneration = options.sessionLogGeneration;
106
297
  this.runDir = options.runDir;
107
298
  this.signal = options.signal;
108
299
  this.queueDepth = Math.min(options.maxParallel ?? getMaxParallel(), options.childMaxConcurrent ?? getChildMaxConcurrent());
@@ -113,14 +304,35 @@ export class WorkflowEngine {
113
304
  this.resumeCompletedCalls = options.resume?.completedCalls ?? new Map();
114
305
  this.resumePreparedInputs = options.resume?.preparedInputs ?? new Map();
115
306
  this.resumeFailedCalls = options.resume?.failedCalls ?? new Map();
307
+ this.resumeNextRetryCounts = options.resume?.nextRetryCounts ?? new Map();
116
308
  this.artifactStore = new ArtifactStore(options.runDir, options.validatorRegistry);
309
+ this.contextBuilderRunner = options.contextBuilderRunner;
310
+ this.contextDefault = options.contextDefault ?? getWorkflowsContextDefault();
311
+ this.defaultCurator = options.defaultCurator ?? getWorkflowsContextDefaultCurator();
312
+ this.commitPreflightContext = options.commitPreflightContext;
313
+ this.restoredWorkflowContext = options.restoredWorkflowContext;
314
+ this.requireContextPreflight = options.requireContextPreflight ?? false;
315
+ this.initialContextPolicy = options.initialContextPolicy;
316
+ this.planReviewContextPolicy = options.planReviewContextPolicy;
317
+ this.planReviewCallBindings = options.planReviewCallBindings === undefined
318
+ ? new Map()
319
+ : validatePlanReviewCallBindings(options.definition, options.planReviewCallBindings);
320
+ validatePlanReviewPreparedInputPolicy(this.resumePreparedInputs, this.planReviewCallBindings, {
321
+ snapshotText: options.planReviewSnapshotText,
322
+ contextPolicy: options.planReviewContextPolicy,
323
+ });
324
+ this.commitPlanReviewContextProvenance = options.commitPlanReviewContextProvenance;
325
+ this.terminalResultPolicy = options.terminalResultPolicy ?? { kind: "final_call" };
326
+ this.deriveInvocationAudit = options.deriveInvocationAudit;
117
327
  this.validateFinalStage();
118
328
  this.validateAdvertisedCliNames();
119
329
  this.contextProducingCalls = collectContextProducingCalls(this.definition);
120
330
  this.seedResumedArtifacts();
121
331
  }
122
332
  async execute() {
123
- const deadlineController = this.createDeadlineController();
333
+ this.workflowDeadlineAtMonoMs =
334
+ monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
335
+ const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
124
336
  const stages = [];
125
337
  const startedAt = new Date().toISOString();
126
338
  try {
@@ -137,7 +349,7 @@ export class WorkflowEngine {
137
349
  state: "running",
138
350
  message: `Workflow ${this.definition.name} is running`,
139
351
  });
140
- await this.validateInputPathsExist();
352
+ await this.prepareContextPreflight(deadlineController.signal);
141
353
  for (let stageIndex = 0; stageIndex < this.definition.stages.length; stageIndex += 1) {
142
354
  this.throwIfAborted(deadlineController.signal);
143
355
  const stage = this.definition.stages[stageIndex];
@@ -169,14 +381,17 @@ export class WorkflowEngine {
169
381
  message: `Stage ${stage.id} ${executedStage.ok ? "succeeded" : "failed"}`,
170
382
  });
171
383
  if (!executedStage.ok && stage.on_failure === "abort") {
172
- throw new WorkflowError(`Stage ${stage.id} failed and on_failure=abort`);
384
+ // Generic boundary for ALL stage aborts, so it must stay permanent by
385
+ // default — a stage that aborts on an ordinary permanent call failure
386
+ // is not a replayable run. The one exception is a genuinely exhausted
387
+ // wall clock: a parallel stage funnels the deadline throw through
388
+ // materializeUnexpectedFailure, which drops the class, so without this
389
+ // the environmental deadline would persist permanent and re-arm the
390
+ // spurious duplicate_of_failed_run warning on an honest replay.
391
+ throw new WorkflowError(`Stage ${stage.id} failed and on_failure=abort`, this.isWorkflowDeadlineExhausted() ? "transient" : undefined);
173
392
  }
174
393
  }
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
- }
394
+ const outputText = await this.resolveTerminalResult(stages);
180
395
  const completedAt = new Date().toISOString();
181
396
  await this.emitRunStateChange({
182
397
  runId: this.runId,
@@ -193,7 +408,7 @@ export class WorkflowEngine {
193
408
  message: `Workflow ${this.definition.name} succeeded`,
194
409
  });
195
410
  return {
196
- outputText: finalCall.outputText,
411
+ outputText,
197
412
  stages,
198
413
  artifactsDir: this.artifactStore.artifactsDir,
199
414
  };
@@ -219,6 +434,18 @@ export class WorkflowEngine {
219
434
  });
220
435
  }
221
436
  else {
437
+ const planReviewError = error instanceof PlanReviewStageError
438
+ ? CouncilPlanReviewErrorSchema.parse({
439
+ schema_version: "council-plan-review-error-v1",
440
+ run_id: this.runId,
441
+ execution_status: "failed",
442
+ stage: error.stage,
443
+ error: {
444
+ reason: error.reason,
445
+ detail: message.slice(0, 2_048),
446
+ },
447
+ })
448
+ : undefined;
222
449
  await this.emitRunStateChange({
223
450
  runId: this.runId,
224
451
  workflowName: this.definition.name,
@@ -227,6 +454,7 @@ export class WorkflowEngine {
227
454
  completedAt,
228
455
  error: message,
229
456
  failureClass: classifyFailureClass(error),
457
+ planReviewError,
230
458
  });
231
459
  await this.emitProgress({
232
460
  runId: this.runId,
@@ -240,7 +468,63 @@ export class WorkflowEngine {
240
468
  }
241
469
  finally {
242
470
  deadlineController.dispose();
471
+ this.workflowDeadlineAtMonoMs = undefined;
472
+ }
473
+ }
474
+ /**
475
+ * Resume-path entry point. `execute()` is never called on the resume preflight
476
+ * engine, so without this the deadline is never installed and the
477
+ * curator/finalizer delegate runs unbounded. Installs the SAME fresh full
478
+ * `timeout_seconds` budget a resumed run gets (D1), runs preflight under it,
479
+ * and always tears it down.
480
+ */
481
+ async prepareContextPreflightWithDeadline() {
482
+ this.workflowDeadlineAtMonoMs =
483
+ monotonicNowMs() + this.definition.settings.timeout_seconds * 1000;
484
+ const deadlineController = this.createDeadlineController(this.definition.settings.timeout_seconds * 1000);
485
+ try {
486
+ await this.prepareContextPreflight(deadlineController.signal);
487
+ }
488
+ finally {
489
+ deadlineController.dispose();
490
+ this.workflowDeadlineAtMonoMs = undefined;
491
+ }
492
+ }
493
+ /**
494
+ * Complete the workflow context gate without dispatching a provider call.
495
+ * Resume uses this before backend construction when workflow_context is absent.
496
+ */
497
+ async prepareContextPreflight(signal) {
498
+ // Protect engine-derived variables from caller overrides before every
499
+ // branch (runner absent, off, zero-selection, or legacy result).
500
+ if (this.restoredWorkflowContext === undefined) {
501
+ this.setEffectiveVariable("context_quality", "not_evaluated");
243
502
  }
503
+ try {
504
+ await this.runContextBuilderPreflight(signal);
505
+ }
506
+ catch (err) {
507
+ if (this.planReviewContextPolicy !== undefined &&
508
+ !(err instanceof PlanReviewStageError) &&
509
+ !(err instanceof WorkflowCancellationError)) {
510
+ throw new PlanReviewStageError("context_preflight", err instanceof Error ? err.message : String(err), "internal_error");
511
+ }
512
+ throw err;
513
+ }
514
+ this.enforcePlanReviewContextExclusions();
515
+ this.deriveContextAvailable();
516
+ await this.validateInputPathsExist();
517
+ }
518
+ async resolveTerminalResult(stages) {
519
+ if (this.terminalResultPolicy.kind === "plan_review_artifacts") {
520
+ return await this.terminalResultPolicy.project();
521
+ }
522
+ const finalStage = stages[stages.length - 1];
523
+ const finalCall = finalStage?.calls[0];
524
+ if (finalCall == null || !finalCall.ok || finalCall.outputText == null) {
525
+ throw new WorkflowError("Final stage did not produce a successful terminal call result");
526
+ }
527
+ return finalCall.outputText;
244
528
  }
245
529
  async executeParallelStage(stage, stageIndex, signal) {
246
530
  const queue = new DispatchQueue(this.queueDepth);
@@ -282,6 +566,15 @@ export class WorkflowEngine {
282
566
  if (resumedCall != null) {
283
567
  return resumedCall;
284
568
  }
569
+ const resumedFailure = this.resumeFailedCalls.get(callKey);
570
+ if (resumedFailure?.terminal === true) {
571
+ return {
572
+ stageId: resumedFailure.stageId,
573
+ callId: resumedFailure.callId,
574
+ ok: false,
575
+ error: resumedFailure.error,
576
+ };
577
+ }
285
578
  // Handle built-in reducers — no LLM call, no retry loop
286
579
  if (call.reducer != null) {
287
580
  const reducer = getReducer(call.reducer);
@@ -407,24 +700,48 @@ export class WorkflowEngine {
407
700
  const retryMode = stage.retry.mode;
408
701
  const backoffSeconds = stage.retry.backoff_seconds;
409
702
  const maxBackoffSeconds = stage.retry.max_backoff_seconds;
410
- // Emit call prepared ONCE (input is stable across retries)
703
+ // Resume never replenishes an already-consumed provider attempt.
704
+ const initialRetryCount = this.resumeNextRetryCounts.get(callKey) ?? 0;
705
+ // Emit call prepared ONCE (input is stable across retries).
706
+ // Invocation audit rides the event, never ClinkInput / input.json.
707
+ const invocation = this.maybeInvocationAudit(input);
411
708
  await this.emitCallPrepared({
412
709
  runId: this.runId,
413
710
  workflowName: this.definition.name,
414
711
  stageId: stage.id,
415
712
  callId: call.id,
416
- attempt: 1,
417
- retryCount: 0,
713
+ attempt: initialRetryCount + 1,
714
+ retryCount: initialRetryCount,
418
715
  contextProducing,
419
716
  input: structuredClone(input),
717
+ ...(invocation === undefined ? {} : { invocation }),
420
718
  });
421
- let lastError = undefined;
719
+ let lastError = initialRetryCount > maxRetries
720
+ ? new Error("Retry budget exhausted before resume")
721
+ : undefined;
422
722
  let lastFailureClass = "permanent";
423
- let lastRetryCount = 0;
424
- for (let retryCount = 0; retryCount <= maxRetries; retryCount += 1) {
723
+ let lastRetryCount = initialRetryCount;
724
+ // F4: set in the catch below for the NEXT attempt only; always derived from
725
+ // the original `input.prompt`, so it never stacks. In-memory only — a
726
+ // resumed retry has no lastError and simply gets no suffix.
727
+ let correctiveSuffix;
728
+ // D7: at most ONE extra JSON-corrective attempt per call under
729
+ // transport_only, whatever max_retries allows. Loop-local, so a resumed
730
+ // call can earn it again — bounded by the persisted retry count anyway.
731
+ let correctiveRetryUsed = false;
732
+ for (let retryCount = initialRetryCount; retryCount <= maxRetries; retryCount += 1) {
425
733
  const attempt = retryCount + 1;
426
734
  lastRetryCount = retryCount;
427
- this.throwIfAborted(signal);
735
+ // Deliberately guarded HERE rather than moved inside the attempt `try`:
736
+ // inside it, a bare deadline WorkflowError would become retryable under
737
+ // retry.mode:"all". Record the terminal outcome, then rethrow unchanged.
738
+ try {
739
+ this.throwIfAborted(signal);
740
+ }
741
+ catch (error) {
742
+ await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, error instanceof WorkflowCancellationError ? "cancelled" : "failed");
743
+ throw error;
744
+ }
428
745
  await this.emitCallStateChange({
429
746
  runId: this.runId,
430
747
  workflowName: this.definition.name,
@@ -450,7 +767,18 @@ export class WorkflowEngine {
450
767
  : `Call ${callKey} is retrying (attempt ${String(attempt)})`,
451
768
  });
452
769
  try {
453
- const result = await this.invokeCall(stage, call, input, signal);
770
+ const attemptInput = correctiveSuffix == null
771
+ ? input
772
+ : {
773
+ ...input,
774
+ prompt: `${typeof input["prompt"] === "string" ? input["prompt"] : ""}${correctiveSuffix}`,
775
+ };
776
+ const result = await this.invokeCall(stage, call, attemptInput, signal, attempt);
777
+ enforceTypedResultFidelity(result.debugMetadata, {
778
+ stageId: stage.id,
779
+ callId: call.id,
780
+ typedOutput: call.response_contract === "json" || call.validator_schema_id != null,
781
+ });
454
782
  const fidelity = analyzeOutputFidelity(result.outputText, result.debugMetadata);
455
783
  enforceOutputFidelity(fidelity, {
456
784
  stageId: stage.id,
@@ -460,6 +788,7 @@ export class WorkflowEngine {
460
788
  const artifact = this.maybeWriteArtifact(stage, call, stageIndex, callIndex, result.outputText);
461
789
  const completedAt = new Date().toISOString();
462
790
  const durationMs = Date.now() - startedAtMs;
791
+ const observedInvocation = extractObservedInvocation(result.debugMetadata);
463
792
  await this.emitCallStateChange({
464
793
  runId: this.runId,
465
794
  workflowName: this.definition.name,
@@ -474,6 +803,7 @@ export class WorkflowEngine {
474
803
  outputText: result.outputText,
475
804
  fidelity,
476
805
  artifact,
806
+ ...(observedInvocation === undefined ? {} : { observedInvocation }),
477
807
  });
478
808
  await this.emitProgress({
479
809
  runId: this.runId,
@@ -497,43 +827,37 @@ export class WorkflowEngine {
497
827
  };
498
828
  }
499
829
  catch (error) {
830
+ // The deadline is never retryable, but the call still has to land in a
831
+ // terminal state before the throw leaves the loop.
832
+ if (error instanceof WorkflowDispatchDeadlineError) {
833
+ await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "failed");
834
+ throw error;
835
+ }
500
836
  // Cancellation is never retryable
501
837
  if (error instanceof WorkflowCancellationError) {
502
- const completedAt = new Date().toISOString();
503
- const durationMs = Date.now() - startedAtMs;
504
- await this.emitCallStateChange({
505
- runId: this.runId,
506
- workflowName: this.definition.name,
507
- stageId: stage.id,
508
- callId: call.id,
509
- state: "cancelled",
510
- attempt,
511
- retryCount,
512
- startedAt,
513
- completedAt,
514
- durationMs,
515
- error: error.message,
516
- });
517
- await this.emitProgress({
518
- runId: this.runId,
519
- workflowName: this.definition.name,
520
- scope: "call",
521
- stageId: stage.id,
522
- stageDescription: stage.description,
523
- callId: call.id,
524
- state: "cancelled",
525
- attempt,
526
- retryCount,
527
- message: `Call ${callKey} cancelled`,
528
- });
838
+ await this.emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, "cancelled");
529
839
  throw error;
530
840
  }
531
841
  const failureClass = classifyFailureClass(error);
532
842
  lastError = error;
533
843
  lastFailureClass = failureClass;
534
844
  // Determine if this error is retryable
535
- const canRetry = retryCount < maxRetries && isRetryable(error, failureClass, retryMode);
845
+ const ordinaryRetry = retryCount < maxRetries &&
846
+ isRetryable(error, failureClass, retryMode) &&
847
+ !shouldVetoWallTimeoutRetry(this.definition.name, error instanceof CallExecutionError ? error.terminationReason : undefined);
848
+ const correctiveRetry = !ordinaryRetry &&
849
+ !correctiveRetryUsed &&
850
+ retryCount < maxRetries &&
851
+ this.isCorrectiveRetryEligible(stage, call, error);
852
+ if (correctiveRetry) {
853
+ correctiveRetryUsed = true;
854
+ }
855
+ const canRetry = ordinaryRetry || correctiveRetry;
536
856
  if (canRetry) {
857
+ // F4: corrective suffix rides only on the retry that was already
858
+ // decided above — it never changes retry eligibility. Unconditional
859
+ // reassignment clears a stale suffix after a non-JSON failure.
860
+ correctiveSuffix = this.buildCorrectiveRetrySuffix(stage, call, input, error);
537
861
  // Compute backoff with jitter
538
862
  const delayMs = computeRetryDelay(retryCount, backoffSeconds, maxBackoffSeconds);
539
863
  const message = error instanceof Error ? error.message : String(error);
@@ -608,11 +932,220 @@ export class WorkflowEngine {
608
932
  error: message,
609
933
  };
610
934
  }
611
- async invokeCall(stage, call, input, signal) {
612
- const timeoutMs = call.timeout_seconds != null
935
+ /**
936
+ * Terminalize a call that is about to be abandoned by a throw rather than
937
+ * by the retry loop's own failure path.
938
+ *
939
+ * Both abort/deadline exits from executeSingleCall leave the loop OUTSIDE
940
+ * the attempt `try`, so without this the persisted entry is stranded as
941
+ * "running" with no completedAt — a durable lie that resume, the dashboard
942
+ * and run health all read. The caller still rethrows: this only records the
943
+ * outcome, it never swallows it.
944
+ */
945
+ async emitTerminalCallAbort(stage, call, attempt, retryCount, startedAt, startedAtMs, error, state) {
946
+ const callKey = `${stage.id}.${call.id}`;
947
+ this.terminalizedCallKeys.add(callKey);
948
+ const message = error instanceof Error ? error.message : String(error);
949
+ const completedAt = new Date().toISOString();
950
+ const durationMs = Date.now() - startedAtMs;
951
+ await this.emitCallStateChange({
952
+ runId: this.runId,
953
+ workflowName: this.definition.name,
954
+ stageId: stage.id,
955
+ callId: call.id,
956
+ state,
957
+ attempt,
958
+ retryCount,
959
+ startedAt,
960
+ completedAt,
961
+ durationMs,
962
+ error: message,
963
+ failureClass: classifyFailureClass(error),
964
+ });
965
+ await this.emitProgress({
966
+ runId: this.runId,
967
+ workflowName: this.definition.name,
968
+ scope: "call",
969
+ stageId: stage.id,
970
+ stageDescription: stage.description,
971
+ callId: call.id,
972
+ state,
973
+ attempt,
974
+ retryCount,
975
+ message: state === "cancelled"
976
+ ? `Call ${callKey} cancelled`
977
+ : `Call ${callKey} failed: ${message}`,
978
+ });
979
+ }
980
+ /**
981
+ * F4: bounded corrective suffix for the next already-scheduled retry after
982
+ * a JSON/schema-shaped failure. Returns undefined (no suffix) unless ALL of:
983
+ * - the call declares a JSON contract (mirrors the expect_json derivation
984
+ * in buildCallInput);
985
+ * - the run is NOT typed plan review (identity: the plan-review-receipts
986
+ * workflow name on the
987
+ * normalized definition, plus the plan-review runtime signals as
988
+ * defense-in-depth — plan-review dispatch bytes are pinned);
989
+ * - the failure is JSON/schema-shaped (SchemaValidationError,
990
+ * OutputSizeError as the second correctable class, or a child
991
+ * invalid_json_output rejection);
992
+ * - appending stays inside the same prompt-budget ceiling the dispatch-time
993
+ * gate enforces (best-effort re-estimate; skip with a warn on overflow or
994
+ * estimator failure — never a new failure mode).
995
+ * The suffix is always derived from the ORIGINAL prompt, so it can never
996
+ * stack across attempts, and it is never persisted to state.
997
+ */
998
+ /**
999
+ * Typed plan review (identity: the plan-review-receipts workflow name, plus
1000
+ * the plan-review runtime signals as defense-in-depth). Its dispatch bytes
1001
+ * and retry behavior are pinned, so no corrective machinery may touch it.
1002
+ */
1003
+ isTypedPlanReviewCall(call) {
1004
+ return (this.definition.name === "plan-review-receipts" ||
1005
+ this.planReviewContextPolicy !== undefined ||
1006
+ call.plan_review_snapshot_text !== undefined);
1007
+ }
1008
+ /**
1009
+ * D7: under retry.mode "transport_only" a malformed-JSON answer is a
1010
+ * permanent failure, so it would never be retried. A JSON-contract call gets
1011
+ * ONE corrective attempt when the failure is primarily JSON-shaped; a
1012
+ * secondary-only invalid_json_output reason never creates eligibility.
1013
+ * failureClass is not reclassified.
1014
+ */
1015
+ isCorrectiveRetryEligible(stage, call, error) {
1016
+ if (stage.retry.mode !== "transport_only") {
1017
+ return false;
1018
+ }
1019
+ if (!declaresJsonContract(call) || this.isTypedPlanReviewCall(call)) {
1020
+ return false;
1021
+ }
1022
+ return (error instanceof SchemaValidationError ||
1023
+ error instanceof OutputSizeError ||
1024
+ (error instanceof CallExecutionError &&
1025
+ error.childDebugMetadata?.semantic_failure_reason === "invalid_json_output"));
1026
+ }
1027
+ buildCorrectiveRetrySuffix(stage, call, input, error) {
1028
+ if (!declaresJsonContract(call)) {
1029
+ return undefined;
1030
+ }
1031
+ if (this.isTypedPlanReviewCall(call)) {
1032
+ return undefined;
1033
+ }
1034
+ const detail = correctiveFailureDetail(error);
1035
+ if (detail == null) {
1036
+ return undefined;
1037
+ }
1038
+ const suffix = buildCorrectiveSuffixText(detail, error instanceof OutputSizeError ? CORRECTIVE_SIZE_SUFFIX_HEADER : CORRECTIVE_SUFFIX_HEADER);
1039
+ try {
1040
+ const basePrompt = typeof input["prompt"] === "string" ? input["prompt"] : "";
1041
+ const files = Array.isArray(input["absolute_file_paths"])
1042
+ ? input["absolute_file_paths"].filter((path) => typeof path === "string")
1043
+ : [];
1044
+ const images = Array.isArray(input["images"])
1045
+ ? input["images"].filter((path) => typeof path === "string")
1046
+ : [];
1047
+ const estimate = estimatePromptBudget({
1048
+ promptText: `${basePrompt}${suffix}`,
1049
+ contextText: typeof input["context"] === "string" ? input["context"] : undefined,
1050
+ fileContributors: files.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
1051
+ imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
1052
+ maxBytes: this.maxPromptBytes,
1053
+ reserveBytes: isCouncilExecutionWorkflow(this.definition.name)
1054
+ ? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
1055
+ : this.promptReserveBytes,
1056
+ });
1057
+ if (estimate.exceeded) {
1058
+ logger.warn({ run_id: this.runId, stage_id: stage.id, call_id: call.id }, "corrective retry suffix skipped: appending would exceed the prompt budget");
1059
+ return undefined;
1060
+ }
1061
+ }
1062
+ catch (budgetError) {
1063
+ logger.warn({
1064
+ run_id: this.runId,
1065
+ stage_id: stage.id,
1066
+ call_id: call.id,
1067
+ err: budgetError instanceof Error ? budgetError.message : String(budgetError),
1068
+ }, "corrective retry suffix skipped: prompt budget estimate failed");
1069
+ return undefined;
1070
+ }
1071
+ return suffix;
1072
+ }
1073
+ async invokeCall(stage, call, input, signal, attempt) {
1074
+ const dispatchDeadline = this.computeDispatchDeadline(call);
1075
+ const dispatchInput = this.prepareDispatchInput(input, dispatchDeadline.callRemainingMs);
1076
+ const sessionLogOwner = this.sessionLogGeneration !== undefined && this.session.supportsSessionLogOwner === true
1077
+ ? {
1078
+ family: "workflow",
1079
+ runId: this.runId,
1080
+ runGeneration: this.sessionLogGeneration,
1081
+ stageId: stage.id,
1082
+ callId: call.id,
1083
+ attempt,
1084
+ }
1085
+ : undefined;
1086
+ // Prompt sizing above performs synchronous filesystem work, during which
1087
+ // the deadline timer cannot run. Recheck at dispatch; the already-rendered
1088
+ // guidance remains a conservative completion target if preparation took time.
1089
+ const finalDispatchDeadline = this.computeDispatchDeadline(call);
1090
+ this.throwIfAborted(signal);
1091
+ return await callClink(this.session, dispatchInput, signal, { stageId: stage.id, callId: call.id }, finalDispatchDeadline.timeoutMs, sessionLogOwner);
1092
+ }
1093
+ computeDispatchDeadline(call) {
1094
+ const configuredCallTimeoutMs = call.timeout_seconds != null
613
1095
  ? Math.max((call.timeout_seconds * 1000) + CALL_TIMEOUT_BUFFER_MS, DEFAULT_PARENT_CALL_TIMEOUT_MS)
614
1096
  : undefined;
615
- return await callClink(this.session, input, signal, { stageId: stage.id, callId: call.id }, timeoutMs);
1097
+ const workflowRemainingMs = this.workflowRemainingMs();
1098
+ if (workflowRemainingMs != null && workflowRemainingMs <= 0) {
1099
+ throw new WorkflowDispatchDeadlineError();
1100
+ }
1101
+ // Keep the existing backend timeout contract for generic and excluded
1102
+ // workflows. The workflow AbortSignal still enforces the outer deadline;
1103
+ // only admitted councils with an explicit call cap receive the tighter
1104
+ // per-dispatch timeout when the workflow is closer to expiry.
1105
+ const timeoutMs = !isCouncilExecutionWorkflow(this.definition.name) ||
1106
+ configuredCallTimeoutMs == null || workflowRemainingMs == null
1107
+ ? configuredCallTimeoutMs
1108
+ : Math.min(configuredCallTimeoutMs, workflowRemainingMs);
1109
+ const callRemainingMs = configuredCallTimeoutMs == null
1110
+ ? workflowRemainingMs
1111
+ : (isCouncilExecutionWorkflow(this.definition.name) && workflowRemainingMs != null
1112
+ ? Math.min(configuredCallTimeoutMs, workflowRemainingMs)
1113
+ : configuredCallTimeoutMs);
1114
+ return {
1115
+ timeoutMs,
1116
+ callRemainingMs,
1117
+ };
1118
+ }
1119
+ prepareDispatchInput(input, callRemainingMs) {
1120
+ if (!isCouncilExecutionWorkflow(this.definition.name) ||
1121
+ this.workflowDeadlineAtMonoMs == null ||
1122
+ callRemainingMs == null ||
1123
+ typeof input["prompt"] !== "string") {
1124
+ return input;
1125
+ }
1126
+ const guidedInput = {
1127
+ ...input,
1128
+ prompt: `${input["prompt"]}${buildCouncilDeadlineGuidance({
1129
+ workflowRemainingMs: this.workflowRemainingMs() ?? 0,
1130
+ callRemainingMs,
1131
+ })}`,
1132
+ };
1133
+ const filePaths = Array.isArray(input["absolute_file_paths"])
1134
+ ? input["absolute_file_paths"].filter((path) => typeof path === "string")
1135
+ : [];
1136
+ const imagePaths = Array.isArray(input["images"])
1137
+ ? input["images"].filter((path) => typeof path === "string")
1138
+ : [];
1139
+ const estimate = estimatePromptBudget({
1140
+ promptText: guidedInput["prompt"],
1141
+ contextText: typeof input["context"] === "string" ? input["context"] : undefined,
1142
+ fileContributors: filePaths.map((path) => createPathContributor(`absolute_file_paths:${path}`, path, "file")),
1143
+ imageContributors: imagePaths.map((path) => createPathContributor(`image:${path}`, path, "image")),
1144
+ maxBytes: this.maxPromptBytes,
1145
+ reserveBytes: this.promptReserveBytes,
1146
+ });
1147
+ enforcePromptBudget(estimate);
1148
+ return guidedInput;
616
1149
  }
617
1150
  maybeWriteArtifact(stage, call, stageIndex, callIndex, outputText) {
618
1151
  const requiresValidation = call.validator_schema_id != null;
@@ -624,19 +1157,78 @@ export class WorkflowEngine {
624
1157
  throw new WorkflowError(`Validated call ${stage.id}.${call.id} must declare validator_schema_id`);
625
1158
  }
626
1159
  const validated = this.artifactStore.validateJsonOutput(stage.id, call.id, validatorSchemaId, outputText);
1160
+ if (call.plan_review_anonymous_aliases != null) {
1161
+ const parsed = validated.parsed;
1162
+ const allowed = this.planReviewDeliveredAliases.get(`${stage.id}.${call.id}`) ?? new Set();
1163
+ const assessmentIds = (parsed.peer_assessments ?? []).map((entry) => entry.candidate_id);
1164
+ const rankingIds = (parsed.ranking ?? []).map((entry) => entry.candidate_id);
1165
+ const exactAliasSet = (ids) => {
1166
+ if (!ids.every((candidate) => typeof candidate === "string" && allowed.has(candidate)))
1167
+ return false;
1168
+ const unique = new Set(ids);
1169
+ return ids.length === allowed.size && unique.size === allowed.size &&
1170
+ [...allowed].every((candidate) => unique.has(candidate));
1171
+ };
1172
+ if (!exactAliasSet(assessmentIds) || !exactAliasSet(rankingIds)) {
1173
+ throw new WorkflowError(`Validation failed for ${stage.id}.${call.id}: peer_assessments and ranking must each contain every bound peer alias exactly once`);
1174
+ }
1175
+ }
1176
+ // Optional post-processing: a call may declare `post_processor: <name>` to
1177
+ // transform the validated value before it is persisted. Unknown names are a
1178
+ // load-time validation error (loader.ts:validatePostProcessors); we fail
1179
+ // fast here too as defense-in-depth, since silently writing the
1180
+ // un-transformed output through could ship un-scrubbed data (e.g. an
1181
+ // un-run citation_check). The transform runs on the parsed data; the
1182
+ // artifact is re-serialized so the persisted bytes reflect it.
1183
+ let persisted = validated;
1184
+ const postProcessorName = call.post_processor;
1185
+ if (postProcessorName != null) {
1186
+ const postProcessor = getPostProcessor(postProcessorName);
1187
+ if (postProcessor == null) {
1188
+ throw new WorkflowError(`Call ${stage.id}.${call.id} declares post_processor ` +
1189
+ `"${postProcessorName}" which is not registered`);
1190
+ }
1191
+ const allowlist = this.buildPostProcessorAllowlist();
1192
+ const processed = postProcessor(validated.parsed, { allowlist, schemaId: validatorSchemaId });
1193
+ const serialized = `${JSON.stringify(processed, null, 2)}\n`;
1194
+ persisted = {
1195
+ parsed: processed,
1196
+ serialized,
1197
+ sizeBytes: Buffer.byteLength(serialized, "utf-8"),
1198
+ };
1199
+ }
627
1200
  return this.artifactStore.writeCallArtifact({
628
1201
  stage,
629
1202
  call,
630
1203
  stageIndex,
631
1204
  callIndex,
632
- validated,
1205
+ validated: persisted,
633
1206
  });
634
1207
  }
635
- resolveDynamicPaths(variableName, context) {
636
- if (variableName == null) {
1208
+ resolveDynamicPaths(variableNames, context) {
1209
+ if (variableNames == null) {
637
1210
  return [];
638
1211
  }
639
- const rawValue = this.variables[variableName];
1212
+ if (typeof variableNames === "string") {
1213
+ const values = this.resolveDynamicPathVariable(variableNames, context);
1214
+ validateDynamicPaths(values, this.allowedRoots, context);
1215
+ return values;
1216
+ }
1217
+ const values = [];
1218
+ const seen = new Set();
1219
+ for (const variableName of variableNames) {
1220
+ for (const value of this.resolveDynamicPathVariable(variableName, context)) {
1221
+ if (!seen.has(value)) {
1222
+ seen.add(value);
1223
+ values.push(value);
1224
+ }
1225
+ }
1226
+ }
1227
+ validateDynamicPaths(values, this.allowedRoots, context);
1228
+ return values;
1229
+ }
1230
+ resolveDynamicPathVariable(variableName, context) {
1231
+ const rawValue = this.effectiveVariables[variableName];
640
1232
  if (!Array.isArray(rawValue)) {
641
1233
  throw new WorkflowError(`Variable "${variableName}" for ${context} must be a string[]`);
642
1234
  }
@@ -647,22 +1239,519 @@ export class WorkflowEngine {
647
1239
  }
648
1240
  return isAbsolute(value) ? value : resolve(workspaceRoot, value);
649
1241
  });
650
- validateDynamicPaths(values, this.allowedRoots, context);
651
1242
  return values;
652
1243
  }
653
- createDeadlineController() {
1244
+ setEffectiveVariable(name, value) {
1245
+ this.effectiveVariables[name] = value;
1246
+ }
1247
+ /**
1248
+ * Derive the engine-computed `context_available` template variable after
1249
+ * context-builder preflight has had a chance to append to
1250
+ * `context_bundle_files`. True when the union of the effective
1251
+ * `context_files` and `context_bundle_files` path lists is non-empty.
1252
+ * Stored as the STRING "true"/"false" since the template engine stringifies
1253
+ * variable values. Engine-derived; setting it is expected.
1254
+ */
1255
+ deriveContextAvailable() {
1256
+ const contextFiles = this.readStringArrayVariable("context_files");
1257
+ const bundleFiles = this.readStringArrayVariable("context_bundle_files");
1258
+ const available = contextFiles.length > 0 || bundleFiles.length > 0;
1259
+ this.setEffectiveVariable("context_available", available ? "true" : "false");
1260
+ }
1261
+ /**
1262
+ * Build the citation allowlist for a post-processor run: the union of files
1263
+ * parsed from the `diff_context` variable, the resolved `context_files`, the
1264
+ * resolved `context_bundle_files`, and the source files selected by any
1265
+ * curated context-builder bundle among them. Both full paths and basenames
1266
+ * are stored so citation matching is lenient.
1267
+ *
1268
+ * A curated preflight copies only `bundle.md` and `manifest.json` into
1269
+ * `context_bundle_files`, so without the manifest's `selected_context`
1270
+ * every citation to a real source file would be stripped. Reading from the
1271
+ * (persisted) variable keeps this working on resume.
1272
+ */
1273
+ buildPostProcessorAllowlist() {
1274
+ const diffRaw = this.effectiveVariables["diff_context"];
1275
+ const allowlist = buildAllowlistFromDiff(typeof diffRaw === "string" ? diffRaw : "");
1276
+ for (const name of ["context_files", "context_bundle_files"]) {
1277
+ for (const path of this.resolvePathListLenient(name)) {
1278
+ addPathWithBasename(allowlist, path);
1279
+ }
1280
+ }
1281
+ const manifestPaths = new Set(this.resolvePathListLenient("context_bundle_files").filter((path) => isAbsolute(path) && basename(path) === "manifest.json"));
1282
+ for (const manifestPath of manifestPaths) {
1283
+ this.addCuratedBundleSelections(allowlist, manifestPath);
1284
+ }
1285
+ return allowlist;
1286
+ }
1287
+ /**
1288
+ * Add each `selected_context` entry of a context-builder manifest to the
1289
+ * allowlist: its root-relative `path` and, when its root resolves, the
1290
+ * absolute form. The allowlist is advisory, so an unreadable, oversized
1291
+ * (> 8 MiB), non-JSON, or non-context-builder manifest is skipped silently
1292
+ * and malformed entries are ignored.
1293
+ */
1294
+ addCuratedBundleSelections(allowlist, manifestPath) {
1295
+ const maxBytes = 8 * 1024 * 1024;
1296
+ let text;
1297
+ let fd;
1298
+ try {
1299
+ fd = openSync(manifestPath, "r");
1300
+ const stats = fstatSync(fd);
1301
+ if (!stats.isFile() || stats.size > maxBytes) {
1302
+ return;
1303
+ }
1304
+ // One spare byte detects a file that grew after fstat; skip it rather
1305
+ // than parse a torn read.
1306
+ const buffer = Buffer.alloc(stats.size + 1);
1307
+ let length = 0;
1308
+ while (length < buffer.length) {
1309
+ const read = readSync(fd, buffer, length, buffer.length - length, null);
1310
+ if (read === 0) {
1311
+ break;
1312
+ }
1313
+ length += read;
1314
+ }
1315
+ if (length > stats.size) {
1316
+ return;
1317
+ }
1318
+ text = buffer.toString("utf-8", 0, length);
1319
+ }
1320
+ catch {
1321
+ return;
1322
+ }
1323
+ finally {
1324
+ if (fd !== undefined) {
1325
+ try {
1326
+ closeSync(fd);
1327
+ }
1328
+ catch {
1329
+ // Advisory read; a close failure must not fail the call.
1330
+ }
1331
+ }
1332
+ }
1333
+ let manifest;
1334
+ try {
1335
+ manifest = JSON.parse(text);
1336
+ }
1337
+ catch {
1338
+ return;
1339
+ }
1340
+ if (typeof manifest !== "object" || manifest === null || Array.isArray(manifest)) {
1341
+ return;
1342
+ }
1343
+ const { selected_context: selected, roots } = manifest;
1344
+ if (!Array.isArray(selected) || !Array.isArray(roots)) {
1345
+ return;
1346
+ }
1347
+ for (const entry of selected) {
1348
+ if (typeof entry !== "object" || entry === null) {
1349
+ continue;
1350
+ }
1351
+ const { path, root_index: rootIndex } = entry;
1352
+ if (typeof path !== "string" || path.length === 0) {
1353
+ continue;
1354
+ }
1355
+ addPathWithBasename(allowlist, path);
1356
+ const root = typeof rootIndex === "number" && Number.isInteger(rootIndex) && rootIndex >= 0
1357
+ ? roots[rootIndex]
1358
+ : undefined;
1359
+ if (typeof root === "object" && root !== null) {
1360
+ const rootPath = root["path"];
1361
+ if (typeof rootPath === "string" && rootPath.length > 0) {
1362
+ addPathWithBasename(allowlist, join(rootPath, path));
1363
+ }
1364
+ }
1365
+ }
1366
+ }
1367
+ /**
1368
+ * Resolve a path-list variable to absolute paths without enforcing allowed
1369
+ * roots (the post-processor allowlist is advisory, not a security boundary).
1370
+ * Both the raw and resolved forms are returned so basename matching works
1371
+ * regardless of how a citation spells the file.
1372
+ */
1373
+ resolvePathListLenient(name) {
1374
+ const value = this.effectiveVariables[name];
1375
+ if (!Array.isArray(value)) {
1376
+ return [];
1377
+ }
1378
+ const workspaceRoot = resolveWorkspaceRoot();
1379
+ const out = [];
1380
+ for (const entry of value) {
1381
+ if (typeof entry !== "string") {
1382
+ continue;
1383
+ }
1384
+ out.push(entry);
1385
+ out.push(isAbsolute(entry) ? entry : resolve(workspaceRoot, entry));
1386
+ }
1387
+ return out;
1388
+ }
1389
+ async runContextBuilderPreflight(signal) {
1390
+ if (this.restoredWorkflowContext !== undefined) {
1391
+ return;
1392
+ }
1393
+ const policy = this.resolveContextPolicy();
1394
+ if (policy.effectiveMode === "off" || policy.effectiveMode === "disabled") {
1395
+ return;
1396
+ }
1397
+ const runner = this.contextBuilderRunner;
1398
+ if (runner == null) {
1399
+ if (this.requireContextPreflight) {
1400
+ throw new WorkflowError(`Workflow context preflight is required in ${policy.effectiveMode} mode but no runner is available`);
1401
+ }
1402
+ return;
1403
+ }
1404
+ // A-7(a): strip force-excluded plan anchors from the caller's focus BEFORE
1405
+ // anything reads them, so the curator is never even asked to select the plan.
1406
+ this.enforcePlanReviewContextExclusions();
1407
+ const focusPaths = this.readStringArrayVariable("cb_focus_paths");
1408
+ const callerSearchTerms = this.readStringArrayVariable("cb_search_terms");
1409
+ this.throwIfAborted(signal);
1410
+ // Optional budget overrides: 6 flat number vars folded into a single budget
1411
+ // object containing ONLY the keys actually supplied. When none are present
1412
+ // the object is undefined and omitted via conditional spread, keeping the
1413
+ // no-budget input byte-identical to the historical shape (cache equality).
1414
+ const budgetEntries = [
1415
+ ["max_estimated_tokens", this.readOptionalNumberVariable("cb_budget_max_estimated_tokens")],
1416
+ ["max_files", this.readOptionalNumberVariable("cb_budget_max_files")],
1417
+ ["max_scan_files", this.readOptionalNumberVariable("cb_budget_max_scan_files")],
1418
+ ["max_bytes_per_file", this.readOptionalNumberVariable("cb_budget_max_bytes_per_file")],
1419
+ ["max_total_bytes", this.readOptionalNumberVariable("cb_budget_max_total_bytes")],
1420
+ ["max_search_results", this.readOptionalNumberVariable("cb_budget_max_search_results")],
1421
+ ];
1422
+ const budgetObject = {};
1423
+ for (const [key, value] of budgetEntries) {
1424
+ if (value !== undefined) {
1425
+ budgetObject[key] = value;
1426
+ }
1427
+ }
1428
+ const budget = Object.keys(budgetObject).length > 0 ? budgetObject : undefined;
1429
+ const contextBudgetAdapter = this.readOptionalStringVariable("cb_budget_adapter");
1430
+ const hasCallerSearchTerms = callerSearchTerms.length > 0;
1431
+ const shouldSynthesizeSeedTerms = focusPaths.length === 0 && !hasCallerSearchTerms;
1432
+ // Match mapPreflightContextInput's nonblank rule: a whitespace-only cb_task
1433
+ // must fall through to user_request, else seed synthesis yields no terms and
1434
+ // allowRootWideDiscovery runs a broad scan with no search terms.
1435
+ const cbTaskSeed = this.readOptionalStringVariable("cb_task");
1436
+ const seedText = resolveSeedSource(cbTaskSeed, this.readOptionalStringVariable("user_request"));
1437
+ const seedSourceError = contextSeedSourceValidationError(seedText);
1438
+ if (shouldSynthesizeSeedTerms && seedSourceError != null) {
1439
+ throw new McpError(ErrorCode.InvalidParams, seedSourceError);
1440
+ }
1441
+ const synthesizedSearchTerms = shouldSynthesizeSeedTerms
1442
+ ? synthesizeSeedTerms(seedText)
1443
+ : [];
1444
+ const searchTerms = shouldSynthesizeSeedTerms ? synthesizedSearchTerms : callerSearchTerms;
1445
+ const input = {
1446
+ runId: this.runId,
1447
+ runDir: this.runDir,
1448
+ cb_task: this.readOptionalStringVariable("cb_task") ?? "",
1449
+ user_request: this.readOptionalStringVariable("user_request") ?? "",
1450
+ roots: this.readStringArrayVariable("cb_roots"),
1451
+ focus_paths: focusPaths,
1452
+ include_globs: this.readStringArrayVariable("cb_include_globs"),
1453
+ // A-7(a): the enforced plan exclusions are folded in HERE — before the
1454
+ // delegate packet is built — so the default-on curator+finalizer never
1455
+ // sees the plan as a candidate. Caller `cb_exclude_globs:[]` cannot drop
1456
+ // them; the fold is unconditional.
1457
+ exclude_globs: this.planReviewContextPolicy == null
1458
+ ? this.readStringArrayVariable("cb_exclude_globs")
1459
+ : foldPlanReviewExcludeGlobs(this.planReviewContextPolicy, this.readStringArrayVariable("cb_exclude_globs")),
1460
+ search_terms: searchTerms,
1461
+ profile: this.readOptionalStringVariable("cb_profile"),
1462
+ allow_secrets: this.readOptionalBooleanVariable("cb_allow_secrets"),
1463
+ context_mode: policy.effectiveMode,
1464
+ ...(shouldSynthesizeSeedTerms ? { allowRootWideDiscovery: true } : {}),
1465
+ ...(this.definition.settings.include_selected_context_artifact === true
1466
+ ? { includeSelectedContext: true }
1467
+ : {}),
1468
+ ...(contextBudgetAdapter == null || contextBudgetAdapter.trim() === ""
1469
+ ? {}
1470
+ : { context_budget_adapter: contextBudgetAdapter }),
1471
+ ...(budget == null ? {} : { budget }),
1472
+ ...(policy.effectiveDelegate == null
1473
+ ? {}
1474
+ : {
1475
+ delegate: foldExternalGapPolicy(policy.effectiveDelegate, this.definition.settings.context_external_gap_policy),
1476
+ }),
1477
+ };
1478
+ // Light-caller provenance: capture how anchors were derived. `root_wide_used`
1479
+ // records the REALIZED `input.allowRootWideDiscovery` flag actually passed to
1480
+ // the runner, while `anchor_source` reflects whether seed synthesis ran. Today
1481
+ // both flow from the same `shouldSynthesizeSeedTerms` decision.
1482
+ const lightCaller = {
1483
+ anchor_source: shouldSynthesizeSeedTerms ? "synthesized" : "caller",
1484
+ root_wide_used: input.allowRootWideDiscovery === true,
1485
+ synthesized_search_terms: shouldSynthesizeSeedTerms ? synthesizedSearchTerms : [],
1486
+ };
1487
+ let result;
1488
+ try {
1489
+ result = await runner.runOrReuse(input, { signal });
1490
+ }
1491
+ catch (error) {
1492
+ // The injected runner lives in the root package, which clinkx-workflows
1493
+ // must not import. We therefore cannot catch the root CancellationError by
1494
+ // type; instead, normalize at the engine boundary using the signal: if the
1495
+ // signal aborted, surface it as a WorkflowCancellationError (preserving any
1496
+ // existing abort reason). A genuine non-abort failure rethrows unchanged.
1497
+ this.throwIfAborted(signal);
1498
+ throw error;
1499
+ }
1500
+ this.throwIfAborted(signal);
1501
+ const effectiveCoverageStatus = result.metrics.files_selected > 0
1502
+ ? result.coverage?.status ?? "not_evaluated"
1503
+ : "not_evaluated";
1504
+ // External gaps under external_gap_policy:"degrade" are the only producer of
1505
+ // a partial delegate without fallback; the coverage report itself is left
1506
+ // untouched so persisted coverage stays the policy's own verdict.
1507
+ const externalGapsDegraded = isDelegateExternalGapDegrade(result.delegate);
1508
+ const contextQuality = externalGapsDegraded ? "degraded" : effectiveCoverageStatus;
1509
+ this.setEffectiveVariable("context_quality", contextQuality);
1510
+ const delegateInput = policy.effectiveDelegate?.enabled === true ? policy.effectiveDelegate : undefined;
1511
+ const fallbackUsed = result.delegate?.fallback_used === true;
1512
+ const fallbackAggregate = recordPreflightFallbackAggregate({
1513
+ delegateEnabled: result.delegate?.enabled === true,
1514
+ fallbackUsed,
1515
+ });
1516
+ logger.info({
1517
+ run_id: this.runId,
1518
+ workflow: this.definition.name,
1519
+ source_run_id: result.source_run_id,
1520
+ mode: policy.effectiveMode,
1521
+ delegate_enabled: delegateInput != null,
1522
+ fallback_used: fallbackUsed,
1523
+ curator_ms: result.observability?.curator_ms ?? null,
1524
+ finalizer_ms: result.observability?.finalizer_ms ?? null,
1525
+ files_seen: result.metrics.files_seen,
1526
+ bytes_read: result.metrics.bytes_read,
1527
+ scan_cap_hit: result.metrics.scan_cap_hit,
1528
+ candidate_pool_size: result.observability?.candidate_pool_size ?? 0,
1529
+ delegate_packet_size: result.observability?.delegate_packet_size ?? 0,
1530
+ files_selected: result.metrics.files_selected,
1531
+ cache: {
1532
+ hit: result.cache.hit,
1533
+ write: result.cache.write,
1534
+ key_prefix: result.cache.key_prefix,
1535
+ },
1536
+ curator: delegateInput == null
1537
+ ? null
1538
+ : {
1539
+ adapter: delegateInput.curator.cli_name,
1540
+ role: delegateInput.curator.role,
1541
+ },
1542
+ finalizer: delegateInput == null
1543
+ ? null
1544
+ : {
1545
+ adapter: delegateInput.finalizer.cli_name,
1546
+ role: delegateInput.finalizer.role,
1547
+ },
1548
+ failure_reason: receiptDelegateFailureReason(result),
1549
+ warning_code: firstReceiptWarningCode(result),
1550
+ budget_caps: result.observability?.budget_caps ?? null,
1551
+ profile: input.profile ?? null,
1552
+ context_budget_adapter: input.context_budget_adapter ?? null,
1553
+ root_source: input.roots.length > 0 ? "explicit" : "fallback",
1554
+ seed_term_count: searchTerms.length,
1555
+ fallback_aggregate: fallbackAggregate,
1556
+ }, "context-builder preflight summary");
1557
+ if (result.metrics.files_selected === 0) {
1558
+ if (this.definition.settings.fail_on_empty_context_selection === true) {
1559
+ throw new WorkflowError("Context-builder preflight selected no files");
1560
+ }
1561
+ logger.warn({
1562
+ run_id: this.runId,
1563
+ source_run_id: result.source_run_id,
1564
+ files_selected: 0,
1565
+ }, "context-builder preflight selected no files; proceeding without bundle");
1566
+ }
1567
+ if (externalGapsDegraded && effectiveCoverageStatus !== "degraded") {
1568
+ if (this.definition.settings.fail_on_degraded_context_coverage === true) {
1569
+ throw new WorkflowError("Context-builder preflight is degraded: the finalizer reported evidence gaps outside the delegate packet");
1570
+ }
1571
+ logger.warn({ run_id: this.runId, source_run_id: result.source_run_id }, "context-builder preflight reported external evidence gaps; proceeding degraded");
1572
+ }
1573
+ if (effectiveCoverageStatus === "degraded") {
1574
+ if (this.definition.settings.fail_on_degraded_context_coverage === true) {
1575
+ throw new WorkflowError("Context-builder preflight coverage is degraded");
1576
+ }
1577
+ logger.warn({
1578
+ run_id: this.runId,
1579
+ source_run_id: result.source_run_id,
1580
+ coverage_status: effectiveCoverageStatus,
1581
+ missing_required: result.coverage?.missing_required ?? [],
1582
+ }, "context-builder preflight coverage degraded; proceeding");
1583
+ }
1584
+ if (result.status === "partial") {
1585
+ const partial = classifyPreflightPartial(result.metrics);
1586
+ const payload = {
1587
+ run_id: this.runId,
1588
+ source_run_id: result.source_run_id,
1589
+ files_selected: result.metrics.files_selected,
1590
+ warning_count: result.warning_count,
1591
+ omission_count: result.omission_count,
1592
+ partial_reason: partial.partial_reason,
1593
+ };
1594
+ if (partial.severity === "degraded") {
1595
+ logger.warn(payload, "context-builder preflight returned partial context; proceeding");
1596
+ }
1597
+ else {
1598
+ logger.info(payload, "context-builder preflight returned partial context; proceeding");
1599
+ }
1600
+ }
1601
+ // Only attach the generated bundle artifacts when the preflight actually
1602
+ // selected files. On a zero-selection run we proceed WITHOUT a bundle
1603
+ // (matching the warn log above): the runner always emits two copied paths
1604
+ // (bundle.md + manifest.json) even for an empty selection, so appending
1605
+ // them would hand downstream seats an empty bundle that looks intentional.
1606
+ if (result.metrics.files_selected > 0) {
1607
+ const existingBundleFiles = this.readStringArrayVariable("context_bundle_files");
1608
+ this.setEffectiveVariable("context_bundle_files", orderedUnique([...existingBundleFiles, ...result.copied_paths]));
1609
+ }
1610
+ // A-7(b): post-filter the FINALIZED selection. This is not redundant with
1611
+ // the pre-dispatch fold — caller `cb_include_globs`/focus anchors can
1612
+ // re-admit exactly what the fold excluded, and this is the point where the
1613
+ // exclusion has to survive them. Runs BEFORE commitPreflightContext so the
1614
+ // persisted inputs snapshot never records an excluded path.
1615
+ const removed = this.enforcePlanReviewContextExclusions();
1616
+ if (this.planReviewContextPolicy != null) {
1617
+ await this.commitPlanReviewContextProvenance?.({
1618
+ enforced: true,
1619
+ ...(policy.effectiveMode != null ? { context_mode: policy.effectiveMode } : {}),
1620
+ copied_paths: [...this.readStringArrayVariable("context_bundle_files")],
1621
+ filtered_out_paths: removed,
1622
+ });
1623
+ }
1624
+ await this.commitPreflightContext?.({
1625
+ variables: { ...this.effectiveVariables },
1626
+ workflow_context: this.buildWorkflowContext(result, policy.effectiveMode, lightCaller),
1627
+ });
1628
+ }
1629
+ /**
1630
+ * Strip every force-excluded plan path from the effective context path
1631
+ * variables (A-7). Idempotent and safe to call on any path — no policy means
1632
+ * no-op, so non-plan-review runs are byte-unchanged.
1633
+ *
1634
+ * Returns the paths that were removed, for provenance.
1635
+ */
1636
+ enforcePlanReviewContextExclusions() {
1637
+ const planPolicy = this.planReviewContextPolicy;
1638
+ if (planPolicy == null) {
1639
+ return [];
1640
+ }
1641
+ const removedAll = [];
1642
+ for (const name of ["context_files", "context_bundle_files", "cb_focus_paths"]) {
1643
+ const values = this.readStringArrayVariable(name);
1644
+ if (values.length === 0)
1645
+ continue;
1646
+ const { kept, removed } = filterExcludedPlanPaths(planPolicy, values, resolveWorkspaceRoot());
1647
+ if (removed.length === 0)
1648
+ continue;
1649
+ removedAll.push(...removed);
1650
+ this.setEffectiveVariable(name, kept);
1651
+ logger.warn({ run_id: this.runId, variable: name, removed }, "plan-review context policy stripped excluded plan paths");
1652
+ }
1653
+ return removedAll;
1654
+ }
1655
+ buildWorkflowContext(result, contextMode, lightCaller) {
1656
+ return mapPreflightResultToWorkflowContext(result, contextMode, lightCaller);
1657
+ }
1658
+ resolveContextPolicy() {
1659
+ if (this.initialContextPolicy !== undefined) {
1660
+ return this.initialContextPolicy;
1661
+ }
1662
+ try {
1663
+ return resolveWorkflowContextMode(this.effectiveVariables["cb_context_mode"], this.contextDefault, true, this.effectiveVariables["cb_delegate"], this.defaultCurator);
1664
+ }
1665
+ catch (error) {
1666
+ if (error instanceof WorkflowContextModeResolverError ||
1667
+ error instanceof WorkflowDelegateValidationError) {
1668
+ throw new WorkflowError(error.message);
1669
+ }
1670
+ throw error;
1671
+ }
1672
+ }
1673
+ readOptionalStringVariable(name) {
1674
+ const value = this.effectiveVariables[name];
1675
+ if (value === undefined) {
1676
+ return undefined;
1677
+ }
1678
+ if (typeof value !== "string") {
1679
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string`);
1680
+ }
1681
+ return value;
1682
+ }
1683
+ readOptionalNumberVariable(name) {
1684
+ const value = this.effectiveVariables[name];
1685
+ if (value === undefined) {
1686
+ return undefined;
1687
+ }
1688
+ if (typeof value !== "number" || !Number.isFinite(value)) {
1689
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a finite number`);
1690
+ }
1691
+ return value;
1692
+ }
1693
+ readOptionalBooleanVariable(name) {
1694
+ const value = this.effectiveVariables[name];
1695
+ if (value === undefined) {
1696
+ return undefined;
1697
+ }
1698
+ if (typeof value === "boolean") {
1699
+ return value;
1700
+ }
1701
+ if (value === "true" || value === "1") {
1702
+ return true;
1703
+ }
1704
+ if (value === "false" || value === "0") {
1705
+ return false;
1706
+ }
1707
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a boolean`);
1708
+ }
1709
+ readStringArrayVariable(name) {
1710
+ const value = this.effectiveVariables[name];
1711
+ if (value === undefined) {
1712
+ return [];
1713
+ }
1714
+ if (!Array.isArray(value) || !value.every((entry) => typeof entry === "string")) {
1715
+ throw new WorkflowError(`Variable "${name}" for context-builder preflight must be a string[]`);
1716
+ }
1717
+ return value;
1718
+ }
1719
+ createDeadlineController(delayMs) {
654
1720
  const controller = new AbortController();
655
1721
  const timeout = setTimeout(() => {
656
1722
  controller.abort(new Error(`Workflow timeout exceeded after ${String(this.definition.settings.timeout_seconds)} seconds`));
657
- }, this.definition.settings.timeout_seconds * 1000);
1723
+ }, Math.max(0, delayMs));
658
1724
  if (this.signal != null) {
659
- this.signal.addEventListener("abort", () => controller.abort(new WorkflowCancellationError()), { once: true });
1725
+ if (this.signal.aborted) {
1726
+ controller.abort(new WorkflowCancellationError());
1727
+ }
1728
+ else {
1729
+ this.signal.addEventListener("abort", () => controller.abort(new WorkflowCancellationError()), { once: true });
1730
+ }
660
1731
  }
661
1732
  return {
662
1733
  signal: controller.signal,
663
1734
  dispose: () => clearTimeout(timeout),
664
1735
  };
665
1736
  }
1737
+ /** Remaining workflow budget in ms; undefined when no deadline is installed. */
1738
+ workflowRemainingMs() {
1739
+ return this.workflowDeadlineAtMonoMs == null
1740
+ ? undefined
1741
+ : Math.floor(this.workflowDeadlineAtMonoMs - monotonicNowMs());
1742
+ }
1743
+ /**
1744
+ * True only while a workflow deadline is set AND already past. Pure: reads
1745
+ * the deadline the running `execute()` installed and the clock, nothing else.
1746
+ * `execute()` clears `workflowDeadlineAtMonoMs` in its `finally`, so this can
1747
+ * never report a stale deadline from a prior run. The reading is monotonic:
1748
+ * a wall-clock step (NTP correction, suspend/resume, manual change) can
1749
+ * neither manufacture nor erase an exhausted budget.
1750
+ */
1751
+ isWorkflowDeadlineExhausted() {
1752
+ const remainingMs = this.workflowRemainingMs();
1753
+ return remainingMs != null && remainingMs <= 0;
1754
+ }
666
1755
  throwIfAborted(signal) {
667
1756
  if (!signal.aborted) {
668
1757
  return;
@@ -672,7 +1761,11 @@ export class WorkflowEngine {
672
1761
  throw reason;
673
1762
  }
674
1763
  if (reason instanceof Error && reason.message.includes("Workflow timeout exceeded")) {
675
- throw new WorkflowError(reason.message);
1764
+ // Same environmental cause as WorkflowDispatchDeadlineError, so it
1765
+ // carries the same class. Kept as a bare WorkflowError: the two are now
1766
+ // handled identically by materializeUnexpectedFailure (both materialize
1767
+ // rather than rethrow), so the distinction buys nothing here.
1768
+ throw new WorkflowError(reason.message, "transient");
676
1769
  }
677
1770
  throw new WorkflowCancellationError();
678
1771
  }
@@ -698,29 +1791,69 @@ export class WorkflowEngine {
698
1791
  `Known cli_name values: ${knownCliNames.join(", ")}`);
699
1792
  }
700
1793
  defaultAllowedRoots() {
701
- const workspaceRoot = resolveWorkspaceRoot();
702
- const stateDir = getStateDir();
703
- const configuredArtifactsDir = getArtifactsDir();
704
- return [
705
- workspaceRoot,
706
- stateDir,
707
- configuredArtifactsDir,
708
- this.runDir,
709
- join(this.runDir, "artifacts"),
710
- ];
1794
+ return defaultWorkflowAllowedRoots(this.runDir);
1795
+ }
1796
+ recordPlanReviewDeliveredAliases(callKey, call, absoluteFilePaths) {
1797
+ if (call.plan_review_anonymous_aliases == null)
1798
+ return;
1799
+ const configured = new Set(Object.values(call.plan_review_anonymous_aliases));
1800
+ const delivered = new Set();
1801
+ const anonymousRoot = resolve(this.artifactStore.artifactsDir, "anonymous");
1802
+ if (Array.isArray(absoluteFilePaths)) {
1803
+ for (const path of absoluteFilePaths) {
1804
+ if (typeof path !== "string")
1805
+ continue;
1806
+ const absolute = resolve(path);
1807
+ if (!absolute.startsWith(anonymousRoot + sep))
1808
+ continue;
1809
+ const alias = basename(path, ".json");
1810
+ if (configured.has(alias))
1811
+ delivered.add(alias);
1812
+ }
1813
+ }
1814
+ this.planReviewDeliveredAliases.set(callKey, delivered);
711
1815
  }
712
1816
  buildCallInput(stage, call, callKey, callIndex) {
713
1817
  const resumedInput = this.resumePreparedInputs.get(callKey);
714
1818
  if (resumedInput != null) {
715
- return structuredClone(resumedInput);
1819
+ const input = structuredClone(resumedInput);
1820
+ this.recordPlanReviewDeliveredAliases(callKey, call, input["absolute_file_paths"]);
1821
+ const binding = this.planReviewCallBindings.get(callKey);
1822
+ if (binding !== undefined) {
1823
+ input["cli_name"] = binding.cli_name;
1824
+ }
1825
+ return input;
716
1826
  }
717
- const prompt = renderCallPrompt(call.prompt, this.definition.variables, this.variables, {
1827
+ const renderedPrompt = renderCallPrompt(call.prompt, { ...this.definition.variables, ...ENGINE_DERIVED_VARIABLE_DECLS }, this.effectiveVariables, {
718
1828
  workflow: { run_id: this.runId },
719
1829
  stage: { id: stage.id },
720
1830
  call: { id: call.id },
721
1831
  });
1832
+ /**
1833
+ * A-6/D9: append the verified plan snapshot AFTER rendering, never before.
1834
+ *
1835
+ * The snapshot must not be an input to `renderCallPrompt`. A plan is
1836
+ * untrusted text that may legitimately contain `{{...}}` (plans in this
1837
+ * repo do), and `PLACEHOLDER_RE` scans the WHOLE string it is given: a
1838
+ * `{{call.id}}` inside the plan would be silently substituted — handing the
1839
+ * actor bytes that are NOT the verified snapshot, which is a D9 violation
1840
+ * requiring no tampering at all — and an undeclared `{{user_request}}`
1841
+ * would throw and brick an honest run. Appending after rendering is what
1842
+ * makes the snapshot reach the actor VERBATIM.
1843
+ *
1844
+ * This lands before `estimatePromptBudget` below on purpose: the snapshot
1845
+ * is real dispatched bytes (up to 512 KiB) and must be billed as such, or
1846
+ * the authoritative gate would under-count by the largest term in it.
1847
+ */
1848
+ const prompt = call.plan_review_snapshot_text === undefined
1849
+ ? renderedPrompt
1850
+ : `${renderedPrompt}${renderPlanSnapshotBlock(call.plan_review_snapshot_text)}`;
722
1851
  const contextArtifacts = call.anonymize_context === true
723
- ? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex)
1852
+ ? this.artifactStore.buildAnonymizedContext(this.definition, call.context_from, callIndex, call.plan_review_semantic_context === true
1853
+ ? projectPlanReviewSemanticBody
1854
+ : undefined, call.plan_review_semantic_context === true
1855
+ ? `${stage.id}-${call.id}`
1856
+ : undefined, call.plan_review_anonymous_aliases)
724
1857
  : this.artifactStore.buildContext(this.definition, call.context_from);
725
1858
  const staticFiles = call.absolute_file_paths ?? [];
726
1859
  const dynamicFiles = this.resolveDynamicPaths(call.absolute_file_paths_from, `${stage.id}.${call.id}.absolute_file_paths_from`);
@@ -731,7 +1864,11 @@ export class WorkflowEngine {
731
1864
  ...staticFiles,
732
1865
  ...dynamicFiles,
733
1866
  ];
1867
+ this.recordPlanReviewDeliveredAliases(callKey, call, contextArtifacts.absoluteFilePaths);
734
1868
  const images = [...staticImages, ...dynamicImages];
1869
+ const reserveBytes = isCouncilExecutionWorkflow(this.definition.name)
1870
+ ? (this.promptReserveBytes ?? DEFAULT_RESERVE_BYTES) + COUNCIL_DEADLINE_GUIDANCE_RESERVE_BYTES
1871
+ : this.promptReserveBytes;
735
1872
  const estimate = estimatePromptBudget({
736
1873
  promptText: prompt,
737
1874
  contextText: contextArtifacts.context,
@@ -742,13 +1879,15 @@ export class WorkflowEngine {
742
1879
  ],
743
1880
  imageContributors: images.map((path) => createPathContributor(`image:${path}`, path, "image")),
744
1881
  maxBytes: this.maxPromptBytes,
745
- reserveBytes: this.promptReserveBytes,
1882
+ reserveBytes,
746
1883
  });
747
1884
  enforcePromptBudget(estimate);
748
1885
  const input = {
749
1886
  cli_name: call.cli_name,
750
1887
  role: call.role,
751
1888
  prompt,
1889
+ ...(call.model !== undefined ? { model: call.model } : {}),
1890
+ ...(call.effort !== undefined ? { effort: call.effort } : {}),
752
1891
  };
753
1892
  if (absoluteFilePaths.length > 0) {
754
1893
  input["absolute_file_paths"] = absoluteFilePaths;
@@ -773,26 +1912,59 @@ export class WorkflowEngine {
773
1912
  }
774
1913
  return input;
775
1914
  }
1915
+ /**
1916
+ * Derive a per-call invocation audit from the materialized input.
1917
+ * Uses the dispatched cli_name/role (plan-review bindings may rewrite
1918
+ * cli_name on resume) and never mutates `input`.
1919
+ */
1920
+ maybeInvocationAudit(input) {
1921
+ const derive = this.deriveInvocationAudit;
1922
+ if (derive == null) {
1923
+ return undefined;
1924
+ }
1925
+ const cliName = input["cli_name"];
1926
+ const role = input["role"];
1927
+ if (typeof cliName !== "string" || typeof role !== "string") {
1928
+ return undefined;
1929
+ }
1930
+ const model = input["model"];
1931
+ const effort = input["effort"];
1932
+ return derive(cliName, role, {
1933
+ ...(typeof model === "string" ? { model } : {}),
1934
+ ...(typeof effort === "string" ? { effort } : {}),
1935
+ });
1936
+ }
776
1937
  async materializeUnexpectedFailure(stage, call, error) {
1938
+ // Only cancellation escapes a parallel stage. Every other failure —
1939
+ // environmental (an exhausted dispatch deadline) as much as a child
1940
+ // failure — materializes into an ExecutedCall{ok:false} so already-settled
1941
+ // sibling results survive Promise.allSettled instead of being discarded by
1942
+ // a rethrow.
777
1943
  if (error instanceof WorkflowCancellationError) {
778
1944
  throw error;
779
1945
  }
780
1946
  const message = error instanceof Error ? error.message : String(error);
781
1947
  this.artifactStore.noteFailedCall(stage.id, call.id, message);
782
1948
  logger.error({ run_id: this.runId, stage_id: stage.id, call_id: call.id, err: message }, "parallel stage call rejected unexpectedly");
783
- await this.emitCallStateChange({
784
- runId: this.runId,
785
- workflowName: this.definition.name,
786
- stageId: stage.id,
787
- callId: call.id,
788
- state: "failed",
789
- attempt: 1,
790
- retryCount: 0,
791
- completedAt: new Date().toISOString(),
792
- error: message,
793
- failureClass: classifyFailureClass(error),
794
- childDebugMetadata: error instanceof CallExecutionError ? error.childDebugMetadata : undefined,
795
- });
1949
+ // Only the genuinely un-terminalized failures need a state emit here.
1950
+ // `emitTerminalCallAbort` already wrote the real attempt/retryCount and
1951
+ // startedAt/durationMs, and onCallStateChange assigns those fields
1952
+ // unconditionally, so re-emitting would overwrite them with placeholders.
1953
+ if (!this.terminalizedCallKeys.has(`${stage.id}.${call.id}`)) {
1954
+ await this.emitCallStateChange({
1955
+ runId: this.runId,
1956
+ workflowName: this.definition.name,
1957
+ stageId: stage.id,
1958
+ callId: call.id,
1959
+ state: "failed",
1960
+ attempt: 1,
1961
+ retryCount: 0,
1962
+ completedAt: new Date().toISOString(),
1963
+ error: message,
1964
+ failureClass: classifyFailureClass(error),
1965
+ childDebugMetadata: error instanceof CallExecutionError ? error.childDebugMetadata : undefined,
1966
+ });
1967
+ }
796
1968
  await this.emitProgress({
797
1969
  runId: this.runId,
798
1970
  workflowName: this.definition.name,
@@ -834,7 +2006,9 @@ export class WorkflowEngine {
834
2006
  }
835
2007
  }
836
2008
  for (const failedCall of this.resumeFailedCalls.values()) {
837
- this.artifactStore.seedFailedCall(failedCall.stageId, failedCall.callId, failedCall.error);
2009
+ if (failedCall.terminal) {
2010
+ this.artifactStore.seedFailedCall(failedCall.stageId, failedCall.callId, failedCall.error);
2011
+ }
838
2012
  }
839
2013
  }
840
2014
  /**
@@ -994,8 +2168,84 @@ function classifyFailureClass(error) {
994
2168
  if (error instanceof WorkflowCancellationError) {
995
2169
  return "permanent";
996
2170
  }
2171
+ // Cross-package classification marker: errors thrown by the injected root
2172
+ // package (e.g. a context-builder preflight delegate timeout) cannot be
2173
+ // narrowed by instanceof here, so honor a well-formed `failureClass`
2174
+ // property when present. Anything else keeps the permanent default.
2175
+ const marker = crossPackageFailureClass(error);
2176
+ if (marker != null) {
2177
+ return marker;
2178
+ }
997
2179
  return "permanent";
998
2180
  }
2181
+ function crossPackageFailureClass(error) {
2182
+ if (error == null || typeof error !== "object") {
2183
+ return undefined;
2184
+ }
2185
+ const value = error.failureClass;
2186
+ return value === "transient" || value === "permanent" ? value : undefined;
2187
+ }
2188
+ /**
2189
+ * Bounded corrective suffix appended to the NEXT attempt's prompt after a
2190
+ * JSON/schema-shaped failure (SchemaValidationError, or OutputSizeError as
2191
+ * the second correctable class) that is already being retried. Total suffix
2192
+ * length is capped at CORRECTIVE_SUFFIX_MAX_CHARS; the failure detail is
2193
+ * truncated to fit. Never persisted — resume replays the original input.
2194
+ */
2195
+ const CORRECTIVE_SUFFIX_MAX_CHARS = 1_500;
2196
+ const CORRECTIVE_SUFFIX_HEADER = "\n\n---\nCORRECTION: Your previous response was rejected because it was not schema-valid. Failure: ";
2197
+ 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: ";
2198
+ const CORRECTIVE_SUFFIX_FOOTER = "\nReturn ONLY a single JSON object conforming to the declared contract — no prose, no markdown, " +
2199
+ "no code fences, and nothing before or after the JSON object.";
2200
+ /**
2201
+ * Detail text for a JSON/schema-shaped failure, or undefined when the failure
2202
+ * is not one a corrective re-prompt could fix (transport death, timeout, …).
2203
+ */
2204
+ function correctiveFailureDetail(error) {
2205
+ if (error instanceof TypedResultFidelityError) {
2206
+ return error.message;
2207
+ }
2208
+ if (error instanceof SchemaValidationError) {
2209
+ return error.message;
2210
+ }
2211
+ if (error instanceof OutputSizeError) {
2212
+ return error.message;
2213
+ }
2214
+ if (error instanceof CallExecutionError) {
2215
+ const debug = error.childDebugMetadata;
2216
+ if (debug?.rejection_reason === "invalid_json_output" ||
2217
+ debug?.semantic_failure_reason === "invalid_json_output" ||
2218
+ debug?.secondary_failure_reason === "invalid_json_output") {
2219
+ return "output did not contain valid JSON";
2220
+ }
2221
+ }
2222
+ return undefined;
2223
+ }
2224
+ /** Mirrors the expect_json derivation in buildCallInput. */
2225
+ function declaresJsonContract(call) {
2226
+ return call.response_contract === "json" || call.validator_schema_id != null;
2227
+ }
2228
+ /**
2229
+ * D1b: fold the workflow's `context_external_gap_policy` into an ENABLED
2230
+ * preflight delegate that does not choose one itself. Runtime-only: the
2231
+ * caller's `cb_delegate` variable (and so the inputs snapshot) is untouched,
2232
+ * and an explicit delegate `external_gap_policy` always wins.
2233
+ */
2234
+ function foldExternalGapPolicy(delegate, policy) {
2235
+ if (policy === undefined || delegate.enabled !== true || delegate.external_gap_policy !== undefined) {
2236
+ return delegate;
2237
+ }
2238
+ return { ...delegate, external_gap_policy: policy };
2239
+ }
2240
+ function buildCorrectiveSuffixText(detail, header = CORRECTIVE_SUFFIX_HEADER) {
2241
+ const budget = CORRECTIVE_SUFFIX_MAX_CHARS -
2242
+ header.length -
2243
+ CORRECTIVE_SUFFIX_FOOTER.length;
2244
+ const bounded = detail.length > budget
2245
+ ? `${detail.slice(0, Math.max(0, budget - 1))}…`
2246
+ : detail;
2247
+ return `${header}${bounded}${CORRECTIVE_SUFFIX_FOOTER}`;
2248
+ }
999
2249
  /**
1000
2250
  * Determine whether an error is retryable based on the retry mode (5-1, 5-2).
1001
2251
  *