harnery 0.36.0 → 0.38.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 (432) hide show
  1. package/dist/commander.d.ts +9 -0
  2. package/dist/commander.d.ts.map +1 -1
  3. package/dist/commander.js +10 -1
  4. package/dist/commands/admission.d.ts +21 -0
  5. package/dist/commands/admission.d.ts.map +1 -0
  6. package/dist/commands/admission.js +565 -0
  7. package/dist/commands/agents.d.ts +7 -0
  8. package/dist/commands/agents.d.ts.map +1 -1
  9. package/dist/commands/agents.js +148 -47
  10. package/dist/commands/artifacts.d.ts.map +1 -1
  11. package/dist/commands/artifacts.js +73 -2
  12. package/dist/commands/browse-ai.d.ts +2 -2
  13. package/dist/commands/browse-ai.d.ts.map +1 -1
  14. package/dist/commands/browse-ai.js +6 -4
  15. package/dist/commands/browse.d.ts.map +1 -1
  16. package/dist/commands/browse.js +360 -21
  17. package/dist/commands/diagnostics.d.ts +4 -0
  18. package/dist/commands/diagnostics.d.ts.map +1 -0
  19. package/dist/commands/diagnostics.js +229 -0
  20. package/dist/commands/fetch.js +1 -0
  21. package/dist/commands/files.d.ts +12 -0
  22. package/dist/commands/files.d.ts.map +1 -1
  23. package/dist/commands/files.js +54 -1
  24. package/dist/commands/governor.d.ts.map +1 -1
  25. package/dist/commands/governor.js +4 -0
  26. package/dist/commands/ledger-v3.d.ts.map +1 -1
  27. package/dist/commands/ledger-v3.js +71 -2
  28. package/dist/commands/qa-record.d.ts +144 -0
  29. package/dist/commands/qa-record.d.ts.map +1 -0
  30. package/dist/commands/qa-record.js +0 -0
  31. package/dist/commands/qa-run.d.ts +8 -0
  32. package/dist/commands/qa-run.d.ts.map +1 -0
  33. package/dist/commands/qa-run.js +396 -0
  34. package/dist/commands/qa-status.d.ts +71 -0
  35. package/dist/commands/qa-status.d.ts.map +1 -0
  36. package/dist/commands/qa-status.js +490 -0
  37. package/dist/commands/qa-verify.d.ts +40 -0
  38. package/dist/commands/qa-verify.d.ts.map +1 -0
  39. package/dist/commands/qa-verify.js +180 -0
  40. package/dist/commands/resources.d.ts +4 -0
  41. package/dist/commands/resources.d.ts.map +1 -0
  42. package/dist/commands/resources.js +29 -0
  43. package/dist/commands/review-pack.d.ts +4 -0
  44. package/dist/commands/review-pack.d.ts.map +1 -0
  45. package/dist/commands/review-pack.js +1001 -0
  46. package/dist/commands/supervisor.d.ts +4 -0
  47. package/dist/commands/supervisor.d.ts.map +1 -0
  48. package/dist/commands/supervisor.js +101 -0
  49. package/dist/commands/web.d.ts.map +1 -1
  50. package/dist/commands/web.js +17 -0
  51. package/dist/commands/workflow.d.ts.map +1 -1
  52. package/dist/commands/workflow.js +4 -0
  53. package/dist/core/agents/cli.js +7 -3
  54. package/dist/core/agents/health.d.ts +17 -0
  55. package/dist/core/agents/health.d.ts.map +1 -0
  56. package/dist/core/agents/health.js +109 -0
  57. package/dist/core/agents/mailbox.d.ts +92 -0
  58. package/dist/core/agents/mailbox.d.ts.map +1 -0
  59. package/dist/core/agents/mailbox.js +325 -0
  60. package/dist/core/agents/qa-signal.d.ts +111 -0
  61. package/dist/core/agents/qa-signal.d.ts.map +1 -0
  62. package/dist/core/agents/qa-signal.js +231 -0
  63. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  64. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  65. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  66. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  67. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  68. package/dist/core/agents/render/prompt-context.js +34 -13
  69. package/dist/core/agents/render/session-context.d.ts +8 -0
  70. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  71. package/dist/core/agents/render/session-context.js +34 -3
  72. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  73. package/dist/core/agents/rules/commit-conflict.js +50 -1
  74. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  75. package/dist/core/agents/rules/stop-hook.js +6 -10
  76. package/dist/core/agents/session-finalizer-v3.js +4 -1
  77. package/dist/core/agents/session-name-display.d.ts +20 -5
  78. package/dist/core/agents/session-name-display.d.ts.map +1 -1
  79. package/dist/core/agents/session-name-display.js +67 -7
  80. package/dist/core/agents/state/heartbeat-reader.d.ts +7 -0
  81. package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
  82. package/dist/core/agents/state/heartbeat-writer.d.ts +11 -0
  83. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  84. package/dist/core/agents/state/heartbeat-writer.js +17 -0
  85. package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
  86. package/dist/core/agents/state/live-coordination-view.js +1 -0
  87. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  88. package/dist/core/artifacts/constants.d.ts +1 -1
  89. package/dist/core/artifacts/constants.js +1 -1
  90. package/dist/core/artifacts/index.d.ts +82 -6
  91. package/dist/core/artifacts/index.d.ts.map +1 -1
  92. package/dist/core/artifacts/index.js +410 -15
  93. package/dist/core/config.d.ts +20 -0
  94. package/dist/core/config.d.ts.map +1 -1
  95. package/dist/core/config.js +59 -0
  96. package/dist/core/diagnostics/advice.d.ts +9 -0
  97. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  98. package/dist/core/diagnostics/advice.js +112 -0
  99. package/dist/core/diagnostics/bundle.d.ts +51 -0
  100. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  101. package/dist/core/diagnostics/bundle.js +968 -0
  102. package/dist/core/diagnostics/comparison.d.ts +5 -0
  103. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  104. package/dist/core/diagnostics/comparison.js +444 -0
  105. package/dist/core/diagnostics/contract.d.ts +245 -0
  106. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  107. package/dist/core/diagnostics/contract.js +18 -0
  108. package/dist/core/diagnostics/identity.d.ts +3 -0
  109. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  110. package/dist/core/diagnostics/identity.js +10 -0
  111. package/dist/core/diagnostics/index.d.ts +8 -0
  112. package/dist/core/diagnostics/index.d.ts.map +1 -0
  113. package/dist/core/diagnostics/index.js +7 -0
  114. package/dist/core/diagnostics/replay.d.ts +8 -0
  115. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  116. package/dist/core/diagnostics/replay.js +213 -0
  117. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  118. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  119. package/dist/core/diagnostics/sanitize.js +84 -0
  120. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  121. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  122. package/dist/core/events/legacy-storage/compression.js +107 -0
  123. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  124. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  125. package/dist/core/events/legacy-storage/index.js +1 -0
  126. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  127. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  128. package/dist/core/events/v3/archive-retention.js +195 -0
  129. package/dist/core/events/v3/bootstrap.d.ts.map +1 -1
  130. package/dist/core/events/v3/bootstrap.js +10 -0
  131. package/dist/core/events/v3/capabilities.d.ts +1 -1
  132. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  133. package/dist/core/events/v3/capabilities.js +1 -0
  134. package/dist/core/events/v3/coordination-view.d.ts +3 -0
  135. package/dist/core/events/v3/coordination-view.d.ts.map +1 -1
  136. package/dist/core/events/v3/coordination-view.js +69 -8
  137. package/dist/core/events/v3/index.d.ts +1 -0
  138. package/dist/core/events/v3/index.d.ts.map +1 -1
  139. package/dist/core/events/v3/index.js +1 -0
  140. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  141. package/dist/core/events/v3/live-routing.js +1 -0
  142. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  143. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  144. package/dist/core/events/v3/producers/hook-base.js +13 -2
  145. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  146. package/dist/core/events/v3/producers/intake.js +10 -2
  147. package/dist/core/events/v3/producers/recorder.d.ts +39 -0
  148. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  149. package/dist/core/events/v3/producers/recorder.js +350 -17
  150. package/dist/core/hooks/adapter/output.d.ts +8 -0
  151. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  152. package/dist/core/hooks/adapter/output.js +11 -1
  153. package/dist/core/hooks/cli.js +303 -219
  154. package/dist/core/hooks/health.d.ts +59 -0
  155. package/dist/core/hooks/health.d.ts.map +1 -0
  156. package/dist/core/hooks/health.js +84 -0
  157. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -1
  158. package/dist/core/hooks/resolve/transcript.js +10 -3
  159. package/dist/core/hooks/session-name-presence.d.ts.map +1 -1
  160. package/dist/core/hooks/session-name-presence.js +4 -1
  161. package/dist/core/qa-artifacts.d.ts +20 -0
  162. package/dist/core/qa-artifacts.d.ts.map +1 -0
  163. package/dist/core/qa-artifacts.js +110 -0
  164. package/dist/core/resources/contract.d.ts +96 -0
  165. package/dist/core/resources/contract.d.ts.map +1 -0
  166. package/dist/core/resources/contract.js +2 -0
  167. package/dist/core/resources/index.d.ts +6 -0
  168. package/dist/core/resources/index.d.ts.map +1 -0
  169. package/dist/core/resources/index.js +5 -0
  170. package/dist/core/resources/sampler.d.ts +34 -0
  171. package/dist/core/resources/sampler.d.ts.map +1 -0
  172. package/dist/core/resources/sampler.js +458 -0
  173. package/dist/core/resources/service-status.d.ts +3 -0
  174. package/dist/core/resources/service-status.d.ts.map +1 -0
  175. package/dist/core/resources/service-status.js +48 -0
  176. package/dist/core/resources/service.d.ts +26 -0
  177. package/dist/core/resources/service.d.ts.map +1 -0
  178. package/dist/core/resources/service.js +292 -0
  179. package/dist/core/resources/storage.d.ts +13 -0
  180. package/dist/core/resources/storage.d.ts.map +1 -0
  181. package/dist/core/resources/storage.js +30 -0
  182. package/dist/core/storage/atomic-json.d.ts +3 -0
  183. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  184. package/dist/core/storage/atomic-json.js +15 -0
  185. package/dist/core/storage/builtins.d.ts.map +1 -1
  186. package/dist/core/storage/builtins.js +45 -6
  187. package/dist/core/storage/logger.d.ts +2 -0
  188. package/dist/core/storage/logger.d.ts.map +1 -1
  189. package/dist/core/storage/logger.js +2 -0
  190. package/dist/core/storage/query.d.ts +14 -0
  191. package/dist/core/storage/query.d.ts.map +1 -1
  192. package/dist/core/storage/query.js +51 -0
  193. package/dist/core/supervisor/activity.d.ts +4 -0
  194. package/dist/core/supervisor/activity.d.ts.map +1 -0
  195. package/dist/core/supervisor/activity.js +141 -0
  196. package/dist/core/supervisor/contract.d.ts +268 -0
  197. package/dist/core/supervisor/contract.d.ts.map +1 -0
  198. package/dist/core/supervisor/contract.js +35 -0
  199. package/dist/core/supervisor/explanations.d.ts +4 -0
  200. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  201. package/dist/core/supervisor/explanations.js +71 -0
  202. package/dist/core/supervisor/findings.d.ts +20 -0
  203. package/dist/core/supervisor/findings.d.ts.map +1 -0
  204. package/dist/core/supervisor/findings.js +399 -0
  205. package/dist/core/supervisor/history.d.ts +9 -0
  206. package/dist/core/supervisor/history.d.ts.map +1 -0
  207. package/dist/core/supervisor/history.js +46 -0
  208. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  209. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  210. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  211. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  212. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  213. package/dist/core/supervisor/hook-health-storage.js +24 -0
  214. package/dist/core/supervisor/hook-health.d.ts +72 -0
  215. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  216. package/dist/core/supervisor/hook-health.js +203 -0
  217. package/dist/core/supervisor/hooks.d.ts +10 -0
  218. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  219. package/dist/core/supervisor/hooks.js +28 -0
  220. package/dist/core/supervisor/index.d.ts +15 -0
  221. package/dist/core/supervisor/index.d.ts.map +1 -0
  222. package/dist/core/supervisor/index.js +14 -0
  223. package/dist/core/supervisor/log-feed.d.ts +9 -0
  224. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  225. package/dist/core/supervisor/log-feed.js +103 -0
  226. package/dist/core/supervisor/service.d.ts +29 -0
  227. package/dist/core/supervisor/service.d.ts.map +1 -0
  228. package/dist/core/supervisor/service.js +450 -0
  229. package/dist/core/supervisor/services.d.ts +11 -0
  230. package/dist/core/supervisor/services.d.ts.map +1 -0
  231. package/dist/core/supervisor/services.js +155 -0
  232. package/dist/core/supervisor/status.d.ts +3 -0
  233. package/dist/core/supervisor/status.d.ts.map +1 -0
  234. package/dist/core/supervisor/status.js +48 -0
  235. package/dist/core/supervisor/storage.d.ts +30 -0
  236. package/dist/core/supervisor/storage.d.ts.map +1 -0
  237. package/dist/core/supervisor/storage.js +73 -0
  238. package/dist/core/supervisor/timeline.d.ts +3 -0
  239. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  240. package/dist/core/supervisor/timeline.js +87 -0
  241. package/dist/core/workflow/admission.d.ts +23 -0
  242. package/dist/core/workflow/admission.d.ts.map +1 -0
  243. package/dist/core/workflow/admission.js +136 -0
  244. package/dist/core/workflow/engine.d.ts.map +1 -1
  245. package/dist/core/workflow/engine.js +77 -2
  246. package/dist/core/workflow/index.d.ts +3 -2
  247. package/dist/core/workflow/index.d.ts.map +1 -1
  248. package/dist/core/workflow/index.js +2 -1
  249. package/dist/core/workflow/proof.d.ts +2 -1
  250. package/dist/core/workflow/proof.d.ts.map +1 -1
  251. package/dist/core/workflow/proof.js +39 -1
  252. package/dist/core/workflow/run-state.d.ts +3 -2
  253. package/dist/core/workflow/run-state.d.ts.map +1 -1
  254. package/dist/core/workflow/run-state.js +8 -1
  255. package/dist/core/workflow/types.d.ts +33 -1
  256. package/dist/core/workflow/types.d.ts.map +1 -1
  257. package/dist/core/workflow/types.js +2 -1
  258. package/dist/lib/admission.d.ts +71 -0
  259. package/dist/lib/admission.d.ts.map +1 -0
  260. package/dist/lib/admission.js +264 -0
  261. package/dist/lib/agent-browser/client.d.ts +1 -1
  262. package/dist/lib/agent-browser/client.d.ts.map +1 -1
  263. package/dist/lib/agent-browser/client.js +1 -5
  264. package/dist/lib/browser/capture-fidelity.d.ts +39 -0
  265. package/dist/lib/browser/capture-fidelity.d.ts.map +1 -0
  266. package/dist/lib/browser/capture-fidelity.js +84 -0
  267. package/dist/lib/browser/client.d.ts +46 -1
  268. package/dist/lib/browser/client.d.ts.map +1 -1
  269. package/dist/lib/browser/client.js +168 -9
  270. package/dist/lib/browser/critique.d.ts +38 -1
  271. package/dist/lib/browser/critique.d.ts.map +1 -1
  272. package/dist/lib/browser/critique.js +34 -6
  273. package/dist/lib/browser/index.d.ts +5 -1
  274. package/dist/lib/browser/index.d.ts.map +1 -1
  275. package/dist/lib/browser/index.js +4 -0
  276. package/dist/lib/browser/page-review-judge.d.ts +64 -0
  277. package/dist/lib/browser/page-review-judge.d.ts.map +1 -0
  278. package/dist/lib/browser/page-review-judge.js +270 -0
  279. package/dist/lib/browser/page-review-pack.d.ts +613 -0
  280. package/dist/lib/browser/page-review-pack.d.ts.map +1 -0
  281. package/dist/lib/browser/page-review-pack.js +1751 -0
  282. package/dist/lib/browser/qa-run-contracts.d.ts +377 -0
  283. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  284. package/dist/lib/browser/qa-run-contracts.js +361 -0
  285. package/dist/lib/browser/qa-run.d.ts +141 -0
  286. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  287. package/dist/lib/browser/qa-run.js +1283 -0
  288. package/dist/lib/browser/request-diagnostics.d.ts +13 -0
  289. package/dist/lib/browser/request-diagnostics.d.ts.map +1 -0
  290. package/dist/lib/browser/request-diagnostics.js +18 -0
  291. package/dist/lib/browser/tiling.d.ts +19 -0
  292. package/dist/lib/browser/tiling.d.ts.map +1 -1
  293. package/dist/lib/browser/tiling.js +28 -0
  294. package/dist/lib/cookies/client.d.ts +9 -0
  295. package/dist/lib/cookies/client.d.ts.map +1 -1
  296. package/dist/lib/cookies/client.js +197 -44
  297. package/dist/lib/cookies/extra.d.ts +18 -0
  298. package/dist/lib/cookies/extra.d.ts.map +1 -0
  299. package/dist/lib/cookies/extra.js +14 -0
  300. package/dist/lib/cookies/index.d.ts +2 -1
  301. package/dist/lib/cookies/index.d.ts.map +1 -1
  302. package/dist/lib/cookies/index.js +2 -1
  303. package/dist/lib/coord-root-id.d.ts +9 -0
  304. package/dist/lib/coord-root-id.d.ts.map +1 -0
  305. package/dist/lib/coord-root-id.js +17 -0
  306. package/dist/lib/durable-job.d.ts +124 -0
  307. package/dist/lib/durable-job.d.ts.map +1 -0
  308. package/dist/lib/durable-job.js +296 -0
  309. package/dist/lib/http/client.d.ts +7 -1
  310. package/dist/lib/http/client.d.ts.map +1 -1
  311. package/dist/lib/http/client.js +2 -0
  312. package/dist/lib/instructions/apply.d.ts +3 -1
  313. package/dist/lib/instructions/apply.d.ts.map +1 -1
  314. package/dist/lib/instructions/apply.js +74 -3
  315. package/dist/lib/instructions/templates.d.ts.map +1 -1
  316. package/dist/lib/instructions/templates.js +11 -2
  317. package/package.json +19 -2
  318. package/src/commander.ts +75 -1
  319. package/src/commands/admission.ts +699 -0
  320. package/src/commands/agents.ts +189 -50
  321. package/src/commands/artifacts.ts +132 -18
  322. package/src/commands/browse-ai.ts +10 -5
  323. package/src/commands/browse.ts +496 -20
  324. package/src/commands/diagnostics.ts +271 -0
  325. package/src/commands/fetch.ts +1 -0
  326. package/src/commands/files.ts +102 -2
  327. package/src/commands/governor.ts +8 -0
  328. package/src/commands/ledger-v3.ts +82 -0
  329. package/src/commands/qa-record.ts +682 -0
  330. package/src/commands/qa-run.ts +503 -0
  331. package/src/commands/qa-status.ts +608 -0
  332. package/src/commands/qa-verify.ts +238 -0
  333. package/src/commands/resources.ts +38 -0
  334. package/src/commands/review-pack.ts +1281 -0
  335. package/src/commands/supervisor.ts +141 -0
  336. package/src/commands/web.ts +19 -0
  337. package/src/commands/workflow.ts +8 -0
  338. package/src/core/agents/cli.ts +7 -3
  339. package/src/core/agents/health.ts +142 -0
  340. package/src/core/agents/mailbox.ts +373 -0
  341. package/src/core/agents/qa-signal.ts +261 -0
  342. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  343. package/src/core/agents/render/prompt-context.ts +42 -12
  344. package/src/core/agents/render/session-context.ts +37 -3
  345. package/src/core/agents/rules/commit-conflict.ts +58 -1
  346. package/src/core/agents/rules/stop-hook.ts +6 -12
  347. package/src/core/agents/session-finalizer-v3.ts +4 -1
  348. package/src/core/agents/session-name-display.ts +78 -7
  349. package/src/core/agents/state/heartbeat-reader.ts +7 -0
  350. package/src/core/agents/state/heartbeat-writer.ts +23 -0
  351. package/src/core/agents/state/live-coordination-view.ts +1 -0
  352. package/src/core/agents/state/live-coordination-writer.ts +5 -0
  353. package/src/core/artifacts/constants.ts +1 -1
  354. package/src/core/artifacts/index.ts +565 -30
  355. package/src/core/config.ts +134 -2
  356. package/src/core/diagnostics/advice.ts +147 -0
  357. package/src/core/diagnostics/bundle.ts +1250 -0
  358. package/src/core/diagnostics/comparison.ts +565 -0
  359. package/src/core/diagnostics/contract.ts +311 -0
  360. package/src/core/diagnostics/identity.ts +11 -0
  361. package/src/core/diagnostics/index.ts +7 -0
  362. package/src/core/diagnostics/replay.ts +314 -0
  363. package/src/core/diagnostics/sanitize.ts +97 -0
  364. package/src/core/events/legacy-storage/compression.ts +130 -0
  365. package/src/core/events/legacy-storage/index.ts +1 -0
  366. package/src/core/events/v3/archive-retention.ts +251 -0
  367. package/src/core/events/v3/bootstrap.ts +10 -0
  368. package/src/core/events/v3/capabilities.ts +2 -0
  369. package/src/core/events/v3/coordination-view.ts +100 -11
  370. package/src/core/events/v3/index.ts +9 -0
  371. package/src/core/events/v3/live-routing.ts +1 -0
  372. package/src/core/events/v3/producers/hook-base.ts +14 -2
  373. package/src/core/events/v3/producers/intake.ts +10 -2
  374. package/src/core/events/v3/producers/recorder.ts +420 -17
  375. package/src/core/hooks/adapter/output.ts +12 -1
  376. package/src/core/hooks/cli.ts +332 -247
  377. package/src/core/hooks/health.ts +146 -0
  378. package/src/core/hooks/resolve/transcript.ts +10 -3
  379. package/src/core/hooks/session-name-presence.ts +6 -1
  380. package/src/core/qa-artifacts.ts +126 -0
  381. package/src/core/resources/contract.ts +105 -0
  382. package/src/core/resources/index.ts +5 -0
  383. package/src/core/resources/sampler.ts +590 -0
  384. package/src/core/resources/service-status.ts +62 -0
  385. package/src/core/resources/service.ts +337 -0
  386. package/src/core/resources/storage.ts +44 -0
  387. package/src/core/storage/atomic-json.ts +15 -0
  388. package/src/core/storage/builtins.ts +62 -6
  389. package/src/core/storage/logger.ts +2 -0
  390. package/src/core/storage/query.ts +58 -0
  391. package/src/core/supervisor/activity.ts +193 -0
  392. package/src/core/supervisor/contract.ts +312 -0
  393. package/src/core/supervisor/explanations.ts +104 -0
  394. package/src/core/supervisor/findings.ts +788 -0
  395. package/src/core/supervisor/history.ts +61 -0
  396. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  397. package/src/core/supervisor/hook-health-storage.ts +25 -0
  398. package/src/core/supervisor/hook-health.ts +301 -0
  399. package/src/core/supervisor/hooks.ts +35 -0
  400. package/src/core/supervisor/index.ts +14 -0
  401. package/src/core/supervisor/log-feed.ts +131 -0
  402. package/src/core/supervisor/service.ts +511 -0
  403. package/src/core/supervisor/services.ts +203 -0
  404. package/src/core/supervisor/status.ts +59 -0
  405. package/src/core/supervisor/storage.ts +126 -0
  406. package/src/core/supervisor/timeline.ts +133 -0
  407. package/src/core/workflow/admission.ts +215 -0
  408. package/src/core/workflow/engine.ts +93 -1
  409. package/src/core/workflow/index.ts +10 -0
  410. package/src/core/workflow/proof.ts +54 -1
  411. package/src/core/workflow/run-state.ts +13 -1
  412. package/src/core/workflow/types.ts +39 -1
  413. package/src/lib/admission.ts +347 -0
  414. package/src/lib/agent-browser/client.ts +2 -10
  415. package/src/lib/browser/capture-fidelity.ts +98 -0
  416. package/src/lib/browser/client.ts +212 -10
  417. package/src/lib/browser/critique.ts +62 -7
  418. package/src/lib/browser/index.ts +65 -0
  419. package/src/lib/browser/page-review-judge.ts +360 -0
  420. package/src/lib/browser/page-review-pack.ts +2384 -0
  421. package/src/lib/browser/qa-run-contracts.ts +743 -0
  422. package/src/lib/browser/qa-run.ts +1462 -0
  423. package/src/lib/browser/request-diagnostics.ts +27 -0
  424. package/src/lib/browser/tiling.ts +32 -0
  425. package/src/lib/cookies/client.ts +228 -42
  426. package/src/lib/cookies/extra.ts +28 -0
  427. package/src/lib/cookies/index.ts +2 -0
  428. package/src/lib/coord-root-id.ts +23 -0
  429. package/src/lib/durable-job.ts +407 -0
  430. package/src/lib/http/client.ts +13 -1
  431. package/src/lib/instructions/apply.ts +81 -2
  432. package/src/lib/instructions/templates.ts +11 -2
