harnery 0.33.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 (657) hide show
  1. package/dist/commander.d.ts.map +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/adapter.js +2 -2
  4. package/dist/commands/agents.d.ts +144 -1
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +1220 -588
  7. package/dist/commands/artifacts.d.ts.map +1 -1
  8. package/dist/commands/artifacts.js +7 -6
  9. package/dist/commands/backup.d.ts +1 -1
  10. package/dist/commands/backup.d.ts.map +1 -1
  11. package/dist/commands/backup.js +29 -16
  12. package/dist/commands/browse.d.ts.map +1 -1
  13. package/dist/commands/browse.js +234 -12
  14. package/dist/commands/checkpoint.d.ts.map +1 -1
  15. package/dist/commands/checkpoint.js +3 -2
  16. package/dist/commands/claude-desktop.d.ts +1 -1
  17. package/dist/commands/claude-desktop.d.ts.map +1 -1
  18. package/dist/commands/claude-desktop.js +3 -74
  19. package/dist/commands/context.js +1 -6
  20. package/dist/commands/decision.d.ts.map +1 -1
  21. package/dist/commands/decision.js +22 -38
  22. package/dist/commands/docs.d.ts.map +1 -1
  23. package/dist/commands/docs.js +91 -28
  24. package/dist/commands/doctor.js +10 -1
  25. package/dist/commands/events.d.ts +5 -0
  26. package/dist/commands/events.d.ts.map +1 -0
  27. package/dist/commands/events.js +189 -0
  28. package/dist/commands/governor.d.ts.map +1 -1
  29. package/dist/commands/governor.js +13 -4
  30. package/dist/commands/init.d.ts +9 -2
  31. package/dist/commands/init.d.ts.map +1 -1
  32. package/dist/commands/init.js +223 -51
  33. package/dist/commands/journal.d.ts.map +1 -1
  34. package/dist/commands/journal.js +4 -13
  35. package/dist/commands/ledger-v3.d.ts +5 -0
  36. package/dist/commands/ledger-v3.d.ts.map +1 -0
  37. package/dist/commands/ledger-v3.js +91 -0
  38. package/dist/commands/presence.d.ts.map +1 -1
  39. package/dist/commands/presence.js +11 -5
  40. package/dist/commands/sync.d.ts +1 -1
  41. package/dist/commands/sync.js +1 -1
  42. package/dist/commands/web.d.ts +6 -0
  43. package/dist/commands/web.d.ts.map +1 -1
  44. package/dist/commands/web.js +67 -7
  45. package/dist/commands/work.d.ts.map +1 -1
  46. package/dist/commands/work.js +22 -5
  47. package/dist/core/adapter.d.ts +5 -0
  48. package/dist/core/adapter.d.ts.map +1 -0
  49. package/dist/core/adapter.js +1 -0
  50. package/dist/core/adapters/attest.d.ts +5 -2
  51. package/dist/core/adapters/attest.d.ts.map +1 -1
  52. package/dist/core/adapters/attest.js +8 -3
  53. package/dist/core/adapters/profiles.d.ts +6 -6
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +4 -4
  56. package/dist/core/agents/canonical-emit.d.ts +17 -15
  57. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  58. package/dist/core/agents/canonical-emit.js +22 -22
  59. package/dist/core/agents/cli.js +207 -234
  60. package/dist/core/agents/codex-archive-v3.d.ts +14 -0
  61. package/dist/core/agents/codex-archive-v3.d.ts.map +1 -0
  62. package/dist/core/agents/codex-archive-v3.js +110 -0
  63. package/dist/core/agents/coord-client.d.ts +19 -81
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +65 -155
  66. package/dist/core/agents/finalization.d.ts +4 -1
  67. package/dist/core/agents/finalization.d.ts.map +1 -1
  68. package/dist/core/agents/finalization.js +89 -32
  69. package/dist/core/agents/index.d.ts +4 -5
  70. package/dist/core/agents/index.d.ts.map +1 -1
  71. package/dist/core/agents/index.js +4 -5
  72. package/dist/core/agents/live-authority-v3.d.ts +59 -0
  73. package/dist/core/agents/live-authority-v3.d.ts.map +1 -0
  74. package/dist/core/agents/live-authority-v3.js +233 -0
  75. package/dist/core/agents/live-lifecycle-v3.d.ts +20 -0
  76. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -0
  77. package/dist/core/agents/live-lifecycle-v3.js +91 -0
  78. package/dist/core/agents/live-observation-v3.d.ts +25 -0
  79. package/dist/core/agents/live-observation-v3.d.ts.map +1 -0
  80. package/dist/core/agents/live-observation-v3.js +157 -0
  81. package/dist/core/agents/render/prompt-context.d.ts +4 -4
  82. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  83. package/dist/core/agents/render/prompt-context.js +23 -58
  84. package/dist/core/agents/render/session-context.d.ts +1 -1
  85. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  86. package/dist/core/agents/render/session-context.js +8 -32
  87. package/dist/core/agents/rules/claim-conflict.d.ts +3 -3
  88. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  89. package/dist/core/agents/rules/claim-conflict.js +79 -127
  90. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  91. package/dist/core/agents/rules/commit-conflict.js +3 -18
  92. package/dist/core/agents/rules/stop-hook.d.ts +9 -50
  93. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  94. package/dist/core/agents/rules/stop-hook.js +193 -236
  95. package/dist/core/agents/session-events.d.ts +5 -7
  96. package/dist/core/agents/session-events.d.ts.map +1 -1
  97. package/dist/core/agents/session-events.js +127 -95
  98. package/dist/core/agents/session-finalization-state-v3.d.ts +30 -0
  99. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -0
  100. package/dist/core/agents/session-finalization-state-v3.js +40 -0
  101. package/dist/core/agents/session-finalizer-v3.d.ts +107 -0
  102. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -0
  103. package/dist/core/agents/session-finalizer-v3.js +734 -0
  104. package/dist/core/agents/session-name-display.d.ts +40 -0
  105. package/dist/core/agents/session-name-display.d.ts.map +1 -0
  106. package/dist/core/agents/session-name-display.js +119 -0
  107. package/dist/core/agents/state/activity-log.d.ts +3 -3
  108. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  109. package/dist/core/agents/state/activity-log.js +7 -16
  110. package/dist/core/agents/state/heartbeat-writer.d.ts +23 -55
  111. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  112. package/dist/core/agents/state/heartbeat-writer.js +56 -228
  113. package/dist/core/agents/state/live-coordination-view.d.ts +31 -0
  114. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -0
  115. package/dist/core/agents/state/live-coordination-view.js +209 -0
  116. package/dist/core/agents/state/session-state.d.ts +4 -3
  117. package/dist/core/agents/state/session-state.d.ts.map +1 -1
  118. package/dist/core/agents/state/session-state.js +18 -9
  119. package/dist/core/agents/state/stale-sweep.d.ts +1 -1
  120. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  121. package/dist/core/agents/state/stale-sweep.js +26 -15
  122. package/dist/core/artifacts/index.d.ts.map +1 -1
  123. package/dist/core/artifacts/index.js +5 -7
  124. package/dist/core/config.d.ts +33 -2
  125. package/dist/core/config.d.ts.map +1 -1
  126. package/dist/core/config.js +98 -2
  127. package/dist/core/context/index.d.ts +1 -4
  128. package/dist/core/context/index.d.ts.map +1 -1
  129. package/dist/core/context/index.js +9 -9
  130. package/dist/core/events/span-parent.d.ts +15 -0
  131. package/dist/core/events/span-parent.d.ts.map +1 -0
  132. package/dist/core/events/span-parent.js +34 -0
  133. package/dist/core/events/v3/advance.d.ts +14 -0
  134. package/dist/core/events/v3/advance.d.ts.map +1 -0
  135. package/dist/core/events/v3/advance.js +134 -0
  136. package/dist/core/events/v3/authority-outbox.d.ts +78 -0
  137. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -0
  138. package/dist/core/events/v3/authority-outbox.js +399 -0
  139. package/dist/core/events/v3/base-builder.d.ts +20 -0
  140. package/dist/core/events/v3/base-builder.d.ts.map +1 -0
  141. package/dist/core/events/v3/base-builder.js +29 -0
  142. package/dist/core/events/v3/base-contract.d.ts +7576 -0
  143. package/dist/core/events/v3/base-contract.d.ts.map +1 -0
  144. package/dist/core/events/v3/base-contract.js +647 -0
  145. package/dist/core/events/v3/bootstrap.d.ts +34 -0
  146. package/dist/core/events/v3/bootstrap.d.ts.map +1 -0
  147. package/dist/core/events/v3/bootstrap.js +175 -0
  148. package/dist/core/events/v3/builder.d.ts +20 -0
  149. package/dist/core/events/v3/builder.d.ts.map +1 -0
  150. package/dist/core/events/v3/builder.js +28 -0
  151. package/dist/core/events/v3/canonical.d.ts +31 -0
  152. package/dist/core/events/v3/canonical.d.ts.map +1 -0
  153. package/dist/core/events/v3/canonical.js +100 -0
  154. package/dist/core/events/v3/capabilities.d.ts +31 -0
  155. package/dist/core/events/v3/capabilities.d.ts.map +1 -0
  156. package/dist/core/events/v3/capabilities.js +107 -0
  157. package/dist/core/events/v3/capability-drift.d.ts +22 -0
  158. package/dist/core/events/v3/capability-drift.d.ts.map +1 -0
  159. package/dist/core/events/v3/capability-drift.js +134 -0
  160. package/dist/core/events/v3/catalog.d.ts +38 -0
  161. package/dist/core/events/v3/catalog.d.ts.map +1 -0
  162. package/dist/core/events/v3/catalog.js +135 -0
  163. package/dist/core/events/v3/clock-order.d.ts +34 -0
  164. package/dist/core/events/v3/clock-order.d.ts.map +1 -0
  165. package/dist/core/events/v3/clock-order.js +35 -0
  166. package/dist/core/events/v3/contract.d.ts +171 -0
  167. package/dist/core/events/v3/contract.d.ts.map +1 -0
  168. package/dist/core/events/v3/contract.js +180 -0
  169. package/dist/core/events/v3/control.d.ts +111 -0
  170. package/dist/core/events/v3/control.d.ts.map +1 -0
  171. package/dist/core/events/v3/control.js +419 -0
  172. package/dist/core/events/v3/coordination-view.d.ts +69 -0
  173. package/dist/core/events/v3/coordination-view.d.ts.map +1 -0
  174. package/dist/core/events/v3/coordination-view.js +119 -0
  175. package/dist/core/events/v3/economics.d.ts +77 -0
  176. package/dist/core/events/v3/economics.d.ts.map +1 -0
  177. package/dist/core/events/v3/economics.js +274 -0
  178. package/dist/core/events/v3/finalization-view.d.ts +26 -0
  179. package/dist/core/events/v3/finalization-view.d.ts.map +1 -0
  180. package/dist/core/events/v3/finalization-view.js +37 -0
  181. package/dist/core/events/v3/fingerprint-keys.d.ts +25 -0
  182. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -0
  183. package/dist/core/events/v3/fingerprint-keys.js +217 -0
  184. package/dist/core/events/v3/generated.d.ts +4 -0
  185. package/dist/core/events/v3/generated.d.ts.map +1 -0
  186. package/dist/core/events/v3/generated.js +47 -0
  187. package/dist/core/events/v3/ids.d.ts +9 -0
  188. package/dist/core/events/v3/ids.d.ts.map +1 -0
  189. package/dist/core/events/v3/ids.js +25 -0
  190. package/dist/core/events/v3/index.d.ts +35 -0
  191. package/dist/core/events/v3/index.d.ts.map +1 -0
  192. package/dist/core/events/v3/index.js +33 -0
  193. package/dist/core/events/v3/latency.d.ts +63 -0
  194. package/dist/core/events/v3/latency.d.ts.map +1 -0
  195. package/dist/core/events/v3/latency.js +412 -0
  196. package/dist/core/events/v3/live-feed.d.ts +45 -0
  197. package/dist/core/events/v3/live-feed.d.ts.map +1 -0
  198. package/dist/core/events/v3/live-feed.js +246 -0
  199. package/dist/core/events/v3/live-observation.d.ts +35 -0
  200. package/dist/core/events/v3/live-observation.d.ts.map +1 -0
  201. package/dist/core/events/v3/live-observation.js +1 -0
  202. package/dist/core/events/v3/live-routing.d.ts +57 -0
  203. package/dist/core/events/v3/live-routing.d.ts.map +1 -0
  204. package/dist/core/events/v3/live-routing.js +107 -0
  205. package/dist/core/events/v3/producers/command-recorder.d.ts +67 -0
  206. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -0
  207. package/dist/core/events/v3/producers/command-recorder.js +477 -0
  208. package/dist/core/events/v3/producers/command.d.ts +50 -0
  209. package/dist/core/events/v3/producers/command.d.ts.map +1 -0
  210. package/dist/core/events/v3/producers/command.js +135 -0
  211. package/dist/core/events/v3/producers/coordination-recorder.d.ts +53 -0
  212. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -0
  213. package/dist/core/events/v3/producers/coordination-recorder.js +376 -0
  214. package/dist/core/events/v3/producers/coordination.d.ts +90 -0
  215. package/dist/core/events/v3/producers/coordination.d.ts.map +1 -0
  216. package/dist/core/events/v3/producers/coordination.js +229 -0
  217. package/dist/core/events/v3/producers/hook-base.d.ts +54 -0
  218. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -0
  219. package/dist/core/events/v3/producers/hook-base.js +424 -0
  220. package/dist/core/events/v3/producers/hook.d.ts +29 -0
  221. package/dist/core/events/v3/producers/hook.d.ts.map +1 -0
  222. package/dist/core/events/v3/producers/hook.js +71 -0
  223. package/dist/core/events/v3/producers/intake.d.ts +65 -0
  224. package/dist/core/events/v3/producers/intake.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/intake.js +244 -0
  226. package/dist/core/events/v3/producers/recorder.d.ts +240 -0
  227. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -0
  228. package/dist/core/events/v3/producers/recorder.js +2176 -0
  229. package/dist/core/events/v3/projection.d.ts +101 -0
  230. package/dist/core/events/v3/projection.d.ts.map +1 -0
  231. package/dist/core/events/v3/projection.js +479 -0
  232. package/dist/core/events/v3/reader.d.ts +75 -0
  233. package/dist/core/events/v3/reader.d.ts.map +1 -0
  234. package/dist/core/events/v3/reader.js +778 -0
  235. package/dist/core/events/v3/recovery-record.d.ts +47 -0
  236. package/dist/core/events/v3/recovery-record.d.ts.map +1 -0
  237. package/dist/core/events/v3/recovery-record.js +167 -0
  238. package/dist/core/events/v3/recovery.d.ts +22 -0
  239. package/dist/core/events/v3/recovery.d.ts.map +1 -0
  240. package/dist/core/events/v3/recovery.js +227 -0
  241. package/dist/core/events/v3/span-state.d.ts +52 -0
  242. package/dist/core/events/v3/span-state.d.ts.map +1 -0
  243. package/dist/core/events/v3/span-state.js +142 -0
  244. package/dist/core/events/v3/targets.d.ts +23 -0
  245. package/dist/core/events/v3/targets.d.ts.map +1 -0
  246. package/dist/core/events/v3/targets.js +134 -0
  247. package/dist/core/events/v3/turn-telemetry.d.ts +41 -0
  248. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -0
  249. package/dist/core/events/v3/turn-telemetry.js +168 -0
  250. package/dist/core/events/v3/validate.d.ts +9 -0
  251. package/dist/core/events/v3/validate.d.ts.map +1 -0
  252. package/dist/core/events/v3/validate.js +281 -0
  253. package/dist/core/events/v3/writer.d.ts +56 -0
  254. package/dist/core/events/v3/writer.d.ts.map +1 -0
  255. package/dist/core/events/v3/writer.js +335 -0
  256. package/dist/core/governor/index.d.ts +1 -1
  257. package/dist/core/governor/index.d.ts.map +1 -1
  258. package/dist/core/governor/index.js +1 -1
  259. package/dist/core/governor/plan-api.d.ts +2 -1
  260. package/dist/core/governor/plan-api.d.ts.map +1 -1
  261. package/dist/core/governor/plan-api.js +6 -3
  262. package/dist/core/governor/read.d.ts +1 -1
  263. package/dist/core/governor/read.d.ts.map +1 -1
  264. package/dist/core/governor/read.js +1 -1
  265. package/dist/core/governor/state.d.ts +9 -0
  266. package/dist/core/governor/state.d.ts.map +1 -1
  267. package/dist/core/governor/state.js +40 -3
  268. package/dist/core/guard/config.d.ts +5 -0
  269. package/dist/core/guard/config.d.ts.map +1 -0
  270. package/dist/core/guard/config.js +157 -0
  271. package/dist/core/guard/coordinator.d.ts +14 -0
  272. package/dist/core/guard/coordinator.d.ts.map +1 -0
  273. package/dist/core/guard/coordinator.js +173 -0
  274. package/dist/core/guard/evaluator.d.ts +4 -0
  275. package/dist/core/guard/evaluator.d.ts.map +1 -0
  276. package/dist/core/guard/evaluator.js +242 -0
  277. package/dist/core/guard/evidence-v3.d.ts +15 -0
  278. package/dist/core/guard/evidence-v3.d.ts.map +1 -0
  279. package/dist/core/guard/evidence-v3.js +171 -0
  280. package/dist/core/guard/index.d.ts +10 -0
  281. package/dist/core/guard/index.d.ts.map +1 -0
  282. package/dist/core/guard/index.js +6 -0
  283. package/dist/core/guard/live-source-v3.d.ts +40 -0
  284. package/dist/core/guard/live-source-v3.d.ts.map +1 -0
  285. package/dist/core/guard/live-source-v3.js +128 -0
  286. package/dist/core/guard/storage.d.ts +27 -0
  287. package/dist/core/guard/storage.d.ts.map +1 -0
  288. package/dist/core/guard/storage.js +140 -0
  289. package/dist/core/guard/types.d.ts +119 -0
  290. package/dist/core/guard/types.d.ts.map +1 -0
  291. package/dist/core/guard/types.js +1 -0
  292. package/dist/core/hooks/adapter/detect.d.ts +18 -1
  293. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  294. package/dist/core/hooks/adapter/detect.js +29 -0
  295. package/dist/core/hooks/adapter/events.d.ts +7 -4
  296. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  297. package/dist/core/hooks/adapter/events.js +12 -4
  298. package/dist/core/hooks/adapter/output.d.ts +2 -2
  299. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  300. package/dist/core/hooks/adapter/parse.d.ts +7 -4
  301. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  302. package/dist/core/hooks/adapter/parse.js +63 -25
  303. package/dist/core/hooks/adapter/wiring.d.ts +30 -4
  304. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  305. package/dist/core/hooks/adapter/wiring.js +89 -11
  306. package/dist/core/hooks/cli.d.ts +1 -1
  307. package/dist/core/hooks/cli.js +305 -444
  308. package/dist/core/hooks/effects/image-capture.d.ts +20 -34
  309. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  310. package/dist/core/hooks/effects/image-capture.js +212 -221
  311. package/dist/core/hooks/effects/index.d.ts +1 -9
  312. package/dist/core/hooks/effects/index.d.ts.map +1 -1
  313. package/dist/core/hooks/effects/index.js +1 -24
  314. package/dist/core/hooks/events/input-hash.d.ts +7 -0
  315. package/dist/core/hooks/events/input-hash.d.ts.map +1 -0
  316. package/dist/core/hooks/events/input-hash.js +43 -0
  317. package/dist/core/hooks/index.d.ts +4 -3
  318. package/dist/core/hooks/index.d.ts.map +1 -1
  319. package/dist/core/hooks/index.js +2 -3
  320. package/dist/core/hooks/resolve/coord-root.d.ts +1 -1
  321. package/dist/core/hooks/resolve/coord-root.js +1 -1
  322. package/dist/core/hooks/resolve/owner.d.ts +16 -15
  323. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  324. package/dist/core/hooks/resolve/owner.js +51 -42
  325. package/dist/core/hooks/resolve/transcript.d.ts +33 -3
  326. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  327. package/dist/core/hooks/resolve/transcript.js +155 -14
  328. package/dist/core/hooks/session-name-presence.d.ts +9 -10
  329. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  330. package/dist/core/hooks/session-name-presence.js +15 -15
  331. package/dist/core/hooks/unsafe-cross-shell.d.ts +1 -1
  332. package/dist/core/hooks/unsafe-cross-shell.d.ts.map +1 -1
  333. package/dist/core/journal/index.d.ts +2 -2
  334. package/dist/core/journal/index.d.ts.map +1 -1
  335. package/dist/core/journal/index.js +9 -22
  336. package/dist/core/presence/blob.d.ts +4 -3
  337. package/dist/core/presence/blob.d.ts.map +1 -1
  338. package/dist/core/presence/blob.js +45 -44
  339. package/dist/core/presence/codec-digest.d.ts +29 -0
  340. package/dist/core/presence/codec-digest.d.ts.map +1 -0
  341. package/dist/core/presence/codec-digest.js +170 -0
  342. package/dist/core/presence/relay-client.d.ts +1 -1
  343. package/dist/core/presence/relay-client.d.ts.map +1 -1
  344. package/dist/core/presence/relay-client.js +5 -5
  345. package/dist/core/work/index.d.ts +1 -1
  346. package/dist/core/work/index.d.ts.map +1 -1
  347. package/dist/core/work/index.js +1 -1
  348. package/dist/core/work/read.d.ts +1 -1
  349. package/dist/core/work/read.d.ts.map +1 -1
  350. package/dist/core/work/read.js +1 -1
  351. package/dist/core/work/runner.d.ts.map +1 -1
  352. package/dist/core/work/runner.js +6 -0
  353. package/dist/core/work/state.d.ts +9 -0
  354. package/dist/core/work/state.d.ts.map +1 -1
  355. package/dist/core/work/state.js +17 -3
  356. package/dist/core/workflow/engine.js +24 -25
  357. package/dist/core/workflow/live-session-v3.d.ts +19 -0
  358. package/dist/core/workflow/live-session-v3.d.ts.map +1 -0
  359. package/dist/core/workflow/live-session-v3.js +86 -0
  360. package/dist/core/workflow/scope-id.d.ts +3 -0
  361. package/dist/core/workflow/scope-id.d.ts.map +1 -0
  362. package/dist/core/workflow/scope-id.js +5 -0
  363. package/dist/core/workflow/workspaces/leases.d.ts +4 -0
  364. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  365. package/dist/core/workflow/workspaces/leases.js +53 -19
  366. package/dist/index.d.ts +1 -1
  367. package/dist/index.js +1 -1
  368. package/dist/lib/browser/client.d.ts +41 -0
  369. package/dist/lib/browser/client.d.ts.map +1 -1
  370. package/dist/lib/browser/client.js +203 -0
  371. package/dist/lib/browser/critique.d.ts +28 -2
  372. package/dist/lib/browser/critique.d.ts.map +1 -1
  373. package/dist/lib/browser/critique.js +74 -18
  374. package/dist/lib/browser/geometry.d.ts +1 -1
  375. package/dist/lib/browser/geometry.d.ts.map +1 -1
  376. package/dist/lib/browser/geometry.js +130 -6
  377. package/dist/lib/browser/index.d.ts +3 -0
  378. package/dist/lib/browser/index.d.ts.map +1 -1
  379. package/dist/lib/browser/index.js +3 -0
  380. package/dist/lib/browser/qa-plan.d.ts +174 -0
  381. package/dist/lib/browser/qa-plan.d.ts.map +1 -0
  382. package/dist/lib/browser/qa-plan.js +321 -0
  383. package/dist/lib/browser/qa-reuse.d.ts +99 -0
  384. package/dist/lib/browser/qa-reuse.d.ts.map +1 -0
  385. package/dist/lib/browser/qa-reuse.js +222 -0
  386. package/dist/lib/browser/qa-snapshot.d.ts +64 -0
  387. package/dist/lib/browser/qa-snapshot.d.ts.map +1 -0
  388. package/dist/lib/browser/qa-snapshot.js +171 -0
  389. package/dist/lib/browser/runts.d.ts.map +1 -1
  390. package/dist/lib/browser/runts.js +22 -1
  391. package/dist/lib/browser/tiling.d.ts +71 -0
  392. package/dist/lib/browser/tiling.d.ts.map +1 -0
  393. package/dist/lib/browser/tiling.js +118 -0
  394. package/dist/lib/claude-desktop.d.ts +0 -47
  395. package/dist/lib/claude-desktop.d.ts.map +1 -1
  396. package/dist/lib/claude-desktop.js +1 -169
  397. package/dist/lib/context/index.d.ts +0 -3
  398. package/dist/lib/context/index.d.ts.map +1 -1
  399. package/dist/lib/context/index.js +20 -42
  400. package/dist/lib/council/index.d.ts +2 -2
  401. package/dist/lib/council/index.d.ts.map +1 -1
  402. package/dist/lib/council/index.js +9 -20
  403. package/dist/lib/docs-frontmatter.d.ts +1 -6
  404. package/dist/lib/docs-frontmatter.d.ts.map +1 -1
  405. package/dist/lib/docs-frontmatter.js +12 -78
  406. package/dist/lib/docs-index.d.ts +9 -0
  407. package/dist/lib/docs-index.d.ts.map +1 -1
  408. package/dist/lib/docs-index.js +32 -3
  409. package/dist/lib/docs-lint.d.ts.map +1 -1
  410. package/dist/lib/docs-lint.js +14 -30
  411. package/dist/lib/docs-metadata-audit.d.ts +19 -0
  412. package/dist/lib/docs-metadata-audit.d.ts.map +1 -0
  413. package/dist/lib/docs-metadata-audit.js +132 -0
  414. package/dist/lib/docs-metadata-managed.d.ts +23 -0
  415. package/dist/lib/docs-metadata-managed.d.ts.map +1 -0
  416. package/dist/lib/docs-metadata-managed.js +78 -0
  417. package/dist/lib/docs-metadata-sync.d.ts +20 -0
  418. package/dist/lib/docs-metadata-sync.d.ts.map +1 -0
  419. package/dist/lib/docs-metadata-sync.js +222 -0
  420. package/dist/lib/docs-metadata-v2-schema.d.ts +273 -0
  421. package/dist/lib/docs-metadata-v2-schema.d.ts.map +1 -0
  422. package/dist/lib/docs-metadata-v2-schema.js +134 -0
  423. package/dist/lib/docs-metadata-v2.d.ts +21 -0
  424. package/dist/lib/docs-metadata-v2.d.ts.map +1 -0
  425. package/dist/lib/docs-metadata-v2.js +424 -0
  426. package/dist/lib/docs-new.d.ts +11 -0
  427. package/dist/lib/docs-new.d.ts.map +1 -0
  428. package/dist/lib/docs-new.js +52 -0
  429. package/dist/lib/docs-sweep.d.ts.map +1 -1
  430. package/dist/lib/docs-sweep.js +45 -29
  431. package/dist/lib/headless/index.d.ts +98 -0
  432. package/dist/lib/headless/index.d.ts.map +1 -0
  433. package/dist/lib/headless/index.js +211 -0
  434. package/dist/lib/identities/assume.d.ts +4 -4
  435. package/dist/lib/identities/assume.d.ts.map +1 -1
  436. package/dist/lib/identities/assume.js +52 -68
  437. package/dist/lib/instructions/apply.d.ts +5 -4
  438. package/dist/lib/instructions/apply.d.ts.map +1 -1
  439. package/dist/lib/instructions/apply.js +32 -20
  440. package/dist/lib/instructions/bundle.d.ts +1 -1
  441. package/dist/lib/instructions/bundle.d.ts.map +1 -1
  442. package/dist/lib/instructions/bundle.js +1 -1
  443. package/dist/lib/instructions/splice.d.ts +0 -1
  444. package/dist/lib/instructions/splice.d.ts.map +1 -1
  445. package/dist/lib/instructions/splice.js +22 -6
  446. package/dist/lib/instructions/templates.d.ts +7 -5
  447. package/dist/lib/instructions/templates.d.ts.map +1 -1
  448. package/dist/lib/instructions/templates.js +79 -9
  449. package/package.json +26 -5
  450. package/schemas/config.schema.json +120 -2
  451. package/schemas/docs-metadata-v2.schema.json +441 -0
  452. package/schemas/event-v3.schema.json +24472 -0
  453. package/src/commander.ts +4 -0
  454. package/src/commands/adapter.ts +2 -2
  455. package/src/commands/agents.ts +1512 -592
  456. package/src/commands/artifacts.ts +7 -6
  457. package/src/commands/backup.ts +29 -21
  458. package/src/commands/browse.ts +349 -8
  459. package/src/commands/checkpoint.ts +4 -3
  460. package/src/commands/claude-desktop.ts +2 -101
  461. package/src/commands/context.ts +1 -6
  462. package/src/commands/decision.ts +27 -44
  463. package/src/commands/docs.ts +100 -33
  464. package/src/commands/doctor.ts +14 -1
  465. package/src/commands/events.ts +256 -0
  466. package/src/commands/governor.ts +18 -4
  467. package/src/commands/init.ts +253 -57
  468. package/src/commands/instructions.ts +1 -1
  469. package/src/commands/journal.ts +4 -13
  470. package/src/commands/ledger-v3.ts +111 -0
  471. package/src/commands/presence.ts +11 -10
  472. package/src/commands/sync.ts +1 -1
  473. package/src/commands/web.ts +67 -8
  474. package/src/commands/work.ts +28 -5
  475. package/src/core/adapter.ts +5 -0
  476. package/src/core/adapters/attest.ts +8 -3
  477. package/src/core/adapters/profiles.ts +4 -6
  478. package/src/core/agents/canonical-emit.ts +34 -25
  479. package/src/core/agents/cli.ts +236 -262
  480. package/src/core/agents/codex-archive-v3.ts +138 -0
  481. package/src/core/agents/coord-client.ts +75 -188
  482. package/src/core/agents/finalization.ts +120 -38
  483. package/src/core/agents/index.ts +4 -5
  484. package/src/core/agents/live-authority-v3.ts +375 -0
  485. package/src/core/agents/live-lifecycle-v3.ts +140 -0
  486. package/src/core/agents/live-observation-v3.ts +216 -0
  487. package/src/core/agents/render/prompt-context.ts +29 -60
  488. package/src/core/agents/render/session-context.ts +10 -31
  489. package/src/core/agents/rules/claim-conflict.ts +85 -126
  490. package/src/core/agents/rules/commit-conflict.ts +3 -14
  491. package/src/core/agents/rules/stop-hook.ts +239 -293
  492. package/src/core/agents/session-events.ts +137 -104
  493. package/src/core/agents/session-finalization-state-v3.ts +90 -0
  494. package/src/core/agents/session-finalizer-v3.ts +953 -0
  495. package/src/core/agents/session-name-display.ts +149 -0
  496. package/src/core/agents/state/activity-log.ts +6 -14
  497. package/src/core/agents/state/heartbeat-writer.ts +82 -277
  498. package/src/core/agents/state/live-coordination-view.ts +290 -0
  499. package/src/core/agents/state/session-state.ts +24 -12
  500. package/src/core/agents/state/stale-sweep.ts +40 -17
  501. package/src/core/artifacts/index.ts +4 -5
  502. package/src/core/config.ts +148 -3
  503. package/src/core/context/index.ts +9 -17
  504. package/src/core/events/span-parent.ts +46 -0
  505. package/src/core/events/v3/advance.ts +168 -0
  506. package/src/core/events/v3/authority-outbox.ts +548 -0
  507. package/src/core/events/v3/base-builder.ts +60 -0
  508. package/src/core/events/v3/base-contract.ts +882 -0
  509. package/src/core/events/v3/bootstrap.ts +226 -0
  510. package/src/core/events/v3/builder.ts +59 -0
  511. package/src/core/events/v3/canonical.ts +153 -0
  512. package/src/core/events/v3/capabilities.ts +171 -0
  513. package/src/core/events/v3/capability-drift.ts +177 -0
  514. package/src/core/events/v3/catalog.ts +202 -0
  515. package/src/core/events/v3/clock-order.ts +41 -0
  516. package/src/core/events/v3/contract.ts +365 -0
  517. package/src/core/events/v3/control.ts +576 -0
  518. package/src/core/events/v3/coordination-view.ts +193 -0
  519. package/src/core/events/v3/economics.ts +384 -0
  520. package/src/core/events/v3/finalization-view.ts +67 -0
  521. package/src/core/events/v3/fingerprint-keys.ts +260 -0
  522. package/src/core/events/v3/generated.ts +48 -0
  523. package/src/core/events/v3/ids.ts +33 -0
  524. package/src/core/events/v3/index.ts +322 -0
  525. package/src/core/events/v3/latency.ts +551 -0
  526. package/src/core/events/v3/live-feed.ts +319 -0
  527. package/src/core/events/v3/live-observation.ts +39 -0
  528. package/src/core/events/v3/live-routing.ts +135 -0
  529. package/src/core/events/v3/producers/command-recorder.ts +631 -0
  530. package/src/core/events/v3/producers/command.ts +201 -0
  531. package/src/core/events/v3/producers/coordination-recorder.ts +538 -0
  532. package/src/core/events/v3/producers/coordination.ts +355 -0
  533. package/src/core/events/v3/producers/hook-base.ts +578 -0
  534. package/src/core/events/v3/producers/hook.ts +119 -0
  535. package/src/core/events/v3/producers/intake.ts +319 -0
  536. package/src/core/events/v3/producers/recorder.ts +2805 -0
  537. package/src/core/events/v3/projection.ts +653 -0
  538. package/src/core/events/v3/reader.ts +1127 -0
  539. package/src/core/events/v3/recovery-record.ts +244 -0
  540. package/src/core/events/v3/recovery.ts +271 -0
  541. package/src/core/events/v3/span-state.ts +187 -0
  542. package/src/core/events/v3/targets.ts +182 -0
  543. package/src/core/events/v3/turn-telemetry.ts +259 -0
  544. package/src/core/events/v3/validate.ts +361 -0
  545. package/src/core/events/v3/writer.ts +419 -0
  546. package/src/core/governor/index.ts +3 -0
  547. package/src/core/governor/plan-api.ts +10 -2
  548. package/src/core/governor/read.ts +3 -0
  549. package/src/core/governor/state.ts +52 -5
  550. package/src/core/guard/config.ts +206 -0
  551. package/src/core/guard/coordinator.ts +227 -0
  552. package/src/core/guard/evaluator.ts +343 -0
  553. package/src/core/guard/evidence-v3.ts +227 -0
  554. package/src/core/guard/index.ts +35 -0
  555. package/src/core/guard/live-source-v3.ts +204 -0
  556. package/src/core/guard/storage.ts +188 -0
  557. package/src/core/guard/types.ts +152 -0
  558. package/src/core/hooks/adapter/detect.ts +28 -1
  559. package/src/core/hooks/adapter/events.ts +14 -6
  560. package/src/core/hooks/adapter/output.ts +7 -2
  561. package/src/core/hooks/adapter/parse.ts +80 -38
  562. package/src/core/hooks/adapter/wiring.ts +119 -13
  563. package/src/core/hooks/cli.ts +351 -466
  564. package/src/core/hooks/effects/image-capture.ts +228 -238
  565. package/src/core/hooks/effects/index.ts +1 -36
  566. package/src/core/hooks/events/input-hash.ts +47 -0
  567. package/src/core/hooks/index.ts +4 -11
  568. package/src/core/hooks/resolve/coord-root.ts +1 -1
  569. package/src/core/hooks/resolve/owner.ts +52 -45
  570. package/src/core/hooks/resolve/transcript.ts +165 -14
  571. package/src/core/hooks/session-name-presence.ts +14 -15
  572. package/src/core/hooks/unsafe-cross-shell.ts +1 -1
  573. package/src/core/journal/index.ts +15 -22
  574. package/src/core/presence/blob.ts +48 -41
  575. package/src/core/presence/codec-digest.ts +215 -0
  576. package/src/core/presence/relay-client.ts +5 -5
  577. package/src/core/work/index.ts +3 -0
  578. package/src/core/work/read.ts +3 -0
  579. package/src/core/work/runner.ts +6 -0
  580. package/src/core/work/state.ts +27 -3
  581. package/src/core/workflow/engine.ts +24 -24
  582. package/src/core/workflow/live-session-v3.ts +126 -0
  583. package/src/core/workflow/scope-id.ts +6 -0
  584. package/src/core/workflow/workspaces/leases.ts +56 -19
  585. package/src/index.ts +1 -1
  586. package/src/lib/browser/client.ts +230 -0
  587. package/src/lib/browser/critique.ts +104 -27
  588. package/src/lib/browser/geometry.ts +143 -14
  589. package/src/lib/browser/index.ts +34 -0
  590. package/src/lib/browser/qa-plan.ts +501 -0
  591. package/src/lib/browser/qa-reuse.ts +306 -0
  592. package/src/lib/browser/qa-snapshot.ts +223 -0
  593. package/src/lib/browser/runts.ts +27 -2
  594. package/src/lib/browser/tiling.ts +164 -0
  595. package/src/lib/claude-desktop.ts +1 -232
  596. package/src/lib/context/index.ts +22 -46
  597. package/src/lib/council/index.ts +9 -21
  598. package/src/lib/docs-frontmatter.ts +8 -79
  599. package/src/lib/docs-index.ts +33 -3
  600. package/src/lib/docs-lint.ts +15 -29
  601. package/src/lib/docs-metadata-audit.ts +166 -0
  602. package/src/lib/docs-metadata-managed.ts +83 -0
  603. package/src/lib/docs-metadata-sync.ts +262 -0
  604. package/src/lib/docs-metadata-v2-schema.ts +138 -0
  605. package/src/lib/docs-metadata-v2.ts +646 -0
  606. package/src/lib/docs-new.ts +63 -0
  607. package/src/lib/docs-sweep.ts +42 -28
  608. package/src/lib/headless/index.ts +287 -0
  609. package/src/lib/identities/assume.ts +67 -86
  610. package/src/lib/instructions/apply.ts +33 -20
  611. package/src/lib/instructions/bundle.ts +2 -2
  612. package/src/lib/instructions/splice.ts +21 -7
  613. package/src/lib/instructions/templates.ts +85 -13
  614. package/dist/core/agents/cli-emit.d.ts +0 -27
  615. package/dist/core/agents/cli-emit.d.ts.map +0 -1
  616. package/dist/core/agents/cli-emit.js +0 -57
  617. package/dist/core/agents/codex-replay.d.ts +0 -29
  618. package/dist/core/agents/codex-replay.d.ts.map +0 -1
  619. package/dist/core/agents/codex-replay.js +0 -138
  620. package/dist/core/agents/events/consume.d.ts +0 -82
  621. package/dist/core/agents/events/consume.d.ts.map +0 -1
  622. package/dist/core/agents/events/consume.js +0 -195
  623. package/dist/core/agents/events/emit.d.ts +0 -42
  624. package/dist/core/agents/events/emit.d.ts.map +0 -1
  625. package/dist/core/agents/events/emit.js +0 -75
  626. package/dist/core/agents/events/ulid.d.ts +0 -11
  627. package/dist/core/agents/events/ulid.d.ts.map +0 -1
  628. package/dist/core/agents/events/ulid.js +0 -47
  629. package/dist/core/agents/state/heartbeat-projector.d.ts +0 -76
  630. package/dist/core/agents/state/heartbeat-projector.d.ts.map +0 -1
  631. package/dist/core/agents/state/heartbeat-projector.js +0 -581
  632. package/dist/core/hooks/events/emit.d.ts +0 -31
  633. package/dist/core/hooks/events/emit.d.ts.map +0 -1
  634. package/dist/core/hooks/events/emit.js +0 -93
  635. package/dist/core/hooks/events/rotate.d.ts +0 -43
  636. package/dist/core/hooks/events/rotate.d.ts.map +0 -1
  637. package/dist/core/hooks/events/rotate.js +0 -142
  638. package/dist/core/hooks/events/schema.d.ts +0 -341
  639. package/dist/core/hooks/events/schema.d.ts.map +0 -1
  640. package/dist/core/hooks/events/schema.js +0 -12
  641. package/dist/core/hooks/events/ulid.d.ts +0 -10
  642. package/dist/core/hooks/events/ulid.d.ts.map +0 -1
  643. package/dist/core/hooks/events/ulid.js +0 -47
  644. package/dist/lib/docs-frontmatter-migrate.d.ts +0 -33
  645. package/dist/lib/docs-frontmatter-migrate.d.ts.map +0 -1
  646. package/dist/lib/docs-frontmatter-migrate.js +0 -364
  647. package/src/core/agents/cli-emit.ts +0 -64
  648. package/src/core/agents/codex-replay.ts +0 -163
  649. package/src/core/agents/events/consume.ts +0 -254
  650. package/src/core/agents/events/emit.ts +0 -115
  651. package/src/core/agents/events/ulid.ts +0 -51
  652. package/src/core/agents/state/heartbeat-projector.ts +0 -645
  653. package/src/core/hooks/events/emit.ts +0 -125
  654. package/src/core/hooks/events/rotate.ts +0 -151
  655. package/src/core/hooks/events/schema.ts +0 -582
  656. package/src/core/hooks/events/ulid.ts +0 -51
  657. package/src/lib/docs-frontmatter-migrate.ts +0 -427
