harnery 0.33.0 → 0.34.1

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 (657) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/adapter.js +2 -2
  4. package/dist/commands/agents.d.ts +144 -1
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +1237 -590
  7. package/dist/commands/artifacts.d.ts.map +1 -1
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/backup.d.ts +1 -1
  10. package/dist/commands/backup.d.ts.map +1 -1
  11. package/dist/commands/backup.js +29 -16
  12. package/dist/commands/browse.d.ts.map +1 -1
  13. package/dist/commands/browse.js +234 -12
  14. package/dist/commands/checkpoint.d.ts.map +1 -1
  15. package/dist/commands/checkpoint.js +3 -2
  16. package/dist/commands/claude-desktop.d.ts +1 -1
  17. package/dist/commands/claude-desktop.d.ts.map +1 -1
  18. package/dist/commands/claude-desktop.js +3 -74
  19. package/dist/commands/context.js +1 -6
  20. package/dist/commands/decision.d.ts.map +1 -1
  21. package/dist/commands/decision.js +22 -38
  22. package/dist/commands/docs.d.ts.map +1 -1
  23. package/dist/commands/docs.js +91 -28
  24. package/dist/commands/doctor.js +10 -1
  25. package/dist/commands/events.d.ts +5 -0
  26. package/dist/commands/events.d.ts.map +1 -0
  27. package/dist/commands/events.js +189 -0
  28. package/dist/commands/governor.d.ts.map +1 -1
  29. package/dist/commands/governor.js +13 -4
  30. package/dist/commands/init.d.ts +9 -2
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +223 -51
  33. package/dist/commands/journal.d.ts.map +1 -1
  34. package/dist/commands/journal.js +4 -13
  35. package/dist/commands/ledger-v3.d.ts +5 -0
  36. package/dist/commands/ledger-v3.d.ts.map +1 -0
  37. package/dist/commands/ledger-v3.js +91 -0
  38. package/dist/commands/presence.d.ts.map +1 -1
  39. package/dist/commands/presence.js +11 -5
  40. package/dist/commands/sync.d.ts +1 -1
  41. package/dist/commands/sync.js +1 -1
  42. package/dist/commands/web.d.ts +6 -0
  43. package/dist/commands/web.d.ts.map +1 -1
  44. package/dist/commands/web.js +67 -7
  45. package/dist/commands/work.d.ts.map +1 -1
  46. package/dist/commands/work.js +22 -5
  47. package/dist/core/adapter.d.ts +5 -0
  48. package/dist/core/adapter.d.ts.map +1 -0
  49. package/dist/core/adapter.js +1 -0
  50. package/dist/core/adapters/attest.d.ts +5 -2
  51. package/dist/core/adapters/attest.d.ts.map +1 -1
  52. package/dist/core/adapters/attest.js +8 -3
  53. package/dist/core/adapters/profiles.d.ts +6 -6
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +4 -4
  56. package/dist/core/agents/canonical-emit.d.ts +17 -15
  57. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  58. package/dist/core/agents/canonical-emit.js +22 -22
  59. package/dist/core/agents/cli.js +207 -234
  60. package/dist/core/agents/codex-archive-v3.d.ts +14 -0
  61. package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
  62. package/dist/core/agents/codex-archive-v3.js +110 -0
  63. package/dist/core/agents/coord-client.d.ts +19 -81
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +65 -155
  66. package/dist/core/agents/finalization.d.ts +4 -1
  67. package/dist/core/agents/finalization.d.ts.map +1 -1
  68. package/dist/core/agents/finalization.js +89 -32
  69. package/dist/core/agents/index.d.ts +4 -5
  70. package/dist/core/agents/index.d.ts.map +1 -1
  71. package/dist/core/agents/index.js +4 -5
  72. package/dist/core/agents/live-authority-v3.d.ts +59 -0
  73. package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
  74. package/dist/core/agents/live-authority-v3.js +233 -0
  75. package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
  76. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
  77. package/dist/core/agents/live-lifecycle-v3.js +91 -0
  78. package/dist/core/agents/live-observation-v3.d.ts +25 -0
  79. package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
  80. package/dist/core/agents/live-observation-v3.js +157 -0
  81. package/dist/core/agents/render/prompt-context.d.ts +4 -4
  82. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  83. package/dist/core/agents/render/prompt-context.js +23 -58
  84. package/dist/core/agents/render/session-context.d.ts +1 -1
  85. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  86. package/dist/core/agents/render/session-context.js +8 -32
  87. package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
  88. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  89. package/dist/core/agents/rules/claim-conflict.js +79 -127
  90. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  91. package/dist/core/agents/rules/commit-conflict.js +3 -18
  92. package/dist/core/agents/rules/stop-hook.d.ts +9 -50
  93. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  94. package/dist/core/agents/rules/stop-hook.js +193 -236
  95. package/dist/core/agents/session-events.d.ts +5 -7
  96. package/dist/core/agents/session-events.d.ts.map +1 -1
  97. package/dist/core/agents/session-events.js +127 -95
  98. package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
  99. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
  100. package/dist/core/agents/session-finalization-state-v3.js +40 -0
  101. package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
  102. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
  103. package/dist/core/agents/session-finalizer-v3.js +734 -0
  104. package/dist/core/agents/session-name-display.d.ts +40 -0
  105. package/dist/core/agents/session-name-display.d.ts.map +1 -0
  106. package/dist/core/agents/session-name-display.js +119 -0
  107. package/dist/core/agents/state/activity-log.d.ts +3 -3
  108. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  109. package/dist/core/agents/state/activity-log.js +7 -16
  110. package/dist/core/agents/state/heartbeat-writer.d.ts +23 -55
  111. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  112. package/dist/core/agents/state/heartbeat-writer.js +56 -228
  113. package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
  114. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
  115. package/dist/core/agents/state/live-coordination-view.js +209 -0
  116. package/dist/core/agents/state/session-state.d.ts +4 -3
  117. package/dist/core/agents/state/session-state.d.ts.map +1 -1
  118. package/dist/core/agents/state/session-state.js +18 -9
  119. package/dist/core/agents/state/stale-sweep.d.ts +1 -1
  120. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  121. package/dist/core/agents/state/stale-sweep.js +26 -15
  122. package/dist/core/artifacts/index.d.ts.map +1 -1
  123. package/dist/core/artifacts/index.js +5 -7
  124. package/dist/core/config.d.ts +33 -2
  125. package/dist/core/config.d.ts.map +1 -1
  126. package/dist/core/config.js +98 -2
  127. package/dist/core/context/index.d.ts +1 -4
  128. package/dist/core/context/index.d.ts.map +1 -1
  129. package/dist/core/context/index.js +9 -9
  130. package/dist/core/events/span-parent.d.ts +15 -0
  131. package/dist/core/events/span-parent.d.ts.map +1 -0
  132. package/dist/core/events/span-parent.js +34 -0
  133. package/dist/core/events/v3/advance.d.ts +14 -0
  134. package/dist/core/events/v3/advance.d.ts.map +1 -0
  135. package/dist/core/events/v3/advance.js +134 -0
  136. package/dist/core/events/v3/authority-outbox.d.ts +78 -0
  137. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
  138. package/dist/core/events/v3/authority-outbox.js +399 -0
  139. package/dist/core/events/v3/base-builder.d.ts +20 -0
  140. package/dist/core/events/v3/base-builder.d.ts.map +1 -0
  141. package/dist/core/events/v3/base-builder.js +29 -0
  142. package/dist/core/events/v3/base-contract.d.ts +7576 -0
  143. package/dist/core/events/v3/base-contract.d.ts.map +1 -0
  144. package/dist/core/events/v3/base-contract.js +647 -0
  145. package/dist/core/events/v3/bootstrap.d.ts +34 -0
  146. package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
  147. package/dist/core/events/v3/bootstrap.js +175 -0
  148. package/dist/core/events/v3/builder.d.ts +20 -0
  149. package/dist/core/events/v3/builder.d.ts.map +1 -0
  150. package/dist/core/events/v3/builder.js +28 -0
  151. package/dist/core/events/v3/canonical.d.ts +31 -0
  152. package/dist/core/events/v3/canonical.d.ts.map +1 -0
  153. package/dist/core/events/v3/canonical.js +100 -0
  154. package/dist/core/events/v3/capabilities.d.ts +31 -0
  155. package/dist/core/events/v3/capabilities.d.ts.map +1 -0
  156. package/dist/core/events/v3/capabilities.js +107 -0
  157. package/dist/core/events/v3/capability-drift.d.ts +22 -0
  158. package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
  159. package/dist/core/events/v3/capability-drift.js +134 -0
  160. package/dist/core/events/v3/catalog.d.ts +38 -0
  161. package/dist/core/events/v3/catalog.d.ts.map +1 -0
  162. package/dist/core/events/v3/catalog.js +135 -0
  163. package/dist/core/events/v3/clock-order.d.ts +34 -0
  164. package/dist/core/events/v3/clock-order.d.ts.map +1 -0
  165. package/dist/core/events/v3/clock-order.js +35 -0
  166. package/dist/core/events/v3/contract.d.ts +171 -0
  167. package/dist/core/events/v3/contract.d.ts.map +1 -0
  168. package/dist/core/events/v3/contract.js +180 -0
  169. package/dist/core/events/v3/control.d.ts +111 -0
  170. package/dist/core/events/v3/control.d.ts.map +1 -0
  171. package/dist/core/events/v3/control.js +419 -0
  172. package/dist/core/events/v3/coordination-view.d.ts +69 -0
  173. package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
  174. package/dist/core/events/v3/coordination-view.js +119 -0
  175. package/dist/core/events/v3/economics.d.ts +77 -0
  176. package/dist/core/events/v3/economics.d.ts.map +1 -0
  177. package/dist/core/events/v3/economics.js +274 -0
  178. package/dist/core/events/v3/finalization-view.d.ts +26 -0
  179. package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
  180. package/dist/core/events/v3/finalization-view.js +37 -0
  181. package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
  182. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
  183. package/dist/core/events/v3/fingerprint-keys.js +217 -0
  184. package/dist/core/events/v3/generated.d.ts +4 -0
  185. package/dist/core/events/v3/generated.d.ts.map +1 -0
  186. package/dist/core/events/v3/generated.js +47 -0
  187. package/dist/core/events/v3/ids.d.ts +9 -0
  188. package/dist/core/events/v3/ids.d.ts.map +1 -0
  189. package/dist/core/events/v3/ids.js +25 -0
  190. package/dist/core/events/v3/index.d.ts +35 -0
  191. package/dist/core/events/v3/index.d.ts.map +1 -0
  192. package/dist/core/events/v3/index.js +33 -0
  193. package/dist/core/events/v3/latency.d.ts +63 -0
  194. package/dist/core/events/v3/latency.d.ts.map +1 -0
  195. package/dist/core/events/v3/latency.js +412 -0
  196. package/dist/core/events/v3/live-feed.d.ts +45 -0
  197. package/dist/core/events/v3/live-feed.d.ts.map +1 -0
  198. package/dist/core/events/v3/live-feed.js +246 -0
  199. package/dist/core/events/v3/live-observation.d.ts +35 -0
  200. package/dist/core/events/v3/live-observation.d.ts.map +1 -0
  201. package/dist/core/events/v3/live-observation.js +1 -0
  202. package/dist/core/events/v3/live-routing.d.ts +57 -0
  203. package/dist/core/events/v3/live-routing.d.ts.map +1 -0
  204. package/dist/core/events/v3/live-routing.js +107 -0
  205. package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
  206. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
  207. package/dist/core/events/v3/producers/command-recorder.js +477 -0
  208. package/dist/core/events/v3/producers/command.d.ts +50 -0
  209. package/dist/core/events/v3/producers/command.d.ts.map +1 -0
  210. package/dist/core/events/v3/producers/command.js +135 -0
  211. package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
  212. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
  213. package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
  214. package/dist/core/events/v3/producers/coordination.d.ts +90 -0
  215. package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
  216. package/dist/core/events/v3/producers/coordination.js +229 -0
  217. package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
  218. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
  219. package/dist/core/events/v3/producers/hook-base.js +424 -0
  220. package/dist/core/events/v3/producers/hook.d.ts +29 -0
  221. package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
  222. package/dist/core/events/v3/producers/hook.js +71 -0
  223. package/dist/core/events/v3/producers/intake.d.ts +65 -0
  224. package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/intake.js +244 -0
  226. package/dist/core/events/v3/producers/recorder.d.ts +240 -0
  227. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
  228. package/dist/core/events/v3/producers/recorder.js +2176 -0
  229. package/dist/core/events/v3/projection.d.ts +101 -0
  230. package/dist/core/events/v3/projection.d.ts.map +1 -0
  231. package/dist/core/events/v3/projection.js +479 -0
  232. package/dist/core/events/v3/reader.d.ts +75 -0
  233. package/dist/core/events/v3/reader.d.ts.map +1 -0
  234. package/dist/core/events/v3/reader.js +778 -0
  235. package/dist/core/events/v3/recovery-record.d.ts +47 -0
  236. package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
  237. package/dist/core/events/v3/recovery-record.js +167 -0
  238. package/dist/core/events/v3/recovery.d.ts +22 -0
  239. package/dist/core/events/v3/recovery.d.ts.map +1 -0
  240. package/dist/core/events/v3/recovery.js +227 -0
  241. package/dist/core/events/v3/span-state.d.ts +52 -0
  242. package/dist/core/events/v3/span-state.d.ts.map +1 -0
  243. package/dist/core/events/v3/span-state.js +142 -0
  244. package/dist/core/events/v3/targets.d.ts +23 -0
  245. package/dist/core/events/v3/targets.d.ts.map +1 -0
  246. package/dist/core/events/v3/targets.js +134 -0
  247. package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
  248. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
  249. package/dist/core/events/v3/turn-telemetry.js +168 -0
  250. package/dist/core/events/v3/validate.d.ts +9 -0
  251. package/dist/core/events/v3/validate.d.ts.map +1 -0
  252. package/dist/core/events/v3/validate.js +281 -0
  253. package/dist/core/events/v3/writer.d.ts +56 -0
  254. package/dist/core/events/v3/writer.d.ts.map +1 -0
  255. package/dist/core/events/v3/writer.js +335 -0
  256. package/dist/core/governor/index.d.ts +1 -1
  257. package/dist/core/governor/index.d.ts.map +1 -1
  258. package/dist/core/governor/index.js +1 -1
  259. package/dist/core/governor/plan-api.d.ts +2 -1
  260. package/dist/core/governor/plan-api.d.ts.map +1 -1
  261. package/dist/core/governor/plan-api.js +6 -3
  262. package/dist/core/governor/read.d.ts +1 -1
  263. package/dist/core/governor/read.d.ts.map +1 -1
  264. package/dist/core/governor/read.js +1 -1
  265. package/dist/core/governor/state.d.ts +9 -0
  266. package/dist/core/governor/state.d.ts.map +1 -1
  267. package/dist/core/governor/state.js +40 -3
  268. package/dist/core/guard/config.d.ts +5 -0
  269. package/dist/core/guard/config.d.ts.map +1 -0
  270. package/dist/core/guard/config.js +157 -0
  271. package/dist/core/guard/coordinator.d.ts +14 -0
  272. package/dist/core/guard/coordinator.d.ts.map +1 -0
  273. package/dist/core/guard/coordinator.js +173 -0
  274. package/dist/core/guard/evaluator.d.ts +4 -0
  275. package/dist/core/guard/evaluator.d.ts.map +1 -0
  276. package/dist/core/guard/evaluator.js +242 -0
  277. package/dist/core/guard/evidence-v3.d.ts +15 -0
  278. package/dist/core/guard/evidence-v3.d.ts.map +1 -0
  279. package/dist/core/guard/evidence-v3.js +171 -0
  280. package/dist/core/guard/index.d.ts +10 -0
  281. package/dist/core/guard/index.d.ts.map +1 -0
  282. package/dist/core/guard/index.js +6 -0
  283. package/dist/core/guard/live-source-v3.d.ts +40 -0
  284. package/dist/core/guard/live-source-v3.d.ts.map +1 -0
  285. package/dist/core/guard/live-source-v3.js +128 -0
  286. package/dist/core/guard/storage.d.ts +27 -0
  287. package/dist/core/guard/storage.d.ts.map +1 -0
  288. package/dist/core/guard/storage.js +140 -0
  289. package/dist/core/guard/types.d.ts +119 -0
  290. package/dist/core/guard/types.d.ts.map +1 -0
  291. package/dist/core/guard/types.js +1 -0
  292. package/dist/core/hooks/adapter/detect.d.ts +18 -1
  293. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  294. package/dist/core/hooks/adapter/detect.js +29 -0
  295. package/dist/core/hooks/adapter/events.d.ts +7 -4
  296. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  297. package/dist/core/hooks/adapter/events.js +12 -4
  298. package/dist/core/hooks/adapter/output.d.ts +2 -2
  299. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  300. package/dist/core/hooks/adapter/parse.d.ts +7 -4
  301. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  302. package/dist/core/hooks/adapter/parse.js +63 -25
  303. package/dist/core/hooks/adapter/wiring.d.ts +30 -4
  304. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  305. package/dist/core/hooks/adapter/wiring.js +89 -11
  306. package/dist/core/hooks/cli.d.ts +1 -1
  307. package/dist/core/hooks/cli.js +306 -444
  308. package/dist/core/hooks/effects/image-capture.d.ts +20 -34
  309. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  310. package/dist/core/hooks/effects/image-capture.js +212 -221
  311. package/dist/core/hooks/effects/index.d.ts +1 -9
  312. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  313. package/dist/core/hooks/effects/index.js +1 -24
  314. package/dist/core/hooks/events/input-hash.d.ts +7 -0
  315. package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
  316. package/dist/core/hooks/events/input-hash.js +43 -0
  317. package/dist/core/hooks/index.d.ts +4 -3
  318. package/dist/core/hooks/index.d.ts.map +1 -1
  319. package/dist/core/hooks/index.js +2 -3
  320. package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
  321. package/dist/core/hooks/resolve/coord-root.js +1 -1
  322. package/dist/core/hooks/resolve/owner.d.ts +16 -15
  323. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  324. package/dist/core/hooks/resolve/owner.js +51 -42
  325. package/dist/core/hooks/resolve/transcript.d.ts +33 -3
  326. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  327. package/dist/core/hooks/resolve/transcript.js +161 -14
  328. package/dist/core/hooks/session-name-presence.d.ts +9 -10
  329. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  330. package/dist/core/hooks/session-name-presence.js +15 -15
  331. package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
  332. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
  333. package/dist/core/journal/index.d.ts +2 -2
  334. package/dist/core/journal/index.d.ts.map +1 -1
  335. package/dist/core/journal/index.js +9 -22
  336. package/dist/core/presence/blob.d.ts +4 -3
  337. package/dist/core/presence/blob.d.ts.map +1 -1
  338. package/dist/core/presence/blob.js +45 -44
  339. package/dist/core/presence/codec-digest.d.ts +29 -0
  340. package/dist/core/presence/codec-digest.d.ts.map +1 -0
  341. package/dist/core/presence/codec-digest.js +170 -0
  342. package/dist/core/presence/relay-client.d.ts +1 -1
  343. package/dist/core/presence/relay-client.d.ts.map +1 -1
  344. package/dist/core/presence/relay-client.js +5 -5
  345. package/dist/core/work/index.d.ts +1 -1
  346. package/dist/core/work/index.d.ts.map +1 -1
  347. package/dist/core/work/index.js +1 -1
  348. package/dist/core/work/read.d.ts +1 -1
  349. package/dist/core/work/read.d.ts.map +1 -1
  350. package/dist/core/work/read.js +1 -1
  351. package/dist/core/work/runner.d.ts.map +1 -1
  352. package/dist/core/work/runner.js +6 -0
  353. package/dist/core/work/state.d.ts +9 -0
  354. package/dist/core/work/state.d.ts.map +1 -1
  355. package/dist/core/work/state.js +17 -3
  356. package/dist/core/workflow/engine.js +24 -25
  357. package/dist/core/workflow/live-session-v3.d.ts +19 -0
  358. package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
  359. package/dist/core/workflow/live-session-v3.js +86 -0
  360. package/dist/core/workflow/scope-id.d.ts +3 -0
  361. package/dist/core/workflow/scope-id.d.ts.map +1 -0
  362. package/dist/core/workflow/scope-id.js +5 -0
  363. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  364. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  365. package/dist/core/workflow/workspaces/leases.js +53 -19
  366. package/dist/index.d.ts +1 -1
  367. package/dist/index.js +1 -1
  368. package/dist/lib/browser/client.d.ts +41 -0
  369. package/dist/lib/browser/client.d.ts.map +1 -1
  370. package/dist/lib/browser/client.js +203 -0
  371. package/dist/lib/browser/critique.d.ts +28 -2
  372. package/dist/lib/browser/critique.d.ts.map +1 -1
  373. package/dist/lib/browser/critique.js +74 -18
  374. package/dist/lib/browser/geometry.d.ts +1 -1
  375. package/dist/lib/browser/geometry.d.ts.map +1 -1
  376. package/dist/lib/browser/geometry.js +130 -6
  377. package/dist/lib/browser/index.d.ts +3 -0
  378. package/dist/lib/browser/index.d.ts.map +1 -1
  379. package/dist/lib/browser/index.js +3 -0
  380. package/dist/lib/browser/qa-plan.d.ts +174 -0
  381. package/dist/lib/browser/qa-plan.d.ts.map +1 -0
  382. package/dist/lib/browser/qa-plan.js +321 -0
  383. package/dist/lib/browser/qa-reuse.d.ts +99 -0
  384. package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
  385. package/dist/lib/browser/qa-reuse.js +222 -0
  386. package/dist/lib/browser/qa-snapshot.d.ts +64 -0
  387. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
  388. package/dist/lib/browser/qa-snapshot.js +171 -0
  389. package/dist/lib/browser/runts.d.ts.map +1 -1
  390. package/dist/lib/browser/runts.js +22 -1
  391. package/dist/lib/browser/tiling.d.ts +71 -0
  392. package/dist/lib/browser/tiling.d.ts.map +1 -0
  393. package/dist/lib/browser/tiling.js +118 -0
  394. package/dist/lib/claude-desktop.d.ts +0 -47
  395. package/dist/lib/claude-desktop.d.ts.map +1 -1
  396. package/dist/lib/claude-desktop.js +1 -169
  397. package/dist/lib/context/index.d.ts +0 -3
  398. package/dist/lib/context/index.d.ts.map +1 -1
  399. package/dist/lib/context/index.js +20 -42
  400. package/dist/lib/council/index.d.ts +2 -2
  401. package/dist/lib/council/index.d.ts.map +1 -1
  402. package/dist/lib/council/index.js +9 -20
  403. package/dist/lib/docs-frontmatter.d.ts +1 -6
  404. package/dist/lib/docs-frontmatter.d.ts.map +1 -1
  405. package/dist/lib/docs-frontmatter.js +12 -78
  406. package/dist/lib/docs-index.d.ts +9 -0
  407. package/dist/lib/docs-index.d.ts.map +1 -1
  408. package/dist/lib/docs-index.js +32 -3
  409. package/dist/lib/docs-lint.d.ts.map +1 -1
  410. package/dist/lib/docs-lint.js +14 -30
  411. package/dist/lib/docs-metadata-audit.d.ts +19 -0
  412. package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
  413. package/dist/lib/docs-metadata-audit.js +132 -0
  414. package/dist/lib/docs-metadata-managed.d.ts +23 -0
  415. package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
  416. package/dist/lib/docs-metadata-managed.js +78 -0
  417. package/dist/lib/docs-metadata-sync.d.ts +20 -0
  418. package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
  419. package/dist/lib/docs-metadata-sync.js +222 -0
  420. package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
  421. package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
  422. package/dist/lib/docs-metadata-v2-schema.js +134 -0
  423. package/dist/lib/docs-metadata-v2.d.ts +21 -0
  424. package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
  425. package/dist/lib/docs-metadata-v2.js +424 -0
  426. package/dist/lib/docs-new.d.ts +11 -0
  427. package/dist/lib/docs-new.d.ts.map +1 -0
  428. package/dist/lib/docs-new.js +52 -0
  429. package/dist/lib/docs-sweep.d.ts.map +1 -1
  430. package/dist/lib/docs-sweep.js +45 -29
  431. package/dist/lib/headless/index.d.ts +98 -0
  432. package/dist/lib/headless/index.d.ts.map +1 -0
  433. package/dist/lib/headless/index.js +211 -0
  434. package/dist/lib/identities/assume.d.ts +4 -4
  435. package/dist/lib/identities/assume.d.ts.map +1 -1
  436. package/dist/lib/identities/assume.js +52 -68
  437. package/dist/lib/instructions/apply.d.ts +5 -4
  438. package/dist/lib/instructions/apply.d.ts.map +1 -1
  439. package/dist/lib/instructions/apply.js +32 -20
  440. package/dist/lib/instructions/bundle.d.ts +1 -1
  441. package/dist/lib/instructions/bundle.d.ts.map +1 -1
  442. package/dist/lib/instructions/bundle.js +1 -1
  443. package/dist/lib/instructions/splice.d.ts +0 -1
  444. package/dist/lib/instructions/splice.d.ts.map +1 -1
  445. package/dist/lib/instructions/splice.js +22 -6
  446. package/dist/lib/instructions/templates.d.ts +7 -5
  447. package/dist/lib/instructions/templates.d.ts.map +1 -1
  448. package/dist/lib/instructions/templates.js +79 -9
  449. package/package.json +26 -5
  450. package/schemas/config.schema.json +120 -2
  451. package/schemas/docs-metadata-v2.schema.json +441 -0
  452. package/schemas/event-v3.schema.json +24472 -0
  453. package/src/commander.ts +4 -0
  454. package/src/commands/adapter.ts +2 -2
  455. package/src/commands/agents.ts +1530 -593
  456. package/src/commands/artifacts.ts +7 -6
  457. package/src/commands/backup.ts +29 -21
  458. package/src/commands/browse.ts +349 -8
  459. package/src/commands/checkpoint.ts +4 -3
  460. package/src/commands/claude-desktop.ts +2 -101
  461. package/src/commands/context.ts +1 -6
  462. package/src/commands/decision.ts +27 -44
  463. package/src/commands/docs.ts +100 -33
  464. package/src/commands/doctor.ts +14 -1
  465. package/src/commands/events.ts +256 -0
  466. package/src/commands/governor.ts +18 -4
  467. package/src/commands/init.ts +253 -57
  468. package/src/commands/instructions.ts +1 -1
  469. package/src/commands/journal.ts +4 -13
  470. package/src/commands/ledger-v3.ts +111 -0
  471. package/src/commands/presence.ts +11 -10
  472. package/src/commands/sync.ts +1 -1
  473. package/src/commands/web.ts +67 -8
  474. package/src/commands/work.ts +28 -5
  475. package/src/core/adapter.ts +5 -0
  476. package/src/core/adapters/attest.ts +8 -3
  477. package/src/core/adapters/profiles.ts +4 -6
  478. package/src/core/agents/canonical-emit.ts +34 -25
  479. package/src/core/agents/cli.ts +236 -262
  480. package/src/core/agents/codex-archive-v3.ts +138 -0
  481. package/src/core/agents/coord-client.ts +75 -188
  482. package/src/core/agents/finalization.ts +120 -38
  483. package/src/core/agents/index.ts +4 -5
  484. package/src/core/agents/live-authority-v3.ts +375 -0
  485. package/src/core/agents/live-lifecycle-v3.ts +140 -0
  486. package/src/core/agents/live-observation-v3.ts +216 -0
  487. package/src/core/agents/render/prompt-context.ts +29 -60
  488. package/src/core/agents/render/session-context.ts +10 -31
  489. package/src/core/agents/rules/claim-conflict.ts +85 -126
  490. package/src/core/agents/rules/commit-conflict.ts +3 -14
  491. package/src/core/agents/rules/stop-hook.ts +239 -293
  492. package/src/core/agents/session-events.ts +137 -104
  493. package/src/core/agents/session-finalization-state-v3.ts +90 -0
  494. package/src/core/agents/session-finalizer-v3.ts +953 -0
  495. package/src/core/agents/session-name-display.ts +149 -0
  496. package/src/core/agents/state/activity-log.ts +6 -14
  497. package/src/core/agents/state/heartbeat-writer.ts +82 -277
  498. package/src/core/agents/state/live-coordination-view.ts +290 -0
  499. package/src/core/agents/state/session-state.ts +24 -12
  500. package/src/core/agents/state/stale-sweep.ts +40 -17
  501. package/src/core/artifacts/index.ts +4 -5
  502. package/src/core/config.ts +148 -3
  503. package/src/core/context/index.ts +9 -17
  504. package/src/core/events/span-parent.ts +46 -0
  505. package/src/core/events/v3/advance.ts +168 -0
  506. package/src/core/events/v3/authority-outbox.ts +548 -0
  507. package/src/core/events/v3/base-builder.ts +60 -0
  508. package/src/core/events/v3/base-contract.ts +882 -0
  509. package/src/core/events/v3/bootstrap.ts +226 -0
  510. package/src/core/events/v3/builder.ts +59 -0
  511. package/src/core/events/v3/canonical.ts +153 -0
  512. package/src/core/events/v3/capabilities.ts +171 -0
  513. package/src/core/events/v3/capability-drift.ts +177 -0
  514. package/src/core/events/v3/catalog.ts +202 -0
  515. package/src/core/events/v3/clock-order.ts +41 -0
  516. package/src/core/events/v3/contract.ts +365 -0
  517. package/src/core/events/v3/control.ts +576 -0
  518. package/src/core/events/v3/coordination-view.ts +193 -0
  519. package/src/core/events/v3/economics.ts +384 -0
  520. package/src/core/events/v3/finalization-view.ts +67 -0
  521. package/src/core/events/v3/fingerprint-keys.ts +260 -0
  522. package/src/core/events/v3/generated.ts +48 -0
  523. package/src/core/events/v3/ids.ts +33 -0
  524. package/src/core/events/v3/index.ts +322 -0
  525. package/src/core/events/v3/latency.ts +551 -0
  526. package/src/core/events/v3/live-feed.ts +319 -0
  527. package/src/core/events/v3/live-observation.ts +39 -0
  528. package/src/core/events/v3/live-routing.ts +135 -0
  529. package/src/core/events/v3/producers/command-recorder.ts +631 -0
  530. package/src/core/events/v3/producers/command.ts +201 -0
  531. package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
  532. package/src/core/events/v3/producers/coordination.ts +355 -0
  533. package/src/core/events/v3/producers/hook-base.ts +578 -0
  534. package/src/core/events/v3/producers/hook.ts +119 -0
  535. package/src/core/events/v3/producers/intake.ts +319 -0
  536. package/src/core/events/v3/producers/recorder.ts +2805 -0
  537. package/src/core/events/v3/projection.ts +653 -0
  538. package/src/core/events/v3/reader.ts +1127 -0
  539. package/src/core/events/v3/recovery-record.ts +244 -0
  540. package/src/core/events/v3/recovery.ts +271 -0
  541. package/src/core/events/v3/span-state.ts +187 -0
  542. package/src/core/events/v3/targets.ts +182 -0
  543. package/src/core/events/v3/turn-telemetry.ts +259 -0
  544. package/src/core/events/v3/validate.ts +361 -0
  545. package/src/core/events/v3/writer.ts +419 -0
  546. package/src/core/governor/index.ts +3 -0
  547. package/src/core/governor/plan-api.ts +10 -2
  548. package/src/core/governor/read.ts +3 -0
  549. package/src/core/governor/state.ts +52 -5
  550. package/src/core/guard/config.ts +206 -0
  551. package/src/core/guard/coordinator.ts +227 -0
  552. package/src/core/guard/evaluator.ts +343 -0
  553. package/src/core/guard/evidence-v3.ts +227 -0
  554. package/src/core/guard/index.ts +35 -0
  555. package/src/core/guard/live-source-v3.ts +204 -0
  556. package/src/core/guard/storage.ts +188 -0
  557. package/src/core/guard/types.ts +152 -0
  558. package/src/core/hooks/adapter/detect.ts +28 -1
  559. package/src/core/hooks/adapter/events.ts +14 -6
  560. package/src/core/hooks/adapter/output.ts +7 -2
  561. package/src/core/hooks/adapter/parse.ts +80 -38
  562. package/src/core/hooks/adapter/wiring.ts +119 -13
  563. package/src/core/hooks/cli.ts +352 -466
  564. package/src/core/hooks/effects/image-capture.ts +228 -238
  565. package/src/core/hooks/effects/index.ts +1 -36
  566. package/src/core/hooks/events/input-hash.ts +47 -0
  567. package/src/core/hooks/index.ts +4 -11
  568. package/src/core/hooks/resolve/coord-root.ts +1 -1
  569. package/src/core/hooks/resolve/owner.ts +52 -45
  570. package/src/core/hooks/resolve/transcript.ts +171 -14
  571. package/src/core/hooks/session-name-presence.ts +14 -15
  572. package/src/core/hooks/unsafe-cross-shell.ts +1 -1
  573. package/src/core/journal/index.ts +15 -22
  574. package/src/core/presence/blob.ts +48 -41
  575. package/src/core/presence/codec-digest.ts +215 -0
  576. package/src/core/presence/relay-client.ts +5 -5
  577. package/src/core/work/index.ts +3 -0
  578. package/src/core/work/read.ts +3 -0
  579. package/src/core/work/runner.ts +6 -0
  580. package/src/core/work/state.ts +27 -3
  581. package/src/core/workflow/engine.ts +24 -24
  582. package/src/core/workflow/live-session-v3.ts +126 -0
  583. package/src/core/workflow/scope-id.ts +6 -0
  584. package/src/core/workflow/workspaces/leases.ts +56 -19
  585. package/src/index.ts +1 -1
  586. package/src/lib/browser/client.ts +230 -0
  587. package/src/lib/browser/critique.ts +104 -27
  588. package/src/lib/browser/geometry.ts +143 -14
  589. package/src/lib/browser/index.ts +34 -0
  590. package/src/lib/browser/qa-plan.ts +501 -0
  591. package/src/lib/browser/qa-reuse.ts +306 -0
  592. package/src/lib/browser/qa-snapshot.ts +223 -0
  593. package/src/lib/browser/runts.ts +27 -2
  594. package/src/lib/browser/tiling.ts +164 -0
  595. package/src/lib/claude-desktop.ts +1 -232
  596. package/src/lib/context/index.ts +22 -46
  597. package/src/lib/council/index.ts +9 -21
  598. package/src/lib/docs-frontmatter.ts +8 -79
  599. package/src/lib/docs-index.ts +33 -3
  600. package/src/lib/docs-lint.ts +15 -29
  601. package/src/lib/docs-metadata-audit.ts +166 -0
  602. package/src/lib/docs-metadata-managed.ts +83 -0
  603. package/src/lib/docs-metadata-sync.ts +262 -0
  604. package/src/lib/docs-metadata-v2-schema.ts +138 -0
  605. package/src/lib/docs-metadata-v2.ts +646 -0
  606. package/src/lib/docs-new.ts +63 -0
  607. package/src/lib/docs-sweep.ts +42 -28
  608. package/src/lib/headless/index.ts +287 -0
  609. package/src/lib/identities/assume.ts +67 -86
  610. package/src/lib/instructions/apply.ts +33 -20
  611. package/src/lib/instructions/bundle.ts +2 -2
  612. package/src/lib/instructions/splice.ts +21 -7
  613. package/src/lib/instructions/templates.ts +85 -13
  614. package/dist/core/agents/cli-emit.d.ts +0 -27
  615. package/dist/core/agents/cli-emit.d.ts.map +0 -1
  616. package/dist/core/agents/cli-emit.js +0 -57
  617. package/dist/core/agents/codex-replay.d.ts +0 -29
  618. package/dist/core/agents/codex-replay.d.ts.map +0 -1
  619. package/dist/core/agents/codex-replay.js +0 -138
  620. package/dist/core/agents/events/consume.d.ts +0 -82
  621. package/dist/core/agents/events/consume.d.ts.map +0 -1
  622. package/dist/core/agents/events/consume.js +0 -195
  623. package/dist/core/agents/events/emit.d.ts +0 -42
  624. package/dist/core/agents/events/emit.d.ts.map +0 -1
  625. package/dist/core/agents/events/emit.js +0 -75
  626. package/dist/core/agents/events/ulid.d.ts +0 -11
  627. package/dist/core/agents/events/ulid.d.ts.map +0 -1
  628. package/dist/core/agents/events/ulid.js +0 -47
  629. package/dist/core/agents/state/heartbeat-projector.d.ts +0 -76
  630. package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
  631. package/dist/core/agents/state/heartbeat-projector.js +0 -581
  632. package/dist/core/hooks/events/emit.d.ts +0 -31
  633. package/dist/core/hooks/events/emit.d.ts.map +0 -1
  634. package/dist/core/hooks/events/emit.js +0 -93
  635. package/dist/core/hooks/events/rotate.d.ts +0 -43
  636. package/dist/core/hooks/events/rotate.d.ts.map +0 -1
  637. package/dist/core/hooks/events/rotate.js +0 -142
  638. package/dist/core/hooks/events/schema.d.ts +0 -341
  639. package/dist/core/hooks/events/schema.d.ts.map +0 -1
  640. package/dist/core/hooks/events/schema.js +0 -12
  641. package/dist/core/hooks/events/ulid.d.ts +0 -10
  642. package/dist/core/hooks/events/ulid.d.ts.map +0 -1
  643. package/dist/core/hooks/events/ulid.js +0 -47
  644. package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
  645. package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
  646. package/dist/lib/docs-frontmatter-migrate.js +0 -364
  647. package/src/core/agents/cli-emit.ts +0 -64
  648. package/src/core/agents/codex-replay.ts +0 -163
  649. package/src/core/agents/events/consume.ts +0 -254
  650. package/src/core/agents/events/emit.ts +0 -115
  651. package/src/core/agents/events/ulid.ts +0 -51
  652. package/src/core/agents/state/heartbeat-projector.ts +0 -645
  653. package/src/core/hooks/events/emit.ts +0 -125
  654. package/src/core/hooks/events/rotate.ts +0 -151
  655. package/src/core/hooks/events/schema.ts +0 -582
  656. package/src/core/hooks/events/ulid.ts +0 -51
  657. package/src/lib/docs-frontmatter-migrate.ts +0 -427
