harnery 0.40.0 → 0.42.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 (595) hide show
  1. package/README.md +1 -1
  2. package/dist/commander.js +4 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +34 -20
  5. package/dist/commands/artifacts.d.ts.map +1 -1
  6. package/dist/commands/artifacts.js +113 -12
  7. package/dist/commands/browse-ai.d.ts.map +1 -1
  8. package/dist/commands/browse-ai.js +4 -0
  9. package/dist/commands/browse.d.ts +164 -0
  10. package/dist/commands/browse.d.ts.map +1 -1
  11. package/dist/commands/browse.js +388 -45
  12. package/dist/commands/checkpoint.js +8 -12
  13. package/dist/commands/claude-desktop.d.ts.map +1 -1
  14. package/dist/commands/claude-desktop.js +52 -0
  15. package/dist/commands/deinit.d.ts +1 -1
  16. package/dist/commands/deinit.d.ts.map +1 -1
  17. package/dist/commands/deinit.js +8 -4
  18. package/dist/commands/disk.d.ts +6 -0
  19. package/dist/commands/disk.d.ts.map +1 -0
  20. package/dist/commands/disk.js +80 -0
  21. package/dist/commands/docs.d.ts.map +1 -1
  22. package/dist/commands/docs.js +2 -0
  23. package/dist/commands/doctor.d.ts +8 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +58 -3
  26. package/dist/commands/fetch.d.ts.map +1 -1
  27. package/dist/commands/fetch.js +10 -0
  28. package/dist/commands/files.d.ts +16 -0
  29. package/dist/commands/files.d.ts.map +1 -1
  30. package/dist/commands/files.js +54 -2
  31. package/dist/commands/init.d.ts +21 -5
  32. package/dist/commands/init.d.ts.map +1 -1
  33. package/dist/commands/init.js +271 -109
  34. package/dist/commands/instructions.js +5 -4
  35. package/dist/commands/ledger-v3.d.ts.map +1 -1
  36. package/dist/commands/ledger-v3.js +2 -1
  37. package/dist/commands/rm.d.ts +4 -0
  38. package/dist/commands/rm.d.ts.map +1 -0
  39. package/dist/commands/rm.js +87 -0
  40. package/dist/commands/servers.d.ts +7 -0
  41. package/dist/commands/servers.d.ts.map +1 -0
  42. package/dist/commands/servers.js +394 -0
  43. package/dist/commands/tunnel.d.ts.map +1 -1
  44. package/dist/commands/tunnel.js +82 -2
  45. package/dist/commands/webview.d.ts +4 -0
  46. package/dist/commands/webview.d.ts.map +1 -0
  47. package/dist/commands/webview.js +94 -0
  48. package/dist/core/adapter.d.ts +50 -1
  49. package/dist/core/adapter.d.ts.map +1 -1
  50. package/dist/core/adapter.js +59 -1
  51. package/dist/core/adapters/attestation.d.ts.map +1 -1
  52. package/dist/core/adapters/attestation.js +3 -2
  53. package/dist/core/adapters/profiles.d.ts +29 -3
  54. package/dist/core/adapters/profiles.d.ts.map +1 -1
  55. package/dist/core/adapters/profiles.js +53 -1
  56. package/dist/core/adapters/registry.d.ts.map +1 -1
  57. package/dist/core/adapters/registry.js +33 -0
  58. package/dist/core/adapters/types.d.ts +9 -0
  59. package/dist/core/adapters/types.d.ts.map +1 -1
  60. package/dist/core/agents/canonical-emit.d.ts +1 -1
  61. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  62. package/dist/core/agents/canonical-emit.js +2 -7
  63. package/dist/core/agents/cli.js +25 -16
  64. package/dist/core/agents/coord-client.d.ts.map +1 -1
  65. package/dist/core/agents/coord-client.js +7 -2
  66. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  67. package/dist/core/agents/render/session-context.js +2 -0
  68. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -1
  69. package/dist/core/agents/rules/claim-conflict.js +3 -9
  70. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  71. package/dist/core/agents/rules/commit-conflict.js +2 -8
  72. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  73. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  74. package/dist/core/agents/rules/stop-hook.js +1 -1
  75. package/dist/core/agents/session-events.d.ts +6 -2
  76. package/dist/core/agents/session-events.d.ts.map +1 -1
  77. package/dist/core/agents/session-events.js +105 -26
  78. package/dist/core/agents/session-finalization-state-v3.d.ts.map +1 -1
  79. package/dist/core/agents/session-finalization-state-v3.js +2 -1
  80. package/dist/core/agents/session-finalizer-v3.d.ts.map +1 -1
  81. package/dist/core/agents/session-finalizer-v3.js +5 -4
  82. package/dist/core/agents/session-name-display.d.ts +28 -0
  83. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  84. package/dist/core/agents/session-name-display.js +43 -0
  85. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  86. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  87. package/dist/core/agents/state/heartbeat-writer.d.ts +10 -0
  88. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  89. package/dist/core/agents/state/heartbeat-writer.js +21 -2
  90. package/dist/core/agents/state/live-coordination-view.d.ts +20 -0
  91. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  92. package/dist/core/agents/state/live-coordination-view.js +61 -0
  93. package/dist/core/agents/state/live-coordination-writer.js +3 -0
  94. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  95. package/dist/core/agents/state/stale-sweep.js +2 -10
  96. package/dist/core/agents/turn-changes.d.ts +17 -0
  97. package/dist/core/agents/turn-changes.d.ts.map +1 -0
  98. package/dist/core/agents/turn-changes.js +76 -0
  99. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  100. package/dist/core/artifacts/delivery-card.js +11 -3
  101. package/dist/core/artifacts/index.d.ts +140 -4
  102. package/dist/core/artifacts/index.d.ts.map +1 -1
  103. package/dist/core/artifacts/index.js +526 -52
  104. package/dist/core/config.d.ts +73 -11
  105. package/dist/core/config.d.ts.map +1 -1
  106. package/dist/core/config.js +142 -3
  107. package/dist/core/context/index.d.ts.map +1 -1
  108. package/dist/core/context/index.js +2 -1
  109. package/dist/core/conversations/archive.d.ts.map +1 -1
  110. package/dist/core/conversations/archive.js +4 -3
  111. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  112. package/dist/core/diagnostics/bundle.js +5 -4
  113. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -1
  114. package/dist/core/events/legacy-storage/canary.js +5 -4
  115. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -1
  116. package/dist/core/events/legacy-storage/compression.js +2 -1
  117. package/dist/core/events/v3/adapter-id.d.ts +10 -2
  118. package/dist/core/events/v3/adapter-id.d.ts.map +1 -1
  119. package/dist/core/events/v3/adapter-id.js +16 -0
  120. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  121. package/dist/core/events/v3/authority-outbox.js +2 -1
  122. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  123. package/dist/core/events/v3/bootstrap.js +5 -4
  124. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  125. package/dist/core/events/v3/capabilities.js +17 -0
  126. package/dist/core/events/v3/contract.d.ts +5 -0
  127. package/dist/core/events/v3/contract.d.ts.map +1 -1
  128. package/dist/core/events/v3/contract.js +7 -1
  129. package/dist/core/events/v3/control-witness.d.ts.map +1 -1
  130. package/dist/core/events/v3/control-witness.js +3 -2
  131. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -1
  132. package/dist/core/events/v3/coordination-transaction-recovery.js +4 -3
  133. package/dist/core/events/v3/fingerprint-keys.d.ts.map +1 -1
  134. package/dist/core/events/v3/fingerprint-keys.js +7 -6
  135. package/dist/core/events/v3/generated.d.ts +1 -1
  136. package/dist/core/events/v3/generated.js +1 -1
  137. package/dist/core/events/v3/live-feed.d.ts.map +1 -1
  138. package/dist/core/events/v3/live-feed.js +5 -4
  139. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  140. package/dist/core/events/v3/producers/command-recorder.js +8 -7
  141. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  142. package/dist/core/events/v3/producers/coordination-recorder.js +8 -7
  143. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/diagnostic-summaries.js +5 -4
  145. package/dist/core/events/v3/producers/hook-base.d.ts +1 -0
  146. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  147. package/dist/core/events/v3/producers/hook-base.js +10 -4
  148. package/dist/core/events/v3/producers/hook.d.ts +2 -0
  149. package/dist/core/events/v3/producers/hook.d.ts.map +1 -1
  150. package/dist/core/events/v3/producers/hook.js +16 -1
  151. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  152. package/dist/core/events/v3/producers/intake.js +16 -6
  153. package/dist/core/events/v3/producers/recorder.d.ts +2 -4
  154. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  155. package/dist/core/events/v3/producers/recorder.js +125 -101
  156. package/dist/core/events/v3/projection.js +9 -1
  157. package/dist/core/events/v3/recovery.d.ts.map +1 -1
  158. package/dist/core/events/v3/recovery.js +4 -3
  159. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -1
  160. package/dist/core/events/v3/support-storage/inventory.js +2 -1
  161. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -1
  162. package/dist/core/events/v3/support-storage/maintenance.js +4 -3
  163. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -1
  164. package/dist/core/events/v3/support-storage/pack-reader.js +4 -3
  165. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -1
  166. package/dist/core/events/v3/support-storage/pack-writer.js +6 -5
  167. package/dist/core/events/v3/tool-line-changes.d.ts +8 -0
  168. package/dist/core/events/v3/tool-line-changes.d.ts.map +1 -0
  169. package/dist/core/events/v3/tool-line-changes.js +135 -0
  170. package/dist/core/events/v3/writer.d.ts.map +1 -1
  171. package/dist/core/events/v3/writer.js +8 -7
  172. package/dist/core/governor/planning.d.ts.map +1 -1
  173. package/dist/core/governor/planning.js +6 -5
  174. package/dist/core/governor/service.d.ts.map +1 -1
  175. package/dist/core/governor/service.js +12 -11
  176. package/dist/core/governor/state.d.ts.map +1 -1
  177. package/dist/core/governor/state.js +7 -6
  178. package/dist/core/guard/evaluator.js +1 -0
  179. package/dist/core/guard/storage.d.ts.map +1 -1
  180. package/dist/core/guard/storage.js +3 -2
  181. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts +4 -0
  182. package/dist/core/hooks/adapter/behaviors/claude-code.d.ts.map +1 -0
  183. package/dist/core/hooks/adapter/behaviors/claude-code.js +31 -0
  184. package/dist/core/hooks/adapter/behaviors/codex.d.ts +15 -0
  185. package/dist/core/hooks/adapter/behaviors/codex.d.ts.map +1 -0
  186. package/dist/core/hooks/adapter/behaviors/codex.js +82 -0
  187. package/dist/core/hooks/adapter/behaviors/cursor.d.ts +7 -0
  188. package/dist/core/hooks/adapter/behaviors/cursor.d.ts.map +1 -0
  189. package/dist/core/hooks/adapter/behaviors/cursor.js +35 -0
  190. package/dist/core/hooks/adapter/behaviors/index.d.ts +11 -0
  191. package/dist/core/hooks/adapter/behaviors/index.d.ts.map +1 -0
  192. package/dist/core/hooks/adapter/behaviors/index.js +20 -0
  193. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts +7 -0
  194. package/dist/core/hooks/adapter/behaviors/openclaw.d.ts.map +1 -0
  195. package/dist/core/hooks/adapter/behaviors/openclaw.js +34 -0
  196. package/dist/core/hooks/adapter/behaviors/opencode.d.ts +7 -0
  197. package/dist/core/hooks/adapter/behaviors/opencode.d.ts.map +1 -0
  198. package/dist/core/hooks/adapter/behaviors/opencode.js +34 -0
  199. package/dist/core/hooks/adapter/behaviors/types.d.ts +89 -0
  200. package/dist/core/hooks/adapter/behaviors/types.d.ts.map +1 -0
  201. package/dist/core/hooks/adapter/behaviors/types.js +1 -0
  202. package/dist/core/hooks/adapter/detect.d.ts +1 -1
  203. package/dist/core/hooks/adapter/detect.d.ts.map +1 -1
  204. package/dist/core/hooks/adapter/detect.js +2 -3
  205. package/dist/core/hooks/adapter/events.d.ts +46 -1
  206. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  207. package/dist/core/hooks/adapter/events.js +36 -0
  208. package/dist/core/hooks/adapter/opencode-plugin.d.ts +60 -0
  209. package/dist/core/hooks/adapter/opencode-plugin.d.ts.map +1 -0
  210. package/dist/core/hooks/adapter/opencode-plugin.js +129 -0
  211. package/dist/core/hooks/adapter/output.d.ts +7 -0
  212. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  213. package/dist/core/hooks/adapter/output.js +8 -1
  214. package/dist/core/hooks/adapter/runtime-telemetry.js +3 -1
  215. package/dist/core/hooks/adapter/wiring.d.ts +52 -8
  216. package/dist/core/hooks/adapter/wiring.d.ts.map +1 -1
  217. package/dist/core/hooks/adapter/wiring.js +133 -12
  218. package/dist/core/hooks/cli.js +676 -531
  219. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  220. package/dist/core/hooks/effects/image-capture.js +2 -1
  221. package/dist/core/hooks/prompt-context/contract.d.ts.map +1 -1
  222. package/dist/core/hooks/prompt-context/contract.js +1 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts +1 -1
  224. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  225. package/dist/core/hooks/resolve/transcript.js +35 -4
  226. package/dist/core/hooks/run-markers.d.ts +37 -0
  227. package/dist/core/hooks/run-markers.d.ts.map +1 -0
  228. package/dist/core/hooks/run-markers.js +109 -0
  229. package/dist/core/hooks/session-name-presence.d.ts +3 -1
  230. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  231. package/dist/core/hooks/session-name-presence.js +7 -2
  232. package/dist/core/hooks/shell-waiter.d.ts +2 -0
  233. package/dist/core/hooks/shell-waiter.d.ts.map +1 -0
  234. package/dist/core/hooks/shell-waiter.js +173 -0
  235. package/dist/core/resources/service.d.ts.map +1 -1
  236. package/dist/core/resources/service.js +5 -4
  237. package/dist/core/semantic/review.d.ts.map +1 -1
  238. package/dist/core/semantic/review.js +5 -4
  239. package/dist/core/semantic/service.d.ts.map +1 -1
  240. package/dist/core/semantic/service.js +15 -14
  241. package/dist/core/semantic/storage.d.ts.map +1 -1
  242. package/dist/core/semantic/storage.js +5 -4
  243. package/dist/core/servers/index.d.ts +211 -0
  244. package/dist/core/servers/index.d.ts.map +1 -0
  245. package/dist/core/servers/index.js +560 -0
  246. package/dist/core/servers/net.d.ts +58 -0
  247. package/dist/core/servers/net.d.ts.map +1 -0
  248. package/dist/core/servers/net.js +270 -0
  249. package/dist/core/servers/tunnels.d.ts +14 -0
  250. package/dist/core/servers/tunnels.d.ts.map +1 -0
  251. package/dist/core/servers/tunnels.js +33 -0
  252. package/dist/core/storage/atomic-json.d.ts.map +1 -1
  253. package/dist/core/storage/atomic-json.js +7 -3
  254. package/dist/core/storage/builtins.js +1 -0
  255. package/dist/core/storage/durable-history.d.ts.map +1 -1
  256. package/dist/core/storage/durable-history.js +6 -5
  257. package/dist/core/storage/maintenance-providers.d.ts.map +1 -1
  258. package/dist/core/storage/maintenance-providers.js +2 -1
  259. package/dist/core/storage/maintenance.d.ts.map +1 -1
  260. package/dist/core/storage/maintenance.js +7 -6
  261. package/dist/core/storage/metrics.d.ts.map +1 -1
  262. package/dist/core/storage/metrics.js +5 -1
  263. package/dist/core/storage/modes.d.ts +17 -0
  264. package/dist/core/storage/modes.d.ts.map +1 -0
  265. package/dist/core/storage/modes.js +47 -0
  266. package/dist/core/storage/process-log.d.ts.map +1 -1
  267. package/dist/core/storage/process-log.js +3 -2
  268. package/dist/core/storage/receipts.d.ts.map +1 -1
  269. package/dist/core/storage/receipts.js +4 -3
  270. package/dist/core/storage/segments.d.ts.map +1 -1
  271. package/dist/core/storage/segments.js +7 -6
  272. package/dist/core/supervisor/hook-health.d.ts +2 -1
  273. package/dist/core/supervisor/hook-health.d.ts.map +1 -1
  274. package/dist/core/supervisor/hook-health.js +4 -3
  275. package/dist/core/supervisor/service.d.ts.map +1 -1
  276. package/dist/core/supervisor/service.js +7 -6
  277. package/dist/core/work/state.d.ts.map +1 -1
  278. package/dist/core/work/state.js +8 -7
  279. package/dist/core/workflow/adapters.d.ts +7 -0
  280. package/dist/core/workflow/adapters.d.ts.map +1 -1
  281. package/dist/core/workflow/adapters.js +9 -0
  282. package/dist/core/workflow/approvals.d.ts.map +1 -1
  283. package/dist/core/workflow/approvals.js +6 -5
  284. package/dist/core/workflow/billing.d.ts.map +1 -1
  285. package/dist/core/workflow/billing.js +33 -4
  286. package/dist/core/workflow/durable-record.d.ts.map +1 -1
  287. package/dist/core/workflow/durable-record.js +4 -3
  288. package/dist/core/workflow/engine.d.ts.map +1 -1
  289. package/dist/core/workflow/engine.js +30 -1
  290. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  291. package/dist/core/workflow/live-session-v3.js +2 -3
  292. package/dist/core/workflow/run-state.d.ts.map +1 -1
  293. package/dist/core/workflow/run-state.js +3 -2
  294. package/dist/core/workflow/sandbox-projection.d.ts +13 -2
  295. package/dist/core/workflow/sandbox-projection.d.ts.map +1 -1
  296. package/dist/core/workflow/sandbox-projection.js +23 -0
  297. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  298. package/dist/core/workflow/spawn-claude.js +6 -1
  299. package/dist/core/workflow/spawn-codex.d.ts +2 -0
  300. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  301. package/dist/core/workflow/spawn-codex.js +20 -1
  302. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  303. package/dist/core/workflow/spawn-cursor.js +6 -1
  304. package/dist/core/workflow/spawn-opencode.d.ts +36 -0
  305. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -0
  306. package/dist/core/workflow/spawn-opencode.js +135 -0
  307. package/dist/core/workflow/transcript.d.ts.map +1 -1
  308. package/dist/core/workflow/transcript.js +3 -2
  309. package/dist/core/workflow/types.d.ts +47 -2
  310. package/dist/core/workflow/types.d.ts.map +1 -1
  311. package/dist/core/workflow/worker-access.d.ts +92 -0
  312. package/dist/core/workflow/worker-access.d.ts.map +1 -0
  313. package/dist/core/workflow/worker-access.js +450 -0
  314. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  315. package/dist/core/workflow/workspaces/leases.js +5 -4
  316. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  317. package/dist/core/workflow/workspaces/local-git.js +3 -2
  318. package/dist/core/workflow/workspaces/paths.d.ts.map +1 -1
  319. package/dist/core/workflow/workspaces/paths.js +2 -1
  320. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  321. package/dist/core/workflow/workspaces/state.js +10 -9
  322. package/dist/lazy-commands.js +8 -1
  323. package/dist/lib/agent-browser/client.d.ts +9 -0
  324. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  325. package/dist/lib/agent-browser/client.js +13 -0
  326. package/dist/lib/browser/batch-steps.d.ts +18 -0
  327. package/dist/lib/browser/batch-steps.d.ts.map +1 -0
  328. package/dist/lib/browser/batch-steps.js +82 -0
  329. package/dist/lib/browser/client.d.ts +35 -1
  330. package/dist/lib/browser/client.d.ts.map +1 -1
  331. package/dist/lib/browser/client.js +117 -8
  332. package/dist/lib/browser/collect/collector.d.ts +99 -0
  333. package/dist/lib/browser/collect/collector.d.ts.map +1 -0
  334. package/dist/lib/browser/collect/collector.js +202 -0
  335. package/dist/lib/browser/collect/fields.d.ts +31 -0
  336. package/dist/lib/browser/collect/fields.d.ts.map +1 -0
  337. package/dist/lib/browser/collect/fields.js +75 -0
  338. package/dist/lib/browser/collect/index.d.ts +6 -0
  339. package/dist/lib/browser/collect/index.d.ts.map +1 -0
  340. package/dist/lib/browser/collect/index.js +5 -0
  341. package/dist/lib/browser/collect/page-driver.d.ts +14 -0
  342. package/dist/lib/browser/collect/page-driver.d.ts.map +1 -0
  343. package/dist/lib/browser/collect/page-driver.js +306 -0
  344. package/dist/lib/browser/collect/presets.d.ts +52 -0
  345. package/dist/lib/browser/collect/presets.d.ts.map +1 -0
  346. package/dist/lib/browser/collect/presets.js +176 -0
  347. package/dist/lib/browser/collect/scroll-plan.d.ts +85 -0
  348. package/dist/lib/browser/collect/scroll-plan.d.ts.map +1 -0
  349. package/dist/lib/browser/collect/scroll-plan.js +118 -0
  350. package/dist/lib/browser/identity.d.ts +25 -0
  351. package/dist/lib/browser/identity.d.ts.map +1 -0
  352. package/dist/lib/browser/identity.js +66 -0
  353. package/dist/lib/browser/index.d.ts +3 -2
  354. package/dist/lib/browser/index.d.ts.map +1 -1
  355. package/dist/lib/browser/index.js +2 -1
  356. package/dist/lib/browser/launch-args.d.ts +26 -0
  357. package/dist/lib/browser/launch-args.d.ts.map +1 -1
  358. package/dist/lib/browser/launch-args.js +41 -1
  359. package/dist/lib/browser/page-review-judge.d.ts +2 -0
  360. package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
  361. package/dist/lib/browser/page-review-judge.js +13 -0
  362. package/dist/lib/browser/qa-run-contracts.d.ts +10 -0
  363. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
  364. package/dist/lib/browser/qa-run.d.ts.map +1 -1
  365. package/dist/lib/browser/qa-run.js +1 -0
  366. package/dist/lib/browser/user-agent.d.ts +53 -0
  367. package/dist/lib/browser/user-agent.d.ts.map +1 -0
  368. package/dist/lib/browser/user-agent.js +79 -0
  369. package/dist/lib/browser/windows-chrome.d.ts +59 -0
  370. package/dist/lib/browser/windows-chrome.d.ts.map +1 -0
  371. package/dist/lib/browser/windows-chrome.js +179 -0
  372. package/dist/lib/claude-desktop-share.d.ts +136 -0
  373. package/dist/lib/claude-desktop-share.d.ts.map +1 -0
  374. package/dist/lib/claude-desktop-share.js +394 -0
  375. package/dist/lib/disk-usage.d.ts +50 -0
  376. package/dist/lib/disk-usage.d.ts.map +1 -0
  377. package/dist/lib/disk-usage.js +303 -0
  378. package/dist/lib/docs-lint.d.ts +6 -0
  379. package/dist/lib/docs-lint.d.ts.map +1 -1
  380. package/dist/lib/docs-lint.js +8 -3
  381. package/dist/lib/docs-metadata-managed.d.ts +1 -0
  382. package/dist/lib/docs-metadata-managed.d.ts.map +1 -1
  383. package/dist/lib/docs-metadata-managed.js +17 -1
  384. package/dist/lib/guarded-remove.d.ts +34 -0
  385. package/dist/lib/guarded-remove.d.ts.map +1 -0
  386. package/dist/lib/guarded-remove.js +310 -0
  387. package/dist/lib/identities/assume.d.ts.map +1 -1
  388. package/dist/lib/identities/assume.js +4 -9
  389. package/dist/lib/instructions/apply.d.ts.map +1 -1
  390. package/dist/lib/instructions/apply.js +2 -0
  391. package/dist/lib/instructions/templates.js +8 -8
  392. package/dist/lib/pace/index.d.ts +2 -0
  393. package/dist/lib/pace/index.d.ts.map +1 -0
  394. package/dist/lib/pace/index.js +1 -0
  395. package/dist/lib/pace/pace.d.ts +95 -0
  396. package/dist/lib/pace/pace.d.ts.map +1 -0
  397. package/dist/lib/pace/pace.js +313 -0
  398. package/dist/lib/tunnel/error-page.d.ts +1 -1
  399. package/dist/lib/tunnel/error-page.d.ts.map +1 -1
  400. package/dist/lib/tunnel/error-page.js +19 -8
  401. package/dist/lib/tunnel/forward-headers.d.ts +11 -0
  402. package/dist/lib/tunnel/forward-headers.d.ts.map +1 -0
  403. package/dist/lib/tunnel/forward-headers.js +17 -0
  404. package/dist/lib/tunnel/gate.js +34 -10
  405. package/dist/lib/tunnel/path-scope.d.ts +22 -0
  406. package/dist/lib/tunnel/path-scope.d.ts.map +1 -0
  407. package/dist/lib/tunnel/path-scope.js +62 -0
  408. package/dist/lib/tunnel/state.d.ts +5 -0
  409. package/dist/lib/tunnel/state.d.ts.map +1 -1
  410. package/dist/lib/tunnel/state.js +17 -3
  411. package/dist/lib/webview/index.d.ts +2 -0
  412. package/dist/lib/webview/index.d.ts.map +1 -0
  413. package/dist/lib/webview/index.js +1 -0
  414. package/dist/lib/webview/webview.d.ts +116 -0
  415. package/dist/lib/webview/webview.d.ts.map +1 -0
  416. package/dist/lib/webview/webview.js +289 -0
  417. package/opencode-plugin/package.json +19 -0
  418. package/opencode-plugin/src/index.ts +791 -0
  419. package/package.json +16 -4
  420. package/schemas/config.schema.json +104 -0
  421. package/schemas/event-v3.schema.json +164 -0
  422. package/src/commander.ts +26 -0
  423. package/src/commands/agents.ts +47 -18
  424. package/src/commands/artifacts.ts +171 -19
  425. package/src/commands/browse-ai.ts +8 -0
  426. package/src/commands/browse.ts +570 -48
  427. package/src/commands/checkpoint.ts +7 -9
  428. package/src/commands/claude-desktop.ts +61 -0
  429. package/src/commands/deinit.ts +9 -4
  430. package/src/commands/disk.ts +114 -0
  431. package/src/commands/docs.ts +2 -0
  432. package/src/commands/doctor.ts +63 -3
  433. package/src/commands/fetch.ts +14 -0
  434. package/src/commands/files.ts +93 -2
  435. package/src/commands/init.ts +334 -122
  436. package/src/commands/instructions.ts +8 -4
  437. package/src/commands/ledger-v3.ts +2 -1
  438. package/src/commands/rm.ts +115 -0
  439. package/src/commands/servers.ts +506 -0
  440. package/src/commands/tunnel.ts +102 -2
  441. package/src/commands/webview.ts +159 -0
  442. package/src/core/adapter.ts +102 -1
  443. package/src/core/adapters/attestation.ts +3 -2
  444. package/src/core/adapters/profiles.ts +63 -1
  445. package/src/core/adapters/registry.ts +37 -0
  446. package/src/core/adapters/types.ts +10 -0
  447. package/src/core/agents/canonical-emit.ts +2 -5
  448. package/src/core/agents/cli.ts +26 -17
  449. package/src/core/agents/coord-client.ts +9 -3
  450. package/src/core/agents/render/session-context.ts +1 -0
  451. package/src/core/agents/rules/claim-conflict.ts +3 -8
  452. package/src/core/agents/rules/commit-conflict.ts +2 -7
  453. package/src/core/agents/rules/stop-hook.ts +3 -1
  454. package/src/core/agents/session-events.ts +120 -26
  455. package/src/core/agents/session-finalization-state-v3.ts +2 -1
  456. package/src/core/agents/session-finalizer-v3.ts +5 -4
  457. package/src/core/agents/session-name-display.ts +48 -0
  458. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  459. package/src/core/agents/state/heartbeat-writer.ts +24 -2
  460. package/src/core/agents/state/live-coordination-view.ts +74 -0
  461. package/src/core/agents/state/live-coordination-writer.ts +3 -0
  462. package/src/core/agents/state/stale-sweep.ts +2 -9
  463. package/src/core/agents/turn-changes.ts +97 -0
  464. package/src/core/artifacts/delivery-card.ts +12 -3
  465. package/src/core/artifacts/index.ts +701 -47
  466. package/src/core/config.ts +206 -4
  467. package/src/core/context/index.ts +2 -1
  468. package/src/core/conversations/archive.ts +4 -3
  469. package/src/core/diagnostics/bundle.ts +5 -3
  470. package/src/core/events/legacy-storage/canary.ts +5 -4
  471. package/src/core/events/legacy-storage/compression.ts +2 -1
  472. package/src/core/events/v3/adapter-id.ts +29 -1
  473. package/src/core/events/v3/authority-outbox.ts +2 -1
  474. package/src/core/events/v3/bootstrap.ts +5 -4
  475. package/src/core/events/v3/capabilities.ts +17 -0
  476. package/src/core/events/v3/contract.ts +9 -1
  477. package/src/core/events/v3/control-witness.ts +3 -2
  478. package/src/core/events/v3/coordination-transaction-recovery.ts +4 -3
  479. package/src/core/events/v3/fingerprint-keys.ts +7 -6
  480. package/src/core/events/v3/generated.ts +1 -1
  481. package/src/core/events/v3/live-feed.ts +5 -4
  482. package/src/core/events/v3/producers/command-recorder.ts +8 -6
  483. package/src/core/events/v3/producers/coordination-recorder.ts +8 -7
  484. package/src/core/events/v3/producers/diagnostic-summaries.ts +5 -4
  485. package/src/core/events/v3/producers/hook-base.ts +11 -4
  486. package/src/core/events/v3/producers/hook.ts +19 -1
  487. package/src/core/events/v3/producers/intake.ts +18 -6
  488. package/src/core/events/v3/producers/recorder.ts +139 -114
  489. package/src/core/events/v3/projection.ts +11 -1
  490. package/src/core/events/v3/recovery.ts +4 -3
  491. package/src/core/events/v3/support-storage/inventory.ts +2 -1
  492. package/src/core/events/v3/support-storage/maintenance.ts +8 -3
  493. package/src/core/events/v3/support-storage/pack-reader.ts +4 -3
  494. package/src/core/events/v3/support-storage/pack-writer.ts +6 -5
  495. package/src/core/events/v3/tool-line-changes.ts +139 -0
  496. package/src/core/events/v3/writer.ts +8 -7
  497. package/src/core/governor/planning.ts +6 -5
  498. package/src/core/governor/service.ts +12 -11
  499. package/src/core/governor/state.ts +7 -6
  500. package/src/core/guard/evaluator.ts +1 -0
  501. package/src/core/guard/storage.ts +3 -2
  502. package/src/core/hooks/adapter/behaviors/claude-code.ts +33 -0
  503. package/src/core/hooks/adapter/behaviors/codex.ts +96 -0
  504. package/src/core/hooks/adapter/behaviors/cursor.ts +37 -0
  505. package/src/core/hooks/adapter/behaviors/index.ts +31 -0
  506. package/src/core/hooks/adapter/behaviors/openclaw.ts +36 -0
  507. package/src/core/hooks/adapter/behaviors/opencode.ts +36 -0
  508. package/src/core/hooks/adapter/behaviors/types.ts +110 -0
  509. package/src/core/hooks/adapter/detect.ts +2 -3
  510. package/src/core/hooks/adapter/events.ts +72 -1
  511. package/src/core/hooks/adapter/opencode-plugin.ts +177 -0
  512. package/src/core/hooks/adapter/output.ts +8 -1
  513. package/src/core/hooks/adapter/runtime-telemetry.ts +3 -1
  514. package/src/core/hooks/adapter/wiring.ts +198 -16
  515. package/src/core/hooks/cli.ts +776 -539
  516. package/src/core/hooks/effects/image-capture.ts +2 -1
  517. package/src/core/hooks/prompt-context/contract.ts +1 -0
  518. package/src/core/hooks/resolve/transcript.ts +42 -5
  519. package/src/core/hooks/run-markers.ts +132 -0
  520. package/src/core/hooks/session-name-presence.ts +10 -3
  521. package/src/core/hooks/shell-waiter.ts +166 -0
  522. package/src/core/resources/service.ts +5 -4
  523. package/src/core/semantic/review.ts +5 -4
  524. package/src/core/semantic/service.ts +15 -14
  525. package/src/core/semantic/storage.ts +5 -4
  526. package/src/core/servers/index.ts +712 -0
  527. package/src/core/servers/net.ts +283 -0
  528. package/src/core/servers/tunnels.ts +39 -0
  529. package/src/core/storage/atomic-json.ts +7 -3
  530. package/src/core/storage/builtins.ts +1 -0
  531. package/src/core/storage/durable-history.ts +6 -5
  532. package/src/core/storage/maintenance-providers.ts +2 -1
  533. package/src/core/storage/maintenance.ts +7 -6
  534. package/src/core/storage/metrics.ts +5 -1
  535. package/src/core/storage/modes.ts +53 -0
  536. package/src/core/storage/process-log.ts +3 -2
  537. package/src/core/storage/receipts.ts +4 -3
  538. package/src/core/storage/segments.ts +7 -6
  539. package/src/core/supervisor/hook-health.ts +4 -4
  540. package/src/core/supervisor/service.ts +7 -6
  541. package/src/core/work/state.ts +8 -7
  542. package/src/core/workflow/adapters.ts +15 -0
  543. package/src/core/workflow/approvals.ts +6 -5
  544. package/src/core/workflow/billing.ts +34 -4
  545. package/src/core/workflow/durable-record.ts +4 -3
  546. package/src/core/workflow/engine.ts +38 -1
  547. package/src/core/workflow/live-session-v3.ts +2 -3
  548. package/src/core/workflow/run-state.ts +3 -2
  549. package/src/core/workflow/sandbox-projection.ts +38 -2
  550. package/src/core/workflow/spawn-claude.ts +5 -1
  551. package/src/core/workflow/spawn-codex.ts +18 -1
  552. package/src/core/workflow/spawn-cursor.ts +5 -1
  553. package/src/core/workflow/spawn-opencode.ts +153 -0
  554. package/src/core/workflow/transcript.ts +3 -2
  555. package/src/core/workflow/types.ts +62 -2
  556. package/src/core/workflow/worker-access.ts +525 -0
  557. package/src/core/workflow/workspaces/leases.ts +5 -4
  558. package/src/core/workflow/workspaces/local-git.ts +3 -2
  559. package/src/core/workflow/workspaces/paths.ts +2 -1
  560. package/src/core/workflow/workspaces/state.ts +10 -9
  561. package/src/lazy-commands.ts +11 -1
  562. package/src/lib/agent-browser/client.ts +18 -0
  563. package/src/lib/browser/batch-steps.ts +79 -0
  564. package/src/lib/browser/client.ts +147 -9
  565. package/src/lib/browser/collect/collector.ts +306 -0
  566. package/src/lib/browser/collect/fields.ts +87 -0
  567. package/src/lib/browser/collect/index.ts +47 -0
  568. package/src/lib/browser/collect/page-driver.ts +327 -0
  569. package/src/lib/browser/collect/presets.ts +237 -0
  570. package/src/lib/browser/collect/scroll-plan.ts +179 -0
  571. package/src/lib/browser/identity.ts +64 -0
  572. package/src/lib/browser/index.ts +16 -1
  573. package/src/lib/browser/launch-args.ts +53 -1
  574. package/src/lib/browser/page-review-judge.ts +16 -0
  575. package/src/lib/browser/qa-run-contracts.ts +11 -0
  576. package/src/lib/browser/qa-run.ts +1 -0
  577. package/src/lib/browser/user-agent.ts +130 -0
  578. package/src/lib/browser/windows-chrome.ts +227 -0
  579. package/src/lib/claude-desktop-share.ts +511 -0
  580. package/src/lib/disk-usage.ts +347 -0
  581. package/src/lib/docs-lint.ts +8 -3
  582. package/src/lib/docs-metadata-managed.ts +21 -0
  583. package/src/lib/guarded-remove.ts +376 -0
  584. package/src/lib/identities/assume.ts +7 -7
  585. package/src/lib/instructions/apply.ts +2 -0
  586. package/src/lib/instructions/templates.ts +8 -8
  587. package/src/lib/pace/index.ts +14 -0
  588. package/src/lib/pace/pace.ts +370 -0
  589. package/src/lib/tunnel/error-page.ts +25 -9
  590. package/src/lib/tunnel/forward-headers.ts +16 -0
  591. package/src/lib/tunnel/gate.ts +39 -10
  592. package/src/lib/tunnel/path-scope.ts +73 -0
  593. package/src/lib/tunnel/state.ts +20 -3
  594. package/src/lib/webview/index.ts +13 -0
  595. package/src/lib/webview/webview.ts +457 -0
