harnery 0.36.0 → 0.37.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 (311) hide show
  1. package/dist/commander.js +4 -0
  2. package/dist/commands/agents.d.ts.map +1 -1
  3. package/dist/commands/agents.js +87 -46
  4. package/dist/commands/artifacts.d.ts.map +1 -1
  5. package/dist/commands/artifacts.js +26 -1
  6. package/dist/commands/browse.d.ts.map +1 -1
  7. package/dist/commands/browse.js +11 -0
  8. package/dist/commands/diagnostics.d.ts +4 -0
  9. package/dist/commands/diagnostics.d.ts.map +1 -0
  10. package/dist/commands/diagnostics.js +229 -0
  11. package/dist/commands/files.d.ts +12 -0
  12. package/dist/commands/files.d.ts.map +1 -1
  13. package/dist/commands/files.js +54 -1
  14. package/dist/commands/governor.d.ts.map +1 -1
  15. package/dist/commands/governor.js +4 -0
  16. package/dist/commands/ledger-v3.d.ts.map +1 -1
  17. package/dist/commands/ledger-v3.js +71 -2
  18. package/dist/commands/qa-run.d.ts +4 -0
  19. package/dist/commands/qa-run.d.ts.map +1 -0
  20. package/dist/commands/qa-run.js +141 -0
  21. package/dist/commands/resources.d.ts +4 -0
  22. package/dist/commands/resources.d.ts.map +1 -0
  23. package/dist/commands/resources.js +29 -0
  24. package/dist/commands/supervisor.d.ts +4 -0
  25. package/dist/commands/supervisor.d.ts.map +1 -0
  26. package/dist/commands/supervisor.js +101 -0
  27. package/dist/commands/web.d.ts.map +1 -1
  28. package/dist/commands/web.js +17 -0
  29. package/dist/commands/workflow.d.ts.map +1 -1
  30. package/dist/commands/workflow.js +4 -0
  31. package/dist/core/agents/cli.js +7 -3
  32. package/dist/core/agents/health.d.ts +17 -0
  33. package/dist/core/agents/health.d.ts.map +1 -0
  34. package/dist/core/agents/health.js +109 -0
  35. package/dist/core/agents/mailbox.d.ts +92 -0
  36. package/dist/core/agents/mailbox.d.ts.map +1 -0
  37. package/dist/core/agents/mailbox.js +325 -0
  38. package/dist/core/agents/reconcile-coordination-v3.d.ts +44 -0
  39. package/dist/core/agents/reconcile-coordination-v3.d.ts.map +1 -0
  40. package/dist/core/agents/reconcile-coordination-v3.js +58 -0
  41. package/dist/core/agents/render/prompt-context.d.ts +5 -0
  42. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  43. package/dist/core/agents/render/prompt-context.js +34 -13
  44. package/dist/core/agents/render/session-context.d.ts +8 -0
  45. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  46. package/dist/core/agents/render/session-context.js +34 -3
  47. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -1
  48. package/dist/core/agents/rules/commit-conflict.js +50 -1
  49. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -1
  50. package/dist/core/agents/rules/stop-hook.js +6 -10
  51. package/dist/core/agents/session-finalizer-v3.js +4 -1
  52. package/dist/core/artifacts/index.d.ts +26 -1
  53. package/dist/core/artifacts/index.d.ts.map +1 -1
  54. package/dist/core/artifacts/index.js +146 -6
  55. package/dist/core/config.d.ts +12 -0
  56. package/dist/core/config.d.ts.map +1 -1
  57. package/dist/core/config.js +43 -0
  58. package/dist/core/diagnostics/advice.d.ts +9 -0
  59. package/dist/core/diagnostics/advice.d.ts.map +1 -0
  60. package/dist/core/diagnostics/advice.js +112 -0
  61. package/dist/core/diagnostics/bundle.d.ts +35 -0
  62. package/dist/core/diagnostics/bundle.d.ts.map +1 -0
  63. package/dist/core/diagnostics/bundle.js +874 -0
  64. package/dist/core/diagnostics/comparison.d.ts +5 -0
  65. package/dist/core/diagnostics/comparison.d.ts.map +1 -0
  66. package/dist/core/diagnostics/comparison.js +444 -0
  67. package/dist/core/diagnostics/contract.d.ts +245 -0
  68. package/dist/core/diagnostics/contract.d.ts.map +1 -0
  69. package/dist/core/diagnostics/contract.js +18 -0
  70. package/dist/core/diagnostics/identity.d.ts +3 -0
  71. package/dist/core/diagnostics/identity.d.ts.map +1 -0
  72. package/dist/core/diagnostics/identity.js +10 -0
  73. package/dist/core/diagnostics/index.d.ts +8 -0
  74. package/dist/core/diagnostics/index.d.ts.map +1 -0
  75. package/dist/core/diagnostics/index.js +7 -0
  76. package/dist/core/diagnostics/replay.d.ts +8 -0
  77. package/dist/core/diagnostics/replay.d.ts.map +1 -0
  78. package/dist/core/diagnostics/replay.js +213 -0
  79. package/dist/core/diagnostics/sanitize.d.ts +9 -0
  80. package/dist/core/diagnostics/sanitize.d.ts.map +1 -0
  81. package/dist/core/diagnostics/sanitize.js +84 -0
  82. package/dist/core/events/legacy-storage/compression.d.ts +13 -0
  83. package/dist/core/events/legacy-storage/compression.d.ts.map +1 -0
  84. package/dist/core/events/legacy-storage/compression.js +107 -0
  85. package/dist/core/events/legacy-storage/index.d.ts +1 -0
  86. package/dist/core/events/legacy-storage/index.d.ts.map +1 -1
  87. package/dist/core/events/legacy-storage/index.js +1 -0
  88. package/dist/core/events/v3/archive-retention.d.ts +33 -0
  89. package/dist/core/events/v3/archive-retention.d.ts.map +1 -0
  90. package/dist/core/events/v3/archive-retention.js +195 -0
  91. package/dist/core/events/v3/capabilities.d.ts +1 -1
  92. package/dist/core/events/v3/capabilities.d.ts.map +1 -1
  93. package/dist/core/events/v3/capabilities.js +1 -0
  94. package/dist/core/events/v3/index.d.ts +1 -0
  95. package/dist/core/events/v3/index.d.ts.map +1 -1
  96. package/dist/core/events/v3/index.js +1 -0
  97. package/dist/core/events/v3/live-routing.d.ts.map +1 -1
  98. package/dist/core/events/v3/live-routing.js +1 -0
  99. package/dist/core/events/v3/producers/hook-base.d.ts +1 -1
  100. package/dist/core/events/v3/producers/hook-base.d.ts.map +1 -1
  101. package/dist/core/events/v3/producers/hook-base.js +13 -2
  102. package/dist/core/events/v3/producers/intake.d.ts.map +1 -1
  103. package/dist/core/events/v3/producers/intake.js +1 -0
  104. package/dist/core/events/v3/producers/recorder.d.ts +16 -0
  105. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  106. package/dist/core/events/v3/producers/recorder.js +88 -1
  107. package/dist/core/hooks/adapter/output.d.ts +8 -0
  108. package/dist/core/hooks/adapter/output.d.ts.map +1 -1
  109. package/dist/core/hooks/adapter/output.js +11 -1
  110. package/dist/core/hooks/cli.js +183 -192
  111. package/dist/core/hooks/health.d.ts +59 -0
  112. package/dist/core/hooks/health.d.ts.map +1 -0
  113. package/dist/core/hooks/health.js +84 -0
  114. package/dist/core/resources/contract.d.ts +90 -0
  115. package/dist/core/resources/contract.d.ts.map +1 -0
  116. package/dist/core/resources/contract.js +2 -0
  117. package/dist/core/resources/index.d.ts +6 -0
  118. package/dist/core/resources/index.d.ts.map +1 -0
  119. package/dist/core/resources/index.js +5 -0
  120. package/dist/core/resources/sampler.d.ts +27 -0
  121. package/dist/core/resources/sampler.d.ts.map +1 -0
  122. package/dist/core/resources/sampler.js +357 -0
  123. package/dist/core/resources/service-status.d.ts +3 -0
  124. package/dist/core/resources/service-status.d.ts.map +1 -0
  125. package/dist/core/resources/service-status.js +48 -0
  126. package/dist/core/resources/service.d.ts +26 -0
  127. package/dist/core/resources/service.d.ts.map +1 -0
  128. package/dist/core/resources/service.js +292 -0
  129. package/dist/core/resources/storage.d.ts +13 -0
  130. package/dist/core/resources/storage.d.ts.map +1 -0
  131. package/dist/core/resources/storage.js +30 -0
  132. package/dist/core/storage/atomic-json.d.ts +3 -0
  133. package/dist/core/storage/atomic-json.d.ts.map +1 -0
  134. package/dist/core/storage/atomic-json.js +15 -0
  135. package/dist/core/storage/builtins.d.ts.map +1 -1
  136. package/dist/core/storage/builtins.js +45 -6
  137. package/dist/core/storage/logger.d.ts +2 -0
  138. package/dist/core/storage/logger.d.ts.map +1 -1
  139. package/dist/core/storage/logger.js +2 -0
  140. package/dist/core/storage/query.d.ts +14 -0
  141. package/dist/core/storage/query.d.ts.map +1 -1
  142. package/dist/core/storage/query.js +51 -0
  143. package/dist/core/supervisor/activity.d.ts +4 -0
  144. package/dist/core/supervisor/activity.d.ts.map +1 -0
  145. package/dist/core/supervisor/activity.js +141 -0
  146. package/dist/core/supervisor/contract.d.ts +268 -0
  147. package/dist/core/supervisor/contract.d.ts.map +1 -0
  148. package/dist/core/supervisor/contract.js +35 -0
  149. package/dist/core/supervisor/explanations.d.ts +4 -0
  150. package/dist/core/supervisor/explanations.d.ts.map +1 -0
  151. package/dist/core/supervisor/explanations.js +71 -0
  152. package/dist/core/supervisor/findings.d.ts +20 -0
  153. package/dist/core/supervisor/findings.d.ts.map +1 -0
  154. package/dist/core/supervisor/findings.js +399 -0
  155. package/dist/core/supervisor/history.d.ts +9 -0
  156. package/dist/core/supervisor/history.d.ts.map +1 -0
  157. package/dist/core/supervisor/history.js +46 -0
  158. package/dist/core/supervisor/hook-health-alerts.d.ts +14 -0
  159. package/dist/core/supervisor/hook-health-alerts.d.ts.map +1 -0
  160. package/dist/core/supervisor/hook-health-alerts.js +70 -0
  161. package/dist/core/supervisor/hook-health-storage.d.ts +5 -0
  162. package/dist/core/supervisor/hook-health-storage.d.ts.map +1 -0
  163. package/dist/core/supervisor/hook-health-storage.js +24 -0
  164. package/dist/core/supervisor/hook-health.d.ts +72 -0
  165. package/dist/core/supervisor/hook-health.d.ts.map +1 -0
  166. package/dist/core/supervisor/hook-health.js +203 -0
  167. package/dist/core/supervisor/hooks.d.ts +10 -0
  168. package/dist/core/supervisor/hooks.d.ts.map +1 -0
  169. package/dist/core/supervisor/hooks.js +28 -0
  170. package/dist/core/supervisor/index.d.ts +15 -0
  171. package/dist/core/supervisor/index.d.ts.map +1 -0
  172. package/dist/core/supervisor/index.js +14 -0
  173. package/dist/core/supervisor/log-feed.d.ts +9 -0
  174. package/dist/core/supervisor/log-feed.d.ts.map +1 -0
  175. package/dist/core/supervisor/log-feed.js +103 -0
  176. package/dist/core/supervisor/service.d.ts +29 -0
  177. package/dist/core/supervisor/service.d.ts.map +1 -0
  178. package/dist/core/supervisor/service.js +450 -0
  179. package/dist/core/supervisor/services.d.ts +11 -0
  180. package/dist/core/supervisor/services.d.ts.map +1 -0
  181. package/dist/core/supervisor/services.js +155 -0
  182. package/dist/core/supervisor/status.d.ts +3 -0
  183. package/dist/core/supervisor/status.d.ts.map +1 -0
  184. package/dist/core/supervisor/status.js +48 -0
  185. package/dist/core/supervisor/storage.d.ts +30 -0
  186. package/dist/core/supervisor/storage.d.ts.map +1 -0
  187. package/dist/core/supervisor/storage.js +73 -0
  188. package/dist/core/supervisor/timeline.d.ts +3 -0
  189. package/dist/core/supervisor/timeline.d.ts.map +1 -0
  190. package/dist/core/supervisor/timeline.js +87 -0
  191. package/dist/core/workflow/admission.d.ts +23 -0
  192. package/dist/core/workflow/admission.d.ts.map +1 -0
  193. package/dist/core/workflow/admission.js +136 -0
  194. package/dist/core/workflow/engine.d.ts.map +1 -1
  195. package/dist/core/workflow/engine.js +77 -2
  196. package/dist/core/workflow/index.d.ts +3 -2
  197. package/dist/core/workflow/index.d.ts.map +1 -1
  198. package/dist/core/workflow/index.js +2 -1
  199. package/dist/core/workflow/proof.d.ts +2 -1
  200. package/dist/core/workflow/proof.d.ts.map +1 -1
  201. package/dist/core/workflow/proof.js +39 -1
  202. package/dist/core/workflow/run-state.d.ts +3 -2
  203. package/dist/core/workflow/run-state.d.ts.map +1 -1
  204. package/dist/core/workflow/run-state.js +8 -1
  205. package/dist/core/workflow/types.d.ts +33 -1
  206. package/dist/core/workflow/types.d.ts.map +1 -1
  207. package/dist/core/workflow/types.js +2 -1
  208. package/dist/lib/browser/client.d.ts +5 -0
  209. package/dist/lib/browser/client.d.ts.map +1 -1
  210. package/dist/lib/browser/client.js +1 -0
  211. package/dist/lib/browser/index.d.ts +2 -0
  212. package/dist/lib/browser/index.d.ts.map +1 -1
  213. package/dist/lib/browser/index.js +2 -0
  214. package/dist/lib/browser/qa-run-contracts.d.ts +173 -0
  215. package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -0
  216. package/dist/lib/browser/qa-run-contracts.js +226 -0
  217. package/dist/lib/browser/qa-run.d.ts +51 -0
  218. package/dist/lib/browser/qa-run.d.ts.map +1 -0
  219. package/dist/lib/browser/qa-run.js +684 -0
  220. package/dist/lib/coord-root-id.d.ts +9 -0
  221. package/dist/lib/coord-root-id.d.ts.map +1 -0
  222. package/dist/lib/coord-root-id.js +17 -0
  223. package/dist/lib/instructions/apply.d.ts +3 -1
  224. package/dist/lib/instructions/apply.d.ts.map +1 -1
  225. package/dist/lib/instructions/apply.js +74 -3
  226. package/dist/lib/instructions/templates.d.ts.map +1 -1
  227. package/dist/lib/instructions/templates.js +6 -0
  228. package/package.json +12 -1
  229. package/src/commander.ts +26 -0
  230. package/src/commands/agents.ts +102 -49
  231. package/src/commands/artifacts.ts +40 -2
  232. package/src/commands/browse.ts +15 -0
  233. package/src/commands/diagnostics.ts +271 -0
  234. package/src/commands/files.ts +102 -2
  235. package/src/commands/governor.ts +8 -0
  236. package/src/commands/ledger-v3.ts +82 -0
  237. package/src/commands/qa-run.ts +184 -0
  238. package/src/commands/resources.ts +38 -0
  239. package/src/commands/supervisor.ts +141 -0
  240. package/src/commands/web.ts +19 -0
  241. package/src/commands/workflow.ts +8 -0
  242. package/src/core/agents/cli.ts +7 -3
  243. package/src/core/agents/health.ts +142 -0
  244. package/src/core/agents/mailbox.ts +373 -0
  245. package/src/core/agents/reconcile-coordination-v3.ts +77 -0
  246. package/src/core/agents/render/prompt-context.ts +42 -12
  247. package/src/core/agents/render/session-context.ts +37 -3
  248. package/src/core/agents/rules/commit-conflict.ts +58 -1
  249. package/src/core/agents/rules/stop-hook.ts +6 -12
  250. package/src/core/agents/session-finalizer-v3.ts +4 -1
  251. package/src/core/artifacts/index.ts +196 -10
  252. package/src/core/config.ts +111 -2
  253. package/src/core/diagnostics/advice.ts +147 -0
  254. package/src/core/diagnostics/bundle.ts +1142 -0
  255. package/src/core/diagnostics/comparison.ts +565 -0
  256. package/src/core/diagnostics/contract.ts +311 -0
  257. package/src/core/diagnostics/identity.ts +11 -0
  258. package/src/core/diagnostics/index.ts +7 -0
  259. package/src/core/diagnostics/replay.ts +314 -0
  260. package/src/core/diagnostics/sanitize.ts +97 -0
  261. package/src/core/events/legacy-storage/compression.ts +130 -0
  262. package/src/core/events/legacy-storage/index.ts +1 -0
  263. package/src/core/events/v3/archive-retention.ts +251 -0
  264. package/src/core/events/v3/capabilities.ts +2 -0
  265. package/src/core/events/v3/index.ts +9 -0
  266. package/src/core/events/v3/live-routing.ts +1 -0
  267. package/src/core/events/v3/producers/hook-base.ts +14 -2
  268. package/src/core/events/v3/producers/intake.ts +1 -0
  269. package/src/core/events/v3/producers/recorder.ts +110 -1
  270. package/src/core/hooks/adapter/output.ts +12 -1
  271. package/src/core/hooks/cli.ts +192 -215
  272. package/src/core/hooks/health.ts +146 -0
  273. package/src/core/resources/contract.ts +98 -0
  274. package/src/core/resources/index.ts +5 -0
  275. package/src/core/resources/sampler.ts +458 -0
  276. package/src/core/resources/service-status.ts +62 -0
  277. package/src/core/resources/service.ts +337 -0
  278. package/src/core/resources/storage.ts +44 -0
  279. package/src/core/storage/atomic-json.ts +15 -0
  280. package/src/core/storage/builtins.ts +62 -6
  281. package/src/core/storage/logger.ts +2 -0
  282. package/src/core/storage/query.ts +58 -0
  283. package/src/core/supervisor/activity.ts +193 -0
  284. package/src/core/supervisor/contract.ts +312 -0
  285. package/src/core/supervisor/explanations.ts +104 -0
  286. package/src/core/supervisor/findings.ts +788 -0
  287. package/src/core/supervisor/history.ts +61 -0
  288. package/src/core/supervisor/hook-health-alerts.ts +108 -0
  289. package/src/core/supervisor/hook-health-storage.ts +25 -0
  290. package/src/core/supervisor/hook-health.ts +301 -0
  291. package/src/core/supervisor/hooks.ts +35 -0
  292. package/src/core/supervisor/index.ts +14 -0
  293. package/src/core/supervisor/log-feed.ts +131 -0
  294. package/src/core/supervisor/service.ts +511 -0
  295. package/src/core/supervisor/services.ts +203 -0
  296. package/src/core/supervisor/status.ts +59 -0
  297. package/src/core/supervisor/storage.ts +126 -0
  298. package/src/core/supervisor/timeline.ts +133 -0
  299. package/src/core/workflow/admission.ts +215 -0
  300. package/src/core/workflow/engine.ts +93 -1
  301. package/src/core/workflow/index.ts +10 -0
  302. package/src/core/workflow/proof.ts +54 -1
  303. package/src/core/workflow/run-state.ts +13 -1
  304. package/src/core/workflow/types.ts +39 -1
  305. package/src/lib/browser/client.ts +6 -0
  306. package/src/lib/browser/index.ts +29 -0
  307. package/src/lib/browser/qa-run-contracts.ts +380 -0
  308. package/src/lib/browser/qa-run.ts +784 -0
  309. package/src/lib/coord-root-id.ts +23 -0
  310. package/src/lib/instructions/apply.ts +81 -2
  311. package/src/lib/instructions/templates.ts +6 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coord-root-id.d.ts","sourceRoot":"","sources":["../../src/lib/coord-root-id.ts"],"names":[],"mappings":"AAIA,mFAAmF;AACnF,eAAO,MAAM,kBAAkB,oBAAoB,CAAC;AAEpD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED,wBAAgB,mBAAmB,CACjC,IAAI,GAAE,MAAM,GAAG,MAAyB,EACxC,QAAQ,SAAU,GACjB,MAAM,CAGR"}
