@iowarp/clio-coder 0.3.8 → 0.4.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 (552) hide show
  1. package/CHANGELOG.md +102 -0
  2. package/NOTICE +33 -0
  3. package/README.md +12 -3
  4. package/dist/{acp-U67UHUK2.js → acp-G5WJBNCT.js} +13 -12
  5. package/dist/{agents-YU6SGALZ.js → agents-FMV2Q5G4.js} +41 -36
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5ZPJOIVG.js → auth-3IDSJEIK.js} +18 -18
  8. package/dist/{builtins-C6JMZVV6.js → builtins-XCZWXSC7.js} +5 -5
  9. package/dist/{chunk-IFBNV6H6.js → chunk-2ANTL7MR.js} +3 -3
  10. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  11. package/dist/{chunk-XWSF374K.js → chunk-2OQE55CK.js} +3 -3
  12. package/dist/{chunk-5H3GB5BO.js → chunk-32KWKNSF.js} +8 -384
  13. package/dist/{chunk-KTYTFRMB.js → chunk-36EJLSQQ.js} +34 -36
  14. package/dist/{chunk-FHJEP5SW.js → chunk-3BT2XMV4.js} +19 -13
  15. package/dist/chunk-3DPEIQKN.js +113 -0
  16. package/dist/{chunk-GU2UIAFZ.js → chunk-3URVFKWK.js} +7 -7
  17. package/dist/{chunk-WEH5XRJQ.js → chunk-3XML7CDN.js} +3 -3
  18. package/dist/chunk-42FMPA75.js +101 -0
  19. package/dist/{chunk-A2NJGIB3.js → chunk-5LXZXPKX.js} +2 -2
  20. package/dist/{chunk-VCBR6CU7.js → chunk-5PSMVOLM.js} +2 -2
  21. package/dist/{chunk-NMPKI6XL.js → chunk-6DB53AJS.js} +203 -25
  22. package/dist/{chunk-3BINW3FP.js → chunk-76ONBSIA.js} +2 -2
  23. package/dist/{chunk-26LEYJZH.js → chunk-7MCTRUCE.js} +2 -2
  24. package/dist/{chunk-TYPGUK6W.js → chunk-AB44T6BB.js} +111 -5
  25. package/dist/{chunk-K4XHGFR5.js → chunk-B7OBL7PK.js} +317 -721
  26. package/dist/{chunk-B5CSFE7B.js → chunk-BBVJUZHB.js} +2 -2
  27. package/dist/{chunk-EQ63NRB7.js → chunk-BBVYXMFO.js} +2 -2
  28. package/dist/{chunk-ZVJ5BLO2.js → chunk-BKFJHQCA.js} +154 -16
  29. package/dist/chunk-BKFM6EJV.js +462 -0
  30. package/dist/chunk-BMS5RKQY.js +27 -0
  31. package/dist/{chunk-ZNLWCMVZ.js → chunk-BPKCPIL7.js} +2 -2
  32. package/dist/{chunk-TLQJPP24.js → chunk-BUMFYQFY.js} +1394 -1349
  33. package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
  34. package/dist/chunk-C2LTL2W6.js +2447 -0
  35. package/dist/{chunk-ME6CCNFO.js → chunk-CODPRO7Q.js} +8 -8
  36. package/dist/{chunk-E77JEWSD.js → chunk-CTJ4RNAA.js} +7 -37
  37. package/dist/{chunk-ODFEOB4F.js → chunk-CY6FY24N.js} +26 -8
  38. package/dist/{chunk-7RFXX52T.js → chunk-DQITNCXG.js} +642 -172
  39. package/dist/{chunk-TTHACPOM.js → chunk-DYJP44XW.js} +578 -117
  40. package/dist/{chunk-2HFZQUHL.js → chunk-F4CKPOEQ.js} +18 -8
  41. package/dist/{chunk-DGSYXYMX.js → chunk-FEFIFZTL.js} +3 -3
  42. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  43. package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
  44. package/dist/chunk-GKF55TAZ.js +391 -0
  45. package/dist/{chunk-VWZOAB7K.js → chunk-GR5G2PVF.js} +9 -8
  46. package/dist/{chunk-4DGYLA73.js → chunk-GXNLGKAB.js} +80 -9
  47. package/dist/chunk-HHV2GANA.js +88 -0
  48. package/dist/{chunk-IIZWH4XA.js → chunk-HI63TFOG.js} +5 -4
  49. package/dist/{chunk-WLFILSD5.js → chunk-HJJTYUHX.js} +113 -83
  50. package/dist/{chunk-TT36MB5S.js → chunk-HLW2MRKE.js} +3 -1
  51. package/dist/{chunk-PMDBGQSJ.js → chunk-HWHKMHUA.js} +7 -7
  52. package/dist/chunk-HZHHCK24.js +1631 -0
  53. package/dist/{chunk-WSB3FPX7.js → chunk-I5VEOC6I.js} +39 -143
  54. package/dist/chunk-IBEBSCYA.js +564 -0
  55. package/dist/chunk-IQ7KR472.js +362 -0
  56. package/dist/{chunk-A3WNZD3P.js → chunk-J4W7KFM7.js} +949 -972
  57. package/dist/{chunk-TB5666IT.js → chunk-JDG2WCRO.js} +5 -5
  58. package/dist/{chunk-N22QMJKY.js → chunk-K5C3NCBD.js} +4 -4
  59. package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
  60. package/dist/{chunk-5C3AQNDW.js → chunk-KFZI4NIL.js} +216 -38
  61. package/dist/chunk-KMVISBZR.js +132 -0
  62. package/dist/{chunk-WXY7KU3G.js → chunk-LDQ2ZF2M.js} +2 -2
  63. package/dist/{chunk-XN3L4EYL.js → chunk-LQ3DZAMX.js} +3 -3
  64. package/dist/{chunk-U6MBIEMB.js → chunk-LY4S7GJC.js} +173 -144
  65. package/dist/{chunk-4SPRNWDE.js → chunk-MLKNTWH2.js} +19 -19
  66. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  67. package/dist/chunk-NEKRRTYW.js +56 -0
  68. package/dist/{chunk-SPULKLCF.js → chunk-NHCZP4K7.js} +3 -3
  69. package/dist/chunk-NHLBIGRH.js +1506 -0
  70. package/dist/chunk-NQQH3YT7.js +302 -0
  71. package/dist/chunk-NYS75XW5.js +15 -0
  72. package/dist/{chunk-GN57SG4G.js → chunk-O4XIVISU.js} +10 -8
  73. package/dist/{chunk-EMYUUSFG.js → chunk-O6TL7WWY.js} +6 -6
  74. package/dist/chunk-OQBA45DZ.js +97 -0
  75. package/dist/{chunk-LU7P4LHA.js → chunk-P3FOHJT4.js} +2 -2
  76. package/dist/chunk-PMZCIOCJ.js +25 -0
  77. package/dist/{chunk-I4HZDVNP.js → chunk-PQEFIJ36.js} +2 -2
  78. package/dist/{chunk-J3YUBZWY.js → chunk-QBJA7R7N.js} +62 -6
  79. package/dist/chunk-QDC3K2U3.js +262 -0
  80. package/dist/{chunk-2HEJ2F35.js → chunk-QLFS5GO2.js} +22 -10
  81. package/dist/{chunk-7RGZWPB6.js → chunk-QLL7ILRG.js} +95 -32
  82. package/dist/{chunk-YS5VLNH5.js → chunk-QREDIESB.js} +6 -6
  83. package/dist/chunk-QSNYB6ZV.js +195 -0
  84. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  85. package/dist/chunk-RKKLTLYB.js +45 -0
  86. package/dist/{chunk-P43ETTHK.js → chunk-SJ5ZKQ4S.js} +2 -2
  87. package/dist/{chunk-GPIEI3LY.js → chunk-SP2RXXYO.js} +6 -54
  88. package/dist/chunk-SUCTJL45.js +45 -0
  89. package/dist/{chunk-DYIM5TJT.js → chunk-SUW5DORT.js} +263 -7
  90. package/dist/chunk-T56WDKA5.js +183 -0
  91. package/dist/chunk-TVHHYFHE.js +255 -0
  92. package/dist/{chunk-FJ3H4MN5.js → chunk-TZ3SGWZZ.js} +3 -3
  93. package/dist/{chunk-MXKJU4JB.js → chunk-U77AMWDL.js} +91 -10
  94. package/dist/{chunk-5DHKRSMQ.js → chunk-ULC6OTWO.js} +11 -7
  95. package/dist/{chunk-RWSI4YD7.js → chunk-UM7N4G5A.js} +33 -12
  96. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  97. package/dist/{chunk-IGWKHNIQ.js → chunk-UXMFQ54G.js} +44 -37
  98. package/dist/{chunk-HFSBBKSQ.js → chunk-V5DHCITQ.js} +171 -3
  99. package/dist/{chunk-5WIGXA4T.js → chunk-VAZSBTKF.js} +111 -4
  100. package/dist/{chunk-VHN4MY6O.js → chunk-VEO4AP2K.js} +2 -2
  101. package/dist/{chunk-IJ7RPIYJ.js → chunk-VFA6GDY5.js} +65 -4
  102. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  103. package/dist/chunk-VO67MWHC.js +75 -0
  104. package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
  105. package/dist/{chunk-TANS5ZJS.js → chunk-VYMXRQI6.js} +36 -22
  106. package/dist/{chunk-WWCZ5F23.js → chunk-W5VSYASO.js} +77 -16
  107. package/dist/{chunk-WNIJTQQK.js → chunk-WZR7K7ZX.js} +72 -116
  108. package/dist/{chunk-5Q2VVUKB.js → chunk-X3YGUTOB.js} +4 -4
  109. package/dist/chunk-X75E3D2N.js +686 -0
  110. package/dist/{chunk-VAWNZU7Z.js → chunk-YDFRH54B.js} +4 -4
  111. package/dist/chunk-YJX4SHTD.js +40 -0
  112. package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
  113. package/dist/chunk-Z2RR6MAK.js +127 -0
  114. package/dist/{chunk-FBVTI2TJ.js → chunk-Z4TXYIEG.js} +12 -131
  115. package/dist/cli/index.js +47 -36
  116. package/dist/{clio-QVTYJ57A.js → clio-2JXHBBY5.js} +7 -7
  117. package/dist/{code-nav-FGGFIE7L.js → code-nav-3YYRMYNF.js} +8 -8
  118. package/dist/{compile-cache-CVJMMODC.js → compile-cache-7FPE6PS3.js} +3 -3
  119. package/dist/{components-ZFA3SAER.js → components-RYZV4JGP.js} +5 -5
  120. package/dist/{config-LW5IJFQN.js → config-QZPCMYSO.js} +99 -63
  121. package/dist/{configure-7XIZCOU4.js → configure-TEGEBYCA.js} +23 -22
  122. package/dist/{context-Y6Y7QPR6.js → context-AV7OEZ4D.js} +12 -12
  123. package/dist/{context-L3WL3X7K.js → context-E6H5RNMC.js} +56 -47
  124. package/dist/{context-N52ZA626.js → context-GSXUE4CT.js} +29 -27
  125. package/dist/{context-clear-MBQRLSDQ.js → context-clear-SHIBYK6T.js} +55 -46
  126. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  127. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-5ZGKPGZQ.js} +13 -13
  128. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-EFMJT4LD.js} +93 -64
  129. package/dist/{docs-7LQ23DLM.js → docs-23KQS3XK.js} +5 -5
  130. package/dist/doctor-QOA5FNY5.js +313 -0
  131. package/dist/{eval-BEC2WHDA.js → eval-TFBYQH4H.js} +2032 -156
  132. package/dist/eval-inventory-SXH7PDKX.js +316 -0
  133. package/dist/{evidence-REJUMSKM.js → evidence-ERGESKGN.js} +203 -48
  134. package/dist/{evolve-PY5ZBA5K.js → evolve-VDXTSYCJ.js} +52 -43
  135. package/dist/{extensions-HVKU65YU.js → extensions-7BGBHN57.js} +13 -7
  136. package/dist/{fleet-7WZEWRFA.js → fleet-2RRVDF2V.js} +228 -108
  137. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-VJ726XIA.js} +11 -11
  138. package/dist/fleet-decisions-EPAPM3XJ.js +157 -0
  139. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-JF5QOATM.js} +18 -15
  140. package/dist/fleet-inspect-VLY4S7QM.js +442 -0
  141. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-AIZUEJOY.js} +5 -5
  142. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-AJRPDMDV.js} +22 -19
  143. package/dist/fleet-verify-JFEL2L3H.js +175 -0
  144. package/dist/fleet-view-ZCON35AG.js +102 -0
  145. package/dist/{init-OG3TPGQG.js → init-DN2WWLFE.js} +72 -62
  146. package/dist/install-XGLBQY5E.js +13 -0
  147. package/dist/interop-OZBKXAYL.js +114 -0
  148. package/dist/{library-CNTMPLRF.js → library-YWZG7IMW.js} +21 -18
  149. package/dist/{memory-6IS7F275.js → memory-I4C4HMLW.js} +54 -45
  150. package/dist/{models-ENRJDA5W.js → models-CEYXJBO6.js} +33 -30
  151. package/dist/{monitor-XLDVO7TN.js → monitor-NZ6GCI3P.js} +59 -52
  152. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-GCGQ4N5I.js} +7827 -7328
  153. package/dist/panes-HMABYVO4.js +58 -0
  154. package/dist/panes-KY6W3V2E.js +103 -0
  155. package/dist/{paths-DBXMZMDU.js → paths-II4K7DNR.js} +5 -5
  156. package/dist/{reset-RZ4ER727.js → reset-DQ6FGCSH.js} +13 -11
  157. package/dist/resources-BB3MVJMD.js +111 -0
  158. package/dist/{run-Y2CNK5RU.js → run-H2GQDUER.js} +119 -87
  159. package/dist/{share-A55GYP6Z.js → share-GTJN6A5O.js} +20 -17
  160. package/dist/{skills-ALC5J6AT.js → skills-L55TEW6R.js} +33 -24
  161. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-XVXPH2JI.js} +67 -56
  162. package/dist/skills-inventory-S4MXPJFV.js +126 -0
  163. package/dist/slash-commands-ZSGASKJC.js +77 -0
  164. package/dist/{steer-GGWFUJUD.js → steer-RZGSCY4R.js} +4 -4
  165. package/dist/{support-MIETYA5E.js → support-PKEUNNQL.js} +6 -6
  166. package/dist/{targets-VGNXIR3S.js → targets-NCPZ644J.js} +68 -40
  167. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-44SV3YCN.js} +6 -4
  168. package/dist/tools-DAF3DI3C.js +27 -0
  169. package/dist/{trace-PNCASAXC.js → trace-FYVW2MQA.js} +207 -10
  170. package/dist/tui-primitives-2AKXQNZK.js +13 -0
  171. package/dist/{uninstall-ZJF5H5ZN.js → uninstall-DW2PNOIC.js} +5 -5
  172. package/dist/{upgrade-FUSUAGHR.js → upgrade-3XPP6OQL.js} +27 -24
  173. package/dist/{usage-N4MKVHKD.js → usage-3NLHGTU2.js} +114 -62
  174. package/dist/{verifiers-YAWOJ3H2.js → verifiers-SSQONKRT.js} +172 -13
  175. package/dist/{verify-LTDHYBGY.js → verify-3U6J7FZI.js} +10 -10
  176. package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-S7RR6GZ7.js} +3 -3
  177. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-CEHYGPGQ.js} +75 -65
  178. package/dist/with-panes-MKB46MPQ.js +782 -0
  179. package/dist/worker/entry.js +106 -75
  180. package/docs/README.md +3 -2
  181. package/docs/acp.md +24 -3
  182. package/docs/alcf-provider.md +1 -1
  183. package/docs/architecture.md +2 -2
  184. package/docs/artifact-versions.md +9 -1
  185. package/docs/built-in-agents.md +1 -1
  186. package/docs/capacity-and-scheduling.md +62 -3
  187. package/docs/commands-and-modes.md +31 -2
  188. package/docs/configuration-and-targets.md +69 -12
  189. package/docs/context-engine.md +63 -4
  190. package/docs/development-pipeline.md +19 -0
  191. package/docs/dispatch-typed-intent.md +385 -0
  192. package/docs/documentation-coverage.md +5 -5
  193. package/docs/documentation-guide.md +1 -1
  194. package/docs/environment-variables.md +3 -0
  195. package/docs/eval-runner.md +262 -11
  196. package/docs/evals-internal.md +72 -2
  197. package/docs/evidence-and-memory.md +12 -11
  198. package/docs/evolution.md +1 -1
  199. package/docs/exit-codes-and-output.md +1 -1
  200. package/docs/extensions-and-sharing.md +27 -1
  201. package/docs/fleet-dispatch.md +25 -4
  202. package/docs/installation-and-lifecycle.md +15 -2
  203. package/docs/middleware-and-components.md +1 -1
  204. package/docs/model-catalog.md +10 -1
  205. package/docs/observability.md +55 -4
  206. package/docs/proactive-memory.md +127 -14
  207. package/docs/prompt-envelope-and-tools.md +19 -1
  208. package/docs/provider-adapter-cookbook.md +1 -1
  209. package/docs/release-cut-checklist.md +19 -3
  210. package/docs/safety-model.md +2 -2
  211. package/docs/scientific-validation.md +3 -3
  212. package/docs/session-lifecycle.md +1 -1
  213. package/docs/skills-marketplace.md +1 -1
  214. package/docs/tool-usage.md +18 -8
  215. package/docs/trace-store.md +1 -1
  216. package/docs/troubleshooting.md +88 -1
  217. package/docs/tui-design.md +1 -1
  218. package/docs/worker-dispatch-mechanics.md +1 -1
  219. package/package.json +5 -2
  220. package/src/cli/acp.ts +6 -2
  221. package/src/cli/agents.ts +1 -1
  222. package/src/cli/argv.ts +25 -0
  223. package/src/cli/config-inspect.ts +33 -6
  224. package/src/cli/config.ts +1 -1
  225. package/src/cli/configure.ts +23 -21
  226. package/src/cli/doctor-panes.ts +124 -0
  227. package/src/cli/doctor-state-size.ts +82 -0
  228. package/src/cli/doctor-toolchain.ts +57 -0
  229. package/src/cli/doctor.ts +22 -1
  230. package/src/cli/eval-inventory.ts +436 -0
  231. package/src/cli/eval.ts +93 -16
  232. package/src/cli/evidence-detail.ts +88 -0
  233. package/src/cli/evidence-inventory.ts +183 -0
  234. package/src/cli/evidence.ts +30 -5
  235. package/src/cli/extensions.ts +5 -1
  236. package/src/cli/fleet-decisions.ts +69 -0
  237. package/src/cli/fleet-inspect.ts +334 -0
  238. package/src/cli/fleet-verify.ts +133 -0
  239. package/src/cli/fleet-view.ts +810 -0
  240. package/src/cli/fleet.ts +179 -39
  241. package/src/cli/index.ts +14 -2
  242. package/src/cli/interop-inspect.ts +128 -0
  243. package/src/cli/interop.ts +34 -0
  244. package/src/cli/panes.ts +35 -0
  245. package/src/cli/reset.ts +5 -2
  246. package/src/cli/run.ts +58 -0
  247. package/src/cli/skills-inventory.ts +185 -0
  248. package/src/cli/skills.ts +16 -13
  249. package/src/cli/targets.ts +44 -13
  250. package/src/cli/tools.ts +321 -0
  251. package/src/cli/trace-inspect.ts +252 -0
  252. package/src/cli/trace.ts +85 -5
  253. package/src/cli/usage.ts +63 -14
  254. package/src/cli/verifiers-inspect.ts +347 -0
  255. package/src/cli/verifiers.ts +9 -0
  256. package/src/core/bus-events.ts +33 -1
  257. package/src/core/cache-telemetry.ts +42 -0
  258. package/src/core/config.ts +67 -0
  259. package/src/core/defaults.ts +124 -8
  260. package/src/core/endpoint-key.ts +27 -0
  261. package/src/core/residency-target-key.ts +25 -0
  262. package/src/core/response-schema.ts +80 -6
  263. package/src/core/theme-token-hex.ts +43 -0
  264. package/src/core/tool-names.ts +2 -1
  265. package/src/core/xdg.ts +1 -1
  266. package/src/domains/agents/fleets/build-review.md +0 -3
  267. package/src/domains/agents/fleets/build-test.md +0 -3
  268. package/src/domains/agents/result-contract-filesystem.ts +32 -0
  269. package/src/domains/agents/result-contract.ts +164 -35
  270. package/src/domains/config/classify.ts +6 -0
  271. package/src/domains/context/codewiki/coordinator.ts +12 -4
  272. package/src/domains/dispatch/admission-error.ts +9 -0
  273. package/src/domains/dispatch/admission.ts +52 -14
  274. package/src/domains/dispatch/capacity-lease.ts +118 -9
  275. package/src/domains/dispatch/contract.ts +11 -0
  276. package/src/domains/dispatch/council-topology.ts +398 -0
  277. package/src/domains/dispatch/execution-plan.ts +44 -4
  278. package/src/domains/dispatch/extension.ts +378 -82
  279. package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
  280. package/src/domains/dispatch/fleet-plan.ts +7 -2
  281. package/src/domains/dispatch/fleet-run.ts +87 -4
  282. package/src/domains/dispatch/gate-decisions.ts +11 -1
  283. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  284. package/src/domains/dispatch/gate-topology.ts +289 -0
  285. package/src/domains/dispatch/heartbeat.ts +32 -8
  286. package/src/domains/dispatch/index.ts +22 -0
  287. package/src/domains/dispatch/intent-compatibility.ts +330 -0
  288. package/src/domains/dispatch/intent.ts +85 -1
  289. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  290. package/src/domains/dispatch/reservation-store.ts +139 -11
  291. package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
  292. package/src/domains/dispatch/run-event-journal.ts +598 -0
  293. package/src/domains/dispatch/state.ts +49 -1
  294. package/src/domains/dispatch/types.ts +13 -0
  295. package/src/domains/dispatch/validation.ts +33 -8
  296. package/src/domains/dispatch/worker-spawn.ts +25 -11
  297. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  298. package/src/domains/dispatch/write-boundary.ts +62 -1
  299. package/src/domains/eval/artifacts/store.ts +62 -0
  300. package/src/domains/eval/compare/behavioral.ts +224 -0
  301. package/src/domains/eval/compare/compare.ts +342 -2
  302. package/src/domains/eval/compare/envelope.ts +128 -0
  303. package/src/domains/eval/compare/gates.ts +24 -6
  304. package/src/domains/eval/compare/thresholds.ts +30 -3
  305. package/src/domains/eval/execution-provenance.ts +240 -0
  306. package/src/domains/eval/inventory.ts +113 -0
  307. package/src/domains/eval/metrics/aggregate.ts +136 -0
  308. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  309. package/src/domains/eval/metrics/tracked.ts +413 -0
  310. package/src/domains/eval/provenance.ts +117 -0
  311. package/src/domains/eval/reports/comparison.ts +128 -0
  312. package/src/domains/eval/reports/junit.ts +17 -3
  313. package/src/domains/eval/reports/markdown.ts +3 -3
  314. package/src/domains/eval/reports/text.ts +14 -0
  315. package/src/domains/eval/run-compare.ts +20 -0
  316. package/src/domains/eval/runners/clio-run.ts +127 -0
  317. package/src/domains/eval/runners/external-command.ts +28 -3
  318. package/src/domains/eval/schema/adapter.ts +111 -0
  319. package/src/domains/eval/schema/artifact.ts +20 -0
  320. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  321. package/src/domains/eval/schema/behavioral.ts +520 -0
  322. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  323. package/src/domains/eval/schema/serving.ts +105 -0
  324. package/src/domains/eval/schema/suite.ts +38 -8
  325. package/src/domains/eval/schema/validate.ts +58 -3
  326. package/src/domains/eval/schema/verdict.ts +237 -0
  327. package/src/domains/eval/suites/resolve.ts +2 -0
  328. package/src/domains/eval/suites/run.ts +264 -33
  329. package/src/domains/eval/verifiers/command.ts +2 -1
  330. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  331. package/src/domains/evidence/build.ts +2 -13
  332. package/src/domains/evidence/eval.ts +2 -12
  333. package/src/domains/evidence/findings-markdown.ts +33 -0
  334. package/src/domains/evidence/run-trust.ts +7 -113
  335. package/src/domains/evidence/store.ts +6 -0
  336. package/src/domains/evidence/trust-projection.ts +2 -2
  337. package/src/domains/extensions/compatibility.ts +285 -0
  338. package/src/domains/extensions/discovery.ts +38 -3
  339. package/src/domains/extensions/resources.ts +1 -1
  340. package/src/domains/extensions/state.ts +12 -3
  341. package/src/domains/extensions/types.ts +2 -0
  342. package/src/domains/lifecycle/doctor.ts +69 -1
  343. package/src/domains/memory/index.ts +14 -1
  344. package/src/domains/memory/task-bank-promotion.ts +64 -0
  345. package/src/domains/memory/task-memory-policy.ts +82 -17
  346. package/src/domains/memory/task-memory-spend.ts +131 -0
  347. package/src/domains/memory/task-memory-status.ts +7 -0
  348. package/src/domains/memory/task-memory-telemetry.ts +3 -0
  349. package/src/domains/middleware/index.ts +1 -0
  350. package/src/domains/middleware/memory-intervention.ts +97 -21
  351. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  352. package/src/domains/mux/contract.ts +434 -0
  353. package/src/domains/mux/detect.ts +158 -0
  354. package/src/domains/mux/extension.ts +47 -0
  355. package/src/domains/mux/index.ts +96 -0
  356. package/src/domains/mux/manifest.ts +6 -0
  357. package/src/domains/mux/operations.ts +164 -0
  358. package/src/domains/mux/pane-registry.ts +90 -0
  359. package/src/domains/mux/protocol.ts +49 -0
  360. package/src/domains/mux/socket-client.ts +816 -0
  361. package/src/domains/mux/types.ts +222 -0
  362. package/src/domains/mux/viewer-command.ts +59 -0
  363. package/src/domains/mux/yazi/assets/init.lua +2 -0
  364. package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
  365. package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
  366. package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
  367. package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
  368. package/src/domains/mux/yazi/assets/yazi.toml +17 -0
  369. package/src/domains/mux/yazi/event-stream.ts +180 -0
  370. package/src/domains/mux/yazi/profile.ts +299 -0
  371. package/src/domains/mux/yazi/session.ts +228 -0
  372. package/src/domains/mux/yazi/theme.ts +30 -0
  373. package/src/domains/observability/background-memory-usage.ts +140 -0
  374. package/src/domains/observability/cost.ts +22 -1
  375. package/src/domains/observability/index.ts +9 -0
  376. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  377. package/src/domains/observability/trace-store.ts +234 -2
  378. package/src/domains/prompts/compiler.ts +100 -13
  379. package/src/domains/providers/endpoint-capacity.ts +228 -0
  380. package/src/domains/providers/endpoint-slots-store.ts +189 -0
  381. package/src/domains/providers/extension.ts +20 -3
  382. package/src/domains/providers/index.ts +32 -0
  383. package/src/domains/providers/model-runtime-capabilities.ts +32 -0
  384. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  385. package/src/domains/providers/runtime-resolution.ts +8 -1
  386. package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
  387. package/src/domains/providers/runtimes/builtins.ts +2 -0
  388. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  389. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  390. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  391. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  392. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  393. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  394. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  395. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  396. package/src/domains/providers/runtimes/protocol/litellm.ts +375 -0
  397. package/src/domains/providers/support.ts +1 -0
  398. package/src/domains/providers/target-model-cache.ts +124 -0
  399. package/src/domains/providers/types/capability-flags.ts +2 -0
  400. package/src/domains/providers/types/target-descriptor.ts +2 -0
  401. package/src/domains/resources/index.ts +3 -0
  402. package/src/domains/resources/prompts/loader.ts +95 -33
  403. package/src/domains/resources/skills/loader.ts +33 -0
  404. package/src/domains/safety/action-classifier.ts +6 -0
  405. package/src/domains/safety/call-target.ts +52 -0
  406. package/src/domains/safety/run-effects.ts +35 -4
  407. package/src/domains/session/context-accounting.ts +52 -1
  408. package/src/domains/session/context-ledger.ts +37 -13
  409. package/src/domains/session/index.ts +6 -0
  410. package/src/domains/session/prompt-cache.ts +140 -0
  411. package/src/domains/session/prompt-manifest.ts +42 -0
  412. package/src/domains/toolchain/archive.ts +175 -0
  413. package/src/domains/toolchain/contract.ts +28 -0
  414. package/src/domains/toolchain/extension.ts +47 -0
  415. package/src/domains/toolchain/index.ts +39 -0
  416. package/src/domains/toolchain/install.ts +327 -0
  417. package/src/domains/toolchain/manifest.ts +8 -0
  418. package/src/domains/toolchain/paths.ts +34 -0
  419. package/src/domains/toolchain/registry.ts +265 -0
  420. package/src/domains/toolchain/remove.ts +218 -0
  421. package/src/domains/toolchain/resolve.ts +182 -0
  422. package/src/domains/toolchain/types.ts +113 -0
  423. package/src/domains/toolchain/version.ts +88 -0
  424. package/src/engine/acp/adapter.ts +18 -3
  425. package/src/engine/acp/server.ts +413 -70
  426. package/src/engine/acp/types.ts +19 -1
  427. package/src/engine/ai.ts +35 -0
  428. package/src/engine/apis/llamacpp-residency.ts +55 -3
  429. package/src/engine/apis/lmstudio.ts +25 -5
  430. package/src/engine/apis/ollama-native.ts +2 -1
  431. package/src/engine/apis/openai-completions.ts +80 -17
  432. package/src/engine/apis/residency-lock.ts +3 -1
  433. package/src/engine/apis/residency.ts +34 -1
  434. package/src/engine/claude/sdk-module.ts +98 -0
  435. package/src/engine/claude/sdk-runtime.ts +19 -11
  436. package/src/engine/provider-payload.ts +29 -1
  437. package/src/engine/tui-primitives.ts +21 -0
  438. package/src/engine/tui.ts +1 -0
  439. package/src/engine/worker-runtime.ts +2 -13
  440. package/src/entry/boot-options.ts +2 -0
  441. package/src/entry/orchestrator.ts +279 -35
  442. package/src/entry/panes-activation.ts +31 -0
  443. package/src/entry/with-panes.ts +20 -0
  444. package/src/interactive/chat-loop-messages.ts +26 -7
  445. package/src/interactive/chat-loop.ts +318 -41
  446. package/src/interactive/chat-panel.ts +62 -8
  447. package/src/interactive/clio-editor.ts +45 -8
  448. package/src/interactive/context-activity.ts +5 -1
  449. package/src/interactive/context-meter.ts +1 -1
  450. package/src/interactive/context-overlay.ts +41 -10
  451. package/src/interactive/cost-overlay.ts +66 -6
  452. package/src/interactive/council-grid.ts +1 -3
  453. package/src/interactive/council.ts +11 -0
  454. package/src/interactive/dispatch-board.ts +102 -20
  455. package/src/interactive/fleet-run-preview.ts +41 -15
  456. package/src/interactive/handoff-round.ts +41 -2
  457. package/src/interactive/interactive-application.ts +177 -7
  458. package/src/interactive/interactive-input-runtime.ts +15 -0
  459. package/src/interactive/interactive-presentation.ts +4 -0
  460. package/src/interactive/interactive-shell.ts +20 -17
  461. package/src/interactive/interactive-slash-runtime.ts +63 -10
  462. package/src/interactive/memory-overlay.ts +9 -0
  463. package/src/interactive/modal-marker.ts +170 -0
  464. package/src/interactive/mutation-preview.ts +295 -0
  465. package/src/interactive/mux-bridge.ts +214 -0
  466. package/src/interactive/overlay-frame.ts +58 -2
  467. package/src/interactive/overlay-general-openers.ts +17 -0
  468. package/src/interactive/overlay-key-routing.ts +52 -3
  469. package/src/interactive/overlay-lifecycle.ts +56 -7
  470. package/src/interactive/overlay-model-selectors.ts +40 -3
  471. package/src/interactive/overlay-permission-lifecycle.ts +112 -24
  472. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  473. package/src/interactive/overlay-transitions.ts +18 -4
  474. package/src/interactive/overlays/agents.ts +1 -0
  475. package/src/interactive/overlays/ask-user.ts +227 -49
  476. package/src/interactive/overlays/auth-dialog.ts +1 -0
  477. package/src/interactive/overlays/context-reset.ts +1 -0
  478. package/src/interactive/overlays/cwd-fallback.ts +1 -0
  479. package/src/interactive/overlays/decisions.ts +11 -11
  480. package/src/interactive/overlays/extensions.ts +1 -0
  481. package/src/interactive/overlays/fleet-run-approval.ts +1 -0
  482. package/src/interactive/overlays/handoff-review.ts +1 -0
  483. package/src/interactive/overlays/help-reference.ts +6 -0
  484. package/src/interactive/overlays/interop.ts +1 -0
  485. package/src/interactive/overlays/library-install-confirm.ts +1 -0
  486. package/src/interactive/overlays/library-tabs.ts +28 -0
  487. package/src/interactive/overlays/list-overlay.ts +10 -1
  488. package/src/interactive/overlays/message-picker.ts +1 -0
  489. package/src/interactive/overlays/model-scope.ts +86 -0
  490. package/src/interactive/overlays/model-selector.ts +1 -0
  491. package/src/interactive/overlays/prompts.ts +12 -1
  492. package/src/interactive/overlays/session-selector.ts +1 -0
  493. package/src/interactive/overlays/settings-sections.ts +30 -0
  494. package/src/interactive/overlays/settings.ts +614 -45
  495. package/src/interactive/overlays/side-question.ts +1 -0
  496. package/src/interactive/overlays/skills-hub.ts +3 -11
  497. package/src/interactive/overlays/tree-selector.ts +1 -0
  498. package/src/interactive/pane-policy.ts +46 -0
  499. package/src/interactive/panes-runtime.ts +292 -0
  500. package/src/interactive/permission-hint.ts +34 -2
  501. package/src/interactive/permission-overlay.ts +159 -9
  502. package/src/interactive/prewarm.ts +197 -0
  503. package/src/interactive/render-trace.ts +162 -15
  504. package/src/interactive/renderers/compaction-summary.ts +29 -0
  505. package/src/interactive/renderers/tool-execution.ts +4 -0
  506. package/src/interactive/renderers/worker-entry.ts +122 -14
  507. package/src/interactive/side-question.ts +58 -1
  508. package/src/interactive/slash-commands.ts +251 -15
  509. package/src/interactive/status/controller.ts +11 -0
  510. package/src/interactive/status/state-machine.ts +54 -2
  511. package/src/interactive/status/types.ts +7 -0
  512. package/src/interactive/tasks-overlay.ts +1 -0
  513. package/src/interactive/terminal-lease.ts +2 -0
  514. package/src/interactive/theme/tokens.ts +3 -14
  515. package/src/interactive/turn-context.ts +346 -33
  516. package/src/interactive/turn-persistence.ts +14 -4
  517. package/src/interactive/turn-prewarm.ts +364 -0
  518. package/src/interactive/turn-queues.ts +7 -4
  519. package/src/interactive/turn-runtime.ts +8 -1
  520. package/src/interactive/turn-state.ts +23 -0
  521. package/src/interactive/view/artifacts.ts +109 -1
  522. package/src/interactive/view/view-overlay.ts +29 -3
  523. package/src/interactive/watch-pane.ts +152 -0
  524. package/src/interactive/worker-progress.ts +7 -1
  525. package/src/interactive/worker-receipts.ts +19 -1
  526. package/src/interactive/worker-stream.ts +5 -0
  527. package/src/interactive/yazi-bridge.ts +444 -0
  528. package/src/tools/ask-user.ts +43 -2
  529. package/src/tools/bootstrap.ts +26 -2
  530. package/src/tools/builtin-tool-catalog.ts +15 -0
  531. package/src/tools/compete-worktrees.ts +83 -2
  532. package/src/tools/core-bootstrap.ts +2 -1
  533. package/src/tools/dispatch-admission.ts +14 -3
  534. package/src/tools/dispatch-arguments.ts +20 -20
  535. package/src/tools/dispatch-plan.ts +17 -9
  536. package/src/tools/dispatch-run-events.ts +134 -19
  537. package/src/tools/dispatch-runner.ts +29 -7
  538. package/src/tools/dispatch-scout.ts +1 -1
  539. package/src/tools/dispatch-types.ts +15 -3
  540. package/src/tools/dispatch.ts +1 -1
  541. package/src/tools/executables.ts +17 -14
  542. package/src/tools/observation.ts +54 -4
  543. package/src/tools/panes-surface.ts +38 -0
  544. package/src/tools/panes.ts +112 -0
  545. package/src/tools/policy.ts +10 -1
  546. package/src/tools/presentation.ts +1 -0
  547. package/src/tools/registry.ts +16 -0
  548. package/dist/chunk-AOCYTWAV.js +0 -449
  549. package/dist/chunk-HLE42MG7.js +0 -37
  550. package/dist/chunk-HWUFFB6L.js +0 -83
  551. package/dist/chunk-JOZYP4GM.js +0 -279
  552. package/dist/doctor-M7YEDGAE.js +0 -91