@@ -0,0 +1,77 @@
1
+ /**
2
+ * The canonical supervisor reconcile pass.
3
+ *
4
+ * Two things have to happen together, in this order:
5
+ *
6
+ * 1. `staleSweep` prunes the disposable coordination cache under
7
+ * `.harnery/active` (plus orphaned pid-map and `.last-peer-hash` files)
8
+ * and leaves a durable lifecycle observation for every deletion.
9
+ * 2. `reconcileSessionFinalizationV3` turns those observations, along with
10
+ * archive/idle/run/host signals, into session finalization requests.
11
+ *
12
+ * Running the sweep first is what makes a single pass self-contained: the
13
+ * `lifecycle.sweep_observed` rows it writes are visible to the finalizer in
14
+ * the same pass, so a stale session is both reaped and finalized without
15
+ * waiting for a later invocation.
16
+ *
17
+ * ADR 0077 already specified that `agents reconcile` and session start
18
+ * discover stale-sweep in the same pass. Both entry points had drifted to
19
+ * calling only the finalizer, so the sweep never ran outside its own
20
+ * subcommand. This module is the single composition both now share; adding a
21
+ * caller means calling this, not re-composing the two halves.
22
+ */
23
+
24
+ import {
25
+ type ReconcileSessionFinalizationOptionsV3,
26
+ type ReconcileSessionFinalizationResultV3,
27
+ reconcileSessionFinalizationV3,
28
+ } from "./session-finalizer-v3.ts";
29
+ import { staleSweep } from "./state/stale-sweep.ts";
30
+
31
+ export interface ReconcileCoordinationResultV3 extends ReconcileSessionFinalizationResultV3 {
32
+ /** Heartbeat cache files deleted from `.harnery/active` by the sweep. */
33
+ swept_heartbeats: number;
34
+ /** Orphaned `.harnery/pid-map` entries deleted by the sweep. */
35
+ swept_pidmaps: number;
36
+ /** Dead-owner `.last-peer-hash.*` files deleted by the sweep. */
37
+ swept_peer_hashes: number;
38
+ }
39
+
40
+ /**
41
+ * Sweep the disposable coordination cache, then reconcile session
42
+ * finalization, and report both sets of counts.
43
+ *
44
+ * The sweep owns its own safety contract (configured coordination freshness
45
+ * for valid rows, an additional stale-mtime gate for malformed or missing
46
+ * timestamps, and a durable audit record before any deletion, failing closed
47
+ * when neither can be written). Nothing here loosens it, and a sweep failure
48
+ * never blocks finalization: the cache is a projection that heals on the
49
+ * owning agent's next tool call, while the V3 ledger is authoritative.
50
+ */
51
+ export function reconcileCoordinationV3(
52
+ coordRoot: string,
53
+ options: ReconcileSessionFinalizationOptionsV3 = {},
54
+ ): ReconcileCoordinationResultV3 {
55
+ let sweptHeartbeats = 0;
56
+ let sweptPidmaps = 0;
57
+ let sweptPeerHashes = 0;
58
+ const sweepDiagnostics: string[] = [];
59
+ try {
60
+ const sweep = staleSweep(coordRoot);
61
+ sweptHeartbeats = sweep.heartbeatsRemoved.length;
62
+ sweptPidmaps = sweep.pidmapsRemoved;
63
+ sweptPeerHashes = sweep.peerHashesRemoved;
64
+ } catch (error) {
65
+ sweepDiagnostics.push(
66
+ `stale_sweep_failed:${error instanceof Error ? error.message : String(error)}`,
67
+ );
68
+ }
69
+ const result = reconcileSessionFinalizationV3(coordRoot, options);
70
+ return {
71
+ ...result,
72
+ diagnostics: [...sweepDiagnostics, ...result.diagnostics],
73
+ swept_heartbeats: sweptHeartbeats,
74
+ swept_pidmaps: sweptPidmaps,
75
+ swept_peer_hashes: sweptPeerHashes,
76
+ };
77
+ }
@@ -46,15 +46,18 @@ import {
46
46
  import { join } from "node:path";
47
47
 
48
48
  import { coordEnv } from "../../../lib/env.ts";
49
+ import type { Adapter } from "../../adapter.ts";
49
50
  import { endOfTurnStatusCommand, hostPromptReminder, resolveBinName } from "../../config.ts";
51
+ import { canReceiveContext } from "../../hooks/adapter/output.ts";
50
52
  import { type RemoteMachine, readRemoteMachines } from "../../presence/index.ts";
53
+ import { drainMailbox, formatMailboxDelivery } from "../mailbox.ts";
51
54
  import { sessionNameDisplayBlock, sessionNameDisplayPending } from "../session-name-display.ts";
52
55
  import {
53
56
  readLiveCoordinationRow,
54
57
  readLiveCoordinationRows,
55
58
  } from "../state/live-coordination-view.ts";
56
59
  import type { AgentActivity, TaskState } from "../state/session-state.ts";
57
- import { formatPendingCouncils } from "./session-context.ts";
60
+ import { formatPendingCouncils, recordDeliveredMessages } from "./session-context.ts";
58
61
 
59
62
  interface HeartbeatRow {
60
63
  instance_id?: string;
@@ -92,6 +95,11 @@ export interface PromptContextOpts {
92
95
  /** When true, append the non-deduplicated Codex status-footer reminder.
93
96
  * The caller enables this only for human-facing Codex sessions. */
94
97
  statusFooterNudge?: boolean;
98
+ /** Adapter id this prompt is being rendered for. Gates the mailbox drain:
99
+ * an adapter that cannot receive UserPromptSubmit context must not consume
100
+ * messages here, or they are removed from the queue and never shown. When
101
+ * omitted, delivery is attempted (the default route can receive). */
102
+ adapter?: string;
95
103
  /** Adapter id whose Stop hook ENFORCES the end-of-turn ritual (task_set +
96
104
  * status check). When set, append a fresh per-prompt reminder of that
97
105
  * contract so the model satisfies it before ending the turn instead of
@@ -119,20 +127,43 @@ export function renderPromptContext(opts: PromptContextOpts): string {
119
127
  hostPromptReminder: promptReminderEnabled,
120
128
  statusFooterNudge,
121
129
  turnRitualNudge,
130
+ adapter,
122
131
  } = opts;
123
132
  const sections: string[] = [];
124
133
 
125
- // 1. Peer table with hash dedup.
134
+ // The hook route does not pass --name, so resolve it from the heartbeat.
135
+ // Every name-addressed section below (messages, councils) is silently
136
+ // skipped without it.
137
+ const resolvedName = agentName ?? readLiveCoordinationRow(coordRoot, instanceId)?.name;
138
+
139
+ // 1. Peer messages. Drained first and never deduped: a message is delivered
140
+ // exactly once, and it is the one section here that carries new information
141
+ // from another agent rather than a reminder.
142
+ //
143
+ // Draining is gated on the adapter being able to receive this event's
144
+ // context. Cursor cannot, so draining there would empty the queue into output
145
+ // that is discarded; those sessions get their messages at SessionStart, which
146
+ // Cursor does deliver.
147
+ const canDeliverHere = !adapter || canReceiveContext(adapter as Adapter, "UserPromptSubmit");
148
+ if (resolvedName && canDeliverHere) {
149
+ const delivered = drainMailbox(coordRoot, resolvedName);
150
+ if (delivered.length > 0) {
151
+ recordDeliveredMessages(instanceId, delivered);
152
+ sections.push(formatMailboxDelivery(coordRoot, delivered));
153
+ }
154
+ }
155
+
156
+ // 2. Peer table with hash dedup.
126
157
  const peerTable = computePeerTableIfChanged(coordRoot, instanceId, sessionId);
127
158
  if (peerTable) sections.push(peerTable);
128
159
 
129
- // 2. Council pending with hash dedup.
130
- if (agentName) {
131
- const councilMsg = computeCouncilPendingIfChanged(coordRoot, instanceId, agentName);
160
+ // 3. Council pending with hash dedup.
161
+ if (resolvedName) {
162
+ const councilMsg = computeCouncilPendingIfChanged(coordRoot, instanceId, resolvedName);
132
163
  if (councilMsg) sections.push(councilMsg);
133
164
  }
134
165
 
135
- // 3. First-session naming for every adapter, plus unset/stale-task reminders
166
+ // 4. First-session naming for every adapter, plus unset/stale-task reminders
136
167
  // for Cursor/Codex. One state machine avoids a second generic "task unset"
137
168
  // reminder immediately after the first-session reminder has been deduped.
138
169
  if (sessionNameNudge || taskNudge) {
@@ -143,7 +174,7 @@ export function renderPromptContext(opts: PromptContextOpts): string {
143
174
  if (nudgeMsg) sections.push(nudgeMsg);
144
175
  }
145
176
 
146
- // 4. A supported adapter gets the host reminder on every prompt. The text is
177
+ // 5. A supported adapter gets the host reminder on every prompt. The text is
147
178
  // intentionally not hash-deduped or persisted into runtime state: freshness
148
179
  // is the feature, and the project config remains its only durable copy.
149
180
  if (promptReminderEnabled) {
@@ -151,7 +182,7 @@ export function renderPromptContext(opts: PromptContextOpts): string {
151
182
  if (reminder) sections.push(reminder);
152
183
  }
153
184
 
154
- // 5. Codex gets this on every prompt. It is intentionally not hash-deduped:
185
+ // 6. Codex gets this on every prompt. It is intentionally not hash-deduped:
155
186
  // the reminder must be fresh in the model's context when it writes the reply.
156
187
  // Missing it is harmless because Codex Stop enforcement remains observe-only.
157
188
  if (statusFooterNudge) {
@@ -159,7 +190,7 @@ export function renderPromptContext(opts: PromptContextOpts): string {
159
190
  if (statusMsg) sections.push(statusMsg);
160
191
  }
161
192
 
162
- // 6. Adapters whose Stop hook ENFORCES the turn ritual get a fresh reminder
193
+ // 7. Adapters whose Stop hook ENFORCES the turn ritual get a fresh reminder
163
194
  // on every prompt. Reminding before the reply is drafted is far cheaper than
164
195
  // the bounce-and-retry the Stop hook otherwise forces: enforcement-only
165
196
  // compliance was measured missing on a double-digit share of turns. Not
@@ -188,10 +219,9 @@ function renderTurnRitualReminder(
188
219
 
189
220
  const bin = resolveBinName(coordRoot);
190
221
  const statusCommand = endOfTurnStatusCommand(coordRoot);
191
- // Claude Code renders the status box only if the reply carries it; Cursor
192
- // shows the box inline on its own, so it is never asked for a second copy.
222
+ // Claude Code and Cursor can both enforce completed assistant reply text.
193
223
  const paste =
194
- adapter === "claude-code"
224
+ adapter === "claude-code" || adapter === "cursor"
195
225
  ? " and paste its output verbatim in a fenced code block at the end of your reply"
196
226
  : "";
197
227
  return (
@@ -12,7 +12,9 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { resolveBinName, resolveHooksSetupHint } from "../../config.ts";
14
14
  import { harneryVersion, loadAdapterWiring } from "../../hooks/adapter/wiring.ts";
15
+ import { appendEntry } from "../../journal/index.ts";
15
16
  import { readRemoteMachines } from "../../presence/index.ts";
17
+ import { drainMailbox, formatMailboxDelivery, type MailboxMessageV1 } from "../mailbox.ts";
16
18
  import { readLiveCoordinationRows } from "../state/live-coordination-view.ts";
17
19
  import { readForkParent } from "../state/names.ts";
18
20
  import type { AgentActivity, TaskState } from "../state/session-state.ts";
@@ -82,13 +84,24 @@ export function renderSessionContext(opts: RenderOpts): string {
82
84
  );
83
85
  }
84
86
 
85
- // 3. Council invites
87
+ // 3. Peer messages queued for this name while no session held it. Draining
88
+ // here is what makes `agents ping` safe to use on a dormant agent: the sender
89
+ // never has to know whether the recipient is running.
90
+ if (agentName) {
91
+ const delivered = drainMailbox(coordRoot, agentName);
92
+ if (delivered.length > 0) {
93
+ recordDeliveredMessages(instanceId, delivered);
94
+ messages.push(formatMailboxDelivery(coordRoot, delivered));
95
+ }
96
+ }
97
+
98
+ // 4. Council invites
86
99
  if (agentName) {
87
100
  const councilMsg = formatPendingCouncils(coordRoot, agentName);
88
101
  if (councilMsg) messages.push(councilMsg);
89
102
  }
90
103
 
91
- // 4. Commit-guard wiring check
104
+ // 5. Commit-guard wiring check
92
105
  const wiringIssues = checkWiring(coordRoot);
93
106
  if (wiringIssues.length > 0) {
94
107
  const hint = resolveHooksSetupHint(coordRoot);
@@ -99,7 +112,7 @@ export function renderSessionContext(opts: RenderOpts): string {
99
112
  messages.push(wiringSummary);
100
113
  }
101
114
 
102
- // 5. Adapter-hook drift: a harnery upgrade changed the hook set, but this
115
+ // 6. Adapter-hook drift: a harnery upgrade changed the hook set, but this
103
116
  // project's settings file hasn't been re-wired. Only fires for a adapter the
104
117
  // project already opted into (≥1 hook wired), so it never nags a project that
105
118
  // simply has a settings file. Remedy is always `<bin> init` (idempotent).
@@ -125,6 +138,27 @@ export function renderSessionContext(opts: RenderOpts): string {
125
138
  return messages.join("\n\n");
126
139
  }
127
140
 
141
+ /**
142
+ * Write delivered peer messages into the recipient's own journal, so they
143
+ * survive context compaction the way any other coordination breadcrumb does.
144
+ * A message the sender already journaled (the recipient was live when it was
145
+ * sent) is skipped to avoid a duplicate entry.
146
+ */
147
+ export function recordDeliveredMessages(
148
+ instanceId: string,
149
+ messages: readonly MailboxMessageV1[],
150
+ ): void {
151
+ for (const m of messages) {
152
+ if (m.journaled) continue;
153
+ try {
154
+ appendEntry(instanceId, "handoff", `from agent-${m.from_name}: ${m.body}`);
155
+ } catch {
156
+ // A journal write failure must never cost the delivery itself; the
157
+ // message is already in the rendered context the model reads.
158
+ }
159
+ }
160
+ }
161
+
128
162
  /** One-line-per-agent view of sessions on other machines (presence refs). */
129
163
  function formatRemoteMachines(coordRoot: string): string {
130
164
  try {
@@ -25,6 +25,7 @@ import { appendFileSync, existsSync } from "node:fs";
25
25
  import { basename, dirname, join } from "node:path";
26
26
  import { coordFreshnessSeconds } from "../../config.ts";
27
27
  import { resolveOwner } from "../../hooks/resolve/owner.ts";
28
+ import { recordLiveClaimChangeV3 } from "../live-authority-v3.ts";
28
29
  import { readLiveCoordinationRows } from "../state/live-coordination-view.ts";
29
30
  import { instanceHasLivePid } from "../state/pidmap.ts";
30
31
 
@@ -34,6 +35,7 @@ interface PeerHeartbeat {
34
35
  name?: string;
35
36
  files_touched?: string[];
36
37
  last_heartbeat?: string;
38
+ platform?: string;
37
39
  }
38
40
 
39
41
  interface Conflict {
@@ -190,6 +192,15 @@ export function evaluateCommit(coordRoot: string, req: CommitVerdictRequest): Co
190
192
  };
191
193
  }
192
194
 
195
+ // A blocked commit proves this session has work in every staged path, even
196
+ // when the edit hook never acquired a claim (for example, a shell edit made
197
+ // just before another session claimed the same file). Keep those paths on
198
+ // the blocked session's durable claim set. The original holder's later
199
+ // commit will then see the overlap instead of silently sweeping this
200
+ // session's working-tree hunks. A successful later commit releases clean
201
+ // paths through the existing post-commit hook.
202
+ retainBlockedStagedPaths(coordRoot, me, mySession, req.staged_paths, peers);
203
+
193
204
  return {
194
205
  allow: false,
195
206
  exit_code: 1,
@@ -204,6 +215,41 @@ export function evaluateCommit(coordRoot: string, req: CommitVerdictRequest): Co
204
215
  };
205
216
  }
206
217
 
218
+ function retainBlockedStagedPaths(
219
+ coordRoot: string,
220
+ instanceId: string,
221
+ sessionId: string,
222
+ stagedPaths: readonly string[],
223
+ peers: readonly PeerHeartbeat[],
224
+ ): void {
225
+ if (instanceId === "__unattributed__") return;
226
+ const self = peers.find((peer) => peer.instance_id === instanceId);
227
+ if (!self) return;
228
+
229
+ for (const path of stagedPaths) {
230
+ try {
231
+ recordLiveClaimChangeV3({
232
+ coordRoot,
233
+ owner: instanceId,
234
+ nativeSessionId: self.session_id ?? sessionId,
235
+ adapter: adapterFromPlatform(self.platform),
236
+ operation: "acquired",
237
+ path,
238
+ access: "write",
239
+ });
240
+ } catch {
241
+ // The commit is already blocked. Coordination remains fail-open for
242
+ // authority-write failures so a damaged cache cannot brick Git.
243
+ }
244
+ }
245
+ }
246
+
247
+ function adapterFromPlatform(platform: unknown): "claude-code" | "cursor" | "codex" {
248
+ if (platform === "cursor") return "cursor";
249
+ if (platform === "codex") return "codex";
250
+ return "claude-code";
251
+ }
252
+
207
253
  function findOverlap(
208
254
  staged: string,
209
255
  files: readonly string[],
@@ -269,6 +315,18 @@ function isHolderSelfAttributed(
269
315
  stagedSet: Set<string>,
270
316
  peers: readonly PeerHeartbeat[],
271
317
  ): boolean {
318
+ const holder = peers.find((p) => p.instance_id === holderId);
319
+
320
+ // Windows-hosted Codex sessions cross a process boundary into WSL, so they
321
+ // deliberately have no usable PID anchor. Their canonical V3 session row is
322
+ // the foreign-identity evidence instead. Treating that row as a transient
323
+ // self identity would suppress the symmetric claim retained after a blocked
324
+ // commit and reopen the working-tree sweep this guard is meant to stop.
325
+ if (holder?.platform === "codex" && holder.session_id) {
326
+ debugGate(coordRoot, { holder: holderId, gate: "B", reason: "canonical_codex_session" });
327
+ return false;
328
+ }
329
+
272
330
  // Gate B (cheaper): live foreign pid-map entry blocks self-attribution.
273
331
  if (holderHasLiveForeignPid(coordRoot, holderId)) {
274
332
  debugGate(coordRoot, { holder: holderId, gate: "B", reason: "live_pid_anchor" });
@@ -276,7 +334,6 @@ function isHolderSelfAttributed(
276
334
  }
277
335
 
278
336
  // Gate A: every held path is either in the staged set or already clean in HEAD.
279
- const holder = peers.find((p) => p.instance_id === holderId);
280
337
  if (!holder) {
281
338
  debugGate(coordRoot, { holder: holderId, gate: "A", reason: "holder_not_found" });
282
339
  return false;
@@ -154,22 +154,16 @@ export function evaluateStopHookV3Events(
154
154
  .map((event) => observedBoolean(event.payload.ritual?.status_box_present_strict))
155
155
  .filter((value): value is boolean => value !== undefined);
156
156
  const currentStrictBox = claudeContinuation ? req.status_box_present_strict : undefined;
157
- if (req.adapter !== "cursor" && latestBox === undefined && currentStrictBox === undefined) {
157
+ if (latestBox === undefined && currentStrictBox === undefined) {
158
158
  return evidenceUnavailable(
159
159
  "turn.completed has no observed V3 assistant-text status-box result; Stop remains fail-open",
160
160
  );
161
161
  }
162
162
 
163
- const ackPresent =
164
- req.adapter === "cursor"
165
- ? requiredStatusChecked
166
- : claudeContinuation
167
- ? strictBoxObservations.includes(true) || currentStrictBox === true
168
- : latestBox === true;
169
- const ackBlock =
170
- req.adapter === "cursor"
171
- ? () => rule13Block(coordRoot, req.session_id)
172
- : () => rule23Block(coordRoot, req.session_id);
163
+ const ackPresent = claudeContinuation
164
+ ? strictBoxObservations.includes(true) || currentStrictBox === true
165
+ : latestBox === true;
166
+ const ackBlock = () => rule23Block(coordRoot, req.session_id);
173
167
 
174
168
  // Pure prose turns keep the human-visible acknowledgement but do not require
175
169
  // task or final-status mutations. This preserves the pre-V3 policy exactly.
@@ -354,7 +348,7 @@ function sessionIdSuffix(sessionId?: string): string {
354
348
  * The verdict keeps the first failure's rule id, so the `blocked_rule`
355
349
  * telemetry and the adapter's `rule=` stderr contract are unchanged; the
356
350
  * reason enumerates the rest so a single continuation can repair all of them.
357
- * Cursor maps the acknowledgement onto rule 1/3, so identical rules dedupe.
351
+ * Duplicate rules are folded so one continuation repairs every missing signal.
358
352
  */
359
353
  function mergeStopBlocks(blocks: VerdictResult[]): VerdictResult | undefined {
360
354
  let primary: VerdictResult | undefined;
@@ -584,7 +584,10 @@ function observeCascades(
584
584
  }
585
585
  if (
586
586
  event.event_type === "lifecycle.sweep_observed" &&
587
- event.payload.observation === "stale_sweep"
587
+ // stale-sweep.ts emits the observation as `stale_heartbeat`; the
588
+ // finalization request it produces is the one named `stale_sweep`.
589
+ // Matching the request name here made this branch unreachable.
590
+ event.payload.observation === "stale_heartbeat"
588
591
  ) {
589
592
  const record = records.find(
590
593
  ({ state }) => !state.terminal && state.instance_id === event.payload.subject_instance_id,
@@ -8,6 +8,7 @@
8
8
  export interface SessionNameDisplayState {
9
9
  suggested_session_name?: string;
10
10
  session_name_seen_for?: string;
11
+ session_name_display_requested_for?: string;
11
12
  }
12
13
 
13
14
  export const SESSION_NAME_DISPLAY_NOTE =
@@ -21,6 +22,45 @@ export function sessionNameDisplayPending(
21
22
  return name;
22
23
  }
23
24
 
25
+ /**
26
+ * Every title whose exact display satisfies the current latch.
27
+ *
28
+ * The pending title always counts. The title the agent was last instructed to
29
+ * display also counts, because the harness — not the agent — is what changed
30
+ * the target after asking. Accepting it costs nothing the operator can see: a
31
+ * clean one-line block did open that reply. Refusing it left Cursor sessions
32
+ * permanently latched, since no Cursor surface can re-open a missed window.
33
+ */
34
+ export function sessionNameDisplayAcceptedNames(
35
+ row: SessionNameDisplayState | null | undefined,
36
+ ): string[] {
37
+ const pending = sessionNameDisplayPending(row);
38
+ if (!pending) return [];
39
+ const requested = row?.session_name_display_requested_for;
40
+ return requested && requested !== pending ? [pending, requested] : [pending];
41
+ }
42
+
43
+ /**
44
+ * Match assistant text against any accepted title and report which one closed
45
+ * the latch, so a drifted display can be stamped against the pending title and
46
+ * logged as drift rather than silently discarded.
47
+ */
48
+ export function matchSessionNameDisplay(
49
+ row: SessionNameDisplayState | null | undefined,
50
+ text: string | undefined,
51
+ startsWithBlock: (
52
+ text: string,
53
+ expectedName: string,
54
+ ) => boolean = assistantTextStartsWithSessionNameBlock,
55
+ ): { pending: string; displayed: string } | null {
56
+ if (!text) return null;
57
+ const accepted = sessionNameDisplayAcceptedNames(row);
58
+ const pending = accepted[0];
59
+ if (!pending) return null;
60
+ const displayed = accepted.find((candidate) => startsWithBlock(text, candidate));
61
+ return displayed ? { pending, displayed } : null;
62
+ }
63
+
24
64
  export function sessionNameDisplayBlock(name: string): string {
25
65
  return `\`\`\`\n${name}\n\`\`\``;
26
66
  }
@@ -80,10 +120,16 @@ function responseContainsSessionNameMint(
80
120
  return true;
81
121
  }
82
122
  } catch {
83
- // Wrapper prose and partial JSON are not authoritative mint evidence.
123
+ // Fall through to the textual check below.
84
124
  }
85
125
  }
86
- return false;
126
+ // The mint command's stdout often reaches the transcript mangled: cut
127
+ // short by `cut`/`head -c`, or with a tail-truncated first line. Whole-JSON
128
+ // parsing then fails even though the name and the mint flag are both
129
+ // legible, and a session that did mint can never latch. Accept the text
130
+ // when the exact quoted name sits under its key AND a mint flag is true;
131
+ // ordinary status output carries the name without any flag.
132
+ return textCarriesSessionNameMint(value, name);
87
133
  }
88
134
 
89
135
  if (typeof value !== "object") return false;
@@ -107,16 +153,41 @@ function responseContainsSessionNameMint(
107
153
  );
108
154
  }
109
155
 
156
+ const MINT_FLAG_PATTERN = /"(?:first_of_session|name_reminted|session_name_retry)"\s*:\s*true\b/;
157
+
158
+ function textCarriesSessionNameMint(text: string, name: string): boolean {
159
+ const quotedName = JSON.stringify(name);
160
+ const keyIndex = text.indexOf('"suggested_session_name"');
161
+ if (keyIndex < 0) return false;
162
+ const afterKey = text.slice(keyIndex + '"suggested_session_name"'.length);
163
+ const valueMatch = /^\s*:\s*("(?:[^"\\]|\\.)*")/.exec(afterKey);
164
+ if (!valueMatch || valueMatch[1] !== quotedName) return false;
165
+ return MINT_FLAG_PATTERN.test(text);
166
+ }
167
+
110
168
  /**
111
- * Accept the exact unlabelled block Harnery requests. `text` and `plaintext`
112
- * are tolerated for older prompts, but the code block may contain only the
113
- * suggested name and must be the first non-whitespace user-facing content.
169
+ * A leading fenced block whose only line is the suggested name.
170
+ *
171
+ * What the contract protects is what the operator sees: the reply opens with a
172
+ * code block containing exactly the title, so it renders as one clean
173
+ * copy-pasteable line. The fence's info string changes none of that, so any
174
+ * single-word language tag passes. Restricting it to `text`/`plaintext` meant a
175
+ * model that wrote ```txt or ```markdown produced a display the operator could
176
+ * read perfectly while the latch refused to close — and since nothing else can
177
+ * close a Cursor latch, that mismatch stranded the whole session.
178
+ *
179
+ * Still exact in every load-bearing way: the block is the first user-facing
180
+ * content, holds one line, and that line equals the name. The backreference
181
+ * requires the closing fence to match the opening run, so a longer fence cannot
182
+ * be closed by a shorter one.
114
183
  */
184
+ const LEADING_SESSION_NAME_BLOCK =
185
+ /^(`{3,})[ \t]*[A-Za-z0-9_.+#-]*[ \t]*\n([^\n]*)\n\1[ \t]*(?:\n|$)/;
186
+
115
187
  export function assistantTextStartsWithSessionNameBlock(text: string, name: string): boolean {
116
188
  if (!text || !name) return false;
117
189
  const normalized = text.replace(/\r\n?/g, "\n").trimStart();
118
- const match = /^```(?:text|plaintext)?[ \t]*\n([^\n]*)\n```(?:[ \t]*(?:\n|$))/.exec(normalized);
119
- return match?.[1] === name;
190
+ return LEADING_SESSION_NAME_BLOCK.exec(normalized)?.[2] === name;
120
191
  }
121
192
 
122
193
  /**
@@ -33,6 +33,13 @@ export interface Heartbeat {
33
33
  session_name_seen_at?: string;
34
34
  /** The specific suggested name proven present by the sighting stamp. */
35
35
  session_name_seen_for?: string;
36
+ /**
37
+ * The suggested name the agent was last actually instructed to display.
38
+ * Usually identical to `suggested_session_name`; it differs only when the
39
+ * title changed after the instruction went out, and a display of it still
40
+ * counts so that drift cannot strand the latch.
41
+ */
42
+ session_name_display_requested_for?: string;
36
43
  last_status_at?: string;
37
44
  current_turn_id?: string;
38
45
  parent_instance_id?: string;
@@ -180,6 +180,29 @@ export function stampSessionNameSeen(
180
180
  }));
181
181
  }
182
182
 
183
+ /**
184
+ * Remember which title the agent was actually asked to display.
185
+ *
186
+ * The gate can only ask for a display through the PostToolUse instruction, and
187
+ * the title it names can change afterwards (an assigned-name rewrite of an
188
+ * `Agent unknown - ...` suggestion, a lifecycle re-mint, a rebuilt cache that
189
+ * re-minted from the current task). Without this record the agent's correct
190
+ * display of the title it was handed matches nothing, the latch never closes,
191
+ * and on Cursor no later surface can repair it.
192
+ */
193
+ export function stampSessionNameRequested(
194
+ coordRoot: string,
195
+ instanceId: string,
196
+ name: string,
197
+ ): Heartbeat | null {
198
+ if (!name) return null;
199
+ return mutate(coordRoot, instanceId, (hb) =>
200
+ hb.session_name_display_requested_for === name
201
+ ? hb
202
+ : { ...hb, session_name_display_requested_for: name },
203
+ );
204
+ }
205
+
183
206
  export function releaseClaim(
184
207
  coordRoot: string,
185
208
  instanceId: string,
@@ -197,6 +197,7 @@ export function projectHeartbeatV3(
197
197
  suggested_session_name: cache?.suggested_session_name,
198
198
  session_name_seen_at: cache?.session_name_seen_at,
199
199
  session_name_seen_for: cache?.session_name_seen_for,
200
+ session_name_display_requested_for: cache?.session_name_display_requested_for,
200
201
  workflow_run_id: generation.run_id,
201
202
  workflow_agent_id: generation.workflow_agent_id,
202
203
  parent_instance_id: parent ? nativeInstanceIdV3(parent.instance_id) : undefined,
@@ -107,6 +107,11 @@ function materializeLiveCoordinationHeartbeat(
107
107
  ...(current.session_name_seen_at
108
108
  ? { session_name_seen_at: current.session_name_seen_at }
109
109
  : {}),
110
+ ...(current.session_name_display_requested_for
111
+ ? {
112
+ session_name_display_requested_for: current.session_name_display_requested_for,
113
+ }
114
+ : {}),
110
115
  }
111
116
  : {};
112
117
  const materialized: V3HeartbeatMaterialization = {
@@ -1,4 +1,4 @@
1
1
  /** Read-only artifact layout constants safe for browser-side server modules. */
2
2
  export const ARTIFACTS_DIR = ".harnery/artifacts";
3
3
  export const ARTIFACT_MANIFEST = ".harnery-artifact.json";
4
- export const ARTIFACT_SCHEMA_VERSION = 1 as const;
4
+ export const ARTIFACT_SCHEMA_VERSION = 2 as const;