@@ -0,0 +1,17 @@
1
+ import { createHash } from "node:crypto";
2
+ import { realpathSync } from "node:fs";
3
+ import { DEFAULT_WEB_PORT } from "./local-file-url.js";
4
+ /** Local-only endpoint used to prove that a dashboard serves the caller's repo. */
5
+ export const COORD_ROOT_ID_PATH = "/api/coord-root";
6
+ /**
7
+ * Opaque identity for a canonical coordination root. The path stays local;
8
+ * only its digest crosses the dashboard boundary.
9
+ */
10
+ export function coordRootId(root) {
11
+ const canonical = realpathSync(root);
12
+ return `sha256:${createHash("sha256").update(canonical).digest("hex")}`;
13
+ }
14
+ export function localCoordRootIdUrl(port = DEFAULT_WEB_PORT, protocol = "http:") {
15
+ const portPart = String(port) === "" ? "" : `:${port}`;
16
+ return `${protocol}//127.0.0.1${portPart}${COORD_ROOT_ID_PATH}`;
17
+ }
@@ -7,13 +7,15 @@
7
7
  * writes, and deletes so init/deinit stay thin and this stays integration-
8
8
  * testable against a temp dir.
9
9
  *
10
- * Two files, one dir:
10
+ * Three files, one dir:
11
11
  * - `AGENTS.md` — the always-on orientation block (a managed region; the
12
12
  * consumer owns the rest of the file).
