harnery 0.34.1 → 0.36.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 (692) hide show
  1. package/dist/commander.d.ts +30 -5
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +124 -94
  4. package/dist/commands/agents.d.ts +37 -6
  5. package/dist/commands/agents.d.ts.map +1 -1
  6. package/dist/commands/agents.js +466 -371
  7. package/dist/commands/backup.d.ts.map +1 -1
  8. package/dist/commands/backup.js +10 -10
  9. package/dist/commands/browse-session.d.ts.map +1 -1
  10. package/dist/commands/browse-session.js +12 -0
  11. package/dist/commands/browse.d.ts +1 -1
  12. package/dist/commands/browse.d.ts.map +1 -1
  13. package/dist/commands/browse.js +31 -6
  14. package/dist/commands/conversations.d.ts +10 -0
  15. package/dist/commands/conversations.d.ts.map +1 -0
  16. package/dist/commands/conversations.js +201 -0
  17. package/dist/commands/decision.d.ts.map +1 -1
  18. package/dist/commands/decision.js +2 -2
  19. package/dist/commands/deinit.d.ts.map +1 -1
  20. package/dist/commands/deinit.js +5 -0
  21. package/dist/commands/docs.d.ts.map +1 -1
  22. package/dist/commands/docs.js +13 -2
  23. package/dist/commands/doctor.d.ts +5 -0
  24. package/dist/commands/doctor.d.ts.map +1 -1
  25. package/dist/commands/doctor.js +51 -7
  26. package/dist/commands/events.d.ts.map +1 -1
  27. package/dist/commands/events.js +18 -1
  28. package/dist/commands/fetch.js +6 -5
  29. package/dist/commands/files.d.ts +22 -0
  30. package/dist/commands/files.d.ts.map +1 -0
  31. package/dist/commands/files.js +113 -0
  32. package/dist/commands/governor.js +14 -12
  33. package/dist/commands/inbox.d.ts +5 -0
  34. package/dist/commands/inbox.d.ts.map +1 -0
  35. package/dist/commands/inbox.js +57 -0
  36. package/dist/commands/init.d.ts +4 -5
  37. package/dist/commands/init.d.ts.map +1 -1
  38. package/dist/commands/init.js +63 -13
  39. package/dist/commands/ledger-v3.d.ts.map +1 -1
  40. package/dist/commands/ledger-v3.js +347 -1
  41. package/dist/commands/logs.d.ts +4 -0
  42. package/dist/commands/logs.d.ts.map +1 -0
  43. package/dist/commands/logs.js +169 -0
  44. package/dist/commands/messages.d.ts +5 -0
  45. package/dist/commands/messages.d.ts.map +1 -0
  46. package/dist/commands/messages.js +45 -0
  47. package/dist/commands/outline.d.ts +1 -1
  48. package/dist/commands/outline.d.ts.map +1 -1
  49. package/dist/commands/outline.js +2 -2
  50. package/dist/commands/presence.d.ts.map +1 -1
  51. package/dist/commands/presence.js +2 -2
  52. package/dist/commands/read.d.ts +1 -1
  53. package/dist/commands/read.d.ts.map +1 -1
  54. package/dist/commands/read.js +2 -2
  55. package/dist/commands/semantic.d.ts +4 -0
  56. package/dist/commands/semantic.d.ts.map +1 -0
  57. package/dist/commands/semantic.js +199 -0
  58. package/dist/commands/storage.d.ts +4 -0
  59. package/dist/commands/storage.d.ts.map +1 -0
  60. package/dist/commands/storage.js +238 -0
  61. package/dist/commands/tunnel.d.ts +5 -1
  62. package/dist/commands/tunnel.d.ts.map +1 -1
  63. package/dist/commands/tunnel.js +44 -31
  64. package/dist/commands/web.d.ts +3 -0
  65. package/dist/commands/web.d.ts.map +1 -1
  66. package/dist/commands/web.js +84 -7
  67. package/dist/commands/work.js +3 -3
  68. package/dist/commands/workflow.js +13 -13
  69. package/dist/core/adapters/profiles.d.ts +1 -1
  70. package/dist/core/agents/canonical-emit.d.ts +3 -0
  71. package/dist/core/agents/canonical-emit.d.ts.map +1 -1
  72. package/dist/core/agents/canonical-emit.js +12 -0
  73. package/dist/core/agents/claim-path.d.ts +11 -0
  74. package/dist/core/agents/claim-path.d.ts.map +1 -0
  75. package/dist/core/agents/claim-path.js +22 -0
  76. package/dist/core/agents/cli.js +209 -39
  77. package/dist/core/agents/coord-client.d.ts +29 -3
  78. package/dist/core/agents/coord-client.d.ts.map +1 -1
  79. package/dist/core/agents/coord-client.js +88 -9
  80. package/dist/core/agents/live-authority-v3.d.ts +47 -1
  81. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  82. package/dist/core/agents/live-authority-v3.js +245 -13
  83. package/dist/core/agents/live-lifecycle-v3.js +9 -4
  84. package/dist/core/agents/live-observation-v3.d.ts.map +1 -1
  85. package/dist/core/agents/live-observation-v3.js +8 -3
  86. package/dist/core/agents/render/prompt-context.d.ts +9 -3
  87. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  88. package/dist/core/agents/render/prompt-context.js +36 -17
  89. package/dist/core/agents/rules/stop-hook.d.ts +2 -0
  90. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  91. package/dist/core/agents/rules/stop-hook.js +115 -22
  92. package/dist/core/agents/rules/verdict.d.ts +1 -0
  93. package/dist/core/agents/rules/verdict.d.ts.map +1 -1
  94. package/dist/core/agents/session-events.d.ts.map +1 -1
  95. package/dist/core/agents/session-events.js +4 -0
  96. package/dist/core/agents/session-finalization-state-v3.js +1 -1
  97. package/dist/core/agents/session-finalizer-v3.js +5 -1
  98. package/dist/core/agents/session-name-display.d.ts +1 -0
  99. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  100. package/dist/core/agents/session-name-display.js +8 -1
  101. package/dist/core/agents/state/activity-log.d.ts +3 -0
  102. package/dist/core/agents/state/activity-log.d.ts.map +1 -1
  103. package/dist/core/agents/state/activity-log.js +6 -3
  104. package/dist/core/agents/state/heartbeat-reader.d.ts +46 -0
  105. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -0
  106. package/dist/core/agents/state/heartbeat-reader.js +25 -0
  107. package/dist/core/agents/state/heartbeat-writer.d.ts +9 -48
  108. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  109. package/dist/core/agents/state/heartbeat-writer.js +34 -34
  110. package/dist/core/agents/state/live-coordination-view.d.ts +6 -8
  111. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  112. package/dist/core/agents/state/live-coordination-view.js +16 -67
  113. package/dist/core/agents/state/live-coordination-writer.d.ts +12 -0
  114. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -0
  115. package/dist/core/agents/state/live-coordination-writer.js +92 -0
  116. package/dist/core/agents/state/names.d.ts +9 -1
  117. package/dist/core/agents/state/names.d.ts.map +1 -1
  118. package/dist/core/agents/state/names.js +190 -182
  119. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  120. package/dist/core/agents/state/stale-sweep.js +23 -5
  121. package/dist/core/artifacts/constants.d.ts +5 -0
  122. package/dist/core/artifacts/constants.d.ts.map +1 -0
  123. package/dist/core/artifacts/constants.js +4 -0
  124. package/dist/core/artifacts/index.d.ts +24 -3
  125. package/dist/core/artifacts/index.d.ts.map +1 -1
  126. package/dist/core/artifacts/index.js +96 -8
  127. package/dist/core/config.d.ts +62 -4
  128. package/dist/core/config.d.ts.map +1 -1
  129. package/dist/core/config.js +98 -4
  130. package/dist/core/conversations/archive.d.ts +49 -0
  131. package/dist/core/conversations/archive.d.ts.map +1 -0
  132. package/dist/core/conversations/archive.js +136 -0
  133. package/dist/core/conversations/catalog.d.ts +10 -0
  134. package/dist/core/conversations/catalog.d.ts.map +1 -0
  135. package/dist/core/conversations/catalog.js +72 -0
  136. package/dist/core/conversations/context-pack.d.ts +6 -0
  137. package/dist/core/conversations/context-pack.d.ts.map +1 -0
  138. package/dist/core/conversations/context-pack.js +60 -0
  139. package/dist/core/conversations/contract.d.ts +155 -0
  140. package/dist/core/conversations/contract.d.ts.map +1 -0
  141. package/dist/core/conversations/contract.js +1 -0
  142. package/dist/core/conversations/evaluation.d.ts +16 -0
  143. package/dist/core/conversations/evaluation.d.ts.map +1 -0
  144. package/dist/core/conversations/evaluation.js +9 -0
  145. package/dist/core/conversations/index.d.ts +10 -0
  146. package/dist/core/conversations/index.d.ts.map +1 -0
  147. package/dist/core/conversations/index.js +9 -0
  148. package/dist/core/conversations/lifecycle.d.ts +28 -0
  149. package/dist/core/conversations/lifecycle.d.ts.map +1 -0
  150. package/dist/core/conversations/lifecycle.js +29 -0
  151. package/dist/core/conversations/normalize.d.ts +14 -0
  152. package/dist/core/conversations/normalize.d.ts.map +1 -0
  153. package/dist/core/conversations/normalize.js +82 -0
  154. package/dist/core/conversations/query.d.ts +8 -0
  155. package/dist/core/conversations/query.d.ts.map +1 -0
  156. package/dist/core/conversations/query.js +280 -0
  157. package/dist/core/conversations/search-index.d.ts +9 -0
  158. package/dist/core/conversations/search-index.d.ts.map +1 -0
  159. package/dist/core/conversations/search-index.js +29 -0
  160. package/dist/core/events/legacy-storage/canary.d.ts +18 -0
  161. package/dist/core/events/legacy-storage/canary.d.ts.map +1 -0
  162. package/dist/core/events/legacy-storage/canary.js +150 -0
  163. package/dist/core/events/legacy-storage/fence.d.ts +15 -0
  164. package/dist/core/events/legacy-storage/fence.d.ts.map +1 -0
  165. package/dist/core/events/legacy-storage/fence.js +115 -0
  166. package/dist/core/events/legacy-storage/index.d.ts +6 -0
  167. package/dist/core/events/legacy-storage/index.d.ts.map +1 -0
  168. package/dist/core/events/legacy-storage/index.js +5 -0
  169. package/dist/core/events/legacy-storage/inventory.d.ts +10 -0
  170. package/dist/core/events/legacy-storage/inventory.d.ts.map +1 -0
  171. package/dist/core/events/legacy-storage/inventory.js +40 -0
  172. package/dist/core/events/legacy-storage/manifest.d.ts +23 -0
  173. package/dist/core/events/legacy-storage/manifest.d.ts.map +1 -0
  174. package/dist/core/events/legacy-storage/manifest.js +108 -0
  175. package/dist/core/events/legacy-storage/reader.d.ts +15 -0
  176. package/dist/core/events/legacy-storage/reader.d.ts.map +1 -0
  177. package/dist/core/events/legacy-storage/reader.js +154 -0
  178. package/dist/core/events/v3/authority-outbox.d.ts +7 -2
  179. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  180. package/dist/core/events/v3/authority-outbox.js +16 -3
  181. package/dist/core/events/v3/base-contract.d.ts +851 -16
  182. package/dist/core/events/v3/base-contract.d.ts.map +1 -1
  183. package/dist/core/events/v3/base-contract.js +50 -7
  184. package/dist/core/events/v3/bootstrap.d.ts +39 -0
  185. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  186. package/dist/core/events/v3/bootstrap.js +167 -3
  187. package/dist/core/events/v3/capabilities.d.ts +11 -0
  188. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  189. package/dist/core/events/v3/capabilities.js +26 -3
  190. package/dist/core/events/v3/control-witness.d.ts +44 -0
  191. package/dist/core/events/v3/control-witness.d.ts.map +1 -0
  192. package/dist/core/events/v3/control-witness.js +233 -0
  193. package/dist/core/events/v3/control-writer.d.ts +4 -0
  194. package/dist/core/events/v3/control-writer.d.ts.map +1 -0
  195. package/dist/core/events/v3/control-writer.js +16 -0
  196. package/dist/core/events/v3/control.d.ts +8 -2
  197. package/dist/core/events/v3/control.d.ts.map +1 -1
  198. package/dist/core/events/v3/control.js +46 -18
  199. package/dist/core/events/v3/coordination-transaction-recovery.d.ts +42 -0
  200. package/dist/core/events/v3/coordination-transaction-recovery.d.ts.map +1 -0
  201. package/dist/core/events/v3/coordination-transaction-recovery.js +342 -0
  202. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  203. package/dist/core/events/v3/coordination-view.js +16 -1
  204. package/dist/core/events/v3/generated.d.ts +1 -1
  205. package/dist/core/events/v3/generated.js +1 -1
  206. package/dist/core/events/v3/index.d.ts +8 -5
  207. package/dist/core/events/v3/index.d.ts.map +1 -1
  208. package/dist/core/events/v3/index.js +7 -4
  209. package/dist/core/events/v3/latency.d.ts +31 -1
  210. package/dist/core/events/v3/latency.d.ts.map +1 -1
  211. package/dist/core/events/v3/latency.js +127 -3
  212. package/dist/core/events/v3/live-route-observer.d.ts +31 -0
  213. package/dist/core/events/v3/live-route-observer.d.ts.map +1 -0
  214. package/dist/core/events/v3/live-route-observer.js +58 -0
  215. package/dist/core/events/v3/live-routing.d.ts +42 -20
  216. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  217. package/dist/core/events/v3/live-routing.js +153 -41
  218. package/dist/core/events/v3/producers/command-recorder.d.ts.map +1 -1
  219. package/dist/core/events/v3/producers/command-recorder.js +10 -1
  220. package/dist/core/events/v3/producers/coordination-recorder.d.ts +11 -1
  221. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  222. package/dist/core/events/v3/producers/coordination-recorder.js +90 -13
  223. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts +129 -0
  224. package/dist/core/events/v3/producers/diagnostic-summaries.d.ts.map +1 -0
  225. package/dist/core/events/v3/producers/diagnostic-summaries.js +499 -0
  226. package/dist/core/events/v3/producers/hook-base.d.ts +17 -1
  227. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  228. package/dist/core/events/v3/producers/hook-base.js +83 -2
  229. package/dist/core/events/v3/producers/intake.d.ts +13 -0
  230. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  231. package/dist/core/events/v3/producers/intake.js +36 -2
  232. package/dist/core/events/v3/producers/producer-state-observer.d.ts +8 -0
  233. package/dist/core/events/v3/producers/producer-state-observer.d.ts.map +1 -0
  234. package/dist/core/events/v3/producers/producer-state-observer.js +45 -0
  235. package/dist/core/events/v3/producers/recorder.d.ts +117 -1
  236. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  237. package/dist/core/events/v3/producers/recorder.js +992 -27
  238. package/dist/core/events/v3/reader.d.ts +51 -0
  239. package/dist/core/events/v3/reader.d.ts.map +1 -1
  240. package/dist/core/events/v3/reader.js +181 -1
  241. package/dist/core/events/v3/rotation-config.d.ts +9 -0
  242. package/dist/core/events/v3/rotation-config.d.ts.map +1 -0
  243. package/dist/core/events/v3/rotation-config.js +91 -0
  244. package/dist/core/events/v3/runtime-build.d.ts +11 -0
  245. package/dist/core/events/v3/runtime-build.d.ts.map +1 -0
  246. package/dist/core/events/v3/runtime-build.js +39 -0
  247. package/dist/core/events/v3/runtime-identity.d.ts +5 -0
  248. package/dist/core/events/v3/runtime-identity.d.ts.map +1 -0
  249. package/dist/core/events/v3/runtime-identity.js +18 -0
  250. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts +52 -0
  251. package/dist/core/events/v3/runtime-telemetry-capabilities.d.ts.map +1 -0
  252. package/dist/core/events/v3/runtime-telemetry-capabilities.js +54 -0
  253. package/dist/core/events/v3/support-storage/classify.d.ts +46 -0
  254. package/dist/core/events/v3/support-storage/classify.d.ts.map +1 -0
  255. package/dist/core/events/v3/support-storage/classify.js +149 -0
  256. package/dist/core/events/v3/support-storage/eligibility.d.ts +29 -0
  257. package/dist/core/events/v3/support-storage/eligibility.d.ts.map +1 -0
  258. package/dist/core/events/v3/support-storage/eligibility.js +35 -0
  259. package/dist/core/events/v3/support-storage/index.d.ts +10 -0
  260. package/dist/core/events/v3/support-storage/index.d.ts.map +1 -0
  261. package/dist/core/events/v3/support-storage/index.js +9 -0
  262. package/dist/core/events/v3/support-storage/inventory.d.ts +30 -0
  263. package/dist/core/events/v3/support-storage/inventory.d.ts.map +1 -0
  264. package/dist/core/events/v3/support-storage/inventory.js +94 -0
  265. package/dist/core/events/v3/support-storage/logical-authority.d.ts +14 -0
  266. package/dist/core/events/v3/support-storage/logical-authority.d.ts.map +1 -0
  267. package/dist/core/events/v3/support-storage/logical-authority.js +104 -0
  268. package/dist/core/events/v3/support-storage/maintenance.d.ts +98 -0
  269. package/dist/core/events/v3/support-storage/maintenance.d.ts.map +1 -0
  270. package/dist/core/events/v3/support-storage/maintenance.js +352 -0
  271. package/dist/core/events/v3/support-storage/pack-contract.d.ts +59 -0
  272. package/dist/core/events/v3/support-storage/pack-contract.d.ts.map +1 -0
  273. package/dist/core/events/v3/support-storage/pack-contract.js +249 -0
  274. package/dist/core/events/v3/support-storage/pack-reader.d.ts +34 -0
  275. package/dist/core/events/v3/support-storage/pack-reader.d.ts.map +1 -0
  276. package/dist/core/events/v3/support-storage/pack-reader.js +258 -0
  277. package/dist/core/events/v3/support-storage/pack-writer.d.ts +30 -0
  278. package/dist/core/events/v3/support-storage/pack-writer.d.ts.map +1 -0
  279. package/dist/core/events/v3/support-storage/pack-writer.js +268 -0
  280. package/dist/core/events/v3/support-storage/recovery.d.ts +15 -0
  281. package/dist/core/events/v3/support-storage/recovery.d.ts.map +1 -0
  282. package/dist/core/events/v3/support-storage/recovery.js +22 -0
  283. package/dist/core/events/v3/turn-telemetry.d.ts +21 -6
  284. package/dist/core/events/v3/turn-telemetry.d.ts.map +1 -1
  285. package/dist/core/events/v3/turn-telemetry.js +32 -2
  286. package/dist/core/events/v3/writer.d.ts +13 -1
  287. package/dist/core/events/v3/writer.d.ts.map +1 -1
  288. package/dist/core/events/v3/writer.js +126 -12
  289. package/dist/core/governor/plan-read.d.ts.map +1 -1
  290. package/dist/core/governor/plan-read.js +5 -21
  291. package/dist/core/governor/planning.js +6 -8
  292. package/dist/core/governor/service-read.d.ts +12 -0
  293. package/dist/core/governor/service-read.d.ts.map +1 -1
  294. package/dist/core/governor/service-read.js +266 -2
  295. package/dist/core/governor/service.d.ts +1 -0
  296. package/dist/core/governor/service.d.ts.map +1 -1
  297. package/dist/core/governor/service.js +53 -18
  298. package/dist/core/guard/evidence-v3.d.ts.map +1 -1
  299. package/dist/core/guard/evidence-v3.js +2 -0
  300. package/dist/core/hooks/adapter/events.d.ts.map +1 -1
  301. package/dist/core/hooks/adapter/events.js +1 -0
  302. package/dist/core/hooks/adapter/output.d.ts +4 -2
  303. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  304. package/dist/core/hooks/adapter/output.js +13 -5
  305. package/dist/core/hooks/adapter/parse.d.ts +3 -0
  306. package/dist/core/hooks/adapter/parse.d.ts.map +1 -1
  307. package/dist/core/hooks/adapter/parse.js +14 -0
  308. package/dist/core/hooks/adapter/runtime-telemetry.d.ts +142 -0
  309. package/dist/core/hooks/adapter/runtime-telemetry.d.ts.map +1 -0
  310. package/dist/core/hooks/adapter/runtime-telemetry.js +869 -0
  311. package/dist/core/hooks/cli.js +318 -61
  312. package/dist/core/hooks/effects/image-capture.d.ts +1 -1
  313. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -1
  314. package/dist/core/hooks/effects/image-capture.js +4 -2
  315. package/dist/core/hooks/remediation-cap.d.ts +39 -0
  316. package/dist/core/hooks/remediation-cap.d.ts.map +1 -0
  317. package/dist/core/hooks/remediation-cap.js +63 -0
  318. package/dist/core/hooks/resolve/coord-root.d.ts +22 -9
  319. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -1
  320. package/dist/core/hooks/resolve/coord-root.js +92 -11
  321. package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
  322. package/dist/core/hooks/resolve/owner.js +5 -0
  323. package/dist/core/hooks/resolve/transcript.d.ts +33 -9
  324. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  325. package/dist/core/hooks/resolve/transcript.js +33 -10
  326. package/dist/core/inbox/contract.d.ts +64 -0
  327. package/dist/core/inbox/contract.d.ts.map +1 -0
  328. package/dist/core/inbox/contract.js +13 -0
  329. package/dist/core/inbox/index.d.ts +5 -0
  330. package/dist/core/inbox/index.d.ts.map +1 -0
  331. package/dist/core/inbox/index.js +4 -0
  332. package/dist/core/inbox/lifecycle.d.ts +22 -0
  333. package/dist/core/inbox/lifecycle.d.ts.map +1 -0
  334. package/dist/core/inbox/lifecycle.js +41 -0
  335. package/dist/core/inbox/service.d.ts +36 -0
  336. package/dist/core/inbox/service.d.ts.map +1 -0
  337. package/dist/core/inbox/service.js +210 -0
  338. package/dist/core/inbox/watcher.d.ts +7 -0
  339. package/dist/core/inbox/watcher.d.ts.map +1 -0
  340. package/dist/core/inbox/watcher.js +26 -0
  341. package/dist/core/presence/codec-digest.d.ts.map +1 -1
  342. package/dist/core/presence/codec-digest.js +14 -3
  343. package/dist/core/presence/relay-client.d.ts +1 -0
  344. package/dist/core/presence/relay-client.d.ts.map +1 -1
  345. package/dist/core/presence/relay-client.js +52 -8
  346. package/dist/core/semantic/adapters.d.ts +37 -0
  347. package/dist/core/semantic/adapters.d.ts.map +1 -0
  348. package/dist/core/semantic/adapters.js +360 -0
  349. package/dist/core/semantic/contract.d.ts +1072 -0
  350. package/dist/core/semantic/contract.d.ts.map +1 -0
  351. package/dist/core/semantic/contract.js +331 -0
  352. package/dist/core/semantic/evidence.d.ts +11 -0
  353. package/dist/core/semantic/evidence.d.ts.map +1 -0
  354. package/dist/core/semantic/evidence.js +262 -0
  355. package/dist/core/semantic/index.d.ts +14 -0
  356. package/dist/core/semantic/index.d.ts.map +1 -0
  357. package/dist/core/semantic/index.js +13 -0
  358. package/dist/core/semantic/once.d.ts +38 -0
  359. package/dist/core/semantic/once.d.ts.map +1 -0
  360. package/dist/core/semantic/once.js +364 -0
  361. package/dist/core/semantic/prompt.d.ts +12 -0
  362. package/dist/core/semantic/prompt.d.ts.map +1 -0
  363. package/dist/core/semantic/prompt.js +83 -0
  364. package/dist/core/semantic/review.d.ts +144 -0
  365. package/dist/core/semantic/review.d.ts.map +1 -0
  366. package/dist/core/semantic/review.js +544 -0
  367. package/dist/core/semantic/routes.d.ts +10 -0
  368. package/dist/core/semantic/routes.d.ts.map +1 -0
  369. package/dist/core/semantic/routes.js +21 -0
  370. package/dist/core/semantic/scheduler.d.ts +27 -0
  371. package/dist/core/semantic/scheduler.d.ts.map +1 -0
  372. package/dist/core/semantic/scheduler.js +91 -0
  373. package/dist/core/semantic/service-status.d.ts +51 -0
  374. package/dist/core/semantic/service-status.d.ts.map +1 -0
  375. package/dist/core/semantic/service-status.js +140 -0
  376. package/dist/core/semantic/service.d.ts +52 -0
  377. package/dist/core/semantic/service.d.ts.map +1 -0
  378. package/dist/core/semantic/service.js +559 -0
  379. package/dist/core/semantic/soak.d.ts +75 -0
  380. package/dist/core/semantic/soak.d.ts.map +1 -0
  381. package/dist/core/semantic/soak.js +476 -0
  382. package/dist/core/semantic/storage.d.ts +81 -0
  383. package/dist/core/semantic/storage.d.ts.map +1 -0
  384. package/dist/core/semantic/storage.js +180 -0
  385. package/dist/core/semantic/usage.d.ts +43 -0
  386. package/dist/core/semantic/usage.d.ts.map +1 -0
  387. package/dist/core/semantic/usage.js +193 -0
  388. package/dist/core/semantic/validate.d.ts +18 -0
  389. package/dist/core/semantic/validate.d.ts.map +1 -0
  390. package/dist/core/semantic/validate.js +207 -0
  391. package/dist/core/storage/buffer.d.ts +28 -0
  392. package/dist/core/storage/buffer.d.ts.map +1 -0
  393. package/dist/core/storage/buffer.js +101 -0
  394. package/dist/core/storage/builtins.d.ts +3 -0
  395. package/dist/core/storage/builtins.d.ts.map +1 -0
  396. package/dist/core/storage/builtins.js +683 -0
  397. package/dist/core/storage/catalog.d.ts +19 -0
  398. package/dist/core/storage/catalog.d.ts.map +1 -0
  399. package/dist/core/storage/catalog.js +365 -0
  400. package/dist/core/storage/config.d.ts +16 -0
  401. package/dist/core/storage/config.d.ts.map +1 -0
  402. package/dist/core/storage/config.js +311 -0
  403. package/dist/core/storage/contract.d.ts +275 -0
  404. package/dist/core/storage/contract.d.ts.map +1 -0
  405. package/dist/core/storage/contract.js +3 -0
  406. package/dist/core/storage/durable-history.d.ts +36 -0
  407. package/dist/core/storage/durable-history.d.ts.map +1 -0
  408. package/dist/core/storage/durable-history.js +606 -0
  409. package/dist/core/storage/health.d.ts +4 -0
  410. package/dist/core/storage/health.d.ts.map +1 -0
  411. package/dist/core/storage/health.js +65 -0
  412. package/dist/core/storage/index.d.ts +16 -0
  413. package/dist/core/storage/index.d.ts.map +1 -0
  414. package/dist/core/storage/index.js +15 -0
  415. package/dist/core/storage/inventory.d.ts +25 -0
  416. package/dist/core/storage/inventory.d.ts.map +1 -0
  417. package/dist/core/storage/inventory.js +666 -0
  418. package/dist/core/storage/jsonl.d.ts +52 -0
  419. package/dist/core/storage/jsonl.d.ts.map +1 -0
  420. package/dist/core/storage/jsonl.js +156 -0
  421. package/dist/core/storage/log-retention.d.ts +52 -0
  422. package/dist/core/storage/log-retention.d.ts.map +1 -0
  423. package/dist/core/storage/log-retention.js +419 -0
  424. package/dist/core/storage/logger.d.ts +82 -0
  425. package/dist/core/storage/logger.d.ts.map +1 -0
  426. package/dist/core/storage/logger.js +411 -0
  427. package/dist/core/storage/maintenance-providers.d.ts +26 -0
  428. package/dist/core/storage/maintenance-providers.d.ts.map +1 -0
  429. package/dist/core/storage/maintenance-providers.js +141 -0
  430. package/dist/core/storage/maintenance.d.ts +125 -0
  431. package/dist/core/storage/maintenance.d.ts.map +1 -0
  432. package/dist/core/storage/maintenance.js +609 -0
  433. package/dist/core/storage/metrics.d.ts +60 -0
  434. package/dist/core/storage/metrics.d.ts.map +1 -0
  435. package/dist/core/storage/metrics.js +134 -0
  436. package/dist/core/storage/policy.d.ts +7 -0
  437. package/dist/core/storage/policy.d.ts.map +1 -0
  438. package/dist/core/storage/policy.js +110 -0
  439. package/dist/core/storage/process-log.d.ts +38 -0
  440. package/dist/core/storage/process-log.d.ts.map +1 -0
  441. package/dist/core/storage/process-log.js +165 -0
  442. package/dist/core/storage/query.d.ts +34 -0
  443. package/dist/core/storage/query.d.ts.map +1 -0
  444. package/dist/core/storage/query.js +295 -0
  445. package/dist/core/storage/receipts.d.ts +23 -0
  446. package/dist/core/storage/receipts.d.ts.map +1 -0
  447. package/dist/core/storage/receipts.js +117 -0
  448. package/dist/core/storage/segments.d.ts +84 -0
  449. package/dist/core/storage/segments.d.ts.map +1 -0
  450. package/dist/core/storage/segments.js +542 -0
  451. package/dist/core/storage/sink-graph.d.ts +65 -0
  452. package/dist/core/storage/sink-graph.d.ts.map +1 -0
  453. package/dist/core/storage/sink-graph.js +213 -0
  454. package/dist/core/storage/storm-control.d.ts +23 -0
  455. package/dist/core/storage/storm-control.d.ts.map +1 -0
  456. package/dist/core/storage/storm-control.js +59 -0
  457. package/dist/core/work/state.d.ts.map +1 -1
  458. package/dist/core/work/state.js +10 -14
  459. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  460. package/dist/core/workflow/live-session-v3.js +2 -1
  461. package/dist/index.d.ts +2 -0
  462. package/dist/index.d.ts.map +1 -1
  463. package/dist/index.js +2 -0
  464. package/dist/lazy-commands.d.ts +26 -0
  465. package/dist/lazy-commands.d.ts.map +1 -0
  466. package/dist/lazy-commands.js +210 -0
  467. package/dist/lib/browser/client.d.ts +11 -0
  468. package/dist/lib/browser/client.d.ts.map +1 -1
  469. package/dist/lib/browser/client.js +13 -0
  470. package/dist/lib/browser/index.d.ts +1 -0
  471. package/dist/lib/browser/index.d.ts.map +1 -1
  472. package/dist/lib/browser/standalone-html.d.ts +25 -0
  473. package/dist/lib/browser/standalone-html.d.ts.map +1 -0
  474. package/dist/lib/browser/standalone-html.js +284 -0
  475. package/dist/lib/docs-lint.d.ts +2 -0
  476. package/dist/lib/docs-lint.d.ts.map +1 -1
  477. package/dist/lib/docs-lint.js +45 -80
  478. package/dist/lib/docs-metadata-audit.d.ts +3 -0
  479. package/dist/lib/docs-metadata-audit.d.ts.map +1 -1
  480. package/dist/lib/docs-metadata-audit.js +13 -23
  481. package/dist/lib/docs-metadata-sync.d.ts +1 -0
  482. package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
  483. package/dist/lib/docs-metadata-sync.js +28 -10
  484. package/dist/lib/docs-repository-view.d.ts +22 -0
  485. package/dist/lib/docs-repository-view.d.ts.map +1 -0
  486. package/dist/lib/docs-repository-view.js +185 -0
  487. package/dist/lib/http/client.d.ts +11 -5
  488. package/dist/lib/http/client.d.ts.map +1 -1
  489. package/dist/lib/http/client.js +22 -4
  490. package/dist/lib/http/index.d.ts +1 -1
  491. package/dist/lib/http/index.d.ts.map +1 -1
  492. package/dist/lib/http/index.js +1 -1
  493. package/dist/lib/indexer-exclusions.d.ts +27 -0
  494. package/dist/lib/indexer-exclusions.d.ts.map +1 -0
  495. package/dist/lib/indexer-exclusions.js +176 -0
  496. package/dist/lib/instructions/apply.d.ts.map +1 -1
  497. package/dist/lib/instructions/apply.js +1 -0
  498. package/dist/lib/instructions/templates.d.ts +2 -0
  499. package/dist/lib/instructions/templates.d.ts.map +1 -1
  500. package/dist/lib/instructions/templates.js +149 -4
  501. package/dist/lib/local-file-url.d.ts +9 -0
  502. package/dist/lib/local-file-url.d.ts.map +1 -0
  503. package/dist/lib/local-file-url.js +20 -0
  504. package/dist/lib/web-performance.d.ts +118 -0
  505. package/dist/lib/web-performance.d.ts.map +1 -0
  506. package/dist/lib/web-performance.js +192 -0
  507. package/package.json +34 -2
  508. package/schemas/config.schema.json +58 -0
  509. package/schemas/event-v3.schema.json +3073 -581
  510. package/src/commander.ts +462 -93
  511. package/src/commands/agents.ts +617 -355
  512. package/src/commands/backup.ts +18 -14
  513. package/src/commands/browse-session.ts +12 -0
  514. package/src/commands/browse.ts +31 -9
  515. package/src/commands/conversations.ts +279 -0
  516. package/src/commands/decision.ts +8 -2
  517. package/src/commands/deinit.ts +6 -0
  518. package/src/commands/docs.ts +25 -6
  519. package/src/commands/doctor.ts +75 -9
  520. package/src/commands/events.ts +21 -1
  521. package/src/commands/fetch.ts +7 -6
  522. package/src/commands/files.ts +143 -0
  523. package/src/commands/governor.ts +13 -12
  524. package/src/commands/inbox.ts +61 -0
  525. package/src/commands/init.ts +78 -16
  526. package/src/commands/ledger-v3.ts +421 -1
  527. package/src/commands/logs.ts +224 -0
  528. package/src/commands/messages.ts +61 -0
  529. package/src/commands/outline.ts +6 -2
  530. package/src/commands/presence.ts +7 -2
  531. package/src/commands/read.ts +6 -2
  532. package/src/commands/semantic.ts +244 -0
  533. package/src/commands/storage.ts +310 -0
  534. package/src/commands/tunnel.ts +66 -52
  535. package/src/commands/web.ts +92 -7
  536. package/src/commands/work.ts +3 -3
  537. package/src/commands/workflow.ts +13 -13
  538. package/src/core/agents/canonical-emit.ts +15 -0
  539. package/src/core/agents/claim-path.ts +23 -0
  540. package/src/core/agents/cli.ts +259 -53
  541. package/src/core/agents/coord-client.ts +92 -8
  542. package/src/core/agents/live-authority-v3.ts +342 -14
  543. package/src/core/agents/live-lifecycle-v3.ts +14 -4
  544. package/src/core/agents/live-observation-v3.ts +13 -3
  545. package/src/core/agents/render/prompt-context.ts +40 -16
  546. package/src/core/agents/rules/stop-hook.ts +129 -25
  547. package/src/core/agents/rules/verdict.ts +1 -0
  548. package/src/core/agents/session-events.ts +4 -0
  549. package/src/core/agents/session-finalization-state-v3.ts +1 -1
  550. package/src/core/agents/session-finalizer-v3.ts +4 -1
  551. package/src/core/agents/session-name-display.ts +9 -1
  552. package/src/core/agents/state/activity-log.ts +7 -2
  553. package/src/core/agents/state/heartbeat-reader.ts +69 -0
  554. package/src/core/agents/state/heartbeat-writer.ts +47 -84
  555. package/src/core/agents/state/live-coordination-view.ts +17 -81
  556. package/src/core/agents/state/live-coordination-writer.ts +141 -0
  557. package/src/core/agents/state/names.ts +190 -182
  558. package/src/core/agents/state/stale-sweep.ts +25 -5
  559. package/src/core/artifacts/constants.ts +4 -0
  560. package/src/core/artifacts/index.ts +113 -8
  561. package/src/core/config.ts +126 -5
  562. package/src/core/conversations/archive.ts +237 -0
  563. package/src/core/conversations/catalog.ts +79 -0
  564. package/src/core/conversations/context-pack.ts +72 -0
  565. package/src/core/conversations/contract.ts +178 -0
  566. package/src/core/conversations/evaluation.ts +30 -0
  567. package/src/core/conversations/index.ts +9 -0
  568. package/src/core/conversations/lifecycle.ts +63 -0
  569. package/src/core/conversations/normalize.ts +105 -0
  570. package/src/core/conversations/query.ts +318 -0
  571. package/src/core/conversations/search-index.ts +48 -0
  572. package/src/core/events/legacy-storage/canary.ts +177 -0
  573. package/src/core/events/legacy-storage/fence.ts +129 -0
  574. package/src/core/events/legacy-storage/index.ts +5 -0
  575. package/src/core/events/legacy-storage/inventory.ts +51 -0
  576. package/src/core/events/legacy-storage/manifest.ts +136 -0
  577. package/src/core/events/legacy-storage/reader.ts +178 -0
  578. package/src/core/events/v3/authority-outbox.ts +22 -1
  579. package/src/core/events/v3/base-contract.ts +56 -7
  580. package/src/core/events/v3/bootstrap.ts +195 -2
  581. package/src/core/events/v3/capabilities.ts +37 -3
  582. package/src/core/events/v3/control-witness.ts +339 -0
  583. package/src/core/events/v3/control-writer.ts +16 -0
  584. package/src/core/events/v3/control.ts +54 -18
  585. package/src/core/events/v3/coordination-transaction-recovery.ts +477 -0
  586. package/src/core/events/v3/coordination-view.ts +17 -1
  587. package/src/core/events/v3/generated.ts +1 -1
  588. package/src/core/events/v3/index.ts +26 -2
  589. package/src/core/events/v3/latency.ts +213 -2
  590. package/src/core/events/v3/live-route-observer.ts +80 -0
  591. package/src/core/events/v3/live-routing.ts +182 -45
  592. package/src/core/events/v3/producers/command-recorder.ts +10 -1
  593. package/src/core/events/v3/producers/coordination-recorder.ts +112 -15
  594. package/src/core/events/v3/producers/diagnostic-summaries.ts +593 -0
  595. package/src/core/events/v3/producers/hook-base.ts +116 -2
  596. package/src/core/events/v3/producers/intake.ts +48 -2
  597. package/src/core/events/v3/producers/producer-state-observer.ts +52 -0
  598. package/src/core/events/v3/producers/recorder.ts +1403 -30
  599. package/src/core/events/v3/reader.ts +243 -2
  600. package/src/core/events/v3/rotation-config.ts +98 -0
  601. package/src/core/events/v3/runtime-build.ts +41 -0
  602. package/src/core/events/v3/runtime-identity.ts +16 -0
  603. package/src/core/events/v3/runtime-telemetry-capabilities.ts +143 -0
  604. package/src/core/events/v3/support-storage/classify.ts +209 -0
  605. package/src/core/events/v3/support-storage/eligibility.ts +58 -0
  606. package/src/core/events/v3/support-storage/index.ts +9 -0
  607. package/src/core/events/v3/support-storage/inventory.ts +135 -0
  608. package/src/core/events/v3/support-storage/logical-authority.ts +117 -0
  609. package/src/core/events/v3/support-storage/maintenance.ts +501 -0
  610. package/src/core/events/v3/support-storage/pack-contract.ts +337 -0
  611. package/src/core/events/v3/support-storage/pack-reader.ts +346 -0
  612. package/src/core/events/v3/support-storage/pack-writer.ts +352 -0
  613. package/src/core/events/v3/support-storage/recovery.ts +43 -0
  614. package/src/core/events/v3/turn-telemetry.ts +66 -5
  615. package/src/core/events/v3/writer.ts +168 -16
  616. package/src/core/governor/plan-read.ts +5 -18
  617. package/src/core/governor/planning.ts +5 -8
  618. package/src/core/governor/service-read.ts +320 -2
  619. package/src/core/governor/service.ts +49 -22
  620. package/src/core/guard/evidence-v3.ts +1 -0
  621. package/src/core/hooks/adapter/events.ts +1 -0
  622. package/src/core/hooks/adapter/output.ts +12 -5
  623. package/src/core/hooks/adapter/parse.ts +16 -0
  624. package/src/core/hooks/adapter/runtime-telemetry.ts +1200 -0
  625. package/src/core/hooks/cli.ts +342 -64
  626. package/src/core/hooks/effects/image-capture.ts +4 -1
  627. package/src/core/hooks/remediation-cap.ts +83 -0
  628. package/src/core/hooks/resolve/coord-root.ts +91 -11
  629. package/src/core/hooks/resolve/owner.ts +4 -0
  630. package/src/core/hooks/resolve/transcript.ts +55 -11
  631. package/src/core/inbox/contract.ts +81 -0
  632. package/src/core/inbox/index.ts +4 -0
  633. package/src/core/inbox/lifecycle.ts +104 -0
  634. package/src/core/inbox/service.ts +305 -0
  635. package/src/core/inbox/watcher.ts +36 -0
  636. package/src/core/presence/codec-digest.ts +15 -9
  637. package/src/core/presence/relay-client.ts +55 -7
  638. package/src/core/semantic/adapters.ts +452 -0
  639. package/src/core/semantic/contract.ts +430 -0
  640. package/src/core/semantic/evidence.ts +336 -0
  641. package/src/core/semantic/index.ts +201 -0
  642. package/src/core/semantic/once.ts +496 -0
  643. package/src/core/semantic/prompt.ts +96 -0
  644. package/src/core/semantic/review.ts +803 -0
  645. package/src/core/semantic/routes.ts +30 -0
  646. package/src/core/semantic/scheduler.ts +151 -0
  647. package/src/core/semantic/service-status.ts +212 -0
  648. package/src/core/semantic/service.ts +681 -0
  649. package/src/core/semantic/soak.ts +638 -0
  650. package/src/core/semantic/storage.ts +300 -0
  651. package/src/core/semantic/usage.ts +272 -0
  652. package/src/core/semantic/validate.ts +243 -0
  653. package/src/core/storage/buffer.ts +124 -0
  654. package/src/core/storage/builtins.ts +916 -0
  655. package/src/core/storage/catalog.ts +467 -0
  656. package/src/core/storage/config.ts +423 -0
  657. package/src/core/storage/contract.ts +342 -0
  658. package/src/core/storage/durable-history.ts +738 -0
  659. package/src/core/storage/health.ts +76 -0
  660. package/src/core/storage/index.ts +15 -0
  661. package/src/core/storage/inventory.ts +859 -0
  662. package/src/core/storage/jsonl.ts +213 -0
  663. package/src/core/storage/log-retention.ts +555 -0
  664. package/src/core/storage/logger.ts +546 -0
  665. package/src/core/storage/maintenance-providers.ts +200 -0
  666. package/src/core/storage/maintenance.ts +929 -0
  667. package/src/core/storage/metrics.ts +199 -0
  668. package/src/core/storage/policy.ts +141 -0
  669. package/src/core/storage/process-log.ts +203 -0
  670. package/src/core/storage/query.ts +360 -0
  671. package/src/core/storage/receipts.ts +162 -0
  672. package/src/core/storage/segments.ts +721 -0
  673. package/src/core/storage/sink-graph.ts +343 -0
  674. package/src/core/storage/storm-control.ts +91 -0
  675. package/src/core/work/state.ts +12 -21
  676. package/src/core/workflow/live-session-v3.ts +2 -4
  677. package/src/index.ts +2 -0
  678. package/src/lazy-commands.ts +294 -0
  679. package/src/lib/browser/client.ts +21 -0
  680. package/src/lib/browser/index.ts +4 -0
  681. package/src/lib/browser/standalone-html.ts +312 -0
  682. package/src/lib/docs-lint.ts +58 -77
  683. package/src/lib/docs-metadata-audit.ts +26 -21
  684. package/src/lib/docs-metadata-sync.ts +32 -9
  685. package/src/lib/docs-repository-view.ts +220 -0
  686. package/src/lib/http/client.ts +44 -8
  687. package/src/lib/http/index.ts +6 -1
  688. package/src/lib/indexer-exclusions.ts +173 -0
  689. package/src/lib/instructions/apply.ts +1 -0
  690. package/src/lib/instructions/templates.ts +154 -4
  691. package/src/lib/local-file-url.ts +31 -0
  692. package/src/lib/web-performance.ts +335 -0
