harnery 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (665) hide show
  1. package/README.md +2 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +7 -1
  4. package/dist/commands/adapter.js +2 -2
  5. package/dist/commands/agents.d.ts +144 -1
  6. package/dist/commands/agents.d.ts.map +1 -1
  7. package/dist/commands/agents.js +1408 -565
  8. package/dist/commands/artifacts.d.ts.map +1 -1
  9. package/dist/commands/artifacts.js +7 -6
  10. package/dist/commands/backup.d.ts +1 -1
  11. package/dist/commands/backup.d.ts.map +1 -1
  12. package/dist/commands/backup.js +29 -16
  13. package/dist/commands/browse.d.ts.map +1 -1
  14. package/dist/commands/browse.js +234 -12
  15. package/dist/commands/checkpoint.d.ts.map +1 -1
  16. package/dist/commands/checkpoint.js +3 -2
  17. package/dist/commands/claude-desktop.d.ts +2 -2
  18. package/dist/commands/claude-desktop.d.ts.map +1 -1
  19. package/dist/commands/claude-desktop.js +2 -2
  20. package/dist/commands/context.js +1 -6
  21. package/dist/commands/decision.d.ts.map +1 -1
  22. package/dist/commands/decision.js +22 -38
  23. package/dist/commands/docs.d.ts.map +1 -1
  24. package/dist/commands/docs.js +91 -28
  25. package/dist/commands/doctor.d.ts +4 -2
  26. package/dist/commands/doctor.d.ts.map +1 -1
  27. package/dist/commands/doctor.js +55 -7
  28. package/dist/commands/events.d.ts +5 -0
  29. package/dist/commands/events.d.ts.map +1 -0
  30. package/dist/commands/events.js +189 -0
  31. package/dist/commands/governor.d.ts.map +1 -1
  32. package/dist/commands/governor.js +13 -4
  33. package/dist/commands/init.d.ts +10 -2
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +233 -51
  36. package/dist/commands/instructions.d.ts +4 -0
  37. package/dist/commands/instructions.d.ts.map +1 -0
  38. package/dist/commands/instructions.js +100 -0
  39. package/dist/commands/journal.d.ts.map +1 -1
  40. package/dist/commands/journal.js +4 -13
  41. package/dist/commands/ledger-v3.d.ts +5 -0
  42. package/dist/commands/ledger-v3.d.ts.map +1 -0
  43. package/dist/commands/ledger-v3.js +91 -0
  44. package/dist/commands/presence.d.ts.map +1 -1
  45. package/dist/commands/presence.js +15 -6
  46. package/dist/commands/sync.d.ts +1 -1
  47. package/dist/commands/sync.js +1 -1
  48. package/dist/commands/web.d.ts +6 -0
  49. package/dist/commands/web.d.ts.map +1 -1
  50. package/dist/commands/web.js +67 -7
  51. package/dist/commands/work.d.ts.map +1 -1
  52. package/dist/commands/work.js +22 -5
  53. package/dist/core/adapter.d.ts +5 -0
  54. package/dist/core/adapter.d.ts.map +1 -0
  55. package/dist/core/adapter.js +1 -0
  56. package/dist/core/adapters/attest.d.ts +5 -2
  57. package/dist/core/adapters/attest.d.ts.map +1 -1
  58. package/dist/core/adapters/attest.js +8 -3
  59. package/dist/core/adapters/profiles.d.ts +6 -6
  60. package/dist/core/adapters/profiles.d.ts.map +1 -1
  61. package/dist/core/adapters/profiles.js +4 -4
  62. package/dist/core/agents/canonical-emit.d.ts +17 -15
  63. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  64. package/dist/core/agents/canonical-emit.js +27 -25
  65. package/dist/core/agents/cli.js +212 -235
  66. package/dist/core/agents/codex-archive-v3.d.ts +14 -0
  67. package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
  68. package/dist/core/agents/codex-archive-v3.js +110 -0
  69. package/dist/core/agents/coord-client.d.ts +19 -70
  70. package/dist/core/agents/coord-client.d.ts.map +1 -1
  71. package/dist/core/agents/coord-client.js +65 -155
  72. package/dist/core/agents/finalization.d.ts +4 -1
  73. package/dist/core/agents/finalization.d.ts.map +1 -1
  74. package/dist/core/agents/finalization.js +89 -32
  75. package/dist/core/agents/index.d.ts +5 -5
  76. package/dist/core/agents/index.d.ts.map +1 -1
  77. package/dist/core/agents/index.js +5 -5
  78. package/dist/core/agents/live-authority-v3.d.ts +59 -0
  79. package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
  80. package/dist/core/agents/live-authority-v3.js +233 -0
  81. package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
  82. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
  83. package/dist/core/agents/live-lifecycle-v3.js +91 -0
  84. package/dist/core/agents/live-observation-v3.d.ts +25 -0
  85. package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
  86. package/dist/core/agents/live-observation-v3.js +157 -0
  87. package/dist/core/agents/render/prompt-context.d.ts +10 -4
  88. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  89. package/dist/core/agents/render/prompt-context.js +61 -55
  90. package/dist/core/agents/render/session-context.d.ts +1 -1
  91. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  92. package/dist/core/agents/render/session-context.js +15 -33
  93. package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
  94. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  95. package/dist/core/agents/rules/claim-conflict.js +79 -127
  96. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  97. package/dist/core/agents/rules/commit-conflict.js +3 -18
  98. package/dist/core/agents/rules/stop-hook.d.ts +9 -50
  99. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  100. package/dist/core/agents/rules/stop-hook.js +193 -236
  101. package/dist/core/agents/session-events.d.ts +5 -7
  102. package/dist/core/agents/session-events.d.ts.map +1 -1
  103. package/dist/core/agents/session-events.js +127 -95
  104. package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
  105. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
  106. package/dist/core/agents/session-finalization-state-v3.js +40 -0
  107. package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
  108. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
  109. package/dist/core/agents/session-finalizer-v3.js +734 -0
  110. package/dist/core/agents/session-name-display.d.ts +40 -0
  111. package/dist/core/agents/session-name-display.d.ts.map +1 -0
  112. package/dist/core/agents/session-name-display.js +119 -0
  113. package/dist/core/agents/state/activity-log.d.ts +3 -3
  114. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  115. package/dist/core/agents/state/activity-log.js +7 -16
  116. package/dist/core/agents/state/heartbeat-writer.d.ts +29 -46
  117. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  118. package/dist/core/agents/state/heartbeat-writer.js +59 -199
  119. package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
  120. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
  121. package/dist/core/agents/state/live-coordination-view.js +209 -0
  122. package/dist/core/agents/state/session-state.d.ts +45 -0
  123. package/dist/core/agents/state/session-state.d.ts.map +1 -0
  124. package/dist/core/agents/state/session-state.js +90 -0
  125. package/dist/core/agents/state/stale-sweep.d.ts +1 -1
  126. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  127. package/dist/core/agents/state/stale-sweep.js +26 -15
  128. package/dist/core/artifacts/index.d.ts.map +1 -1
  129. package/dist/core/artifacts/index.js +5 -7
  130. package/dist/core/config.d.ts +33 -2
  131. package/dist/core/config.d.ts.map +1 -1
  132. package/dist/core/config.js +98 -2
  133. package/dist/core/context/index.d.ts +1 -4
  134. package/dist/core/context/index.d.ts.map +1 -1
  135. package/dist/core/context/index.js +9 -9
  136. package/dist/core/events/span-parent.d.ts +15 -0
  137. package/dist/core/events/span-parent.d.ts.map +1 -0
  138. package/dist/core/events/span-parent.js +34 -0
  139. package/dist/core/events/v3/advance.d.ts +14 -0
  140. package/dist/core/events/v3/advance.d.ts.map +1 -0
  141. package/dist/core/events/v3/advance.js +134 -0
  142. package/dist/core/events/v3/authority-outbox.d.ts +78 -0
  143. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
  144. package/dist/core/events/v3/authority-outbox.js +399 -0
  145. package/dist/core/events/v3/base-builder.d.ts +20 -0
  146. package/dist/core/events/v3/base-builder.d.ts.map +1 -0
  147. package/dist/core/events/v3/base-builder.js +29 -0
  148. package/dist/core/events/v3/base-contract.d.ts +7576 -0
  149. package/dist/core/events/v3/base-contract.d.ts.map +1 -0
  150. package/dist/core/events/v3/base-contract.js +647 -0
  151. package/dist/core/events/v3/bootstrap.d.ts +34 -0
  152. package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
  153. package/dist/core/events/v3/bootstrap.js +175 -0
  154. package/dist/core/events/v3/builder.d.ts +20 -0
  155. package/dist/core/events/v3/builder.d.ts.map +1 -0
  156. package/dist/core/events/v3/builder.js +28 -0
  157. package/dist/core/events/v3/canonical.d.ts +31 -0
  158. package/dist/core/events/v3/canonical.d.ts.map +1 -0
  159. package/dist/core/events/v3/canonical.js +100 -0
  160. package/dist/core/events/v3/capabilities.d.ts +31 -0
  161. package/dist/core/events/v3/capabilities.d.ts.map +1 -0
  162. package/dist/core/events/v3/capabilities.js +107 -0
  163. package/dist/core/events/v3/capability-drift.d.ts +22 -0
  164. package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
  165. package/dist/core/events/v3/capability-drift.js +134 -0
  166. package/dist/core/events/v3/catalog.d.ts +38 -0
  167. package/dist/core/events/v3/catalog.d.ts.map +1 -0
  168. package/dist/core/events/v3/catalog.js +135 -0
  169. package/dist/core/events/v3/clock-order.d.ts +34 -0
  170. package/dist/core/events/v3/clock-order.d.ts.map +1 -0
  171. package/dist/core/events/v3/clock-order.js +35 -0
  172. package/dist/core/events/v3/contract.d.ts +171 -0
  173. package/dist/core/events/v3/contract.d.ts.map +1 -0
  174. package/dist/core/events/v3/contract.js +180 -0
  175. package/dist/core/events/v3/control.d.ts +111 -0
  176. package/dist/core/events/v3/control.d.ts.map +1 -0
  177. package/dist/core/events/v3/control.js +419 -0
  178. package/dist/core/events/v3/coordination-view.d.ts +69 -0
  179. package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
  180. package/dist/core/events/v3/coordination-view.js +119 -0
  181. package/dist/core/events/v3/economics.d.ts +77 -0
  182. package/dist/core/events/v3/economics.d.ts.map +1 -0
  183. package/dist/core/events/v3/economics.js +274 -0
  184. package/dist/core/events/v3/finalization-view.d.ts +26 -0
  185. package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
  186. package/dist/core/events/v3/finalization-view.js +37 -0
  187. package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
  188. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
  189. package/dist/core/events/v3/fingerprint-keys.js +217 -0
  190. package/dist/core/events/v3/generated.d.ts +4 -0
  191. package/dist/core/events/v3/generated.d.ts.map +1 -0
  192. package/dist/core/events/v3/generated.js +47 -0
  193. package/dist/core/events/v3/ids.d.ts +9 -0
  194. package/dist/core/events/v3/ids.d.ts.map +1 -0
  195. package/dist/core/events/v3/ids.js +25 -0
  196. package/dist/core/events/v3/index.d.ts +35 -0
  197. package/dist/core/events/v3/index.d.ts.map +1 -0
  198. package/dist/core/events/v3/index.js +33 -0
  199. package/dist/core/events/v3/latency.d.ts +63 -0
  200. package/dist/core/events/v3/latency.d.ts.map +1 -0
  201. package/dist/core/events/v3/latency.js +412 -0
  202. package/dist/core/events/v3/live-feed.d.ts +45 -0
  203. package/dist/core/events/v3/live-feed.d.ts.map +1 -0
  204. package/dist/core/events/v3/live-feed.js +246 -0
  205. package/dist/core/events/v3/live-observation.d.ts +35 -0
  206. package/dist/core/events/v3/live-observation.d.ts.map +1 -0
  207. package/dist/core/events/v3/live-observation.js +1 -0
  208. package/dist/core/events/v3/live-routing.d.ts +57 -0
  209. package/dist/core/events/v3/live-routing.d.ts.map +1 -0
  210. package/dist/core/events/v3/live-routing.js +107 -0
  211. package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
  212. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
  213. package/dist/core/events/v3/producers/command-recorder.js +477 -0
  214. package/dist/core/events/v3/producers/command.d.ts +50 -0
  215. package/dist/core/events/v3/producers/command.d.ts.map +1 -0
  216. package/dist/core/events/v3/producers/command.js +135 -0
  217. package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
  218. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
  219. package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
  220. package/dist/core/events/v3/producers/coordination.d.ts +90 -0
  221. package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
  222. package/dist/core/events/v3/producers/coordination.js +229 -0
  223. package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
  224. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/hook-base.js +424 -0
  226. package/dist/core/events/v3/producers/hook.d.ts +29 -0
  227. package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
  228. package/dist/core/events/v3/producers/hook.js +71 -0
  229. package/dist/core/events/v3/producers/intake.d.ts +65 -0
  230. package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
  231. package/dist/core/events/v3/producers/intake.js +244 -0
  232. package/dist/core/events/v3/producers/recorder.d.ts +240 -0
  233. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
  234. package/dist/core/events/v3/producers/recorder.js +2176 -0
  235. package/dist/core/events/v3/projection.d.ts +101 -0
  236. package/dist/core/events/v3/projection.d.ts.map +1 -0
  237. package/dist/core/events/v3/projection.js +479 -0
  238. package/dist/core/events/v3/reader.d.ts +75 -0
  239. package/dist/core/events/v3/reader.d.ts.map +1 -0
  240. package/dist/core/events/v3/reader.js +778 -0
  241. package/dist/core/events/v3/recovery-record.d.ts +47 -0
  242. package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
  243. package/dist/core/events/v3/recovery-record.js +167 -0
  244. package/dist/core/events/v3/recovery.d.ts +22 -0
  245. package/dist/core/events/v3/recovery.d.ts.map +1 -0
  246. package/dist/core/events/v3/recovery.js +227 -0
  247. package/dist/core/events/v3/span-state.d.ts +52 -0
  248. package/dist/core/events/v3/span-state.d.ts.map +1 -0
  249. package/dist/core/events/v3/span-state.js +142 -0
  250. package/dist/core/events/v3/targets.d.ts +23 -0
  251. package/dist/core/events/v3/targets.d.ts.map +1 -0
  252. package/dist/core/events/v3/targets.js +134 -0
  253. package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
  254. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
  255. package/dist/core/events/v3/turn-telemetry.js +168 -0
  256. package/dist/core/events/v3/validate.d.ts +9 -0
  257. package/dist/core/events/v3/validate.d.ts.map +1 -0
  258. package/dist/core/events/v3/validate.js +281 -0
  259. package/dist/core/events/v3/writer.d.ts +56 -0
  260. package/dist/core/events/v3/writer.d.ts.map +1 -0
  261. package/dist/core/events/v3/writer.js +335 -0
  262. package/dist/core/governor/index.d.ts +1 -1
  263. package/dist/core/governor/index.d.ts.map +1 -1
  264. package/dist/core/governor/index.js +1 -1
  265. package/dist/core/governor/plan-api.d.ts +2 -1
  266. package/dist/core/governor/plan-api.d.ts.map +1 -1
  267. package/dist/core/governor/plan-api.js +6 -3
  268. package/dist/core/governor/read.d.ts +1 -1
  269. package/dist/core/governor/read.d.ts.map +1 -1
  270. package/dist/core/governor/read.js +1 -1
  271. package/dist/core/governor/state.d.ts +9 -0
  272. package/dist/core/governor/state.d.ts.map +1 -1
  273. package/dist/core/governor/state.js +40 -3
  274. package/dist/core/guard/config.d.ts +5 -0
  275. package/dist/core/guard/config.d.ts.map +1 -0
  276. package/dist/core/guard/config.js +157 -0
  277. package/dist/core/guard/coordinator.d.ts +14 -0
  278. package/dist/core/guard/coordinator.d.ts.map +1 -0
  279. package/dist/core/guard/coordinator.js +173 -0
  280. package/dist/core/guard/evaluator.d.ts +4 -0
  281. package/dist/core/guard/evaluator.d.ts.map +1 -0
  282. package/dist/core/guard/evaluator.js +242 -0
  283. package/dist/core/guard/evidence-v3.d.ts +15 -0
  284. package/dist/core/guard/evidence-v3.d.ts.map +1 -0
  285. package/dist/core/guard/evidence-v3.js +171 -0
  286. package/dist/core/guard/index.d.ts +10 -0
  287. package/dist/core/guard/index.d.ts.map +1 -0
  288. package/dist/core/guard/index.js +6 -0
  289. package/dist/core/guard/live-source-v3.d.ts +40 -0
  290. package/dist/core/guard/live-source-v3.d.ts.map +1 -0
  291. package/dist/core/guard/live-source-v3.js +128 -0
  292. package/dist/core/guard/storage.d.ts +27 -0
  293. package/dist/core/guard/storage.d.ts.map +1 -0
  294. package/dist/core/guard/storage.js +140 -0
  295. package/dist/core/guard/types.d.ts +119 -0
  296. package/dist/core/guard/types.d.ts.map +1 -0
  297. package/dist/core/guard/types.js +1 -0
  298. package/dist/core/hooks/adapter/detect.d.ts +18 -1
  299. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  300. package/dist/core/hooks/adapter/detect.js +29 -0
  301. package/dist/core/hooks/adapter/events.d.ts +7 -4
  302. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  303. package/dist/core/hooks/adapter/events.js +13 -4
  304. package/dist/core/hooks/adapter/output.d.ts +2 -2
  305. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  306. package/dist/core/hooks/adapter/parse.d.ts +7 -4
  307. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  308. package/dist/core/hooks/adapter/parse.js +64 -24
  309. package/dist/core/hooks/adapter/wiring.d.ts +30 -4
  310. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  311. package/dist/core/hooks/adapter/wiring.js +89 -11
  312. package/dist/core/hooks/cli.d.ts +1 -1
  313. package/dist/core/hooks/cli.js +345 -400
  314. package/dist/core/hooks/codex-authorization.d.ts +30 -0
  315. package/dist/core/hooks/codex-authorization.d.ts.map +1 -0
  316. package/dist/core/hooks/codex-authorization.js +199 -0
  317. package/dist/core/hooks/effects/image-capture.d.ts +20 -34
  318. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  319. package/dist/core/hooks/effects/image-capture.js +212 -221
  320. package/dist/core/hooks/effects/index.d.ts +1 -9
  321. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  322. package/dist/core/hooks/effects/index.js +1 -24
  323. package/dist/core/hooks/events/input-hash.d.ts +7 -0
  324. package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
  325. package/dist/core/hooks/events/input-hash.js +43 -0
  326. package/dist/core/hooks/index.d.ts +4 -3
  327. package/dist/core/hooks/index.d.ts.map +1 -1
  328. package/dist/core/hooks/index.js +2 -3
  329. package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
  330. package/dist/core/hooks/resolve/coord-root.js +1 -1
  331. package/dist/core/hooks/resolve/owner.d.ts +16 -15
  332. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  333. package/dist/core/hooks/resolve/owner.js +51 -42
  334. package/dist/core/hooks/resolve/transcript.d.ts +33 -3
  335. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  336. package/dist/core/hooks/resolve/transcript.js +155 -14
  337. package/dist/core/hooks/session-name-presence.d.ts +9 -10
  338. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  339. package/dist/core/hooks/session-name-presence.js +15 -15
  340. package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
  341. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
  342. package/dist/core/journal/index.d.ts +2 -2
  343. package/dist/core/journal/index.d.ts.map +1 -1
  344. package/dist/core/journal/index.js +9 -22
  345. package/dist/core/presence/blob.d.ts +8 -3
  346. package/dist/core/presence/blob.d.ts.map +1 -1
  347. package/dist/core/presence/blob.js +54 -41
  348. package/dist/core/presence/codec-digest.d.ts +29 -0
  349. package/dist/core/presence/codec-digest.d.ts.map +1 -0
  350. package/dist/core/presence/codec-digest.js +170 -0
  351. package/dist/core/presence/index.js +15 -1
  352. package/dist/core/presence/relay-client.d.ts +1 -1
  353. package/dist/core/presence/relay-client.d.ts.map +1 -1
  354. package/dist/core/presence/relay-client.js +5 -5
  355. package/dist/core/work/index.d.ts +1 -1
  356. package/dist/core/work/index.d.ts.map +1 -1
  357. package/dist/core/work/index.js +1 -1
  358. package/dist/core/work/read.d.ts +1 -1
  359. package/dist/core/work/read.d.ts.map +1 -1
  360. package/dist/core/work/read.js +1 -1
  361. package/dist/core/work/runner.d.ts.map +1 -1
  362. package/dist/core/work/runner.js +6 -0
  363. package/dist/core/work/state.d.ts +9 -0
  364. package/dist/core/work/state.d.ts.map +1 -1
  365. package/dist/core/work/state.js +17 -3
  366. package/dist/core/workflow/engine.js +24 -25
  367. package/dist/core/workflow/live-session-v3.d.ts +19 -0
  368. package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
  369. package/dist/core/workflow/live-session-v3.js +86 -0
  370. package/dist/core/workflow/scope-id.d.ts +3 -0
  371. package/dist/core/workflow/scope-id.d.ts.map +1 -0
  372. package/dist/core/workflow/scope-id.js +5 -0
  373. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  374. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  375. package/dist/core/workflow/workspaces/leases.js +53 -19
  376. package/dist/index.d.ts +1 -1
  377. package/dist/index.js +1 -1
  378. package/dist/lib/browser/client.d.ts +41 -0
  379. package/dist/lib/browser/client.d.ts.map +1 -1
  380. package/dist/lib/browser/client.js +203 -0
  381. package/dist/lib/browser/critique.d.ts +28 -2
  382. package/dist/lib/browser/critique.d.ts.map +1 -1
  383. package/dist/lib/browser/critique.js +74 -18
  384. package/dist/lib/browser/geometry.d.ts +1 -1
  385. package/dist/lib/browser/geometry.d.ts.map +1 -1
  386. package/dist/lib/browser/geometry.js +130 -6
  387. package/dist/lib/browser/index.d.ts +3 -0
  388. package/dist/lib/browser/index.d.ts.map +1 -1
  389. package/dist/lib/browser/index.js +3 -0
  390. package/dist/lib/browser/qa-plan.d.ts +174 -0
  391. package/dist/lib/browser/qa-plan.d.ts.map +1 -0
  392. package/dist/lib/browser/qa-plan.js +321 -0
  393. package/dist/lib/browser/qa-reuse.d.ts +99 -0
  394. package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
  395. package/dist/lib/browser/qa-reuse.js +222 -0
  396. package/dist/lib/browser/qa-snapshot.d.ts +64 -0
  397. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
  398. package/dist/lib/browser/qa-snapshot.js +171 -0
  399. package/dist/lib/browser/runts.d.ts.map +1 -1
  400. package/dist/lib/browser/runts.js +22 -1
  401. package/dist/lib/browser/tiling.d.ts +71 -0
  402. package/dist/lib/browser/tiling.d.ts.map +1 -0
  403. package/dist/lib/browser/tiling.js +118 -0
  404. package/dist/lib/context/index.d.ts +0 -3
  405. package/dist/lib/context/index.d.ts.map +1 -1
  406. package/dist/lib/context/index.js +20 -42
  407. package/dist/lib/council/index.d.ts +2 -2
  408. package/dist/lib/council/index.d.ts.map +1 -1
  409. package/dist/lib/council/index.js +9 -20
  410. package/dist/lib/docs-frontmatter.d.ts +1 -6
  411. package/dist/lib/docs-frontmatter.d.ts.map +1 -1
  412. package/dist/lib/docs-frontmatter.js +12 -78
  413. package/dist/lib/docs-index.d.ts +9 -0
  414. package/dist/lib/docs-index.d.ts.map +1 -1
  415. package/dist/lib/docs-index.js +32 -3
  416. package/dist/lib/docs-lint.d.ts.map +1 -1
  417. package/dist/lib/docs-lint.js +14 -30
  418. package/dist/lib/docs-metadata-audit.d.ts +19 -0
  419. package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
  420. package/dist/lib/docs-metadata-audit.js +132 -0
  421. package/dist/lib/docs-metadata-managed.d.ts +23 -0
  422. package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
  423. package/dist/lib/docs-metadata-managed.js +78 -0
  424. package/dist/lib/docs-metadata-sync.d.ts +20 -0
  425. package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
  426. package/dist/lib/docs-metadata-sync.js +222 -0
  427. package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
  428. package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
  429. package/dist/lib/docs-metadata-v2-schema.js +134 -0
  430. package/dist/lib/docs-metadata-v2.d.ts +21 -0
  431. package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
  432. package/dist/lib/docs-metadata-v2.js +424 -0
  433. package/dist/lib/docs-new.d.ts +11 -0
  434. package/dist/lib/docs-new.d.ts.map +1 -0
  435. package/dist/lib/docs-new.js +52 -0
  436. package/dist/lib/docs-sweep.d.ts.map +1 -1
  437. package/dist/lib/docs-sweep.js +45 -29
  438. package/dist/lib/headless/index.d.ts +98 -0
  439. package/dist/lib/headless/index.d.ts.map +1 -0
  440. package/dist/lib/headless/index.js +211 -0
  441. package/dist/lib/identities/assume.d.ts +4 -4
  442. package/dist/lib/identities/assume.d.ts.map +1 -1
  443. package/dist/lib/identities/assume.js +52 -68
  444. package/dist/lib/instructions/apply.d.ts +5 -4
  445. package/dist/lib/instructions/apply.d.ts.map +1 -1
  446. package/dist/lib/instructions/apply.js +32 -20
  447. package/dist/lib/instructions/bundle.d.ts +33 -0
  448. package/dist/lib/instructions/bundle.d.ts.map +1 -0
  449. package/dist/lib/instructions/bundle.js +149 -0
  450. package/dist/lib/instructions/splice.d.ts +0 -1
  451. package/dist/lib/instructions/splice.d.ts.map +1 -1
  452. package/dist/lib/instructions/splice.js +22 -6
  453. package/dist/lib/instructions/templates.d.ts +7 -5
  454. package/dist/lib/instructions/templates.d.ts.map +1 -1
  455. package/dist/lib/instructions/templates.js +79 -9
  456. package/package.json +27 -6
  457. package/schemas/config.schema.json +120 -2
  458. package/schemas/docs-metadata-v2.schema.json +441 -0
  459. package/schemas/event-v3.schema.json +24472 -0
  460. package/src/commander.ts +7 -1
  461. package/src/commands/adapter.ts +2 -2
  462. package/src/commands/agents.ts +1737 -570
  463. package/src/commands/artifacts.ts +7 -6
  464. package/src/commands/backup.ts +29 -21
  465. package/src/commands/browse.ts +349 -8
  466. package/src/commands/checkpoint.ts +4 -3
  467. package/src/commands/claude-desktop.ts +7 -5
  468. package/src/commands/context.ts +1 -6
  469. package/src/commands/decision.ts +27 -44
  470. package/src/commands/docs.ts +100 -33
  471. package/src/commands/doctor.ts +73 -8
  472. package/src/commands/events.ts +256 -0
  473. package/src/commands/governor.ts +18 -4
  474. package/src/commands/init.ts +264 -57
  475. package/src/commands/instructions.ts +129 -0
  476. package/src/commands/journal.ts +4 -13
  477. package/src/commands/ledger-v3.ts +111 -0
  478. package/src/commands/presence.ts +18 -11
  479. package/src/commands/sync.ts +1 -1
  480. package/src/commands/web.ts +67 -8
  481. package/src/commands/work.ts +28 -5
  482. package/src/core/adapter.ts +5 -0
  483. package/src/core/adapters/attest.ts +8 -3
  484. package/src/core/adapters/profiles.ts +4 -6
  485. package/src/core/agents/canonical-emit.ts +39 -28
  486. package/src/core/agents/cli.ts +242 -263
  487. package/src/core/agents/codex-archive-v3.ts +138 -0
  488. package/src/core/agents/coord-client.ts +75 -177
  489. package/src/core/agents/finalization.ts +120 -38
  490. package/src/core/agents/index.ts +5 -5
  491. package/src/core/agents/live-authority-v3.ts +375 -0
  492. package/src/core/agents/live-lifecycle-v3.ts +140 -0
  493. package/src/core/agents/live-observation-v3.ts +216 -0
  494. package/src/core/agents/render/prompt-context.ts +89 -55
  495. package/src/core/agents/render/session-context.ts +23 -32
  496. package/src/core/agents/rules/claim-conflict.ts +85 -126
  497. package/src/core/agents/rules/commit-conflict.ts +3 -14
  498. package/src/core/agents/rules/stop-hook.ts +239 -293
  499. package/src/core/agents/session-events.ts +137 -104
  500. package/src/core/agents/session-finalization-state-v3.ts +90 -0
  501. package/src/core/agents/session-finalizer-v3.ts +953 -0
  502. package/src/core/agents/session-name-display.ts +149 -0
  503. package/src/core/agents/state/activity-log.ts +6 -14
  504. package/src/core/agents/state/heartbeat-writer.ts +98 -242
  505. package/src/core/agents/state/live-coordination-view.ts +290 -0
  506. package/src/core/agents/state/session-state.ts +143 -0
  507. package/src/core/agents/state/stale-sweep.ts +40 -17
  508. package/src/core/artifacts/index.ts +4 -5
  509. package/src/core/config.ts +148 -3
  510. package/src/core/context/index.ts +9 -17
  511. package/src/core/events/span-parent.ts +46 -0
  512. package/src/core/events/v3/advance.ts +168 -0
  513. package/src/core/events/v3/authority-outbox.ts +548 -0
  514. package/src/core/events/v3/base-builder.ts +60 -0
  515. package/src/core/events/v3/base-contract.ts +882 -0
  516. package/src/core/events/v3/bootstrap.ts +226 -0
  517. package/src/core/events/v3/builder.ts +59 -0
  518. package/src/core/events/v3/canonical.ts +153 -0
  519. package/src/core/events/v3/capabilities.ts +171 -0
  520. package/src/core/events/v3/capability-drift.ts +177 -0
  521. package/src/core/events/v3/catalog.ts +202 -0
  522. package/src/core/events/v3/clock-order.ts +41 -0
  523. package/src/core/events/v3/contract.ts +365 -0
  524. package/src/core/events/v3/control.ts +576 -0
  525. package/src/core/events/v3/coordination-view.ts +193 -0
  526. package/src/core/events/v3/economics.ts +384 -0
  527. package/src/core/events/v3/finalization-view.ts +67 -0
  528. package/src/core/events/v3/fingerprint-keys.ts +260 -0
  529. package/src/core/events/v3/generated.ts +48 -0
  530. package/src/core/events/v3/ids.ts +33 -0
  531. package/src/core/events/v3/index.ts +322 -0
  532. package/src/core/events/v3/latency.ts +551 -0
  533. package/src/core/events/v3/live-feed.ts +319 -0
  534. package/src/core/events/v3/live-observation.ts +39 -0
  535. package/src/core/events/v3/live-routing.ts +135 -0
  536. package/src/core/events/v3/producers/command-recorder.ts +631 -0
  537. package/src/core/events/v3/producers/command.ts +201 -0
  538. package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
  539. package/src/core/events/v3/producers/coordination.ts +355 -0
  540. package/src/core/events/v3/producers/hook-base.ts +578 -0
  541. package/src/core/events/v3/producers/hook.ts +119 -0
  542. package/src/core/events/v3/producers/intake.ts +319 -0
  543. package/src/core/events/v3/producers/recorder.ts +2805 -0
  544. package/src/core/events/v3/projection.ts +653 -0
  545. package/src/core/events/v3/reader.ts +1127 -0
  546. package/src/core/events/v3/recovery-record.ts +244 -0
  547. package/src/core/events/v3/recovery.ts +271 -0
  548. package/src/core/events/v3/span-state.ts +187 -0
  549. package/src/core/events/v3/targets.ts +182 -0
  550. package/src/core/events/v3/turn-telemetry.ts +259 -0
  551. package/src/core/events/v3/validate.ts +361 -0
  552. package/src/core/events/v3/writer.ts +419 -0
  553. package/src/core/governor/index.ts +3 -0
  554. package/src/core/governor/plan-api.ts +10 -2
  555. package/src/core/governor/read.ts +3 -0
  556. package/src/core/governor/state.ts +52 -5
  557. package/src/core/guard/config.ts +206 -0
  558. package/src/core/guard/coordinator.ts +227 -0
  559. package/src/core/guard/evaluator.ts +343 -0
  560. package/src/core/guard/evidence-v3.ts +227 -0
  561. package/src/core/guard/index.ts +35 -0
  562. package/src/core/guard/live-source-v3.ts +204 -0
  563. package/src/core/guard/storage.ts +188 -0
  564. package/src/core/guard/types.ts +152 -0
  565. package/src/core/hooks/adapter/detect.ts +28 -1
  566. package/src/core/hooks/adapter/events.ts +15 -6
  567. package/src/core/hooks/adapter/output.ts +7 -2
  568. package/src/core/hooks/adapter/parse.ts +81 -36
  569. package/src/core/hooks/adapter/wiring.ts +119 -13
  570. package/src/core/hooks/cli.ts +398 -422
  571. package/src/core/hooks/codex-authorization.ts +253 -0
  572. package/src/core/hooks/effects/image-capture.ts +228 -238
  573. package/src/core/hooks/effects/index.ts +1 -36
  574. package/src/core/hooks/events/input-hash.ts +47 -0
  575. package/src/core/hooks/index.ts +4 -11
  576. package/src/core/hooks/resolve/coord-root.ts +1 -1
  577. package/src/core/hooks/resolve/owner.ts +52 -45
  578. package/src/core/hooks/resolve/transcript.ts +165 -14
  579. package/src/core/hooks/session-name-presence.ts +14 -15
  580. package/src/core/hooks/unsafe-cross-shell.ts +1 -1
  581. package/src/core/journal/index.ts +15 -22
  582. package/src/core/presence/blob.ts +63 -38
  583. package/src/core/presence/codec-digest.ts +215 -0
  584. package/src/core/presence/index.ts +18 -1
  585. package/src/core/presence/relay-client.ts +5 -5
  586. package/src/core/work/index.ts +3 -0
  587. package/src/core/work/read.ts +3 -0
  588. package/src/core/work/runner.ts +6 -0
  589. package/src/core/work/state.ts +27 -3
  590. package/src/core/workflow/engine.ts +24 -24
  591. package/src/core/workflow/live-session-v3.ts +126 -0
  592. package/src/core/workflow/scope-id.ts +6 -0
  593. package/src/core/workflow/workspaces/leases.ts +56 -19
  594. package/src/index.ts +1 -1
  595. package/src/lib/browser/client.ts +230 -0
  596. package/src/lib/browser/critique.ts +104 -27
  597. package/src/lib/browser/geometry.ts +143 -14
  598. package/src/lib/browser/index.ts +34 -0
  599. package/src/lib/browser/qa-plan.ts +501 -0
  600. package/src/lib/browser/qa-reuse.ts +306 -0
  601. package/src/lib/browser/qa-snapshot.ts +223 -0
  602. package/src/lib/browser/runts.ts +27 -2
  603. package/src/lib/browser/tiling.ts +164 -0
  604. package/src/lib/context/index.ts +22 -46
  605. package/src/lib/council/index.ts +9 -21
  606. package/src/lib/docs-frontmatter.ts +8 -79
  607. package/src/lib/docs-index.ts +33 -3
  608. package/src/lib/docs-lint.ts +15 -29
  609. package/src/lib/docs-metadata-audit.ts +166 -0
  610. package/src/lib/docs-metadata-managed.ts +83 -0
  611. package/src/lib/docs-metadata-sync.ts +262 -0
  612. package/src/lib/docs-metadata-v2-schema.ts +138 -0
  613. package/src/lib/docs-metadata-v2.ts +646 -0
  614. package/src/lib/docs-new.ts +63 -0
  615. package/src/lib/docs-sweep.ts +42 -28
  616. package/src/lib/headless/index.ts +287 -0
  617. package/src/lib/identities/assume.ts +67 -86
  618. package/src/lib/instructions/apply.ts +33 -20
  619. package/src/lib/instructions/bundle.ts +199 -0
  620. package/src/lib/instructions/splice.ts +21 -7
  621. package/src/lib/instructions/templates.ts +85 -13
  622. package/dist/core/agents/cli-emit.d.ts +0 -27
  623. package/dist/core/agents/cli-emit.d.ts.map +0 -1
  624. package/dist/core/agents/cli-emit.js +0 -57
  625. package/dist/core/agents/codex-replay.d.ts +0 -29
  626. package/dist/core/agents/codex-replay.d.ts.map +0 -1
  627. package/dist/core/agents/codex-replay.js +0 -138
  628. package/dist/core/agents/events/consume.d.ts +0 -82
  629. package/dist/core/agents/events/consume.d.ts.map +0 -1
  630. package/dist/core/agents/events/consume.js +0 -195
  631. package/dist/core/agents/events/emit.d.ts +0 -42
  632. package/dist/core/agents/events/emit.d.ts.map +0 -1
  633. package/dist/core/agents/events/emit.js +0 -75
  634. package/dist/core/agents/events/ulid.d.ts +0 -11
  635. package/dist/core/agents/events/ulid.d.ts.map +0 -1
  636. package/dist/core/agents/events/ulid.js +0 -47
  637. package/dist/core/agents/state/heartbeat-projector.d.ts +0 -65
  638. package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
  639. package/dist/core/agents/state/heartbeat-projector.js +0 -527
  640. package/dist/core/hooks/events/emit.d.ts +0 -31
  641. package/dist/core/hooks/events/emit.d.ts.map +0 -1
  642. package/dist/core/hooks/events/emit.js +0 -93
  643. package/dist/core/hooks/events/rotate.d.ts +0 -43
  644. package/dist/core/hooks/events/rotate.d.ts.map +0 -1
  645. package/dist/core/hooks/events/rotate.js +0 -142
  646. package/dist/core/hooks/events/schema.d.ts +0 -312
  647. package/dist/core/hooks/events/schema.d.ts.map +0 -1
  648. package/dist/core/hooks/events/schema.js +0 -12
  649. package/dist/core/hooks/events/ulid.d.ts +0 -10
  650. package/dist/core/hooks/events/ulid.d.ts.map +0 -1
  651. package/dist/core/hooks/events/ulid.js +0 -47
  652. package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
  653. package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
  654. package/dist/lib/docs-frontmatter-migrate.js +0 -364
  655. package/src/core/agents/cli-emit.ts +0 -64
  656. package/src/core/agents/codex-replay.ts +0 -163
  657. package/src/core/agents/events/consume.ts +0 -254
  658. package/src/core/agents/events/emit.ts +0 -115
  659. package/src/core/agents/events/ulid.ts +0 -51
  660. package/src/core/agents/state/heartbeat-projector.ts +0 -582
  661. package/src/core/hooks/events/emit.ts +0 -125
  662. package/src/core/hooks/events/rotate.ts +0 -151
  663. package/src/core/hooks/events/schema.ts +0 -538
  664. package/src/core/hooks/events/ulid.ts +0 -51
  665. package/src/lib/docs-frontmatter-migrate.ts +0 -427