13
13
  * - `CLAUDE.md` — claude-code only; Claude Code reads CLAUDE.md, not AGENTS.md,
14
14
  * so a fresh consumer gets a CLAUDE.md whose managed region imports
15
15
  * `@AGENTS.md`. A CLAUDE.md that already imports AGENTS.md or already carries
16
16
  * the block (a host that generates CLAUDE.md from AGENTS.md) is left alone.
17
+ * - `.cursor/rules/harnery-turn-ritual.mdc` — cursor only; an Always Apply
18
+ * rule that makes the Stop-enforced status footer visible in reply text.
17
19
  * - `<adapter-skill-dir>/<skill>/SKILL.md` — fully-owned files, honoring
18
20
  * `skills.exclude` in `.harnery/config.jsonc`. Claude Code uses
19
21
  * `.claude/skills/`; Cursor and Codex use `.agents/skills/`.
@@ -1 +1 @@
1
- {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAsDH,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAYlE;AAmBD,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CA+GnF;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CAkFrF;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC,WAAW,CAkDb"}
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/apply.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAyFH,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAYlE;AAmBD,UAAU,SAAS;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,WAAW,CAoInF;AAED,UAAU,UAAU;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,WAAW,CA8FrF;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC,WAAW,CA2Db"}
@@ -7,25 +7,28 @@
7
7
  * writes, and deletes so init/deinit stay thin and this stays integration-
8
8
  * testable against a temp dir.
9
9
  *
10
- * Two files, one dir:
10
+ * Three files, one dir:
11
11
  * - `AGENTS.md` — the always-on orientation block (a managed region; the
12
12
  * consumer owns the rest of the file).
13
13
  * - `CLAUDE.md` — claude-code only; Claude Code reads CLAUDE.md, not AGENTS.md,
14
14
  * so a fresh consumer gets a CLAUDE.md whose managed region imports
15
15
  * `@AGENTS.md`. A CLAUDE.md that already imports AGENTS.md or already carries
16
16
  * the block (a host that generates CLAUDE.md from AGENTS.md) is left alone.
17
+ * - `.cursor/rules/harnery-turn-ritual.mdc` — cursor only; an Always Apply
18
+ * rule that makes the Stop-enforced status footer visible in reply text.
17
19
  * - `<adapter-skill-dir>/<skill>/SKILL.md` — fully-owned files, honoring
18
20
  * `skills.exclude` in `.harnery/config.jsonc`. Claude Code uses
19
21
  * `.claude/skills/`; Cursor and Codex use `.agents/skills/`.
20
22
  */
21
23
  import { existsSync, mkdirSync, readdirSync, readFileSync, rmdirSync, rmSync, writeFileSync, } from "node:fs";
22
24
  import { dirname, join } from "node:path";
23
- import { stripJsonComments } from "../../core/config.js";
25
+ import { endOfTurnStatusCommand, stripJsonComments } from "../../core/config.js";
24
26
  import { readHostAddendum } from "./host-addendum.js";
25
- import { checkOwnedSkill, checkRegion, isOwnedFile, removeRegion, spliceRegion, } from "./splice.js";
27
+ import { checkOwnedSkill, checkRegion, isOwnedFile, removeRegion, shortHash, spliceRegion, } from "./splice.js";
26
28
  import { HOST_ADDENDUM_REGION, IMPORT_REGION, INSTRUCTIONS_REGION, renderInstructionsBlock, SKILLS, } from "./templates.js";
27
29
  const AGENTS_FILE = "AGENTS.md";
28
30
  const CLAUDE_FILE = "CLAUDE.md";
31
+ const CURSOR_RULE_FILE = join(".cursor", "rules", "harnery-turn-ritual.mdc");
29
32
  const ADAPTER_SKILLS_DIR = {
30
33
  "claude-code": join(".claude", "skills"),
31
34
  cursor: join(".agents", "skills"),
@@ -43,6 +46,36 @@ function skillBody(render, binName) {
43
46
  const content = render(binName);
44
47
  return content.slice(content.indexOf("-->") + 3).trim();
45
48
  }
49
+ function cursorRuleBody(projectRoot, binName) {
50
+ const statusCommand = endOfTurnStatusCommand(projectRoot);
51
+ return [
52
+ "# Harnery turn ritual",
53
+ "",
54
+ "On every turn that uses tools:",
55
+ "",
56
+ `1. Run \`${binName} agents set-task "<short focus>"\` during the turn, even when the focus is unchanged.`,
57
+ `2. Make \`${statusCommand}\` your final tool call.`,
58
+ "3. Append that command's stdout verbatim in a fenced code block at the bottom of the same substantive reply.",
59
+ "",
60
+ "Cursor Stop enforces all three signals. A missing signal produces one follow-up that names every repair needed.",
61
+ ].join("\n");
62
+ }
63
+ function cursorRuleContent(projectRoot, binName) {
64
+ const body = cursorRuleBody(projectRoot, binName);
65
+ const marker = `<!-- harnery:generated cursor-turn-ritual v=${shortHash(body)}; machine-owned; ` +
66
+ `regenerated by \`${binName} init\`, removed by \`${binName} deinit\`. ` +
67
+ "Edit the harnery template, not this file. -->";
68
+ return [
69
+ "---",
70
+ "description: Harnery end-of-turn coordination ritual",
71
+ "alwaysApply: true",
72
+ "---",
73
+ marker,
74
+ "",
75
+ body,
76
+ "",
77
+ ].join("\n");
78
+ }
46
79
  /** Read `skills.exclude` from `.harnery/config.jsonc` (absent/unparseable → none). */
47
80
  export function readSkillsExclude(projectRoot) {
48
81
  const p = join(projectRoot, ".harnery", "config.jsonc");
@@ -157,6 +190,26 @@ export function applyInstructions(projectRoot, opts) {
157
190
  }
158
191
  }
159
192
  }
193
+ // ── Cursor Always Apply rule ────────────────────────────────────────────
194
+ if (opts.adapter === "cursor") {
195
+ const rulePath = join(projectRoot, CURSOR_RULE_FILE);
196
+ const content = cursorRuleContent(projectRoot, opts.binName);
197
+ const before = existsSync(rulePath) ? readFileSync(rulePath, "utf8") : null;
198
+ if (before === content) {
199
+ actions.push(`· ${CURSOR_RULE_FILE} already current`);
200
+ }
201
+ else if (before !== null && !isOwnedFile(before)) {
202
+ warnings.push(`left ${CURSOR_RULE_FILE} (hand-edited; no harnery ownership marker)`);
203
+ }
204
+ else {
205
+ if (!opts.dryRun) {
206
+ mkdirSync(dirname(rulePath), { recursive: true });
207
+ writeFileSync(rulePath, content);
208
+ }
209
+ const fresh = before === null;
210
+ actions.push(`${fresh ? "+" : "~"} ${verbed(fresh ? "write" : "update", fresh ? "wrote" : "updated")} ${CURSOR_RULE_FILE}`);
211
+ }
212
+ }
160
213
  // ── shipped skills ───────────────────────────────────────────────────────
161
214
  if (skillsDir) {
162
215
  const exclude = readSkillsExclude(projectRoot);
@@ -239,6 +292,19 @@ export function removeInstructions(projectRoot, opts) {
239
292
  }
240
293
  }
241
294
  }