@@ -0,0 +1,62 @@
1
+ /**
2
+ * What one fleet node is told about its own answer.
3
+ *
4
+ * A fleet contract renders a single prompt body and the plan compiler gives it
5
+ * to every agent node, because the body describes the whole chain and each node
6
+ * does one part of it. That is fine for the work description and wrong for the
7
+ * answer: nodes in the same chain hold different result contracts, so a body
8
+ * that names one contract names the wrong one for every other node. A loop's
9
+ * check node is where the mismatch is fatal rather than merely untidy. It is
10
+ * dispatched with `verifier-report` as its expected contract, and in
11
+ * `build-review` and `build-test` it received a body ending in "Answer with
12
+ * your `mutation-report`". The verifier complied with the instruction it was
13
+ * given, the gate held it to the schema that instruction never mentioned, and
14
+ * both were behaving correctly: every review cycle in those two fleets ended in
15
+ * `review gate produced no structured verdict` and the bounded revise loop was
16
+ * unresolvable by construction.
17
+ *
18
+ * So each node states its own contract, appended to the shared body, and the
19
+ * precedence line settles the conflict explicitly rather than leaving it to the
20
+ * model to guess which of two shapes the run will actually validate. This is
21
+ * the same remedy `COUNCIL_VOTE_MEMBER_DIRECTIVE` applies to a voting council
22
+ * member, for the same reason and with the same shape of failure behind it
23
+ * (#230): a run whose postcondition differs from the shape its prompt names
24
+ * fails on every input, not intermittently.
25
+ *
26
+ * The shape itself is quoted from `resultContractShape`, the one place a
27
+ * contract's wire example is written, so this directive cannot drift from the
28
+ * validator that judges the answer.
29
+ */
30
+
31
+ import { type ResultContract, resultContractShape } from "../agents/result-contract.js";
32
+
33
+ /**
34
+ * The answer directive for a node holding this contract, or null when the kind
35
+ * alone does not fix the shape.
36
+ *
37
+ * `architect-plan` names the path it must write, and a plan compiler resolving
38
+ * agents holds only the contract's kind. Inventing a path to render would state
39
+ * a location the validator does not check, so that kind gets no directive and
40
+ * its recipe keeps sole authority over the shape. Every other kind is fully
41
+ * determined by its kind and can be quoted exactly.
42
+ */
43
+ export function fleetNodeAnswerDirective(kind: ResultContract["kind"]): string | null {
44
+ if (kind === "architect-plan") return null;
45
+ return [
46
+ `Your result contract for this step is \`${kind}\`.`,
47
+ "The task above describes the whole chain. Any result shape it states belongs to the step that produces it, and does not apply to you.",
48
+ `End with exactly this shape and nothing else: ${resultContractShape({ kind })}`,
49
+ // The wire example carries literal names and evidence, and a small model
50
+ // handed one on a first attempt copies them: the first run of this
51
+ // directive came back claiming `npm run typecheck` / `exit 0` in the same
52
+ // breath as admitting it could not run a command. The fields are the
53
+ // contract; the values in the example are not.
54
+ "Its field values are an example. Fill them with what this run actually produced or observed, and never report a name or evidence you did not.",
55
+ ].join("\n");
56
+ }
57
+
58
+ /** The shared body plus the node's own answer directive. */
59
+ export function renderFleetNodeTask(task: string, kind: ResultContract["kind"]): string {
60
+ const directive = fleetNodeAnswerDirective(kind);
61
+ return directive === null ? task : `${task}\n\n${directive}`;
62
+ }
@@ -32,6 +32,7 @@ import {
32
32
  type ExecutionPlanStepInput,
33
33
  } from "./execution-plan.js";