@@ -8,7 +8,7 @@
8
8
  * 4. Resolve instance_id (env → payload → pid-map walk).
9
9
  * 5. Map event-name → canonical event_type.
10
10
  * 6. Build event data from payload + resolvers (intent, transcript scan).
11
- * 7. Append envelope to .harnery/events.ndjson via emit() under flock.
11
+ * 7. Record the normalized observation in the canonical V3 ledger.
12
12
  * 8. (Still also writes a debug breadcrumb to .harnery/debug/ for visibility.)
13
13
  *
14
14
  * Phase 2 ship criterion: confirms parser correctness across thousands of
@@ -20,28 +20,32 @@ import { spawnSync } from "node:child_process";
20
20
  import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
21
21
  import { dirname, join } from "node:path";
22
22
  import { coordEnv } from "../../lib/env.js";
23
- import { replayCodexJsonl } from "../agents/codex-replay.js";
23
+ import { buildInstructionBundle } from "../../lib/instructions/bundle.js";
24
24
  import { coordBinPath } from "../agents/coord-bin.js";
25
- import { consumeSince, writeCursor } from "../agents/events/consume.js";
26
25
  import { classifyWriteClaimFinalization, formatWriteClaimFinalizationDenial, } from "../agents/finalization.js";
27
- import { evaluateStopHook } from "../agents/rules/stop-hook.js";
28
- import { projectHeartbeats } from "../agents/state/heartbeat-projector.js";
26
+ import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.js";
27
+ import { assistantTextStartsWithSessionNameBlock, isSessionNameRemediationCommand, sessionNameDisplayInstruction, sessionNameDisplayPending, toolResponseMintedSessionName, } from "../agents/session-name-display.js";
28
+ import { stampSessionNameSeen } from "../agents/state/heartbeat-writer.js";
29
+ import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.js";
29
30
  import { writePidmapRow } from "../agents/state/pidmap.js";