295
+ if (opts.adapter === "cursor") {
296
+ const rulePath = join(projectRoot, CURSOR_RULE_FILE);
297
+ if (existsSync(rulePath)) {
298
+ if (!isOwnedFile(readFileSync(rulePath, "utf8"))) {
299
+ warnings.push(`left ${CURSOR_RULE_FILE} (hand-edited; no harnery ownership marker)`);
300
+ }
301
+ else {
302
+ if (!opts.dryRun)
303
+ rmSync(rulePath);
304
+ actions.push(`+ ${opts.dryRun ? "would delete" : "deleted"} ${CURSOR_RULE_FILE}`);
305
+ }
306
+ }
307
+ }
242
308
  // ── shipped skills ───────────────────────────────────────────────────────
243
309
  if (skillsDir) {
244
310
  for (const skill of SKILLS) {
@@ -305,6 +371,11 @@ export function checkInstructions(projectRoot, opts) {
305
371
  note(`skill ${skill.id}`, checkOwnedSkill(c, skillBody(skill.render, opts.binName)));
306
372
  }
307
373
  }
374
+ if (opts.adapter === "cursor") {
375
+ const rulePath = join(projectRoot, CURSOR_RULE_FILE);
376
+ const content = existsSync(rulePath) ? readFileSync(rulePath, "utf8") : "";
377
+ note(`${CURSOR_RULE_FILE} rule`, checkOwnedSkill(content, cursorRuleBody(projectRoot, opts.binName)));
378
+ }
308
379
  }