@@ -21,10 +21,9 @@ import {
21
21
  readdirSync,
22
22
  readFileSync,
23
23
  rmSync,
24
- statSync,
25
24
  writeFileSync,
26
25
  } from "node:fs";
27
- import { homedir, tmpdir } from "node:os";
26
+ import { tmpdir } from "node:os";
28
27
  import { basename, join, resolve } from "node:path";
29
28
  import type { Command } from "commander";
30
29
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
@@ -38,13 +37,18 @@ import {
38
37
  import {
39
38
  emitEventV3,
40
39
  monorepoRoot,
40
+ nativeSessionIdentity,
41
41
  normalizeAdapter,
42
42
  resolveOwner,
43
43
  resolveOwnerBySessionEnv,
44
44
  resolveOwnerWithSource,
45
45
  sessionIdentityFromEnv,
46
46
  } from "../core/agents/index.ts";
47
- import { reopenLiveCoordinationGenerationV3 } from "../core/agents/live-authority-v3.ts";
47
+ import {
48
+ bootstrapLiveCoordinationAuthorityV3,
49
+ LiveCoordinationAuthorityV3Error,
50
+ reopenLiveCoordinationGenerationV3,
51
+ } from "../core/agents/live-authority-v3.ts";
48
52
  import {
49
53
  listSessionFinalizationRequestsV3,
50
54
  observeHostDisappearedV3,
@@ -52,7 +56,11 @@ import {
52
56
  requestSessionEndExplicitV3,
53
57
  type SessionFinalizationRequestV3,
54
58
  } from "../core/agents/session-finalizer-v3.ts";
55
- import { SESSION_NAME_DISPLAY_NOTE } from "../core/agents/session-name-display.ts";
59
+ import {
60
+ SESSION_NAME_DISPLAY_NOTE,
61
+ sessionNameDisplayPending,
62
+ } from "../core/agents/session-name-display.ts";
63
+ import { agentDisplayName } from "../core/agents/state/activity-log.ts";
56
64
  import {
57
65
  buildLifecycleSuggestedName,
58
66
  buildSuggestedName,
@@ -77,6 +85,10 @@ import type { EventTypeV3 } from "../core/events/v3/contract.ts";
77
85
  import { readEventV3ControlState } from "../core/events/v3/control.ts";
78
86
  import { projectCoordinationViewV3 } from "../core/events/v3/coordination-view.ts";
79
87
  import { liveInstanceIdV3 } from "../core/events/v3/live-routing.ts";
88
+ import {
89
+ countSummarizedSinceV3,
90
+ listDiagnosticSummariesV3,
91
+ } from "../core/events/v3/producers/diagnostic-summaries.ts";
80
92
  import {
81
93
  listHookIntakeGroupsV3,
82
94
  listHookIntakeRecordsV3,
@@ -89,6 +101,10 @@ import { readLedgerV3 } from "../core/events/v3/reader.ts";
89
101
  import { EVENT_V3_LEDGER_RELATIVE_ROOT } from "../core/events/v3/writer.ts";
90
102
  import type { RunQualitySnapshot, RunQualityStatus } from "../core/guard/index.ts";
91
103
  import { evaluateRunQualityIfDue } from "../core/guard/index.ts";
104
+ import {
105
+ readRuntimeContextTelemetry,
106
+ readRuntimeContextUsage,
107
+ } from "../core/hooks/adapter/runtime-telemetry.ts";
92
108
  import { type RemoteMachine, readRemoteMachines } from "../core/presence/index.ts";
93
109
  import { registerContextCommand } from "./context.ts";
94
110
 
@@ -152,9 +168,8 @@ function freshnessCutoffSecs(): number {
152
168
  }
153
169
 
154
170
  const SUBAGENT_NOTE =
155
- "Bash identity is process-level; if you're running inside a subagent, " +
156
- "this resolves to the parent group's name, not the subagent's. A subagent-aware " +
157
- "bridge (per-shell marker file at .harnery/shells/<pid>) is out of scope.";
171
+ "Native adapter subagents resolve to their own child generation after the hook bridge " +
172
+ "observes delegation. An unbridged subprocess shell still resolves through its parent.";
158
173
 
159
174
  /**
160
175
  * Spawn options for agent-coord child processes: pin the coord root the
@@ -248,19 +263,7 @@ function taskStateOf(hb: Pick<Heartbeat, "task_state">): TaskState {
248
263
  return hb.task_state ?? "active";
249
264
  }
250
265
 
251
- /** Producer joins use the private native session ID; `session_id` is the canonical V3 fingerprint. */
252
- export function nativeSessionIdentity(
253
- row: Pick<Heartbeat, "native_session_id" | "session_id"> | null | undefined,
254
- fallback: string,
255
- ): string {
256
- if (row?.native_session_id) return row.native_session_id;
257
- // A projection-only row carries the privacy-safe canonical session
258
- // fingerprint, not the adapter's native session ID needed to join the hook
259
- // producer. Until a disposable cache exists, the resolved owner is the only
260
- // native identity available to command surfaces such as `agents status`.
261
- if (row?.session_id && !/^sid_[a-f0-9]{64}$/.test(row.session_id)) return row.session_id;
262
- return fallback;
263
- }
266
+ export { nativeSessionIdentity };
264
267
 
265
268
  function lifecycleLabel(hb: Pick<Heartbeat, "task_state" | "task_state_reason">): string {
266
269
  const state = taskStateOf(hb);
@@ -273,6 +276,7 @@ export function registerAgentsCommand(
273
276
  program: Command,
274
277
  emitParam: EmitContext,
275
278
  programContext?: HarneryProgramContext,
279
+ binName = resolveBinName(),
276
280
  ): void {
277
281
  emit = emitParam;
278
282
  const cmd = program
@@ -329,9 +333,9 @@ export function registerAgentsCommand(
329
333
  .command("suggest-name [description...]")
330
334
  .description(
331
335
  'Reprint a session name ("Agent <you> - <description>") for the operator to set as their ' +
332
- "adapter tab title. With no arg, derives it from your current task. The primary naming path " +
336
+ "adapter tab title. With no arg, prefers the exact pending name, then derives from your current task. The primary naming path " +
333
337
  "is set-task (it suggests a name on the first focus declaration); reach for this to reprint " +
334
- "or re-suggest after a topic pivot. Read-only.",
338
+ "or to create an explicit pending-display retry with --json. Read-only.",
335
339
  )
336
340
  .option("--json", "JSON output instead of the bare name")
337
341
  .option(
@@ -394,7 +398,7 @@ export function registerAgentsCommand(
394
398
  )
395
399
  .option(
396
400
  "--session-id <id>",
397
- `Set the task on the heartbeat with this session_id directly, bypassing the ppid walk. Mirror of \`status --session-id\`: use it when the ppid walk can't resolve self (e.g. Cursor, whose shell tool calls don't descend from a pid-map-registered anchor). Discover the id via \`${resolveBinName()} agents list --json\`.`,
401
+ `Set the task on the heartbeat with this session_id directly, bypassing the ppid walk. Mirror of \`status --session-id\`: use it when the ppid walk can't resolve self (e.g. Cursor, whose shell tool calls don't descend from a pid-map-registered anchor). Discover the id via \`${binName} agents list --json\`.`,
398
402
  )
399
403
  .action((text: string[], opts: { sessionId?: string }) => {
400
404
  runSetTask(text.join(" "), opts);
@@ -493,7 +497,7 @@ export function registerAgentsCommand(
493
497
  cmd
494
498
  .command("wait <name>")
495
499
  .description(
496
- `Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${resolveBinName()} agents ping\` to coordinate hand-offs.`,
500
+ `Block until a peer agent releases files (their \`files_touched\` becomes empty, OR they exit). Pair with \`${binName} agents ping\` to coordinate hand-offs.`,
497
501
  )
498
502
  .option(
499
503
  "--file <path>",
@@ -576,12 +580,11 @@ export function registerAgentsCommand(
576
580
  cmd
577
581
  .command("heal")
578
582
  .description(
579
- "Force a coord-layer recovery action on a specific agent. " +
580
- "Kinds: pidmap (repair process attribution) and cache (rebuild the " +
581
- "disposable V3 coordination cache from the authoritative ledger).",
583
+ "Repair the current session's disposable coordination cache from V3. " +
584
+ "Use explicit options to target another generation or repair process attribution.",
582
585
  )
583
- .requiredOption("--owner <id>", "Target agent's instance_id")
584
- .requiredOption("--kind <kind>", "pidmap | cache")
586
+ .option("--owner <id>", "Target agent's instance_id. Defaults to the current session.")
587
+ .option("--kind <kind>", "pidmap | cache. Defaults to cache.")
585
588
  .option(
586
589
  "--session-id <id>",
587
590
  "(--kind cache) native session id used to join the authoritative V3 generation.",
@@ -598,14 +601,26 @@ export function registerAgentsCommand(
598
601
  "when calling from outside Claude Code's Bash tool tree (e.g. " +
599
602
  "from cron or an external script).",
600
603
  )
604
+ .option(
605
+ "--quarantine-transaction <id>",
606
+ "Quarantine one irreconcilable prepared authority transaction after proving its event was never committed.",
607
+ )
608
+ .option(
609
+ "--approval-record-id <id>",
610
+ "Durable identifier for the operator approval authorizing transaction quarantine.",
611
+ )
612
+ .option("--yes", "Confirm the exact prepared transaction may be quarantined.")
601
613
  .option("--json", "JSON envelope output")
602
614
  .action(
603
615
  (opts: {
604
- owner: string;
605
- kind: string;
616
+ owner?: string;
617
+ kind?: string;
606
618
  sessionId?: string;
607
619
  adapter?: string;
608
620
  pid?: string;
621
+ quarantineTransaction?: string;
622
+ approvalRecordId?: string;
623
+ yes?: boolean;
609
624
  json?: boolean;
610
625
  }) => {
611
626
  runHeal(opts);
@@ -944,14 +959,15 @@ function runIdentityAssume(
944
959
  });
945
960
  process.exit(1);
946
961
  }
947
- if (!opts.sessionId) ensureCursorSession(root);
962
+ if (!opts.sessionId) ensureAdapterSession(root);
948
963
  const owner = opts.sessionId ?? resolveOwner();
949
964
  if (!owner) {
950
- emit.error({
951
- code: "no_pidmap_entry",
952
- message:
965
+ emit.error(
966
+ sessionResolutionFailure(
967
+ root,
953
968
  "not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
954
- });
969
+ ),
970
+ );
955
971
  process.exit(1);
956
972
  }
957
973
  try {
@@ -1223,14 +1239,13 @@ function runWhoami(opts: { json?: boolean }): void {
1223
1239
  process.exit(1);
1224
1240
  }
1225
1241
 
1226
- ensureCursorSession(root);
1242
+ ensureAdapterSession(root);
1227
1243
  const resolved = resolveOwnerWithSource();
1228
1244
  const myOwner = resolved.owner;
1229
1245
  if (!myOwner) {
1230
- emit.error({
1231
- code: "no_pidmap_entry",
1232
- message: "not in an agent session; ppid walk found no pid-map entry",
1233
- });
1246
+ emit.error(
1247
+ sessionResolutionFailure(root, "not in an agent session; ppid walk found no pid-map entry"),
1248
+ );
1234
1249
  process.exit(1);
1235
1250
  }
1236
1251
 
@@ -1712,49 +1727,96 @@ function cursorEnvSessionId(): string | null {
1712
1727
  return raw.startsWith("bc-") && raw.length > 3 ? raw.slice(3) : raw;
1713
1728
  }
1714
1729
 
1715
- function shouldBootstrapCursorSession(): boolean {
1716
- return process.env.CURSOR_AGENT === "1" && cursorEnvSessionId() !== null;
1730
+ interface CommandSessionBootstrap {
1731
+ adapter: "claude-code" | "cursor" | "codex";
1732
+ sessionId: string;
1717
1733
  }
1718
1734
 
1719
- function ensureCursorSession(root: string): void {
1720
- if (!shouldBootstrapCursorSession()) return;
1735
+ function commandSessionBootstrap(): CommandSessionBootstrap | null {
1736
+ const cursorSessionId = cursorEnvSessionId();
1737
+ if (process.env.CURSOR_AGENT === "1" && cursorSessionId) {
1738
+ return { adapter: "cursor", sessionId: cursorSessionId };
1739
+ }
1740
+ if (process.env.HARNERY_AGENT_COORD_BRIDGE?.trim() === "codex-wsl") {
1741
+ const sessionId = sessionIdentityFromEnv();
1742
+ if (sessionId) return { adapter: "codex", sessionId };
1743
+ }
1744
+ const claudeSessionId = process.env.CLAUDE_CODE_SESSION_ID?.trim();
1745
+ if (claudeSessionId) {
1746
+ return { adapter: "claude-code", sessionId: claudeSessionId };
1747
+ }
1748
+ return null;
1749
+ }
1750
+
1751
+ function ensureAdapterSession(root: string): void {
1752
+ const bootstrap = commandSessionBootstrap();
1753
+ if (!bootstrap) return;
1754
+ // Claude Code owns a reliable native SessionStart hook. Its environment is
1755
+ // accepted by the explicit `agents heal` recovery path, but ordinary
1756
+ // identity and status commands must not impersonate that hook. The heal
1757
+ // bootstrap records derived provenance instead.
1758
+ if (bootstrap.adapter === "claude-code") return;
1721
1759
  // Explicit owner override means the caller already knows who they are.
1722
1760
  // Bootstrapping sessionStart inherits that env into the child hook, which
1723
1761
  // re-projects a fresh heartbeat onto the override owner and can wipe an
1724
1762
  // assumed persona agent_id (seen under Cursor integration fixtures).
1725
- if (process.env.HARNERY_AGENT_COORD_OWNER?.trim()) return;
1763
+ if (bootstrap.adapter === "cursor" && process.env.HARNERY_AGENT_COORD_OWNER?.trim()) return;
1726
1764
  if (resolveOwnerBySessionEnv(root)) return;
1727
1765
 
1728
- const sessionId = cursorEnvSessionId();
1729
- if (!sessionId) return;
1730
1766
  const agentHook = coordBinPath("agent-hook", root);
1731
1767
  if (!agentHook) return;
1732
1768
 
1733
1769
  const payload = JSON.stringify({
1734
- conversation_id: sessionId,
1735
- session_id: sessionId,
1770
+ ...(bootstrap.adapter === "cursor" ? { conversation_id: bootstrap.sessionId } : {}),
1771
+ session_id: bootstrap.sessionId,
1736
1772
  hook_event_name: "sessionStart",
1737
1773
  workspace_roots: [root],
1738
1774
  cwd: root,
1739
- composer_mode: "agent",
1740
- is_background_agent: false,
1775
+ ...(bootstrap.adapter === "cursor"
1776
+ ? { composer_mode: "agent", is_background_agent: false }
1777
+ : { source: "resume" }),
1741
1778
  });
1742
1779
 
1743
- spawnSync("bash", [agentHook, "session-start", "--adapter", "cursor"], {
1780
+ spawnSync("bash", [agentHook, "session-start", "--adapter", bootstrap.adapter], {
1744
1781
  input: payload,
1745
1782
  cwd: root,
1746
1783
  encoding: "utf8",
1747
1784
  timeout: 3000,
1748
1785
  env: {
1749
1786
  ...process.env,
1750
- HARNERY_AGENT_COORD_PLATFORM: "cursor",
1787
+ HARNERY_AGENT_COORD_PLATFORM: bootstrap.adapter,
1751
1788
  HARNERY_COORD_ROOT_OVERRIDE: root,
1752
- CURSOR_SESSION_ID: sessionId,
1753
- CURSOR_CONVERSATION_ID: sessionId,
1789
+ HARNERY_AGENT_COORD_SESSION_ID: bootstrap.sessionId,
1790
+ ...(bootstrap.adapter === "cursor"
1791
+ ? {
1792
+ CURSOR_SESSION_ID: bootstrap.sessionId,
1793
+ CURSOR_CONVERSATION_ID: bootstrap.sessionId,
1794
+ }
1795
+ : { CODEX_THREAD_ID: bootstrap.sessionId }),
1754
1796
  },
1755
1797
  });
1756
1798
  }
1757
1799
 
1800
+ function sessionResolutionFailure(
1801
+ root: string,
1802
+ fallbackMessage: string,
1803
+ ): { code: string; message: string } {
1804
+ if (!sessionIdentityFromEnv()) return { code: "no_pidmap_entry", message: fallbackMessage };
1805
+ const control = readEventV3ControlState(root);
1806
+ if (control.state !== "candidate" && control.state !== "active") {
1807
+ const detail = `${control.state}:${control.reason}`;
1808
+ return {
1809
+ code: "event_v3_control_unavailable",
1810
+ message: `the adapter session identity is present, but the V3 control state cannot accept session onboarding (${detail})`,
1811
+ };
1812
+ }
1813
+ return {
1814
+ code: "session_generation_unavailable",
1815
+ message:
1816
+ "the adapter session identity is present, but no current V3 session generation was available after onboarding",
1817
+ };
1818
+ }
1819
+
1758
1820
  function runReleaseClaim(path: string): void {
1759
1821
  const root = monorepoRoot();
1760
1822
  if (!root) {
@@ -1766,10 +1828,9 @@ function runReleaseClaim(path: string): void {
1766
1828
  }
1767
1829
  const myOwner = resolveOwner();
1768
1830
  if (!myOwner) {
1769
- emit.error({
1770
- code: "no_pidmap_entry",
1771
- message: "not in an agent session; ppid walk found no pid-map entry",
1772
- });
1831
+ emit.error(
1832
+ sessionResolutionFailure(root, "not in an agent session; ppid walk found no pid-map entry"),
1833
+ );
1773
1834
  process.exit(1);
1774
1835
  }
1775
1836
  // Canonicalize: absolute paths under coordRoot get the prefix stripped;
@@ -1810,14 +1871,15 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
1810
1871
  // returns null by design, and erroring here orphans the session's first
1811
1872
  // ritual command. The env id carries the same trust as an explicit
1812
1873
  // --session-id, and set-task mints the heartbeat exactly as that path does.
1813
- if (!opts?.sessionId) ensureCursorSession(root);
1874
+ if (!opts?.sessionId) ensureAdapterSession(root);
1814
1875
  const myOwner = opts?.sessionId ?? resolveOwner() ?? sessionIdentityFromEnv();
1815
1876
  if (!myOwner) {
1816
- emit.error({
1817
- code: "no_pidmap_entry",
1818
- message:
1877
+ emit.error(
1878
+ sessionResolutionFailure(
1879
+ root,
1819
1880
  "not in an agent session; no session-id environment identity and the ppid walk found no pid-map entry (pass --session-id to bypass)",
1820
- });
1881
+ ),
1882
+ );
1821
1883
  process.exit(1);
1822
1884
  }
1823
1885
 
@@ -1854,27 +1916,18 @@ function runSetTask(task: string, opts?: { sessionId?: string }): void {
1854
1916
  // the first time. `first_of_session: true` means exactly "this call produced
1855
1917
  // the session name" — it is never true with a null name (an empty first
1856
1918
  // declaration or a `--session-id` relay both keep the window open instead).
1857
- const firstSuggestedName =
1858
- !priorHb?.suggested_session_name && hb?.suggested_session_name
1859
- ? hb.suggested_session_name
1860
- : null;
1861
-
1862
- // A readable but misclassified first display would otherwise leave the
1863
- // session permanently latched: set-task is allowed through the gate, but a
1864
- // normal repeat used to return no name and therefore could not establish a
1865
- // fresh ordered transcript boundary. Re-emit the unchanged pending name as
1866
- // an explicit retry. This does not rename the session or claim that the
1867
- // display was seen; it only gives the next exact block new mint evidence.
1868
- const retriedSuggestedName =
1869
- !firstSuggestedName &&
1870
- priorHb?.suggested_session_name &&
1871
- priorHb.session_name_seen_for !== priorHb.suggested_session_name &&
1872
- hb?.suggested_session_name === priorHb.suggested_session_name
1873
- ? hb.suggested_session_name
1874
- : null;
1875
- const suggestedName = firstSuggestedName ?? retriedSuggestedName;
1919
+ // A native generation restart may rebuild the current cache during the
1920
+ // mutation and carry an already-seen suggestion from the prior generation.
1921
+ // That title is continuity evidence, not a new display request.
1922
+ const pendingName = sessionNameDisplayPending(hb);
1923
+ const firstSuggestedName = !priorHb?.suggested_session_name && pendingName ? pendingName : null;
1924
+
1925
+ // Routine focus declarations stay title-silent. A pending display no longer
1926
+ // turns every repeated set-task into a fresh name instruction; explicit
1927
+ // recovery belongs to `agents suggest-name --json`.
1928
+ const suggestedName = firstSuggestedName;
1876
1929
  const firstOfSession = firstSuggestedName !== null;
1877
- const sessionNameRetry = retriedSuggestedName !== null;
1930
+ const sessionNameRetry = false;
1878
1931
 
1879
1932
  emit.data({
1880
1933
  instance_id: myOwner,
@@ -1915,14 +1968,15 @@ function runLifecycle(rawState: string, opts: { reason?: string; sessionId?: str
1915
1968
  process.exitCode = 1;
1916
1969
  return;
1917
1970
  }
1918
- if (!opts.sessionId) ensureCursorSession(root);
1971
+ if (!opts.sessionId) ensureAdapterSession(root);
1919
1972
  const myOwner = opts.sessionId ?? resolveOwner() ?? sessionIdentityFromEnv();
1920
1973
  if (!myOwner) {
1921
- emit.error({
1922
- code: "no_pidmap_entry",
1923
- message:
1974
+ emit.error(
1975
+ sessionResolutionFailure(
1976
+ root,
1924
1977
  "not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
1925
- });
1978
+ ),
1979
+ );
1926
1980
  process.exitCode = 1;
1927
1981
  return;
1928
1982
  }
@@ -2097,25 +2151,30 @@ function runSuggestName(
2097
2151
 
2098
2152
  // Identity: prefer explicit --session-id (ppid-walk-free escape hatch, mirrors
2099
2153
  // `status`/`set-task`), fall back to the ppid walk for interactive shell use.
2100
- if (!opts.sessionId) ensureCursorSession(root);
2154
+ if (!opts.sessionId) ensureAdapterSession(root);
2101
2155
  const myOwner = opts.sessionId ?? resolveOwner();
2102
2156
  if (!myOwner) {
2103
- emit.error({
2104
- code: "no_pidmap_entry",
2105
- message:
2157
+ emit.error(
2158
+ sessionResolutionFailure(
2159
+ root,
2106
2160
  "not in an agent session; ppid walk found no pid-map entry (pass --session-id to bypass)",
2107
- });
2161
+ ),
2162
+ );
2108
2163
  process.exit(1);
2109
2164
  }
2110
2165
 
2111
2166
  const hb = readCurrentCoordinationRow(myOwner);
2112
2167
  const agentName = hb?.name || "unknown";
2113
- // Description resolution: an explicit arg wins; with no arg, fall back to the
2114
- // agent's current declared task, so a bare `suggest-name` reprints the running
2115
- // session's name (handy after a topic pivot). Read-only — mutates no state;
2116
- // the primary naming path is set-task's first-of-session suggestion.
2168
+ // A bare suggest-name must prefer the exact pending suggestion over rebuilding
2169
+ // from the current task. The task may have changed since the title was minted;
2170
+ // reconstructing it would produce a block the display latch can never accept.
2171
+ // An explicit description keeps the read-only re-suggest behavior.
2172
+ const pendingName = sessionNameDisplayPending(hb);
2117
2173
  const parts = descriptionParts.length > 0 ? descriptionParts : hb?.task ? [hb.task] : [];
2118
- const built = buildSuggestedName(agentName, parts);
2174
+ const built =
2175
+ descriptionParts.length === 0 && pendingName
2176
+ ? { suggestedName: pendingName, description: hb?.task ?? pendingName }
2177
+ : buildSuggestedName(agentName, parts);
2119
2178
  if (!built) {
2120
2179
  emit.error({
2121
2180
  code: "no_description",
@@ -2132,6 +2191,7 @@ function runSuggestName(
2132
2191
  emit.data({
2133
2192
  name: displayName,
2134
2193
  suggested_session_name: suggestedName,
2194
+ session_name_retry: pendingName === suggestedName,
2135
2195
  agent_name: agentName,
2136
2196
  description,
2137
2197
  });
@@ -2171,14 +2231,15 @@ function runStatus(opts: {
2171
2231
 
2172
2232
  // Identity resolution: prefer explicit --session-id (hook-friendly), fall
2173
2233
  // back to ppid walk for interactive shell usage.
2174
- if (!opts.sessionId) ensureCursorSession(root);
2234
+ if (!opts.sessionId) ensureAdapterSession(root);
2175
2235
  const myOwner = opts.sessionId ?? resolveOwner();
2176
2236
  if (!myOwner) {
2177
- emit.error({
2178
- code: "no_pidmap_entry",
2179
- message:
2237
+ emit.error(
2238
+ sessionResolutionFailure(
2239
+ root,
2180
2240
  "not in an agent session; ppid walk found no pid-map entry (pass --session-id from a hook payload)",
2181
- });
2241
+ ),
2242
+ );
2182
2243
  process.exit(1);
2183
2244
  }
2184
2245
 
@@ -2220,7 +2281,7 @@ function runStatus(opts: {
2220
2281
  }
2221
2282
  }
2222
2283
 
2223
- emitEventV3({
2284
+ const statusEmitted = emitEventV3({
2224
2285
  owner: myOwner,
2225
2286
  session: nativeSessionIdentity(hb, myOwner),
2226
2287
  adapter: normalizeAdapter(hb.platform),
@@ -2229,6 +2290,16 @@ function runStatus(opts: {
2229
2290
  status: opts.endTurn ? "end_turn_checked" : opts.json ? "json_checked" : "box_checked",
2230
2291
  },
2231
2292
  });
2293
+ // The closing box is a success artifact. Never print it when the exact
2294
+ // evidence enforced by the Stop hook did not reach the ledger.
2295
+ if (opts.endTurn && !statusEmitted) {
2296
+ emit.error({
2297
+ code: "status_event_failed",
2298
+ message: `the end-of-turn status event could not be recorded; no status box was issued; rerun \`${resolveBinName(root)} agents status --end-turn\``,
2299
+ });
2300
+ process.exitCode = 1;
2301
+ return;
2302
+ }
2232
2303
 
2233
2304
  let sessionEnd:
2234
2305
  | { state: "queued" | "already_requested"; request_id: string }
@@ -2298,10 +2369,7 @@ function runStatus(opts: {
2298
2369
  if (!ctxUsage) {
2299
2370
  ctxStr = "unavailable";
2300
2371
  } else if (ctxUsage.percentOnly) {
2301
- // Cursor reports percent only; absolutes are estimates against a hard-coded
2302
- // window. Show the percent + window estimate, marked as estimated.
2303
- const pct = Math.round((ctxUsage.used / ctxUsage.window) * 100);
2304
- ctxStr = `~${pct}% (Cursor; ${fmtTokens(ctxUsage.window)} window est.)`;
2372
+ ctxStr = `${Math.round(ctxUsage.usedPercent)}% (Cursor first-party)`;
2305
2373
  } else {
2306
2374
  ctxStr = `${fmtTokens(ctxUsage.used)} / ${fmtTokens(ctxUsage.window)} (${Math.round(
2307
2375
  (ctxUsage.used / ctxUsage.window) * 100,
@@ -2309,7 +2377,7 @@ function runStatus(opts: {
2309
2377
  }
2310
2378
 
2311
2379
  const timeStr = formatLocalTime(new Date());
2312
- const displayName = `agent-${hb.name || "unknown"}`;
2380
+ const displayName = agentDisplayName(hb.instance_id, hb.name);
2313
2381
 
2314
2382
  // Council pending: list of council IDs where this agent is a member of an
2315
2383
  // active council in `open` round_status without a contribution to that round.
@@ -2353,8 +2421,8 @@ function runStatus(opts: {
2353
2421
  })),
2354
2422
  })),
2355
2423
  pending_councils: pendingCouncils,
2356
- context_used: ctxUsage?.used ?? null,
2357
- context_window: ctxUsage?.window ?? null,
2424
+ context_used: ctxUsage && "used" in ctxUsage ? ctxUsage.used : null,
2425
+ context_window: ctxUsage && "window" in ctxUsage ? ctxUsage.window : null,
2358
2426
  timestamp_iso: new Date().toISOString(),
2359
2427
  timestamp_local: timeStr,
2360
2428
  ...(quality ? { quality } : {}),
@@ -2498,15 +2566,17 @@ export function collectStatusPeerHealth(
2498
2566
  root: string,
2499
2567
  myOwner: string,
2500
2568
  nowMs = Date.now(),
2569
+ readRows: (root: string) => StatusPeerHeartbeat[] = readLiveCoordinationRows,
2570
+ freshnessSeconds = freshnessCutoffSecs(),
2501
2571
  ): { livePeers: StatusPeerHeartbeat[]; stale: number } {
2502
2572
  let rows: StatusPeerHeartbeat[] = [];
2503
2573
  try {
2504
- rows = readLiveCoordinationRows(root);
2574
+ rows = readRows(root);
2505
2575
  } catch {
2506
2576
  // V3 authority failures must not resurrect disposable cache rows.
2507
2577
  }
2508
2578
 
2509
- const cutoffMs = nowMs - freshnessCutoffSecs() * 1000;
2579
+ const cutoffMs = nowMs - freshnessSeconds * 1000;
2510
2580
  const livePeers: StatusPeerHeartbeat[] = [];
2511
2581
  let stale = 0;
2512
2582
  for (const peer of rows) {
@@ -2528,205 +2598,28 @@ function fmtTokens(n: number): string {
2528
2598
  function readContextUsage(
2529
2599
  sessionId: string,
2530
2600
  platform?: string | null,
2531
- ): { used: number; window: number; percentOnly?: boolean } | null {
2601
+ ):
2602
+ | { used: number; window: number; percentOnly?: false }
2603
+ | { usedPercent: number; percentOnly: true }
2604
+ | null {
2532
2605
  if (!sessionId) return null;
2533
2606
  // Dispatch by platform: Codex's JSONL shape is different from Claude Code's
2534
2607
  // (event_msg/response_item vs user/assistant), and Codex transcripts live
2535
2608
  // under ~/.codex/sessions/YYYY/MM/DD/ rather than ~/.claude/projects/.
2536
- // Cursor doesn't surface token counts in its transcript JSONL but DOES
2537
- // store `contextUsagePercent` per composer in workspaceStorage's state.vscdb;
2538
- // readCursorContextUsage reads that via bun:sqlite (percent-only).
2539
- if (platform === "codex") return readCodexContextUsage(sessionId);
2540
- if (platform === "cursor") return readCursorContextUsage(sessionId);
2541
- return readClaudeContextUsage(sessionId);
2542
- }
2543
-
2544
- function readCursorContextUsage(
2545
- sessionId: string,
2546
- ): { used: number; window: number; percentOnly: true } | null {
2547
- // Cursor stores per-chat (composer) context usage as a percent in each
2548
- // workspace's state.vscdb at ItemTable.composer.composerData. The value
2549
- // is a JSON blob with .allComposers[].contextUsagePercent. We find the
2550
- // composer matching the agent's session_id (Cursor uses session_id == composerId
2551
- // for parent chats).
2552
- //
2553
- // Roots searched: ~/.config/Cursor/User/workspaceStorage (Linux native install)
2554
- // and /mnt/c/Users/*/AppData/Roaming/Cursor/User/workspaceStorage (WSL).
2555
- // We don't filter by workspace path; we scan every workspace's state.vscdb
2556
- // looking for a composerId match. Cheap enough at status time (~60K per file,
2557
- // <10ms typical). When a match is found, returns synthetic { used, window }
2558
- // pair where the percent is what's real; absolutes are derived as
2559
- // (window * percent / 100). Window hard-coded to a Cursor-typical 200K.
2560
- const roots: string[] = [];
2561
- const linuxRoot = resolve(homedir(), ".config", "Cursor", "User", "workspaceStorage");
2562
- if (existsSync(linuxRoot)) roots.push(linuxRoot);
2563
- try {
2564
- if (existsSync("/mnt/c/Users")) {
2565
- for (const u of readdirSync("/mnt/c/Users")) {
2566
- const p = `/mnt/c/Users/${u}/AppData/Roaming/Cursor/User/workspaceStorage`;
2567
- if (existsSync(p)) roots.push(p);
2568
- }
2569
- }
2570
- } catch {
2571
- // ignore
2572
- }
2573
- for (const root of roots) {
2574
- let workspaces: string[];
2575
- try {
2576
- workspaces = readdirSync(root);
2577
- } catch {
2578
- continue;
2579
- }
2580
- for (const ws of workspaces) {
2581
- const dbPath = `${root}/${ws}/state.vscdb`;
2582
- if (!existsSync(dbPath)) continue;
2583
- try {
2584
- // Lazy-import bun:sqlite so non-Bun runtimes (if any) don't choke at load.
2585
- // eslint-disable-next-line @typescript-eslint/no-require-imports
2586
- const { Database } = require("bun:sqlite");
2587
- const db = new Database(dbPath, { readonly: true });
2588
- const row = db
2589
- .query("SELECT value FROM ItemTable WHERE key = 'composer.composerData'")
2590
- .get() as { value: string | Uint8Array } | null;
2591
- db.close();
2592
- if (!row?.value) continue;
2593
- const text =
2594
- typeof row.value === "string" ? row.value : Buffer.from(row.value).toString("utf8");
2595
- const parsed = JSON.parse(text);
2596
- const composers = Array.isArray(parsed?.allComposers) ? parsed.allComposers : [];
2597
- for (const c of composers) {
2598
- if (c?.composerId === sessionId && typeof c?.contextUsagePercent === "number") {
2599
- const window = 200000; // hard-coded Cursor-typical window; refine when Cursor exposes the actual ceiling
2600
- const used = Math.round((window * c.contextUsagePercent) / 100);
2601
- return { used, window, percentOnly: true };
2602
- }
2603
- }
2604
- } catch {
2605
- // ignore: DB locked, schema drift, etc. Move on to the next workspace.
2606
- }
2607
- }
2608
- }
2609
- return null;
2610
- }
2611
-
2612
- function readClaudeContextUsage(sessionId: string): { used: number; window: number } | null {
2613
- const root = monorepoRoot();
2614
- if (!root) return null;
2615
- // Claude Code's project dir scheme: prepend "-", replace "/" → "-".
2616
- const encoded = `-${root.replace(/^\//, "").replace(/\//g, "-")}`;
2617
- const transcriptPath = resolve(homedir(), ".claude", "projects", encoded, `${sessionId}.jsonl`);
2618
- if (!existsSync(transcriptPath)) return null;
2619
- let raw: string;
2620
- try {
2621
- raw = readFileSync(transcriptPath, "utf8");
2622
- } catch {
2623
- return null;
2624
- }
2625
- const lines = raw.split("\n");
2626
- for (let i = lines.length - 1; i >= 0; i--) {
2627
- const line = lines[i].trim();
2628
- if (!line) continue;
2629
- try {
2630
- const entry = JSON.parse(line);
2631
- const usage = entry?.message?.usage;
2632
- if (entry?.type === "assistant" && usage) {
2633
- const used =
2634
- (usage.input_tokens ?? 0) +
2635
- (usage.cache_creation_input_tokens ?? 0) +
2636
- (usage.cache_read_input_tokens ?? 0);
2637
- // Window: hardcode 1M for Opus 4.7 1M (current default). Refine to
2638
- // model-aware lookup when other models become routine.
2639
- return { used, window: 1000000 };
2640
- }
2641
- } catch {
2642
- // skip malformed line
2643
- }
2644
- }
2645
- return null;
2646
- }
2647
-
2648
- function readCodexContextUsage(sessionId: string): { used: number; window: number } | null {
2649
- // Codex transcripts: ~/.codex/sessions/YYYY/MM/DD/rollout-<TS>-<sessionId>.jsonl
2650
- // On WSL the active install often lives on the Windows side under
2651
- // /mnt/c/Users/<user>/.codex/sessions/; both are searched.
2652
- const path = findCodexTranscript(sessionId);
2653
- if (!path) return null;
2654
- let raw: string;
2655
- try {
2656
- raw = readFileSync(path, "utf8");
2657
- } catch {
2658
- return null;
2659
- }
2660
- const lines = raw.split("\n");
2661
- // Walk backwards: most recent token_count event wins.
2662
- for (let i = lines.length - 1; i >= 0; i--) {
2663
- const line = lines[i].trim();
2664
- if (!line) continue;
2665
- try {
2666
- const entry = JSON.parse(line);
2667
- if (entry?.type === "event_msg" && entry?.payload?.type === "token_count") {
2668
- const info = entry.payload.info;
2669
- const used = info?.last_token_usage?.input_tokens;
2670
- const window = info?.model_context_window;
2671
- if (typeof used === "number" && typeof window === "number" && window > 0) {
2672
- return { used, window };
2673
- }
2674
- }
2675
- } catch {
2676
- // skip malformed line
2677
- }
2678
- }
2679
- return null;
2680
- }
2681
-
2682
- function findCodexTranscript(sessionId: string): string | null {
2683
- // Candidate roots in priority order.
2684
- const homeRoot = resolve(homedir(), ".codex", "sessions");
2685
- const wslRoots: string[] = [];
2686
- try {
2687
- if (existsSync("/mnt/c/Users")) {
2688
- for (const entry of readdirSync("/mnt/c/Users")) {
2689
- wslRoots.push(`/mnt/c/Users/${entry}/.codex/sessions`);
2690
- }
2691
- }
2692
- } catch {
2693
- // ignore
2694
- }
2695
- const roots = [homeRoot, ...wslRoots];
2696
- const suffix = `-${sessionId}.jsonl`;
2697
- for (const root of roots) {
2698
- if (!existsSync(root)) continue;
2699
- try {
2700
- // Recursive scan: sessions are partitioned by YYYY/MM/DD/, so depth
2701
- // is bounded at 3 + one file per session. Cheap enough at status time.
2702
- const stack: string[] = [root];
2703
- while (stack.length) {
2704
- const dir = stack.pop()!;
2705
- let entries: string[];
2706
- try {
2707
- entries = readdirSync(dir);
2708
- } catch {
2709
- continue;
2710
- }
2711
- for (const name of entries) {
2712
- const full = `${dir}/${name}`;
2713
- try {
2714
- const stat = statSync(full);
2715
- if (stat.isDirectory()) {
2716
- stack.push(full);
2717
- } else if (name.endsWith(suffix)) {
2718
- return full;
2719
- }
2720
- } catch {
2721
- // ignore
2722
- }
2723
- }
2724
- }
2725
- } catch {
2726
- // ignore root scan failures
2727
- }
2609
+ // Cursor stores a first-party percentage in workspaceStorage. The shared
2610
+ // runtime reader returns it without fabricating a token denominator.
2611
+ if (platform === "codex") return readRuntimeContextUsage("codex", sessionId);
2612
+ if (platform === "cursor") {
2613
+ const telemetry = readRuntimeContextTelemetry({
2614
+ adapter: "cursor",
2615
+ session_id: sessionId,
2616
+ mode: "status",
2617
+ });
2618
+ return telemetry.state === "observed" && "used_percent" in telemetry
2619
+ ? { usedPercent: telemetry.used_percent, percentOnly: true }
2620
+ : null;
2728
2621
  }
2729
- return null;
2622
+ return readRuntimeContextUsage("claude-code", sessionId);
2730
2623
  }
2731
2624
 
2732
2625
  function formatLocalTime(d: Date): string {
@@ -3126,9 +3019,9 @@ interface HealthReport {
3126
3019
  lines: number;
3127
3020
  cursor_backlog: number;
3128
3021
  };
3129
- // Heartbeats present in active/ but broken: no name, unparseable, or an
3130
- // absurd (epoch-ish) last_heartbeat. These are the `agent-unknown` peer-table
3131
- // ghosts; a positive count means dead files the sweep isn't catching.
3022
+ // Heartbeat cache files present in active/ but stale or malformed. These are
3023
+ // files the sweep is not catching; a parseable old heartbeat is stale, not
3024
+ // evidence of an epoch-like or corrupt timestamp.
3132
3025
  zombies: {
3133
3026
  count: number;
3134
3027
  samples: string[];
@@ -3136,6 +3029,15 @@ interface HealthReport {
3136
3029
  // V3 event-ledger producer health: open tool spans, pending finalization
3137
3030
  // requests, intake/diagnostics spool depth, span-count pressure.
3138
3031
  event_ledger: EventLedgerHealthV3;
3032
+ // Stop-hook remediation cap exhaustions in the window: sessions whose
3033
+ // end-of-turn evidence never landed, so the hook stopped bouncing them and
3034
+ // let the turn end unenforced. Each one is a session that could not comply —
3035
+ // the signal that used to be visible only as a repeating notification sound.
3036
+ stop_remediation: {
3037
+ total: number;
3038
+ latest_at: string | null;
3039
+ sessions: string[];
3040
+ };
3139
3041
  anomalies: string[];
3140
3042
  }
3141
3043
 
@@ -3173,12 +3075,21 @@ export type EventLedgerHealthV3 =
3173
3075
  groups: Array<{ adapter: string; session_hash: string; count: number }>;
3174
3076
  };
3175
3077
  diagnostics_spool: {
3078
+ /** Logical occurrences: loose files plus coalesced summary counts. */
3176
3079
  total: number;
3080
+ /** Physical loose diagnostic files. */
3081
+ loose_total: number;
3082
+ /** Occurrences coalesced into summaries instead of loose files. */
3083
+ summarized_total: number;
3084
+ /** Physical summary files in `diagnostic-summaries/`. */
3085
+ summary_files: number;
3177
3086
  last_24h: number;
3178
3087
  last_1h: number;
3179
3088
  latest_at: string | null;
3180
3089
  by_category: Record<string, { total: number; last_24h: number }>;
3181
3090
  recent_by_category: Record<string, { last_1h: number; latest_at: string | null }>;
3091
+ /** Times the summary gate failed open (loose write proceeded). */
3092
+ mitigation_fail_open: number;
3182
3093
  };
3183
3094
  span_pressure: Array<{ instance_id: string; generation_id: string; span_count: number }>;
3184
3095
  collection_errors: string[];
@@ -3318,6 +3229,47 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
3318
3229
  collectionErrors.push(`diagnostics spool unreadable: ${errorText(error)}`);
3319
3230
  }
3320
3231
 
3232
+ // 4b) Coalesced diagnostic summaries: occurrences the producer bounded into
3233
+ // `diagnostic-summaries/` instead of loose files. Counted into the logical
3234
+ // totals so mitigation can never make an active fault look healthy; the
3235
+ // physical loose file count stays visible separately.
3236
+ let summarizedTotal = 0;
3237
+ let summaryFiles = 0;
3238
+ let mitigationFailOpen = 0;
3239
+ try {
3240
+ const listing = listDiagnosticSummariesV3(root);
3241
+ summaryFiles = listing.file_count;
3242
+ mitigationFailOpen = listing.mitigation_health?.fail_open_count ?? 0;
3243
+ if (listing.unreadable_count > 0) {
3244
+ collectionErrors.push(`${listing.unreadable_count} diagnostic summary file(s) unreadable`);
3245
+ }
3246
+ const dayAgoMs = nowMs - 24 * 60 * 60 * 1000;
3247
+ const hourAgoMs = nowMs - 60 * 60 * 1000;
3248
+ for (const summary of listing.summaries) {
3249
+ summarizedTotal += summary.summarized_count;
3250
+ const entry = byCategory[summary.category] ?? { total: 0, last_24h: 0 };
3251
+ const recent = recentByCategory[summary.category] ?? { last_1h: 0, latest_at: null };
3252
+ byCategory[summary.category] = entry;
3253
+ recentByCategory[summary.category] = recent;
3254
+ entry.total += summary.summarized_count;
3255
+ const day = countSummarizedSinceV3([summary], dayAgoMs, undefined, nowMs);
3256
+ const hour = countSummarizedSinceV3([summary], hourAgoMs, undefined, nowMs);
3257
+ entry.last_24h += day;
3258
+ diagnostics24h += day;
3259
+ recent.last_1h += hour;
3260
+ diagnostics1h += hour;
3261
+ const lastMs = summary.last_summarized_at ? Date.parse(summary.last_summarized_at) : NaN;
3262
+ if (Number.isFinite(lastMs)) {
3263
+ latestDiagnosticMs = Math.max(latestDiagnosticMs, lastMs);
3264
+ if (!recent.latest_at || lastMs > Date.parse(recent.latest_at)) {
3265
+ recent.latest_at = new Date(lastMs).toISOString();
3266
+ }
3267
+ }
3268
+ }
3269
+ } catch (error) {
3270
+ collectionErrors.push(`diagnostic summaries unreadable: ${errorText(error)}`);
3271
+ }
3272
+
3321
3273
  return {
3322
3274
  state: "live",
3323
3275
  mode: control.state,
@@ -3328,7 +3280,10 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
3328
3280
  pending_finalizations: pending,
3329
3281
  intake_spool: { total: intakeTotal, groups: intakeGroups },
3330
3282
  diagnostics_spool: {
3331
- total: diagnosticsTotal,
3283
+ total: diagnosticsTotal + summarizedTotal,
3284
+ loose_total: diagnosticsTotal,
3285
+ summarized_total: summarizedTotal,
3286
+ summary_files: summaryFiles,
3332
3287
  last_24h: diagnostics24h,
3333
3288
  last_1h: diagnostics1h,
3334
3289
  latest_at: Number.isFinite(latestDiagnosticMs)
@@ -3336,12 +3291,57 @@ export function collectEventLedgerHealthV3(root: string, nowMs = Date.now()): Ev
3336
3291
  : null,
3337
3292
  by_category: byCategory,
3338
3293
  recent_by_category: recentByCategory,
3294
+ mitigation_fail_open: mitigationFailOpen,
3339
3295
  },
3340
3296
  span_pressure: spanPressure,
3341
3297
  collection_errors: collectionErrors,
3342
3298
  };
3343
3299
  }
3344
3300
 
3301
+ /**
3302
+ * Tally stop-hook remediation-cap exhaustions in the window from
3303
+ * `.harnery/debug/agent-hook.ndjson` (rows with
3304
+ * `skipped: "stop-remediation-cap-exhausted"`). Each one is a session whose
3305
+ * end-of-turn evidence never landed, so the Stop hook gave up bouncing it and
3306
+ * let the turn end unenforced — a compliance gap an operator should see
3307
+ * without grepping the debug ledger.
3308
+ */
3309
+ export function readStopRemediationExhaustions(
3310
+ root: string,
3311
+ cutoffMs: number,
3312
+ ): { total: number; latestAt: string | null; sessions: string[] } {
3313
+ const p = resolve(root, ".harnery", "debug", "agent-hook.ndjson");
3314
+ if (!existsSync(p)) return { total: 0, latestAt: null, sessions: [] };
3315
+ let raw: string;
3316
+ try {
3317
+ raw = readFileSync(p, "utf8");
3318
+ } catch {
3319
+ return { total: 0, latestAt: null, sessions: [] };
3320
+ }
3321
+ let total = 0;
3322
+ let latestMs = Number.NEGATIVE_INFINITY;
3323
+ const sessions = new Set<string>();
3324
+ for (const line of raw.split("\n")) {
3325
+ if (!line.includes("stop-remediation-cap-exhausted")) continue;
3326
+ try {
3327
+ const e = JSON.parse(line) as { ts?: string; skipped?: string; session_id?: string };
3328
+ if (e.skipped !== "stop-remediation-cap-exhausted") continue;
3329
+ const tsMs = e.ts ? Date.parse(e.ts) : Number.NaN;
3330
+ if (!Number.isFinite(tsMs) || tsMs < cutoffMs) continue;
3331
+ total++;
3332
+ latestMs = Math.max(latestMs, tsMs);
3333
+ if (e.session_id && sessions.size < 5) sessions.add(e.session_id);
3334
+ } catch {
3335
+ /* skip malformed */
3336
+ }
3337
+ }
3338
+ return {
3339
+ total,
3340
+ latestAt: Number.isFinite(latestMs) ? new Date(latestMs).toISOString() : null,
3341
+ sessions: [...sessions],
3342
+ };
3343
+ }
3344
+
3345
3345
  /** Tally agent-hook failures (.harnery/debug/agent-hook.errors.ndjson) in the
3346
3346
  * window by exact error and phase, while separating current-hour failures from
3347
3347
  * historical rows. Each line is {ts, error, phase, ...}. */
@@ -3797,13 +3797,15 @@ type ActiveHealthHeartbeat = {
3797
3797
  export function collectActiveAgentHealth(
3798
3798
  root: string,
3799
3799
  nowMs = Date.now(),
3800
+ readRows: (root: string) => ActiveHealthHeartbeat[] = readLiveCoordinationRows,
3801
+ freshnessSeconds = freshnessCutoffSecs(),
3800
3802
  ): ActiveAgentHealthSummary {
3801
3803
  const source: ActiveAgentHealthSummary["source"] = "event-ledger-v3";
3802
3804
  let heartbeats: ActiveHealthHeartbeat[] = [];
3803
3805
  try {
3804
3806
  const control = readEventV3ControlState(root);
3805
3807
  if (control.state === "candidate" || control.state === "active") {
3806
- heartbeats = readLiveCoordinationRows(root);
3808
+ heartbeats = readRows(root);
3807
3809
  }
3808
3810
  } catch {
3809
3811
  // The event-ledger section reports the authority read failure. Do not
@@ -3829,7 +3831,7 @@ export function collectActiveAgentHealth(
3829
3831
  const ageMs = Number.isFinite(lastObservedMs)
3830
3832
  ? nowMs - lastObservedMs
3831
3833
  : Number.POSITIVE_INFINITY;
3832
- if (ageMs > freshnessCutoffSecs() * 1000) stale += 1;
3834
+ if (ageMs > freshnessSeconds * 1000) stale += 1;
3833
3835
  }
3834
3836
  return {
3835
3837
  source,
@@ -3841,6 +3843,22 @@ export function collectActiveAgentHealth(
3841
3843
  };
3842
3844
  }
3843
3845
 
3846
+ export type HeartbeatCacheIssue = "no-name" | "invalid-heartbeat" | "stale-age";
3847
+
3848
+ /** Classify a parseable heartbeat cache file without conflating age with corruption. */
3849
+ export function classifyHeartbeatCacheIssue(
3850
+ heartbeat: Pick<Heartbeat, "name" | "last_heartbeat">,
3851
+ nowMs: number,
3852
+ staleAgeMs: number,
3853
+ ): HeartbeatCacheIssue | null {
3854
+ if (!heartbeat.name || heartbeat.name === "unknown") return "no-name";
3855
+ const lastHeartbeatMs = heartbeat.last_heartbeat
3856
+ ? Date.parse(heartbeat.last_heartbeat)
3857
+ : Number.NaN;
3858
+ if (!Number.isFinite(lastHeartbeatMs)) return "invalid-heartbeat";
3859
+ return nowMs - lastHeartbeatMs > staleAgeMs ? "stale-age" : null;
3860
+ }
3861
+
3844
3862
  function runHealth(opts: { since: string; json?: boolean }): void {
3845
3863
  if (opts.json) emit.config({ format: "json" });
3846
3864
 
@@ -3902,6 +3920,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3902
3920
  }
3903
3921
 
3904
3922
  const hookErrors = readHookErrors(root, cutoffMs, nowMs);
3923
+ const stopRemediation = readStopRemediationExhaustions(root, cutoffMs);
3905
3924
  const stream = readStreamStats(root);
3906
3925
  const eventLedger = collectEventLedgerHealthV3(root);
3907
3926
 
@@ -3924,12 +3943,12 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3924
3943
  .map(([agent, count]) => ({ agent, count }));
3925
3944
 
3926
3945
  const activeAgents = collectActiveAgentHealth(root, nowMs);
3927
- // Zombies: files in active/ that are broken: unparseable, nameless, or an
3928
- // absurd (epoch-ish) last_heartbeat. These show as `agent-unknown` ghosts and
3929
- // mean dead files the sweep isn't reaping.
3946
+ // Heartbeat cache issues: files in active/ that are unparseable, nameless,
3947
+ // invalidly timestamped, or stale. Old parseable timestamps are ordinary
3948
+ // staleness, not evidence of epoch-like timestamp corruption.
3930
3949
  let zombieCount = 0;
3931
3950
  const zombieSamples: string[] = [];
3932
- const ABSURD_AGE_MS = 24 * 60 * 60 * 1000; // > 1 day = clearly not a live, self-healing agent
3951
+ const STALE_HEARTBEAT_AGE_MS = 24 * 60 * 60 * 1000;
3933
3952
  if (existsSync(activeDir)) {
3934
3953
  for (const file of readdirSync(activeDir)) {
3935
3954
  if (!file.endsWith(".json")) continue;
@@ -3946,15 +3965,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3946
3965
  zombieSamples.push(`${idFromFile.slice(0, 12)} (unparseable/no-id)`);
3947
3966
  continue;
3948
3967
  }
3949
- const lastHbMs = hb.last_heartbeat ? Date.parse(hb.last_heartbeat) : Number.NaN;
3950
- const ageMs = Number.isFinite(lastHbMs) ? nowMs - lastHbMs : Number.POSITIVE_INFINITY;
3951
- // Zombie heuristics on a parseable heartbeat: no name, or an age so large
3952
- // it can only be a broken/epoch timestamp (a real agent would have healed).
3953
- if (!hb.name || hb.name === "unknown" || ageMs > ABSURD_AGE_MS) {
3968
+ const cacheIssue = classifyHeartbeatCacheIssue(hb, nowMs, STALE_HEARTBEAT_AGE_MS);
3969
+ if (cacheIssue) {
3954
3970
  zombieCount++;
3955
3971
  if (zombieSamples.length < 5) {
3956
- const why = !hb.name || hb.name === "unknown" ? "no-name" : "epoch-age";
3957
- zombieSamples.push(`${idFromFile.slice(0, 12)} (${why})`);
3972
+ zombieSamples.push(`${idFromFile.slice(0, 12)} (${cacheIssue})`);
3958
3973
  }
3959
3974
  }
3960
3975
  }
@@ -4018,7 +4033,16 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4018
4033
  }
4019
4034
  if (zombieCount > 0) {
4020
4035
  anomalies.push(
4021
- `${zombieCount} zombie heartbeat(s) in active/ (${zombieSamples.join(", ")}); broken files the sweep isn't reaping`,
4036
+ `${zombieCount} heartbeat cache issue(s) in active/ (${zombieSamples.join(", ")}); stale or malformed files the sweep isn't reaping`,
4037
+ );
4038
+ }
4039
+ if (stopRemediation.total > 0) {
4040
+ const who =
4041
+ stopRemediation.sessions.length > 0
4042
+ ? ` (sessions: ${stopRemediation.sessions.map((s) => s.slice(0, 8)).join(", ")})`
4043
+ : "";
4044
+ anomalies.push(
4045
+ `stop-hook remediation cap exhausted ${stopRemediation.total}x in ${opts.since}${who}; these sessions ended turns without the end-of-turn ritual — check why their evidence never lands`,
4022
4046
  );
4023
4047
  }
4024
4048
  if (eventLedger.state === "live") {
@@ -4089,6 +4113,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4089
4113
  stream,
4090
4114
  zombies: { count: zombieCount, samples: zombieSamples },
4091
4115
  event_ledger: eventLedger,
4116
+ stop_remediation: {
4117
+ total: stopRemediation.total,
4118
+ latest_at: stopRemediation.latestAt,
4119
+ sessions: stopRemediation.sessions,
4120
+ },
4092
4121
  anomalies,
4093
4122
  };
4094
4123
 
@@ -4160,7 +4189,7 @@ function renderHealthBox(report: HealthReport): void {
4160
4189
  `open spans ${ledger.open_spans.total}${orphanGenerations > 0 ? ` (${orphanGenerations} gen turn-closed)` : ""}`,
4161
4190
  `pending ends ${ledger.pending_finalizations.length}${pendingAges ? ` (${pendingAges})` : ""}`,
4162
4191
  `intake ${ledger.intake_spool.total}`,
4163
- `diagnostics ${ledger.diagnostics_spool.total} (${ledger.diagnostics_spool.last_1h} in 1h / ${ledger.diagnostics_spool.last_24h} in 24h)`,
4192
+ `diagnostics ${ledger.diagnostics_spool.total}${ledger.diagnostics_spool.summarized_total > 0 ? ` (${ledger.diagnostics_spool.loose_total} loose + ${ledger.diagnostics_spool.summarized_total} summarized)` : ""} (${ledger.diagnostics_spool.last_1h} in 1h / ${ledger.diagnostics_spool.last_24h} in 24h)`,
4164
4193
  ].join(" · ");
4165
4194
  }
4166
4195
 
@@ -4181,7 +4210,7 @@ function renderHealthBox(report: HealthReport): void {
4181
4210
  ["stream", streamStr],
4182
4211
  ["event ledger", ledgerStr],
4183
4212
  [
4184
- "zombies",
4213
+ "cache issues",
4185
4214
  report.zombies.count === 0
4186
4215
  ? "0"
4187
4216
  : `${report.zombies.count} (${report.zombies.samples.join(", ")})`,
@@ -4617,10 +4646,18 @@ function runPing(name: string, message: string, opts: { json?: boolean }): void
4617
4646
  }
4618
4647
  const myOwner = resolveOwner();
4619
4648
  if (!myOwner) {
4620
- emit.error({
4621
- code: "no_pidmap_entry",
4622
- message: "not in an agent session; ppid walk found no pid-map entry",
4623
- });
4649
+ const root = monorepoRoot();
4650
+ emit.error(
4651
+ root
4652
+ ? sessionResolutionFailure(
4653
+ root,
4654
+ "not in an agent session; ppid walk found no pid-map entry",
4655
+ )
4656
+ : {
4657
+ code: "no_pidmap_entry",
4658
+ message: "not in an agent session; ppid walk found no pid-map entry",
4659
+ },
4660
+ );
4624
4661
  process.exit(1);
4625
4662
  }
4626
4663
  const peerOwner = resolveOwnerByName(name);
@@ -4787,21 +4824,22 @@ function emitWaitResult(
4787
4824
  }
4788
4825
 
4789
4826
  function runHeal(opts: {
4790
- owner: string;
4791
- kind: string;
4827
+ owner?: string;
4828
+ kind?: string;
4792
4829
  sessionId?: string;
4793
4830
  adapter?: string;
4794
4831
  pid?: string;
4832
+ quarantineTransaction?: string;
4833
+ approvalRecordId?: string;
4834
+ yes?: boolean;
4795
4835
  json?: boolean;
4796
4836
  }): void {
4797
4837
  if (opts.json) emit.config({ format: "json" });
4798
4838
 
4799
- const owner = opts.owner.trim();
4800
- const kind = opts.kind.trim();
4801
- if (!owner) {
4802
- emit.error({ code: "missing_owner", message: "--owner is required" });
4803
- process.exit(1);
4804
- }
4839
+ const requestedOwner = opts.owner?.trim() ?? "";
4840
+ const quarantineTransaction = opts.quarantineTransaction?.trim() ?? "";
4841
+ const approvalRecordId = opts.approvalRecordId?.trim() ?? "";
4842
+ const kind = opts.kind?.trim() || "cache";
4805
4843
  if (kind !== "pidmap" && kind !== "cache") {
4806
4844
  emit.error({
4807
4845
  code: "bad_kind",
@@ -4809,6 +4847,30 @@ function runHeal(opts: {
4809
4847
  });
4810
4848
  process.exit(1);
4811
4849
  }
4850
+ if (quarantineTransaction) {
4851
+ if (opts.kind || opts.pid) {
4852
+ emit.error({
4853
+ code: "conflicting_recovery_options",
4854
+ message: "--quarantine-transaction cannot be combined with --kind or --pid",
4855
+ });
4856
+ process.exit(1);
4857
+ }
4858
+ if (!opts.yes || !approvalRecordId) {
4859
+ emit.error({
4860
+ code: "transaction_quarantine_confirmation_required",
4861
+ message:
4862
+ "--quarantine-transaction requires --approval-record-id <id> and --yes; " +
4863
+ "the command abandons one uncommitted authority mutation while preserving its record",
4864
+ });
4865
+ process.exit(1);
4866
+ }
4867
+ } else if (approvalRecordId || opts.yes) {
4868
+ emit.error({
4869
+ code: "transaction_quarantine_target_required",
4870
+ message: "--approval-record-id and --yes require --quarantine-transaction <id>",
4871
+ });
4872
+ process.exit(1);
4873
+ }
4812
4874
  if (
4813
4875
  opts.adapter &&
4814
4876
  opts.adapter !== "claude-code" &&
@@ -4831,7 +4893,52 @@ function runHeal(opts: {
4831
4893
  process.exit(1);
4832
4894
  }
4833
4895
 
4834
- const action = kind === "pidmap" ? "heal-pidmap" : "repair-coordination-cache";
4896
+ const currentSessionRepair =
4897
+ kind === "cache" &&
4898
+ opts.owner === undefined &&
4899
+ opts.sessionId === undefined &&
4900
+ opts.adapter === undefined &&
4901
+ !quarantineTransaction;
4902
+
4903
+ const ownerResolution = resolveOwnerWithSource();
4904
+ const bootstrapIdentity = currentSessionRepair ? commandSessionBootstrap() : null;
4905
+ const resolvedCurrentOwner =
4906
+ bootstrapIdentity &&
4907
+ (ownerResolution.source === "none" || ownerResolution.source === "active_singleton")
4908
+ ? bootstrapIdentity.sessionId
4909
+ : ownerResolution.owner;
4910
+ const owner = requestedOwner || resolvedCurrentOwner || sessionIdentityFromEnv() || "";
4911
+ if (!owner) {
4912
+ emit.error({
4913
+ code: "session_identity_missing",
4914
+ message:
4915
+ `could not resolve the current session; run ${resolveBinName()} doctor, or pass ` +
4916
+ "--owner <instance-id> --session-id <native-session-id> --adapter <adapter>",
4917
+ });
4918
+ process.exit(1);
4919
+ }
4920
+
4921
+ const currentRow = readLiveCoordinationRow(root, owner);
4922
+ const inferredAdapter = normalizeAdapter(
4923
+ opts.adapter?.trim() || currentRow?.platform || commandSessionBootstrap()?.adapter,
4924
+ );
4925
+ const sessionId =
4926
+ opts.sessionId?.trim() ||
4927
+ (currentSessionRepair ? sessionIdentityFromEnv() : null) ||
4928
+ nativeSessionIdentity(currentRow, owner);
4929
+
4930
+ if (kind === "cache") {
4931
+ const refusal = cacheHealAuthorityRefusal(root, owner, sessionId, inferredAdapter, currentRow);
4932
+ if (refusal) {
4933
+ emitHealFailure(refusal.reason, refusal.message);
4934
+ }
4935
+ }
4936
+
4937
+ const action = quarantineTransaction
4938
+ ? "quarantine-authority-transaction"
4939
+ : kind === "pidmap"
4940
+ ? "heal-pidmap"
4941
+ : "repair-coordination-cache";
4835
4942
 
4836
4943
  // Refuse to materialize a V3 cache at a truncated owner id.
4837
4944
  //
@@ -4874,23 +4981,72 @@ function runHeal(opts: {
4874
4981
  // heal-pidmap <instance_id> [<pid>]
4875
4982
  // repair-coordination-cache <instance_id> [<session_id>]
4876
4983
  const helperArgs: string[] = [action, owner];
4877
- if (kind === "pidmap" && opts.pid) helperArgs.push(opts.pid);
4878
- if (kind === "cache" && opts.sessionId) helperArgs.push(opts.sessionId);
4879
- if (kind === "cache" && opts.adapter) helperArgs.push(`--adapter=${opts.adapter}`);
4984
+ if (quarantineTransaction) {
4985
+ helperArgs.push(
4986
+ quarantineTransaction,
4987
+ approvalRecordId,
4988
+ sessionId,
4989
+ `--adapter=${inferredAdapter}`,
4990
+ );
4991
+ } else {
4992
+ if (kind === "pidmap" && opts.pid) helperArgs.push(opts.pid);
4993
+ if (kind === "cache") helperArgs.push(sessionId, `--adapter=${inferredAdapter}`);
4994
+ }
4880
4995
 
4881
4996
  // Both recovery actions are handled by the bundled agent-coord binary.
4882
4997
  const helper = agentCoordOrExit(root);
4883
- const proc = spawnSync(helper, helperArgs, {
4998
+ let proc = spawnSync(helper, helperArgs, {
4884
4999
  encoding: "utf8",
4885
5000
  ...coordHelperOpts(root),
4886
5001
  });
4887
5002
 
4888
- if (proc.status !== 0) {
4889
- emit.error({
4890
- code: "heal_failed",
4891
- message: spawnFailureMessage(proc, `agent-coord ${action}`),
5003
+ let bootstrapState: "created" | "reused" | undefined;
5004
+ if (
5005
+ proc.status !== 0 &&
5006
+ currentSessionRepair &&
5007
+ missingAuthorityForCurrentSessionBootstrap(root, owner, sessionId)
5008
+ ) {
5009
+ const bootstrap = commandSessionBootstrap();
5010
+ if (
5011
+ !bootstrap ||
5012
+ bootstrap.sessionId !== sessionId ||
5013
+ bootstrap.sessionId !== owner ||
5014
+ bootstrap.adapter !== inferredAdapter
5015
+ ) {
5016
+ emitHealFailure(
5017
+ "native_identity_unverified",
5018
+ "the current adapter did not provide one matching native session id and adapter",
5019
+ );
5020
+ }
5021
+ try {
5022
+ const bootstrapped = bootstrapLiveCoordinationAuthorityV3({
5023
+ coordRoot: root,
5024
+ owner,
5025
+ nativeSessionId: bootstrap.sessionId,
5026
+ adapter: bootstrap.adapter,
5027
+ });
5028
+ bootstrapState = bootstrapped.state;
5029
+ } catch (error) {
5030
+ const reason =
5031
+ error instanceof LiveCoordinationAuthorityV3Error
5032
+ ? error.reason
5033
+ : "authority_bootstrap_failed";
5034
+ emitHealFailure(
5035
+ reason,
5036
+ "the current native session could not establish authority; no fallback cache was accepted",
5037
+ );
5038
+ }
5039
+ proc = spawnSync(helper, helperArgs, {
5040
+ encoding: "utf8",
5041
+ ...coordHelperOpts(root),
4892
5042
  });
4893
- process.exit(1);
5043
+ }
5044
+
5045
+ if (proc.status !== 0) {
5046
+ emitHealFailure(
5047
+ healFailureReason(root, owner, sessionId, inferredAdapter),
5048
+ spawnFailureMessage(proc, `agent-coord ${action}`),
5049
+ );
4894
5050
  }
4895
5051
 
4896
5052
  // Read the derived cache to surface post-action state.
@@ -4901,8 +5057,9 @@ function runHeal(opts: {
4901
5057
  after = null;
4902
5058
  }
4903
5059
 
4904
- const outcome =
4905
- kind === "pidmap"
5060
+ const outcome = quarantineTransaction
5061
+ ? "transaction_quarantined"
5062
+ : kind === "pidmap"
4906
5063
  ? proc.status === 0
4907
5064
  ? "ok"
4908
5065
  : "failed"
@@ -4917,10 +5074,15 @@ function runHeal(opts: {
4917
5074
  action,
4918
5075
  outcome,
4919
5076
  after,
5077
+ ...(quarantineTransaction ? { recovery: JSON.parse(proc.stdout.trim()) as unknown } : {}),
4920
5078
  },
4921
5079
  ],
4922
5080
  meta: {
4923
5081
  kind,
5082
+ automatic: currentSessionRepair && !quarantineTransaction,
5083
+ authority: "event-ledger-v3",
5084
+ adapter: kind === "cache" ? inferredAdapter : undefined,
5085
+ bootstrap: bootstrapState,
4924
5086
  // The path actually spawned, not a guess at the layout: the helper is
4925
5087
  // resolved from harnery's own package location, which differs between a
4926
5088
  // submodule, an installed dependency, and a standalone checkout.
@@ -4928,10 +5090,18 @@ function runHeal(opts: {
4928
5090
  },
4929
5091
  });
4930
5092
  if (!opts.json) {
4931
- if (kind === "pidmap") {
5093
+ if (quarantineTransaction) {
5094
+ emit.text(
5095
+ `coordination recovered: ${quarantineTransaction} quarantined with approval ${approvalRecordId}; ` +
5096
+ `cache ${after ? "present" : "absent"} (${inferredAdapter})\n`,
5097
+ );
5098
+ } else if (kind === "pidmap") {
4932
5099
  emit.text(`agent-coord ${action} ok\n`);
4933
5100
  } else {
4934
- emit.text(`agent-coord ${action} ok: cache ${after ? "present" : "absent"} after\n`);
5101
+ emit.text(
5102
+ `coordination repaired: V3 authority live; ${currentSessionRepair ? "current-session " : ""}` +
5103
+ `cache ${after ? "present" : "absent"} (${inferredAdapter})\n`,
5104
+ );
4935
5105
  }
4936
5106
  }
4937
5107
 
@@ -4942,6 +5112,98 @@ function runHeal(opts: {
4942
5112
  // unconditionally on every `harn agents heal`, which over-counted no-op heals.)
4943
5113
  }
4944
5114
 
5115
+ type HealAuthorityRefusal = {
5116
+ reason: "adapter_mismatch" | "owner_mismatch" | "session_mismatch" | "terminal_generation";
5117
+ message: string;
5118
+ };
5119
+
5120
+ function cacheHealAuthorityRefusal(
5121
+ root: string,
5122
+ owner: string,
5123
+ sessionId: string,
5124
+ adapter: "claude-code" | "cursor" | "codex",
5125
+ currentRow: LiveCoordinationRow | null,
5126
+ ): HealAuthorityRefusal | null {
5127
+ if (currentRow && normalizeAdapter(currentRow.platform) !== adapter) {
5128
+ return {
5129
+ reason: "adapter_mismatch",
5130
+ message: `the live generation belongs to adapter ${normalizeAdapter(currentRow.platform)}, not ${adapter}`,
5131
+ };
5132
+ }
5133
+ let producer: ReturnType<typeof readHookProducerStateV3>;
5134
+ try {
5135
+ producer = readHookProducerStateV3(root, adapter, sessionId);
5136
+ } catch {
5137
+ return {
5138
+ reason: "session_mismatch",
5139
+ message: "the native session producer state could not be read safely",
5140
+ };
5141
+ }
5142
+ if (producer?.terminal) {
5143
+ return {
5144
+ reason: "terminal_generation",
5145
+ message: "the matching generation is terminal and cannot be repaired or reopened by heal",
5146
+ };
5147
+ }
5148
+ if (producer && producer.instance_id !== liveInstanceIdV3(owner)) {
5149
+ return {
5150
+ reason: "owner_mismatch",
5151
+ message: "the native session is bound to a different instance owner",
5152
+ };
5153
+ }
5154
+ if (currentRow && (!producer || producer.generation_id !== currentRow.v3_generation_id)) {
5155
+ return {
5156
+ reason: "session_mismatch",
5157
+ message: "the native session does not bind to the requested live generation",
5158
+ };
5159
+ }
5160
+ return null;
5161
+ }
5162
+
5163
+ function missingAuthorityForCurrentSessionBootstrap(
5164
+ root: string,
5165
+ owner: string,
5166
+ sessionId: string,
5167
+ ): boolean {
5168
+ if (readLiveCoordinationRow(root, owner)) return false;
5169
+ try {
5170
+ const nativeSessionHasProducer = (["claude-code", "codex", "cursor"] as const).some(
5171
+ (adapter) => readHookProducerStateV3(root, adapter, sessionId) !== undefined,
5172
+ );
5173
+ if (nativeSessionHasProducer) return false;
5174
+ const instanceId = liveInstanceIdV3(owner);
5175
+ return !listHookProducerStateRecordsV3(root, { includeTerminal: true }).some(
5176
+ ({ state }) => state.instance_id === instanceId,
5177
+ );
5178
+ } catch {
5179
+ return false;
5180
+ }
5181
+ }
5182
+
5183
+ function healFailureReason(
5184
+ root: string,
5185
+ owner: string,
5186
+ sessionId: string,
5187
+ adapter: "claude-code" | "cursor" | "codex",
5188
+ ): string {
5189
+ const refusal = cacheHealAuthorityRefusal(
5190
+ root,
5191
+ owner,
5192
+ sessionId,
5193
+ adapter,
5194
+ readLiveCoordinationRow(root, owner),
5195
+ );
5196
+ return refusal?.reason ?? "authority_missing";
5197
+ }
5198
+
5199
+ function emitHealFailure(reason: string, message: string): never {
5200
+ emit.error({
5201
+ code: "heal_failed",
5202
+ message: `coordination heal refused (reason=${reason}): ${message}`,
5203
+ });
5204
+ process.exit(1);
5205
+ }
5206
+
4945
5207
  /**
4946
5208
  * Emit one V3 council audit transition keyed to the exact durable manifest.
4947
5209
  * Falls through silently when no live session can attest the observation.