@@ -0,0 +1,2176 @@
1
+ import { createHash, randomUUID } from "node:crypto";
2
+ import { chmodSync, closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
3
+ import { hostname } from "node:os";
4
+ import { basename, join, resolve } from "node:path";
5
+ import { extractBashCommand } from "../../../hooks/adapter/parse.js";
6
+ import { fsyncParentDirectory } from "../../../workflow/durable-record.js";
7
+ import { acquireNoClobberLease } from "../../../workflow/workspaces/leases.js";
8
+ import { buildEventV3 } from "../builder.js";
9
+ import { fingerprintV3, normalizeNativeIdV3 } from "../canonical.js";
10
+ import { adapterCapabilityProfileDigestV3, adapterSignalSupportV3, cursorToolChannelSupportV3, } from "../capabilities.js";
11
+ import { capabilityDriftPayloadsV3 } from "../capability-drift.js";
12
+ import { markObservedClockRegressionV3 } from "../clock-order.js";
13
+ import { readEventV3ControlState } from "../control.js";
14
+ import { fingerprintContextV3 } from "../fingerprint-keys.js";
15
+ import { attestationIdV3, clockIdV3, delegationIdV3, generationIdV3, spanIdV3 } from "../ids.js";
16
+ import { readLedgerV3 } from "../reader.js";
17
+ import { closeSpanStateV3, openSpanStateV3, recoverSpanUpperBoundV3, } from "../span-state.js";
18
+ import { extractTurnTelemetryV3, } from "../turn-telemetry.js";
19
+ import { assertEventV3, validateEventV3 } from "../validate.js";
20
+ import { EVENT_V3_LEDGER_RELATIVE_ROOT, writeEventV3, } from "../writer.js";
21
+ import { normalizeHookEventV3 } from "./hook.js";
22
+ import { appendHookIntakeRecordV3, hookIntakeGroupDirV3, listHookIntakeGroupsV3, listHookIntakeRecordsV3, removeIntakeRecordV3, writeProducerDiagnosticV3, } from "./intake.js";
23
+ const STATE_FORMAT = "harnery-v3-hook-producer";
24
+ const STATE_VERSION = 3;
25
+ /** ADR 0078 recovery policy constants. */
26
+ const CLOSED_SPAN_TURN_RETENTION = 2;
27
+ const CLOSED_SPAN_MEMORY_CAP = 512;
28
+ const CLOSED_TURN_MEMORY_CAP = 16;
29
+ const SPAN_SOFT_WATERMARK = 128;
30
+ /**
31
+ * Adapters whose turn-boundary recovery and mid-flight onboarding are enabled.
32
+ * Kept in code, outside the digested capability profiles, so tuning recovery
33
+ * never changes an adapter capability digest (ADR 0078).
34
+ */
35
+ const RECOVERY_ENABLED_ADAPTERS = new Set(["claude-code", "codex", "cursor"]);
36
+ const STATE_LEASE_RETRY_ATTEMPTS = 8;
37
+ const STATE_LEASE_RETRY_DELAY_MS = 25;
38
+ /**
39
+ * Record one hook signal through a private, crash-recoverable producer state file.
40
+ * The function is inert unless the exact requested candidate or active gate is open.
41
+ *
42
+ * Delivery guarantee: the parsed signal is appended to a durable intake spool
43
+ * BEFORE any producer state is read or validated, so a lost lease, a crash, or
44
+ * a state-format mismatch never destroys a delivered signal. Whichever process
45
+ * holds the session's state lease drains the spool in append order and rescans
46
+ * until an empty pass; reconcile and session-start hooks drain any group whose
47
+ * final appender never got the lease.
48
+ */
49
+ export function recordHookSignalV3(input) {
50
+ const control = readEventV3ControlState(input.coordRoot);
51
+ if (control.state !== input.mode) {
52
+ return { state: "gate_closed", reason: control.state };
53
+ }
54
+ const gate = hookSignalGate(control, input.adapter, input.signal);
55
+ if (gate)
56
+ return gate;
57
+ const rootId = control.genesis.event.scope.root_id;
58
+ const epochId = control.genesis.profile.privacy_key_epoch;
59
+ const rootFingerprintContext = fingerprintContextV3(input.coordRoot, rootId, undefined, epochId);
60
+ const sessionHash = sessionHashForSignal(input, rootFingerprintContext);
61
+ const path = producerStatePath(input.coordRoot, input.adapter, sessionHash);
62
+ const spoolPath = appendHookIntakeRecordV3(input.coordRoot, sessionHash, intakeRecord(input));
63
+ const lease = acquireStateLeaseWithRetry(input.coordRoot, path, STATE_LEASE_RETRY_ATTEMPTS);
64
+ if (!lease)
65
+ return { state: "spooled" };
66
+ try {
67
+ return (drainSessionIntakeLocked(input.coordRoot, control, input.adapter, sessionHash, path, {
68
+ path: spoolPath,
69
+ input,
70
+ }) ?? { state: "spooled" });
71
+ }
72
+ finally {
73
+ lease.release();
74
+ }
75
+ }
76
+ function hookSignalGate(control, adapter, signal) {
77
+ const expectedCapabilityDigest = `sha256:${adapterCapabilityProfileDigestV3(adapter).slice(4)}`;
78
+ if (!control.genesis.profile.adapter_capability_profile_digests.includes(expectedCapabilityDigest)) {
79
+ return { state: "gate_closed", reason: "capability_profile_not_approved" };
80
+ }
81
+ const requiredCapability = hookSignalCapability(signal);
82
+ if (adapterSignalSupportV3(adapter, requiredCapability) === "unsupported") {
83
+ return {
84
+ state: "gate_closed",
85
+ reason: `signal_not_approved:${requiredCapability}`,
86
+ };
87
+ }
88
+ return undefined;
89
+ }
90
+ function sessionHashForSignal(input, context) {
91
+ const nativeSession = input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id;
92
+ if (nativeSession !== undefined) {
93
+ return normalizeNativeIdV3(context, `${input.adapter}.session`, nativeSession);
94
+ }
95
+ // Cursor carries native session identity only on its start signal. Later
96
+ // hooks may still have an exact coordination instance, so reuse its single
97
+ // live producer authority instead of hashing the instance into a second
98
+ // state path. Ambiguous or absent matches keep the fail-closed fallback.
99
+ if (input.adapter === "cursor") {
100
+ const matches = listHookProducerStateRecordsV3(input.coordRoot).filter(({ state }) => state.adapter === input.adapter && state.instance_id === input.instance_id);
101
+ if (matches.length === 1) {
102
+ const name = basename(matches[0].path);
103
+ if (/^hid_[a-f0-9]{64}\.json$/.test(name)) {
104
+ return name.slice(0, -".json".length);
105
+ }
106
+ }
107
+ }
108
+ return normalizeNativeIdV3(context, `${input.adapter}.session`, input.instance_id);
109
+ }
110
+ function intakeRecord(input) {
111
+ return {
112
+ format: "harnery-v3-hook-intake",
113
+ format_version: 1,
114
+ mode: input.mode,
115
+ signal: input.signal,
116
+ payload: input.payload,
117
+ adapter: input.adapter,
118
+ instance_id: input.instance_id,
119
+ producer_id: input.producer_id,
120
+ build_id: input.build_id,
121
+ platform: input.platform,
122
+ ...(input.bridge ? { bridge: input.bridge } : {}),
123
+ ...(input.adapterVersion ? { adapterVersion: input.adapterVersion } : {}),
124
+ ...(input.harnessVersion ? { harnessVersion: input.harnessVersion } : {}),
125
+ ...(input.monotonic_ns ? { monotonic_ns: input.monotonic_ns } : {}),
126
+ ...(input.observed_at ? { observed_at: input.observed_at } : {}),
127
+ ...(input.hook_name ? { hook_name: safeRole(input.hook_name) } : {}),
128
+ ...(input.hook_duration_ms !== undefined
129
+ ? { hook_duration_ms: Math.max(0, Math.floor(input.hook_duration_ms)) }
130
+ : {}),
131
+ ...(input.stop_remediation !== undefined ? { stop_remediation: input.stop_remediation } : {}),
132
+ ...(input.turn_ritual ? { turn_ritual: input.turn_ritual } : {}),
133
+ };
134
+ }
135
+ function inputForIntakeRecord(coordRoot, record) {
136
+ return {
137
+ coordRoot,
138
+ mode: record.mode,
139
+ signal: record.signal,
140
+ payload: record.payload,
141
+ adapter: record.adapter,
142
+ instance_id: record.instance_id,
143
+ producer_id: record.producer_id,
144
+ build_id: record.build_id,
145
+ platform: record.platform,
146
+ ...(record.bridge ? { bridge: record.bridge } : {}),
147
+ ...(record.adapterVersion ? { adapterVersion: record.adapterVersion } : {}),
148
+ ...(record.harnessVersion ? { harnessVersion: record.harnessVersion } : {}),
149
+ ...(record.monotonic_ns ? { monotonic_ns: record.monotonic_ns } : {}),
150
+ ...(record.observed_at ? { observed_at: record.observed_at } : {}),
151
+ ...(record.hook_name ? { hook_name: record.hook_name } : {}),
152
+ ...(record.hook_duration_ms !== undefined ? { hook_duration_ms: record.hook_duration_ms } : {}),
153
+ ...(record.stop_remediation !== undefined ? { stop_remediation: record.stop_remediation } : {}),
154
+ ...(record.turn_ritual ? { turn_ritual: record.turn_ritual } : {}),
155
+ };
156
+ }
157
+ /**
158
+ * Drain one session's intake spool under its held state lease. Records are
159
+ * processed in append order and deleted only after their outcome is durably
160
+ * published; the loop rescans until an empty pass so appends that landed while
161
+ * draining are still picked up. A record that throws is preserved in the
162
+ * diagnostics spool and removed so it cannot poison the drain; the caller's
163
+ * own record rethrows to keep today's error visibility.
164
+ */
165
+ function drainSessionIntakeLocked(coordRoot, control, adapter, sessionHash, statePath, own) {
166
+ const directory = hookIntakeGroupDirV3(coordRoot, adapter, sessionHash);
167
+ let ownResult;
168
+ for (;;) {
169
+ const entries = listHookIntakeRecordsV3(directory);
170
+ if (entries.length === 0)
171
+ break;
172
+ for (const entry of entries) {
173
+ const isOwn = own !== undefined && entry.path === own.path;
174
+ let result;
175
+ if (!entry.record) {
176
+ writeProducerDiagnosticV3(coordRoot, "intake_unreadable", { path: entry.path });
177
+ }
178
+ else if (entry.record.mode !== control.state) {
179
+ writeProducerDiagnosticV3(coordRoot, "intake_gate_mismatch", {
180
+ observed_gate: control.state,
181
+ ...entry.record,
182
+ });
183
+ }
184
+ else {
185
+ const recordInput = isOwn ? own.input : inputForIntakeRecord(coordRoot, entry.record);
186
+ try {
187
+ result = processHookSignalLocked(control, recordInput, sessionHash, statePath);
188
+ }
189
+ catch (error) {
190
+ writeProducerDiagnosticV3(coordRoot, "intake_poison", {
191
+ error: String(error),
192
+ ...entry.record,
193
+ });
194
+ removeIntakeRecordV3(entry.path);
195
+ if (isOwn)
196
+ throw error;
197
+ continue;
198
+ }
199
+ }
200
+ removeIntakeRecordV3(entry.path);
201
+ if (isOwn)
202
+ ownResult = result ?? { state: "spooled" };
203
+ }
204
+ }
205
+ return ownResult;
206
+ }
207
+ /**
208
+ * Drain every session's pending intake records. This is the terminal drainer:
209
+ * it does not depend on a "next signal" ever arriving for a session, so it is
210
+ * wired into reconcile and session-start paths to pick up a final signal whose
211
+ * appender lost the lease and exited.
212
+ */
213
+ export function drainHookIntakeSpoolV3(coordRoot) {
214
+ const result = {
215
+ groups_with_records: 0,
216
+ groups_drained: 0,
217
+ groups_skipped_busy: 0,
218
+ };
219
+ const control = readEventV3ControlState(coordRoot);
220
+ if (control.state !== "candidate" && control.state !== "active")
221
+ return result;
222
+ for (const group of listHookIntakeGroupsV3(coordRoot)) {
223
+ if (listHookIntakeRecordsV3(group.directory).length === 0)
224
+ continue;
225
+ result.groups_with_records += 1;
226
+ const statePath = producerStatePath(coordRoot, group.adapter, group.session_hash);
227
+ const lease = acquireStateLeaseWithRetry(coordRoot, statePath, 2);
228
+ if (!lease) {
229
+ result.groups_skipped_busy += 1;
230
+ continue;
231
+ }
232
+ try {
233
+ drainSessionIntakeLocked(coordRoot, control, group.adapter, group.session_hash, statePath);
234
+ result.groups_drained += 1;
235
+ }
236
+ finally {
237
+ lease.release();
238
+ }
239
+ }
240
+ return result;
241
+ }
242
+ function acquireStateLeaseWithRetry(coordRoot, statePath, attempts) {
243
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
244
+ try {
245
+ return acquireStateLease(coordRoot, statePath);
246
+ }
247
+ catch (error) {
248
+ const message = String(error);
249
+ const contended = message.includes("held by a live or unexpired owner") ||
250
+ message.includes("recovery is already in progress");
251
+ if (!contended)
252
+ throw error;
253
+ if (attempt < attempts)
254
+ sleepSync(STATE_LEASE_RETRY_DELAY_MS);
255
+ }
256
+ }
257
+ return undefined;
258
+ }
259
+ function sleepSync(milliseconds) {
260
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
261
+ }
262
+ /** The pre-spool body of the recorder: requires the session's state lease to be held. */
263
+ function processHookSignalLocked(control, input, sessionHash, path) {
264
+ const rootId = control.genesis.event.scope.root_id;
265
+ const epochId = control.genesis.profile.privacy_key_epoch;
266
+ const boundaryEventId = control.state === "candidate"
267
+ ? control.genesis.event.event_id
268
+ : control.activation.event.event_id;
269
+ const rootFingerprintContext = fingerprintContextV3(input.coordRoot, rootId, undefined, epochId);
270
+ const sessionId = `sid_${sessionHash.slice(4)}`;
271
+ try {
272
+ let state = existsSync(path) ? readProducerState(path) : undefined;
273
+ if (state &&
274
+ (state.adapter !== input.adapter ||
275
+ state.session_id !== sessionId ||
276
+ state.privacy_epoch_id !== epochId ||
277
+ state.capability_profile !== adapterCapabilityProfileDigestV3(input.adapter))) {
278
+ throw new Error("V3 producer state authority does not match the active boundary");
279
+ }
280
+ if (state?.adapter === "cursor" &&
281
+ input.payload.cursor_mode &&
282
+ input.payload.cursor_mode !== "unknown") {
283
+ if (!state.cursor_mode || state.cursor_mode === "unknown") {
284
+ state.cursor_mode = input.payload.cursor_mode;
285
+ }
286
+ else if (state.cursor_mode !== input.payload.cursor_mode) {
287
+ throw new Error("Cursor execution mode changed within one V3 generation");
288
+ }
289
+ }
290
+ let recovered;
291
+ if (state?.pending) {
292
+ const pendingSource = state.pending.source_id;
293
+ const incomingSource = sourceIdForSignal(input, rootFingerprintContext);
294
+ const pendingEvent = state.pending.event;
295
+ const durability = writeEventV3(input.coordRoot, pendingEvent);
296
+ applyCommittedEvent(state, pendingEvent);
297
+ state.pending = undefined;
298
+ publishProducerState(path, state);
299
+ if (pendingSource && incomingSource === pendingSource) {
300
+ recovered = { state: "recorded", event: pendingEvent, durability, recovered: true };
301
+ }
302
+ }
303
+ if (recovered)
304
+ return recovered;
305
+ if (input.signal === "session-start") {
306
+ if (state && !state.terminal && state.started_event_id) {
307
+ return { state: "already_started", event_id: state.started_event_id };
308
+ }
309
+ if (input.session_start_derivation && !state?.terminal) {
310
+ return { state: "missing_session_start" };
311
+ }
312
+ state = newProducerState(input, sessionId, epochId, boundaryEventId);
313
+ }
314
+ else if (!state || state.terminal) {
315
+ // Mid-flight onboarding (ADR 0078): a live session with no producer
316
+ // state in this epoch (fresh epoch, lost session-start hook) opens a
317
+ // new generation with a derived session.started so its activity is
318
+ // recorded instead of refused. A TERMINAL state never re-onboards:
319
+ // resurrection after authoritative termination stays forbidden.
320
+ // Onboarding requires the payload's own native session identity: an
321
+ // env-attributed signal must not mint a generation under a hash derived
322
+ // from the observer instance (that would split the real session).
323
+ const nativeSessionIdentity = input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id;
324
+ const onboardable = !state &&
325
+ RECOVERY_ENABLED_ADAPTERS.has(input.adapter) &&
326
+ input.signal !== "session-end" &&
327
+ nativeSessionIdentity !== undefined;
328
+ if (!onboardable) {
329
+ writeProducerDiagnosticV3(input.coordRoot, "missing_session_start", {
330
+ adapter: input.adapter,
331
+ instance_id: input.instance_id,
332
+ signal: input.signal,
333
+ session_hash: sessionHash,
334
+ payload: input.payload,
335
+ });
336
+ return { state: "missing_session_start" };
337
+ }
338
+ state = newProducerState(input, sessionId, epochId, boundaryEventId);
339
+ const cursorPromptBootstrap = isCursorPromptBootstrap(input);
340
+ const onboarding = buildMidFlightSessionStart(input, state, rootId, cursorPromptBootstrap);
341
+ commitEventLocked(input, state, path, onboarding);
342
+ if (!cursorPromptBootstrap) {
343
+ writeProducerDiagnosticV3(input.coordRoot, "mid_flight_onboarding", {
344
+ adapter: input.adapter,
345
+ instance_id: input.instance_id,
346
+ signal: input.signal,
347
+ session_hash: sessionHash,
348
+ });
349
+ }
350
+ }
351
+ const fingerprintContext = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
352
+ const recoveryEnabled = RECOVERY_ENABLED_ADAPTERS.has(input.adapter);
353
+ const nativeTid = input.payload.turn_id
354
+ ? normalizeNativeIdV3(fingerprintContext, `${input.adapter}.turn`, input.payload.turn_id).replace(/^hid_/, "tid_")
355
+ : undefined;
356
+ const cursorPromptWithinOpenTurn = input.adapter === "cursor" &&
357
+ input.signal === "user-prompt-submit" &&
358
+ state.current_turn_id !== undefined &&
359
+ state.current_turn_span !== undefined;
360
+ const startsDifferentNativeTurn = recoveryEnabled &&
361
+ input.signal === "user-prompt-submit" &&
362
+ !cursorPromptWithinOpenTurn &&
363
+ nativeTid !== undefined &&
364
+ state.current_turn_id !== undefined &&
365
+ state.current_turn_id !== nativeTid &&
366
+ state.current_turn_span !== undefined;
367
+ if (startsDifferentNativeTurn) {
368
+ closeResolvedWaits(input, state, path, rootId, fingerprintContext, nativeTid);
369
+ const endingTid = state.current_turn_id;
370
+ sweepOpenSpans(input, state, path, rootId, (candidate) => candidate.turn_id === endingTid, "completion_not_observed_before_next_turn");
371
+ commitRecoveredTurnCompleted(input, state, path, rootId, fingerprintContext);
372
+ }
373
+ const staleClosedTurnTool = recoveryEnabled &&
374
+ isToolSignal(input.signal) &&
375
+ nativeTid !== undefined &&
376
+ state.closed_turn_ids.includes(nativeTid) &&
377
+ state.current_turn_id !== nativeTid;
378
+ if (staleClosedTurnTool && !state.current_turn_id) {
379
+ commitRecoveredToolTurnStart(input, state, path, rootId, fingerprintContext);
380
+ }
381
+ const eventInput = staleClosedTurnTool
382
+ ? { ...input, payload: withoutNativeTurnId(input.payload) }
383
+ : input;
384
+ const duplicateOpenTurnStart = input.signal === "user-prompt-submit" &&
385
+ state.current_turn_span !== undefined &&
386
+ (cursorPromptWithinOpenTurn ||
387
+ (nativeTid !== undefined && state.current_turn_id === nativeTid));
388
+ recordTurnHarnessTiming(state, input, !duplicateOpenTurnStart);
389
+ closeResolvedWaits(input, state, path, rootId, fingerprintContext, nativeTid);
390
+ if (input.signal === "permission-request") {
391
+ const waitId = waitIdForInput(input, fingerprintContext);
392
+ if (waitId && state.waits.some((wait) => wait.wait_id === waitId)) {
393
+ return { state: "ignored" };
394
+ }
395
+ }
396
+ // Turn-boundary recovery (ADR 0078): a turn terminal is authoritative for
397
+ // the spans it owns. Derived terminals land BEFORE the native turn event.
398
+ if (recoveryEnabled && (input.signal === "stop" || input.signal === "stop-failure")) {
399
+ // The open producer turn is authoritative for a terminal. Cursor may
400
+ // report a different generation_id on Stop than beforeSubmitPrompt.
401
+ const endingTid = state.current_turn_id ?? nativeTid;
402
+ if (endingTid) {
403
+ sweepOpenSpans(input, state, path, rootId, (candidate) => candidate.turn_id !== undefined && candidate.turn_id === endingTid, "completion_not_observed_before_turn_end");
404
+ }
405
+ }
406
+ else if (recoveryEnabled && input.signal === "user-prompt-submit") {
407
+ sweepOpenSpans(input, state, path, rootId, (candidate) => candidate.turn_id !== undefined &&
408
+ (nativeTid === undefined || candidate.turn_id !== nativeTid), "completion_not_observed_before_next_turn");
409
+ }
410
+ if (duplicateOpenTurnStart) {
411
+ writeProducerDiagnosticV3(input.coordRoot, "duplicate_turn_start_suppressed", {
412
+ adapter: input.adapter,
413
+ signal: input.signal,
414
+ reason: cursorPromptWithinOpenTurn
415
+ ? "cursor_prompt_while_turn_open"
416
+ : "native_turn_already_open",
417
+ instance_id: input.instance_id,
418
+ session_hash: sessionHash,
419
+ generation_id: state.generation_id,
420
+ turn_id: state.current_turn_id ?? nativeTid,
421
+ span_id: state.current_turn_span?.span_id,
422
+ payload: input.payload,
423
+ });
424
+ publishProducerState(path, state);
425
+ return { state: "ignored" };
426
+ }
427
+ // Every event derived before the native signal has now been committed.
428
+ // Sample once for the native event and every span it opens or closes so
429
+ // their self-contained timing cannot drift by a millisecond.
430
+ const eventClock = signalClock(input);
431
+ if (input.signal === "user-prompt-submit" && !state.current_turn_span) {
432
+ state.current_turn_span = openSpanStateV3({
433
+ span_id: spanIdV3(),
434
+ parent_span_id: state.session_span.span_id,
435
+ boot_id: state.boot_id,
436
+ clock: eventClock,
437
+ });
438
+ }
439
+ let sourceId = sourceIdForSignal(input, rootFingerprintContext);
440
+ const semanticKey = cursorShellSemanticKey(input, rootFingerprintContext);
441
+ let span;
442
+ let delegation;
443
+ if (input.signal === "pre-tool-use") {
444
+ span = semanticKey
445
+ ? state.spans.find((candidate) => candidate.semantic_key === semanticKey)
446
+ : undefined;
447
+ if (span?.requested_event_id)
448
+ return { state: "ignored" };
449
+ if (!sourceId && semanticKey) {
450
+ sourceId = cursorShellFallbackSourceId(semanticKey, state, rootFingerprintContext);
451
+ }
452
+ if (!sourceId)
453
+ return unpairableTool(input, sessionHash, "missing_tool_use_id");
454
+ if (state.closed_spans.some((closed) => closed.source_id === sourceId)) {
455
+ // A late pre for a closed span must never open a fresh span: that is
456
+ // the pre/post-inversion orphan (ADR 0078).
457
+ return suppressClosedSpanSignal(input, sessionHash, "late_pre_suppressed");
458
+ }
459
+ if (recoveryEnabled && state.spans.length >= SPAN_SOFT_WATERMARK) {
460
+ const currentTid = nativeTid ?? state.current_turn_id;
461
+ sweepOpenSpans(input, state, path, rootId, (candidate) => candidate.turn_id !== undefined && candidate.turn_id !== currentTid, "span_cap_pressure");
462
+ }
463
+ span ??= state.spans.find((candidate) => candidate.source_id === sourceId);
464
+ if (span?.requested_event_id)
465
+ return { state: "ignored" };
466
+ if (!span) {
467
+ const opened = openSpanStateV3({
468
+ span_id: spanIdV3(),
469
+ parent_span_id: state.current_turn_span?.span_id ?? state.session_span.span_id,
470
+ boot_id: state.boot_id,
471
+ clock: eventClock,
472
+ });
473
+ span = {
474
+ source_id: sourceId,
475
+ ...(semanticKey ? { semantic_key: semanticKey } : {}),
476
+ ...opened,
477
+ ...(input.payload.tool_name ? { tool_name: safeRole(input.payload.tool_name) } : {}),
478
+ };
479
+ state.spans.push(span);
480
+ }
481
+ }
482
+ else if (input.signal === "post-tool-use" || input.signal === "post-tool-use-failure") {
483
+ span = sourceId
484
+ ? state.spans.find((candidate) => candidate.source_id === sourceId)
485
+ : undefined;
486
+ span ??= semanticKey
487
+ ? state.spans.find((candidate) => candidate.semantic_key === semanticKey)
488
+ : undefined;
489
+ if (span)
490
+ sourceId = span.source_id;
491
+ if (!sourceId && semanticKey) {
492
+ sourceId = cursorShellFallbackSourceId(semanticKey, state, rootFingerprintContext);
493
+ }
494
+ if (!sourceId)
495
+ return unpairableTool(input, sessionHash, "missing_tool_use_id");
496
+ if (!span) {
497
+ if (semanticKey &&
498
+ state.closed_spans.some((closed) => closed.semantic_key === semanticKey)) {
499
+ return { state: "ignored" };
500
+ }
501
+ if (state.closed_spans.some((closed) => closed.source_id === sourceId)) {
502
+ return suppressClosedSpanSignal(input, sessionHash, "late_post_suppressed");
503
+ }
504
+ if (!recoveryEnabled)
505
+ return unpairableTool(input, sessionHash, "no_open_span");
506
+ // Unmatched post (ADR 0078): record a derived request and pair the
507
+ // native completion on a fresh span instead of discarding the result.
508
+ const opened = openSpanStateV3({
509
+ span_id: spanIdV3(),
510
+ parent_span_id: state.current_turn_span?.span_id ?? state.session_span.span_id,
511
+ boot_id: state.boot_id,
512
+ clock: eventClock,
513
+ });
514
+ span = {
515
+ source_id: sourceId,
516
+ ...(semanticKey ? { semantic_key: semanticKey } : {}),
517
+ ...opened,
518
+ recovery_reason: "request_not_observed",
519
+ ...(input.payload.tool_name ? { tool_name: safeRole(input.payload.tool_name) } : {}),
520
+ };
521
+ const derivedRequest = normalizeHookEventV3("pre-tool-use", eventInput.payload, {
522
+ coordRoot: input.coordRoot,
523
+ adapter: input.adapter,
524
+ adapterVersion: input.adapterVersion,
525
+ harnessVersion: input.harnessVersion,
526
+ root_id: rootId,
527
+ run_id: input.run_id,
528
+ workflow_id: input.workflow_id,
529
+ workflow_agent_id: input.workflow_agent_id,
530
+ instance_id: input.instance_id,
531
+ generation_id: state.generation_id,
532
+ attestation_id: state.attestation_id,
533
+ producer_id: input.producer_id,
534
+ boot_id: state.boot_id,
535
+ sequence: state.next_sequence,
536
+ build_id: input.build_id,
537
+ platform: input.platform,
538
+ bridge: input.bridge,
539
+ capability_profile: state.capability_profile,
540
+ fingerprintContext,
541
+ turn_id: state.current_turn_id,
542
+ span_id: span.span_id,
543
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
544
+ observed_at: eventClock.observed_at,
545
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
546
+ clock_id: state.clock_id,
547
+ });
548
+ if (!derivedRequest)
549
+ return unpairableTool(input, sessionHash, "no_open_span");
550
+ derivedRequest.provenance = {
551
+ ...derivedRequest.provenance,
552
+ attestation: "derived",
553
+ confidence: input.payload.tool_input !== undefined ? "high" : "medium",
554
+ };
555
+ derivedRequest.payload.recovery = {
556
+ reason: "request_not_observed",
557
+ };
558
+ state.spans.push(span);
559
+ commitEventLocked(input, state, path, derivedRequest);
560
+ span.requested_event_id = derivedRequest.event_id;
561
+ span.open_event_id = derivedRequest.event_id;
562
+ stampSpanTurn(span, derivedRequest, eventInput, state);
563
+ }
564
+ }
565
+ if (input.signal === "sub-agent-start") {
566
+ if (!sourceId)
567
+ return { state: "ignored" };
568
+ delegation = state.delegations.find((candidate) => candidate.source_id === sourceId);
569
+ if (!delegation) {
570
+ const opened = openSpanStateV3({
571
+ span_id: spanIdV3(),
572
+ parent_span_id: state.current_turn_span?.span_id ?? state.session_span.span_id,
573
+ boot_id: state.boot_id,
574
+ clock: eventClock,
575
+ });
576
+ delegation = {
577
+ source_id: sourceId,
578
+ ...opened,
579
+ delegation_id: delegationIdV3(),
580
+ child_generation_id: generationIdV3(),
581
+ role: safeRole(input.payload.raw.agent_type),
582
+ };
583
+ state.delegations.push(delegation);
584
+ }
585
+ }
586
+ else if (input.signal === "sub-agent-stop") {
587
+ if (!sourceId)
588
+ return { state: "ignored" };
589
+ delegation = state.delegations.find((candidate) => candidate.source_id === sourceId);
590
+ if (!delegation)
591
+ return { state: "ignored" };
592
+ }
593
+ const waitSpan = input.signal === "permission-request"
594
+ ? openSpanStateV3({
595
+ span_id: spanIdV3(),
596
+ parent_span_id: state.current_turn_span?.span_id ?? state.session_span.span_id,
597
+ boot_id: state.boot_id,
598
+ clock: eventClock,
599
+ })
600
+ : undefined;
601
+ const openingSpan = input.signal === "session-start"
602
+ ? state.session_span
603
+ : input.signal === "user-prompt-submit"
604
+ ? state.current_turn_span
605
+ : input.signal === "pre-tool-use"
606
+ ? span
607
+ : input.signal === "permission-request"
608
+ ? waitSpan
609
+ : input.signal === "sub-agent-start"
610
+ ? delegation
611
+ : undefined;
612
+ const closingSpan = input.signal === "session-end"
613
+ ? state.session_span
614
+ : input.signal === "stop" || input.signal === "stop-failure"
615
+ ? state.current_turn_span
616
+ : input.signal === "post-tool-use" || input.signal === "post-tool-use-failure"
617
+ ? span
618
+ : input.signal === "sub-agent-stop"
619
+ ? delegation
620
+ : undefined;
621
+ const terminalSpan = closingSpan
622
+ ? closeSpanStateV3(closingSpan, {
623
+ boot_id: state.boot_id,
624
+ clock: eventClock,
625
+ ...(span && closingSpan === span && span.recovery_reason
626
+ ? { recovery_reason: span.recovery_reason }
627
+ : {}),
628
+ })
629
+ : undefined;
630
+ const turnTelemetry = input.signal === "stop" || input.signal === "stop-failure"
631
+ ? extractTurnTelemetryV3(input.adapter, input.payload.raw, eventClock.observed_at)
632
+ : undefined;
633
+ const cursorToolChannelSupport = input.adapter === "cursor"
634
+ ? cursorToolChannelSupportV3(state.cursor_mode ?? "unknown")
635
+ : undefined;
636
+ const cursorToolChannelUnattested = (input.signal === "stop" || input.signal === "stop-failure") &&
637
+ input.adapter === "cursor" &&
638
+ cursorToolChannelSupport !== "unsupported" &&
639
+ state.tool_call_count === 0;
640
+ if (input.signal === "sub-agent-start" && delegation && !delegation.open_event_id) {
641
+ commitAgentDelegated(input, state, path, rootId, fingerprintContext, delegation);
642
+ }
643
+ const toolCallCountScopeMismatch = (input.signal === "stop" || input.signal === "stop-failure") &&
644
+ (!state.current_turn_id || state.tool_call_count_turn_id !== state.current_turn_id);
645
+ const event = normalizeHookEventV3(input.signal, eventInput.payload, {
646
+ coordRoot: input.coordRoot,
647
+ adapter: input.adapter,
648
+ adapterVersion: input.adapterVersion,
649
+ harnessVersion: input.harnessVersion,
650
+ root_id: rootId,
651
+ run_id: input.run_id,
652
+ workflow_id: input.workflow_id,
653
+ workflow_agent_id: input.workflow_agent_id,
654
+ // Native subagent tool hooks can carry the parent's session id while
655
+ // resolving to a child process instance. The session-keyed producer
656
+ // state remains the generation authority; route those signals through
657
+ // its instance instead of rejecting or forging a child/parent scope.
658
+ instance_id: state.instance_id,
659
+ generation_id: state.generation_id,
660
+ attestation_id: state.attestation_id,
661
+ producer_id: input.producer_id,
662
+ boot_id: state.boot_id,
663
+ sequence: state.next_sequence,
664
+ build_id: input.build_id,
665
+ platform: input.platform,
666
+ bridge: input.bridge,
667
+ capability_profile: state.capability_profile,
668
+ fingerprintContext,
669
+ turn_id: state.current_turn_id,
670
+ span_id: openingSpan?.span_id ?? closingSpan?.span_id,
671
+ parent_span_id: openingSpan?.parent_span_id ?? closingSpan?.parent_span_id,
672
+ terminal_span: terminalSpan,
673
+ harness_timing: state.turn_harness,
674
+ turn_telemetry: turnTelemetry,
675
+ caused_by: [
676
+ ...(state.last_event_id ? [state.last_event_id] : []),
677
+ ...(terminalSpan?.open_event_id ? [terminalSpan.open_event_id] : []),
678
+ ].filter((value, index, values) => values.indexOf(value) === index),
679
+ observed_at: eventClock.observed_at,
680
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
681
+ clock_id: state.clock_id,
682
+ duration_ms: durationMilliseconds(span?.opened_monotonic_ns, input.monotonic_ns),
683
+ // Cursor's terminal payload has no native tool aggregate. With no
684
+ // delivered tool hook, the recorder has no evidence that zero calls
685
+ // occurred; emitting an exact zero would turn hook loss into false data.
686
+ tool_call_count: cursorToolChannelSupport === "unsupported" ||
687
+ cursorToolChannelUnattested ||
688
+ toolCallCountScopeMismatch
689
+ ? undefined
690
+ : state.tool_call_count,
691
+ tool_call_count_support: cursorToolChannelSupport,
692
+ tool_call_count_missing_reason: cursorToolChannelUnattested
693
+ ? "tool_channel_unattested"
694
+ : toolCallCountScopeMismatch
695
+ ? "tool_count_turn_scope_unattested"
696
+ : undefined,
697
+ delegation_id: delegation?.delegation_id,
698
+ child_generation_id: delegation?.child_generation_id,
699
+ agent_role: delegation?.role,
700
+ stop_remediation: input.stop_remediation,
701
+ turn_ritual: input.turn_ritual,
702
+ });
703
+ if (!event)
704
+ return { state: "ignored" };
705
+ if (input.signal === "session-start" &&
706
+ input.session_start_derivation === "approved_lifecycle_reopen" &&
707
+ event.event_type === "session.started") {
708
+ event.provenance = {
709
+ ...event.provenance,
710
+ source_event: `${input.adapter}.approved-lifecycle-reopen`,
711
+ attestation: "derived",
712
+ confidence: "high",
713
+ attribution: {
714
+ method: "session_env",
715
+ state: "verified",
716
+ subject_instance_id: state.instance_id,
717
+ },
718
+ };
719
+ event.payload.resume = {
720
+ state: "unknown",
721
+ reason: "approved_lifecycle_reopen",
722
+ };
723
+ assertEventV3(event);
724
+ }
725
+ if (input.signal === "pre-tool-use" && span && !span.requested_event_id) {
726
+ span.requested_event_id = event.event_id;
727
+ span.open_event_id = event.event_id;
728
+ stampSpanTurn(span, event, eventInput, state);
729
+ }
730
+ const durability = commitEventLocked(input, state, path, event, sourceId);
731
+ if (event.event_type === "tool.requested" && sourceId) {
732
+ const explicitWait = explicitNativeWait(input);
733
+ if (explicitWait) {
734
+ commitNativeWait(input, state, path, rootId, sourceId, explicitWait);
735
+ }
736
+ else if (input.payload.tool_name === "request_user_input") {
737
+ commitNativeWait(input, state, path, rootId, sourceId, { kind: "needs_input" });
738
+ }
739
+ }
740
+ const progressKind = progressKindForCompletedTool(event, input);
741
+ if (progressKind) {
742
+ commitProgressObservation(input, state, path, rootId, event, progressKind);
743
+ }
744
+ if (turnTelemetry) {
745
+ commitTurnContextObservation(input, state, path, rootId, event, turnTelemetry.context);
746
+ }
747
+ if (event.event_type === "session.ended") {
748
+ commitCapabilityDriftEvents(input, state, path, rootId);
749
+ }
750
+ return { state: "recorded", event, durability, recovered: false };
751
+ }
752
+ finally {
753
+ // The session's state lease is held by the draining caller.
754
+ }
755
+ }
756
+ function commitAgentDelegated(input, state, path, rootId, fingerprintContext, delegation) {
757
+ const event = buildEventV3("agent.delegated", {
758
+ producer: producerForDerivedEvent(input, state),
759
+ scope: scopeForDerivedEvent(input, state, rootId),
760
+ attestation_id: state.attestation_id,
761
+ links: {
762
+ span_id: delegation.span_id,
763
+ ...(delegation.parent_span_id ? { parent_span_id: delegation.parent_span_id } : {}),
764
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
765
+ },
766
+ provenance: derivedProvenance(input, state, "subagent-delegated"),
767
+ observed_at: signalClock(input).observed_at,
768
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
769
+ clock_id: state.clock_id,
770
+ payload: {
771
+ delegation_id: delegation.delegation_id,
772
+ child_generation_id: delegation.child_generation_id,
773
+ role: delegation.role,
774
+ ownership_fingerprint: fingerprintV3(fingerprintContext, "delegation-ownership", {
775
+ role: delegation.role,
776
+ source_id: delegation.source_id,
777
+ }),
778
+ },
779
+ });
780
+ commitEventLocked(input, state, path, event);
781
+ }
782
+ function commitNativeWait(input, state, path, rootId, waitId, observation) {
783
+ if (!state.current_turn_id || state.waits.some((wait) => wait.wait_id === waitId))
784
+ return;
785
+ const span = openSpanStateV3({
786
+ span_id: spanIdV3(),
787
+ parent_span_id: state.current_turn_span?.span_id ?? state.session_span.span_id,
788
+ boot_id: state.boot_id,
789
+ clock: signalClock(input),
790
+ });
791
+ const event = buildEventV3("wait.started", {
792
+ producer: producerForDerivedEvent(input, state),
793
+ scope: scopeForDerivedEvent(input, state, rootId),
794
+ attestation_id: state.attestation_id,
795
+ links: {
796
+ span_id: span.span_id,
797
+ ...(span.parent_span_id ? { parent_span_id: span.parent_span_id } : {}),
798
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
799
+ },
800
+ provenance: derivedProvenance(input, state, "native-typed-wait"),
801
+ observed_at: signalClock(input).observed_at,
802
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
803
+ clock_id: state.clock_id,
804
+ payload: {
805
+ wait_id: waitId,
806
+ kind: observation.kind,
807
+ ...(observation.wake_at ? { wake_at: observation.wake_at } : {}),
808
+ ...(observation.authority_reference
809
+ ? { authority_reference: observation.authority_reference }
810
+ : {}),
811
+ },
812
+ });
813
+ commitEventLocked(input, state, path, event);
814
+ }
815
+ function progressKindForCompletedTool(event, input) {
816
+ if (event.event_type !== "tool.completed" || event.payload.outcome !== "succeeded")
817
+ return;
818
+ const explicit = explicitNativeProgress(input);
819
+ if (explicit)
820
+ return explicit;
821
+ const name = event.payload.tool.name;
822
+ if (["apply_patch", "Edit", "Write", "MultiEdit", "NotebookEdit"].includes(name))
823
+ return "write";
824
+ if (name === "view_image")
825
+ return "review";
826
+ const leaf = name
827
+ .split(/__|[/:]/)
828
+ .at(-1)
829
+ ?.toLowerCase() ?? name.toLowerCase();
830
+ if (["run_tests", "test", "vitest", "jest", "playwright_test"].includes(leaf))
831
+ return "test";
832
+ if (["git_commit", "commit"].includes(leaf))
833
+ return "commit";
834
+ if (["deploy", "deploy_app", "deploy_service"].includes(leaf))
835
+ return "deploy";
836
+ if (["publish", "wiki_publish", "publish_page"].includes(leaf))
837
+ return "publication";
838
+ if (["build", "compile", "bundle"].includes(leaf))
839
+ return "artifact";
840
+ if (/imagegen|create_document|create_spreadsheet|create_presentation/i.test(name)) {
841
+ return "artifact";
842
+ }
843
+ return undefined;
844
+ }
845
+ function commitProgressObservation(input, state, path, rootId, terminal, kind) {
846
+ const event = buildEventV3("progress.observed", {
847
+ producer: producerForDerivedEvent(input, state),
848
+ scope: scopeForDerivedEvent(input, state, rootId),
849
+ attestation_id: state.attestation_id,
850
+ links: { caused_by: [terminal.event_id] },
851
+ provenance: derivedProvenance(input, state, "semantic-progress"),
852
+ observed_at: terminal.time.observed_at,
853
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
854
+ clock_id: state.clock_id,
855
+ payload: {
856
+ kind,
857
+ evidence_event_ids: [terminal.event_id],
858
+ reducer_build_id: input.build_id,
859
+ },
860
+ });
861
+ commitEventLocked(input, state, path, event);
862
+ }
863
+ function explicitNativeProgress(input) {
864
+ const value = input.payload.raw.harnery_progress_kind ?? input.payload.raw.progress_kind;
865
+ return value === "write" ||
866
+ value === "test" ||
867
+ value === "commit" ||
868
+ value === "deploy" ||
869
+ value === "publication" ||
870
+ value === "review" ||
871
+ value === "artifact"
872
+ ? value
873
+ : undefined;
874
+ }
875
+ function explicitNativeWait(input) {
876
+ if (input.signal !== "pre-tool-use")
877
+ return undefined;
878
+ const raw = input.payload.raw;
879
+ const value = raw.harnery_wait_kind ?? raw.wait_kind;
880
+ const kind = value === "permission" ||
881
+ value === "needs_input" ||
882
+ value === "decision" ||
883
+ value === "approval" ||
884
+ value === "scheduled" ||
885
+ value === "rate_limit" ||
886
+ value === "unknown"
887
+ ? value
888
+ : undefined;
889
+ if (!kind)
890
+ return undefined;
891
+ const wakeCandidate = raw.harnery_wake_at ?? raw.wake_at;
892
+ const wake_at = typeof wakeCandidate === "string" &&
893
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(wakeCandidate) &&
894
+ Number.isFinite(Date.parse(wakeCandidate))
895
+ ? wakeCandidate
896
+ : undefined;
897
+ const authorityCandidate = raw.harnery_authority_reference ?? raw.authority_reference;
898
+ const authority_reference = safeTokenOrUndefined(authorityCandidate);
899
+ return {
900
+ kind,
901
+ ...(wake_at ? { wake_at } : {}),
902
+ ...(authority_reference ? { authority_reference } : {}),
903
+ };
904
+ }
905
+ function producerForDerivedEvent(input, state) {
906
+ return {
907
+ producer_id: input.producer_id,
908
+ boot_id: state.boot_id,
909
+ sequence: state.next_sequence,
910
+ component: "agent-hook",
911
+ build_id: input.build_id,
912
+ platform: input.platform,
913
+ ...(input.bridge ? { bridge: input.bridge } : {}),
914
+ };
915
+ }
916
+ function scopeForDerivedEvent(input, state, rootId) {
917
+ return {
918
+ root_id: rootId,
919
+ instance_id: state.instance_id,
920
+ session_id: state.session_id,
921
+ generation_id: state.generation_id,
922
+ ...(state.current_turn_id ? { turn_id: state.current_turn_id } : {}),
923
+ ...(input.run_id ? { run_id: input.run_id } : {}),
924
+ ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
925
+ ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
926
+ };
927
+ }
928
+ function derivedProvenance(input, state, signal) {
929
+ return {
930
+ source_event: `${input.adapter}.${signal}`,
931
+ attestation: "derived",
932
+ confidence: "high",
933
+ attribution: {
934
+ method: "session_env",
935
+ state: "verified",
936
+ subject_instance_id: state.instance_id,
937
+ },
938
+ };
939
+ }
940
+ /**
941
+ * Stamp the span with the turn its request event was attributed to. No stamp
942
+ * is written without a real turn context: a native payload turn id or an open
943
+ * producer-state turn. Unstamped spans are excluded from every boundary and
944
+ * cap sweep (fail closed; explicit-end salvage may still reach them).
945
+ */
946
+ function stampSpanTurn(span, requestEvent, input, state) {
947
+ if (input.payload.turn_id) {
948
+ span.turn_id = requestEvent.scope.turn_id;
949
+ span.turn_stamp = "native_payload";
950
+ }
951
+ else if (state.current_turn_id) {
952
+ span.turn_id = state.current_turn_id;
953
+ span.turn_stamp = "producer_state";
954
+ }
955
+ }
956
+ function isToolSignal(signal) {
957
+ return (signal === "pre-tool-use" || signal === "post-tool-use" || signal === "post-tool-use-failure");
958
+ }
959
+ function withoutNativeTurnId(payload) {
960
+ const { turn_id: _turnId, ...rest } = payload;
961
+ return rest;
962
+ }
963
+ function recoveryBoundaryPayload(payload) {
964
+ return {
965
+ raw: {},
966
+ ...(payload.session_id ? { session_id: payload.session_id } : {}),
967
+ ...(payload.conversation_id ? { conversation_id: payload.conversation_id } : {}),
968
+ ...(payload.agent_id ? { agent_id: payload.agent_id } : {}),
969
+ };
970
+ }
971
+ /** Close an open turn when a different native prompt proves the next boundary. */
972
+ function commitRecoveredTurnCompleted(input, state, path, rootId, fingerprintContext) {
973
+ const turnId = state.current_turn_id;
974
+ const span = state.current_turn_span;
975
+ if (!turnId || !span)
976
+ return;
977
+ const clock = signalClock(input);
978
+ const terminalSpan = closeSpanStateV3(span, {
979
+ boot_id: state.boot_id,
980
+ clock,
981
+ recovery_reason: "completion_not_observed_before_next_turn",
982
+ });
983
+ const toolCountAttested = state.tool_call_count_turn_id === turnId;
984
+ const event = normalizeHookEventV3("stop", recoveryBoundaryPayload(input.payload), {
985
+ coordRoot: input.coordRoot,
986
+ adapter: input.adapter,
987
+ adapterVersion: input.adapterVersion,
988
+ harnessVersion: input.harnessVersion,
989
+ root_id: rootId,
990
+ run_id: input.run_id,
991
+ workflow_id: input.workflow_id,
992
+ workflow_agent_id: input.workflow_agent_id,
993
+ instance_id: state.instance_id,
994
+ generation_id: state.generation_id,
995
+ attestation_id: state.attestation_id,
996
+ producer_id: input.producer_id,
997
+ boot_id: state.boot_id,
998
+ sequence: state.next_sequence,
999
+ build_id: input.build_id,
1000
+ platform: input.platform,
1001
+ bridge: input.bridge,
1002
+ capability_profile: state.capability_profile,
1003
+ fingerprintContext,
1004
+ turn_id: turnId,
1005
+ span_id: span.span_id,
1006
+ terminal_span: terminalSpan,
1007
+ harness_timing: state.turn_harness,
1008
+ caused_by: [
1009
+ ...(state.last_event_id ? [state.last_event_id] : []),
1010
+ ...(terminalSpan.open_event_id ? [terminalSpan.open_event_id] : []),
1011
+ ].filter((value, index, values) => values.indexOf(value) === index),
1012
+ observed_at: clock.observed_at,
1013
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1014
+ clock_id: state.clock_id,
1015
+ tool_call_count: toolCountAttested ? state.tool_call_count : undefined,
1016
+ tool_call_count_missing_reason: toolCountAttested
1017
+ ? undefined
1018
+ : "tool_count_turn_scope_unattested",
1019
+ });
1020
+ if (event?.event_type !== "turn.completed") {
1021
+ throw new Error("recovered turn terminal could not be normalized");
1022
+ }
1023
+ event.provenance = {
1024
+ ...event.provenance,
1025
+ source_event: `${input.adapter}.recovery`,
1026
+ attestation: "derived",
1027
+ confidence: "medium",
1028
+ attribution: {
1029
+ method: "native_payload",
1030
+ state: "verified",
1031
+ subject_instance_id: state.instance_id,
1032
+ },
1033
+ };
1034
+ event.payload.outcome = "unknown";
1035
+ assertEventV3(event);
1036
+ commitEventLocked(input, state, path, event);
1037
+ }
1038
+ /** Open a derived turn for tools that arrive after their native turn closed. */
1039
+ function commitRecoveredToolTurnStart(input, state, path, rootId, fingerprintContext) {
1040
+ const clock = signalClock(input);
1041
+ const span = openSpanStateV3({
1042
+ span_id: spanIdV3(),
1043
+ parent_span_id: state.session_span.span_id,
1044
+ boot_id: state.boot_id,
1045
+ clock,
1046
+ });
1047
+ state.current_turn_span = span;
1048
+ const event = normalizeHookEventV3("user-prompt-submit", recoveryBoundaryPayload(input.payload), {
1049
+ coordRoot: input.coordRoot,
1050
+ adapter: input.adapter,
1051
+ adapterVersion: input.adapterVersion,
1052
+ harnessVersion: input.harnessVersion,
1053
+ root_id: rootId,
1054
+ run_id: input.run_id,
1055
+ workflow_id: input.workflow_id,
1056
+ workflow_agent_id: input.workflow_agent_id,
1057
+ instance_id: state.instance_id,
1058
+ generation_id: state.generation_id,
1059
+ attestation_id: state.attestation_id,
1060
+ producer_id: input.producer_id,
1061
+ boot_id: state.boot_id,
1062
+ sequence: state.next_sequence,
1063
+ build_id: input.build_id,
1064
+ platform: input.platform,
1065
+ bridge: input.bridge,
1066
+ capability_profile: state.capability_profile,
1067
+ fingerprintContext,
1068
+ turn_native_id: `post-terminal:${state.generation_id}:${state.turn_ordinal + 1}`,
1069
+ span_id: span.span_id,
1070
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
1071
+ observed_at: clock.observed_at,
1072
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1073
+ clock_id: state.clock_id,
1074
+ });
1075
+ if (event?.event_type !== "turn.started") {
1076
+ throw new Error("recovered tool turn start could not be normalized");
1077
+ }
1078
+ event.provenance = {
1079
+ ...event.provenance,
1080
+ source_event: `${input.adapter}.recovery`,
1081
+ attestation: "derived",
1082
+ confidence: "medium",
1083
+ attribution: {
1084
+ method: "session_env",
1085
+ state: "verified",
1086
+ subject_instance_id: state.instance_id,
1087
+ },
1088
+ };
1089
+ assertEventV3(event);
1090
+ commitEventLocked(input, state, path, event);
1091
+ }
1092
+ /** The single pending-publish/write/apply/publish cycle every locked event commit uses. */
1093
+ function commitEventLocked(input, state, path, event, sourceId) {
1094
+ markObservedClockRegressionV3(event, state.last_observed_at);
1095
+ assertEventV3(event);
1096
+ state.pending = { ...(sourceId ? { source_id: sourceId } : {}), event };
1097
+ publishProducerState(path, state);
1098
+ const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
1099
+ applyCommittedEvent(state, event);
1100
+ state.pending = undefined;
1101
+ publishProducerState(path, state);
1102
+ return durability;
1103
+ }
1104
+ function commitTurnContextObservation(input, state, path, rootId, turnTerminal, measurement) {
1105
+ if (turnTerminal.event_type !== "turn.completed")
1106
+ return;
1107
+ const event = buildEventV3("context.observed", {
1108
+ producer: {
1109
+ producer_id: input.producer_id,
1110
+ boot_id: state.boot_id,
1111
+ sequence: state.next_sequence,
1112
+ component: "agent-hook",
1113
+ build_id: input.build_id,
1114
+ platform: input.platform,
1115
+ ...(input.bridge ? { bridge: input.bridge } : {}),
1116
+ },
1117
+ scope: {
1118
+ root_id: rootId,
1119
+ instance_id: state.instance_id,
1120
+ session_id: state.session_id,
1121
+ generation_id: state.generation_id,
1122
+ turn_id: turnTerminal.scope.turn_id,
1123
+ ...(input.run_id ? { run_id: input.run_id } : {}),
1124
+ ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
1125
+ ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
1126
+ },
1127
+ attestation_id: state.attestation_id,
1128
+ links: { caused_by: [turnTerminal.event_id] },
1129
+ provenance: {
1130
+ source_event: `${input.adapter}.turn-context`,
1131
+ attestation: "native",
1132
+ confidence: "exact",
1133
+ attribution: {
1134
+ method: "native_payload",
1135
+ state: "verified",
1136
+ subject_instance_id: state.instance_id,
1137
+ },
1138
+ },
1139
+ observed_at: measurement.state === "observed"
1140
+ ? measurement.value.measured_at
1141
+ : turnTerminal.time.observed_at,
1142
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1143
+ clock_id: state.clock_id,
1144
+ payload: {
1145
+ measurement,
1146
+ },
1147
+ });
1148
+ commitEventLocked(input, state, path, event);
1149
+ }
1150
+ function commitCapabilityDriftEvents(input, state, path, rootId) {
1151
+ const read = readLedgerV3(input.coordRoot);
1152
+ if (!read.complete)
1153
+ return;
1154
+ const generationEvents = read.events
1155
+ .map(({ event }) => event)
1156
+ .filter((event) => "generation_id" in event.scope && event.scope.generation_id === state.generation_id);
1157
+ const emittedSignals = new Set(generationEvents
1158
+ .filter((event) => event.event_type === "health.capability_drift")
1159
+ .map((event) => event.payload.signal));
1160
+ for (const payload of capabilityDriftPayloadsV3(input.adapter, generationEvents)) {
1161
+ if (emittedSignals.has(payload.signal))
1162
+ continue;
1163
+ const event = buildEventV3("health.capability_drift", {
1164
+ producer: {
1165
+ producer_id: input.producer_id,
1166
+ boot_id: state.boot_id,
1167
+ sequence: state.next_sequence,
1168
+ component: "agent-hook",
1169
+ build_id: input.build_id,
1170
+ platform: input.platform,
1171
+ ...(input.bridge ? { bridge: input.bridge } : {}),
1172
+ },
1173
+ scope: {
1174
+ root_id: rootId,
1175
+ instance_id: state.instance_id,
1176
+ session_id: state.session_id,
1177
+ generation_id: state.generation_id,
1178
+ },
1179
+ attestation_id: state.attestation_id,
1180
+ links: { caused_by: state.last_event_id ? [state.last_event_id] : [] },
1181
+ provenance: {
1182
+ source_event: `${input.adapter}.capability-coherence`,
1183
+ attestation: "derived",
1184
+ confidence: "exact",
1185
+ attribution: {
1186
+ method: "session_env",
1187
+ state: "verified",
1188
+ subject_instance_id: state.instance_id,
1189
+ },
1190
+ },
1191
+ observed_at: signalClock(input).observed_at,
1192
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1193
+ clock_id: state.clock_id,
1194
+ payload,
1195
+ });
1196
+ commitEventLocked(input, state, path, event);
1197
+ }
1198
+ }
1199
+ /**
1200
+ * Terminalize every open span matching the predicate with a derived
1201
+ * `tool.completed` (ADR 0078). One event per span, committed sequentially
1202
+ * through the pending cycle so a crash replays exactly one.
1203
+ */
1204
+ function sweepOpenSpans(input, state, path, rootId, matches, reason) {
1205
+ for (const span of [...state.spans]) {
1206
+ if (!matches(span))
1207
+ continue;
1208
+ const event = buildDerivedToolCompleted(input, state, rootId, span, reason);
1209
+ commitEventLocked(input, state, path, event);
1210
+ }
1211
+ }
1212
+ function buildDerivedToolCompleted(input, state, rootId, span, reason, producerOverride, observedAt) {
1213
+ const clock = signalClock(input);
1214
+ if (observedAt !== undefined)
1215
+ clock.observed_at = observedAt;
1216
+ const baseConfidence = reason === "completion_not_observed_before_turn_end" || reason === "explicit_end_salvage"
1217
+ ? "medium"
1218
+ : "low";
1219
+ const confidence = span.turn_stamp === "producer_state" && baseConfidence === "medium" ? "low" : baseConfidence;
1220
+ const terminalSpan = closeSpanStateV3(span, {
1221
+ boot_id: state.boot_id,
1222
+ clock,
1223
+ recovery_reason: reason,
1224
+ });
1225
+ const elapsedUpperBound = recoverSpanUpperBoundV3(span, {
1226
+ boot_id: state.boot_id,
1227
+ clock,
1228
+ });
1229
+ const event = buildEventV3("tool.completed", {
1230
+ producer: {
1231
+ producer_id: producerOverride?.producer_id ?? input.producer_id,
1232
+ boot_id: producerOverride?.boot_id ?? state.boot_id,
1233
+ sequence: producerOverride?.sequence ?? state.next_sequence,
1234
+ component: "agent-hook",
1235
+ build_id: input.build_id,
1236
+ platform: input.platform,
1237
+ ...(input.bridge ? { bridge: input.bridge } : {}),
1238
+ },
1239
+ scope: {
1240
+ root_id: rootId,
1241
+ instance_id: state.instance_id,
1242
+ session_id: state.session_id,
1243
+ generation_id: state.generation_id,
1244
+ turn_id: span.turn_id,
1245
+ },
1246
+ attestation_id: state.attestation_id,
1247
+ links: {
1248
+ caused_by: [
1249
+ ...(state.last_event_id ? [state.last_event_id] : []),
1250
+ ...(span.open_event_id ? [span.open_event_id] : []),
1251
+ ].filter((value, index, values) => values.indexOf(value) === index),
1252
+ span_id: span.span_id,
1253
+ },
1254
+ provenance: {
1255
+ source_event: `${input.adapter}.recovery`,
1256
+ attestation: "derived",
1257
+ confidence,
1258
+ attribution: {
1259
+ method: span.turn_stamp === "native_payload" ? "native_payload" : "session_env",
1260
+ state: "verified",
1261
+ subject_instance_id: state.instance_id,
1262
+ },
1263
+ },
1264
+ observed_at: clock.observed_at,
1265
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1266
+ clock_id: state.clock_id,
1267
+ payload: {
1268
+ tool: { namespace: input.adapter, name: span.tool_name ?? "unknown_tool" },
1269
+ outcome: "unknown",
1270
+ duration_ms: structuredClone(terminalSpan.duration_ms),
1271
+ span: terminalSpan,
1272
+ result: { storage: "omitted", media_type: "application/octet-stream", bytes: 0 },
1273
+ recovery: {
1274
+ reason,
1275
+ ...(span.requested_event_id ? { requested_event_id: span.requested_event_id } : {}),
1276
+ elapsed_upper_bound_ms: elapsedUpperBound,
1277
+ },
1278
+ },
1279
+ });
1280
+ assertEventV3(event);
1281
+ return event;
1282
+ }
1283
+ /**
1284
+ * A derived `session.started` for a live session Harnery first observed
1285
+ * mid-flight (fresh epoch or lost session-start hook). Records that the
1286
+ * session exists without claiming the adapter delivered a start signal.
1287
+ */
1288
+ function buildMidFlightSessionStart(input, state, rootId, cursorPromptBootstrap = false) {
1289
+ const fingerprintContext = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
1290
+ const event = normalizeHookEventV3("session-start", input.payload, {
1291
+ coordRoot: input.coordRoot,
1292
+ adapter: input.adapter,
1293
+ adapterVersion: input.adapterVersion,
1294
+ harnessVersion: input.harnessVersion,
1295
+ root_id: rootId,
1296
+ run_id: input.run_id,
1297
+ workflow_id: input.workflow_id,
1298
+ workflow_agent_id: input.workflow_agent_id,
1299
+ instance_id: input.instance_id,
1300
+ generation_id: state.generation_id,
1301
+ attestation_id: state.attestation_id,
1302
+ producer_id: input.producer_id,
1303
+ boot_id: state.boot_id,
1304
+ sequence: state.next_sequence,
1305
+ build_id: input.build_id,
1306
+ platform: input.platform,
1307
+ bridge: input.bridge,
1308
+ capability_profile: state.capability_profile,
1309
+ fingerprintContext,
1310
+ caused_by: state.last_event_id ? [state.last_event_id] : [],
1311
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1312
+ clock_id: state.clock_id,
1313
+ });
1314
+ if (!event)
1315
+ throw new Error("mid-flight session start could not be normalized");
1316
+ event.provenance = {
1317
+ ...event.provenance,
1318
+ attestation: "derived",
1319
+ confidence: cursorPromptBootstrap ? "high" : "medium",
1320
+ };
1321
+ event.payload.resume = {
1322
+ state: "unknown",
1323
+ reason: cursorPromptBootstrap ? "cursor_prompt_bootstrap" : "mid_flight_onboarding",
1324
+ };
1325
+ assertEventV3(event);
1326
+ return event;
1327
+ }
1328
+ /**
1329
+ * Cursor can deliver its native `beforeSubmitPrompt` signal before its
1330
+ * `sessionStart` hook. The prompt carries both conversation and turn identity,
1331
+ * so it is a high-confidence session boundary rather than degraded recovery.
1332
+ */
1333
+ function isCursorPromptBootstrap(input) {
1334
+ return (input.adapter === "cursor" &&
1335
+ input.signal === "user-prompt-submit" &&
1336
+ (input.payload.conversation_id !== undefined || input.payload.session_id !== undefined) &&
1337
+ input.payload.turn_id !== undefined);
1338
+ }
1339
+ function suppressClosedSpanSignal(input, sessionHash, category) {
1340
+ writeProducerDiagnosticV3(input.coordRoot, category, {
1341
+ adapter: input.adapter,
1342
+ instance_id: input.instance_id,
1343
+ signal: input.signal,
1344
+ session_hash: sessionHash,
1345
+ payload: input.payload,
1346
+ });
1347
+ return { state: "suppressed", reason: "closed_span" };
1348
+ }
1349
+ /**
1350
+ * End one exact live generation under the same private-state lease used by its
1351
+ * native hook producer. This is the only approved-authority terminal writer:
1352
+ * archive reconciliation, explicit `harn-end`, and policy cascades all converge
1353
+ * here so they cannot race each other or append activity after termination.
1354
+ */
1355
+ export function recordApprovedSessionEndV3(input) {
1356
+ const control = readEventV3ControlState(input.coordRoot);
1357
+ if (control.state !== input.mode) {
1358
+ return { state: "gate_closed", reason: control.state };
1359
+ }
1360
+ const matches = listHookProducerStateRecordsV3(input.coordRoot, { includeTerminal: true }).filter(({ state }) => state.instance_id === input.instance_id && state.generation_id === input.generation_id);
1361
+ if (matches.length !== 1) {
1362
+ return {
1363
+ state: "generation_unavailable",
1364
+ reason: matches.length === 0 ? "not_found" : "ambiguous",
1365
+ };
1366
+ }
1367
+ const record = matches[0];
1368
+ const lease = acquireStateLease(input.coordRoot, record.path);
1369
+ try {
1370
+ const state = readProducerState(record.path);
1371
+ if (state.instance_id !== input.instance_id || state.generation_id !== input.generation_id) {
1372
+ return { state: "generation_unavailable", reason: "authority_changed" };
1373
+ }
1374
+ let recovered = false;
1375
+ if (state.pending) {
1376
+ const pending = state.pending.event;
1377
+ const durability = writeEventV3(input.coordRoot, pending, input.writerOptions);
1378
+ applyCommittedEvent(state, pending);
1379
+ state.pending = undefined;
1380
+ publishProducerState(record.path, state);
1381
+ recovered = true;
1382
+ if (pending.event_type === "session.ended") {
1383
+ return { state: "recorded", event: pending, durability, recovered };
1384
+ }
1385
+ }
1386
+ if (state.terminal)
1387
+ return { state: "already_ended", event_id: state.last_event_id };
1388
+ if (!state.started_event_id || !state.last_event_id) {
1389
+ return { state: "generation_unavailable", reason: "session_start_missing" };
1390
+ }
1391
+ const rootId = control.genesis.event.scope.root_id;
1392
+ const context = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
1393
+ const expected = [
1394
+ "session_started",
1395
+ "turn_closed",
1396
+ "tool_spans_closed",
1397
+ "delegated_children_closed",
1398
+ "coordination_finalized",
1399
+ ];
1400
+ const observed = [
1401
+ ...(state.started_event_id ? ["session_started"] : []),
1402
+ ...(!state.current_turn_id ? ["turn_closed"] : []),
1403
+ ...(state.spans.length === 0 ? ["tool_spans_closed"] : []),
1404
+ ...(state.delegations.length === 0 ? ["delegated_children_closed"] : []),
1405
+ ...(input.coordination_finalized ? ["coordination_finalized"] : []),
1406
+ ];
1407
+ const observedSet = new Set(observed);
1408
+ const missing = expected.filter((field) => !observedSet.has(field));
1409
+ const causedBy = [
1410
+ ...new Set([state.last_event_id, input.caused_by_event_id, state.session_span.open_event_id].filter(Boolean)),
1411
+ ];
1412
+ const event = buildEventV3("session.ended", {
1413
+ producer: {
1414
+ producer_id: "prd_agent-finalizer",
1415
+ boot_id: `boot_${randomUUID()}`,
1416
+ sequence: 1,
1417
+ component: "agent-coord",
1418
+ build_id: input.build_id,
1419
+ platform: input.platform,
1420
+ },
1421
+ scope: {
1422
+ root_id: rootId,
1423
+ instance_id: state.instance_id,
1424
+ session_id: state.session_id,
1425
+ generation_id: state.generation_id,
1426
+ },
1427
+ attestation_id: state.attestation_id,
1428
+ links: { caused_by: causedBy },
1429
+ provenance: {
1430
+ source_event: `agent-coord.session-finalizer.${input.reason}`,
1431
+ attestation: "derived",
1432
+ confidence: input.confidence ?? "high",
1433
+ source_record_id: normalizeNativeIdV3(context, "agent-coord.approved-session-end", `${input.reason}\0${state.generation_id}\0${input.observed_at ?? "now"}`),
1434
+ attribution: {
1435
+ method: "explicit_argument",
1436
+ state: "verified",
1437
+ observer_instance_id: state.instance_id,
1438
+ subject_instance_id: state.instance_id,
1439
+ },
1440
+ },
1441
+ observed_at: input.observed_at,
1442
+ clock_id: state.clock_id,
1443
+ payload: {
1444
+ outcome: input.outcome,
1445
+ authority: "approved",
1446
+ reason: input.reason,
1447
+ span: closeSpanStateV3(state.session_span, {
1448
+ boot_id: state.boot_id,
1449
+ clock: { observed_at: input.observed_at ?? new Date().toISOString() },
1450
+ }),
1451
+ completeness: {
1452
+ state: "observed",
1453
+ value: { expected, observed, missing },
1454
+ attestation: "derived",
1455
+ confidence: input.confidence ?? "high",
1456
+ },
1457
+ },
1458
+ });
1459
+ markObservedClockRegressionV3(event, state.last_observed_at);
1460
+ assertEventV3(event);
1461
+ state.pending = { event };
1462
+ publishProducerState(record.path, state);
1463
+ const durability = writeEventV3(input.coordRoot, event, input.writerOptions);
1464
+ applyCommittedEvent(state, event);
1465
+ state.pending = undefined;
1466
+ publishProducerState(record.path, state);
1467
+ return { state: "recorded", event, durability, recovered };
1468
+ }
1469
+ finally {
1470
+ lease.release();
1471
+ }
1472
+ }
1473
+ /**
1474
+ * Explicit-end salvage (ADR 0078): terminalize exactly the approved open-span
1475
+ * set with derived recovery terminals so an authorized end stops wedging
1476
+ * behind spans nothing else can close. Runs under the same private-state
1477
+ * lease as the native producer; spans outside the approved set are refused by
1478
+ * the caller's eligibility gate and never touched here.
1479
+ */
1480
+ export function salvageOpenSpansV3(input) {
1481
+ const control = readEventV3ControlState(input.coordRoot);
1482
+ if (control.state !== input.mode) {
1483
+ return { state: "gate_closed", reason: control.state };
1484
+ }
1485
+ const matches = listHookProducerStateRecordsV3(input.coordRoot).filter(({ state }) => state.instance_id === input.instance_id && state.generation_id === input.generation_id);
1486
+ if (matches.length !== 1) {
1487
+ return {
1488
+ state: "generation_unavailable",
1489
+ reason: matches.length === 0 ? "not_found" : "ambiguous",
1490
+ };
1491
+ }
1492
+ const record = matches[0];
1493
+ const lease = acquireStateLeaseWithRetry(input.coordRoot, record.path, 2);
1494
+ if (!lease)
1495
+ return { state: "generation_unavailable", reason: "busy" };
1496
+ try {
1497
+ const state = readProducerState(record.path);
1498
+ if (state.instance_id !== input.instance_id ||
1499
+ state.generation_id !== input.generation_id ||
1500
+ state.terminal) {
1501
+ return { state: "generation_unavailable", reason: "authority_changed" };
1502
+ }
1503
+ const rootId = control.genesis.event.scope.root_id;
1504
+ const salvageInput = {
1505
+ coordRoot: input.coordRoot,
1506
+ mode: input.mode,
1507
+ signal: "stop",
1508
+ payload: { raw: {} },
1509
+ adapter: state.adapter,
1510
+ instance_id: state.instance_id,
1511
+ producer_id: "prd_agent-finalizer",
1512
+ build_id: input.build_id,
1513
+ platform: input.platform,
1514
+ writerOptions: input.writerOptions,
1515
+ };
1516
+ if (state.pending) {
1517
+ const pendingEvent = state.pending.event;
1518
+ writeEventV3(input.coordRoot, pendingEvent, input.writerOptions);
1519
+ applyCommittedEvent(state, pendingEvent);
1520
+ state.pending = undefined;
1521
+ publishProducerState(record.path, state);
1522
+ }
1523
+ const allowed = new Set(input.allowed_span_ids);
1524
+ const fingerprintContext = fingerprintContextV3(input.coordRoot, rootId, state.generation_id, state.privacy_epoch_id);
1525
+ // Salvage runs outside the hook producer chain: a fresh boot starting at
1526
+ // sequence 1 keeps the reader's per-(producer, boot) continuity intact.
1527
+ const salvageBoot = `boot_${randomUUID()}`;
1528
+ let salvageSequence = 1;
1529
+ let closed = 0;
1530
+ for (const span of [...state.spans]) {
1531
+ if (!allowed.has(span.span_id))
1532
+ continue;
1533
+ if (!span.turn_id) {
1534
+ // An unstamped span still needs a turn scope; the requested turn is
1535
+ // the only honest owner the explicit end named, and a salvage-scoped
1536
+ // synthetic id is the last resort.
1537
+ span.turn_id =
1538
+ input.requested_turn_id ??
1539
+ `tid_${normalizeNativeIdV3(fingerprintContext, `${state.adapter}.turn`, `salvage:${span.span_id}`).slice(4)}`;
1540
+ }
1541
+ const event = buildDerivedToolCompleted(salvageInput, state, rootId, span, "explicit_end_salvage", { producer_id: "prd_agent-finalizer", boot_id: salvageBoot, sequence: salvageSequence }, input.observed_at);
1542
+ salvageSequence += 1;
1543
+ commitEventLocked(salvageInput, state, record.path, event);
1544
+ closed += 1;
1545
+ }
1546
+ return { state: "salvaged", closed };
1547
+ }
1548
+ finally {
1549
+ lease.release();
1550
+ }
1551
+ }
1552
+ export function listHookProducerStateRecordsV3(coordRoot, options = {}) {
1553
+ const control = readEventV3ControlState(coordRoot);
1554
+ if (control.state !== "candidate" && control.state !== "active")
1555
+ return [];
1556
+ const producerRoot = join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers");
1557
+ if (!existsSync(producerRoot))
1558
+ return [];
1559
+ const records = [];
1560
+ for (const adapter of ["claude-code", "codex", "cursor"]) {
1561
+ const directory = join(producerRoot, adapter);
1562
+ if (!existsSync(directory))
1563
+ continue;
1564
+ const metadata = lstatSync(directory);
1565
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
1566
+ throw new Error("V3 producer state directory is unsafe");
1567
+ }
1568
+ for (const name of readdirSync(directory).filter((entry) => /^hid_[a-f0-9]{64}\.json$/.test(entry))) {
1569
+ const path = join(directory, name);
1570
+ const state = readProducerState(path);
1571
+ if (!options.includeTerminal && state.terminal)
1572
+ continue;
1573
+ records.push({ path, modified_at_ms: lstatSync(path).mtimeMs, state });
1574
+ }
1575
+ }
1576
+ return records.sort((left, right) => left.state.generation_id.localeCompare(right.state.generation_id) ||
1577
+ left.path.localeCompare(right.path));
1578
+ }
1579
+ function hookSignalCapability(signal) {
1580
+ switch (signal) {
1581
+ case "session-start":
1582
+ return "session_start";
1583
+ case "session-end":
1584
+ return "session_end";
1585
+ case "user-prompt-submit":
1586
+ return "prompt";
1587
+ case "stop":
1588
+ case "stop-failure":
1589
+ return "turn_completion";
1590
+ case "pre-tool-use":
1591
+ return "tool_request";
1592
+ case "post-tool-use":
1593
+ return "tool_result";
1594
+ case "post-tool-use-failure":
1595
+ return "tool_failure";
1596
+ case "permission-request":
1597
+ return "permission";
1598
+ case "sub-agent-start":
1599
+ case "sub-agent-stop":
1600
+ return "subagent";
1601
+ case "pre-compact":
1602
+ return "pre_compaction";
1603
+ case "post-compact":
1604
+ return "post_compaction";
1605
+ }
1606
+ }
1607
+ export function readHookProducerStateV3(coordRoot, adapter, nativeSessionId) {
1608
+ const control = readEventV3ControlState(coordRoot);
1609
+ if (control.state !== "candidate" && control.state !== "active")
1610
+ return undefined;
1611
+ const rootId = control.genesis.event.scope.root_id;
1612
+ const context = fingerprintContextV3(coordRoot, rootId, undefined, control.genesis.profile.privacy_key_epoch);
1613
+ const sessionHash = normalizeNativeIdV3(context, `${adapter}.session`, nativeSessionId);
1614
+ const path = producerStatePath(coordRoot, adapter, sessionHash);
1615
+ return existsSync(path) ? readProducerState(path) : undefined;
1616
+ }
1617
+ /** Resolve one terminal producer only when native session and instance identity agree. */
1618
+ export function readTerminalHookProducerStateV3(coordRoot, nativeSessionId, instanceId) {
1619
+ const matches = ["claude-code", "codex", "cursor"]
1620
+ .map((adapter) => readHookProducerStateV3(coordRoot, adapter, nativeSessionId))
1621
+ .filter((state) => state?.terminal === true && state.instance_id === instanceId);
1622
+ return matches.length === 1 ? matches[0] : undefined;
1623
+ }
1624
+ export function readHookProducerStateByInstanceV3(coordRoot, instanceId) {
1625
+ const control = readEventV3ControlState(coordRoot);
1626
+ if (control.state !== "candidate" && control.state !== "active")
1627
+ return undefined;
1628
+ const producerRoot = join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers");
1629
+ if (!existsSync(producerRoot))
1630
+ return undefined;
1631
+ const matches = [];
1632
+ for (const adapter of ["claude-code", "codex", "cursor"]) {
1633
+ const directory = join(producerRoot, adapter);
1634
+ if (!existsSync(directory))
1635
+ continue;
1636
+ const metadata = lstatSync(directory);
1637
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) {
1638
+ throw new Error("V3 producer state directory is unsafe");
1639
+ }
1640
+ for (const name of readdirSync(directory).filter((entry) => /^hid_[a-f0-9]{64}\.json$/.test(entry))) {
1641
+ const state = readProducerState(join(directory, name));
1642
+ if (state.instance_id === instanceId && !state.terminal)
1643
+ matches.push(state);
1644
+ }
1645
+ }
1646
+ return matches.length === 1 ? matches[0] : undefined;
1647
+ }
1648
+ function newProducerState(input, sessionId, epochId, boundaryEventId) {
1649
+ const bootId = `boot_${randomUUID()}`;
1650
+ return {
1651
+ format: STATE_FORMAT,
1652
+ format_version: STATE_VERSION,
1653
+ adapter: input.adapter,
1654
+ instance_id: input.instance_id,
1655
+ session_id: sessionId,
1656
+ generation_id: generationIdV3(),
1657
+ attestation_id: attestationIdV3(),
1658
+ capability_profile: adapterCapabilityProfileDigestV3(input.adapter),
1659
+ cursor_mode: input.adapter === "cursor" ? (input.payload.cursor_mode ?? "unknown") : undefined,
1660
+ privacy_epoch_id: epochId,
1661
+ boot_id: bootId,
1662
+ clock_id: clockIdV3(),
1663
+ next_sequence: 1,
1664
+ tool_call_count: 0,
1665
+ last_event_id: boundaryEventId,
1666
+ session_span: openSpanStateV3({
1667
+ span_id: spanIdV3(),
1668
+ boot_id: bootId,
1669
+ clock: signalClock(input),
1670
+ }),
1671
+ terminal: false,
1672
+ spans: [],
1673
+ delegations: [],
1674
+ closed_spans: [],
1675
+ closed_turn_ids: [],
1676
+ waits: [],
1677
+ turn_harness: emptyTurnHarnessTiming(),
1678
+ turn_ordinal: 0,
1679
+ };
1680
+ }
1681
+ function applyCommittedEvent(state, event) {
1682
+ // Sequence continuity is keyed on (producer_id, boot_id): only events that
1683
+ // ride the state's own producer chain advance it. Finalizer-authored events
1684
+ // (fresh boot, sequence 1) must not create gaps in the hook chain.
1685
+ if (event.producer.boot_id === state.boot_id)
1686
+ state.next_sequence += 1;
1687
+ if (event.time.monotonic_ns)
1688
+ state.last_monotonic_ns = event.time.monotonic_ns;
1689
+ // Mirrors the reader, which tracks the last observed value per clock even
1690
+ // when that value regressed. Holding the maximum instead would hide a
1691
+ // second regression behind the first.
1692
+ state.last_observed_at = event.time.observed_at;
1693
+ state.last_event_id = event.event_id;
1694
+ if (event.event_type === "session.started") {
1695
+ state.started_event_id = event.event_id;
1696
+ state.session_span.open_event_id = event.event_id;
1697
+ }
1698
+ if (event.event_type === "turn.started") {
1699
+ const nextTurnId = event.scope.turn_id;
1700
+ state.current_turn_id = nextTurnId;
1701
+ if (state.tool_call_count_turn_id !== nextTurnId) {
1702
+ state.tool_call_count = 0;
1703
+ state.tool_call_count_turn_id = nextTurnId;
1704
+ }
1705
+ if (state.current_turn_span) {
1706
+ state.current_turn_span.open_event_id = event.event_id;
1707
+ }
1708
+ state.turn_ordinal += 1;
1709
+ state.closed_spans = state.closed_spans.filter((closed) => closed.turn_ordinal >= state.turn_ordinal - CLOSED_SPAN_TURN_RETENTION);
1710
+ }
1711
+ if (event.event_type === "tool.requested") {
1712
+ const requestTurnId = event.scope.turn_id;
1713
+ if (state.tool_call_count_turn_id !== requestTurnId) {
1714
+ state.tool_call_count = 0;
1715
+ state.tool_call_count_turn_id = requestTurnId;
1716
+ }
1717
+ state.tool_call_count += 1;
1718
+ }
1719
+ if (event.event_type === "wait.started") {
1720
+ const waitId = event.payload.wait_id;
1721
+ const turnId = event.scope.turn_id;
1722
+ const waitLinks = event.links;
1723
+ if (!state.waits.some((wait) => wait.wait_id === waitId)) {
1724
+ state.waits.push({
1725
+ wait_id: waitId,
1726
+ started_event_id: event.event_id,
1727
+ turn_id: turnId,
1728
+ span_id: waitLinks.span_id,
1729
+ ...(waitLinks.parent_span_id ? { parent_span_id: waitLinks.parent_span_id } : {}),
1730
+ opened_at: event.time.observed_at,
1731
+ boot_id: state.boot_id,
1732
+ ...(event.time.monotonic_ns ? { opened_monotonic_ns: event.time.monotonic_ns } : {}),
1733
+ open_event_id: event.event_id,
1734
+ });
1735
+ }
1736
+ }
1737
+ if (event.event_type === "wait.ended") {
1738
+ const waitId = event.payload.wait_id;
1739
+ state.waits = state.waits.filter((wait) => wait.wait_id !== waitId);
1740
+ }
1741
+ if (event.event_type === "tool.completed") {
1742
+ const completedSpan = event.links.span_id;
1743
+ const closing = state.spans.find((span) => span.span_id === completedSpan);
1744
+ if (closing) {
1745
+ state.closed_spans.push({
1746
+ source_id: closing.source_id,
1747
+ ...(closing.semantic_key ? { semantic_key: closing.semantic_key } : {}),
1748
+ span_id: closing.span_id,
1749
+ closed_event_id: event.event_id,
1750
+ turn_ordinal: state.turn_ordinal,
1751
+ });
1752
+ if (state.closed_spans.length > CLOSED_SPAN_MEMORY_CAP) {
1753
+ state.closed_spans = state.closed_spans.slice(-CLOSED_SPAN_MEMORY_CAP);
1754
+ }
1755
+ }
1756
+ state.spans = state.spans.filter((span) => span.span_id !== completedSpan);
1757
+ }
1758
+ if (event.event_type === "agent.completed") {
1759
+ const completedDelegation = event.payload.delegation_id;
1760
+ state.delegations = state.delegations.filter((delegation) => delegation.delegation_id !== completedDelegation);
1761
+ }
1762
+ if (event.event_type === "agent.started") {
1763
+ const openedDelegation = state.delegations.find((candidate) => candidate.delegation_id === event.payload.delegation_id);
1764
+ if (openedDelegation)
1765
+ openedDelegation.open_event_id = event.event_id;
1766
+ }
1767
+ if (event.event_type === "turn.completed") {
1768
+ const completedTurnId = event.scope.turn_id;
1769
+ if (!state.closed_turn_ids.includes(completedTurnId)) {
1770
+ state.closed_turn_ids.push(completedTurnId);
1771
+ if (state.closed_turn_ids.length > CLOSED_TURN_MEMORY_CAP) {
1772
+ state.closed_turn_ids = state.closed_turn_ids.slice(-CLOSED_TURN_MEMORY_CAP);
1773
+ }
1774
+ }
1775
+ state.current_turn_id = undefined;
1776
+ state.current_turn_span = undefined;
1777
+ state.tool_call_count = 0;
1778
+ state.tool_call_count_turn_id = undefined;
1779
+ state.turn_harness = emptyTurnHarnessTiming();
1780
+ }
1781
+ if (event.event_type === "session.ended")
1782
+ state.terminal = true;
1783
+ }
1784
+ function unpairableTool(input, sessionHash, reason) {
1785
+ writeProducerDiagnosticV3(input.coordRoot, "unpairable_tool", {
1786
+ reason,
1787
+ adapter: input.adapter,
1788
+ instance_id: input.instance_id,
1789
+ signal: input.signal,
1790
+ session_hash: sessionHash,
1791
+ payload: input.payload,
1792
+ });
1793
+ return { state: "unpairable_tool", reason };
1794
+ }
1795
+ function sourceIdForSignal(input, context) {
1796
+ const toolSignal = input.signal === "pre-tool-use" ||
1797
+ input.signal === "post-tool-use" ||
1798
+ input.signal === "post-tool-use-failure";
1799
+ const subagentSignal = input.signal === "sub-agent-start" || input.signal === "sub-agent-stop";
1800
+ const native = toolSignal
1801
+ ? input.payload.tool_use_id
1802
+ : subagentSignal
1803
+ ? (input.payload.subagent_id ?? input.payload.agent_id)
1804
+ : (input.payload.turn_id ??
1805
+ (input.signal === "session-start"
1806
+ ? (input.payload.session_id ?? input.payload.conversation_id ?? input.payload.agent_id)
1807
+ : undefined));
1808
+ return native
1809
+ ? normalizeNativeIdV3(context, `${input.adapter}.hook-source`, `${toolSignal ? "tool" : subagentSignal ? "subagent" : input.signal}:${native}`)
1810
+ : undefined;
1811
+ }
1812
+ function cursorShellSemanticKey(input, context) {
1813
+ if (input.adapter !== "cursor")
1814
+ return undefined;
1815
+ const command = extractBashCommand(input.payload.tool_name, input.payload.tool_input);
1816
+ return command
1817
+ ? normalizeNativeIdV3(context, "cursor.shell-operation", command.normalize("NFC"))
1818
+ : undefined;
1819
+ }
1820
+ function cursorShellFallbackSourceId(semanticKey, state, context) {
1821
+ return normalizeNativeIdV3(context, "cursor.hook-source", `shell:${semanticKey}:${state.turn_ordinal}:${state.tool_call_count}`);
1822
+ }
1823
+ function closeResolvedWaits(input, state, path, rootId, fingerprintContext, nativeTid) {
1824
+ const toolResolution = input.signal === "pre-tool-use" ||
1825
+ input.signal === "post-tool-use" ||
1826
+ input.signal === "post-tool-use-failure";
1827
+ const resolvedWaitId = toolResolution ? waitIdForInput(input, fingerprintContext) : undefined;
1828
+ const endingTurnId = input.signal === "stop" || input.signal === "stop-failure"
1829
+ ? (nativeTid ?? state.current_turn_id)
1830
+ : undefined;
1831
+ let waits = state.waits.filter((wait) => wait.wait_id === resolvedWaitId || wait.turn_id === endingTurnId);
1832
+ if (endingTurnId && waits.length === 0)
1833
+ waits = [...state.waits];
1834
+ if (toolResolution && waits.length === 0 && state.current_turn_id) {
1835
+ const turnWaits = state.waits.filter((wait) => wait.turn_id === state.current_turn_id);
1836
+ if (turnWaits.length === 1)
1837
+ waits = turnWaits;
1838
+ }
1839
+ for (const wait of waits) {
1840
+ const outcome = endingTurnId
1841
+ ? "interrupted"
1842
+ : input.signal === "post-tool-use-failure"
1843
+ ? "denied"
1844
+ : "succeeded";
1845
+ const event = buildEventV3("wait.ended", {
1846
+ producer: {
1847
+ producer_id: input.producer_id,
1848
+ boot_id: state.boot_id,
1849
+ sequence: state.next_sequence,
1850
+ component: "agent-hook",
1851
+ build_id: input.build_id,
1852
+ platform: input.platform,
1853
+ ...(input.bridge ? { bridge: input.bridge } : {}),
1854
+ },
1855
+ scope: {
1856
+ root_id: rootId,
1857
+ instance_id: state.instance_id,
1858
+ session_id: state.session_id,
1859
+ generation_id: state.generation_id,
1860
+ turn_id: wait.turn_id,
1861
+ ...(input.run_id ? { run_id: input.run_id } : {}),
1862
+ ...(input.workflow_id ? { workflow_id: input.workflow_id } : {}),
1863
+ ...(input.workflow_agent_id ? { workflow_agent_id: input.workflow_agent_id } : {}),
1864
+ },
1865
+ attestation_id: state.attestation_id,
1866
+ links: { caused_by: [wait.started_event_id] },
1867
+ provenance: {
1868
+ source_event: `${input.adapter}.permission-resolution`,
1869
+ attestation: "derived",
1870
+ confidence: "high",
1871
+ attribution: {
1872
+ method: "native_payload",
1873
+ state: "verified",
1874
+ subject_instance_id: state.instance_id,
1875
+ },
1876
+ },
1877
+ monotonic_ns: orderedEventMonotonic(state, input.monotonic_ns),
1878
+ clock_id: state.clock_id,
1879
+ payload: {
1880
+ wait_id: wait.wait_id,
1881
+ outcome,
1882
+ resolution_reference: endingTurnId ? "turn_terminal" : input.signal,
1883
+ span: closeSpanStateV3(wait, {
1884
+ boot_id: state.boot_id,
1885
+ clock: signalClock(input),
1886
+ }),
1887
+ },
1888
+ });
1889
+ commitEventLocked(input, state, path, event);
1890
+ }
1891
+ }
1892
+ function waitIdForInput(input, fingerprintContext) {
1893
+ return input.payload.tool_use_id
1894
+ ? normalizeNativeIdV3(fingerprintContext, `${input.adapter}.wait`, input.payload.tool_use_id)
1895
+ : undefined;
1896
+ }
1897
+ function signalClock(input) {
1898
+ return {
1899
+ observed_at: input.observed_at ?? new Date().toISOString(),
1900
+ ...(input.monotonic_ns ? { monotonic_ns: input.monotonic_ns } : {}),
1901
+ };
1902
+ }
1903
+ function durationMilliseconds(start, end) {
1904
+ if (!start || !end || !/^\d+$/.test(start) || !/^\d+$/.test(end))
1905
+ return undefined;
1906
+ const delta = BigInt(end) - BigInt(start);
1907
+ if (delta < 0n)
1908
+ return undefined;
1909
+ const milliseconds = Number(delta / 1000000n);
1910
+ return Number.isSafeInteger(milliseconds) ? milliseconds : undefined;
1911
+ }
1912
+ function safeRole(value) {
1913
+ if (typeof value !== "string")
1914
+ return "agent";
1915
+ const normalized = value
1916
+ .normalize("NFC")
1917
+ .replace(/[^a-zA-Z0-9._:/+-]/g, "_")
1918
+ .slice(0, 128);
1919
+ return /^[a-zA-Z0-9]/.test(normalized) ? normalized : "agent";
1920
+ }
1921
+ function safeTokenOrUndefined(value) {
1922
+ if (typeof value !== "string")
1923
+ return undefined;
1924
+ const normalized = value.normalize("NFC");
1925
+ return /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(normalized) ? normalized : undefined;
1926
+ }
1927
+ function producerStatePath(coordRoot, adapter, sessionHash) {
1928
+ return join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers", adapter, `${sessionHash}.json`);
1929
+ }
1930
+ function acquireStateLease(coordRoot, statePath) {
1931
+ const directory = join(statePath, "..");
1932
+ const producerRoot = join(resolve(coordRoot), EVENT_V3_LEDGER_RELATIVE_ROOT, "private-producers");
1933
+ mkdirSync(directory, { recursive: true, mode: 0o700 });
1934
+ chmodSync(producerRoot, 0o700);
1935
+ chmodSync(directory, 0o700);
1936
+ return acquireNoClobberLease({
1937
+ path: `${statePath}.lease`,
1938
+ scope: "event-v3-hook-producer",
1939
+ authoritySha256: createHash("sha256")
1940
+ .update(resolve(coordRoot))
1941
+ .update("\0")
1942
+ .update(statePath)
1943
+ .digest("hex"),
1944
+ staleAfterMs: 5_000,
1945
+ validateStaleOwner: (owner) => owner.host === hostname() && !pidIsAlive(owner.pid),
1946
+ });
1947
+ }
1948
+ function publishProducerState(path, state) {
1949
+ const temporary = `${path}.tmp-${process.pid}-${randomUUID()}`;
1950
+ let fd;
1951
+ try {
1952
+ fd = openSync(temporary, "wx", 0o600);
1953
+ writeFileSync(fd, `${JSON.stringify(state)}\n`, "utf8");
1954
+ fsyncSync(fd);
1955
+ closeSync(fd);
1956
+ fd = undefined;
1957
+ renameSync(temporary, path);
1958
+ chmodSync(path, 0o600);
1959
+ fsyncParentDirectory(path);
1960
+ }
1961
+ finally {
1962
+ if (fd !== undefined)
1963
+ closeSync(fd);
1964
+ if (existsSync(temporary))
1965
+ unlinkSync(temporary);
1966
+ }
1967
+ }
1968
+ function readProducerState(path) {
1969
+ const metadata = lstatSync(path);
1970
+ if (!metadata.isFile() || metadata.isSymbolicLink()) {
1971
+ throw new Error("V3 producer state path is unsafe");
1972
+ }
1973
+ if ((metadata.mode & 0o077) !== 0)
1974
+ throw new Error("V3 producer state is not owner-only");
1975
+ let parsed;
1976
+ try {
1977
+ parsed = JSON.parse(readFileSync(path, "utf8"));
1978
+ }
1979
+ catch {
1980
+ throw new Error("V3 producer state is unreadable");
1981
+ }
1982
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1983
+ throw new Error("V3 producer state is invalid");
1984
+ }
1985
+ const state = parsed;
1986
+ // In-place additive upgrade from format 1 (pre-ADR-0078): the new fields
1987
+ // default empty; an old build reading a version-2 file still throws, and its
1988
+ // signal is already durable in the intake spool.
1989
+ if (state.format_version === 1 && state.format === STATE_FORMAT) {
1990
+ state.format_version = STATE_VERSION;
1991
+ state.closed_spans ??= [];
1992
+ state.turn_ordinal ??= 0;
1993
+ }
1994
+ // Additive format-2 state: existing files predate wait tracking but remain
1995
+ // valid and acquire an empty set on their next read.
1996
+ state.waits ??= [];
1997
+ state.closed_turn_ids ??= [];
1998
+ state.turn_harness ??= emptyTurnHarnessTiming();
1999
+ if (state.adapter === "cursor")
2000
+ state.cursor_mode ??= "unknown";
2001
+ const allowedKeys = new Set([
2002
+ "adapter",
2003
+ "attestation_id",
2004
+ "boot_id",
2005
+ "capability_profile",
2006
+ "clock_id",
2007
+ "closed_spans",
2008
+ "closed_turn_ids",
2009
+ "current_turn_id",
2010
+ "current_turn_span",
2011
+ "cursor_mode",
2012
+ "delegations",
2013
+ "format",
2014
+ "format_version",
2015
+ "generation_id",
2016
+ "instance_id",
2017
+ "last_event_id",
2018
+ "last_monotonic_ns",
2019
+ "last_observed_at",
2020
+ "next_sequence",
2021
+ "pending",
2022
+ "privacy_epoch_id",
2023
+ "session_id",
2024
+ "session_span",
2025
+ "spans",
2026
+ "started_event_id",
2027
+ "terminal",
2028
+ "tool_call_count",
2029
+ "tool_call_count_turn_id",
2030
+ "turn_harness",
2031
+ "turn_ordinal",
2032
+ "waits",
2033
+ ]);
2034
+ if (Object.keys(state).some((key) => !allowedKeys.has(key)) ||
2035
+ state.format !== STATE_FORMAT ||
2036
+ state.format_version !== STATE_VERSION ||
2037
+ !["claude-code", "codex", "cursor"].includes(state.adapter) ||
2038
+ (state.cursor_mode !== undefined &&
2039
+ !["local", "cloud", "unknown"].includes(state.cursor_mode)) ||
2040
+ !/^inst_[a-zA-Z0-9._-]{1,128}$/.test(state.instance_id) ||
2041
+ !/^sid_[a-f0-9]{64}$/.test(state.session_id) ||
2042
+ !/^gen_[0-9a-f-]{36}$/.test(state.generation_id) ||
2043
+ !/^att_[0-9a-f-]{36}$/.test(state.attestation_id) ||
2044
+ !/^cap_[a-f0-9]{64}$/.test(state.capability_profile) ||
2045
+ !/^pep_[a-zA-Z0-9._-]+$/.test(state.privacy_epoch_id) ||
2046
+ !/^boot_[a-zA-Z0-9._-]+$/.test(state.boot_id) ||
2047
+ !/^clk_[0-9a-f-]{36}$/.test(state.clock_id) ||
2048
+ !Number.isSafeInteger(state.next_sequence) ||
2049
+ state.next_sequence < 1 ||
2050
+ !Number.isSafeInteger(state.tool_call_count) ||
2051
+ state.tool_call_count < 0 ||
2052
+ (state.tool_call_count_turn_id !== undefined &&
2053
+ !/^tid_[a-f0-9]{64}$/.test(state.tool_call_count_turn_id)) ||
2054
+ !validTurnHarnessTiming(state.turn_harness) ||
2055
+ !validOpenSpanState(state.session_span) ||
2056
+ (state.current_turn_span !== undefined && !validOpenSpanState(state.current_turn_span)) ||
2057
+ typeof state.terminal !== "boolean" ||
2058
+ !Array.isArray(state.spans) ||
2059
+ !Array.isArray(state.delegations) ||
2060
+ state.delegations.length > 256 ||
2061
+ state.delegations.some((delegation) => !/^hid_[a-f0-9]{64}$/.test(delegation.source_id) ||
2062
+ !/^del_[0-9a-f-]{36}$/.test(delegation.delegation_id) ||
2063
+ !/^gen_[0-9a-f-]{36}$/.test(delegation.child_generation_id) ||
2064
+ !validOpenSpanState(delegation) ||
2065
+ !/^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(delegation.role)) ||
2066
+ state.spans.length > 256 ||
2067
+ state.spans.some((span) => !/^hid_[a-f0-9]{64}$/.test(span.source_id) ||
2068
+ !validOpenSpanState(span) ||
2069
+ (span.recovery_reason !== undefined &&
2070
+ !/^[a-z0-9][a-z0-9._-]{0,79}$/.test(span.recovery_reason)) ||
2071
+ (span.turn_id !== undefined && !/^tid_[a-f0-9]{64}$/.test(span.turn_id)) ||
2072
+ (span.turn_stamp !== undefined &&
2073
+ span.turn_stamp !== "native_payload" &&
2074
+ span.turn_stamp !== "producer_state") ||
2075
+ (span.requested_event_id !== undefined &&
2076
+ !/^evt_[0-9a-f-]{36}$/.test(span.requested_event_id)) ||
2077
+ (span.semantic_key !== undefined && !/^hid_[a-f0-9]{64}$/.test(span.semantic_key)) ||
2078
+ (span.tool_name !== undefined &&
2079
+ !/^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(span.tool_name))) ||
2080
+ !Array.isArray(state.closed_spans) ||
2081
+ state.closed_spans.length > CLOSED_SPAN_MEMORY_CAP ||
2082
+ state.closed_spans.some((closed) => !/^hid_[a-f0-9]{64}$/.test(closed.source_id) ||
2083
+ (closed.semantic_key !== undefined && !/^hid_[a-f0-9]{64}$/.test(closed.semantic_key)) ||
2084
+ !/^span_[0-9a-f-]{36}$/.test(closed.span_id) ||
2085
+ !/^evt_[0-9a-f-]{36}$/.test(closed.closed_event_id) ||
2086
+ !Number.isSafeInteger(closed.turn_ordinal) ||
2087
+ closed.turn_ordinal < 0) ||
2088
+ !Array.isArray(state.closed_turn_ids) ||
2089
+ state.closed_turn_ids.length > CLOSED_TURN_MEMORY_CAP ||
2090
+ state.closed_turn_ids.some((turnId) => !/^tid_[a-f0-9]{64}$/.test(turnId)) ||
2091
+ !Array.isArray(state.waits) ||
2092
+ state.waits.length > 256 ||
2093
+ state.waits.some((wait) => !/^hid_[a-f0-9]{64}$/.test(wait.wait_id) ||
2094
+ !/^evt_[0-9a-f-]{36}$/.test(wait.started_event_id) ||
2095
+ !/^tid_[a-f0-9]{64}$/.test(wait.turn_id) ||
2096
+ !validOpenSpanState(wait)) ||
2097
+ !Number.isSafeInteger(state.turn_ordinal) ||
2098
+ state.turn_ordinal < 0 ||
2099
+ (state.current_turn_id !== undefined && !/^tid_[a-f0-9]{64}$/.test(state.current_turn_id)) ||
2100
+ (state.last_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.last_event_id)) ||
2101
+ (state.last_monotonic_ns !== undefined && !/^\d+$/.test(state.last_monotonic_ns)) ||
2102
+ (state.last_observed_at !== undefined &&
2103
+ !Number.isFinite(Date.parse(state.last_observed_at))) ||
2104
+ (state.started_event_id !== undefined && !/^evt_[0-9a-f-]{36}$/.test(state.started_event_id)) ||
2105
+ (state.pending?.source_id !== undefined &&
2106
+ !/^hid_[a-f0-9]{64}$/.test(state.pending.source_id)) ||
2107
+ (state.pending && !validateEventV3(state.pending.event).ok)) {
2108
+ throw new Error("V3 producer state is invalid");
2109
+ }
2110
+ return state;
2111
+ }
2112
+ function recordTurnHarnessTiming(state, input, resetForTurnStart = true) {
2113
+ if (input.signal === "user-prompt-submit" && resetForTurnStart) {
2114
+ state.turn_harness = emptyTurnHarnessTiming();
2115
+ }
2116
+ if (input.hook_duration_ms === undefined ||
2117
+ (!state.current_turn_id && input.signal !== "user-prompt-submit")) {
2118
+ return;
2119
+ }
2120
+ const duration = Math.max(0, Math.floor(input.hook_duration_ms));
2121
+ if (!Number.isSafeInteger(duration))
2122
+ return;
2123
+ const hook = safeRole(input.hook_name ?? input.signal);
2124
+ state.turn_harness = {
2125
+ hook_time_ms: state.turn_harness.hook_time_ms + duration,
2126
+ hook_count: state.turn_harness.hook_count + 1,
2127
+ slowest_hook: duration >= state.turn_harness.slowest_hook_ms ? hook : state.turn_harness.slowest_hook,
2128
+ slowest_hook_ms: Math.max(duration, state.turn_harness.slowest_hook_ms),
2129
+ };
2130
+ }
2131
+ /**
2132
+ * Concurrent hook processes can acquire the session lease in a different
2133
+ * order from their clock capture. Preserve raw readings in span state for
2134
+ * pairing, but omit an out-of-order reading from the producer event chain so
2135
+ * a valid global clock is never asserted falsely.
2136
+ */
2137
+ function orderedEventMonotonic(state, candidate) {
2138
+ if (!candidate || !/^\d+$/.test(candidate))
2139
+ return undefined;
2140
+ if (!state.last_monotonic_ns)
2141
+ return candidate;
2142
+ return BigInt(candidate) < BigInt(state.last_monotonic_ns) ? undefined : candidate;
2143
+ }
2144
+ function emptyTurnHarnessTiming() {
2145
+ return { hook_time_ms: 0, hook_count: 0, slowest_hook_ms: 0 };
2146
+ }
2147
+ function validOpenSpanState(value) {
2148
+ return Boolean(value &&
2149
+ /^span_[0-9a-f-]{36}$/.test(value.span_id) &&
2150
+ (value.parent_span_id === undefined || /^span_[0-9a-f-]{36}$/.test(value.parent_span_id)) &&
2151
+ Number.isFinite(Date.parse(value.opened_at)) &&
2152
+ /^boot_[a-zA-Z0-9._-]+$/.test(value.boot_id) &&
2153
+ (value.opened_monotonic_ns === undefined || /^\d+$/.test(value.opened_monotonic_ns)) &&
2154
+ (value.open_event_id === undefined || /^evt_[0-9a-f-]{36}$/.test(value.open_event_id)));
2155
+ }
2156
+ function validTurnHarnessTiming(value) {
2157
+ return (Number.isSafeInteger(value.hook_time_ms) &&
2158
+ value.hook_time_ms >= 0 &&
2159
+ Number.isSafeInteger(value.hook_count) &&
2160
+ value.hook_count >= 0 &&
2161
+ Number.isSafeInteger(value.slowest_hook_ms) &&
2162
+ value.slowest_hook_ms >= 0 &&
2163
+ (value.slowest_hook === undefined ||
2164
+ /^[a-zA-Z0-9][a-zA-Z0-9._:/+-]{0,127}$/.test(value.slowest_hook)));
2165
+ }
2166
+ function pidIsAlive(pid) {
2167
+ if (!Number.isSafeInteger(pid) || pid < 1)
2168
+ return false;
2169
+ try {
2170
+ process.kill(pid, 0);
2171
+ return true;
2172
+ }
2173
+ catch (error) {
2174
+ return error.code !== "ESRCH";
2175
+ }
2176
+ }