34
34
  import type { ExecutionRole } from "./execution-role.js";
35
+ import { renderFleetNodeTask } from "./fleet-node-prompt.js";
35
36
 
36
37
  /** Where an agent node sits, so the caller can resolve its role and authority. */
37
38
  export interface FleetPlanAgentContext {
@@ -58,7 +59,11 @@ export interface FleetPlanAgentResolution {
58
59
 
59
60
  export interface CompileFleetPlanInput {
60
61
  contract: FleetContract;
61
- /** Rendered prompt body; every agent node carries it as its task. */
62
+ /**
63
+ * Rendered prompt body. It describes the whole chain, so every agent node
64
+ * carries it, followed by that node's own answer directive: the nodes hold
65
+ * different result contracts and the body can only ever name one of them.
66
+ */
62
67
  task: string;
63
68
  resolveAgent(context: FleetPlanAgentContext): FleetPlanAgentResolution;
64
69
  }
@@ -143,7 +148,7 @@ export function compileFleetExecutionPlan(input: CompileFleetPlanInput): Executi
143
148
  agentId: context.agentId,
144
149
  scope: context.scope,
145
150
  dependencies: [...dependencies],
146
- task,
151
+ task: renderFleetNodeTask(task, resolved.expectedResultContract),
147
152
  expectedResultContract: resolved.expectedResultContract,
148
153
  requestedAuthority: resolved.requestedAuthority,
149
154
  approvedAuthority: resolved.approvedAuthority,
@@ -21,6 +21,8 @@ import type { FleetCommandRegistry } from "../agents/fleet-commands.js";
21
21
  import type { FleetContract } from "../agents/fleet-contract.js";
22
22
  import { resultContractAuthorship } from "../agents/result-contract.js";
23
23
  import type { AgentSpec } from "../agents/spec.js";
24
+ import { aggregateCostAmounts, type CostAggregate, renderCostAmount } from "../observability/cost.js";
25
+ import type { CostProvenance } from "../providers/index.js";
24
26
  import { claimAssignmentVerdict, recordAssignmentAttempt, settleStoredAssignment } from "./assignment-store.js";
25
27
  import { runCodeStep } from "./code-step.js";
26
28
  import { codeStepDir, writeCodeStepRecord } from "./code-step-store.js";
@@ -43,9 +45,11 @@ import {
43
45
  materializePendingGateDecision,
44
46
  stagePendingGateDecision,
45
47
  } from "./gate-decisions.js";
48
+ import { type DispatchIntent, declaredScopeIntent } from "./intent.js";
46
49
  import { verifyReceiptIntegrity } from "./receipt-integrity.js";
47
50
  import { type FleetRunRecord, writeFleetRun } from "./state.js";
48
51
  import type { RunReceipt } from "./types.js";
52
+ import type { WriteBoundaryAttribution } from "./write-boundary.js";
49
53
  import { createWriteBoundaryEnforcer } from "./write-boundary-enforcer.js";
50
54
 
51
55
  /** Owner id for a plan with no model runs; it holds nothing to release. */
@@ -82,6 +86,15 @@ export interface FleetRunStepOutcome extends FleetRunStepEvent {
82
86
  /** Terminal run id, which is the receipt id for an agent step. */
83
87
  terminalRunId: string;
84
88
  costUsd: number;
89
+ /**
90
+ * Where the number in `costUsd` came from, carried beside it because the two
91
+ * are one fact. A gateway-routed run seals `costUsd: 0` with `unknown`: its
92
+ * tokens were measured and nobody priced them. A renderer that reads the
93
+ * amount without this prints `$0.0000` and turns unpriced work into a
94
+ * measured zero. `known_free` on a deterministic step is the honest reading:
95
+ * no model ran, so the zero is something code observed rather than missed.
96
+ */
97
+ costProvenance: CostProvenance;
85
98
  /** The sealed agent receipt when this was an agent step. */
86
99
  receipt?: RunReceipt;
87
100
  /** The durable code-step record when this was a deterministic step. */
@@ -191,7 +204,18 @@ export interface FleetRunOutcome {
191
204
  planHash: string;
192
205
  result: ExecutionPlanResult;
193
206
  receipts: ReadonlyArray<RunReceipt>;
207
+ /**
208
+ * The bare sum of every receipt's `costUsd`, which adds unpriced zeros to
209
+ * priced dollars and so cannot be rendered on its own. Retained for the
210
+ * interactive fleet overlay, which still reads it. Render `totalCost`.
211
+ */
194
212
  totalCostUsd: number;
213
+ /**
214
+ * The fold that keeps unpriced calls distinguishable from priced ones, so a
215
+ * run whose every receipt came back `unknown` renders as not measured rather
216
+ * than as a total of zero dollars.
217
+ */
218
+ totalCost: CostAggregate;
195
219
  /** Steps that ran unconditionally, excluding nodes a resolved loop skipped. */
196
220
  requiredStepCount: number;
197
221
  succeededStepCount: number;
@@ -216,6 +240,24 @@ function fleetGateCorrelation(subject: RunReceipt, decider: RunReceipt): GateDec
216
240
  return { agent, target, modelFamily, runtime, node, independent };
217
241
  }
218
242
 
243
+ /**
244
+ * Typed scope for one fleet step, drawn from the boundary its contract already
245
+ * declared. Returns null when the contract declared nothing for this position,
246
+ * which is every step of a pre-v4 contract and every readonly step: those keep
247
+ * the legacy inference path rather than being handed an empty declaration that
248
+ * would silently switch off path-scoped rule selection for them.
249
+ *
250
+ * A contract whose declared paths do not survive the boundary grammar is a
251
+ * compile-time authoring bug, not a dispatch-time one, so a malformed entry
252
+ * falls back to inference here rather than aborting a run mid-wave; the
253
+ * contract loader is the surface that refuses it.
254
+ */
255
+ function fleetStepIntent(writes: ReadonlyArray<string> | undefined): DispatchIntent | null {
256
+ if (writes === undefined || writes.length === 0) return null;
257
+ const built = declaredScopeIntent({ relevantPaths: writes });
258
+ return built.ok ? built.intent : null;
259
+ }
260
+
219
261
  export function fleetPlanWaveIndex(plan: ExecutionPlan, stepId: string): number {
220
262
  return plan.waves.findIndex((wave) => wave.includes(stepId));
221
263
  }
@@ -278,10 +320,29 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
278
320
  // A code step runs a registered command in a subprocess. Nothing
279
321
  // enumerates what that command wrote, so the step contributes no record
280
322
  // and its window falls back to blaming the whole diff.
281
- if (step === undefined || step.kind !== "agent") return null;
323
+ if (step === undefined) return null;
324
+ if (step.kind !== "agent") {
325
+ return {
326
+ recorded: [],
327
+ complete: false,
328
+ downgrades: [
329
+ {
330
+ reason: "code_step_untracked",
331
+ tool: null,
332
+ toolCallId: null,
333
+ runId: null,
334
+ stepId,
335
+ },
336
+ ],
337
+ } satisfies WriteBoundaryAttribution;
338
+ }
282
339
  const receipt = receiptsByStep.get(stepId);
283
340
  if (receipt === undefined) return null;
284
- return dispatch.observedRunWrites?.(receipt.runId) ?? null;
341
+ if (dispatch.observedRunWriteAttribution !== undefined) {
342
+ return dispatch.observedRunWriteAttribution(receipt.runId);
343
+ }
344
+ const recorded = dispatch.observedRunWrites?.(receipt.runId) ?? null;
345
+ return recorded === null ? null : { recorded, complete: true, downgrades: [] };
285
346
  },
286
347
  onVerdict(verdict, path) {
287
348
  // Concurrent changes are reported too. The window did not fail for them
@@ -318,6 +379,9 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
318
379
  succeeded: true,
319
380
  terminalRunId: replayedResult.terminalRunId,
320
381
  costUsd: receiptsByStep.get(stepId)?.costUsd ?? 0,
382
+ // A replay whose receipt no longer exists on disk has no cost claim at
383
+ // all, which is `unknown` rather than a zero the projection invented.
384
+ costProvenance: receiptsByStep.get(stepId)?.costProvenance ?? "unknown",
321
385
  result: replayedResult,
322
386
  });
323
387
  await fileStepAttempt(replayedResult.terminalRunId);
@@ -414,11 +478,24 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
414
478
  // The fleet write boundary is deliberately enforced after the step. The enforcer in
415
479
  // write-boundary-enforcer.ts snapshots and verifies the step window. Pre-emptive
416
480
  // confinement for declared commands would require a command sandbox, which does not exist.
481
+ //
482
+ // That enforcement split is exactly why a step's declared `writes:` reaches
483
+ // typed intent as `relevant_paths` rather than as `write_roots`. The
484
+ // contract already said what this position may change and the enforcer
485
+ // already holds it; restating it as intent write roots would mint a second
486
+ // grant enforced at the per-tool worker seam, which refuses outright on the
487
+ // subprocess and ACP runtimes a fleet may legitimately route a step to.
488
+ // Carrying it as declared scope changes no authority and gets the
489
+ // contract's own paths into project-rule selection and worker context,
490
+ // where prose tokens scraped from the rendered prompt were standing in for
491
+ // a declaration the contract had already made.
492
+ const stepIntent = fleetStepIntent(step.writes);
417
493
  const request: DispatchRequest = {
418
494
  agentId: step.agentId,
419
495
  executionRole: step.executionRole,
420
496
  task: step.task,
421
497
  cwd: workspaceRoot,
498
+ ...(stepIntent === null ? {} : { intent: stepIntent }),
422
499
  predecessorHandoffs: handoffs,
423
500
  requestOrigin: "user",
424
501
  lineage: {
@@ -571,11 +648,12 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
571
648
  succeeded: stepSucceeded,
572
649
  terminalRunId: receipt.runId,
573
650
  costUsd: receipt.costUsd,
651
+ costProvenance: receipt.costProvenance,
574
652
  receipt,
575
653
  ...(failureReason !== undefined ? { failureReason } : {}),
576
654
  });
577
655
  notice(
578
- `step ${step.id} ${step.agentId}: ${stepSucceeded ? "succeeded" : "failed"}${failureReason !== undefined ? ` reason=${failureReason}` : ""} assignment=${handle.runId} terminal-run=${receipt.runId} cost=$${receipt.costUsd.toFixed(4)}`,
656
+ `step ${step.id} ${step.agentId}: ${stepSucceeded ? "succeeded" : "failed"}${failureReason !== undefined ? ` reason=${failureReason}` : ""} assignment=${handle.runId} terminal-run=${receipt.runId} cost=${renderCostAmount(receipt.costUsd, receipt.costProvenance)}`,
579
657
  );
580
658
  return {
581
659
  stepId: step.id,
@@ -638,6 +716,8 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
638
716
  succeeded: outcome.report.passed,
639
717
  terminalRunId: outcome.record.runId,
640
718
  costUsd: 0,
719
+ // No model ran, so nothing needed pricing. This zero was observed.
720
+ costProvenance: "known_free",
641
721
  codeStep: outcome.record,
642
722
  recordPath,
643
723
  });