@@ -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
@@ -22,20 +22,25 @@ import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
22
22
  import { dirname, join } from "node:path";
23
23
  import { coordEnv } from "../../lib/env.ts";
24
24
  import { buildInstructionBundle } from "../../lib/instructions/bundle.ts";
25
- import { replayCodexJsonl } from "../agents/codex-replay.ts";
25
+ import type { Adapter } from "../adapter.ts";
26
26
  import { coordBinPath } from "../agents/coord-bin.ts";
27
- import { consumeSince, writeCursor } from "../agents/events/consume.ts";
28
27
  import {
29
28
  type ClaimFinalizationDecision,
30
29
  type ClaimFinalizationDescriptor,
31
30
  classifyWriteClaimFinalization,
32
31
  formatWriteClaimFinalizationDenial,
33
32
  } from "../agents/finalization.ts";
34
- import { evaluateStopHook } from "../agents/rules/stop-hook.ts";
35
- import { projectHeartbeats } from "../agents/state/heartbeat-projector.ts";
36
- import { stampSessionStateEvent } from "../agents/state/heartbeat-writer.ts";
33
+ import { evaluateStopHook, STOP_REMEDIATION_MARKER } from "../agents/rules/stop-hook.ts";
34
+ import {
35
+ assistantTextStartsWithSessionNameBlock,
36
+ isSessionNameRemediationCommand,
37
+ sessionNameDisplayInstruction,
38
+ sessionNameDisplayPending,
39
+ toolResponseMintedSessionName,
40
+ } from "../agents/session-name-display.ts";
41
+ import { stampSessionNameSeen } from "../agents/state/heartbeat-writer.ts";
42
+ import { readLiveCoordinationRow } from "../agents/state/live-coordination-view.ts";
37
43
  import { writePidmapRow } from "../agents/state/pidmap.ts";