309
380
  catch (err) {
310
381
  errored = true;
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,OAAO,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,WAAoE,GAC3E,MAAM,CA0GR;AAID,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAmVD,eAAO,MAAM,MAAM,EAAE,aAAa,EAiDjC,CAAC"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../../../src/lib/instructions/templates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAKH,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAClD,sEAAsE;AACtE,eAAO,MAAM,aAAa,WAAW,CAAC;AACtC;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,kBAAkB,CAAC;AAEpD,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,8CAA8C;IAC9C,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,2CAA2C;IAC3C,GAAG,EAAE,OAAO,CAAC;IACb,4CAA4C;IAC5C,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,WAAoE,GAC3E,MAAM,CAgHR;AAID,mFAAmF;AACnF,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;CACrC;AAmVD,eAAO,MAAM,MAAM,EAAE,aAAa,EAiDjC,CAAC"}
@@ -113,6 +113,12 @@ coordination ledger (\`.harnery/ledgers/v3/\`). Lead a shell command with a
113
113
  carries a reason instead of \`(no intent)\`; the [tool-intent
114
114
  guide](https://harnery.com/guides/tool-intent/) owns the details.
115
115
 
116
+ **Messaging another agent.** \`${b} agents ping <name> "<message>"\` reaches an
117
+ agent by name whether or not they are running: a live agent sees it on their next
118
+ prompt, and a dormant name holds it until a session of that name next starts.
119
+ Send it as your first and only step. Do not check who is live first, and do not
120
+ hunt for somewhere else to leave it. Only a never-used name is refused.
121
+
116
122
  **Journal.** \`${b} journal add <category> "<text>"\` (category = ${journalCats})
117
123
  leaves breadcrumbs that survive context compaction;
118
124
  \`${b} journal read\` reads yours, \`${b} journal read --name <peer>\` reads a peer's.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "harnery",
3
- "version": "0.36.0",
3
+ "version": "0.37.0",
4
4
  "description": "Multi-agent coordination for AI coding agents - Claude Code, Cursor, and Codex.",
5
5
  "license": "MIT",
6
6
  "author": "Ryan Kelly",
@@ -132,6 +132,16 @@
132
132
  "types": "./dist/core/semantic/index.d.ts",
133
133
  "import": "./dist/core/semantic/index.js"
134
134
  },
135
+ "./core/supervisor": {
136
+ "bun": "./src/core/supervisor/index.ts",
137
+ "types": "./dist/core/supervisor/index.d.ts",
138
+ "import": "./dist/core/supervisor/index.js"
139
+ },
140
+ "./core/diagnostics": {
141
+ "bun": "./src/core/diagnostics/index.ts",
142
+ "types": "./dist/core/diagnostics/index.d.ts",
143
+ "import": "./dist/core/diagnostics/index.js"
144
+ },
135
145
  "./core/governor/plans": {
136
146
  "bun": "./src/core/governor/plan-read.ts",
137
147
  "types": "./dist/core/governor/plan-read.d.ts",
@@ -230,6 +240,7 @@
230
240
  "check:portability": "bun run scripts/check-portability.ts",
231
241
  "check:public-surface": "bun run scripts/check-public-surface.ts",
232
242
  "check:layering": "bun run scripts/check-layering.ts",
243
+ "probe:supervisor-budget": "bun run scripts/probe-supervisor-budget.ts",
233
244
  "lint:fix": "bun x @biomejs/biome check --write src",
234
245
  "format": "bun x @biomejs/biome format --write src",
235
246
  "test": "bun run scripts/test.mjs",
package/src/commander.ts CHANGED
@@ -440,6 +440,12 @@ function harneryCommandBundles({
440
440
  ),
441
441
  { hasOptions: true },
442
442
  ),
443
+ lazy(
444
+ "qa-run <target>",
445
+ `Run the whole page-QA matrix in one command (planner, deterministic gates per context, interactions, critique, snapshot) via child \`${binName} browse\` processes; writes page-qa-result.json with a fail-closed verdict.`,
446
+ async (program) => (await import("./commands/qa-run.ts")).registerQaRunCommand(program, emit),
447
+ { hasOptions: true },
448
+ ),
443
449
  lazy(
444
450
  "browse-session",
445
451
  "Control an opted-in headed browse session through an owner-only descriptor",
@@ -509,6 +515,26 @@ function harneryCommandBundles({
509
515
  async (program) =>
510
516
  (await import("./commands/semantic.ts")).registerSemanticCommand(program, emit, context),
511
517
  ),
518
+ lazy(
519
+ "resources",
520
+ "Observe local CPU and memory pressure by agent and process tree",
521
+ async (program) =>
522
+ (await import("./commands/resources.ts")).registerResourcesCommand(program, emit, context),
523
+ ),
524
+ lazy("supervisor", "Run Harnery's optional local diagnostic supervisor", async (program) =>
525
+ (await import("./commands/supervisor.ts")).registerSupervisorCommand(program, emit, context),
526
+ ),
527
+ lazy(
528
+ "diagnostics",
529
+ "Inspect, capture, and replay bounded local diagnostic findings",
530
+ async (program) =>
531
+ (await import("./commands/diagnostics.ts")).registerDiagnosticsCommand(
532
+ program,
533
+ emit,
534
+ context,
535
+ ),
536
+ { hasOptions: true },
537
+ ),
512
538
  lazy(
513
539
  "storage",
514
540
  "Inspect registered Harnery storage without reading file bodies",
@@ -49,6 +49,14 @@ import {
49
49
  LiveCoordinationAuthorityV3Error,
50
50
  reopenLiveCoordinationGenerationV3,
51
51
  } from "../core/agents/live-authority-v3.ts";
52
+ import {
53
+ isAddressableName,
54
+ MailboxCapacityError,
55
+ type QueueResult,
56
+ queueMailboxMessage,
57
+ suggestNames,
58
+ } from "../core/agents/mailbox.ts";
59
+ import { reconcileCoordinationV3 } from "../core/agents/reconcile-coordination-v3.ts";
52
60
  import {
53
61
  listSessionFinalizationRequestsV3,
54
62
  observeHostDisappearedV3,
@@ -435,7 +443,8 @@ export function registerAgentsCommand(
435
443
  cmd
436
444
  .command("reconcile")
437
445
  .description(
438
- "Reconcile archive, idle, parent/run completion, stale, superseded, and host lifecycle signals into V3 session finalization.",
446
+ "Sweep the stale coordination cache, then reconcile archive, idle, parent/run " +
447
+ "completion, stale, superseded, and host lifecycle signals into V3 session finalization.",
439
448
  )
440
449
  .option("--watch", "Keep reconciling until interrupted")
441
450
  .option("--interval-seconds <n>", "Watch interval in seconds")
@@ -485,9 +494,10 @@ export function registerAgentsCommand(
485
494
  cmd
486
495
  .command("ping <name> <message...>")
487
496
  .description(
488
- "Append a 'handoff' entry to a peer agent's journal. Body prefixed with " +
489
- "`from agent-<me>:`. Use to leave actionable coordination notes for peers " +
490
- "currently holding files you need.",
497
+ "Send a message to another agent by name. Works whether or not that agent " +
498
+ "is running: a live peer sees it on their next prompt, and a name with no " +
499
+ "live session gets it queued until a session of that name next starts. " +
500
+ "Only an unknown name is refused.",
491
501
  )
492
502
  .option("--json", "JSON output")
493
503
  .action((name: string, message: string[], opts: { json?: boolean }) => {
@@ -546,7 +556,7 @@ export function registerAgentsCommand(
546
556
  .command("health")
547
557
  .description(
548
558
  "One-screen coord-layer health rollup: heal events, council activity, " +
549
- "zombie detection, and anomalies. Designed for " +
559
+ "heartbeat cache issues, and findings. Designed for " +
550
560
  "daily glance + dashboard ingestion. Reads .harnery/.",
551
561
  )
552
562
  .option("--since <window>", "Window (Nh | Nd). Default: 24h.", "24h")
@@ -747,7 +757,7 @@ async function runSessionReconcile(opts: {
747
757
  }
748
758
  let stopped = false;
749
759
  do {
750
- emit.data(reconcileSessionFinalizationV3(root));
760
+ emit.data(reconcileCoordinationV3(root));
751
761
  if (!opts.watch || stopped) return;
752
762
  await new Promise<void>((resolvePromise) => {
753
763
  const timer = setTimeout(resolvePromise, interval * 1_000);
@@ -3022,7 +3032,7 @@ interface HealthReport {
3022
3032
  // Heartbeat cache files present in active/ but stale or malformed. These are
3023
3033
  // files the sweep is not catching; a parseable old heartbeat is stale, not
3024
3034
  // evidence of an epoch-like or corrupt timestamp.
3025
- zombies: {
3035
+ heartbeat_cache_issues: {
3026
3036
  count: number;
3027
3037
  samples: string[];
3028
3038
  };
@@ -3038,7 +3048,7 @@ interface HealthReport {
3038
3048
  latest_at: string | null;
3039
3049
  sessions: string[];
3040
3050
  };
3041
- anomalies: string[];
3051
+ findings: string[];
3042
3052
  }
3043
3053
 
3044
3054
  /** Open-span soft watermark per producer state. The producer-state reader
@@ -3946,8 +3956,8 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3946
3956
  // Heartbeat cache issues: files in active/ that are unparseable, nameless,
3947
3957
  // invalidly timestamped, or stale. Old parseable timestamps are ordinary
3948
3958
  // staleness, not evidence of epoch-like timestamp corruption.
3949
- let zombieCount = 0;
3950
- const zombieSamples: string[] = [];
3959
+ let cacheIssueCount = 0;
3960
+ const cacheIssueSamples: string[] = [];
3951
3961
  const STALE_HEARTBEAT_AGE_MS = 24 * 60 * 60 * 1000;
3952
3962
  if (existsSync(activeDir)) {
3953
3963
  for (const file of readdirSync(activeDir)) {
@@ -3960,16 +3970,16 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3960
3970
  hb = null;
3961
3971
  }
3962
3972
  if (!hb || typeof hb.instance_id !== "string") {
3963
- zombieCount++;
3964
- if (zombieSamples.length < 5)
3965
- zombieSamples.push(`${idFromFile.slice(0, 12)} (unparseable/no-id)`);
3973
+ cacheIssueCount++;
3974
+ if (cacheIssueSamples.length < 5)
3975
+ cacheIssueSamples.push(`${idFromFile.slice(0, 12)} (unparseable/no-id)`);
3966
3976
  continue;
3967
3977
  }
3968
3978
  const cacheIssue = classifyHeartbeatCacheIssue(hb, nowMs, STALE_HEARTBEAT_AGE_MS);
3969
3979
  if (cacheIssue) {
3970
- zombieCount++;
3971
- if (zombieSamples.length < 5) {
3972
- zombieSamples.push(`${idFromFile.slice(0, 12)} (${cacheIssue})`);
3980
+ cacheIssueCount++;
3981
+ if (cacheIssueSamples.length < 5) {
3982
+ cacheIssueSamples.push(`${idFromFile.slice(0, 12)} (${cacheIssue})`);
3973
3983
  }
3974
3984
  }
3975
3985
  }
@@ -3985,11 +3995,11 @@ function runHealth(opts: { since: string; json?: boolean }): void {
3985
3995
  }
3986
3996
  }
3987
3997
 
3988
- // Anomaly detection.
3989
- const anomalies: string[] = [];
3998
+ // Finding detection.
3999
+ const findings: string[] = [];
3990
4000
  for (const { agent, count } of healTopAgents) {
3991
4001
  if (count >= 5) {
3992
- anomalies.push(
4002
+ findings.push(
3993
4003
  `${agent} self-healed ${count}x in ${opts.since}; possible idle-prune loop or PID instability`,
3994
4004
  );
3995
4005
  }
@@ -4000,7 +4010,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4000
4010
  activeAgents.source === "event-ledger-v3"
4001
4011
  ? "lifecycle reconciliation may be delayed"
4002
4012
  : "heal mechanism may not be firing";
4003
- anomalies.push(
4013
+ findings.push(
4004
4014
  `${activeAgents.stale} active ${noun}(s) without activity for ${Math.floor(freshnessCutoffSecs() / 60)}min; ${action}`,
4005
4015
  );
4006
4016
  }
@@ -4009,31 +4019,33 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4009
4019
  (schema) => schema !== expectedSchema,
4010
4020
  );
4011
4021
  if (unexpectedSchemas.length > 0) {
4012
- anomalies.push(
4022
+ findings.push(
4013
4023
  `Unexpected active-agent schema versions in use: ${unexpectedSchemas.join(", ")} (expected ${expectedSchema})`,
4014
4024
  );
4015
4025
  }
4016
- // Only recent hook failures are active anomalies. Historical rows remain in
4026
+ // Only recent hook failures are active findings. Historical rows remain in
4017
4027
  // the report with exact error signatures and their latest timestamp.
4018
4028
  if (hookErrors.last1h > 0) {
4019
4029
  const top = hookErrors.recentTopErrors[0];
4020
4030
  const detail = top ? `: top '${top.error.slice(0, 80)}' x${top.count} (${top.phase})` : "";
4021
- anomalies.push(`agent-hook errored ${hookErrors.last1h}x in the last hour${detail}`);
4031
+ findings.push(`agent-hook errored ${hookErrors.last1h}x in the last hour${detail}`);
4022
4032
  }
4023
4033
  if (stream.cursor_backlog > 500) {
4024
- anomalies.push(
4034
+ findings.push(
4025
4035
  `projection cursor is ${stream.cursor_backlog} events behind; drain lagging (stop projection may be failing)`,
4026
4036
  );
4027
4037
  }
4028
- // Raw V3 stream size is not an anomaly; catalog rotation bounds readers.
4038
+ // Raw V3 stream size is not a finding; catalog rotation bounds readers.
4029
4039
  if ((sweptByReason.unparseable ?? 0) > 0) {
4030
- anomalies.push(
4040
+ findings.push(
4031
4041
  `${sweptByReason.unparseable} heartbeat(s) swept as unparseable in ${opts.since}; possible corruption or a non-atomic writer`,
4032
4042
  );
4033
4043
  }
4034
- if (zombieCount > 0) {
4035
- anomalies.push(
4036
- `${zombieCount} heartbeat cache issue(s) in active/ (${zombieSamples.join(", ")}); stale or malformed files the sweep isn't reaping`,
4044
+ if (cacheIssueCount > 0) {
4045
+ findings.push(
4046
+ `${cacheIssueCount} heartbeat cache issue(s) in active/ (${cacheIssueSamples.join(", ")}); ` +
4047
+ "rows the last sweep deliberately kept (malformed but mtime-fresh, or neither audit " +
4048
+ "write could persist), or that aged past the freshness cutoff since it ran",
4037
4049
  );
4038
4050
  }
4039
4051
  if (stopRemediation.total > 0) {
@@ -4041,7 +4053,7 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4041
4053
  stopRemediation.sessions.length > 0
4042
4054
  ? ` (sessions: ${stopRemediation.sessions.map((s) => s.slice(0, 8)).join(", ")})`
4043
4055
  : "";
4044
- anomalies.push(
4056
+ findings.push(
4045
4057
  `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`,
4046
4058
  );
4047
4059
  }
@@ -4056,17 +4068,17 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4056
4068
  (sum, generation) => sum + generation.span_count,
4057
4069
  0,
4058
4070
  );
4059
- anomalies.push(
4071
+ findings.push(
4060
4072
  `${orphanSpans} open tool span(s) across ${orphanGenerations.length} generation(s) with no open turn; orphaned spans block clean session end`,
4061
4073
  );
4062
4074
  }
4063
4075
  for (const pressure of eventLedger.span_pressure) {
4064
- anomalies.push(
4076
+ findings.push(
4065
4077
  `${pressure.instance_id} (${pressure.generation_id}) holds ${pressure.span_count} open spans (soft watermark ${SPAN_PRESSURE_SOFT_WATERMARK}; the reader's 256-span cap makes the state unreadable)`,
4066
4078
  );
4067
4079
  }
4068
4080
  for (const collectionError of eventLedger.collection_errors) {
4069
- anomalies.push(`event-ledger health: ${collectionError}`);
4081
+ findings.push(`event-ledger health: ${collectionError}`);
4070
4082
  }
4071
4083
  }
4072
4084
 
@@ -4111,14 +4123,14 @@ function runHealth(opts: { since: string; json?: boolean }): void {
4111
4123
  recent_top_errors: hookErrors.recentTopErrors,
4112
4124
  },
4113
4125
  stream,
4114
- zombies: { count: zombieCount, samples: zombieSamples },
4126
+ heartbeat_cache_issues: { count: cacheIssueCount, samples: cacheIssueSamples },
4115
4127
  event_ledger: eventLedger,
4116
4128
  stop_remediation: {
4117
4129
  total: stopRemediation.total,
4118
4130
  latest_at: stopRemediation.latestAt,
4119
4131
  sessions: stopRemediation.sessions,
4120
4132
  },
4121
- anomalies,
4133
+ findings,
4122
4134
  };
4123
4135
 
4124
4136
  if (opts.json) {
@@ -4211,12 +4223,12 @@ function renderHealthBox(report: HealthReport): void {
4211
4223
  ["event ledger", ledgerStr],
4212
4224
  [
4213
4225
  "cache issues",
4214
- report.zombies.count === 0
4226
+ report.heartbeat_cache_issues.count === 0
4215
4227
  ? "0"
4216
- : `${report.zombies.count} (${report.zombies.samples.join(", ")})`,
4228
+ : `${report.heartbeat_cache_issues.count} (${report.heartbeat_cache_issues.samples.join(", ")})`,
4217
4229
  ],
4218
4230
  ["councils", councilParts.join(", ")],
4219
- ["anomalies", report.anomalies.length === 0 ? "(clean)" : `${report.anomalies.length} flagged`],
4231
+ ["findings", report.findings.length === 0 ? "(clean)" : `${report.findings.length} flagged`],
4220
4232
  ];
4221
4233
 
4222
4234
  const localTime = formatLocalTime(new Date(report.generated_at));
@@ -4224,9 +4236,9 @@ function renderHealthBox(report: HealthReport): void {
4224
4236
 
4225
4237
  process.stdout.write(`${formatBox(title, rows)}\n`); // lint-ok-emission: chat-paste path; mirrors runStatus's direct write so the box surfaces in both TTY + harn-session-teed contexts
4226
4238
 
4227
- if (report.anomalies.length > 0) {
4239
+ if (report.findings.length > 0) {
4228
4240
  process.stdout.write("\n"); // lint-ok-emission: same chat-paste path
4229
- for (const a of report.anomalies) {
4241
+ for (const a of report.findings) {
4230
4242
  process.stdout.write(` ! ${a}\n`); // lint-ok-emission: same
4231
4243
  }
4232
4244
  }
@@ -4660,18 +4672,52 @@ function runPing(name: string, message: string, opts: { json?: boolean }): void
4660
4672
  );
4661
4673
  process.exit(1);
4662
4674
  }
4675
+ const coordRoot = monorepoRoot();
4663
4676
  const peerOwner = resolveOwnerByName(name);
4664
- if (!peerOwner) {
4677
+
4678
+ // A name with no live session is the common case, not an error: the agent it
4679
+ // refers to may have ended minutes ago and may come back under the same name.
4680
+ // Refuse only a name that no agent has ever held, which is a typo — queueing
4681
+ // that would swallow the message silently.
4682
+ if (!peerOwner && coordRoot && !isAddressableName(coordRoot, name)) {
4683
+ const suggestions = suggestNames(coordRoot, name);
4665
4684
  emit.error({
4666
- code: "no_peer",
4667
- message: `no live agent named "${name}" (case-insensitive). Run \`${resolveBinName()} agents list\` to see who's active.`,
4685
+ code: "unknown_agent",
4686
+ message:
4687
+ `no agent has ever been named "${name}". ` +
4688
+ (suggestions.length > 0 ? `Did you mean ${suggestions.join(", ")}? ` : "") +
4689
+ `Run \`${resolveBinName()} agents list\` for live sessions.`,
4668
4690
  });
4669
4691
  process.exit(1);
4670
4692
  }
4693
+
4671
4694
  const myHb = readCurrentCoordinationRow(myOwner);
4672
4695
  const fromName = myHb?.name ?? "anonymous";
4673
4696
  const body = `from agent-${fromName}: ${message.trim()}`;
4674
- const doc = appendEntry(peerOwner, "handoff", body);
4697
+
4698
+ // A live recipient gets the journal entry immediately, so a peer reading
4699
+ // their own journal mid-session sees it without waiting for a prompt. A
4700
+ // dormant recipient has no journal to write to yet; the drain writes it into
4701
+ // whichever session picks the message up.
4702
+ const doc = peerOwner ? appendEntry(peerOwner, "handoff", body) : null;
4703
+
4704
+ let queued: QueueResult;
4705
+ try {
4706
+ queued = queueMailboxMessage({
4707
+ coordRoot: coordRoot ?? process.cwd(),
4708
+ toName: name,
4709
+ fromName,
4710
+ fromInstanceId: myOwner,
4711
+ body: message.trim(),
4712
+ journaled: Boolean(peerOwner),
4713
+ });
4714
+ } catch (error) {
4715
+ if (error instanceof MailboxCapacityError) {
4716
+ emit.error({ code: `mailbox_${error.reason_code}`, message: error.message });
4717
+ process.exit(1);
4718
+ }
4719
+ throw error;
4720
+ }
4675
4721
 
4676
4722
  // Canonical delivery record: sender is the envelope owner, recipient rides
4677
4723
  // in data, so read-only observers can render the communication without
@@ -4683,18 +4729,21 @@ function runPing(name: string, message: string, opts: { json?: boolean }): void
4683
4729
  observation: {
4684
4730
  event_type: "coord.message_observed",
4685
4731
  direction: "sent",
4686
- subject: peerOwner,
4732
+ subject: peerOwner ?? `name:${queued.message.to_name}`,
4687
4733
  body: message.trim(),
4688
4734
  },
4689
4735
  });
4690
4736
 
4691
4737
  const data = {
4692
- peer: name,
4738
+ peer: queued.message.to_name,
4693
4739
  peer_instance_id: peerOwner,
4740
+ delivery: peerOwner ? ("live" as const) : ("queued" as const),
4741
+ message_id: queued.message.message_id,
4742
+ pending: queued.pending,
4694
4743
  from: fromName,
4695
4744
  body,
4696
- journal_path: doc.path,
4697
- journal_bytes: doc.bytes,
4745
+ journal_path: doc?.path ?? null,
4746
+ journal_bytes: doc?.bytes ?? null,
4698
4747
  };
4699
4748
 
4700
4749
  if (opts.json) {
@@ -4703,7 +4752,11 @@ function runPing(name: string, message: string, opts: { json?: boolean }): void
4703
4752
  return;
4704
4753
  }
4705
4754
  emit.data(data);
4706
- emit.text(`pinged agent-${name}: "${truncate(message.trim(), 80)}"\n`);
4755
+ emit.text(
4756
+ peerOwner
4757
+ ? `delivered to agent-${data.peer} (live; they see it on their next prompt): "${truncate(message.trim(), 80)}"\n`
4758
+ : `queued for agent-${data.peer} (no live session; delivered when a session of that name next starts): "${truncate(message.trim(), 80)}"\n`,
4759
+ );
4707
4760
  }
4708
4761
 
4709
4762
  async function runWait(