@@ -704,7 +784,7 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
704
784
  cancel: (assignmentId) => dispatch.abort(assignmentId),
705
785
  release: (ownerId) => {
706
786
  if (ownerId === NO_WORKER_RESERVATION) return;
707
- dispatch.reservations?.rollbackUnconsumed(ownerId);
787
+ dispatch.reservations?.release(ownerId);
708
788
  },
709
789
  releaseUnconsumed: (ownerId) => {
710
790
  if (ownerId === NO_WORKER_RESERVATION) return;
@@ -783,6 +863,9 @@ export async function executeFleetRun(input: ExecuteFleetRunInput): Promise<Flee
783
863
  result,
784
864
  receipts,
785
865
  totalCostUsd: receipts.reduce((sum, receipt) => sum + receipt.costUsd, 0),
866
+ totalCost: aggregateCostAmounts(
867
+ receipts.map((receipt) => ({ usd: receipt.costUsd, provenance: receipt.costProvenance })),
868
+ ),
786
869
  requiredStepCount: required.length,
787
870
  succeededStepCount,
788
871
  resolvedLoopCount: result.loops.length - failedLoops.length,
@@ -298,10 +298,20 @@ export interface StagePendingGateDecisionOptions {
298
298
  stateDir?: string;
299
299
  }
300
300
 
301
- function decisionsDirectory(stateDir = clioStateDir()): string {
301
+ /**
302
+ * Where the durable decision artifacts live.
303
+ *
304
+ * Exported so a read-only projection can bound its own scan without duplicating
305
+ * the layout. Nothing outside this module may compose an artifact path from it.
306
+ */
307
+ export function gateDecisionsDirectory(stateDir = clioStateDir()): string {
302
308
  return join(stateDir, "gate-decisions");
303
309
  }
304
310
 
311
+ function decisionsDirectory(stateDir = clioStateDir()): string {
312
+ return gateDecisionsDirectory(stateDir);
313
+ }
314
+
305
315
  function pendingDecisionsDirectory(stateDir = clioStateDir()): string {
306
316
  return join(decisionsDirectory(stateDir), "pending");
307
317
  }
@@ -2,6 +2,15 @@
2
2
 
3
3
  import { COUNCIL_BALLOT_SHAPE, COUNCIL_BALLOT_VERDICT_MAX_BYTES } from "../agents/result-contract.js";
4
4
 
5
+ /**
6
+ * Roster bounds for a council. Two is the point of the topology: one seated
7
+ * agent is a plain dispatch, and there is nothing to compare. Capacity denials
8
+ * quote the floor because it is the reason a council cannot answer saturation
9
+ * by dispatching fewer members.
10
+ */
11
+ export const COUNCIL_MIN_MEMBERS = 2;
12
+ export const COUNCIL_MAX_MEMBERS = 5;
13
+
5
14
  export const REVIEWER_GATE_PROMPT = [
6
15
  "You are a strict, read-only reviewer of one implementation run.",
7
16
  "Inspect the supplied result and repository state with read-only tools.",
@@ -0,0 +1,289 @@
1
+ /**
2
+ * Read-only projection of the durable coordinator gate decisions.
3
+ *
4
+ * Unlike a council, a gate has a record of its own. A reviewer verdict and a
5
+ * judge's winner cannot be written back into the subject receipts without
6
+ * invalidating them, so the coordinator seals a separate integrity-covered
7
+ * artifact per decision that links the decider receipt to every subject
8
+ * receipt. {@link gate-decisions.ts} owns writing and verifying those; this
9
+ * module reads a bounded newest-first window of them and projects the shape.
10
+ *
11
+ * Two things stay behind. The decision `detail` is free prose that interpolates
12
+ * a thrown message, a pipeline failure reason, a protected artifact list, or a
13
+ * plan hash, so it is classified against a closed set and never quoted. And the
14
+ * receipt digests are omitted outright: a verified artifact carries one per
15
+ * subject by construction, so repeating them would be a fingerprint that tells
16
+ * a reader nothing the run id does not.
17
+ */
18
+
19
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
20
+ import { join } from "node:path";
21
+ import { clioStateDir } from "../../core/xdg.js";
22
+ import type { GateRouteCorrelation } from "./execution-role.js";
23
+ import {
24
+ type GateDecisionArtifact,
25
+ type GateDecisionOutcome,
26
+ gateDecisionsDirectory,
27
+ verifyGateDecisionArtifact,
28
+ } from "./gate-decisions.js";
29
+
30
+ /** How many decisions one projection reports, newest first. */
31
+ export const GATE_TOPOLOGY_MAX_DECISIONS = 8;
32
+
33
+ /** Subject receipts named per decision. A compete gate judges at most four. */
34
+ export const GATE_TOPOLOGY_MAX_SUBJECTS = 6;
35
+
36
+ /**
37
+ * How many artifact files one listing will open.
38
+ *
39
+ * The directory is unbounded and a decision id sorts by its group before its
40
+ * timestamp, so the newest decision is only discoverable by reading each file's
41
+ * `createdAt`. The cap keeps the scan proportional on an installation that has
42
+ * accumulated gates for months, at the cost of missing a decision older than
43
+ * the cap. That is the right trade here, the same one {@link listFleetRuns}
44
+ * takes, because this window only ever wants the recent end.
45
+ */
46
+ export const GATE_TOPOLOGY_MAX_SCAN = 128;
47
+
48
+ /**
49
+ * Why a gate ended the way it did, as a closed set.
50
+ *
51
+ * Every member is the coordinator's own classification of its own branch. The
52
+ * durable `detail` string those branches write is not repeated: several
53
+ * interpolate a receipt's failure reason, the names of protected artifacts a
54
+ * candidate touched, or an approval request id, and any of the three would
55
+ * carry prose across a boundary that exists to stop exactly that. Anything this
56
+ * classifier does not recognise becomes `unclassified` rather than crossing.
57
+ */
58
+ export type GateDecisionReason =
59
+ | "all-candidates-failed"
60
+ | "builder-run-failed"
61
+ | "reviewer-run-failed"
62
+ | "reviewer-checks-failed"
63
+ | "reviewer-report-invalid"
64
+ | "judge-run-failed"
65
+ | "judge-result-invalid"
66
+ | "judge-winner-out-of-range"
67
+ | "judge-picked-failed-candidate"
68
+ | "winner-touches-protected-artifact"
69
+ | "operator-confirmed-winner"
70
+ | "full-auto-applied-winner"
71
+ | "unclassified";
72
+
73
+ export const GATE_DECISION_REASONS: ReadonlyArray<GateDecisionReason> = [
74
+ "all-candidates-failed",
75
+ "builder-run-failed",
76
+ "reviewer-run-failed",
77
+ "reviewer-checks-failed",
78
+ "reviewer-report-invalid",
79
+ "judge-run-failed",
80
+ "judge-result-invalid",
81
+ "judge-winner-out-of-range",
82
+ "judge-picked-failed-candidate",
83
+ "winner-touches-protected-artifact",
84
+ "operator-confirmed-winner",
85
+ "full-auto-applied-winner",
86
+ "unclassified",
87
+ ];
88
+
89
+ /**
90
+ * Prefix rules, first match wins, ordered so no rule shadows another.
91
+ *
92
+ * These are matched against the exact strings the coordinator writes. A rule
93
+ * failing to match costs its decision the specific reason and nothing else,
94
+ * because `unclassified` is a real answer here: the operator still learns that
95
+ * the gate ended and how, just not which branch produced the text.
96
+ */
97
+ const REASON_RULES: ReadonlyArray<readonly [string, GateDecisionReason]> = [
98
+ ["every candidate builder failed", "all-candidates-failed"],
99
+ ["builder ended ", "builder-run-failed"],
100
+ ["reviewer ended ", "reviewer-run-failed"],
101
+ ["reviewer reported ", "reviewer-checks-failed"],
102
+ ["reviewer did not return a valid verifier report", "reviewer-report-invalid"],
103
+ ["judge ended ", "judge-run-failed"],
104
+ ["judge result must", "judge-result-invalid"],
105
+ ["judge result has unknown fields", "judge-result-invalid"],
106
+ ["judge winner must be an integer", "judge-winner-out-of-range"],
107
+ ["judge picked failed or missing candidate", "judge-picked-failed-candidate"],
108
+ ["judge-selected candidate ", "winner-touches-protected-artifact"],
109
+ ["operator confirmation ", "operator-confirmed-winner"],
110
+ ["full-auto applied ", "full-auto-applied-winner"],
111
+ ];
112
+
113
+ /**
114
+ * The identifier shape the decision store already guarantees for both its ids
115
+ * and, transitively, its groups.
116
+ *
117
+ * A decision id is minted from a sanitized group plus a timestamp and random
118
+ * suffix, and a group is a coordinator-minted gate id. Re-enforcing the shape
119
+ * here means whatever wrote the file, the identifiers this projection reports
120
+ * can carry no separator, space, quote, or control byte.
121
+ */
122
+ const SAFE_GATE_IDENTIFIER = /^[A-Za-z0-9._:-]{1,128}$/u;
123
+
124
+ /** The independence facts sealed on the decision, exactly as the artifact holds them. */
125
+ export type GateDecisionCorrelationFacts = Pick<
126
+ GateRouteCorrelation,
127
+ "agent" | "target" | "modelFamily" | "runtime" | "node" | "independent"
128
+ >;
129
+
130
+ export interface GateTopologyWinner {
131
+ /** 1-based candidate ordinal the judge picked. */
132
+ readonly index: number;
133
+ readonly runId: string;
134
+ }
135
+
136
+ export interface GateTopologyDecision {
137
+ readonly id: string;
138
+ readonly group: string;
139
+ readonly topology: "review" | "compete";
140
+ readonly cycle: number;
141
+ readonly outcome: GateDecisionOutcome;
142
+ readonly decidedAt: string;
143
+ /** Runs this decision graded, in the order the coordinator sealed them. */
144
+ readonly subjects: readonly string[];
145
+ readonly subjectsTruncated: boolean;
146
+ /** The run that produced the verdict, or null when none ran. */
147
+ readonly decider: string | null;
148
+ /** Present exactly when a decider ran; the store requires the pair. */
149
+ readonly correlation: GateDecisionCorrelationFacts | null;
150
+ readonly winner: GateTopologyWinner | null;
151
+ /** A prior judge decision this one confirmed, by id. */
152
+ readonly confirms: string | null;
153
+ readonly reason: GateDecisionReason | null;
154
+ }
155
+
156
+ export interface GateTopologyResult {
157
+ /**
158
+ * Whether this installation has a decision store at all.
159
+ *
160
+ * An installation that has never run a gate and one whose gates all aged out
161
+ * both report no decisions, and they are different operator states, so the
162
+ * fact is read rather than inferred from an empty list.
163
+ */
164
+ readonly present: boolean;
165
+ readonly decisions: readonly GateTopologyDecision[];
166
+ /** True when the scanned window held decisions older than the reported ones. */
167
+ readonly truncated: boolean;
168
+ /**
169
+ * Files in the scanned window whose integrity did not verify.
170
+ *
171
+ * The store's own read API drops these silently, which is right for a
172
+ * consumer that must not act on untrusted evidence and wrong for an operator
173
+ * surface: a gate artifact that no longer authenticates is exactly the thing
174
+ * worth knowing about. Counted, never opened for content.
175
+ */
176
+ readonly unverifiable: number;
177
+ }
178
+
179
+ function classifyReason(detail: unknown): GateDecisionReason | null {
180
+ if (typeof detail !== "string" || detail.length === 0) return null;
181
+ return REASON_RULES.find(([prefix]) => detail.startsWith(prefix))?.[1] ?? "unclassified";
182
+ }
183
+
184
+ function safeIdentifier(value: unknown): string | null {
185
+ return typeof value === "string" && SAFE_GATE_IDENTIFIER.test(value) ? value : null;
186
+ }
187
+
188
+ /** Newest decision first. An unparseable stamp sorts oldest rather than throwing. */
189
+ function byNewest(a: GateDecisionArtifact, b: GateDecisionArtifact): number {
190
+ return (Date.parse(b.createdAt) || 0) - (Date.parse(a.createdAt) || 0);
191
+ }
192
+
193
+ /**
194
+ * Read a bounded window of verified decision artifacts, counting the rest.
195
+ *
196
+ * Reads defensively per file for the same reason the ledger scan does: one
197
+ * artifact written by another build, half-written, or removed between the
198
+ * listing and the open must cost that decision its row, never the whole
199
+ * listing. A file that parses but fails its own integrity check is a different
200
+ * state and is counted rather than dropped.
201
+ */
202
+ function readDecisionWindow(stateDir: string): {
203
+ present: boolean;
204
+ artifacts: GateDecisionArtifact[];
205
+ unverifiable: number;
206
+ } {
207
+ const directory = gateDecisionsDirectory(stateDir);
208
+ if (!existsSync(directory)) return { present: false, artifacts: [], unverifiable: 0 };
209
+ let entries: string[];
210
+ try {
211
+ entries = readdirSync(directory).filter((name) => name.endsWith(".json"));
212
+ } catch {
213
+ // The directory is there and unreadable, which is not the same as absent.
214
+ return { present: true, artifacts: [], unverifiable: 0 };
215
+ }
216
+ const artifacts: GateDecisionArtifact[] = [];
217
+ let unverifiable = 0;
218
+ for (const name of entries.slice(0, GATE_TOPOLOGY_MAX_SCAN)) {
219
+ let artifact: GateDecisionArtifact;
220
+ try {
221
+ artifact = JSON.parse(readFileSync(join(directory, name), "utf8")) as GateDecisionArtifact;
222
+ } catch {
223
+ unverifiable += 1;
224
+ continue;
225
+ }
226
+ if (verifyGateDecisionArtifact(artifact).ok) artifacts.push(artifact);
227
+ else unverifiable += 1;
228
+ }
229
+ artifacts.sort(byNewest);
230
+ return { present: true, artifacts, unverifiable };
231
+ }
232
+
233
+ function projectDecision(artifact: GateDecisionArtifact): GateTopologyDecision | null {
234
+ const id = safeIdentifier(artifact.id);
235
+ const group = safeIdentifier(artifact.group);
236
+ // The store mints both and already refuses either outside this shape, so a
237
+ // stored value failing it means something else wrote the file. A decision
238
+ // this projection cannot name is one it will not report.
239
+ if (id === null || group === null) return null;
240
+ const allSubjects = artifact.subjects.map((subject) => safeIdentifier(subject.runId));
241
+ if (allSubjects.some((runId) => runId === null)) return null;
242
+ const subjects = (allSubjects as string[]).slice(0, GATE_TOPOLOGY_MAX_SUBJECTS);
243
+ const decider = artifact.decider === undefined ? null : safeIdentifier(artifact.decider.runId);
244
+ if (artifact.decider !== undefined && decider === null) return null;
245
+ let winner: GateTopologyWinner | null = null;
246
+ if (artifact.winner !== undefined) {
247
+ const runId = safeIdentifier(artifact.winner.subject.runId);
248
+ if (runId === null) return null;
249
+ winner = { index: artifact.winner.index, runId };
250
+ }
251
+ return {
252
+ id,
253
+ group,
254
+ topology: artifact.topology,
255
+ cycle: artifact.cycle,
256
+ outcome: artifact.outcome,
257
+ decidedAt: new Date(artifact.createdAt).toISOString(),
258
+ subjects,
259
+ subjectsTruncated: allSubjects.length > subjects.length,
260
+ decider,
261
+ correlation: artifact.correlation === undefined ? null : { ...artifact.correlation },
262
+ winner,
263
+ confirms: artifact.confirmation === undefined ? null : safeIdentifier(artifact.confirmation.id),
264
+ reason: classifyReason(artifact.detail),
265
+ };
266
+ }
267
+
268
+ /**
269
+ * Bounded newest-first coordinator gate decisions.
270
+ *
271
+ * Selected by this function, never by a caller's argument, so a host that
272
+ * invokes the command behind it knows the process cannot be steered into a
273
+ * different window.
274
+ */
275
+ export function gateTopology(stateDir: string = clioStateDir()): GateTopologyResult {
276
+ const { present, artifacts, unverifiable } = readDecisionWindow(stateDir);
277
+ const window = artifacts.slice(0, GATE_TOPOLOGY_MAX_DECISIONS);
278
+ const decisions: GateTopologyDecision[] = [];
279
+ for (const artifact of window) {
280
+ const decision = projectDecision(artifact);
281
+ if (decision !== null) decisions.push(decision);
282
+ }
283
+ return {
284
+ present,
285
+ decisions,
286
+ truncated: artifacts.length > window.length || decisions.length !== window.length,
287
+ unverifiable,
288
+ };
289
+ }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * Pure heartbeat watchdog classifier (Phase 6 slice 3).
3
3
  *
4
- * Maps a last-seen heartbeat timestamp to one of three states. The dispatch
5
- * domain drives this on each scheduler tick; the orchestrator uses `stale` to
6
- * surface a warning in the UI and `dead` to reap a subprocess whose event
7
- * stream has gone quiet for too long.
4
+ * Maps a last-seen monotonic stamp to one of three states. The dispatch domain
5
+ * drives this on each scheduler tick; the orchestrator uses `stale` to surface
6
+ * a warning in the UI and `dead` to reap a subprocess whose event stream has
7
+ * gone quiet for too long.
8
8
  *
9
- * No I/O, no clocks of its own. Callers pass `now` to keep the function pure
10
- * and easy to test.
9
+ * No I/O, no clocks of its own. Callers pass both monotonic values to keep the
10
+ * function pure and easy to test.
11
11
  */
12
12
 
13
13
  export interface HeartbeatSpec {
@@ -17,10 +17,34 @@ export interface HeartbeatSpec {
17
17
 
18
18
  export type HeartbeatStatus = "alive" | "stale" | "dead";
19
19
 
20
+ /**
21
+ * Mutable heartbeat facts shared by a transport and the dispatch watchdog.
22
+ * `current` is an absolute wall-clock instant for display and persistence.
23
+ * `monotonic` is from this process's monotonic clock and is the only value
24
+ * liveness code may compare with a later clock read.
25
+ *
26
+ * The monotonic field is optional only for injected legacy test handles. Real
27
+ * transports always provide it. Treating an old handle's `current` as its
28
+ * monotonic stamp preserves the structural test seam without reintroducing a
29
+ * subtraction between two wall-clock reads in the product path.
30
+ */
31
+ export interface HeartbeatStamp {
32
+ current: number;
33
+ monotonic?: number;
34
+ }
35
+
20
36
  export const DEFAULT_HEARTBEAT_SPEC: HeartbeatSpec = { windowMs: 5000, graceMs: 10000 };
21
37
 
22
- export function classifyHeartbeat(heartbeatAt: number, now: number, spec: HeartbeatSpec): HeartbeatStatus {
23
- const age = now - heartbeatAt;
38
+ export function heartbeatMonotonicAt(heartbeat: HeartbeatStamp): number {
39
+ return heartbeat.monotonic ?? heartbeat.current;
40
+ }
41
+
42
+ export function classifyHeartbeat(
43
+ heartbeatMonotonicAt: number,
44
+ monotonicNow: number,
45
+ spec: HeartbeatSpec,
46
+ ): HeartbeatStatus {
47
+ const age = monotonicNow - heartbeatMonotonicAt;
24
48
  if (age <= spec.windowMs) return "alive";
25
49
  if (age <= spec.windowMs + spec.graceMs) return "stale";
26
50
  return "dead";