38
- import { shellMutationPaths } from "../agents/state/shell-mutation.ts";
39
44
  import { agentsRequireGitFinalization, resolveBinName } from "../config.ts";
40
45
  import {
41
46
  checkpointContext,
@@ -47,8 +52,15 @@ import {
47
52
  readContextState,
48
53
  recordContextSample,
49
54
  } from "../context/index.ts";
50
- import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.ts";
51
- import { detectAdapter } from "./adapter/detect.ts";
55
+ import { tryWriteLiveDisplayV3 } from "../events/v3/live-feed.ts";
56
+ import {
57
+ recordLiveHookSignalV3,
58
+ resolveLiveEventLedgerRouteV3,
59
+ } from "../events/v3/live-routing.ts";
60
+ import { captureSpanClockV3 } from "../events/v3/span-state.ts";
61
+ import { fetchPresence } from "../presence/index.ts";
62
+ import { stableScopeId } from "../workflow/scope-id.ts";
63
+ import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.ts";
52
64
  import {
53
65
  extractBashCommand,
54
66
  extractToolDescription,
@@ -71,21 +83,21 @@ import {
71
83
  journalJanitor,
72
84
  journalRecoveryCue,
73
85
  playSound,
86
+ recordImageArtifactsV3,
74
87
  resetSoundCounters,
75
88
  runSessionSyncExtension,
76
- runTurnSummary,
77
89
  soundForEvent,
78
90
  } from "./effects/index.ts";
79
- import { emit } from "./events/emit.ts";
80
- import type { Adapter } from "./events/schema.ts";
91
+ import { toolInputHash, toolTargetHash } from "./events/input-hash.ts";
81
92
  import { canonicalize } from "./guard-path.ts";
82
93
  import { adapterPidFromEnv, parsePsChainLine, selectAnchorPid } from "./resolve/anchor.ts";
83
94
  import { findCoordRoot } from "./resolve/coord-root.ts";
84
95
  import { extractIntentComment, resolveIntent } from "./resolve/intent.ts";
85
96
  import { resolveOwner } from "./resolve/owner.ts";
86
97
  import {
87
- detectForkParent,
98
+ inspectSessionNameDisplayImmediately,
88
99
  scanAssistantTextIncludes,
100
+ scanSessionNameDisplayedImmediately,
89
101
  scanStatusBoxPresent,
90
102
  scanTranscriptModel,
91
103
  } from "./resolve/transcript.ts";
@@ -172,7 +184,7 @@ function logError(coordRoot: string | null, err: unknown, context: Record<string
172
184
  /**
173
185
  * Spawn `agent-coord assign-name <owner> <kind>` to mint or recover the
174
186
  * hurricane-style name for this owner. Returns null on any failure so
175
- * session.start emission never breaks the adapter flow.
187
+ * session.started emission never breaks the adapter flow.
176
188
  *
177
189
  * Lives at agent-hooks side (not agent-coord) to keep emitter/consumer
178
190
  * separation: we spawn rather than import.
@@ -209,7 +221,7 @@ function assignNameViaAgentCoord(
209
221
 
210
222
  /**
211
223
  * Direct (in-process) pidmap write, avoiding the spawn overhead of going via
212
- * the agent-coord CLI for every session.start / subagent.start. Pid-map rows
224
+ * the agent-coord CLI for every session.started signal. Pid-map rows
213
225
  * are essential for `harn agents whoami` ppid resolution.
214
226
  *
215
227
  * This used to inline its own copy of the write to keep this module's
@@ -251,6 +263,7 @@ interface BuildContext {
251
263
  raw: string;
252
264
  adapter: Adapter;
253
265
  instanceId: string;
266
+ eventName: string;
254
267
  }
255
268
 
256
269
  function buildEventData(
@@ -259,7 +272,7 @@ function buildEventData(
259
272
  ): Record<string, unknown> {
260
273
  const p = ctx.payload;
261
274
  switch (eventType) {
262
- case "session.start": {
275
+ case "session.started": {
263
276
  const adapterPlatform =
264
277
  ctx.adapter === "claude-code"
265
278
  ? "claude-code"
@@ -267,11 +280,11 @@ function buildEventData(
267
280
  ? "cursor"
268
281
  : "codex";
269
282
  // Recorded fork lineage is NOT detected here. On claude-code a fork
270
- // never fires its own session.start — SessionStart fires under the
283
+ // never fires its own session.started — SessionStart fires under the
271
284
  // PARENT's session id (source=resume) before the fork id is minted
272
285
  // (verified 2026-08-05) — so detection at this point can only mislabel
273
286
  // the resumed parent. The fork's new instance is caught by the
274
- // tool.pre_use heal path instead. The forkedFrom plumbing below stays
287
+ // tool.requested heal path instead. The forkedFrom plumbing below stays
275
288
  // for adapters that DO report a parent at session start.
276
289
  const forkedFrom: string | undefined = undefined;
277
290
  // Assign (or recover) name + kind via agent-coord. Idempotent: resume
@@ -298,7 +311,7 @@ function buildEventData(
298
311
  cwd: p?.cwd ?? process.cwd(),
299
312
  // Claude Code's SessionStart payload omits `model` (Codex + Cursor
300
313
  // supply it). Fall back to the transcript, populated on `resume`, and
301
- // backfilled later by `turn.stop` for a fresh `startup` session.
314
+ // backfilled later by `turn.completed` for a fresh startup session.
302
315
  model: p?.model ?? scanTranscriptModel(p?.transcript_path),
303
316
  pid: adapterPid,
304
317
  source: p?.source,
@@ -335,26 +348,35 @@ function buildEventData(
335
348
  };
336
349
  }
337
350
 
338
- case "session.end":
351
+ case "session.ended":
339
352
  return {
340
353
  ended_at: new Date().toISOString(),
341
354
  clean_exit: p?.clean_exit ?? true,
342
355
  };
343
356
 
344
- case "user_prompt.submit": {
357
+ case "turn.started": {
358
+ // Cursor can deliver beforeSubmitPrompt before sessionStart. Ensure that
359
+ // high-confidence prompt bootstrap also mints durable display identity;
360
+ // a later SessionStart remains idempotent.
361
+ if (
362
+ ctx.adapter === "cursor" &&
363
+ !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name
364
+ ) {
365
+ assignNameViaAgentCoord(ctx.coordRoot, ctx.instanceId, "session");
366
+ }
345
367
  const prompt = p?.prompt ?? "";
346
368
  const { value, truncated } = clampString(prompt, 4000);
347
369
  return { prompt_text: value, ...(truncated ? { truncated: true } : {}) };
348
370
  }
349
371
 
350
- case "turn.stop": {
372
+ case "turn.completed": {
351
373
  const lastAssistantMessage = (p?.raw.last_assistant_message as string | undefined) ?? "";
352
374
  const fileLinkTelemetry =
353
375
  ctx.adapter === "codex"
354
376
  ? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
355
377
  : null;
356
378
  return {
357
- // Backfill the model for adapters that omit it at session.start
379
+ // Backfill the model for adapters that omit it at session.started
358
380
  // (Claude Code). The transcript is populated with assistant turns by
359
381
  // Stop-hook time, so this resolves even for fresh `startup` sessions.
360
382
  model: p?.model ?? scanTranscriptModel(p?.transcript_path),
@@ -368,7 +390,7 @@ function buildEventData(
368
390
  // Box present if the transcript scan finds it OR the final assistant
369
391
  // message carries the `┌─ agent-` prefix. The latter covers codex's
370
392
  // text-only stop (box in last_assistant_message, no transcript), which
371
- // the verdict now sees because agent-hook emits this turn.stop itself
393
+ // the verdict now sees because agent-hook emits this turn.completed itself
372
394
  // (the previous path passed those via the no-history fail-open).
373
395
  status_box_present:
374
396
  scanStatusBoxPresent(p?.transcript_path) || lastAssistantMessage.includes("┌─ agent-"),
@@ -388,15 +410,19 @@ function buildEventData(
388
410
  // and which name that refers to. Reported on every stop while a name
389
411
  // exists (the stop-hook.session_name verdict reads it per turn); the
390
412
  // transcript scan itself stops once the name has been sighted.
391
- ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, lastAssistantMessage, (name) =>
392
- scanAssistantTextIncludes(p?.transcript_path, name),
413
+ ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) =>
414
+ scanSessionNameDisplayedImmediately(
415
+ p?.transcript_path,
416
+ name,
417
+ assistantTextStartsWithSessionNameBlock,
418
+ ),
393
419
  ),
394
420
  ...(fileLinkTelemetry ?? {}),
395
421
  stop_hook_active: p?.stop_hook_active,
396
422
  };
397
423
  }
398
424
 
399
- case "subagent.start": {
425
+ case "agent.started": {
400
426
  const subagentCallId =
401
427
  (p?.raw.subagent_id as string | undefined) ?? (p?.raw.agent_id as string | undefined);
402
428
  // Subagents inherit parent's name via the resolve-name session_id path
@@ -417,14 +443,14 @@ function buildEventData(
417
443
  };
418
444
  }
419
445
 
420
- case "subagent.stop": {
446
+ case "agent.completed": {
421
447
  const status = p?.exit_status;
422
448
  const normalized: "ok" | "error" | "interrupted" =
423
449
  status === "error" || status === "interrupted" ? status : "ok";
424
450
  return { exit_status: normalized, reason: p?.reason };
425
451
  }
426
452
 
427
- case "tool.pre_use": {
453
+ case "tool.requested": {
428
454
  const toolName = p?.tool_name ?? "unknown";
429
455
  const command = extractBashCommand(toolName, p?.tool_input);
430
456
  const description = extractToolDescription(p?.tool_input);
@@ -439,6 +465,8 @@ function buildEventData(
439
465
  return {
440
466
  tool_name: toolName,
441
467
  tool_input: clamped.value,
468
+ input_hash: toolInputHash(toolName, p?.tool_input ?? null),
469
+ target_hash: toolTargetHash(toolName, p?.tool_input ?? null),
442
470
  intent,
443
471
  intent_source: source,
444
472
  tool_use_id: p?.tool_use_id,
@@ -446,7 +474,7 @@ function buildEventData(
446
474
  };
447
475
  }
448
476
 
449
- case "interaction.input_requested": {
477
+ case "wait.started": {
450
478
  const description = extractToolDescription(p?.tool_input);
451
479
  const reason = description ? clampString(description, 500).value : undefined;
452
480
  return {
@@ -456,32 +484,28 @@ function buildEventData(
456
484
  };
457
485
  }
458
486
 
459
- case "tool.post_use": {
460
- const toolName = p?.tool_name ?? "unknown";
461
- const summary = summarizeOutput(p?.tool_response);
462
- return {
463
- tool_name: toolName,
464
- output_summary: summary.summary,
465
- exit_status: "ok" as const,
466
- duration_ms: 0, // Phase 3 pairs pre/post via tool_use_id
467
- tool_use_id: p?.tool_use_id,
468
- ...(summary.truncated ? { truncated: true } : {}),
469
- };
470
- }
471
-
472
- case "tool.post_use_failure": {
487
+ case "tool.completed": {
473
488
  const toolName = p?.tool_name ?? "unknown";
474
489
  const summary = summarizeOutput(p?.tool_response);
475
- return {
476
- tool_name: toolName,
477
- error: summary.summary,
478
- duration_ms: 0,
479
- tool_use_id: p?.tool_use_id,
480
- ...(summary.truncated ? { truncated: true } : {}),
481
- };
490
+ return ctx.eventName === "post-tool-use-failure"
491
+ ? {
492
+ tool_name: toolName,
493
+ error: summary.summary,
494
+ duration_ms: 0,
495
+ tool_use_id: p?.tool_use_id,
496
+ ...(summary.truncated ? { truncated: true } : {}),
497
+ }
498
+ : {
499
+ tool_name: toolName,
500
+ output_summary: summary.summary,
501
+ exit_status: "ok" as const,
502
+ duration_ms: 0,
503
+ tool_use_id: p?.tool_use_id,
504
+ ...(summary.truncated ? { truncated: true } : {}),
505
+ };
482
506
  }
483
507
 
484
- case "context.compaction.started": {
508
+ case "context.compaction_started": {
485
509
  const metadata = objectRecord(p?.raw.compact_metadata);
486
510
  return {
487
511
  trigger: stringField(p?.raw.trigger) ?? stringField(metadata?.trigger),
@@ -492,7 +516,7 @@ function buildEventData(
492
516
  };
493
517
  }
494
518
 
495
- case "context.compaction.completed": {
519
+ case "context.compaction_completed": {
496
520
  const metadata = objectRecord(p?.raw.compact_metadata);
497
521
  return {
498
522
  trigger: stringField(p?.raw.trigger) ?? stringField(metadata?.trigger),
@@ -533,10 +557,38 @@ function numberField(value: unknown): number | undefined {
533
557
  }
534
558
 
535
559
  async function main(): Promise<number> {
560
+ const hookStartedAt = performance.now();
561
+ const hookClock = captureSpanClockV3();
536
562
  const { eventName, extra } = parseArgv(process.argv.slice(2));
537
563
  const adapter = detectAdapter(process.argv.slice(2));
538
564
  const raw = await readStdin();
539
565
 
566
+ // Cursor executes the Claude Code project hooks too on hosts wired for both
567
+ // adapters, piping them its own payload (identifiable by the top-level
568
+ // cursor_version envelope field). That stray `--adapter claude-code`
569
+ // dispatch must not record (twin generation) or play Claude-Code-only
570
+ // sounds, so it bails here, before every effect. Cursor's own
571
+ // `--adapter cursor` dispatch of the same payload carries the session.
572
+ if (shouldSkipHookAdapter(adapter, raw)) {
573
+ if (coordEnv("AGENT_COORD_OFF") !== "1") {
574
+ const coordRoot = findCoordRoot(process.cwd());
575
+ if (coordRoot) {
576
+ appendDebug(coordRoot, {
577
+ ts: new Date().toISOString(),
578
+ event_name: eventName,
579
+ adapter,
580
+ extra_argv: extra,
581
+ payload_bytes: raw.length,
582
+ cwd: process.cwd(),
583
+ pid: process.pid,
584
+ ppid: process.ppid,
585
+ skipped: "cursor-payload-claude-adapter",
586
+ });
587
+ }
588
+ }
589
+ return 0;
590
+ }
591
+
540
592
  // Kill-switch-INDEPENDENT effects: notification sounds fire BEFORE the
541
593
  // HARNERY_AGENT_COORD_OFF gate so audible feedback survives incident-triage
542
594
  // bypass: sound playback happens before the kill-switch bailout.
@@ -566,6 +618,7 @@ async function main(): Promise<number> {
566
618
 
567
619
  const coordRoot = findCoordRoot(process.cwd());
568
620
  if (!coordRoot) return 0;
621
+ const ledgerRoute = resolveLiveEventLedgerRouteV3(coordRoot);
569
622
 
570
623
  // Always log a breadcrumb, useful when an event_type maps to null or owner
571
624
  // resolution fails. Stays cheap (one append) and self-prunes via repo log
@@ -593,7 +646,10 @@ async function main(): Promise<number> {
593
646
  }
594
647
 
595
648
  const payload = parsePayload(raw, adapter);
596
- const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
649
+ const owner = resolveOwner({
650
+ payload: payload?.raw ?? null,
651
+ coordRoot,
652
+ });
597
653
  if (!owner) {
598
654
  appendDebug(coordRoot, {
599
655
  ...debugBase,
@@ -611,42 +667,110 @@ async function main(): Promise<number> {
611
667
  raw,
612
668
  adapter,
613
669
  instanceId: owner.instance_id,
670
+ eventName,
614
671
  });
672
+ const turnRitual =
673
+ norm.event_type === "turn.completed"
674
+ ? {
675
+ status_box_present: data.status_box_present === true,
676
+ status_box_present_strict: data.status_box_present_strict === true,
677
+ session_name_required: typeof data.session_name_present_for === "string",
678
+ session_name_present: data.session_name_present === true,
679
+ }
680
+ : undefined;
681
+ const stopRemediation =
682
+ norm.event_type === "turn.started" &&
683
+ typeof data.prompt_text === "string" &&
684
+ data.prompt_text.includes(STOP_REMEDIATION_MARKER);
615
685
 
616
- const envelope = emit(coordRoot, {
617
- event_type: norm.event_type,
618
- instance_id: owner.instance_id,
619
- session_id: sessionId,
620
- parent_session_id: payload?.parent_session_id,
621
- turn_id: payload?.turn_id,
622
- parent_turn_id: payload?.parent_turn_id,
623
- adapter,
624
- data,
625
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
626
- } as Parameters<typeof emit>[1]);
627
-
628
- appendDebug(coordRoot, {
629
- ...debugBase,
630
- event_type: norm.event_type,
631
- owner_source: owner.source,
632
- event_id: envelope.event_id,
633
- });
686
+ if (ledgerRoute.state === "blocked") {
687
+ appendDebug(coordRoot, {
688
+ ...debugBase,
689
+ skipped: "v3-control-blocked",
690
+ reason: ledgerRoute.reason,
691
+ event_type: norm.event_type,
692
+ owner_source: owner.source,
693
+ });
694
+ return 0;
695
+ }
634
696
 
635
- // Activity is only useful while it is current. Stamp prompt and permission
636
- // evidence directly from this envelope rather than draining the global
637
- // projector: a drain here could apply a tool event before its authorization
638
- // verdict. Tool progress is stamped later, after the guard allows it.
697
+ let capturedImages: ReturnType<typeof captureImages> = [];
639
698
  if (
640
- norm.event_type === "user_prompt.submit" ||
641
- norm.event_type === "interaction.input_requested"
699
+ norm.event_type === "tool.requested" ||
700
+ (norm.event_type === "tool.completed" && eventName === "post-tool-use")
642
701
  ) {
643
702
  try {
644
- stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
703
+ capturedImages = captureImages(coordRoot, norm.event_type, payload);
645
704
  } catch (err) {
646
- logError(coordRoot, err, { phase: "activity-projection" });
705
+ logError(coordRoot, err, { phase: "image-capture" });
647
706
  }
648
707
  }
649
708
 
709
+ const v3Result = recordLiveHookSignalV3({
710
+ coordRoot,
711
+ route: ledgerRoute,
712
+ eventName,
713
+ payload,
714
+ adapter,
715
+ instanceId: owner.instance_id,
716
+ ...(coordEnv("WORKFLOW_CHILD") === "1" && coordEnv("WORKFLOW_RUN_ID")
717
+ ? {
718
+ run_id: stableScopeId("run", coordEnv("WORKFLOW_RUN_ID")!),
719
+ workflow_id: stableScopeId("wf", coordEnv("WORKFLOW_RUN_ID")!),
720
+ ...(coordEnv("WORKFLOW_AGENT_ID")
721
+ ? { workflow_agent_id: coordEnv("WORKFLOW_AGENT_ID") }
722
+ : {}),
723
+ }
724
+ : {}),
725
+ ...(adapter === "codex" && isWslUncPath(payload?.cwd) ? { bridge: "codex-wsl" as const } : {}),
726
+ hook_name: eventName,
727
+ hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
728
+ monotonic_ns: hookClock.monotonic_ns,
729
+ ...(stopRemediation ? { stop_remediation: true } : {}),
730
+ ...(turnRitual ? { turn_ritual: turnRitual } : {}),
731
+ ...(coordEnv("EXPERIMENT_DEFER_V3_DRAIN") === "1" ? { defer_drain: true } : {}),
732
+ });
733
+ const v3EventId =
734
+ v3Result && "event" in v3Result
735
+ ? v3Result.event.event_id
736
+ : v3Result && "event_id" in v3Result
737
+ ? v3Result.event_id
738
+ : undefined;
739
+
740
+ if (v3Result?.state === "recorded" && capturedImages.length > 0) {
741
+ try {
742
+ recordImageArtifactsV3(coordRoot, v3Result.event, capturedImages);
743
+ } catch (err) {
744
+ logError(coordRoot, err, { phase: "image-artifact-observation" });
745
+ }
746
+ }
747
+
748
+ if (
749
+ norm.event_type === "tool.requested" &&
750
+ v3Result &&
751
+ v3Result.state === "recorded" &&
752
+ "generation_id" in v3Result.event.scope
753
+ ) {
754
+ const intent = typeof data.intent === "string" ? data.intent : undefined;
755
+ if (intent && intent !== "(no intent)") {
756
+ tryWriteLiveDisplayV3(coordRoot, {
757
+ generation_id: v3Result.event.scope.generation_id,
758
+ event_id: v3Result.event.event_id,
759
+ ...(typeof data.tool_name === "string" ? { executable: data.tool_name } : {}),
760
+ intent_display: intent,
761
+ });
762
+ }
763
+ }
764
+
765
+ appendDebug(coordRoot, {
766
+ ...debugBase,
767
+ event_type: norm.event_type,
768
+ owner_source: owner.source,
769
+ ...(v3EventId ? { event_v3_id: v3EventId } : {}),
770
+ ...(v3Result ? { event_v3_state: v3Result.state } : {}),
771
+ ...(v3Result && "reason" in v3Result ? { event_v3_reason: v3Result.reason } : {}),
772
+ });
773
+
650
774
  // Context telemetry is opportunistic and truthful: only persist a sample
651
775
  // when the adapter payload actually exposes usage/window data. Identical
652
776
  // measurements are de-duplicated before they reach the canonical stream.
@@ -661,22 +785,7 @@ async function main(): Promise<number> {
661
785
  });
662
786
  if (sample) {
663
787
  const recorded = recordContextSample(coordRoot, owner.instance_id, sample);
664
- if (recorded.changed) {
665
- emit(coordRoot, {
666
- event_type: "context.sampled",
667
- instance_id: owner.instance_id,
668
- session_id: sessionId,
669
- adapter,
670
- data: {
671
- model: sample.model,
672
- used_tokens: sample.used_tokens,
673
- window_tokens: sample.window_tokens,
674
- used_percent: sample.used_percent,
675
- telemetry_source: sample.source,
676
- confidence: sample.confidence,
677
- },
678
- });
679
- }
788
+ void recorded;
680
789
  }
681
790
  } catch (err) {
682
791
  logError(coordRoot, err, { phase: "context-sample" });
@@ -686,7 +795,7 @@ async function main(): Promise<number> {
686
795
  // A native pre-compaction signal is the safest available point to capture
687
796
  // external work state. The operation is idempotent until recovery, so a
688
797
  // adapter retry cannot create a storm of near-identical capsules.
689
- if (norm.event_type === "context.compaction.started") {
798
+ if (norm.event_type === "context.compaction_started") {
690
799
  try {
691
800
  const checkpoint = checkpointContext(coordRoot, {
692
801
  sessionId,
@@ -696,25 +805,13 @@ async function main(): Promise<number> {
696
805
  reason: "pre_compact",
697
806
  model: payload?.model,
698
807
  });
699
- emit(coordRoot, {
700
- event_type: "context.checkpoint.created",
701
- instance_id: owner.instance_id,
702
- session_id: sessionId,
703
- adapter,
704
- data: {
705
- capsule_id: checkpoint.capsule.capsule_id,
706
- generation: checkpoint.capsule.generation,
707
- path: checkpoint.state.latest_capsule ?? "",
708
- reason: checkpoint.capsule.reason,
709
- reused: checkpoint.reused,
710
- },
711
- });
808
+ void checkpoint;
712
809
  } catch (err) {
713
810
  logError(coordRoot, err, { phase: "context-checkpoint" });
714
811
  }
715
812
  }
716
813
 
717
- if (norm.event_type === "context.compaction.completed") {
814
+ if (norm.event_type === "context.compaction_completed") {
718
815
  try {
719
816
  markContextCompactionCompleted(coordRoot, {
720
817
  sessionId,
@@ -730,29 +827,15 @@ async function main(): Promise<number> {
730
827
  // systemMessage JSON (peer table + wiring check + council invites).
731
828
  // Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
732
829
  // and the equivalent per-adapter bash session_start handlers.
733
- if (norm.event_type === "session.start") {
830
+ if (norm.event_type === "session.started") {
734
831
  // Effect (claude-code): prune stale journal archives + sweep orphans.
735
832
  // The recovery-cue is merged into the
736
833
  // session-start additionalContext inside emitSessionStartSystemMessage.
737
834
  if (adapter === "claude-code") journalJanitor(coordRoot);
738
- // Image-feed retention sweep (size + age cap on .harnery/images/). Adapter-
739
- // agnostic, cheap (one readdir), fail-soft. Paired with journalJanitor as a
740
- // session-start "tidy the coord layer" step.
741
- try {
742
- imageJanitor(coordRoot);
743
- } catch (err) {
744
- logError(coordRoot, err, { phase: "session-start-image-janitor" });
745
- }
835
+ imageJanitor(coordRoot);
746
836
  let recovery: PreparedContextRecovery | null = null;
747
837
  if (payload?.source === "compact") {
748
838
  try {
749
- emit(coordRoot, {
750
- event_type: "context.compaction.completed",
751
- instance_id: owner.instance_id,
752
- session_id: sessionId,
753
- adapter,
754
- data: { trigger: "auto" },
755
- });
756
839
  markContextCompactionCompleted(coordRoot, {
757
840
  sessionId,
758
841
  instanceId: owner.instance_id,
@@ -776,25 +859,15 @@ async function main(): Promise<number> {
776
859
  recovery?.briefing ?? "",
777
860
  );
778
861
  if (injected && recovery) {
779
- completeRecoveryInjection(
780
- coordRoot,
781
- owner.instance_id,
782
- sessionId,
783
- adapter,
784
- recovery,
785
- "SessionStart",
786
- );
862
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
787
863
  }
788
864
  } catch (err) {
789
865
  logError(coordRoot, err, { phase: "session-start-systemMessage" });
790
866
  }
791
- // Cross-machine presence (ADR 0016): announce this machine's sessions and
792
- // pull peers'. Both are throttled + fail-silent; network runs detached.
793
- // When a relay is configured, also make sure the live-socket daemon runs.
867
+ // Pull advisory cross-machine presence. V3 never publishes from the
868
+ // disposable heartbeat cache.
794
869
  try {
795
- publishPresence(coordRoot);
796
870
  fetchPresence(coordRoot);
797
- ensureRelayDaemon(coordRoot);
798
871
  } catch (err) {
799
872
  logError(coordRoot, err, { phase: "session-start-presence" });
800
873
  }
@@ -802,7 +875,7 @@ async function main(): Promise<number> {
802
875
 
803
876
  // Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
804
877
  // agnostic since v0.5.0.
805
- if (norm.event_type === "session.end") {
878
+ if (norm.event_type === "session.ended") {
806
879
  try {
807
880
  cleanupSessionEnd(coordRoot, owner.instance_id, (data.reason as string) ?? "unknown");
808
881
  } catch (err) {
@@ -814,19 +887,12 @@ async function main(): Promise<number> {
814
887
  journalArchive(coordRoot, owner.instance_id);
815
888
  runSessionSyncExtension(coordRoot, true);
816
889
  }
817
- // Presence: publish the post-cleanup state so peers see this session gone
818
- // promptly rather than waiting out the remote-stale window.
819
- try {
820
- publishPresence(coordRoot);
821
- } catch (err) {
822
- logError(coordRoot, err, { phase: "session-end-presence" });
823
- }
824
890
  }
825
891
 
826
892
  // Phase 8: SubagentStart: sync-project to create the subagent heartbeat,
827
893
  // log the lifecycle event, and emit a context message announcing the
828
894
  // subagent (claude-code + cursor; codex doesn't fan out subagents today).
829
- if (norm.event_type === "subagent.start") {
895
+ if (norm.event_type === "agent.started") {
830
896
  try {
831
897
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
832
898
  if (existsSync(agentCoordBin)) {
@@ -853,7 +919,7 @@ async function main(): Promise<number> {
853
919
  }
854
920
 
855
921
  // Phase 8: SubagentStop: delete subagent heartbeat + log.
856
- if (norm.event_type === "subagent.stop") {
922
+ if (norm.event_type === "agent.completed") {
857
923
  try {
858
924
  cleanupSessionEnd(coordRoot, owner.instance_id, (data.reason as string) ?? "unknown");
859
925
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
@@ -876,7 +942,7 @@ async function main(): Promise<number> {
876
942
 
877
943
  // Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
878
944
  // and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
879
- if (norm.event_type === "user_prompt.submit") {
945
+ if (norm.event_type === "turn.started") {
880
946
  // Effects (claude-code): reset per-turn sound rate-limit counters + run
881
947
  // presence detection on the prompt.
882
948
  if (adapter === "claude-code") {
@@ -917,81 +983,27 @@ async function main(): Promise<number> {
917
983
  recovery?.briefing ?? "",
918
984
  );
919
985
  if (injected && recovery) {
920
- completeRecoveryInjection(
921
- coordRoot,
922
- owner.instance_id,
923
- sessionId,
924
- adapter,
925
- recovery,
926
- "UserPromptSubmit",
927
- );
986
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
928
987
  }
929
988
  } catch (err) {
930
989
  logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
931
990
  }
932
991
  }
933
992
 
934
- // turn.stop: telemetry + turn-summary effects, then the stop verdict. The
993
+ // turn.completed: telemetry, then the stop verdict. The
935
994
  // verdict + codex-replay previously lived in the per-adapter shell adapters;
936
995
  // agent-hook owns them now. Runs on the normal "stop" event only;
937
996
  // "stop-failure" (API error) gets no gate, matching the previous
938
997
  // stop vs stop-failure split.
939
- if (norm.event_type === "turn.stop" && eventName === "stop") {
940
- // Codex: replay the JSONL transcript → canonical events so the verdict has
941
- // the status_checked / task_set / status_box_present evidence (codex
942
- // doesn't emit those live; this re-emits turn.stop after agent-hook's own,
943
- // so the verdict reads the replay's box signal as the latest).
944
- if (adapter === "codex" && payload?.transcript_path && existsSync(payload.transcript_path)) {
945
- try {
946
- replayCodexJsonl({
947
- coordRoot,
948
- jsonlPath: payload.transcript_path,
949
- sessionId,
950
- instanceId: owner.instance_id,
951
- lastAssistantMessage: (payload.raw.last_assistant_message as string | undefined) ?? "",
952
- });
953
- } catch (err) {
954
- logError(coordRoot, err, { phase: "codex-replay" });
955
- }
956
- }
957
-
958
- // CC effects: rate-limited session-telemetry sync + turn-summary Haiku
959
- // auto-summary.
998
+ if (norm.event_type === "turn.completed" && eventName === "stop") {
999
+ // Claude Code session telemetry sync remains an independent side effect.
960
1000
  if (adapter === "claude-code") {
961
1001
  runSessionSyncExtension(coordRoot, false);
962
- runTurnSummary(coordRoot, owner.instance_id, sessionId, payload?.transcript_path);
963
- }
964
-
965
- // Master-state heartbeat projection. Drains events.ndjson since the last
966
- // cursor → per-owner heartbeats. Was a SECOND binary (`agent-coord project`)
967
- // pinned to Claude Code Stop only; folded in here so it (a) is one
968
- // entry per event like everything else and (b) fires on EVERY adapter's stop,
969
- // not just CC. Runs unconditionally before the verdict's possible exit-2 return
970
- // (the events are real regardless of whether the agent gets nagged), and after
971
- // codex-replay above so codex's replayed events are included in the drain.
972
- // Not an emitter (consumes + writes heartbeats), so no emitter/consumer conflict.
973
- try {
974
- const result = consumeSince(coordRoot);
975
- projectHeartbeats(coordRoot, result.events);
976
- if (result.lastEventId) writeCursor(coordRoot, result.lastEventId);
977
- } catch (err) {
978
- logError(coordRoot, err, { phase: "stop-projection" });
979
- }
980
-
981
- // Presence: publish AFTER the projection above so the blob carries this
982
- // turn's fresh task/turn_summary/files. Change-batched + keepalive'd;
983
- // the push itself runs detached. The relay daemon (when configured) is
984
- // re-ensured here too — it self-exits on idle, and the next turn revives it.
985
- try {
986
- publishPresence(coordRoot);
987
- ensureRelayDaemon(coordRoot);
988
- } catch (err) {
989
- logError(coordRoot, err, { phase: "stop-presence" });
990
1002
  }
991
1003
 
992
- // Stop verdict (status-box + set-task gate). Direct in-process call: the
993
- // rule lives in harnery. agent-hook already emitted this turn.stop (with
994
- // status_box_present) above, so the evidence is in the stream.
1004
+ // Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
1005
+ // rule lives in Harnery. agent-hook already emitted this turn.completed
1006
+ // with privacy-safe ritual observations, so the evidence is in the stream.
995
1007
  const verdict = evaluateStopHook(coordRoot, {
996
1008
  rule: "stop-hook",
997
1009
  instance_id: owner.instance_id,
@@ -1000,31 +1012,29 @@ async function main(): Promise<number> {
1000
1012
  bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
1001
1013
  workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
1002
1014
  });
1003
- const enforcementMode = stopEnforcementMode(verdict.rule);
1004
- emit(coordRoot, {
1005
- event_type: "stop.verdict",
1006
- instance_id: owner.instance_id,
1007
- session_id: sessionId,
1008
- turn_id: payload?.turn_id,
1009
- parent_turn_id: payload?.parent_turn_id,
1010
- adapter,
1011
- data: {
1012
- allow: verdict.allow,
1013
- rule: verdict.rule,
1014
- ...(verdict.reason ? { reason: verdict.reason } : {}),
1015
- enforcement_mode: enforcementMode,
1016
- eligible: enforcementMode === "enforced",
1017
- nag_delivered: enforcementMode === "enforced" && !verdict.allow,
1018
- },
1019
- });
1020
1015
  if (!verdict.allow) {
1021
- // Adapter-aware enforcement channel: Claude Code / Codex honor exit-2 +
1022
- // stderr as a turn block; Cursor ignores exit codes (fail-open) and
1023
- // re-prompts only via a `followup_message` it auto-submits. emitStopBlock
1024
- // writes the right shape and returns the exit code to use.
1016
+ // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
1017
+ // as a turn block; Cursor ignores exit codes and re-prompts only via a
1018
+ // `followup_message` it auto-submits. Codex returned observe-only above.
1019
+ // emitStopBlock writes the right shape and returns the exit code to use.
1025
1020
  const { emitStopBlock } = await import("./adapter/output.ts");
1026
1021
  return emitStopBlock(adapter, verdict, coordRoot);
1027
1022
  }
1023
+
1024
+ // An explicit end requested from inside this turn cannot be authoritative
1025
+ // until the adapter has committed turn.completed above. Reconcile only
1026
+ // when such a request exists; any later real work cancels it in the
1027
+ // finalizer instead of being terminated underneath the agent.
1028
+ try {
1029
+ const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import(
1030
+ "../agents/session-finalizer-v3.ts"
1031
+ );
1032
+ if (hasPendingExplicitSessionEndV3(coordRoot)) {
1033
+ reconcileSessionFinalizationV3(coordRoot);
1034
+ }
1035
+ } catch (err) {
1036
+ logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
1037
+ }
1028
1038
  }
1029
1039
 
1030
1040
  // Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
@@ -1036,41 +1046,35 @@ async function main(): Promise<number> {
1036
1046
  // side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
1037
1047
  // preserved the heartbeat half but dropped the pid-map half; the pid-map
1038
1048
  // call was restored here afterward.
1039
- if (norm.event_type === "tool.pre_use") {
1049
+ if (norm.event_type === "tool.requested") {
1040
1050
  try {
1041
1051
  // Recorded fork lineage, heal-path flavor. A forked CC conversation
1042
- // never fires its own session.start (SessionStart fires under the
1052
+ // never fires its own session.started (SessionStart fires under the
1043
1053
  // PARENT's session id with source=resume, before the fork id is
1044
1054
  // minted; verified 2026-08-05), so the fork's new instance first
1045
1055
  // materializes right here. Gate detection on "no heartbeat yet" so the
1046
1056
  // transcript scan runs once per instance lifetime, not per tool call.
1047
- let forkedFrom: string | undefined;
1048
- if (
1049
- adapter === "claude-code" &&
1050
- payload?.transcript_path &&
1051
- !existsSync(join(coordRoot, ".harnery", "active", `${owner.instance_id}.json`))
1052
- ) {
1053
- forkedFrom = detectForkParent(payload.transcript_path, owner.instance_id);
1054
- }
1055
- healHeartbeatViaCli(coordRoot, owner.instance_id, sessionId, adapter, forkedFrom);
1056
1057
  refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
1057
1058
  } catch (err) {
1058
1059
  logError(coordRoot, err, { phase: "pre-tool-use-heal" });
1059
1060
  }
1060
1061
 
1061
- // Image feed: a Read on an image file is the "agent viewed this" signal.
1062
- // Capture the bytes (content-addressed, dedup'd) + emit image.captured.
1062
+ // The suggested name is a pending display latch. The set-task call itself
1063
+ // runs before a name exists; every later tool waits until the exact block
1064
+ // is the first assistant text after the mint result. Cursor supplies
1065
+ // agent_message directly; Claude Code and Codex are resolved from their
1066
+ // JSONL transcripts. Later commentary must not erase an already-correct
1067
+ // display, and an unreadable transcript must not deadlock every tool.
1063
1068
  try {
1064
- captureImages(coordRoot, {
1065
- eventType: "tool.pre_use",
1066
- data,
1067
- payload,
1068
- instanceId: owner.instance_id,
1069
- sessionId,
1069
+ const displayAllowed = await enforcePendingSessionNameDisplay(
1070
+ coordRoot,
1071
+ owner.instance_id,
1070
1072
  adapter,
1071
- });
1073
+ payload,
1074
+ );
1075
+ if (!displayAllowed) return 0;
1072
1076
  } catch (err) {
1073
- logError(coordRoot, err, { phase: "pre-tool-use-image-capture" });
1077
+ logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
1074
1078
  }
1075
1079
 
1076
1080
  // Windows-native Codex + WSL UNC only: block the one cross-shell shape
@@ -1084,13 +1088,6 @@ async function main(): Promise<number> {
1084
1088
  toolInput: payload?.tool_input,
1085
1089
  });
1086
1090
  if (unsafeShellReason) {
1087
- emit(coordRoot, {
1088
- event_type: "decision.block",
1089
- instance_id: owner.instance_id,
1090
- session_id: sessionId,
1091
- adapter,
1092
- data: { rule: "unsafe_cross_shell", reason: unsafeShellReason },
1093
- });
1094
1091
  const { emitDeny } = await import("./adapter/output.ts");
1095
1092
  emitDeny(adapter, unsafeShellReason);
1096
1093
  return 0;
@@ -1116,83 +1113,29 @@ async function main(): Promise<number> {
1116
1113
  } catch (err) {
1117
1114
  logError(coordRoot, err, { phase: "pre-tool-use-guard" });
1118
1115
  }
1119
-
1120
- if (guardAllowed) {
1121
- try {
1122
- stampSessionStateEvent(coordRoot, owner.instance_id, envelope);
1123
- } catch (err) {
1124
- logError(coordRoot, err, { phase: "activity-projection" });
1125
- }
1126
- }
1127
-
1128
- // Shell-mutation warn (warn-only, never blocks). Was the cursor
1129
- // beforeShellExecution + codex preToolUse-Bash shell-mutation-claim-log in
1130
- // the per-adapter shell adapters. Cursor sends the command at payload.command;
1131
- // codex Bash at tool_input.command. Emits a decision.warn per candidate-mutated
1132
- // path so a peer sees the write in events.ndjson. (CC never did this,
1133
- // preserved; it emits with its own hooks-side emitter per the
1134
- // independent-emitter rule.)
1135
- const shellCmd =
1136
- eventName === "before-shell-execution"
1137
- ? ((payload?.raw.command as string | undefined) ?? "")
1138
- : adapter === "codex" && data.tool_name === "Bash"
1139
- ? (((payload?.raw.tool_input as Record<string, unknown> | undefined)?.command as
1140
- | string
1141
- | undefined) ?? "")
1142
- : "";
1143
- if (shellCmd) {
1144
- try {
1145
- const paths = shellMutationPaths(shellCmd, coordRoot);
1146
- const truncated = shellCmd.length > 80 ? shellCmd.slice(0, 80) : shellCmd;
1147
- const platform = adapterPlatform(adapter);
1148
- for (const p of paths) {
1149
- emit(coordRoot, {
1150
- event_type: "decision.warn",
1151
- instance_id: owner.instance_id,
1152
- session_id: sessionId,
1153
- adapter,
1154
- data: {
1155
- rule: "shell_mutation_candidate",
1156
- reason: `path=${p} cmd=${truncated} platform=${platform}`,
1157
- },
1158
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
1159
- } as Parameters<typeof emit>[1]);
1160
- }
1161
- } catch (err) {
1162
- logError(coordRoot, err, { phase: "shell-mutation-warn" });
1163
- }
1164
- }
1116
+ if (!guardAllowed) return 0;
1165
1117
  }
1166
1118
 
1167
- // Phase 7: PostToolUse: stamp last_tool + last_tool_target on heartbeat.
1168
- // Adapter-agnostic for the same reason as tool.pre_use above.
1169
- if (norm.event_type === "tool.post_use") {
1119
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use") {
1120
+ // Inject only at the successful tool boundary that actually minted a name:
1121
+ // the first non-empty set-task or the transition to lifecycle done. The
1122
+ // coordination row intentionally remains pending until transcript evidence
1123
+ // catches up; reading that latch alone here would re-inject after every
1124
+ // later tool and make the agent print the same block repeatedly.
1170
1125
  try {
1171
- stampToolActivity(coordRoot, owner.instance_id, data);
1172
- } catch (err) {
1173
- logError(coordRoot, err, { phase: "post-tool-use-stamp" });
1174
- }
1175
-
1176
- // Image feed: a Bash command that wrote an image (harn browse, harn image,
1177
- // --diff, …) is the "agent produced this" signal. Scan the command + its
1178
- // output for freshly-written image paths and capture them.
1179
- try {
1180
- captureImages(coordRoot, {
1181
- eventType: "tool.post_use",
1182
- data,
1183
- payload,
1184
- instanceId: owner.instance_id,
1185
- sessionId,
1186
- adapter,
1187
- });
1126
+ const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, owner.instance_id));
1127
+ if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
1128
+ const { emitContext } = await import("./adapter/output.ts");
1129
+ emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
1130
+ }
1188
1131
  } catch (err) {
1189
- logError(coordRoot, err, { phase: "post-tool-use-image-capture" });
1132
+ logError(coordRoot, err, { phase: "post-tool-use-session-name" });
1190
1133
  }
1191
1134
  }
1192
1135
 
1193
1136
  // Phase 7: PostToolUseFailure: release claim on failed Edit (the file
1194
1137
  // never landed; the claim is stale). Adapter-agnostic.
1195
- if (norm.event_type === "tool.post_use_failure") {
1138
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure") {
1196
1139
  try {
1197
1140
  releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
1198
1141
  } catch (err) {
@@ -1203,11 +1146,48 @@ async function main(): Promise<number> {
1203
1146
  return 0;
1204
1147
  }
1205
1148
 
1206
- function stopEnforcementMode(rule: string): "enforced" | "observe_only" | "exempt" | "fail_open" {
1207
- if (rule === "stop-hook.codex_observe_only") return "observe_only";
1208
- if (rule === "stop-hook.fail_open" || rule === "stop-hook.no_history") return "fail_open";
1209
- if (rule === "stop-hook.bypass" || rule === "stop-hook.workflow_child") return "exempt";
1210
- return "enforced";
1149
+ async function enforcePendingSessionNameDisplay(
1150
+ coordRoot: string,
1151
+ instanceId: string,
1152
+ adapter: Adapter,
1153
+ payload: ParsedPayload | null,
1154
+ ): Promise<boolean> {
1155
+ const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, instanceId));
1156
+ if (!name) return true;
1157
+
1158
+ const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
1159
+ if (isSessionNameRemediationCommand(command, resolveBinName(coordRoot))) return true;
1160
+
1161
+ const inspection =
1162
+ payload?.agent_message !== undefined
1163
+ ? {
1164
+ state: assistantTextStartsWithSessionNameBlock(payload.agent_message, name)
1165
+ ? ("present" as const)
1166
+ : ("absent" as const),
1167
+ }
1168
+ : inspectSessionNameDisplayImmediately(
1169
+ payload?.transcript_path,
1170
+ name,
1171
+ assistantTextStartsWithSessionNameBlock,
1172
+ );
1173
+ if (inspection.state === "present") {
1174
+ stampSessionNameSeen(coordRoot, instanceId, name);
1175
+ return true;
1176
+ }
1177
+
1178
+ if (inspection.state === "unavailable") {
1179
+ const { emitContext } = await import("./adapter/output.ts");
1180
+ emitContext(
1181
+ adapter,
1182
+ "PreToolUse",
1183
+ "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.",
1184
+ );
1185
+ return true;
1186
+ }
1187
+
1188
+ const { emitDeny } = await import("./adapter/output.ts");
1189
+ emitDeny(adapter, sessionNameDisplayInstruction(name));
1190
+ return false;
1211
1191
  }
1212
1192
 
1213
1193
  async function runPreToolUseGuard(
@@ -1297,24 +1277,6 @@ async function runPreToolUseGuard(
1297
1277
  emitDeny(adapter, reason);
1298
1278
  return false;
1299
1279
  }
1300
-
1301
- // The verdict mutates the live heartbeat immediately, but that file is a
1302
- // projection and can be rebuilt at any later hook. Persist the acquisition
1303
- // in the canonical stream too. This matters most for Codex apply_patch:
1304
- // one tool call can claim several files, and tool.pre_use's clamped payload
1305
- // is not a durable ownership representation for those targets.
1306
- emit(coordRoot, {
1307
- event_type: "claim.acquire",
1308
- instance_id: instanceId,
1309
- session_id: sessionId,
1310
- adapter,
1311
- source: "agent-hooks",
1312
- data: {
1313
- path: target.path,
1314
- mode: "write",
1315
- ...(target.finalization ? { finalization: target.finalization } : {}),
1316
- },
1317
- });
1318
1280
  }
1319
1281
  return true;
1320
1282
  }
@@ -1375,33 +1337,10 @@ function parseApplyPatchPaths(data: Record<string, unknown>): string[] {
1375
1337
  return out;
1376
1338
  }
1377
1339
 
1378
- function healHeartbeatViaCli(
1379
- coordRoot: string,
1380
- instanceId: string,
1381
- sessionId: string,
1382
- adapter: string,
1383
- forkedFrom?: string,
1384
- ): void {
1385
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1386
- if (!existsSync(agentCoordBin)) return;
1387
- // Pass the detected adapter so a pruned Cursor/Codex heartbeat is recreated
1388
- // with the correct platform; without it, healHeartbeat defaults to
1389
- // claude-code and the dashboard mislabels the agent. See
1390
- // heartbeat-writer.healHeartbeat. --forked-from carries detected fork
1391
- // lineage for an instance that never session.started (the CC fork flow).
1392
- const args = ["heal-heartbeat", instanceId, sessionId, `--adapter=${adapter}`];
1393
- if (forkedFrom) args.push(`--forked-from=${forkedFrom}`);
1394
- spawnSync(agentCoordBin, args, {
1395
- encoding: "utf8",
1396
- timeout: 2000,
1397
- env: childEnv(coordRoot),
1398
- });
1399
- }
1400
-
1401
1340
  /**
1402
1341
  * Walk up the ppid chain on Linux/WSL looking for the adapter anchor PID,
1403
1342
  * the PID of the claude / cursor / codex binary. Finds the agent PID. Used by
1404
- * `tool.pre_use`'s pid-map self-heal so a re-parented adapter binary (the
1343
+ * `tool.requested` pid-map self-heal so a re-parented adapter binary (the
1405
1344
  * VS Code 2.1.x sibling-claude spawn case) gets its pid-map row rewritten on
1406
1345
  * the next tool call rather than going invisible until SessionStart fires
1407
1346
  * again, which it may never do.
@@ -1468,9 +1407,7 @@ function findAdapterAnchorPid(adapter?: Adapter): number | undefined {
1468
1407
  }
1469
1408
 
1470
1409
  /**
1471
- * Pid-map self-heal for `tool.pre_use`. Symmetric counterpart to
1472
- * `healHeartbeatViaCli`; the two were paired before the Phase 6 refactor split
1473
- * them apart, then restored together afterward.
1410
+ * Pid-map self-heal for `tool.requested`.
1474
1411
  *
1475
1412
  * The pid argument prefers the payload's `pid` (CC populates it on
1476
1413
  * SessionStart and may also send it on PreToolUse), then
@@ -1493,40 +1430,6 @@ function refreshPidmap(
1493
1430
  writePidmapViaAgentCoord(coordRoot, pid, instanceId, adapterPlatform(adapter));
1494
1431
  }
1495
1432
 
1496
- function stampToolActivity(
1497
- coordRoot: string,
1498
- instanceId: string,
1499
- data: Record<string, unknown>,
1500
- ): void {
1501
- const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1502
- if (!existsSync(agentCoordBin)) return;
1503
- const toolName = (data.tool_name as string | undefined) ?? "";
1504
- // Extract a 1-line target from the tool_input blob (file path / command head).
1505
- const toolInputRaw = data.tool_input;
1506
- let target = "";
1507
- if (typeof toolInputRaw === "string") {
1508
- try {
1509
- const parsed = JSON.parse(toolInputRaw) as Record<string, unknown>;
1510
- target =
1511
- (parsed.file_path as string | undefined) ??
1512
- (parsed.path as string | undefined) ??
1513
- (parsed.notebook_path as string | undefined) ??
1514
- (parsed.command as string | undefined) ??
1515
- (parsed.url as string | undefined) ??
1516
- (parsed.pattern as string | undefined) ??
1517
- "";
1518
- } catch {
1519
- /* skip */
1520
- }
1521
- }
1522
- if (target.length > 200) target = target.slice(0, 200);
1523
- spawnSync(agentCoordBin, ["stamp-tool-activity", instanceId, toolName, target], {
1524
- encoding: "utf8",
1525
- timeout: 2000,
1526
- env: childEnv(coordRoot),
1527
- });
1528
- }
1529
-
1530
1433
  function releaseClaimOnFailure(
1531
1434
  coordRoot: string,
1532
1435
  instanceId: string,
@@ -1570,6 +1473,12 @@ function releaseClaimOnFailure(
1570
1473
  : filePath;
1571
1474
  }
1572
1475
 
1476
+ // In V3 the release is an authority event, not a heartbeat mutation. Avoid
1477
+ // creating a disposable cache for a failure that never acquired a claim,
1478
+ // but release the exact path when the validated projection says it is held.
1479
+ const row = readLiveCoordinationRow(coordRoot, instanceId);
1480
+ if (!row?.files_touched?.includes(canonical)) return;
1481
+
1573
1482
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1574
1483
  if (!existsSync(agentCoordBin)) return;
1575
1484
  spawnSync(agentCoordBin, ["release-claim", instanceId, canonical], {
@@ -1580,15 +1489,6 @@ function releaseClaimOnFailure(
1580
1489
  }
1581
1490
 
1582
1491
  function cleanupSessionEnd(coordRoot: string, instanceId: string, reason: string): void {
1583
- // Remove heartbeat from the canonical .harnery/active/ dir.
1584
- const path = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
1585
- try {
1586
- if (existsSync(path)) {
1587
- require("node:fs").unlinkSync(path);
1588
- }
1589
- } catch {
1590
- /* swallow */
1591
- }
1592
1492
  // Sweep pid-map entries pointing to this instance
1593
1493
  const pidmapDir = join(coordRoot, ".harnery", "pid-map");
1594
1494
  if (existsSync(pidmapDir)) {
@@ -1633,21 +1533,7 @@ async function emitUserPromptSubmitSystemMessage(
1633
1533
  let additionalContext = "";
1634
1534
 
1635
1535
  if (existsSync(agentCoordBin)) {
1636
- // Look up the agent's name from its heartbeat (for council pending rendering).
1637
- let agentName = "";
1638
- try {
1639
- const fs = require("node:fs") as typeof import("node:fs");
1640
- const hbPath = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
1641
- if (fs.existsSync(hbPath)) {
1642
- const hb = JSON.parse(fs.readFileSync(hbPath, "utf8")) as { name?: string };
1643
- agentName = hb.name ?? "";
1644
- }
1645
- } catch {
1646
- /* fall through with empty name; peer table still renders */
1647
- }
1648
-
1649
1536
  const args = ["prompt-context", "--instance", instanceId, "--session", sessionId];
1650
- if (agentName) args.push("--name", agentName);
1651
1537
  // Every human-facing adapter gets the first-session naming reminder from
1652
1538
  // the shared hook path. Cursor + Codex additionally get ongoing set-task
1653
1539
  // staleness nudges; Claude Code enforces those via its Stop transcript.
@@ -1688,7 +1574,7 @@ function emitSubagentStartContext(
1688
1574
  adapter: Adapter,
1689
1575
  ): void {
1690
1576
  // Look up the subagent's assigned name (just-written by agent-coord assignName
1691
- // in session.start data) + the parent's short id for the "you are a subagent
1577
+ // in session.started data) + the parent's short id for the "you are a subagent
1692
1578
  // of X" framing.
1693
1579
  const subagentName = (data.name as string | undefined) ?? "";
1694
1580
  if (!subagentName) return;
@@ -1733,13 +1619,19 @@ async function emitSessionStartSystemMessage(
1733
1619
  recoveryBriefing = "",
1734
1620
  ): Promise<boolean> {
1735
1621
  const agentCoordBin = coordBinPath("agent-coord", coordRoot) ?? "";
1622
+ const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
1736
1623
  let additionalContext = "";
1737
1624
  if (existsSync(agentCoordBin)) {
1738
1625
  // Sync-project so the heartbeat exists for downstream readers (peer table,
1739
1626
  // wiring check, council invites).
1740
1627
  spawnSync(agentCoordBin, ["project"], { encoding: "utf8", timeout: 3000 });
1741
- // Stale-sweep dead peers before rendering peer table.
1742
- spawnSync(agentCoordBin, ["stale-sweep"], { encoding: "utf8", timeout: 3000 });
1628
+ // Opportunistic reconciliation makes normal session starts a failsafe for
1629
+ // archive, idle, cascade, and host lifecycle observations.
1630
+ spawnSync(agentCoordBin, ["reconcile-finalization"], {
1631
+ encoding: "utf8",
1632
+ timeout: 5000,
1633
+ env: { ...process.env, HARNERY_COORD_ROOT_OVERRIDE: coordRoot },
1634
+ });
1743
1635
 
1744
1636
  // SESSION_START activity log line, fired across all adapters.
1745
1637
  const model = (emittedData.model as string | undefined) ?? "unknown";
@@ -1756,32 +1648,37 @@ async function emitSessionStartSystemMessage(
1756
1648
  { encoding: "utf8", timeout: 2000 },
1757
1649
  );
1758
1650
 
1759
- // Render the systemMessage via agent-coord.
1760
- const agentName = (emittedData.name as string | undefined) ?? "";
1761
- const args = ["session-context", "--instance", instanceId, "--session", sessionId];
1762
- if (agentName) args.push("--name", agentName);
1763
- // The "You are agent-X." prefix in session-context renders unqualified by
1764
- // default (claude-code-style). For cursor/codex the bash dispatchers add
1765
- // a "(Cursor)" / "(Codex)" suffix; pass it through as --platform-label.
1766
- if (adapter !== "claude-code") {
1767
- args.push("--platform-label", platform === "cursor" ? "Cursor" : "Codex");
1651
+ // Workflow children retain lifecycle/event capture but do not receive
1652
+ // operator-facing peer, council, or init-remediation context. Injecting
1653
+ // that context can make a bounded child follow housekeeping instructions
1654
+ // instead of its assigned prompt.
1655
+ if (!workflowChild) {
1656
+ const agentName = (emittedData.name as string | undefined) ?? "";
1657
+ const args = ["session-context", "--instance", instanceId, "--session", sessionId];
1658
+ if (agentName) args.push("--name", agentName);
1659
+ // The "You are agent-X." prefix in session-context renders unqualified by
1660
+ // default (claude-code-style). For cursor/codex the bash dispatchers add
1661
+ // a "(Cursor)" / "(Codex)" suffix; pass it through as --platform-label.
1662
+ if (adapter !== "claude-code") {
1663
+ args.push("--platform-label", platform === "cursor" ? "Cursor" : "Codex");
1664
+ }
1665
+ const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1666
+ if (result.status === 0 && result.stdout) additionalContext = result.stdout.trim();
1768
1667
  }
1769
- const result = spawnSync(agentCoordBin, args, { encoding: "utf8", timeout: 3000 });
1770
- if (result.status === 0 && result.stdout) additionalContext = result.stdout.trim();
1771
1668
  }
1772
1669
 
1773
1670
  // Effect (claude-code): merge the journal recovery cue into the session-start
1774
1671
  // context. Was a standalone additionalContext emission from the previous
1775
1672
  // journal-on-start adapter; now that agent-hook is the single SessionStart
1776
1673
  // entry, it folds in here.
1777
- if (adapter === "claude-code") {
1674
+ if (adapter === "claude-code" && !workflowChild) {
1778
1675
  const cue = journalRecoveryCue(coordRoot);
1779
1676
  if (cue) additionalContext = [additionalContext, cue].filter(Boolean).join("\n\n");
1780
1677
  }
1781
- if (recoveryBriefing) {
1678
+ if (recoveryBriefing && !workflowChild) {
1782
1679
  additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
1783
1680
  }
1784
- if (adapter === "codex" && isWslUncPath(emittedData.cwd)) {
1681
+ if (adapter === "codex" && !workflowChild && isWslUncPath(emittedData.cwd)) {
1785
1682
  const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, emittedData.cwd);
1786
1683
  if (fileLinkContext) {
1787
1684
  additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
@@ -1807,22 +1704,10 @@ function completeRecoveryInjection(
1807
1704
  coordRoot: string,
1808
1705
  instanceId: string,
1809
1706
  sessionId: string,
1810
- adapter: Adapter,
1811
1707
  recovery: PreparedContextRecovery,
1812
- injectionEvent: "SessionStart" | "UserPromptSubmit",
1813
1708
  ): void {
1814
1709
  completeContextRecovery(coordRoot, { sessionId, instanceId });
1815
- emit(coordRoot, {
1816
- event_type: "context.recovery.injected",
1817
- instance_id: instanceId,
1818
- session_id: sessionId,
1819
- adapter,
1820
- data: {
1821
- capsule_id: recovery.capsule.capsule_id,
1822
- generation: recovery.capsule.generation,
1823
- injection_event: injectionEvent,
1824
- },
1825
- });
1710
+ void recovery;
1826
1711
  }
1827
1712
 
1828
1713
  main()