@@ -22,7 +22,7 @@ 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 type { Adapter } from "../adapter.ts";
25
+ import { type Adapter, adapterFromPlatform as sharedAdapterFromPlatform } from "../adapter.ts";
26
26
  import { resolveCoordRoot } from "../agents/coord-client.ts";
27
27
  import {
28
28
  type ClaimFinalizationDecision,
@@ -43,12 +43,15 @@ import {
43
43
  sessionNameDisplayInstruction,
44
44
  sessionNameDisplayPending,
45
45
  sessionNameDisplayRecoveryInstruction,
46
+ sessionNameDisplayReminder,
47
+ sessionNameDisplayReminderDue,
46
48
  toolResponseMintedSessionName,
47
49
  } from "../agents/session-name-display.ts";
48
50
  import type { Heartbeat } from "../agents/state/heartbeat-reader.ts";
49
51
  import {
50
52
  readHeartbeat,
51
53
  setAssignedNameCache,
54
+ stampSessionNameReminded,
52
55
  stampSessionNameRequested,
53
56
  stampSessionNameSeen,
54
57
  } from "../agents/state/heartbeat-writer.ts";
@@ -80,11 +83,14 @@ import {
80
83
  recordLiveHookSignalV3,
81
84
  resolveLiveEventLedgerRouteV3,
82
85
  } from "../events/v3/live-routing.ts";
86
+ import type { TurnRitualEvidenceV3 } from "../events/v3/producers/hook-base.ts";
83
87
  import { captureSpanClockV3 } from "../events/v3/span-state.ts";
84
88
  import { ensureRelayDaemon, fetchPresence, publishPresence } from "../presence/index.ts";
85
89
  import { closeProcessLoggers, legacyLogFields, processLogger } from "../storage/logger.ts";
86
90
  import { stableScopeId } from "../workflow/scope-id.ts";
91
+ import { type AdapterBehavior, adapterBehavior } from "./adapter/behaviors/index.ts";
87
92
  import { detectAdapter, shouldSkipHookAdapter } from "./adapter/detect.ts";
93
+ import { ADAPTER_SPECS } from "./adapter/events.ts";
88
94
  import {
89
95
  extractBashCommand,
90
96
  extractToolDescription,
@@ -93,14 +99,8 @@ import {
93
99
  type ParsedPayload,
94
100
  parsePayload,
95
101
  } from "./adapter/parse.ts";
96
- import { discoverCodexSessionTranscript } from "./adapter/runtime-telemetry.ts";
102
+ import { hookTimeoutFor } from "./adapter/wiring.ts";
97
103
  import { scheduleBackupSnapshot } from "./backup-schedule.ts";
98
- import {
99
- codexWslFileLinkTelemetry,
100
- inspectCodexWslBridge,
101
- isWslUncPath,
102
- renderCodexWslFileLinkContext,
103
- } from "./codex-wsl-bridge.ts";
104
104
  import {
105
105
  captureImages,
106
106
  detectPresence,
@@ -130,12 +130,12 @@ import type { SessionNameDisplayInspection } from "./resolve/transcript.ts";
130
130
  import {
131
131
  inspectSessionNameDisplayImmediately,
132
132
  scanAssistantStatusBoxPresent,
133
- scanSessionNameDisplayedImmediately,
134
133
  scanStatusBoxPresent,
135
134
  scanTranscriptRuntime,
136
135
  } from "./resolve/transcript.ts";
136
+ import { clearHookRunMarker, writeHookRunMarker } from "./run-markers.ts";
137
137
  import { sessionNamePresence } from "./session-name-presence.ts";
138
- import { unsafeCrossShellReason } from "./unsafe-cross-shell.ts";
138
+ import { shellWaiterReason } from "./shell-waiter.ts";
139
139
 
140
140
  interface Argv {
141
141
  eventName: string | null;
@@ -143,6 +143,7 @@ interface Argv {
143
143
  }
144
144
 
145
145
  let hookHealthState: HookHealthState | undefined;
146
+ let hookRunMarkerPath: string | null = null;
146
147
 
147
148
  function parseArgv(argv: string[]): Argv {
148
149
  const out: Argv = { eventName: null, extra: [] };
@@ -341,12 +342,9 @@ function buildEventData(
341
342
  const p = ctx.payload;
342
343
  switch (eventType) {
343
344
  case "session.started": {
344
- const adapterPlatform =
345
- ctx.adapter === "claude-code"
346
- ? "claude-code"
347
- : ctx.adapter === "cursor"
348
- ? "cursor"
349
- : "codex";
345
+ // The platform label is the adapter id itself; enumerating and defaulting
346
+ // to codex silently mislabels any adapter not in the list.
347
+ const adapterPlatform = ctx.adapter;
350
348
  // Recorded fork lineage is NOT detected here. On claude-code a fork
351
349
  // never fires its own session.started — SessionStart fires under the
352
350
  // PARENT's session id (source=resume) before the fork id is minted
@@ -357,7 +355,12 @@ function buildEventData(
357
355
  const forkedFrom: string | undefined = undefined;
358
356
  // Assign (or recover) name + kind in-process. Idempotent: resume
359
357
  // returns the original name; new owner consumes a counter slot.
360
- const assigned = assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
358
+ // Workflow children stay unnamed, as on the tool path: a pool name
359
+ // would make them the addressee of mail held for that name.
360
+ const assigned =
361
+ coordEnv("WORKFLOW_CHILD") === "1"
362
+ ? undefined
363
+ : assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session", forkedFrom);
361
364
  // Write the adapter pid-map row so `harn agents whoami` ppid-walks find
362
365
  // this owner. Prefer the payload pid (the actual claude binary), then the
363
366
  // anchor walk (the `node` ancestor for Cursor, which has no payload pid),
@@ -422,7 +425,10 @@ function buildEventData(
422
425
  // SessionStart. Mint durable display identity here so a later
423
426
  // SessionStart remains idempotent and Codec is not stuck on the
424
427
  // 8-character native id.
425
- if (!readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name) {
428
+ if (
429
+ coordEnv("WORKFLOW_CHILD") !== "1" &&
430
+ !readLiveCoordinationRow(ctx.coordRoot, ctx.instanceId)?.name
431
+ ) {
426
432
  assignNameInProcess(ctx.coordRoot, ctx.instanceId, "session");
427
433
  }
428
434
  const prompt = p?.prompt ?? "";
@@ -433,9 +439,11 @@ function buildEventData(
433
439
  case "turn.completed": {
434
440
  const lastAssistantMessage = (p?.raw.last_assistant_message as string | undefined) ?? "";
435
441
  const fileLinkTelemetry =
436
- ctx.adapter === "codex"
437
- ? codexWslFileLinkTelemetry(ctx.coordRoot, p?.cwd, lastAssistantMessage)
438
- : null;
442
+ adapterBehavior(ctx.adapter).fileLinkTelemetry?.(
443
+ ctx.coordRoot,
444
+ p?.cwd,
445
+ lastAssistantMessage,
446
+ ) ?? null;
439
447
  return {
440
448
  // Backfill the model for adapters that omit it at session.started
441
449
  // (Claude Code). The transcript is populated with assistant turns by
@@ -471,13 +479,12 @@ function buildEventData(
471
479
  // exists (the stop-hook.session_name verdict reads it per turn); the
472
480
  // transcript scan itself stops once the name has been sighted.
473
481
  ...sessionNamePresence(ctx.coordRoot, ctx.instanceId, (name) =>
474
- scanSessionNameDisplayedImmediately(
475
- // Codex stops carry no transcript_path; discover the rollout by
476
- // session id so the naming ritual can verify and stamp there too.
482
+ inspectSessionNameDisplayImmediately(
483
+ // Some adapters omit transcript_path on stop; the behavior may
484
+ // discover the transcript by session id so the naming ritual can
485
+ // verify and stamp there too.
477
486
  p?.transcript_path ??
478
- (ctx.adapter === "codex"
479
- ? discoverCodexSessionTranscript(p?.session_id ?? ctx.instanceId)
480
- : undefined),
487
+ adapterBehavior(ctx.adapter).discoverTranscript?.(p?.session_id ?? ctx.instanceId),
481
488
  name,
482
489
  assistantTextStartsWithSessionNameBlock,
483
490
  ),
@@ -622,12 +629,70 @@ function numberField(value: unknown): number | undefined {
622
629
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : undefined;
623
630
  }
624
631
 
632
+ /** Everything one hook invocation resolved before its event handlers run. */
633
+ interface HookRun {
634
+ coordRoot: string;
635
+ adapter: Adapter;
636
+ behavior: AdapterBehavior;
637
+ eventName: string;
638
+ norm: NonNullable<ReturnType<typeof normalizeEventName>>;
639
+ payload: ParsedPayload | null;
640
+ raw: string;
641
+ owner: NonNullable<ReturnType<typeof resolveOwner>>;
642
+ sessionId: string;
643
+ data: Record<string, unknown>;
644
+ debugBase: Record<string, unknown>;
645
+ ledgerRoute: Exclude<ReturnType<typeof resolveLiveEventLedgerRouteV3>, { state: "blocked" }>;
646
+ hookStartedAt: number;
647
+ hookClock: ReturnType<typeof captureSpanClockV3>;
648
+ priorCoordination: Heartbeat | null;
649
+ topLevelSession: boolean;
650
+ stopRemediation: boolean;
651
+ bridge: ReturnType<AdapterBehavior["bridgeFor"]>;
652
+ turnRitual: TurnRitualEvidenceV3 | undefined;
653
+ /** Set by recordSignal once the canonical V3 event has landed. */
654
+ v3Result?: ReturnType<typeof recordLiveHookSignalV3>;
655
+ v3EventId?: string;
656
+ recordedGenerationId?: `gen_${string}`;
657
+ }
658
+
659
+ /** The completed-reply ritual path runs before event normalization and owner resolution. */
660
+ interface CompletedResponseRun {
661
+ coordRoot: string;
662
+ adapter: Adapter;
663
+ eventName: string;
664
+ raw: string;
665
+ debugBase: Record<string, unknown>;
666
+ ledgerRoute: ReturnType<typeof resolveLiveEventLedgerRouteV3>;
667
+ hookStartedAt: number;
668
+ hookClock: ReturnType<typeof captureSpanClockV3>;
669
+ }
670
+
625
671
  async function main(): Promise<number> {
626
672
  const hookStartedAt = performance.now();
627
673
  const hookStartedRss = process.memoryUsage().rss;
628
674
  const hookClock = captureSpanClockV3();
629
675
  const { eventName, extra } = parseArgv(process.argv.slice(2));
630
676
  const adapter = detectAdapter(process.argv.slice(2));
677
+ const behavior = adapter ? adapterBehavior(adapter) : null;
678
+ // Adapters that kill a hook at its timeout get a start marker, so a killed
679
+ // run leaves evidence for doctor (ADR 0197). Written before stdin is read.
680
+ const markerSpec = adapter ? ADAPTER_SPECS[adapter] : undefined;
681
+ const markerTimeout =
682
+ markerSpec && eventName && markerSpec.events.some((e) => e.subcommand === eventName)
683
+ ? hookTimeoutFor(markerSpec, eventName)
684
+ : undefined;
685
+ if (markerTimeout !== undefined && adapter && eventName && coordEnv("AGENT_COORD_OFF") !== "1") {
686
+ const markerRoot = resolveCoordRoot(process.cwd());
687
+ if (markerRoot) {
688
+ hookRunMarkerPath = writeHookRunMarker(markerRoot, {
689
+ event: eventName,
690
+ adapter,
691
+ pid: process.pid,
692
+ timeout_sec: markerTimeout,
693
+ });
694
+ }
695
+ }
631
696
  const raw = await readStdin();
632
697
  hookHealthState = beginHookHealth({
633
698
  started_at_ms: hookStartedAt,
@@ -667,8 +732,8 @@ async function main(): Promise<number> {
667
732
  // Kill-switch-INDEPENDENT effects: notification sounds fire BEFORE the
668
733
  // HARNERY_AGENT_COORD_OFF gate so audible feedback survives incident-triage
669
734
  // bypass: sound playback happens before the kill-switch bailout.
670
- // Claude-Code-only; stop-failure → error, sub-agent-start → subagent-start.
671
- if (adapter === "claude-code" && eventName) {
735
+ // Behavior-gated; stop-failure → error, sub-agent-start → subagent-start.
736
+ if (behavior?.soundEffects && eventName) {
672
737
  const s = soundForEvent(eventName);
673
738
  if (s) {
674
739
  const repoRoot = resolveCoordRoot(process.cwd());
@@ -724,7 +789,7 @@ async function main(): Promise<number> {
724
789
  ppid: process.ppid,
725
790
  };
726
791
 
727
- if (!eventName || !adapter) {
792
+ if (!eventName || !adapter || !behavior) {
728
793
  appendDebug(coordRoot, { ...debugBase, skipped: "missing-event-or-adapter" });
729
794
  return 0;
730
795
  }
@@ -736,88 +801,17 @@ async function main(): Promise<number> {
736
801
  // assistant text. Preserve only privacy-safe ritual booleans in the V3
737
802
  // producer's open-turn state; Stop consumes them into the authoritative
738
803
  // turn.completed event. The response body never enters V3 or heartbeat.
739
- if (adapter === "cursor" && eventName === "after-agent-response") {
740
- const payload = parsePayload(raw, adapter);
741
- const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
742
- if (!owner) {
743
- appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
744
- return 0;
745
- }
746
- const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
747
- const name = sessionNameDisplayPending(row);
748
- const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
749
- const sighting = matchSessionNameDisplay(row, text);
750
- if (sighting) {
751
- stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
752
- appendDebug(coordRoot, {
753
- ...debugBase,
754
- effect: "session-name-display-stamped",
755
- session_name_pending: sighting.pending,
756
- ...(sighting.displayed === sighting.pending
757
- ? {}
758
- : { session_name_displayed: sighting.displayed, session_name_drift: true }),
759
- });
760
- } else if (!name) {
761
- // Nothing was owed. Distinct from a miss: conflating the two is what made
762
- // a latched Cursor session undiagnosable from this log.
763
- appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
764
- } else {
765
- appendDebug(coordRoot, {
766
- ...debugBase,
767
- skipped: "session-name-block-absent",
768
- session_name_pending: name,
769
- reply_bytes: text.length,
770
- reply_leads_with_fence: /^\s*`{3,}/.test(text),
771
- });
772
- }
773
- if (ledgerRoute.state === "blocked") {
774
- appendDebug(coordRoot, {
775
- ...debugBase,
776
- skipped: "v3-control-blocked",
777
- reason: ledgerRoute.reason,
778
- owner_source: owner.source,
779
- });
780
- return 0;
781
- }
782
- const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
783
- // The hook parser needs the completed reply long enough to scan it, but
784
- // durable hook intake must never retain that body. Keep every other native
785
- // field so turn correlation remains available to the producer.
786
- const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
787
- const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
788
- const v3Result = recordLiveHookSignalV3({
804
+ if (behavior && eventName && eventName === behavior.completedResponseEvent) {
805
+ return handleCompletedResponse({
789
806
  coordRoot,
790
- route: ledgerRoute,
791
- eventName,
792
- payload: privacySafePayload,
793
807
  adapter,
794
- instanceId: owner.instance_id,
795
- hook_name: eventName,
796
- hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
797
- monotonic_ns: hookClock.monotonic_ns,
798
- turn_ritual: {
799
- status_box_present: statusBoxPresentStrict,
800
- status_box_present_strict: statusBoxPresentStrict,
801
- session_name_required: name !== undefined,
802
- session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
803
- },
804
- });
805
- observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
806
- appendDebug(coordRoot, {
807
- ...debugBase,
808
- effect: "cursor-response-ritual-observed",
809
- owner_source: owner.source,
810
- event_v3_state: v3Result.state,
811
- ...(v3Result.state === "observed"
812
- ? {
813
- generation_id: v3Result.generation_id,
814
- turn_id: v3Result.turn_id,
815
- response_observed_at: v3Result.observed_at,
816
- status_box_present_strict: statusBoxPresentStrict,
817
- }
818
- : {}),
808
+ eventName,
809
+ raw,
810
+ debugBase,
811
+ ledgerRoute,
812
+ hookStartedAt,
813
+ hookClock,
819
814
  });
820
- return 0;
821
815
  }
822
816
 
823
817
  const norm = normalizeEventName(eventName);
@@ -827,6 +821,7 @@ async function main(): Promise<number> {
827
821
  }
828
822
 
829
823
  const payload = parsePayload(raw, adapter);
824
+ const bridge = behavior.bridgeFor(payload?.cwd);
830
825
  const owner = resolveOwner({
831
826
  payload: payload?.raw ?? null,
832
827
  coordRoot,
@@ -874,8 +869,20 @@ async function main(): Promise<number> {
874
869
  status_box_present_strict: data.status_box_present_strict === true,
875
870
  session_name_required: typeof data.session_name_present_for === "string",
876
871
  session_name_present: data.session_name_present === true,
872
+ ...(typeof data.session_name_unavailable_reason === "string"
873
+ ? { session_name_unavailable_reason: data.session_name_unavailable_reason }
874
+ : {}),
877
875
  }
878
876
  : undefined;
877
+ if (typeof data.session_name_unavailable_reason === "string") {
878
+ reportSessionNameObservationUnavailable(
879
+ coordRoot,
880
+ owner.instance_id,
881
+ adapter,
882
+ eventName,
883
+ data.session_name_unavailable_reason,
884
+ );
885
+ }
879
886
  const stopRemediation =
880
887
  norm.event_type === "turn.started" &&
881
888
  typeof data.prompt_text === "string" &&
@@ -892,6 +899,107 @@ async function main(): Promise<number> {
892
899
  return 0;
893
900
  }
894
901
 
902
+ const run: HookRun = {
903
+ coordRoot,
904
+ adapter,
905
+ behavior,
906
+ eventName,
907
+ norm,
908
+ payload,
909
+ raw,
910
+ owner,
911
+ sessionId,
912
+ data,
913
+ debugBase,
914
+ ledgerRoute,
915
+ hookStartedAt,
916
+ hookClock,
917
+ priorCoordination,
918
+ topLevelSession,
919
+ stopRemediation,
920
+ bridge,
921
+ turnRitual,
922
+ };
923
+ recordSignal(run);
924
+ recordContextTelemetry(run);
925
+
926
+ // Phase 8: SessionStart post-emit: project the event so the heartbeat
927
+ // lands synchronously, run stale-sweep, and emit the adapter-shaped
928
+ // systemMessage JSON (peer table + wiring check + council invites).
929
+ // Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
930
+ // and the equivalent per-adapter bash session_start handlers.
931
+ if (norm.event_type === "session.started") await handleSessionStarted(run);
932
+
933
+ // Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
934
+ // agnostic since v0.5.0.
935
+ if (norm.event_type === "session.ended") handleSessionEnded(run);
936
+
937
+ // Phase 8: SubagentStart: the child generation was opened above from the
938
+ // native delegation identity. Refresh its disposable projection, log the
939
+ // lifecycle event, and emit a context message announcing the subagent.
940
+ if (norm.event_type === "agent.started") await handleAgentStarted(run);
941
+
942
+ // Phase 8: SubagentStop: delete subagent heartbeat + log.
943
+ if (norm.event_type === "agent.completed") await handleAgentCompleted(run);
944
+
945
+ // Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
946
+ // and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
947
+ if (norm.event_type === "turn.started") await handleTurnStarted(run);
948
+
949
+ // turn.completed: telemetry, then the stop verdict. The
950
+ // verdict + codex-replay previously lived in the per-adapter shell adapters;
951
+ // agent-hook owns them now. Runs on the normal "stop" event only;
952
+ // "stop-failure" (API error) gets no gate, matching the previous
953
+ // stop vs stop-failure split.
954
+ if (norm.event_type === "turn.completed" && eventName === "stop") {
955
+ const exit = await handleStop(run);
956
+ if (exit !== undefined) return exit;
957
+ }
958
+
959
+ // Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
960
+ // Adapter-agnostic: both writes have the same shape regardless of who fired.
961
+ // Cursor/Codex bash dispatchers still fire their own G-guard logic, but the
962
+ // heals here keep the agent-coord layer's view of liveness fresh.
963
+ //
964
+ // The heartbeat + pid-map heals are paired by design; they were wired
965
+ // side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
966
+ // preserved the heartbeat half but dropped the pid-map half; the pid-map
967
+ // call was restored here afterward.
968
+ if (norm.event_type === "tool.requested") {
969
+ const exit = await handleToolRequested(run);
970
+ if (exit !== undefined) return exit;
971
+ }
972
+
973
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use")
974
+ await handlePostToolUse(run);
975
+
976
+ // Phase 7: PostToolUseFailure: release claim on failed Edit (the file
977
+ // never landed; the claim is stale). Adapter-agnostic.
978
+ if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure")
979
+ handlePostToolUseFailure(run);
980
+
981
+ return 0;
982
+ }
983
+
984
+ function recordSignal(run: HookRun): void {
985
+ const {
986
+ coordRoot,
987
+ adapter,
988
+ eventName,
989
+ norm,
990
+ payload,
991
+ owner,
992
+ sessionId,
993
+ data,
994
+ debugBase,
995
+ ledgerRoute,
996
+ hookStartedAt,
997
+ hookClock,
998
+ priorCoordination,
999
+ stopRemediation,
1000
+ bridge,
1001
+ turnRitual,
1002
+ } = run;
895
1003
  let capturedImages: ReturnType<typeof captureImages> = [];
896
1004
  if (
897
1005
  norm.event_type === "tool.requested" ||
@@ -924,7 +1032,7 @@ async function main(): Promise<number> {
924
1032
  : {}),
925
1033
  }
926
1034
  : {}),
927
- ...(adapter === "codex" && isWslUncPath(payload?.cwd) ? { bridge: "codex-wsl" as const } : {}),
1035
+ ...(bridge ? { bridge } : {}),
928
1036
  hook_name: eventName,
929
1037
  hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
930
1038
  monotonic_ns: hookClock.monotonic_ns,
@@ -980,9 +1088,7 @@ async function main(): Promise<number> {
980
1088
  parentEvent: v3Result.event,
981
1089
  payload,
982
1090
  adapter,
983
- ...(adapter === "codex" && isWslUncPath(payload.cwd)
984
- ? { bridge: "codex-wsl" as const }
985
- : {}),
1091
+ ...(bridge ? { bridge } : {}),
986
1092
  monotonic_ns: hookClock.monotonic_ns,
987
1093
  });
988
1094
  const nativeChild = payload.subagent_id ?? payload.agent_id;
@@ -1030,7 +1136,13 @@ async function main(): Promise<number> {
1030
1136
  ...(v3Result ? { event_v3_state: v3Result.state } : {}),
1031
1137
  ...(v3Result && "reason" in v3Result ? { event_v3_reason: v3Result.reason } : {}),
1032
1138
  });
1139
+ run.v3Result = v3Result;
1140
+ run.v3EventId = v3EventId;
1141
+ run.recordedGenerationId = recordedGenerationId;
1142
+ }
1033
1143
 
1144
+ function recordContextTelemetry(run: HookRun): void {
1145
+ const { coordRoot, adapter, norm, payload, owner, sessionId } = run;
1034
1146
  // Context telemetry is opportunistic and truthful: only persist a sample
1035
1147
  // when the adapter payload actually exposes usage/window data. Identical
1036
1148
  // measurements are de-duplicated before they reach the canonical stream.
@@ -1081,437 +1193,549 @@ async function main(): Promise<number> {
1081
1193
  logError(coordRoot, err, { phase: "context-compaction-completed" });
1082
1194
  }
1083
1195
  }
1196
+ }
1084
1197
 
1085
- // Phase 8: SessionStart post-emit: project the event so the heartbeat
1086
- // lands synchronously, run stale-sweep, and emit the adapter-shaped
1087
- // systemMessage JSON (peer table + wiring check + council invites).
1088
- // Adapter-agnostic since v0.5.0; replaces the previous bash UX layer
1089
- // and the equivalent per-adapter bash session_start handlers.
1090
- if (norm.event_type === "session.started") {
1091
- // A configured backup runs out of process and is freshness-gated per host.
1092
- // Startup never waits for restic or a remote provider; the child owns no
1093
- // hook stdio, so the harness is not held open until it finishes. A failed
1094
- // previous run surfaces as one line in this session's context.
1095
- let backupCue = "";
1096
- try {
1097
- backupCue = scheduleBackupSnapshot(coordRoot).cue ?? "";
1098
- } catch (err) {
1099
- logError(coordRoot, err, { phase: "backup-schedule" });
1100
- }
1101
- // Effect (claude-code): prune stale journal archives + sweep orphans.
1102
- // The recovery-cue is merged into the
1103
- // session-start additionalContext inside emitSessionStartSystemMessage.
1104
- if (adapter === "claude-code") journalJanitor(coordRoot);
1105
- imageJanitor(coordRoot);
1106
- // Session start is the only caller of the artifact janitor and the storage
1107
- // maintenance pass, so both load here instead of on every tool call.
1108
- const { autoCleanArtifacts } = await import("../artifacts/index.ts");
1109
- const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.ts");
1110
- const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import(
1111
- "../storage/maintenance-providers.ts"
1198
+ async function handleSessionStarted(run: HookRun): Promise<void> {
1199
+ const { coordRoot, adapter, behavior, payload, owner, sessionId, data } = run;
1200
+ // A configured backup runs out of process and is freshness-gated per host.
1201
+ // Startup never waits for restic or a remote provider; the child owns no
1202
+ // hook stdio, so the harness is not held open until it finishes. A failed
1203
+ // previous run surfaces as one line in this session's context.
1204
+ let backupCue = "";
1205
+ try {
1206
+ backupCue = scheduleBackupSnapshot(coordRoot).cue ?? "";
1207
+ } catch (err) {
1208
+ logError(coordRoot, err, { phase: "backup-schedule" });
1209
+ }
1210
+ // Effect (journal-maintaining adapters): prune stale journal archives +
1211
+ // sweep orphans. The recovery-cue is merged into the session-start
1212
+ // additionalContext inside emitSessionStartSystemMessage.
1213
+ if (behavior.journalMaintenance) journalJanitor(coordRoot);
1214
+ imageJanitor(coordRoot);
1215
+ // Session start is the only caller of the artifact janitor and the storage
1216
+ // maintenance pass, so both load here instead of on every tool call.
1217
+ const { autoCleanArtifacts } = await import("../artifacts/index.ts");
1218
+ const { autoCleanEventV3Archives } = await import("../events/v3/archive-retention.ts");
1219
+ const { createAutomaticMaintenanceComposition, runAutomaticMaintenancePass } = await import(
1220
+ "../storage/maintenance-providers.ts"
1221
+ );
1222
+ // Effect: throttled hourly artifact sweep (see autoCleanArtifacts). It
1223
+ // deletes expired workspaces, plus unheld workspaces over the per-bundle
1224
+ // ceiling or repository budget once they have been idle past
1225
+ // artifacts.idle_grace_hours. Each deletion is rechecked first and recorded
1226
+ // in .harnery/artifact-deletions.jsonl. Best-effort like every janitor
1227
+ // here: a failure logs and never blocks session start.
1228
+ try {
1229
+ autoCleanArtifacts(coordRoot);
1230
+ } catch (err) {
1231
+ logError(coordRoot, err, { phase: "artifact-auto-clean" });
1232
+ }
1233
+ // Effect: apply the same daily throttle to closed V3 epochs. The newest
1234
+ // recovery epochs remain protected by the archive retention policy.
1235
+ try {
1236
+ autoCleanEventV3Archives(coordRoot);
1237
+ } catch (err) {
1238
+ logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
1239
+ }
1240
+ // Plan one bounded, claim-first global slice from cached pressure only.
1241
+ // Existing owner janitors above remain the only active executors until
1242
+ // each provider receives a separate production activation.
1243
+ try {
1244
+ await runAutomaticMaintenancePass(
1245
+ createAutomaticMaintenanceComposition(coordRoot, {
1246
+ journal: () => {
1247
+ if (behavior.journalMaintenance) journalJanitor(coordRoot);
1248
+ },
1249
+ images: () => imageJanitor(coordRoot),
1250
+ artifacts: () => autoCleanArtifacts(coordRoot),
1251
+ }),
1112
1252
  );
1113
- // Effect: throttled daily sweep of expired artifact workspaces (guarded,
1114
- // managed-expired only; see autoCleanArtifacts). Best-effort like every
1115
- // janitor here: a failure logs and never blocks session start.
1116
- try {
1117
- autoCleanArtifacts(coordRoot);
1118
- } catch (err) {
1119
- logError(coordRoot, err, { phase: "artifact-auto-clean" });
1120
- }
1121
- // Effect: apply the same daily throttle to closed V3 epochs. The newest
1122
- // recovery epochs remain protected by the archive retention policy.
1123
- try {
1124
- autoCleanEventV3Archives(coordRoot);
1125
- } catch (err) {
1126
- logError(coordRoot, err, { phase: "event-v3-archive-auto-clean" });
1127
- }
1128
- // Plan one bounded, claim-first global slice from cached pressure only.
1129
- // Existing owner janitors above remain the only active executors until
1130
- // each provider receives a separate production activation.
1131
- try {
1132
- await runAutomaticMaintenancePass(
1133
- createAutomaticMaintenanceComposition(coordRoot, {
1134
- journal: () => {
1135
- if (adapter === "claude-code") journalJanitor(coordRoot);
1136
- },
1137
- images: () => imageJanitor(coordRoot),
1138
- artifacts: () => autoCleanArtifacts(coordRoot),
1139
- }),
1140
- );
1141
- } catch (err) {
1142
- logError(coordRoot, err, { phase: "storage-maintenance-auto" });
1143
- }
1144
- let recovery: PreparedContextRecovery | null = null;
1145
- if (payload?.source === "compact") {
1146
- try {
1147
- markContextCompactionCompleted(coordRoot, {
1148
- sessionId,
1149
- instanceId: owner.instance_id,
1150
- });
1151
- recovery = prepareContextRecovery(coordRoot, {
1152
- instanceId: owner.instance_id,
1153
- sessionId,
1154
- cwd: payload?.cwd ?? process.cwd(),
1155
- });
1156
- } catch (err) {
1157
- logError(coordRoot, err, { phase: "context-recovery-session-start" });
1158
- }
1159
- }
1253
+ } catch (err) {
1254
+ logError(coordRoot, err, { phase: "storage-maintenance-auto" });
1255
+ }
1256
+ let recovery: PreparedContextRecovery | null = null;
1257
+ if (payload?.source === "compact") {
1160
1258
  try {
1161
- const injected = await emitSessionStartSystemMessage(
1162
- coordRoot,
1163
- owner.instance_id,
1259
+ markContextCompactionCompleted(coordRoot, {
1164
1260
  sessionId,
1165
- data,
1166
- adapter,
1167
- recovery?.briefing ?? "",
1168
- backupCue,
1169
- );
1170
- if (injected && recovery) {
1171
- completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
1172
- }
1261
+ instanceId: owner.instance_id,
1262
+ });
1263
+ recovery = prepareContextRecovery(coordRoot, {
1264
+ instanceId: owner.instance_id,
1265
+ sessionId,
1266
+ cwd: payload?.cwd ?? process.cwd(),
1267
+ });
1173
1268
  } catch (err) {
1174
- logError(coordRoot, err, { phase: "session-start-systemMessage" });
1269
+ logError(coordRoot, err, { phase: "context-recovery-session-start" });
1175
1270
  }
1176
- // Cross-machine presence is derived from the authority-safe V3 projection,
1177
- // not the disposable heartbeat cache. Announce this session, pull peers,
1178
- // and keep the optional live relay connected.
1179
- try {
1180
- publishPresence(coordRoot);
1181
- fetchPresence(coordRoot);
1182
- ensureRelayDaemon(coordRoot);
1183
- } catch (err) {
1184
- logError(coordRoot, err, { phase: "session-start-presence" });
1271
+ }
1272
+ try {
1273
+ const injected = await emitSessionStartSystemMessage(
1274
+ coordRoot,
1275
+ owner.instance_id,
1276
+ sessionId,
1277
+ data,
1278
+ adapter,
1279
+ recovery?.briefing ?? "",
1280
+ backupCue,
1281
+ );
1282
+ if (injected && recovery) {
1283
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
1185
1284
  }
1285
+ } catch (err) {
1286
+ logError(coordRoot, err, { phase: "session-start-systemMessage" });
1186
1287
  }
1288
+ // Cross-machine presence is derived from the authority-safe V3 projection,
1289
+ // not the disposable heartbeat cache. Announce this session, pull peers,
1290
+ // and keep the optional live relay connected.
1291
+ try {
1292
+ publishPresence(coordRoot);
1293
+ fetchPresence(coordRoot);
1294
+ ensureRelayDaemon(coordRoot);
1295
+ } catch (err) {
1296
+ logError(coordRoot, err, { phase: "session-start-presence" });
1297
+ }
1298
+ }
1187
1299
 
1188
- // Phase 8: SessionEnd cleanup: delete heartbeat + pid-map rows. Adapter-
1189
- // agnostic since v0.5.0.
1190
- if (norm.event_type === "session.ended") {
1191
- try {
1192
- cleanupSessionEnd(coordRoot, owner.instance_id);
1193
- } catch (err) {
1194
- logError(coordRoot, err, { phase: "session-end-cleanup" });
1195
- }
1196
- // Effects (claude-code): archive the ending agent's journal + force a
1197
- // session-telemetry sync (via HARNERY_CLAUDE_SESSIONS_FORCE=1).
1198
- if (adapter === "claude-code") {
1199
- journalArchive(coordRoot, owner.instance_id);
1200
- runSessionSyncExtension(coordRoot, true);
1201
- }
1202
- // Publish the post-cleanup V3 projection so remote machines see this
1203
- // session disappear without waiting for the stale window.
1204
- try {
1205
- publishPresence(coordRoot);
1206
- } catch (err) {
1207
- logError(coordRoot, err, { phase: "session-end-presence" });
1208
- }
1300
+ function handleSessionEnded(run: HookRun): void {
1301
+ const { coordRoot, behavior, owner } = run;
1302
+ try {
1303
+ cleanupSessionEnd(coordRoot, owner.instance_id);
1304
+ } catch (err) {
1305
+ logError(coordRoot, err, { phase: "session-end-cleanup" });
1306
+ }
1307
+ // Effects: archive the ending agent's journal + force a session-telemetry
1308
+ // sync (via HARNERY_CLAUDE_SESSIONS_FORCE=1), each gated by the behavior.
1309
+ if (behavior.journalMaintenance) journalArchive(coordRoot, owner.instance_id);
1310
+ if (behavior.sessionTelemetrySync) runSessionSyncExtension(coordRoot, true);
1311
+ // Publish the post-cleanup V3 projection so remote machines see this
1312
+ // session disappear without waiting for the stale window.
1313
+ try {
1314
+ publishPresence(coordRoot);
1315
+ } catch (err) {
1316
+ logError(coordRoot, err, { phase: "session-end-presence" });
1209
1317
  }
1318
+ }
1210
1319
 
1211
- // Phase 8: SubagentStart: the child generation was opened above from the
1212
- // native delegation identity. Refresh its disposable projection, log the
1213
- // lifecycle event, and emit a context message announcing the subagent.
1214
- if (norm.event_type === "agent.started") {
1215
- try {
1216
- const childInstanceId = (data.agent_id as string | undefined) ?? owner.instance_id;
1217
- await emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
1218
- } catch (err) {
1219
- logError(coordRoot, err, { phase: "subagent-start-context" });
1220
- }
1320
+ async function handleAgentStarted(run: HookRun): Promise<void> {
1321
+ const { coordRoot, adapter, owner, sessionId, data } = run;
1322
+ try {
1323
+ const childInstanceId = (data.agent_id as string | undefined) ?? owner.instance_id;
1324
+ await emitSubagentStartContext(coordRoot, childInstanceId, sessionId, data, adapter);
1325
+ } catch (err) {
1326
+ logError(coordRoot, err, { phase: "subagent-start-context" });
1221
1327
  }
1328
+ }
1222
1329
 
1223
- // Phase 8: SubagentStop: delete subagent heartbeat + log.
1224
- if (norm.event_type === "agent.completed") {
1225
- try {
1226
- const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
1227
- cleanupSessionEnd(coordRoot, childInstanceId);
1228
- const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.ts");
1229
- reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
1230
- } catch (err) {
1231
- logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
1232
- }
1330
+ async function handleAgentCompleted(run: HookRun): Promise<void> {
1331
+ const { coordRoot, payload, owner } = run;
1332
+ try {
1333
+ const childInstanceId = payload?.subagent_id ?? payload?.agent_id ?? owner.instance_id;
1334
+ cleanupSessionEnd(coordRoot, childInstanceId);
1335
+ const { reconcileSessionFinalizationV3 } = await import("../agents/session-finalizer-v3.ts");
1336
+ reconcileSessionFinalizationV3(coordRoot, { archive_observations: [] });
1337
+ } catch (err) {
1338
+ logError(coordRoot, err, { phase: "subagent-stop-cleanup" });
1233
1339
  }
1340
+ }
1234
1341
 
1235
- // Phase 8: UserPromptSubmit: render dedup'd peer table + council pending
1236
- // and emit the adapter-shaped systemMessage JSON. Adapter-agnostic since v0.5.0.
1237
- if (norm.event_type === "turn.started") {
1238
- // Effects (claude-code): reset per-turn sound rate-limit counters + run
1239
- // presence detection on the prompt.
1240
- if (adapter === "claude-code") {
1241
- resetSoundCounters(sessionId);
1242
- const prompt = (payload?.raw?.prompt as string | undefined) ?? "";
1243
- if (prompt) detectPresence(prompt);
1244
- }
1245
- // Presence: throttled pull of peer machines' presence refs (default 60s
1246
- // interval; the systemMessage below reads whatever is locally known).
1342
+ async function handleTurnStarted(run: HookRun): Promise<void> {
1343
+ const {
1344
+ coordRoot,
1345
+ adapter,
1346
+ behavior,
1347
+ payload,
1348
+ owner,
1349
+ sessionId,
1350
+ debugBase,
1351
+ topLevelSession,
1352
+ stopRemediation,
1353
+ v3EventId,
1354
+ } = run;
1355
+ // Effects: reset per-turn sound rate-limit counters + run presence
1356
+ // detection on the prompt, each gated by the behavior.
1357
+ if (behavior.soundEffects) resetSoundCounters(sessionId);
1358
+ if (behavior.promptPresenceDetection) {
1359
+ const prompt = (payload?.raw?.prompt as string | undefined) ?? "";
1360
+ if (prompt) detectPresence(prompt);
1361
+ }
1362
+ // Presence: throttled pull of peer machines' presence refs (default 60s
1363
+ // interval; the systemMessage below reads whatever is locally known).
1364
+ try {
1365
+ fetchPresence(coordRoot);
1366
+ } catch (err) {
1367
+ logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
1368
+ }
1369
+ let recovery: PreparedContextRecovery | null = null;
1370
+ const continuityState = readContextState(coordRoot, sessionId);
1371
+ if (
1372
+ continuityState?.phase === "checkpointed" &&
1373
+ continuityState.compaction_completed_at !== undefined
1374
+ ) {
1247
1375
  try {
1248
- fetchPresence(coordRoot);
1376
+ recovery = prepareContextRecovery(coordRoot, {
1377
+ instanceId: owner.instance_id,
1378
+ sessionId,
1379
+ cwd: payload?.cwd ?? process.cwd(),
1380
+ });
1249
1381
  } catch (err) {
1250
- logError(coordRoot, err, { phase: "user-prompt-submit-presence" });
1251
- }
1252
- let recovery: PreparedContextRecovery | null = null;
1253
- const continuityState = readContextState(coordRoot, sessionId);
1254
- if (
1255
- continuityState?.phase === "checkpointed" &&
1256
- continuityState.compaction_completed_at !== undefined
1257
- ) {
1258
- try {
1259
- recovery = prepareContextRecovery(coordRoot, {
1260
- instanceId: owner.instance_id,
1261
- sessionId,
1262
- cwd: payload?.cwd ?? process.cwd(),
1263
- });
1264
- } catch (err) {
1265
- logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
1266
- }
1267
- }
1268
- let hostPromptContext = "";
1269
- const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
1270
- if (hostPromptEnabled && topLevelSession && !stopRemediation) {
1271
- try {
1272
- const { runPromptContext } = await import("./prompt-context/runner.ts");
1273
- const result = await runPromptContext({
1274
- coordRoot,
1275
- adapter,
1276
- sessionId,
1277
- turnId: payload?.turn_id ?? v3EventId ?? sessionId,
1278
- cwd: payload?.cwd ?? process.cwd(),
1279
- prompt: payload?.prompt ?? "",
1280
- });
1281
- appendDebug(coordRoot, {
1282
- ...debugBase,
1283
- effect: "prompt-context",
1284
- ...result.audit,
1285
- });
1286
- hostPromptContext = result.context ?? "";
1287
- } catch (err) {
1288
- logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
1289
- }
1382
+ logError(coordRoot, err, { phase: "context-recovery-user-prompt" });
1290
1383
  }
1384
+ }
1385
+ let hostPromptContext = "";
1386
+ const hostPromptEnabled = hostPromptContextConfig(coordRoot)?.enabled === true;
1387
+ if (hostPromptEnabled && topLevelSession && !stopRemediation) {
1291
1388
  try {
1292
- const injected = await emitUserPromptSubmitSystemMessage(
1389
+ const { runPromptContext } = await import("./prompt-context/runner.ts");
1390
+ const result = await runPromptContext({
1293
1391
  coordRoot,
1294
- owner.instance_id,
1295
- sessionId,
1296
1392
  adapter,
1297
- payload?.cwd,
1298
- recovery?.briefing ?? "",
1299
- hostPromptContext,
1300
- );
1301
- if (injected && recovery) {
1302
- completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
1303
- }
1393
+ sessionId,
1394
+ turnId: payload?.turn_id ?? v3EventId ?? sessionId,
1395
+ cwd: payload?.cwd ?? process.cwd(),
1396
+ prompt: payload?.prompt ?? "",
1397
+ });
1398
+ appendDebug(coordRoot, {
1399
+ ...debugBase,
1400
+ effect: "prompt-context",
1401
+ ...result.audit,
1402
+ });
1403
+ hostPromptContext = result.context ?? "";
1304
1404
  } catch (err) {
1305
- logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
1405
+ logError(coordRoot, err, { phase: "prompt-context-user-prompt" });
1306
1406
  }
1307
1407
  }
1308
-
1309
- // turn.completed: telemetry, then the stop verdict. The
1310
- // verdict + codex-replay previously lived in the per-adapter shell adapters;
1311
- // agent-hook owns them now. Runs on the normal "stop" event only;
1312
- // "stop-failure" (API error) gets no gate, matching the previous
1313
- // stop vs stop-failure split.
1314
- if (norm.event_type === "turn.completed" && eventName === "stop") {
1315
- // Claude Code session telemetry sync remains an independent side effect.
1316
- if (adapter === "claude-code") {
1317
- runSessionSyncExtension(coordRoot, false);
1318
- }
1319
-
1320
- // Publish after the canonical turn event has landed so the blob carries
1321
- // current task, activity, and claim state. Re-ensure the relay here because
1322
- // its daemon intentionally exits when a machine has no live sessions.
1323
- try {
1324
- publishPresence(coordRoot);
1325
- ensureRelayDaemon(coordRoot);
1326
- } catch (err) {
1327
- logError(coordRoot, err, { phase: "stop-presence" });
1328
- }
1329
-
1330
- // Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
1331
- // rule lives in Harnery. agent-hook already emitted this turn.completed
1332
- // with privacy-safe ritual observations, so the evidence is in the stream.
1333
- const verdict = evaluateStopHook(coordRoot, {
1334
- rule: "stop-hook",
1335
- instance_id: owner.instance_id,
1336
- session_id: sessionId,
1408
+ try {
1409
+ const injected = await emitUserPromptSubmitSystemMessage(
1410
+ coordRoot,
1411
+ owner.instance_id,
1412
+ sessionId,
1337
1413
  adapter,
1338
- stop_hook_active: payload?.stop_hook_active === true,
1339
- status_box_present_strict: data.status_box_present_strict === true,
1340
- bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
1341
- workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
1342
- });
1343
- if (!verdict.allow) {
1344
- // Backstop for sessions whose evidence can never land (e.g. a headless
1345
- // child whose status command resolves to a different owner): after
1346
- // `cap` consecutive blocked stops in one cycle, allow the stop instead
1347
- // of bouncing the model forever. Cycle boundaries come from the
1348
- // adapter's continuation flag, so adapters without one are unaffected.
1349
- const { recordRemediationBlock } = await import("./remediation-cap.ts");
1350
- const remediation = recordRemediationBlock(sessionId, payload?.stop_hook_active === true);
1351
- if (remediation.exceeded) {
1352
- appendDebug(coordRoot, {
1353
- ...debugBase,
1354
- skipped: "stop-remediation-cap-exhausted",
1355
- blocked_rule: verdict.rule,
1356
- blocked_count: remediation.count,
1357
- session_id: sessionId,
1358
- remediation_cycle_anchor: verdict.remediation_cycle_anchor,
1359
- });
1360
- } else {
1361
- // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
1362
- // as a turn block; Cursor ignores exit codes and re-prompts only via a
1363
- // `followup_message` it auto-submits.
1364
- const { emitStopOutcome } = await import("./adapter/output.ts");
1365
- return emitStopOutcome(adapter, { verdict }, coordRoot);
1366
- }
1367
- } else {
1368
- const { clearRemediationCount } = await import("./remediation-cap.ts");
1369
- clearRemediationCount(sessionId);
1414
+ payload?.cwd,
1415
+ recovery?.briefing ?? "",
1416
+ hostPromptContext,
1417
+ );
1418
+ if (injected && recovery) {
1419
+ completeRecoveryInjection(coordRoot, owner.instance_id, sessionId, recovery);
1370
1420
  }
1421
+ } catch (err) {
1422
+ logError(coordRoot, err, { phase: "user-prompt-submit-systemMessage" });
1423
+ }
1424
+ }
1371
1425
 
1372
- // An explicit end requested from inside this turn cannot be authoritative
1373
- // until the adapter has committed turn.completed above. Reconcile only
1374
- // when such a request exists; any later real work cancels it in the
1375
- // finalizer instead of being terminated underneath the agent.
1376
- try {
1377
- const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import(
1378
- "../agents/session-finalizer-v3.ts"
1379
- );
1380
- if (hasPendingExplicitSessionEndV3(coordRoot)) {
1381
- reconcileSessionFinalizationV3(coordRoot);
1382
- }
1383
- } catch (err) {
1384
- logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
1385
- }
1386
- if (adapter === "codex") scheduleCodexRuntimeContextRetry(coordRoot, sessionId);
1426
+ async function handleStop(run: HookRun): Promise<number | undefined> {
1427
+ const { coordRoot, adapter, behavior, payload, owner, sessionId, data, debugBase } = run;
1428
+ // Session telemetry sync remains an independent side effect.
1429
+ if (behavior.sessionTelemetrySync) runSessionSyncExtension(coordRoot, false);
1430
+
1431
+ // Publish after the canonical turn event has landed so the blob carries
1432
+ // current task, activity, and claim state. Re-ensure the relay here because
1433
+ // its daemon intentionally exits when a machine has no live sessions.
1434
+ try {
1435
+ publishPresence(coordRoot);
1436
+ ensureRelayDaemon(coordRoot);
1437
+ } catch (err) {
1438
+ logError(coordRoot, err, { phase: "stop-presence" });
1387
1439
  }
1388
1440
 
1389
- // Phase 7: PreToolUse: heartbeat + pid-map self-heal on every tool call.
1390
- // Adapter-agnostic: both writes have the same shape regardless of who fired.
1391
- // Cursor/Codex bash dispatchers still fire their own G-guard logic, but the
1392
- // heals here keep the agent-coord layer's view of liveness fresh.
1393
- //
1394
- // The heartbeat + pid-map heals are paired by design; they were wired
1395
- // side-by-side in the previous pre-tool-use adapter. The Phase 4-6 refactor
1396
- // preserved the heartbeat half but dropped the pid-map half; the pid-map
1397
- // call was restored here afterward.
1398
- if (norm.event_type === "tool.requested") {
1399
- try {
1400
- // Recorded fork lineage, heal-path flavor. A forked CC conversation
1401
- // never fires its own session.started (SessionStart fires under the
1402
- // PARENT's session id with source=resume, before the fork id is
1403
- // minted; verified 2026-08-05), so the fork's new instance first
1404
- // materializes right here. Gate detection on "no heartbeat yet" so the
1405
- // transcript scan runs once per instance lifetime, not per tool call.
1406
- refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
1407
- } catch (err) {
1408
- logError(coordRoot, err, { phase: "pre-tool-use-heal" });
1441
+ // Stop verdict (V3 ritual + task/status gate). Direct in-process call: the
1442
+ // rule lives in Harnery. agent-hook already emitted this turn.completed
1443
+ // with privacy-safe ritual observations, so the evidence is in the stream.
1444
+ const verdict = evaluateStopHook(coordRoot, {
1445
+ rule: "stop-hook",
1446
+ instance_id: owner.instance_id,
1447
+ session_id: sessionId,
1448
+ adapter,
1449
+ stop_hook_active: payload?.stop_hook_active === true,
1450
+ status_box_present_strict: data.status_box_present_strict === true,
1451
+ session_name_observation_unavailable: typeof data.session_name_unavailable_reason === "string",
1452
+ bypass: coordEnv("AGENT_COORD_BYPASS_STOP") === "1",
1453
+ workflow_child: coordEnv("WORKFLOW_CHILD") === "1",
1454
+ });
1455
+ if (!verdict.allow) {
1456
+ // Backstop for sessions whose evidence can never land (e.g. a headless
1457
+ // child whose status command resolves to a different owner): after
1458
+ // `cap` consecutive blocked stops in one cycle, allow the stop instead
1459
+ // of bouncing the model forever. Cycle boundaries come from the
1460
+ // adapter's continuation flag, so adapters without one are unaffected.
1461
+ const { recordRemediationBlock } = await import("./remediation-cap.ts");
1462
+ const remediation = recordRemediationBlock(sessionId, payload?.stop_hook_active === true);
1463
+ if (remediation.exceeded) {
1464
+ appendDebug(coordRoot, {
1465
+ ...debugBase,
1466
+ skipped: "stop-remediation-cap-exhausted",
1467
+ blocked_rule: verdict.rule,
1468
+ blocked_count: remediation.count,
1469
+ session_id: sessionId,
1470
+ remediation_cycle_anchor: verdict.remediation_cycle_anchor,
1471
+ });
1472
+ } else {
1473
+ // Adapter-aware enforcement channel: Claude Code honors exit-2 + stderr
1474
+ // as a turn block; Cursor ignores exit codes and re-prompts only via a
1475
+ // `followup_message` it auto-submits.
1476
+ const { emitStopOutcome } = await import("./adapter/output.ts");
1477
+ return emitStopOutcome(adapter, { verdict }, coordRoot);
1409
1478
  }
1479
+ } else {
1480
+ const { clearRemediationCount } = await import("./remediation-cap.ts");
1481
+ clearRemediationCount(sessionId);
1482
+ }
1410
1483
 
1411
- // The suggested name is a pending display latch. The set-task call itself
1412
- // runs before a name exists; every later tool waits until the exact block
1413
- // is the first assistant text after the mint result. Cursor supplies
1414
- // agent_message directly; Claude Code and Codex are resolved from their
1415
- // JSONL transcripts. Cursor's current narration can provide positive
1416
- // evidence, but cannot disprove an earlier response; afterAgentResponse
1417
- // stamps that durable sighting. Later commentary must not erase an
1418
- // already-correct display, and unavailable evidence must not deadlock
1419
- // every tool.
1420
- try {
1421
- const displayAllowed = await enforcePendingSessionNameDisplay(
1422
- coordRoot,
1423
- owner.instance_id,
1424
- adapter,
1425
- payload,
1426
- generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId),
1427
- );
1428
- if (!displayAllowed) return 0;
1429
- } catch (err) {
1430
- logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
1484
+ // An explicit end requested from inside this turn cannot be authoritative
1485
+ // until the adapter has committed turn.completed above. Reconcile only
1486
+ // when such a request exists; any later real work cancels it in the
1487
+ // finalizer instead of being terminated underneath the agent.
1488
+ try {
1489
+ const { hasPendingExplicitSessionEndV3, reconcileSessionFinalizationV3 } = await import(
1490
+ "../agents/session-finalizer-v3.ts"
1491
+ );
1492
+ if (hasPendingExplicitSessionEndV3(coordRoot)) {
1493
+ reconcileSessionFinalizationV3(coordRoot);
1431
1494
  }
1495
+ } catch (err) {
1496
+ logError(coordRoot, err, { phase: "stop-explicit-session-finalization" });
1497
+ }
1498
+ if (behavior.runtimeContextRetry) scheduleRuntimeContextRetry(coordRoot, adapter, sessionId);
1499
+ return undefined;
1500
+ }
1432
1501
 
1433
- // Windows-native Codex + WSL UNC only: block the one cross-shell shape
1434
- // proven to corrupt argument boundaries. Normal WSL argv calls, literal
1435
- // bash -s scripts, native Linux/macOS sessions, and every other adapter
1436
- // pass through. The host instructions name its concrete safe bridge.
1437
- const unsafeShellReason = unsafeCrossShellReason({
1502
+ async function handleToolRequested(run: HookRun): Promise<number | undefined> {
1503
+ const { coordRoot, adapter, behavior, payload, owner, sessionId, data, recordedGenerationId } =
1504
+ run;
1505
+ try {
1506
+ // Recorded fork lineage, heal-path flavor. A forked CC conversation
1507
+ // never fires its own session.started (SessionStart fires under the
1508
+ // PARENT's session id with source=resume, before the fork id is
1509
+ // minted; verified 2026-08-05), so the fork's new instance first
1510
+ // materializes right here. Gate detection on "no heartbeat yet" so the
1511
+ // transcript scan runs once per instance lifetime, not per tool call.
1512
+ refreshPidmap(coordRoot, owner.instance_id, adapter, payload?.pid);
1513
+ } catch (err) {
1514
+ logError(coordRoot, err, { phase: "pre-tool-use-heal" });
1515
+ }
1516
+
1517
+ // The suggested name is a pending display latch. The set-task call itself
1518
+ // runs before a name exists; every later tool waits until the exact block
1519
+ // is the first assistant text after the mint result. Cursor supplies
1520
+ // agent_message directly; Claude Code and Codex are resolved from their
1521
+ // JSONL transcripts. Cursor's current narration can provide positive
1522
+ // evidence, but cannot disprove an earlier response; afterAgentResponse
1523
+ // stamps that durable sighting. Later commentary must not erase an
1524
+ // already-correct display, and unavailable evidence must not deadlock
1525
+ // every tool.
1526
+ try {
1527
+ const displayAllowed = await enforcePendingSessionNameDisplay(
1528
+ coordRoot,
1529
+ owner.instance_id,
1438
1530
  adapter,
1531
+ payload,
1532
+ generationBoundHeartbeat(readHeartbeat(coordRoot, owner.instance_id), recordedGenerationId),
1533
+ );
1534
+ if (!displayAllowed) return 0;
1535
+ } catch (err) {
1536
+ logError(coordRoot, err, { phase: "pre-tool-use-session-name" });
1537
+ }
1538
+
1539
+ // Windows-native Codex + WSL UNC only: block the one cross-shell shape
1540
+ // proven to corrupt argument boundaries. Normal WSL argv calls, literal
1541
+ // bash -s scripts, native Linux/macOS sessions, and every other adapter
1542
+ // pass through. The host instructions name its concrete safe bridge.
1543
+ const unsafeShellReason =
1544
+ behavior.unsafeCrossShellReason?.({
1439
1545
  cwd: payload?.cwd,
1440
1546
  toolName: payload?.tool_name,
1441
1547
  toolInput: payload?.tool_input,
1442
- });
1443
- if (unsafeShellReason) {
1444
- const { emitDeny } = await import("./adapter/output.ts");
1445
- emitDeny(adapter, unsafeShellReason);
1446
- return 0;
1447
- }
1548
+ }) ?? null;
1549
+ if (unsafeShellReason) {
1550
+ const { emitDeny } = await import("./adapter/output.ts");
1551
+ emitDeny(adapter, unsafeShellReason);
1552
+ return 0;
1553
+ }
1448
1554
 
1449
- // G-guard for ALL adapters. Claude Code previously ran this via a
1450
- // pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
1451
- // that adapter is now deleted, so agent-hook owns the deny for every adapter.
1452
- // emitDeny() inside emits the adapter-shaped permission JSON (claude-code +
1453
- // codex use hookSpecificOutput.permissionDecision; cursor uses .permission).
1454
- // apply_patch (codex) parses paths from the patch body and runs verdict
1455
- // per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
1456
- // (incl. Agent) yield no targets and pass through with no deny.
1457
- let guardAllowed = true;
1458
- try {
1459
- guardAllowed = await runPreToolUseGuard(
1460
- coordRoot,
1461
- owner.instance_id,
1462
- sessionId,
1463
- data,
1464
- adapter,
1465
- );
1466
- } catch (err) {
1467
- logError(coordRoot, err, { phase: "pre-tool-use-guard" });
1468
- }
1469
- if (!guardAllowed) return 0;
1555
+ const waiterReason = shellWaiterReason(payload?.tool_name, payload?.tool_input);
1556
+ if (waiterReason) {
1557
+ const { emitDeny } = await import("./adapter/output.ts");
1558
+ emitDeny(adapter, waiterReason);
1559
+ return 0;
1470
1560
  }
1471
1561
 
1472
- if (norm.event_type === "tool.completed" && eventName === "post-tool-use") {
1473
- // Inject only at the successful tool boundary that actually minted or
1474
- // retried a name: the first non-empty set-task, a pending-name set-task
1475
- // retry, or the transition to lifecycle done. The
1476
- // coordination row intentionally remains pending until transcript evidence
1477
- // catches up; reading that latch alone here would re-inject after every
1478
- // later tool and make the agent print the same block repeatedly.
1479
- try {
1480
- const name = sessionNameDisplayPending(readLiveCoordinationRow(coordRoot, owner.instance_id));
1481
- if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
1482
- // Record the title before asking for it. The suggestion can still
1483
- // change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
1484
- // rebuilt cache), and the agent must not be stranded for displaying
1485
- // exactly what it was handed.
1486
- stampSessionNameRequested(coordRoot, owner.instance_id, name);
1562
+ // G-guard for ALL adapters. Claude Code previously ran this via a
1563
+ // pre-tool-use bash adapter (which called `agent-coord verdict --rule=claim`);
1564
+ // that adapter is now deleted, so agent-hook owns the deny for every adapter.
1565
+ // emitDeny() inside emits the adapter-shaped permission JSON (claude-code +
1566
+ // codex use hookSpecificOutput.permissionDecision; cursor uses .permission).
1567
+ // apply_patch (codex) parses paths from the patch body and runs verdict
1568
+ // per-path; Edit/Write/NotebookEdit resolve a single target. Non-write tools
1569
+ // (incl. Agent) yield no targets and pass through with no deny.
1570
+ let guardAllowed = true;
1571
+ try {
1572
+ guardAllowed = await runPreToolUseGuard(coordRoot, owner.instance_id, sessionId, data, adapter);
1573
+ } catch (err) {
1574
+ logError(coordRoot, err, { phase: "pre-tool-use-guard" });
1575
+ }
1576
+ if (!guardAllowed) return 0;
1577
+ return undefined;
1578
+ }
1579
+
1580
+ async function handlePostToolUse(run: HookRun): Promise<void> {
1581
+ const { coordRoot, adapter, eventName, payload, owner } = run;
1582
+ // Inject the full instruction only at the successful tool boundary that
1583
+ // actually minted or retried a name: the first non-empty set-task, a
1584
+ // pending-name set-task retry, or the transition to lifecycle done. The
1585
+ // coordination row intentionally remains pending until transcript evidence
1586
+ // catches up, so the latch alone cannot drive injection: that would print
1587
+ // the same block after every later tool.
1588
+ //
1589
+ // Between those two extremes sits a bounded reminder. Since ADR 0182 the
1590
+ // PreToolUse gate allows a tool when transcript evidence is unavailable
1591
+ // and says so only on stderr, which the model never reads. A model that
1592
+ // skipped the block after the mint was therefore never asked again
1593
+ // (ADR 0183). While the requested title is still pending, ordinary tool
1594
+ // results carry an idempotent reminder up to a fixed count; coordination
1595
+ // chores (set-task, status, suggest-name) are skipped because a mint or
1596
+ // retry response already carries the full instruction and an end-turn
1597
+ // status is the wrong place to ask.
1598
+ try {
1599
+ const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
1600
+ // A workflow child has no user-visible title to set.
1601
+ const name = coordEnv("WORKFLOW_CHILD") === "1" ? undefined : sessionNameDisplayPending(row);
1602
+ if (name && toolResponseMintedSessionName(payload?.tool_response, name)) {
1603
+ // Record the title before asking for it. The suggestion can still
1604
+ // change afterwards (an assigned-name rewrite, a lifecycle re-mint, a
1605
+ // rebuilt cache), and the agent must not be stranded for displaying
1606
+ // exactly what it was handed.
1607
+ stampSessionNameRequested(coordRoot, owner.instance_id, name);
1608
+ const { emitContext } = await import("./adapter/output.ts");
1609
+ emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
1610
+ } else if (name) {
1611
+ const due = sessionNameDisplayReminderDue(row);
1612
+ const command = extractBashCommand(payload?.tool_name, payload?.tool_input);
1613
+ if (due && !isSessionNameRemediationCommand(command, resolveBinName(coordRoot))) {
1614
+ stampSessionNameReminded(coordRoot, owner.instance_id, due);
1615
+ appendDebug(coordRoot, {
1616
+ ts: new Date().toISOString(),
1617
+ event_name: eventName,
1618
+ adapter,
1619
+ instance_id: owner.instance_id,
1620
+ effect: "session-name-display-reminded",
1621
+ session_name_pending: due,
1622
+ reminders: (row?.session_name_display_reminders ?? 0) + 1,
1623
+ });
1487
1624
  const { emitContext } = await import("./adapter/output.ts");
1488
- emitContext(adapter, "PostToolUse", sessionNameDisplayInstruction(name));
1625
+ emitContext(adapter, "PostToolUse", sessionNameDisplayReminder(due));
1489
1626
  }
1490
- } catch (err) {
1491
- logError(coordRoot, err, { phase: "post-tool-use-session-name" });
1492
1627
  }
1628
+ } catch (err) {
1629
+ logError(coordRoot, err, { phase: "post-tool-use-session-name" });
1493
1630
  }
1631
+ }
1494
1632
 
1495
- // Phase 7: PostToolUseFailure: release claim on failed Edit (the file
1496
- // never landed; the claim is stale). Adapter-agnostic.
1497
- if (norm.event_type === "tool.completed" && eventName === "post-tool-use-failure") {
1498
- try {
1499
- releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
1500
- } catch (err) {
1501
- logError(coordRoot, err, { phase: "post-tool-use-failure-release" });
1502
- }
1633
+ function handlePostToolUseFailure(run: HookRun): void {
1634
+ const { coordRoot, payload, owner, data } = run;
1635
+ try {
1636
+ releaseClaimOnFailure(coordRoot, owner.instance_id, data, payload?.raw);
1637
+ } catch (err) {
1638
+ logError(coordRoot, err, { phase: "post-tool-use-failure-release" });
1503
1639
  }
1640
+ }
1504
1641
 
1642
+ async function handleCompletedResponse(run: CompletedResponseRun): Promise<number> {
1643
+ const { coordRoot, adapter, eventName, raw, debugBase, ledgerRoute, hookStartedAt, hookClock } =
1644
+ run;
1645
+ const payload = parsePayload(raw, adapter);
1646
+ const owner = resolveOwner({ payload: payload?.raw ?? null, coordRoot });
1647
+ if (!owner) {
1648
+ appendDebug(coordRoot, { ...debugBase, skipped: "no-owner-resolved" });
1649
+ return 0;
1650
+ }
1651
+ const row = readLiveCoordinationRow(coordRoot, owner.instance_id);
1652
+ const name = sessionNameDisplayPending(row);
1653
+ const text = typeof payload?.raw.text === "string" ? payload.raw.text : "";
1654
+ const sighting = matchSessionNameDisplay(row, text);
1655
+ if (sighting) {
1656
+ stampSessionNameSeen(coordRoot, owner.instance_id, sighting.pending);
1657
+ appendDebug(coordRoot, {
1658
+ ...debugBase,
1659
+ effect: "session-name-display-stamped",
1660
+ session_name_pending: sighting.pending,
1661
+ ...(sighting.displayed === sighting.pending
1662
+ ? {}
1663
+ : { session_name_displayed: sighting.displayed, session_name_drift: true }),
1664
+ });
1665
+ } else if (!name) {
1666
+ // Nothing was owed. Distinct from a miss: conflating the two is what made
1667
+ // a latched Cursor session undiagnosable from this log.
1668
+ appendDebug(coordRoot, { ...debugBase, skipped: "no-pending-session-name" });
1669
+ } else {
1670
+ appendDebug(coordRoot, {
1671
+ ...debugBase,
1672
+ skipped: "session-name-block-absent",
1673
+ session_name_pending: name,
1674
+ reply_bytes: text.length,
1675
+ reply_leads_with_fence: /^\s*`{3,}/.test(text),
1676
+ });
1677
+ }
1678
+ if (ledgerRoute.state === "blocked") {
1679
+ appendDebug(coordRoot, {
1680
+ ...debugBase,
1681
+ skipped: "v3-control-blocked",
1682
+ reason: ledgerRoute.reason,
1683
+ owner_source: owner.source,
1684
+ });
1685
+ return 0;
1686
+ }
1687
+ const statusBoxPresentStrict = scanAssistantStatusBoxPresent(undefined, text);
1688
+ // The hook parser needs the completed reply long enough to scan it, but
1689
+ // durable hook intake must never retain that body. Keep every other native
1690
+ // field so turn correlation remains available to the producer.
1691
+ const { text: _completedReply, ...privacySafeRaw } = payload?.raw ?? {};
1692
+ const privacySafePayload = payload ? { ...payload, raw: privacySafeRaw } : null;
1693
+ const v3Result = recordLiveHookSignalV3({
1694
+ coordRoot,
1695
+ route: ledgerRoute,
1696
+ eventName,
1697
+ payload: privacySafePayload,
1698
+ adapter,
1699
+ instanceId: owner.instance_id,
1700
+ hook_name: eventName,
1701
+ hook_duration_ms: Math.max(0, Math.floor(performance.now() - hookStartedAt)),
1702
+ monotonic_ns: hookClock.monotonic_ns,
1703
+ turn_ritual: {
1704
+ status_box_present: statusBoxPresentStrict,
1705
+ status_box_present_strict: statusBoxPresentStrict,
1706
+ session_name_required: name !== undefined,
1707
+ session_name_present: name ? assistantTextStartsWithSessionNameBlock(text, name) : false,
1708
+ },
1709
+ });
1710
+ observeHookDebug(hookHealthState, { event_v3_state: v3Result.state });
1711
+ appendDebug(coordRoot, {
1712
+ ...debugBase,
1713
+ effect: "cursor-response-ritual-observed",
1714
+ owner_source: owner.source,
1715
+ event_v3_state: v3Result.state,
1716
+ status_box_present_strict: statusBoxPresentStrict,
1717
+ ...(v3Result.state === "observed"
1718
+ ? {
1719
+ generation_id: v3Result.generation_id,
1720
+ turn_id: v3Result.turn_id,
1721
+ response_observed_at: v3Result.observed_at,
1722
+ }
1723
+ : {}),
1724
+ });
1505
1725
  return 0;
1506
1726
  }
1507
1727
 
1508
- function scheduleCodexRuntimeContextRetry(coordRoot: string, nativeSessionId: string): void {
1728
+ function scheduleRuntimeContextRetry(
1729
+ coordRoot: string,
1730
+ adapter: Adapter,
1731
+ nativeSessionId: string,
1732
+ ): void {
1509
1733
  const modulePath = process.argv[1];
1510
1734
  if (!modulePath) return;
1511
1735
  try {
1512
1736
  const child = spawn(
1513
1737
  process.execPath,
1514
- [modulePath, "runtime-context-retry", "--adapter", "codex", "--session-id", nativeSessionId],
1738
+ [modulePath, "runtime-context-retry", "--adapter", adapter, "--session-id", nativeSessionId],
1515
1739
  {
1516
1740
  cwd: coordRoot,
1517
1741
  detached: true,
@@ -1531,7 +1755,7 @@ async function runRuntimeContextRetryWorker(
1531
1755
  extra: string[],
1532
1756
  ): Promise<number> {
1533
1757
  const nativeSessionId = extraArg(extra, "--session-id");
1534
- if (adapter !== "codex" || !nativeSessionId) return 0;
1758
+ if (!adapter || !adapterBehavior(adapter).runtimeContextRetry || !nativeSessionId) return 0;
1535
1759
  for (const [index, delayMs] of [400, 1_000].entries()) {
1536
1760
  await new Promise((resolve) => setTimeout(resolve, delayMs));
1537
1761
  const route = resolveLiveEventLedgerRouteV3(coordRoot);
@@ -1547,6 +1771,27 @@ async function runRuntimeContextRetryWorker(
1547
1771
  return 0;
1548
1772
  }
1549
1773
 
1774
+ function reportSessionNameObservationUnavailable(
1775
+ coordRoot: string,
1776
+ instanceId: string,
1777
+ adapter: Adapter,
1778
+ eventName: string,
1779
+ reason: string,
1780
+ ): void {
1781
+ appendDebug(coordRoot, {
1782
+ ts: new Date().toISOString(),
1783
+ event_name: eventName,
1784
+ adapter,
1785
+ instance_id: instanceId,
1786
+ effect: "session-name-observation-unavailable",
1787
+ reason,
1788
+ action: "fail-open",
1789
+ });
1790
+ process.stderr.write(
1791
+ `Harnery: session-name observation unavailable (${reason}); allowing this ${eventName} without display enforcement. The name remains pending.\n`,
1792
+ );
1793
+ }
1794
+
1550
1795
  async function enforcePendingSessionNameDisplay(
1551
1796
  coordRoot: string,
1552
1797
  instanceId: string,
@@ -1554,6 +1799,7 @@ async function enforcePendingSessionNameDisplay(
1554
1799
  payload: ParsedPayload | null,
1555
1800
  coordination: Heartbeat | null,
1556
1801
  ): Promise<boolean> {
1802
+ if (coordEnv("WORKFLOW_CHILD") === "1") return true;
1557
1803
  const name = sessionNameDisplayPending(coordination);
1558
1804
  if (!name) return true;
1559
1805
 
@@ -1567,11 +1813,13 @@ async function enforcePendingSessionNameDisplay(
1567
1813
  }
1568
1814
 
1569
1815
  if (inspection.state === "unavailable") {
1570
- // PostToolUse already delivered the exact block for this mint. An
1571
- // unavailable transcript cannot become evidence, and repeating a generic
1572
- // reminder on every tool creates an unbounded Codex loop. Keep the latch
1573
- // honestly pending and fail open without another instruction. A later
1574
- // readable transcript can still verify it.
1816
+ reportSessionNameObservationUnavailable(
1817
+ coordRoot,
1818
+ instanceId,
1819
+ adapter,
1820
+ "pre-tool-use",
1821
+ inspection.reason,
1822
+ );
1575
1823
  return true;
1576
1824
  }
1577
1825
 
@@ -1605,21 +1853,20 @@ function inspectDisplayEvidence(
1605
1853
  payload: ParsedPayload | null,
1606
1854
  coordination: Heartbeat | null,
1607
1855
  ): SessionNameDisplayInspection {
1608
- if (adapter === "cursor" && payload?.agent_message !== undefined) {
1609
- return matchSessionNameDisplay(coordination, payload.agent_message)
1856
+ const behavior = adapterBehavior(adapter);
1857
+ const inlineText = behavior.inlineAssistantText?.(payload);
1858
+ if (inlineText !== undefined) {
1859
+ return matchSessionNameDisplay(coordination, inlineText)
1610
1860
  ? { state: "present" }
1611
1861
  : { state: "unavailable", reason: "transcript_not_ready" };
1612
1862
  }
1613
1863
 
1614
- // Codex hook payloads omit transcript_path on every event, which left this
1615
- // inspection permanently unavailable and the latch never stamped. Discover
1616
- // the rollout by session id; the scan only runs while a name is pending, so
1617
- // the latch closes after one success.
1864
+ // Some adapters omit transcript_path on every event, which left this
1865
+ // inspection permanently unavailable and the latch never stamped. The
1866
+ // behavior may discover the transcript by session id; the scan only runs
1867
+ // while a name is pending, so the latch closes after one success.
1618
1868
  const transcriptPath =
1619
- payload?.transcript_path ??
1620
- (adapter === "codex"
1621
- ? discoverCodexSessionTranscript(payload?.session_id ?? instanceId)
1622
- : undefined);
1869
+ payload?.transcript_path ?? behavior.discoverTranscript?.(payload?.session_id ?? instanceId);
1623
1870
 
1624
1871
  let verdict: SessionNameDisplayInspection = { state: "absent" };
1625
1872
  for (const candidate of sessionNameDisplayAcceptedNames(coordination)) {
@@ -1913,7 +2160,7 @@ function releaseClaimOnFailure(
1913
2160
  coordRoot,
1914
2161
  owner: instanceId,
1915
2162
  nativeSessionId: before?.session_id ?? instanceId,
1916
- adapter: adapterFromPlatform(before?.platform),
2163
+ adapter: sharedAdapterFromPlatform(before?.platform, { context: "hook-claim-release" }),
1917
2164
  operation: "released",
1918
2165
  path: canonical,
1919
2166
  access: "write",
@@ -1956,22 +2203,22 @@ async function emitUserPromptSubmitSystemMessage(
1956
2203
  recoveryBriefing = "",
1957
2204
  hostPromptContext = "",
1958
2205
  ): Promise<boolean> {
2206
+ // Workflow children get no prompt-time coordination context, matching
2207
+ // SessionStart. The turn ritual is not enforced for them, the naming nudge
2208
+ // would register them as named peers, and rendering drains peer mail.
2209
+ if (coordEnv("WORKFLOW_CHILD") === "1") return false;
2210
+ const behavior = adapterBehavior(adapter);
1959
2211
  const { renderPromptContext } = await import("../agents/render/prompt-context.ts");
1960
2212
  let additionalContext = renderPromptContext({
1961
2213
  coordRoot,
1962
2214
  instanceId,
1963
2215
  sessionId,
1964
2216
  sessionNameNudge: true,
1965
- taskNudge: adapter === "cursor" || adapter === "codex",
1966
- hostPromptReminder: adapter === "codex" || adapter === "cursor",
1967
- statusFooterNudge: adapter === "codex",
1968
- turnRitualNudge: adapter === "claude-code" ? adapter : undefined,
2217
+ ...behavior.promptContextNudges,
1969
2218
  }).trim();
1970
- if (adapter === "codex") {
1971
- const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, workspaceCwd);
1972
- if (fileLinkContext) {
1973
- additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
1974
- }
2219
+ const fileLinkContext = behavior.fileLinkContext?.(coordRoot, workspaceCwd) ?? "";
2220
+ if (fileLinkContext) {
2221
+ additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
1975
2222
  }
1976
2223
  if (recoveryBriefing) {
1977
2224
  additionalContext = [additionalContext, recoveryBriefing].filter(Boolean).join("\n\n");
@@ -2022,16 +2269,9 @@ async function emitSubagentStartContext(
2022
2269
  }
2023
2270
 
2024
2271
  function adapterPlatform(adapter: Adapter): string {
2025
- if (adapter === "claude-code") return "claude-code";
2026
2272
  return adapter;
2027
2273
  }
2028
2274
 
2029
- function adapterFromPlatform(platform: unknown): Adapter {
2030
- if (platform === "cursor") return "cursor";
2031
- if (platform === "codex") return "codex";
2032
- return "claude-code";
2033
- }
2034
-
2035
2275
  async function emitSessionStartSystemMessage(
2036
2276
  coordRoot: string,
2037
2277
  instanceId: string,
@@ -2041,6 +2281,7 @@ async function emitSessionStartSystemMessage(
2041
2281
  recoveryBriefing = "",
2042
2282
  backupCue = "",
2043
2283
  ): Promise<boolean> {
2284
+ const behavior = adapterBehavior(adapter);
2044
2285
  const workflowChild = coordEnv("WORKFLOW_CHILD") === "1";
2045
2286
  let additionalContext = "";
2046
2287
  // Opportunistic reconciliation makes normal session starts a failsafe for
@@ -2061,16 +2302,15 @@ async function emitSessionStartSystemMessage(
2061
2302
  instanceId,
2062
2303
  sessionId,
2063
2304
  agentName: agentName || undefined,
2064
- platformLabel:
2065
- adapter === "claude-code" ? undefined : adapter === "cursor" ? "Cursor" : "Codex",
2305
+ platformLabel: behavior.sessionStartPlatformLabel,
2066
2306
  }).trim();
2067
2307
  }
2068
2308
 
2069
- // Effect (claude-code): merge the journal recovery cue into the session-start
2070
- // context. Was a standalone additionalContext emission from the previous
2071
- // journal-on-start adapter; now that agent-hook is the single SessionStart
2072
- // entry, it folds in here.
2073
- if (adapter === "claude-code" && !workflowChild) {
2309
+ // Effect (journal-maintaining adapters): merge the journal recovery cue into
2310
+ // the session-start context. Was a standalone additionalContext emission
2311
+ // from the previous journal-on-start adapter; now that agent-hook is the
2312
+ // single SessionStart entry, it folds in here.
2313
+ if (behavior.journalMaintenance && !workflowChild) {
2074
2314
  const cue = journalRecoveryCue(coordRoot);
2075
2315
  if (cue) additionalContext = [additionalContext, cue].filter(Boolean).join("\n\n");
2076
2316
  }
@@ -2080,19 +2320,14 @@ async function emitSessionStartSystemMessage(
2080
2320
  if (backupCue && !workflowChild) {
2081
2321
  additionalContext = [additionalContext, backupCue].filter(Boolean).join("\n\n");
2082
2322
  }
2083
- if (adapter === "codex" && !workflowChild && isWslUncPath(emittedData.cwd)) {
2084
- const fileLinkContext = renderCodexWslFileLinkContext(coordRoot, emittedData.cwd);
2323
+ if (!workflowChild && behavior.bridgeFor(emittedData.cwd)) {
2324
+ const fileLinkContext = behavior.fileLinkContext?.(coordRoot, emittedData.cwd) ?? "";
2085
2325
  if (fileLinkContext) {
2086
2326
  additionalContext = [additionalContext, fileLinkContext].filter(Boolean).join("\n\n");
2087
2327
  }
2088
- const bridge = inspectCodexWslBridge(process.env, { expected: true });
2089
- if (bridge && !bridge.ok) {
2090
- additionalContext = [
2091
- additionalContext,
2092
- `Harnery hybrid warning: ${bridge.detail}. Run \`${resolveBinName(coordRoot)} doctor\` for the repair hint.`,
2093
- ]
2094
- .filter(Boolean)
2095
- .join("\n\n");
2328
+ const warning = behavior.bridgeWarning?.(coordRoot, emittedData.cwd);
2329
+ if (warning) {
2330
+ additionalContext = [additionalContext, warning].filter(Boolean).join("\n\n");
2096
2331
  }
2097
2332
  }
2098
2333
  const { emitContext } = await import("./adapter/output.ts");
@@ -2111,6 +2346,7 @@ function completeRecoveryInjection(
2111
2346
 
2112
2347
  main()
2113
2348
  .then(async (code) => {
2349
+ clearHookRunMarker(hookRunMarkerPath);
2114
2350
  writeHookHealthCompletion(hookHealthState, {
2115
2351
  finished_at_ms: performance.now(),
2116
2352
  finished_rss_bytes: process.memoryUsage().rss,
@@ -2125,6 +2361,7 @@ main()
2125
2361
  pid: process.pid,
2126
2362
  phase: "top-level",
2127
2363
  });
2364
+ clearHookRunMarker(hookRunMarkerPath);
2128
2365
  writeHookHealthCompletion(hookHealthState, {
2129
2366
  finished_at_ms: performance.now(),
2130
2367
  finished_rss_bytes: process.memoryUsage().rss,