30
- import { shellMutationPaths } from "../agents/state/shell-mutation.js";
31
31
  import { agentsRequireGitFinalization, resolveBinName } from "../config.js";
32
32
  import { checkpointContext, completeContextRecovery, extractContextSample, markContextCompactionCompleted, prepareContextRecovery, readContextState, recordContextSample, } from "../context/index.js";
33
- import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.js";
34
- import { detectAdapter } from "./adapter/detect.js";
33
+ import { tryWriteLiveDisplayV3 } from "../events/v3/live-feed.js";
34
+ import { recordLiveHookSignalV3, resolveLiveEventLedgerRouteV3, } from "../events/v3/live-routing.js";
35
+ import { captureSpanClockV3 } from "../events/v3/span-state.js";
36
+ import { fetchPresence } from "../presence/index.js";
37
+ import { stableScopeId } from "../workflow/scope-id.js";
38
+ import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.js";
35
39
  import { extractBashCommand, extractToolDescription, normalizeEventName, parsePayload, } from "./adapter/parse.js";
36
40
  import { codexWslFileLinkTelemetry, inspectCodexWslBridge, isWslUncPath, renderCodexWslFileLinkContext, } from "./codex-wsl-bridge.js";
37
- import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, resetSoundCounters, runSessionSyncExtension, runTurnSummary, soundForEvent, } from "./effects/index.js";
38
- import { emit } from "./events/emit.js";
41
+ import { captureImages, detectPresence, imageJanitor, journalArchive, journalJanitor, journalRecoveryCue, playSound, recordImageArtifactsV3, resetSoundCounters, runSessionSyncExtension, soundForEvent, } from "./effects/index.js";
42
+ import { toolInputHash, toolTargetHash } from "./events/input-hash.js";
39
43
  import { canonicalize } from "./guard-path.js";
40
44
  import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.js";
41
45
  import { findCoordRoot } from "./resolve/coord-root.js";
42
46
  import { extractIntentComment, resolveIntent } from "./resolve/intent.js";
43
47
  import { resolveOwner } from "./resolve/owner.js";
44
- import { detectForkParent, scanAssistantTextIncludes, scanStatusBoxPresent, scanTranscriptModel, } from "./resolve/transcript.js";
48
+ import { inspectSessionNameDisplayImmediately, scanAssistantTextIncludes, scanSessionNameDisplayedImmediately, scanStatusBoxPresent, scanTranscriptModel, } from "./resolve/transcript.js";
45
49
  import { sessionNamePresence } from "./session-name-presence.js";
46
50
  import { unsafeCrossShellReason } from "./unsafe-cross-shell.js";
47
51
  function parseArgv(argv) {
@@ -117,7 +121,7 @@ function logError(coordRoot, err, context) {
117
121
  /**
118
122
  * Spawn `agent-coord assign-name <owner> <kind>` to mint or recover the
119
123
  * hurricane-style name for this owner. Returns null on any failure so
120
- * session.start emission never breaks the adapter flow.
124
+ * session.started emission never breaks the adapter flow.
121
125
  *
122
126
  * Lives at agent-hooks side (not agent-coord) to keep emitter/consumer
123
127
  * separation: we spawn rather than import.
@@ -149,7 +153,7 @@ function assignNameViaAgentCoord(coordRoot, instanceId, kind, forkedFrom) {
149
153
  }
150
154
  /**
151
155
  * Direct (in-process) pidmap write, avoiding the spawn overhead of going via
152
- * the agent-coord CLI for every session.start / subagent.start. Pid-map rows
156
+ * the agent-coord CLI for every session.started signal. Pid-map rows
153
157
  * are essential for `harn agents whoami` ppid resolution.
154
158
  *
155
159
  * This used to inline its own copy of the write to keep this module's
@@ -183,18 +187,18 @@ function summarizeOutput(value, headTail = 500) {
183
187
  function buildEventData(eventType, ctx) {
184
188
  const p = ctx.payload;
185
189
  switch (eventType) {
186
- case "session.start": {
190
+ case "session.started": {
187
191
  const adapterPlatform = ctx.adapter === "claude-code"
188
192
  ? "claude-code"
189
193
  : ctx.adapter === "cursor"
190
194
  ? "cursor"
191
195
  : "codex";
192
196
  // Recorded fork lineage is NOT detected here. On claude-code a fork
193
- // never fires its own session.start — SessionStart fires under the
197
+ // never fires its own session.started — SessionStart fires under the
194
198
  // PARENT's session id (source=resume) before the fork id is minted
195
199
  // (verified 2026-08-05) — so detection at this point can only mislabel
196
200
  // the resumed parent. The fork's new instance is caught by the
197
- // tool.pre_use heal path instead. The forkedFrom plumbing below stays
201
+ // tool.requested heal path instead. The forkedFrom plumbing below stays
198
202
  // for adapters that DO report a parent at session start.
199
203
  const forkedFrom = undefined;
200
204
  // Assign (or recover) name + kind via agent-coord. Idempotent: resume
@@ -210,12 +214,13 @@ function buildEventData(eventType, ctx) {
210
214
  if (adapterPid) {
211
215
  writePidmapViaAgentCoord(ctx.coordRoot, adapterPid, ctx.instanceId, adapterPlatform);
212
216
  }
217
+ const bundle = safeInstructionBundle(ctx.coordRoot, p?.cwd ?? process.cwd(), ctx.adapter);
213
218
  return {
214
219
  started_at: new Date().toISOString(),
215
220
  cwd: p?.cwd ?? process.cwd(),
216
221
  // Claude Code's SessionStart payload omits `model` (Codex + Cursor
217
222
  // supply it). Fall back to the transcript, populated on `resume`, and
218
- // backfilled later by `turn.stop` for a fresh `startup` session.
223
+ // backfilled later by `turn.completed` for a fresh startup session.
219
224
  model: p?.model ?? scanTranscriptModel(p?.transcript_path),
220
225
  pid: adapterPid,
221
226
  source: p?.source,
@@ -223,6 +228,14 @@ function buildEventData(eventType, ctx) {
223
228
  name: assigned?.name,
224
229
  kind: "session",
225
230
  agent_id: ctx.instanceId,
231
+ ...(bundle
232
+ ? {
233
+ instruction_bundle_id: bundle.instruction_bundle_id,
234
+ instruction_source_id: bundle.canonical_source_id,
235
+ instruction_profile_root: bundle.profile_root,
236
+ instruction_component_count: bundle.components.length,
237
+ }
238
+ : {}),
226
239
  // Recorded fork lineage rides the canonical event too, so full replay
227
240
  // and derived readers converge on the same parent linkage that
228
241
  // .name-history carries (the ADR 0017 dual-write pattern).
@@ -243,23 +256,30 @@ function buildEventData(eventType, ctx) {
243
256
  : {}),
244
257
  };
245
258
  }
246
- case "session.end":
259
+ case "session.ended":
247
260
  return {
248
261
  ended_at: new Date().toISOString(),
249
262
  clean_exit: p?.clean_exit ?? true,
250
263
  };
251
- case "user_prompt.submit": {
264
+ case "turn.started": {
265
+ // Cursor can deliver beforeSubmitPrompt before sessionStart. Ensure that
266
+ // high-confidence prompt bootstrap also mints durable display identity;
267
+ // a later SessionStart remains idempotent.
268
+ if (ctx.adapter === "cursor" &&
269
+ !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
270
+ assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "session");
271
+ }
252
272
  const prompt = p?.prompt ?? "";
253
273
  const { value, truncated } = clampString(prompt, 4000);
254
274
  return { prompt_text: value, ...(truncated ? { truncated: true } : {}) };
255
275
  }
256
- case "turn.stop": {
276
+ case "turn.completed": {
257
277
  const lastAssistantMessage = p?.raw.last_assistant_message ?? "";
258
278
  const fileLinkTelemetry = ctx.adapter === "codex"
259
279
  ? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
260
280
  : null;
261
281
  return {
262
- // Backfill the model for adapters that omit it at session.start
282
+ // Backfill the model for adapters that omit it at session.started
263
283
  // (Claude Code). The transcript is populated with assistant turns by
264
284
  // Stop-hook time, so this resolves even for fresh `startup` sessions.
265
285
  model: p?.model ?? scanTranscriptModel(p?.transcript_path),
@@ -273,7 +293,7 @@ function buildEventData(eventType, ctx) {
273
293
  // Box present if the transcript scan finds it OR the final assistant
274
294
  // message carries the `┌─ agent-` prefix. The latter covers codex's
275
295
  // text-only stop (box in last_assistant_message, no transcript), which
276
- // the verdict now sees because agent-hook emits this turn.stop itself
296
+ // the verdict now sees because agent-hook emits this turn.completed itself
277
297
  // (the previous path passed those via the no-history fail-open).
278
298
  status_box_present: scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
279
299
  // Shadow measurement for the rule-2/3 detector: the loose scan above
@@ -291,12 +311,12 @@ function buildEventData(eventType, ctx) {
291
311
  // and which name that refers to. Reported on every stop while a name
292
312
  // exists (the stop-hook.session_name verdict reads it per turn); the
293
313
  // transcript scan itself stops once the name has been sighted.
294
- ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, lastAssistantMessage, (name) => scanAssistantTextIncludes(p?.transcript_path, name)),
314
+ ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) => scanSessionNameDisplayedImmediately(p?.transcript_path, name, assistantTextStartsWithSessionNameBlock)),
295
315
  ...(fileLinkTelemetry ?? {}),
296
316
  stop_hook_active: p?.stop_hook_active,
297
317
  };
298
318
  }
299
- case "subagent.start": {
319
+ case "agent.started": {
300
320
  const subagentCallId = p?.raw.subagent_id ?? p?.raw.agent_id;
301
321
  // Subagents inherit parent's name via the resolve-name session_id path
302
322
  // (agent-coord/state/names.ts → kind=transient). Use the call ID as the
@@ -314,12 +334,12 @@ function buildEventData(eventType, ctx) {
314
334
  parent_session_id: p?.parent_session_id,
315
335
  };
316
336
  }
317
- case "subagent.stop": {
337
+ case "agent.completed": {
318
338
  const status = p?.exit_status;
319
339
  const normalized = status === "error" || status === "interrupted" ? status : "ok";
320
340
  return { exit_status: normalized, reason: p?.reason };
321
341
  }
322
- case "tool.pre_use": {
342
+ case "tool.requested": {
323
343
  const toolName = p?.tool_name ?? "unknown";
324
344
  const command = extractBashCommand(toolName, p?.tool_input);
325
345
  const description = extractToolDescription(p?.tool_input);
@@ -334,36 +354,44 @@ function buildEventData(eventType, ctx) {
334
354
  return {
335
355
  tool_name: toolName,
336
356
  tool_input: clamped.value,
357
+ input_hash: toolInputHash(toolName, p?.tool_input ?? null),
358
+ target_hash: toolTargetHash(toolName, p?.tool_input ?? null),
337
359
  intent,
338
360
  intent_source: source,
339
361
  tool_use_id: p?.tool_use_id,
340
362
  ...(clamped.truncated ? { truncated: true } : {}),
341
363
  };
342
364
  }
343
- case "tool.post_use": {
344
- const toolName = p?.tool_name ?? "unknown";
345
- const summary = summarizeOutput(p?.tool_response);
365
+ case "wait.started": {
366
+ const description = extractToolDescription(p?.tool_input);
367
+ const reason = description ? clampString(description, 500).value : undefined;
346
368
  return {
347
- tool_name: toolName,
348
- output_summary: summary.summary,
349
- exit_status: "ok",
350
- duration_ms: 0, // Phase 3 pairs pre/post via tool_use_id
351
- tool_use_id: p?.tool_use_id,
352
- ...(summary.truncated ? { truncated: true } : {}),
369
+ request_kind: "permission",
370
+ tool_name: p?.tool_name,
371
+ ...(reason ? { reason } : {}),
353
372
  };
354
373
  }
355
- case "tool.post_use_failure": {
374
+ case "tool.completed": {
356
375
  const toolName = p?.tool_name ?? "unknown";
357
376
  const summary = summarizeOutput(p?.tool_response);
358
- return {
359
- tool_name: toolName,
360
- error: summary.summary,
361
- duration_ms: 0,
362
- tool_use_id: p?.tool_use_id,
363
- ...(summary.truncated ? { truncated: true } : {}),
364
- };
365
- }
366
- case "context.compaction.started": {
377
+ return ctx.eventName === "post-tool-use-failure"
378
+ ? {
379
+ tool_name: toolName,
380
+ error: summary.summary,
381
+ duration_ms: 0,
382
+ tool_use_id: p?.tool_use_id,
383
+ ...(summary.truncated ? { truncated: true } : {}),
384
+ }
385
+ : {
386
+ tool_name: toolName,
387
+ output_summary: summary.summary,
388
+ exit_status: "ok",
389
+ duration_ms: 0,
390
+ tool_use_id: p?.tool_use_id,
391
+ ...(summary.truncated ? { truncated: true } : {}),
392
+ };
393
+ }
394
+ case "context.compaction_started": {
367
395
  const metadata = objectRecord(p?.raw.compact_metadata);
368
396
  return {
369
397
  trigger: stringField(p?.raw.trigger) ?? stringField(metadata?.trigger),
@@ -372,7 +400,7 @@ function buildEventData(eventType, ctx) {
372
400
  numberField(metadata?.pre_compact_tokens),
373
401
  };
374
402
  }
375
- case "context.compaction.completed": {
403
+ case "context.compaction_completed": {
376
404
  const metadata = objectRecord(p?.raw.compact_metadata);
377
405
  return {
378
406
  trigger: stringField(p?.raw.trigger) ?? stringField(metadata?.trigger),
@@ -386,6 +414,15 @@ function buildEventData(eventType, ctx) {
386
414
  }
387
415
  }
388
416
  }
417
+ function safeInstructionBundle(coordRoot, cwd, adapter) {
418
+ try {
419
+ return buildInstructionBundle({ coordRoot, cwd, adapter });
420
+ }
421
+ catch (err) {
422
+ logError(coordRoot, err, { phase: "instruction-bundle-identity" });
423
+ return null;
424
+ }
425
+ }
389
426
  function objectRecord(value) {
390
427
  return typeof value === "object" && value !== null && !Array.isArray(value)
391
428
  ? value
@@ -398,9 +435,36 @@ function numberField(value) {
398
435
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
399
436
  }
400
437
  async function main() {
438
+ const hookStartedAt = performance.now();
439
+ const hookClock = captureSpanClockV3();
401
440
  const { eventName, extra } = parseArgv(process.argv.slice(2));
402
441
  const adapter = detectAdapter(process.argv.slice(2));
403
442
  const raw = await readStdin();
443
+ // Cursor executes the Claude Code project hooks too on hosts wired for both
444
+ // adapters, piping them its own payload (identifiable by the top-level
445
+ // cursor_version envelope field). That stray `--adapter claude-code`
446
+ // dispatch must not record (twin generation) or play Claude-Code-only
447
+ // sounds, so it bails here, before every effect. Cursor's own
448
+ // `--adapter cursor` dispatch of the same payload carries the session.
449
+ if (shouldSkipHookAdapter(adapter, raw)) {
450
+ if (coordEnv("AGENT_COORD_OFF") !== "1") {
451
+ const coordRoot = findCoordRoot(process.cwd());
452
+ if (coordRoot) {
453
+ appendDebug(coordRoot, {
454
+ ts: new Date().toISOString(),
455
+ event_name: eventName,
456
+ adapter,
457
+ extra_argv: extra,
458
+ payload_bytes: raw.length,
459
+ cwd: process.cwd(),
460
+ pid: process.pid,
461
+ ppid: process.ppid,
462
+ skipped: "cursor-payload-claude-adapter",
463
+ });
464
+ }
465
+ }
466
+ return 0;
467
+ }
404
468
  // Kill-switch-INDEPENDENT effects: notification sounds fire BEFORE the
405
469
  // HARNERY_AGENT_COORD_OFF gate so audible feedback survives incident-triage
406
470
  // bypass: sound playback happens before the kill-switch bailout.
@@ -431,6 +495,7 @@ async function main() {
431
495
  const coordRoot = findCoordRoot(process.cwd());
432
496
  if (!coordRoot)
433
497
  return 0;
498
+ const ledgerRoute = resolveLiveEventLedgerRouteV3(coordRoot);
434
499
  // Always log a breadcrumb, useful when an event_type maps to null or owner
435
500
  // resolution fails. Stays cheap (one append) and self-prunes via repo log
436
501
  // rotation policy.
@@ -454,7 +519,10 @@ async function main() {
454
519
  return 0;
455
520
  }
456
521
  const payload = parsePayload(raw, adapter);
457
- const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
522
+ const owner = resolveOwner({
523
+ payload: payload?.raw ?? null,
524
+ coordRoot,
525
+ });
458
526
  if (!owner) {
459
527
  appendDebug(coordRoot, {
460
528
  ...debugBase,
@@ -470,23 +538,97 @@ async function main() {
470
538
  raw,
471
539
  adapter,
472
540
  instanceId: owner.instance_id,
541
+ eventName,
473
542
  });
474
- const envelope = emit(coordRoot, {
475
- event_type: norm.event_type,
476
- instance_id: owner.instance_id,
477
- session_id: sessionId,
478
- parent_session_id: payload?.parent_session_id,
479
- turn_id: payload?.turn_id,
480
- parent_turn_id: payload?.parent_turn_id,
543
+ const turnRitual = norm.event_type === "turn.completed"
544
+ ? {
545
+ status_box_present: data.status_box_present === true,
546
+ status_box_present_strict: data.status_box_present_strict === true,
547
+ session_name_required: typeof data.session_name_present_for === "string",
548
+ session_name_present: data.session_name_present === true,
549
+ }
550
+ : undefined;
551
+ const stopRemediation = norm.event_type === "turn.started" &&
552
+ typeof data.prompt_text === "string" &&
553
+ data.prompt_text.includes(STOP_REMEDIATION_MARKER);
554
+ if (ledgerRoute.state === "blocked") {
555
+ appendDebug(coordRoot, {
556
+ ...debugBase,
557
+ skipped: "v3-control-blocked",
558
+ reason: ledgerRoute.reason,
559
+ event_type: norm.event_type,
560
+ owner_source: owner.source,
561
+ });
562
+ return 0;
563
+ }
564
+ let capturedImages = [];
565
+ if (norm.event_type === "tool.requested" ||
566
+ (norm.event_type === "tool.completed" && eventName === "post-tool-use")) {
567
+ try {
568
+ capturedImages = captureImages(coordRoot, norm.event_type, payload);
569
+ }
570
+ catch (err) {
571
+ logError(coordRoot, err, { phase: "image-capture" });
572
+ }
573
+ }
574
+ const v3Result = recordLiveHookSignalV3({
575
+ coordRoot,
576
+ route: ledgerRoute,
577
+ eventName,
578
+ payload,
481
579
  adapter,
482
- data,
483
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
580
+ instanceId: owner.instance_id,
581
+ ...(coordEnv("WORKFLOW_CHILD") === "1" && coordEnv("WORKFLOW_RUN_ID")
582
+ ? {
583
+ run_id: stableScopeId("run", coordEnv("WORKFLOW_RUN_ID")),
584
+ workflow_id: stableScopeId("wf", coordEnv("WORKFLOW_RUN_ID")),
585
+ ...(coordEnv("WORKFLOW_AGENT_ID")
586
+ ? { workflow_agent_id: coordEnv("WORKFLOW_AGENT_ID") }
587
+ : {}),
588
+ }
589
+ : {}),
590
+ ...(adapter === "codex" && isWslUncPath(payload?.cwd) ? { bridge: "codex-wsl" } : {}),
591
+ hook_name: eventName,
592
+ hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
593
+ monotonic_ns: hookClock.monotonic_ns,
594
+ ...(stopRemediation ? { stop_remediation: true } : {}),
595
+ ...(turnRitual ? { turn_ritual: turnRitual } : {}),
596
+ ...(coordEnv("EXPERIMENT_DEFER_V3_DRAIN") === "1" ? { defer_drain: true } : {}),
484
597
  });
598
+ const v3EventId = v3Result && "event" in v3Result
599
+ ? v3Result.event.event_id
600
+ : v3Result && "event_id" in v3Result
601
+ ? v3Result.event_id
602
+ : undefined;
603
+ if (v3Result?.state === "recorded" && capturedImages.length > 0) {
604
+ try {
605
+ recordImageArtifactsV3(coordRoot, v3Result.event, capturedImages);
606
+ }
607
+ catch (err) {
608
+ logError(coordRoot, err, { phase: "image-artifact-observation" });
609
+ }
610
+ }
611
+ if (norm.event_type === "tool.requested" &&
612
+ v3Result &&
613
+ v3Result.state === "recorded" &&
614
+ "generation_id" in v3Result.event.scope) {
615
+ const intent = typeof data.intent === "string" ? data.intent : undefined;
616
+ if (intent && intent !== "(no intent)") {
617
+ tryWriteLiveDisplayV3(coordRoot, {
618
+ generation_id: v3Result.event.scope.generation_id,
619
+ event_id: v3Result.event.event_id,
620
+ ...(typeof data.tool_name === "string" ? { executable: data.tool_name } : {}),
621
+ intent_display: intent,
622
+ });
623
+ }
624
+ }
485
625
  appendDebug(coordRoot, {
486
626
  ...debugBase,
487
627
  event_type: norm.event_type,
488
628
  owner_source: owner.source,
489
- event_id: envelope.event_id,
629
+ ...(v3EventId ? { event_v3_id: v3EventId } : {}),
630
+ ...(v3Result ? { event_v3_state: v3Result.state } : {}),
631
+ ...(v3Result && "reason" in v3Result ? { event_v3_reason: v3Result.reason } : {}),
490
632
  });
491
633
  // Context telemetry is opportunistic and truthful: only persist a sample
492
634
  // when the adapter payload actually exposes usage/window data. Identical
@@ -502,22 +644,7 @@ async function main() {
502
644
  });
503
645
  if (sample) {
504
646
  const recorded = recordContextSample(coordRoot, owner.instance_id, sample);
505
- if (recorded.changed) {
506
- emit(coordRoot, {
507
- event_type: "context.sampled",
508
- instance_id: owner.instance_id,
509
- session_id: sessionId,
510
- adapter,
511
- data: {
512
- model: sample.model,
513
- used_tokens: sample.used_tokens,
514
- window_tokens: sample.window_tokens,
515
- used_percent: sample.used_percent,
516
- telemetry_source: sample.source,
517
- confidence: sample.confidence,
518
- },
519
- });
520
- }
647
+ void recorded;
521
648
  }
522
649
  }
523
650
  catch (err) {
@@ -527,7 +654,7 @@ async function main() {
527
654
  // A native pre-compaction signal is the safest available point to capture
528
655
  // external work state. The operation is idempotent until recovery, so a
529
656
  // adapter retry cannot create a storm of near-identical capsules.
530
- if (norm.event_type === "context.compaction.started") {
657
+ if (norm.event_type === "context.compaction_started") {
531
658
  try {
532
659
  const checkpoint = checkpointContext(coordRoot, {
533
660
  sessionId,
@@ -537,25 +664,13 @@ async function main() {
537
664
  reason: "pre_compact",
538
665
  model: payload?.model,
539
666
  });
540
- emit(coordRoot, {
541
- event_type: "context.checkpoint.created",
542
- instance_id: owner.instance_id,
543
- session_id: sessionId,
544
- adapter,
545
- data: {
546
- capsule_id: checkpoint.capsule.capsule_id,
547
- generation: checkpoint.capsule.generation,
548
- path: checkpoint.state.latest_capsule ?? "",
549
- reason: checkpoint.capsule.reason,
550
- reused: checkpoint.reused,
551
- },
552
- });
667
+ void checkpoint;
553
668
  }
554
669
  catch (err) {
555
670
  logError(coordRoot, err, { phase: "context-checkpoint" });
556
671
  }
557
672
  }
558
- if (norm.event_type === "context.compaction.completed") {
673
+ if (norm.event_type === "context.compaction_completed") {
559
674
  try {
560
675
  markContextCompactionCompleted(coordRoot, {
561
676
  sessionId,
@@ -571,31 +686,16 @@ async function main() {
571
686
  // systemMessage JSON (peer table + wiring check + council invites).
572
687
  // Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
573
688
  // and the equivalent per-adapter bash session_start handlers.
574
- if (norm.event_type === "session.start") {
689
+ if (norm.event_type === "session.started") {
575
690
  // Effect (claude-code): prune stale journal archives + sweep orphans.
576
691
  // The recovery-cue is merged into the
577
692
  // session-start additionalContext inside emitSessionStartSystemMessage.
578
693
  if (adapter === "claude-code")
579
694
  journalJanitor(coordRoot);
580
- // Image-feed retention sweep (size + age cap on .harnery/images/). Adapter-
581
- // agnostic, cheap (one readdir), fail-soft. Paired with journalJanitor as a
582
- // session-start "tidy the coord layer" step.
583
- try {
584
- imageJanitor(coordRoot);
585
- }
586
- catch (err) {
587
- logError(coordRoot, err, { phase: "session-start-image-janitor" });
588
- }
695
+ imageJanitor(coordRoot);
589
696
  let recovery = null;
590
697
  if (payload?.source === "compact") {
591
698
  try {
592
- emit(coordRoot, {
593
- event_type: "context.compaction.completed",
594
- instance_id: owner.instance_id,
595
- session_id: sessionId,
596
- adapter,
597
- data: { trigger: "auto" },
598
- });
599
699
  markContextCompactionCompleted(coordRoot, {
600
700
  sessionId,
601
701
  instanceId: owner.instance_id,
@@ -613,19 +713,16 @@ async function main() {
613
713
  try {
614
714
  const injected = await emitSessionStartSystemMessage(coordRoot, owner.instance_id, sessionId, data, adapter, recovery?.briefing ?? "");
615
715
  if (injected && recovery) {
616
- completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, adapter, recovery, "SessionStart");
716
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
617
717
  }
618
718
  }
619
719
  catch (err) {
620
720
  logError(coordRoot, err, { phase: "session-start-systemMessage" });
621
721
  }
622
- // Cross-machine presence (ADR 0016): announce this machine's sessions and
623
- // pull peers'. Both are throttled + fail-silent; network runs detached.
624
- // When a relay is configured, also make sure the live-socket daemon runs.
722
+ // Pull advisory cross-machine presence. V3 never publishes from the
723
+ // disposable heartbeat cache.
625
724
  try {
626
- publishPresence(coordRoot);
627
725
  fetchPresence(coordRoot);
628
- ensureRelayDaemon(coordRoot);
629
726
  }
630
727
  catch (err) {
631
728
  logError(coordRoot, err, { phase: "session-start-presence" });
@@ -633,7 +730,7 @@ async function main() {
633
730
  }
634
731
  // Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
635
732
  // agnostic since v0.5.0.
636
- if (norm.event_type === "session.end") {
733
+ if (norm.event_type === "session.ended") {
637
734
  try {
638
735
  cleanupSessionEnd(coordRoot, owner.instance_id, data.reason ?? "unknown");
639
736
  }
@@ -646,19 +743,11 @@ async function main() {
646
743
  journalArchive(coordRoot, owner.instance_id);
647
744
  runSessionSyncExtension(coordRoot, true);
648
745
  }
649
- // Presence: publish the post-cleanup state so peers see this session gone
650
- // promptly rather than waiting out the remote-stale window.
651
- try {
652
- publishPresence(coordRoot);
653
- }
654
- catch (err) {
655
- logError(coordRoot, err, { phase: "session-end-presence" });
656
- }
657
746
  }
658
747
  // Phase 8: SubagentStart: sync-project to create the subagent heartbeat,
659
748
  // log the lifecycle event, and emit a context message announcing the
660
749
  // subagent (claude-code + cursor; codex doesn't fan out subagents today).
661
- if (norm.event_type === "subagent.start") {
750
+ if (norm.event_type === "agent.started") {
662
751
  try {
663
752
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
664
753
  if (existsSync(agentCoordBin)) {
@@ -681,7 +770,7 @@ async function main() {
681
770
  }
682
771
  }
683
772
  // Phase 8: SubagentStop: delete subagent heartbeat + log.
684
- if (norm.event_type === "subagent.stop") {
773
+ if (norm.event_type === "agent.completed") {
685
774
  try {
686
775
  cleanupSessionEnd(coordRoot, owner.instance_id, data.reason ?? "unknown");
687
776
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
@@ -700,7 +789,7 @@ async function main() {
700
789
  }
701
790
  // Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
702
791
  // and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
703
- if (norm.event_type === "user_prompt.submit") {
792
+ if (norm.event_type === "turn.started") {
704
793
  // Effects (claude-code): reset per-turn sound rate-limit counters + run
705
794
  // presence detection on the prompt.
706
795
  if (adapter === "claude-code") {
@@ -735,74 +824,26 @@ async function main() {
735
824
  try {
736
825
  const injected = await emitUserPromptSubmitSystemMessage(coordRoot, owner.instance_id, sessionId, adapter, payload?.cwd, recovery?.briefing ?? "");
737
826
  if (injected && recovery) {
738
- completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, adapter, recovery, "UserPromptSubmit");
827
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
739
828
  }
740
829
  }
741
830
  catch (err) {
742
831
  logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
743
832
  }
744
833
  }
745
- // turn.stop: telemetry + turn-summary effects, then the stop verdict. The
834
+ // turn.completed: telemetry, then the stop verdict. The
746
835
  // verdict + codex-replay previously lived in the per-adapter shell adapters;
747
836
  // agent-hook owns them now. Runs on the normal "stop" event only;
748
837
  // "stop-failure" (API error) gets no gate, matching the previous
749
838
  // stop vs stop-failure split.
750
- if (norm.event_type === "turn.stop" && eventName === "stop") {
751
- // Codex: replay the JSONL transcript → canonical events so the verdict has
752
- // the status_checked / task_set / status_box_present evidence (codex
753
- // doesn't emit those live; this re-emits turn.stop after agent-hook's own,
754
- // so the verdict reads the replay's box signal as the latest).
755
- if (adapter === "codex" && payload?.transcript_path && existsSync(payload.transcript_path)) {
756
- try {
757
- replayCodexJsonl({
758
- coordRoot,
759
- jsonlPath: payload.transcript_path,
760
- sessionId,
761
- instanceId: owner.instance_id,
762
- lastAssistantMessage: payload.raw.last_assistant_message ?? "",
763
- });
764
- }
765
- catch (err) {
766
- logError(coordRoot, err, { phase: "codex-replay" });
767
- }
768
- }
769
- // CC effects: rate-limited session-telemetry sync + turn-summary Haiku
770
- // auto-summary.
839
+ if (norm.event_type === "turn.completed" && eventName === "stop") {
840
+ // Claude Code session telemetry sync remains an independent side effect.
771
841
  if (adapter === "claude-code") {
772
842
  runSessionSyncExtension(coordRoot, false);
773
- runTurnSummary(coordRoot, owner.instance_id, sessionId, payload?.transcript_path);
774
- }
775
- // Master-state heartbeat projection. Drains events.ndjson since the last
776
- // cursor → per-owner heartbeats. Was a SECOND binary (`agent-coord project`)
777
- // pinned to Claude Code Stop only; folded in here so it (a) is one
778
- // entry per event like everything else and (b) fires on EVERY adapter's stop,
779
- // not just CC. Runs unconditionally before the verdict's possible exit-2 return
780
- // (the events are real regardless of whether the agent gets nagged), and after
781
- // codex-replay above so codex's replayed events are included in the drain.
782
- // Not an emitter (consumes + writes heartbeats), so no emitter/consumer conflict.
783
- try {
784
- const result = consumeSince(coordRoot);
785
- projectHeartbeats(coordRoot, result.events);
786
- if (result.lastEventId)
787
- writeCursor(coordRoot, result.lastEventId);
788
843
  }
789
- catch (err) {
790
- logError(coordRoot, err, { phase: "stop-projection" });
791
- }
792
- // Presence: publish AFTER the projection above so the blob carries this
793
- // turn's fresh task/turn_summary/files. Change-batched + keepalive'd;
794
- // the push itself runs detached. The relay daemon (when configured) is
795
- // re-ensured here too — it self-exits on idle, and the next turn revives it.
796
- try {
797
- publishPresence(coordRoot);
798
- ensureRelayDaemon(coordRoot);
799
- }
800
- catch (err) {
801
- logError(coordRoot, err, { phase: "stop-presence" });
802
- }
803
- // Stop verdict (status-box + set-task gate). Direct in-process call: the
804
- // rule lives in harnery. agent-hook already emitted this turn.stop (with
805
- // status_box_present) above, so the evidence is in the stream.
844
+ // Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
845
+ // rule lives in Harnery. agent-hook already emitted this turn.completed
846
+ // with privacy-safe ritual observations, so the evidence is in the stream.
806
847
  const verdict = evaluateStopHook(coordRoot, {
807
848
  rule: "stop-hook",
808
849
  instance_id: owner.instance_id,
@@ -812,13 +853,26 @@ async function main() {
812
853
  workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
813
854
  });
814
855
  if (!verdict.allow) {
815
- // Adapter-aware enforcement channel: Claude Code / Codex honor exit-2 +
816
- // stderr as a turn block; Cursor ignores exit codes (fail-open) and
817
- // re-prompts only via a `followup_message` it auto-submits. emitStopBlock
818
- // writes the right shape and returns the exit code to use.
856
+ // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
857
+ // as a turn block; Cursor ignores exit codes and re-prompts only via a
858
+ // `followup_message` it auto-submits. Codex returned observe-only above.
859
+ // emitStopBlock writes the right shape and returns the exit code to use.
819
860
  const { emitStopBlock } = await import("./adapter/output.js");
820
861
  return emitStopBlock(adapter, verdict, coordRoot);
821
862
  }
863
+ // An explicit end requested from inside this turn cannot be authoritative
864
+ // until the adapter has committed turn.completed above. Reconcile only
865
+ // when such a request exists; any later real work cancels it in the
866
+ // finalizer instead of being terminated underneath the agent.
867
+ try {
868
+ const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.js");
869
+ if (hasPendingExplicitSessionEndV3(coordRoot)) {
870
+ reconcileSessionFinalizationV3(coordRoot);
871
+ }
872
+ }
873
+ catch (err) {
874
+ logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
875
+ }
822
876
  }
823
877
  // Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
824
878
  // Adapter-agnostic: both writes have the same shape regardless of who fired.
@@ -829,40 +883,32 @@ async function main() {
829
883
  // side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
830
884
  // preserved the heartbeat half but dropped the pid-map half; the pid-map
831
885
  // call was restored here afterward.
832
- if (norm.event_type === "tool.pre_use") {
886
+ if (norm.event_type === "tool.requested") {
833
887
  try {
834
888
  // Recorded fork lineage, heal-path flavor. A forked CC conversation
835
- // never fires its own session.start (SessionStart fires under the
889
+ // never fires its own session.started (SessionStart fires under the
836
890
  // PARENT's session id with source=resume, before the fork id is
837
891
  // minted; verified 2026-08-05), so the fork's new instance first
838
892
  // materializes right here. Gate detection on "no heartbeat yet" so the
839
893
  // transcript scan runs once per instance lifetime, not per tool call.
840
- let forkedFrom;
841
- if (adapter === "claude-code" &&
842
- payload?.transcript_path &&
843
- !existsSync(join(coordRoot, ".harnery", "active", `${owner.instance_id}.json`))) {
844
- forkedFrom = detectForkParent(payload.transcript_path, owner.instance_id);
845
- }
846
- healHeartbeatViaCli(coordRoot, owner.instance_id, sessionId, adapter, forkedFrom);
847
894
  refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
848
895
  }
849
896
  catch (err) {
850
897
  logError(coordRoot, err, { phase: "pre-tool-use-heal" });
851
898
  }
852
- // Image feed: a Read on an image file is the "agent viewed this" signal.
853
- // Capture the bytes (content-addressed, dedup'd) + emit image.captured.
899
+ // The suggested name is a pending display latch. The set-task call itself
900
+ // runs before a name exists; every later tool waits until the exact block
901
+ // is the first assistant text after the mint result. Cursor supplies
902
+ // agent_message directly; Claude Code and Codex are resolved from their
903
+ // JSONL transcripts. Later commentary must not erase an already-correct
904
+ // display, and an unreadable transcript must not deadlock every tool.
854
905
  try {
855
- captureImages(coordRoot, {
856
- eventType: "tool.pre_use",
857
- data,
858
- payload,
859
- instanceId: owner.instance_id,
860
- sessionId,
861
- adapter,
862
- });
906
+ const displayAllowed = await enforcePendingSessionNameDisplay(coordRoot, owner.instance_id, adapter, payload);
907
+ if (!displayAllowed)
908
+ return 0;
863
909
  }
864
910
  catch (err) {
865
- logError(coordRoot, err, { phase: "pre-tool-use-image-capture" });
911
+ logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
866
912
  }
867
913
  // Windows-native Codex + WSL UNC only: block the one cross-shell shape
868
914
  // proven to corrupt argument boundaries. Normal WSL argv calls, literal
@@ -875,13 +921,6 @@ async function main() {
875
921
  toolInput: payload?.tool_input,
876
922
  });
877
923
  if (unsafeShellReason) {
878
- emit(coordRoot, {
879
- event_type: "decision.block",
880
- instance_id: owner.instance_id,
881
- session_id: sessionId,
882
- adapter,
883
- data: { rule: "unsafe_cross_shell", reason: unsafeShellReason },
884
- });
885
924
  const { emitDeny } = await import("./adapter/output.js");
886
925
  emitDeny(adapter, unsafeShellReason);
887
926
  return 0;
@@ -894,77 +933,36 @@ async function main() {
894
933
  // apply_patch (codex) parses paths from the patch body and runs verdict
895
934
  // per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
896
935
  // (incl. Agent) yield no targets and pass through with no deny.
936
+ let guardAllowed = true;
897
937
  try {
898
- await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
938
+ guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
899
939
  }
900
940
  catch (err) {
901
941
  logError(coordRoot, err, { phase: "pre-tool-use-guard" });
902
942
  }
903
- // Shell-mutation warn (warn-only, never blocks). Was the cursor
904
- // beforeShellExecution + codex preToolUse-Bash shell-mutation-claim-log in
905
- // the per-adapter shell adapters. Cursor sends the command at payload.command;
906
- // codex Bash at tool_input.command. Emits a decision.warn per candidate-mutated
907
- // path so a peer sees the write in events.ndjson. (CC never did this,
908
- // preserved; it emits with its own hooks-side emitter per the
909
- // independent-emitter rule.)
910
- const shellCmd = eventName === "before-shell-execution"
911
- ? (payload?.raw.command ?? "")
912
- : adapter === "codex" && data.tool_name === "Bash"
913
- ? (payload?.raw.tool_input?.command ?? "")
914
- : "";
915
- if (shellCmd) {
916
- try {
917
- const paths = shellMutationPaths(shellCmd, coordRoot);
918
- const truncated = shellCmd.length > 80 ? shellCmd.slice(0, 80) : shellCmd;
919
- const platform = adapterPlatform(adapter);
920
- for (const p of paths) {
921
- emit(coordRoot, {
922
- event_type: "decision.warn",
923
- instance_id: owner.instance_id,
924
- session_id: sessionId,
925
- adapter,
926
- data: {
927
- rule: "shell_mutation_candidate",
928
- reason: `path=${p} cmd=${truncated} platform=${platform}`,
929
- },
930
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
931
- });
932
- }
933
- }
934
- catch (err) {
935
- logError(coordRoot, err, { phase: "shell-mutation-warn" });
936
- }
937
- }
943
+ if (!guardAllowed)
944
+ return 0;
938
945
  }
939
- // Phase 7: PostToolUse: stamp last_tool + last_tool_target on heartbeat.
940
- // Adapter-agnostic for the same reason as tool.pre_use above.
941
- if (norm.event_type === "tool.post_use") {
946
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use") {
947
+ // Inject only at the successful tool boundary that actually minted a name:
948
+ // the first non-empty set-task or the transition to lifecycle done. The
949
+ // coordination row intentionally remains pending until transcript evidence
950
+ // catches up; reading that latch alone here would re-inject after every
951
+ // later tool and make the agent print the same block repeatedly.
942
952
  try {
943
- stampToolActivity(coordRoot, owner.instance_id, data);
944
- }
945
- catch (err) {
946
- logError(coordRoot, err, { phase: "post-tool-use-stamp" });
947
- }
948
- // Image feed: a Bash command that wrote an image (harn browse, harn image,
949
- // --diff, …) is the "agent produced this" signal. Scan the command + its
950
- // output for freshly-written image paths and capture them.
951
- try {
952
- captureImages(coordRoot, {
953
- eventType: "tool.post_use",
954
- data,
955
- payload,
956
- instanceId: owner.instance_id,
957
- sessionId,
958
- adapter,
959
- });
953
+ const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, owner.instance_id));
954
+ if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
955
+ const { emitContext } = await import("./adapter/output.js");
956
+ emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
957
+ }
960
958
  }
961
959
  catch (err) {
962
- logError(coordRoot, err, { phase: "post-tool-use-image-capture" });
960
+ logError(coordRoot, err, { phase: "post-tool-use-session-name" });
963
961
  }
964
962
  }
965
963
  // Phase 7: PostToolUseFailure: release claim on failed Edit (the file
966
964
  // never landed; the claim is stale). Adapter-agnostic.
967
- if (norm.event_type === "tool.post_use_failure") {
965
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure") {
968
966
  try {
969
967
  releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
970
968
  }
@@ -974,11 +972,38 @@ async function main() {
974
972
  }
975
973
  return 0;
976
974
  }
975
+ async function enforcePendingSessionNameDisplay(coordRoot, instanceId, adapter, payload) {
976
+ const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, instanceId));
977
+ if (!name)
978
+ return true;
979
+ const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
980
+ if (isSessionNameRemediationCommand(command, resolveBinName(coordRoot)))
981
+ return true;
982
+ const inspection = payload?.agent_message !== undefined
983
+ ? {
984
+ state: assistantTextStartsWithSessionNameBlock(payload.agent_message, name)
985
+ ? "present"
986
+ : "absent",
987
+ }
988
+ : inspectSessionNameDisplayImmediately(payload?.transcript_path, name, assistantTextStartsWithSessionNameBlock);
989
+ if (inspection.state === "present") {
990
+ stampSessionNameSeen(coordRoot, instanceId, name);
991
+ return true;
992
+ }
993
+ if (inspection.state === "unavailable") {
994
+ const { emitContext } = await import("./adapter/output.js");
995
+ emitContext(adapter, "PreToolUse", "Harnery could not verify the pending session-name display because the adapter transcript is unavailable or not flushed yet. This tool is allowed so the session cannot deadlock; display the requested block before later work. Verification remains pending.");
996
+ return true;
997
+ }
998
+ const { emitDeny } = await import("./adapter/output.js");
999
+ emitDeny(adapter, sessionNameDisplayInstruction(name));
1000
+ return false;
1001
+ }
977
1002
  async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapter) {
978
1003
  const toolName = data.tool_name ?? "";
979
1004
  const rawTargets = collectGuardTargets(toolName, data);
980
1005
  if (rawTargets.length === 0)
981
- return;
1006
+ return true;
982
1007
  let targets;
983
1008
  if (agentsRequireGitFinalization(coordRoot)) {
984
1009
  const decisions = rawTargets.map((path) => classifyWriteClaimFinalization(coordRoot, path));
@@ -986,7 +1011,7 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
986
1011
  if (denied && !denied.allow) {
987
1012
  const { emitDeny } = await import("./adapter/output.js");
988
1013
  emitDeny(adapter, formatWriteClaimFinalizationDenial(denied, resolveBinName(coordRoot)));
989
- return;
1014
+ return false;
990
1015
  }
991
1016
  const allowed = decisions.filter((decision) => decision.allow);
992
1017
  targets = allowed.map((decision) => ({
@@ -1001,14 +1026,15 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
1001
1026
  .map((path) => ({ path }));
1002
1027
  }
1003
1028
  if (targets.length === 0)
1004
- return;
1029
+ return true;
1005
1030
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1006
1031
  if (!existsSync(agentCoordBin)) {
1007
1032
  if (agentsRequireGitFinalization(coordRoot)) {
1008
1033
  const { emitDeny } = await import("./adapter/output.js");
1009
1034
  emitDeny(adapter, "Harnery denied the write before mutation because the claim coordinator is unavailable; repair the project's Harnery installation and retry.");
1035
+ return false;
1010
1036
  }
1011
- return;
1037
+ return true;
1012
1038
  }
1013
1039
  // For apply_patch (multi-file), collect siblings so the deny reason names
1014
1040
  // them. For single-file tools the array has one entry.
@@ -1048,26 +1074,10 @@ async function runPreToolUseGuard(coordRoot, instanceId, sessionId, data, adapte
1048
1074
  }
1049
1075
  const { emitDeny } = await import("./adapter/output.js");
1050
1076
  emitDeny(adapter, reason);
1051
- return;
1052
- }
1053
- // The verdict mutates the live heartbeat immediately, but that file is a
1054
- // projection and can be rebuilt at any later hook. Persist the acquisition
1055
- // in the canonical stream too. This matters most for Codex apply_patch:
1056
- // one tool call can claim several files, and tool.pre_use's clamped payload
1057
- // is not a durable ownership representation for those targets.
1058
- emit(coordRoot, {
1059
- event_type: "claim.acquire",
1060
- instance_id: instanceId,
1061
- session_id: sessionId,
1062
- adapter,
1063
- source: "agent-hooks",
1064
- data: {
1065
- path: target.path,
1066
- mode: "write",
1067
- ...(target.finalization ? { finalization: target.finalization } : {}),
1068
- },
1069
- });
1077
+ return false;
1078
+ }
1070
1079
  }
1080
+ return true;
1071
1081
  }
1072
1082
  /** Canonicalize a path to monorepo-relative form. Absolute paths under
1073
1083
  * coordRoot get the prefix stripped; relative paths pass through (assumed
@@ -1125,28 +1135,10 @@ function parseApplyPatchPaths(data) {
1125
1135
  }
1126
1136
  return out;
1127
1137
  }
1128
- function healHeartbeatViaCli(coordRoot, instanceId, sessionId, adapter, forkedFrom) {
1129
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1130
- if (!existsSync(agentCoordBin))
1131
- return;
1132
- // Pass the detected adapter so a pruned Cursor/Codex heartbeat is recreated
1133
- // with the correct platform; without it, healHeartbeat defaults to
1134
- // claude-code and the dashboard mislabels the agent. See
1135
- // heartbeat-writer.healHeartbeat. --forked-from carries detected fork
1136
- // lineage for an instance that never session.started (the CC fork flow).
1137
- const args = ["heal-heartbeat", instanceId, sessionId, `--adapter=${adapter}`];
1138
- if (forkedFrom)
1139
- args.push(`--forked-from=${forkedFrom}`);
1140
- spawnSync(agentCoordBin, args, {
1141
- encoding: "utf8",
1142
- timeout: 2000,
1143
- env: childEnv(coordRoot),
1144
- });
1145
- }
1146
1138
  /**
1147
1139
  * Walk up the ppid chain on Linux/WSL looking for the adapter anchor PID,
1148
1140
  * the PID of the claude / cursor / codex binary. Finds the agent PID. Used by
1149
- * `tool.pre_use`'s pid-map self-heal so a re-parented adapter binary (the
1141
+ * `tool.requested` pid-map self-heal so a re-parented adapter binary (the
1150
1142
  * VS Code 2.1.x sibling-claude spawn case) gets its pid-map row rewritten on
1151
1143
  * the next tool call rather than going invisible until SessionStart fires
1152
1144
  * again, which it may never do.
@@ -1219,9 +1211,7 @@ function findAdapterAnchorPid(adapter) {
1219
1211
  return selectAnchorPid(chain, adapter);
1220
1212
  }
1221
1213
  /**
1222
- * Pid-map self-heal for `tool.pre_use`. Symmetric counterpart to
1223
- * `healHeartbeatViaCli`; the two were paired before the Phase 6 refactor split
1224
- * them apart, then restored together afterward.
1214
+ * Pid-map self-heal for `tool.requested`.
1225
1215
  *
1226
1216
  * The pid argument prefers the payload's `pid` (CC populates it on
1227
1217
  * SessionStart and may also send it on PreToolUse), then
@@ -1239,38 +1229,6 @@ function refreshPidmap(coordRoot, instanceId, adapter, payloadPid) {
1239
1229
  return;
1240
1230
  writePidmapViaAgentCoord(coordRoot, pid, instanceId, adapterPlatform(adapter));
1241
1231
  }
1242
- function stampToolActivity(coordRoot, instanceId, data) {
1243
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1244
- if (!existsSync(agentCoordBin))
1245
- return;
1246
- const toolName = data.tool_name ?? "";
1247
- // Extract a 1-line target from the tool_input blob (file path / command head).
1248
- const toolInputRaw = data.tool_input;
1249
- let target = "";
1250
- if (typeof toolInputRaw === "string") {
1251
- try {
1252
- const parsed = JSON.parse(toolInputRaw);
1253
- target =
1254
- parsed.file_path ??
1255
- parsed.path ??
1256
- parsed.notebook_path ??
1257
- parsed.command ??
1258
- parsed.url ??
1259
- parsed.pattern ??
1260
- "";
1261
- }
1262
- catch {
1263
- /* skip */
1264
- }
1265
- }
1266
- if (target.length > 200)
1267
- target = target.slice(0, 200);
1268
- spawnSync(agentCoordBin, ["stamp-tool-activity", instanceId, toolName, target], {
1269
- encoding: "utf8",
1270
- timeout: 2000,
1271
- env: childEnv(coordRoot),
1272
- });
1273
- }
1274
1232
  function releaseClaimOnFailure(coordRoot, instanceId, data, rawPayload) {
1275
1233
  const toolName = data.tool_name ?? "";
1276
1234
  if (toolName !== "Edit" && toolName !== "Write" && toolName !== "NotebookEdit")
@@ -1310,6 +1268,12 @@ function releaseClaimOnFailure(coordRoot, instanceId, data, rawPayload) {
1310
1268
  ? filePath.slice(coordRoot.length + 1)
1311
1269
  : filePath;
1312
1270
  }
1271
+ // In V3 the release is an authority event, not a heartbeat mutation. Avoid
1272
+ // creating a disposable cache for a failure that never acquired a claim,
1273
+ // but release the exact path when the validated projection says it is held.
1274
+ const row = readLiveCoordinationRow(coordRoot, instanceId);
1275
+ if (!row?.files_touched?.includes(canonical))
1276
+ return;
1313
1277
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1314
1278
  if (!existsSync(agentCoordBin))
1315
1279
  return;
@@ -1320,16 +1284,6 @@ function releaseClaimOnFailure(coordRoot, instanceId, data, rawPayload) {
1320
1284
  });
1321
1285
  }
1322
1286
  function cleanupSessionEnd(coordRoot, instanceId, reason) {
1323
- // Remove heartbeat from the canonical .harnery/active/ dir.
1324
- const path = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
1325
- try {
1326
- if (existsSync(path)) {
1327
- require("node:fs").unlinkSync(path);
1328
- }
1329
- }
1330
- catch {
1331
- /* swallow */
1332
- }
1333
1287
  // Sweep pid-map entries pointing to this instance
1334
1288
  const pidmapDir = join(coordRoot, ".harnery", "pid-map");
1335
1289
  if (existsSync(pidmapDir)) {
@@ -1363,32 +1317,22 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
1363
1317
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1364
1318
  let additionalContext = "";
1365
1319
  if (existsSync(agentCoordBin)) {
1366
- // Look up the agent's name from its heartbeat (for council pending rendering).
1367
- let agentName = "";
1368
- try {
1369
- const fs = require("node:fs");
1370
- const hbPath = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
1371
- if (fs.existsSync(hbPath)) {
1372
- const hb = JSON.parse(fs.readFileSync(hbPath, "utf8"));
1373
- agentName = hb.name ?? "";
1374
- }
1375
- }
1376
- catch {
1377
- /* fall through with empty name; peer table still renders */
1378
- }
1379
1320
  const args = ["prompt-context", "--instance", instanceId, "--session", sessionId];
1380
- if (agentName)
1381
- args.push("--name", agentName);
1382
1321
  // Every human-facing adapter gets the first-session naming reminder from
1383
1322
  // the shared hook path. Cursor + Codex additionally get ongoing set-task
1384
1323
  // staleness nudges; Claude Code enforces those via its Stop transcript.
1385
1324
  // Codex gets a fresh status-footer reminder on every prompt. Its Stop hook
1386
1325
  // stays observe-only, so missing the footer never replaces the answer.
1326
+ // Claude Code + Cursor (the Stop-ENFORCING adapters) get a fresh per-turn
1327
+ // ritual reminder: satisfying the contract up front is far cheaper than
1328
+ // the bounce-and-retry the Stop hook otherwise forces.
1387
1329
  args.push("--session-name-nudge");
1388
1330
  if (adapter === "cursor" || adapter === "codex")
1389
1331
  args.push("--task-nudge");
1390
1332
  if (adapter === "codex")
1391
1333
  args.push("--status-footer-nudge");
1334
+ if (adapter === "claude-code" || adapter === "cursor")
1335
+ args.push("--turn-ritual-nudge", adapter);
1392
1336
  const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1393
1337
  if (result.status === 0 && result.stdout)
1394
1338
  additionalContext = result.stdout.trim();
@@ -1410,7 +1354,7 @@ async function emitUserPromptSubmitSystemMessage(coordRoot, instanceId, sessionI
1410
1354
  }
1411
1355
  function emitSubagentStartContext(coordRoot, instanceId, sessionId, data, adapter) {
1412
1356
  // Look up the subagent's assigned name (just-written by agent-coord assignName
1413
- // in session.start data) + the parent's short id for the "you are a subagent
1357
+ // in session.started data) + the parent's short id for the "you are a subagent
1414
1358
  // of X" framing.
1415
1359
  const subagentName = data.name ?? "";
1416
1360
  if (!subagentName)
@@ -1442,13 +1386,19 @@ function adapterPlatform(adapter) {
1442
1386
  }
1443
1387
  async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, emittedData, adapter, recoveryBriefing = "") {
1444
1388
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1389
+ const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
1445
1390
  let additionalContext = "";
1446
1391
  if (existsSync(agentCoordBin)) {
1447
1392
  // Sync-project so the heartbeat exists for downstream readers (peer table,
1448
1393
  // wiring check, council invites).
1449
1394
  spawnSync(agentCoordBin, ["project"], { encoding: "utf8", timeout: 3000 });
1450
- // Stale-sweep dead peers before rendering peer table.
1451
- spawnSync(agentCoordBin, ["stale-sweep"], { encoding: "utf8", timeout: 3000 });
1395
+ // Opportunistic reconciliation makes normal session starts a failsafe for
1396
+ // archive, idle, cascade, and host lifecycle observations.
1397
+ spawnSync(agentCoordBin, ["reconcile-finalization"], {
1398
+ encoding: "utf8",
1399
+ timeout: 5000,
1400
+ env: { ...process.env, HARNERY_COORD_ROOT_OVERRIDE: coordRoot },
1401
+ });
1452
1402
  // SESSION_START activity log line, fired across all adapters.
1453
1403
  const model = emittedData.model ?? "unknown";
1454
1404
  const source = emittedData.source ?? "startup";
@@ -1459,34 +1409,39 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
1459
1409
  "--instance",
1460
1410
  instanceId,
1461
1411
  ], { encoding: "utf8", timeout: 2000 });
1462
- // Render the systemMessage via agent-coord.
1463
- const agentName = emittedData.name ?? "";
1464
- const args = ["session-context", "--instance", instanceId, "--session", sessionId];
1465
- if (agentName)
1466
- args.push("--name", agentName);
1467
- // The "You are agent-X." prefix in session-context renders unqualified by
1468
- // default (claude-code-style). For cursor/codex the bash dispatchers add
1469
- // a "(Cursor)" / "(Codex)" suffix; pass it through as --platform-label.
1470
- if (adapter !== "claude-code") {
1471
- args.push("--platform-label", platform === "cursor" ? "Cursor" : "Codex");
1412
+ // Workflow children retain lifecycle/event capture but do not receive
1413
+ // operator-facing peer, council, or init-remediation context. Injecting
1414
+ // that context can make a bounded child follow housekeeping instructions
1415
+ // instead of its assigned prompt.
1416
+ if (!workflowChild) {
1417
+ const agentName = emittedData.name ?? "";
1418
+ const args = ["session-context", "--instance", instanceId, "--session", sessionId];
1419
+ if (agentName)
1420
+ args.push("--name", agentName);
1421
+ // The "You are agent-X." prefix in session-context renders unqualified by
1422
+ // default (claude-code-style). For cursor/codex the bash dispatchers add
1423
+ // a "(Cursor)" / "(Codex)" suffix; pass it through as --platform-label.
1424
+ if (adapter !== "claude-code") {
1425
+ args.push("--platform-label", platform === "cursor" ? "Cursor" : "Codex");
1426
+ }
1427
+ const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1428
+ if (result.status === 0 && result.stdout)
1429
+ additionalContext = result.stdout.trim();
1472
1430
  }
1473
- const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1474
- if (result.status === 0 && result.stdout)
1475
- additionalContext = result.stdout.trim();
1476
1431
  }
1477
1432
  // Effect (claude-code): merge the journal recovery cue into the session-start
1478
1433
  // context. Was a standalone additionalContext emission from the previous
1479
1434
  // journal-on-start adapter; now that agent-hook is the single SessionStart
1480
1435
  // entry, it folds in here.
1481
- if (adapter === "claude-code") {
1436
+ if (adapter === "claude-code" && !workflowChild) {
1482
1437
  const cue = journalRecoveryCue(coordRoot);
1483
1438
  if (cue)
1484
1439
  additionalContext = [additionalContext, cue].filter(Boolean).join("\n\n");
1485
1440
  }
1486
- if (recoveryBriefing) {
1441
+ if (recoveryBriefing && !workflowChild) {
1487
1442
  additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
1488
1443
  }
1489
- if (adapter === "codex" && isWslUncPath(emittedData.cwd)) {
1444
+ if (adapter === "codex" && !workflowChild && isWslUncPath(emittedData.cwd)) {
1490
1445
  const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, emittedData.cwd);
1491
1446
  if (fileLinkContext) {
1492
1447
  additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
@@ -1507,19 +1462,9 @@ async function emitSessionStartSystemMessage(coordRoot, instanceId, sessionId, e
1507
1462
  emitContext(adapter, "SessionStart", additionalContext);
1508
1463
  return true;
1509
1464
  }
1510
- function completeRecoveryInjection(coordRoot, instanceId, sessionId, adapter, recovery, injectionEvent) {
1465
+ function completeRecoveryInjection(coordRoot, instanceId, sessionId, recovery) {
1511
1466
  completeContextRecovery(coordRoot, { sessionId, instanceId });
1512
- emit(coordRoot, {
1513
- event_type: "context.recovery.injected",
1514
- instance_id: instanceId,
1515
- session_id: sessionId,
1516
- adapter,
1517
- data: {
1518
- capsule_id: recovery.capsule.capsule_id,
1519
- generation: recovery.capsule.generation,
1520
- injection_event: injectionEvent,
1521
- },
1522
- });
1467
+ void recovery;
1523
1468
  }
1524
1469
  main()
1525
1470
  .then((code) => process.exit(code))