harnery 0.43.0 → 0.45.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 (323) hide show
  1. package/README.md +3 -3
  2. package/dist/commander.js +1 -0
  3. package/dist/commands/agents.d.ts.map +1 -1
  4. package/dist/commands/agents.js +41 -48
  5. package/dist/commands/browse.d.ts.map +1 -1
  6. package/dist/commands/browse.js +38 -11
  7. package/dist/commands/diagnostics.d.ts.map +1 -1
  8. package/dist/commands/diagnostics.js +10 -0
  9. package/dist/commands/doctor.d.ts +12 -0
  10. package/dist/commands/doctor.d.ts.map +1 -1
  11. package/dist/commands/doctor.js +22 -7
  12. package/dist/commands/files.d.ts +2 -2
  13. package/dist/commands/files.d.ts.map +1 -1
  14. package/dist/commands/files.js +7 -4
  15. package/dist/commands/governor.js +22 -10
  16. package/dist/commands/inbox.d.ts +1 -1
  17. package/dist/commands/inbox.d.ts.map +1 -1
  18. package/dist/commands/inbox.js +55 -2
  19. package/dist/commands/rm.d.ts.map +1 -1
  20. package/dist/commands/rm.js +28 -4
  21. package/dist/commands/servers.d.ts.map +1 -1
  22. package/dist/commands/servers.js +26 -17
  23. package/dist/commands/supervisor.d.ts.map +1 -1
  24. package/dist/commands/supervisor.js +7 -2
  25. package/dist/commands/toc.d.ts +14 -0
  26. package/dist/commands/toc.d.ts.map +1 -1
  27. package/dist/commands/toc.js +7 -4
  28. package/dist/commands/tunnel.d.ts +15 -0
  29. package/dist/commands/tunnel.d.ts.map +1 -1
  30. package/dist/commands/tunnel.js +453 -153
  31. package/dist/commands/turn-rollback.d.ts +5 -0
  32. package/dist/commands/turn-rollback.d.ts.map +1 -0
  33. package/dist/commands/turn-rollback.js +76 -0
  34. package/dist/commands/workflow.d.ts.map +1 -1
  35. package/dist/commands/workflow.js +68 -30
  36. package/dist/core/agents/coord-client.d.ts.map +1 -1
  37. package/dist/core/agents/coord-client.js +18 -0
  38. package/dist/core/agents/live-authority-v3.d.ts +1 -0
  39. package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
  40. package/dist/core/agents/live-authority-v3.js +11 -0
  41. package/dist/core/agents/live-lifecycle-v3.d.ts +4 -2
  42. package/dist/core/agents/live-lifecycle-v3.d.ts.map +1 -1
  43. package/dist/core/agents/live-lifecycle-v3.js +28 -7
  44. package/dist/core/agents/mailbox.d.ts +9 -8
  45. package/dist/core/agents/mailbox.d.ts.map +1 -1
  46. package/dist/core/agents/mailbox.js +24 -17
  47. package/dist/core/agents/render/prompt-context.d.ts +2 -0
  48. package/dist/core/agents/render/prompt-context.d.ts.map +1 -1
  49. package/dist/core/agents/render/prompt-context.js +8 -12
  50. package/dist/core/agents/render/session-context.d.ts +2 -8
  51. package/dist/core/agents/render/session-context.d.ts.map +1 -1
  52. package/dist/core/agents/render/session-context.js +7 -30
  53. package/dist/core/agents/rules/stop-hook.js +3 -3
  54. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -1
  55. package/dist/core/agents/state/heartbeat-writer.js +25 -14
  56. package/dist/core/agents/state/live-coordination-writer.d.ts.map +1 -1
  57. package/dist/core/agents/state/live-coordination-writer.js +5 -0
  58. package/dist/core/agents/state/pidmap.d.ts.map +1 -1
  59. package/dist/core/agents/state/pidmap.js +5 -0
  60. package/dist/core/agents/state/proc-start.d.ts +19 -1
  61. package/dist/core/agents/state/proc-start.d.ts.map +1 -1
  62. package/dist/core/agents/state/proc-start.js +46 -1
  63. package/dist/core/agents/state/process-handle.d.ts +26 -0
  64. package/dist/core/agents/state/process-handle.d.ts.map +1 -0
  65. package/dist/core/agents/state/process-handle.js +121 -0
  66. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -1
  67. package/dist/core/agents/state/stale-sweep.js +180 -70
  68. package/dist/core/agents/state/unchanged-file.d.ts +44 -0
  69. package/dist/core/agents/state/unchanged-file.d.ts.map +1 -0
  70. package/dist/core/agents/state/unchanged-file.js +390 -0
  71. package/dist/core/artifacts/delivery-card.d.ts.map +1 -1
  72. package/dist/core/artifacts/delivery-card.js +2 -1
  73. package/dist/core/context/index.d.ts +22 -10
  74. package/dist/core/context/index.d.ts.map +1 -1
  75. package/dist/core/context/index.js +381 -119
  76. package/dist/core/context/observation.d.ts +26 -0
  77. package/dist/core/context/observation.d.ts.map +1 -0
  78. package/dist/core/context/observation.js +11 -0
  79. package/dist/core/diagnostics/bundle.d.ts.map +1 -1
  80. package/dist/core/diagnostics/bundle.js +25 -14
  81. package/dist/core/diagnostics/path-identity.d.ts +3 -0
  82. package/dist/core/diagnostics/path-identity.d.ts.map +1 -0
  83. package/dist/core/diagnostics/path-identity.js +12 -0
  84. package/dist/core/diagnostics/sanitize.d.ts.map +1 -1
  85. package/dist/core/diagnostics/sanitize.js +4 -3
  86. package/dist/core/events/v3/authority-outbox.d.ts.map +1 -1
  87. package/dist/core/events/v3/authority-outbox.js +29 -0
  88. package/dist/core/events/v3/generation-custody.d.ts +5 -0
  89. package/dist/core/events/v3/generation-custody.d.ts.map +1 -0
  90. package/dist/core/events/v3/generation-custody.js +50 -0
  91. package/dist/core/events/v3/producers/coordination-recorder.d.ts +1 -0
  92. package/dist/core/events/v3/producers/coordination-recorder.d.ts.map +1 -1
  93. package/dist/core/events/v3/producers/coordination-recorder.js +7 -0
  94. package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
  95. package/dist/core/events/v3/producers/recorder.js +77 -64
  96. package/dist/core/governor/planning.d.ts.map +1 -1
  97. package/dist/core/governor/planning.js +14 -1
  98. package/dist/core/governor/service-read.d.ts +10 -2
  99. package/dist/core/governor/service-read.d.ts.map +1 -1
  100. package/dist/core/governor/service-read.js +105 -22
  101. package/dist/core/governor/service.d.ts +20 -2
  102. package/dist/core/governor/service.d.ts.map +1 -1
  103. package/dist/core/governor/service.js +72 -47
  104. package/dist/core/hooks/cli.js +33 -5
  105. package/dist/core/inbox/contract.d.ts +2 -1
  106. package/dist/core/inbox/contract.d.ts.map +1 -1
  107. package/dist/core/inbox/index.d.ts +2 -0
  108. package/dist/core/inbox/index.d.ts.map +1 -1
  109. package/dist/core/inbox/index.js +2 -0
  110. package/dist/core/inbox/peer-messages.d.ts +37 -0
  111. package/dist/core/inbox/peer-messages.d.ts.map +1 -0
  112. package/dist/core/inbox/peer-messages.js +72 -0
  113. package/dist/core/inbox/service.d.ts +8 -1
  114. package/dist/core/inbox/service.d.ts.map +1 -1
  115. package/dist/core/inbox/service.js +67 -21
  116. package/dist/core/inbox/targets.d.ts +22 -0
  117. package/dist/core/inbox/targets.d.ts.map +1 -0
  118. package/dist/core/inbox/targets.js +108 -0
  119. package/dist/core/inbox/watcher.js +7 -3
  120. package/dist/core/journal/index.d.ts.map +1 -1
  121. package/dist/core/journal/index.js +17 -2
  122. package/dist/core/resources/contract.d.ts +3 -0
  123. package/dist/core/resources/contract.d.ts.map +1 -1
  124. package/dist/core/resources/service-status.d.ts +14 -1
  125. package/dist/core/resources/service-status.d.ts.map +1 -1
  126. package/dist/core/resources/service-status.js +23 -13
  127. package/dist/core/resources/service.d.ts +7 -13
  128. package/dist/core/resources/service.d.ts.map +1 -1
  129. package/dist/core/resources/service.js +34 -91
  130. package/dist/core/resources/status.d.ts +2 -5
  131. package/dist/core/resources/status.d.ts.map +1 -1
  132. package/dist/core/resources/status.js +36 -7
  133. package/dist/core/semantic/service.d.ts.map +1 -1
  134. package/dist/core/semantic/service.js +2 -9
  135. package/dist/core/servers/index.d.ts +34 -22
  136. package/dist/core/servers/index.d.ts.map +1 -1
  137. package/dist/core/servers/index.js +297 -144
  138. package/dist/core/servers/tunnels.d.ts +7 -7
  139. package/dist/core/servers/tunnels.d.ts.map +1 -1
  140. package/dist/core/servers/tunnels.js +47 -10
  141. package/dist/core/supervisor/contract.d.ts +12 -0
  142. package/dist/core/supervisor/contract.d.ts.map +1 -1
  143. package/dist/core/supervisor/explanations.d.ts.map +1 -1
  144. package/dist/core/supervisor/explanations.js +2 -1
  145. package/dist/core/supervisor/findings.d.ts.map +1 -1
  146. package/dist/core/supervisor/findings.js +54 -9
  147. package/dist/core/supervisor/service.d.ts +7 -1
  148. package/dist/core/supervisor/service.d.ts.map +1 -1
  149. package/dist/core/supervisor/service.js +59 -29
  150. package/dist/core/supervisor/services.d.ts.map +1 -1
  151. package/dist/core/supervisor/services.js +22 -1
  152. package/dist/core/supervisor/status.d.ts +17 -2
  153. package/dist/core/supervisor/status.d.ts.map +1 -1
  154. package/dist/core/supervisor/status.js +70 -12
  155. package/dist/core/supervisor/timeline.d.ts.map +1 -1
  156. package/dist/core/supervisor/timeline.js +2 -1
  157. package/dist/core/turn-rollback/content.d.ts +53 -0
  158. package/dist/core/turn-rollback/content.d.ts.map +1 -0
  159. package/dist/core/turn-rollback/content.js +396 -0
  160. package/dist/core/turn-rollback/index.d.ts +72 -0
  161. package/dist/core/turn-rollback/index.d.ts.map +1 -0
  162. package/dist/core/turn-rollback/index.js +443 -0
  163. package/dist/core/work/state.js +7 -7
  164. package/dist/core/workflow/engine.d.ts.map +1 -1
  165. package/dist/core/workflow/engine.js +105 -32
  166. package/dist/core/workflow/failure-evidence.d.ts +16 -0
  167. package/dist/core/workflow/failure-evidence.d.ts.map +1 -0
  168. package/dist/core/workflow/failure-evidence.js +114 -0
  169. package/dist/core/workflow/index.d.ts +5 -3
  170. package/dist/core/workflow/index.d.ts.map +1 -1
  171. package/dist/core/workflow/index.js +3 -2
  172. package/dist/core/workflow/live-session-v3.d.ts +6 -0
  173. package/dist/core/workflow/live-session-v3.d.ts.map +1 -1
  174. package/dist/core/workflow/live-session-v3.js +126 -5
  175. package/dist/core/workflow/proof.d.ts +5 -5
  176. package/dist/core/workflow/proof.d.ts.map +1 -1
  177. package/dist/core/workflow/proof.js +115 -20
  178. package/dist/core/workflow/recovery-audit.d.ts +30 -0
  179. package/dist/core/workflow/recovery-audit.d.ts.map +1 -0
  180. package/dist/core/workflow/recovery-audit.js +116 -0
  181. package/dist/core/workflow/run-state.d.ts +20 -1
  182. package/dist/core/workflow/run-state.d.ts.map +1 -1
  183. package/dist/core/workflow/run-state.js +141 -32
  184. package/dist/core/workflow/spawn-claude.d.ts.map +1 -1
  185. package/dist/core/workflow/spawn-claude.js +19 -0
  186. package/dist/core/workflow/spawn-codex.d.ts.map +1 -1
  187. package/dist/core/workflow/spawn-codex.js +13 -0
  188. package/dist/core/workflow/spawn-cursor.d.ts.map +1 -1
  189. package/dist/core/workflow/spawn-cursor.js +19 -0
  190. package/dist/core/workflow/spawn-opencode.d.ts.map +1 -1
  191. package/dist/core/workflow/spawn-opencode.js +14 -0
  192. package/dist/core/workflow/types.d.ts +64 -17
  193. package/dist/core/workflow/types.d.ts.map +1 -1
  194. package/dist/core/workflow/workspaces/index.d.ts +0 -1
  195. package/dist/core/workflow/workspaces/index.d.ts.map +1 -1
  196. package/dist/core/workflow/workspaces/index.js +0 -1
  197. package/dist/core/workflow/workspaces/leases.d.ts +3 -1
  198. package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
  199. package/dist/core/workflow/workspaces/leases.js +30 -16
  200. package/dist/core/workflow/workspaces/local-git.d.ts +6 -2
  201. package/dist/core/workflow/workspaces/local-git.d.ts.map +1 -1
  202. package/dist/core/workflow/workspaces/local-git.js +328 -8
  203. package/dist/core/workflow/workspaces/ownership.d.ts +11 -0
  204. package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -0
  205. package/dist/core/workflow/workspaces/ownership.js +194 -0
  206. package/dist/core/workflow/workspaces/reclaim.d.ts +40 -0
  207. package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
  208. package/dist/core/workflow/workspaces/reclaim.js +14 -0
  209. package/dist/core/workflow/workspaces/recovery.d.ts +2 -0
  210. package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
  211. package/dist/core/workflow/workspaces/recovery.js +42 -10
  212. package/dist/core/workflow/workspaces/state.d.ts +1 -0
  213. package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
  214. package/dist/core/workflow/workspaces/state.js +21 -1
  215. package/dist/lib/browser/windows-chrome.d.ts +100 -12
  216. package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
  217. package/dist/lib/browser/windows-chrome.js +262 -73
  218. package/dist/lib/docs-links.d.ts.map +1 -1
  219. package/dist/lib/docs-links.js +45 -2
  220. package/dist/lib/guarded-remove.d.ts +7 -0
  221. package/dist/lib/guarded-remove.d.ts.map +1 -1
  222. package/dist/lib/guarded-remove.js +106 -12
  223. package/dist/lib/identities/assume.d.ts.map +1 -1
  224. package/dist/lib/identities/assume.js +4 -0
  225. package/dist/lib/tunnel/state.d.ts +13 -6
  226. package/dist/lib/tunnel/state.d.ts.map +1 -1
  227. package/dist/lib/tunnel/state.js +58 -12
  228. package/dist/lib/zip/index.d.ts +41 -0
  229. package/dist/lib/zip/index.d.ts.map +1 -0
  230. package/dist/lib/zip/index.js +398 -0
  231. package/package.json +12 -1
  232. package/src/commander.ts +10 -0
  233. package/src/commands/agents.ts +54 -63
  234. package/src/commands/browse.ts +50 -14
  235. package/src/commands/diagnostics.ts +10 -0
  236. package/src/commands/doctor.ts +26 -7
  237. package/src/commands/files.ts +13 -5
  238. package/src/commands/governor.ts +23 -10
  239. package/src/commands/inbox.ts +52 -2
  240. package/src/commands/rm.ts +43 -4
  241. package/src/commands/servers.ts +27 -26
  242. package/src/commands/supervisor.ts +7 -2
  243. package/src/commands/toc.ts +14 -6
  244. package/src/commands/tunnel.ts +533 -166
  245. package/src/commands/turn-rollback.ts +111 -0
  246. package/src/commands/workflow.ts +89 -30
  247. package/src/core/agents/coord-client.ts +20 -0
  248. package/src/core/agents/live-authority-v3.ts +17 -0
  249. package/src/core/agents/live-lifecycle-v3.ts +52 -14
  250. package/src/core/agents/mailbox.ts +26 -17
  251. package/src/core/agents/render/prompt-context.ts +10 -12
  252. package/src/core/agents/render/session-context.ts +9 -32
  253. package/src/core/agents/rules/stop-hook.ts +3 -3
  254. package/src/core/agents/state/heartbeat-writer.ts +29 -12
  255. package/src/core/agents/state/live-coordination-writer.ts +29 -0
  256. package/src/core/agents/state/pidmap.ts +13 -0
  257. package/src/core/agents/state/proc-start.ts +62 -2
  258. package/src/core/agents/state/process-handle.ts +165 -0
  259. package/src/core/agents/state/stale-sweep.ts +247 -82
  260. package/src/core/agents/state/unchanged-file.ts +456 -0
  261. package/src/core/artifacts/delivery-card.ts +7 -1
  262. package/src/core/context/index.ts +446 -136
  263. package/src/core/context/observation.ts +29 -0
  264. package/src/core/diagnostics/bundle.ts +23 -18
  265. package/src/core/diagnostics/path-identity.ts +15 -0
  266. package/src/core/diagnostics/sanitize.ts +4 -3
  267. package/src/core/events/v3/authority-outbox.ts +56 -0
  268. package/src/core/events/v3/generation-custody.ts +59 -0
  269. package/src/core/events/v3/producers/coordination-recorder.ts +15 -0
  270. package/src/core/events/v3/producers/recorder.ts +94 -68
  271. package/src/core/governor/planning.ts +20 -8
  272. package/src/core/governor/service-read.ts +128 -20
  273. package/src/core/governor/service.ts +119 -42
  274. package/src/core/hooks/cli.ts +33 -5
  275. package/src/core/inbox/contract.ts +2 -1
  276. package/src/core/inbox/index.ts +2 -0
  277. package/src/core/inbox/peer-messages.ts +94 -0
  278. package/src/core/inbox/service.ts +88 -22
  279. package/src/core/inbox/targets.ts +148 -0
  280. package/src/core/inbox/watcher.ts +8 -9
  281. package/src/core/journal/index.ts +19 -2
  282. package/src/core/resources/contract.ts +4 -0
  283. package/src/core/resources/service-status.ts +35 -12
  284. package/src/core/resources/service.ts +57 -95
  285. package/src/core/resources/status.ts +57 -8
  286. package/src/core/semantic/service.ts +2 -8
  287. package/src/core/servers/index.ts +376 -141
  288. package/src/core/servers/tunnels.ts +57 -13
  289. package/src/core/supervisor/contract.ts +13 -0
  290. package/src/core/supervisor/explanations.ts +4 -1
  291. package/src/core/supervisor/findings.ts +68 -10
  292. package/src/core/supervisor/service.ts +87 -26
  293. package/src/core/supervisor/services.ts +26 -8
  294. package/src/core/supervisor/status.ts +89 -12
  295. package/src/core/supervisor/timeline.ts +2 -1
  296. package/src/core/turn-rollback/content.ts +499 -0
  297. package/src/core/turn-rollback/index.ts +580 -0
  298. package/src/core/work/state.ts +7 -7
  299. package/src/core/workflow/engine.ts +131 -49
  300. package/src/core/workflow/failure-evidence.ts +141 -0
  301. package/src/core/workflow/index.ts +12 -2
  302. package/src/core/workflow/live-session-v3.ts +146 -5
  303. package/src/core/workflow/proof.ts +134 -26
  304. package/src/core/workflow/recovery-audit.ts +181 -0
  305. package/src/core/workflow/run-state.ts +165 -34
  306. package/src/core/workflow/spawn-claude.ts +18 -0
  307. package/src/core/workflow/spawn-codex.ts +13 -0
  308. package/src/core/workflow/spawn-cursor.ts +18 -0
  309. package/src/core/workflow/spawn-opencode.ts +14 -0
  310. package/src/core/workflow/types.ts +51 -17
  311. package/src/core/workflow/workspaces/index.ts +0 -1
  312. package/src/core/workflow/workspaces/leases.ts +48 -17
  313. package/src/core/workflow/workspaces/local-git.ts +388 -7
  314. package/src/core/workflow/workspaces/ownership.ts +232 -0
  315. package/src/core/workflow/workspaces/reclaim.ts +62 -0
  316. package/src/core/workflow/workspaces/recovery.ts +45 -10
  317. package/src/core/workflow/workspaces/state.ts +20 -0
  318. package/src/lib/browser/windows-chrome.ts +346 -86
  319. package/src/lib/docs-links.ts +49 -2
  320. package/src/lib/guarded-remove.ts +132 -14
  321. package/src/lib/identities/assume.ts +4 -0
  322. package/src/lib/tunnel/state.ts +70 -15
  323. package/src/lib/zip/index.ts +483 -0
@@ -134,6 +134,7 @@ import {
134
134
  import {
135
135
  harvestDomain,
136
136
  harvestWindowsChromeCookies,
137
+ keepWindowsChromeSession,
137
138
  stopWindowsChromeProfile,
138
139
  windowsChromePath,
139
140
  windowsLocalAppData,
@@ -3465,15 +3466,18 @@ function useWindowsPlainLogin(): boolean {
3465
3466
 
3466
3467
  /**
3467
3468
  * The WSL form of `--login`: the person signs in through the Windows
3468
- * Chrome on a dedicated Windows profile with nothing attached. When the window
3469
- * closes, the profile's cookies for the sign-in site are read and merged into
3470
- * the shared cookie store, which later `browse` runs attach.
3469
+ * Chrome on a dedicated Windows profile with nothing attached. Before the
3470
+ * window opens, the profile is set to continue where it left off, so Chrome
3471
+ * keeps browser-session cookies when the window closes. When the window
3472
+ * closes, the profile's cookies for the sign-in site are read and merged
3473
+ * into the shared cookie store, which later `browse` runs attach, and the
3474
+ * last page open on that site is reported so an agent can resume from it.
3471
3475
  */
3472
3476
  async function runWindowsPlainLogin(
3473
3477
  url: string,
3474
3478
  storePath: string,
3475
3479
  loginCloseFile: string | null,
3476
- emit: { log: (message: string, level: "info" | "warn" | "error" | "debug") => void },
3480
+ emit: Pick<EmitContext, "log" | "data">,
3477
3481
  ): Promise<void> {
3478
3482
  const chromeLinux = windowsChromePath();
3479
3483
  const localAppData = windowsLocalAppData();
@@ -3484,6 +3488,13 @@ async function runWindowsPlainLogin(
3484
3488
  }
3485
3489
  const chromeWindows = wslPathToWindows(chromeLinux);
3486
3490
  const profileWindows = windowsPlainProfile(localAppData);
3491
+ const keep = keepWindowsChromeSession({ chromeWindows, profileWindows });
3492
+ if (!keep.kept) {
3493
+ emit.log(
3494
+ "[--login] Could not set the sign-in profile to keep browser-session cookies. A sign-in that relies on them survives only if you end it from the terminal instead of closing the window.",
3495
+ "warn",
3496
+ );
3497
+ }
3487
3498
  const child = spawn(
3488
3499
  chromeLinux,
3489
3500
  [`--user-data-dir=${profileWindows}`, "--no-first-run", "--no-default-browser-check", url],
@@ -3507,24 +3518,49 @@ async function runWindowsPlainLogin(
3507
3518
  );
3508
3519
  await Promise.race(waits);
3509
3520
  lifecycleAbort.abort();
3510
- stopWindowsChromeProfile(profileWindows);
3521
+ const stop = stopWindowsChromeProfile(profileWindows);
3522
+ if (stop.forced > 0) {
3523
+ emit.log(
3524
+ "[--login] Chrome did not close when asked and was stopped. Cookies set in its last 30 seconds may be missing; run the sign-in again if later pages are signed out.",
3525
+ "warn",
3526
+ );
3527
+ }
3511
3528
  await Promise.race([exited, new Promise((r) => setTimeout(r, 5000))]);
3512
3529
  const domain = harvestDomain(url);
3513
- const cookies = harvestWindowsChromeCookies({ chromeWindows, profileWindows, domain });
3530
+ const { cookies, pages } = harvestWindowsChromeCookies({ chromeWindows, profileWindows, domain });
3531
+ const lastPage = pages[0] ?? null;
3532
+ const names = [...new Set(cookies.map((cookie) => cookie.name))].sort();
3533
+ const sessionCookies = cookies.filter((cookie) => cookie.expires <= 0).length;
3514
3534
  if (cookies.length === 0) {
3515
3535
  emit.log(
3516
3536
  `[--login] No ${domain} cookies were found in the Windows profile; the sign-in may not have completed.`,
3517
3537
  "warn",
3518
3538
  );
3519
- return;
3539
+ } else {
3540
+ const jar = new CookieJar({ path: storePath, source: "harn-browse" });
3541
+ jar.save(mergeCookies(jar.load(), cookies));
3542
+ emit.log(
3543
+ `[--login] Copied ${cookies.length} ${domain} cookies (${sessionCookies} browser-session) into ${storePath}: ${names.join(", ")}.`,
3544
+ "info",
3545
+ );
3520
3546
  }
3521
- const jar = new CookieJar({ path: storePath, source: "harn-browse" });
3522
- jar.save(mergeCookies(jar.load(), cookies));
3523
- const names = [...new Set(cookies.map((cookie) => cookie.name))].sort().join(", ");
3524
- emit.log(
3525
- `[--login] Copied ${cookies.length} ${domain} cookies into ${storePath}: ${names}.`,
3526
- "info",
3527
- );
3547
+ if (lastPage) {
3548
+ emit.log(
3549
+ `[--login] Last page: ${lastPage} (the last page you had open on ${domain}; open it to continue where the sign-in left off, since its address may carry a sign-in token that cookies alone do not replace).`,
3550
+ "info",
3551
+ );
3552
+ }
3553
+ emit.data({
3554
+ login: {
3555
+ domain,
3556
+ store: storePath,
3557
+ cookies: cookies.length,
3558
+ sessionCookies,
3559
+ names,
3560
+ lastPage,
3561
+ pages,
3562
+ },
3563
+ });
3528
3564
  }
3529
3565
 
3530
3566
  async function waitForLoginCloseFile(path: string, signal?: AbortSignal): Promise<void> {
@@ -50,6 +50,7 @@ export function registerDiagnosticsCommand(
50
50
  findings,
51
51
  bundles,
52
52
  capability,
53
+ supervisor: readSupervisorStatus(repoRoot),
53
54
  total_findings: findings.length,
54
55
  total_bundles: bundles.length,
55
56
  });
@@ -68,6 +69,7 @@ export function registerDiagnosticsCommand(
68
69
  schema_version: DIAGNOSTIC_COMMAND_SCHEMA_VERSION,
69
70
  kind: "diagnostic_finding",
70
71
  finding,
72
+ supervisor: readSupervisorStatus(repoRoot),
71
73
  timeline:
72
74
  readSupervisorTimeline(repoRoot, finding.id) ?? buildSupervisorTimeline(finding),
73
75
  explanation:
@@ -118,6 +120,7 @@ export function registerDiagnosticsCommand(
118
120
  schema_version: DIAGNOSTIC_COMMAND_SCHEMA_VERSION,
119
121
  kind: "diagnostic_advice",
120
122
  source: { mode: "live" },
123
+ supervisor: readSupervisorStatus(repoRoot),
121
124
  advice: buildDiagnosticAdvice({
122
125
  assessment: published.assessment,
123
126
  priorHysteresis: published.prior_hysteresis,
@@ -204,6 +207,13 @@ function liveFindingsCapability(repoRoot: string, reportPresent: boolean): Super
204
207
  }
205
208
  const status = readSupervisorStatus(repoRoot);
206
209
  if (status.running) return { source_kind: "supervisor.findings", state: "supported" };
210
+ if (status.liveness === "unknown")
211
+ return {
212
+ source_kind: "supervisor.findings",
213
+ state: "error",
214
+ reason_code: "supervisor_generation_unknown",
215
+ detail: status.build_status,
216
+ };
207
217
  return {
208
218
  source_kind: "supervisor.findings",
209
219
  state: "expired",
@@ -421,15 +421,34 @@ function checkWorkflowAdapter(adapter: AdapterName): { check: Check; installed:
421
421
  }
422
422
 
423
423
  function checkNode(): Check {
424
- const v = process.versions.node;
424
+ return nodeCheck(whichVersion("node"), process.versions);
425
+ }
426
+
427
+ /**
428
+ * Report the `node` on PATH, which is what Node-based tools run. Under Bun,
429
+ * `process.versions.node` is the Node version Bun emulates, not an installed
430
+ * Node, so it is used only when doctor itself runs on Node and PATH has none.
431
+ */
432
+ export function nodeCheck(
433
+ onPath: { ok: boolean; out: string },
434
+ runtime: { node: string; bun?: string },
435
+ ): Check {
436
+ const hint = "https://nodejs.org/en/download";
437
+ let v = onPath.ok ? onPath.out.replace(/^v/, "") : "";
438
+ if (!v) {
439
+ if (runtime.bun) {
440
+ return {
441
+ name: "node",
442
+ severity: "warn",
443
+ detail: "not on PATH (Bun runs harnery; Node-based tools need it)",
444
+ hint,
445
+ };
446
+ }
447
+ v = runtime.node;
448
+ }
425
449
  const major = Number.parseInt(v.split(".")[0], 10);
426
450
  if (Number.isNaN(major) || major < 20) {
427
- return {
428
- name: "node",
429
- severity: "fail",
430
- detail: `${v} (need ≥ 20)`,
431
- hint: "https://nodejs.org/en/download",
432
- };
451
+ return { name: "node", severity: "fail", detail: `${v} (need ≥ 20)`, hint };
433
452
  }
434
453
  return { name: "node", severity: "ok", detail: v };
435
454
  }
@@ -4,6 +4,7 @@ import type { Command } from "commander";
4
4
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
5
5
  import { resolveCoordRoot } from "../core/agents/coord-client.ts";
6
6
  import { DEFAULT_WEB_PORT, resolveWebPort } from "../core/config.ts";
7
+ import { createTunnelReadinessVerifier } from "../core/servers/index.ts";
7
8
  import { coordRootId, localCoordRootIdUrl } from "../lib/coord-root-id.ts";
8
9
  import {
9
10
  encodedRepoPath,
@@ -14,10 +15,10 @@ import {
14
15
  } from "../lib/local-file-url.ts";
15
16
  import { isPathAllowed } from "../lib/tunnel/path-scope.ts";
16
17
  import {
17
- isTunnelStateLive,
18
18
  listStates,
19
- type ProcessAliveCheck,
19
+ type TunnelReadinessVerifier,
20
20
  type TunnelState,
21
+ tunnelReadiness,
21
22
  tunnelTargetPort,
22
23
  } from "../lib/tunnel/state.ts";
23
24
 
@@ -208,10 +209,10 @@ export function tunnelFileUrl(
208
209
  local: LocalFileUrl,
209
210
  port: number,
210
211
  states: TunnelState[],
211
- processAlive?: ProcessAliveCheck,
212
+ verifier: TunnelReadinessVerifier,
212
213
  ): string {
213
214
  const live = states.filter(
214
- (state) => tunnelTargetPort(state) === port && isTunnelStateLive(state, processAlive),
215
+ (state) => tunnelTargetPort(state) === port && tunnelReadiness(state, verifier) === "ready",
215
216
  );
216
217
  if (local.mode === "raw") {
217
218
  const path = `/${encodedRepoPath(local.relPath)}`;
@@ -309,7 +310,14 @@ export function registerFilesCommand(
309
310
  mode: opts.raw ? "raw" : opts.viewer ? "viewer" : "auto",
310
311
  });
311
312
  if (opts.verify !== false) await verifyDashboardRoot(coordRoot, port);
312
- const url = opts.tunnel ? tunnelFileUrl(result, port, listStates(coordRoot)) : result.url;
313
+ const url = opts.tunnel
314
+ ? tunnelFileUrl(
315
+ result,
316
+ port,
317
+ listStates(coordRoot),
318
+ createTunnelReadinessVerifier({ coordRoot }),
319
+ )
320
+ : result.url;
313
321
  emit.text(`${url}\n`);
314
322
  } catch (error) {
315
323
  if (error instanceof TunnelLinkError) {
@@ -385,12 +385,13 @@ function registerServiceCommand(
385
385
  .action(async (opts: { json?: boolean }) => {
386
386
  await withGovernorRootAsync(emit, async (coordRoot) => {
387
387
  let status = requestGovernorServiceStop(coordRoot);
388
+ const signal = { signal_sent: status.signal_sent, signal_verdict: status.signal_verdict };
388
389
  const deadline = Date.now() + 5_000;
389
390
  while (status.running && Date.now() < deadline) {
390
391
  await delay(50);
391
392
  status = readGovernorServiceStatus(coordRoot);
392
393
  }
393
- emitServiceStatus(status, opts.json, emit);
394
+ emitServiceStatus({ ...status, ...signal }, opts.json, emit);
394
395
  });
395
396
  });
396
397
 
@@ -534,19 +535,31 @@ function emitServiceStatus(
534
535
  emit.data(status);
535
536
  return;
536
537
  }
537
- if (!status.config && !status.record) {
538
- emit.text("governor service: unconfigured\n");
539
- return;
540
- }
541
- const state = status.running
542
- ? (status.record?.state ?? "running")
543
- : status.stale
544
- ? "stale"
545
- : "stopped";
538
+ const state =
539
+ status.liveness === "unknown"
540
+ ? "unknown"
541
+ : !status.config && !status.record
542
+ ? "unconfigured"
543
+ : status.running
544
+ ? (status.record?.state ?? "running")
545
+ : status.stale
546
+ ? "stale"
547
+ : "stopped";
546
548
  const lines = [
547
549
  `governor service: ${state}`,
550
+ `generation: ${status.generation}`,
551
+ `liveness: ${status.liveness}`,
552
+ `running build: ${status.running_build ? `${status.running_build.version} (${status.running_build.revision})` : "unknown"}`,
553
+ `installed build: ${status.installed_build.version} (${status.installed_build.revision})`,
554
+ `build: ${status.build_status}`,
548
555
  `goals: ${status.config?.goal_ids.join(", ") || "none"}`,
549
556
  ];
557
+ if (status.signal_sent !== undefined) {
558
+ lines.push(`signal sent: ${status.signal_sent ? "yes" : "no"} (${status.signal_verdict})`);
559
+ if (!status.signal_sent && status.record?.state !== "stopped") {
560
+ lines.push("The durable stop request remains for the service to read.");
561
+ }
562
+ }
550
563
  if (status.record) {
551
564
  lines.push(`pid: ${status.record.pid} on ${status.record.host}`);
552
565
  lines.push(`heartbeat: ${status.record.heartbeat_at}`);
@@ -1,18 +1,21 @@
1
1
  import type { Command } from "commander";
2
2
  import type { EmitContext } from "../commander.ts";
3
3
  import type { HarneryInboxService } from "../core/inbox/service.ts";
4
+ import { watchInbox } from "../core/inbox/watcher.ts";
4
5
 
5
6
  export function registerInboxCommand(
6
7
  program: Command,
7
8
  emit: EmitContext,
8
9
  service?: HarneryInboxService,
10
+ defaultRecipient?: () => string,
9
11
  ): void {
10
12
  const inbox = program.command("inbox").description("Inspect private coordination inbox state");
11
13
  inbox
12
- .command("list <recipient>")
14
+ .command("list [recipient]")
13
15
  .option("--json", "Emit stable JSON")
14
- .action((recipient: string, options: { json?: boolean }) => {
16
+ .action((recipient: string | undefined, options: { json?: boolean }) => {
15
17
  run(emit, () => {
18
+ recipient = requireRecipient(recipient, defaultRecipient);
16
19
  const rows = requireInboxService(service)
17
20
  .pending(recipient)
18
21
  .map((record) => ({
@@ -22,6 +25,7 @@ export function registerInboxCommand(
22
25
  created_at: record.created_at,
23
26
  body_bytes: record.body_bytes,
24
27
  body: record.body,
28
+ state: "pending",
25
29
  }));
26
30
  if (options.json) {
27
31
  emit.config({ format: "json" });
@@ -41,6 +45,52 @@ export function registerInboxCommand(
41
45
  } else emit.rows([status as unknown as Record<string, unknown>]);
42
46
  });
43
47
  });
48
+ inbox
49
+ .command("watch [recipient]")
50
+ .description("Surface pending messages for this instance; poll history until timeout")
51
+ .option("--timeout <seconds>", "Stop after this many seconds", "60")
52
+ .option("--json", "Emit one JSON record per surfaced message")
53
+ .action(async (recipient: string | undefined, options: { json?: boolean; timeout: string }) => {
54
+ let controller: AbortController | undefined;
55
+ let timer: ReturnType<typeof setTimeout> | undefined;
56
+ try {
57
+ const instanceId = requireRecipient(recipient, defaultRecipient);
58
+ const inboxService = requireInboxService(service);
59
+ const seconds = Number(options.timeout);
60
+ if (!Number.isFinite(seconds) || seconds <= 0 || seconds > 3600)
61
+ throw new Error("--timeout must be greater than 0 and at most 3600 seconds");
62
+ controller = new AbortController();
63
+ timer = setTimeout(() => controller!.abort(), seconds * 1000);
64
+ if (options.json) emit.config({ format: "json" });
65
+ for await (const message of watchInbox(inboxService, instanceId, {
66
+ signal: controller.signal,
67
+ })) {
68
+ const row = { ...message, state: "surfaced" };
69
+ if (options.json) emit.data(row);
70
+ else emit.rows([row]);
71
+ // An output error leaves this ID pending for prompt recovery.
72
+ await new Promise<void>((resolve, reject) =>
73
+ process.stdout.write("", (error) => (error ? reject(error) : resolve())),
74
+ );
75
+ inboxService.acknowledgeSurface(instanceId, [message.message_id]);
76
+ }
77
+ } catch (error) {
78
+ emit.error({
79
+ code: "inbox_failed",
80
+ message: error instanceof Error ? error.message : String(error),
81
+ });
82
+ emit.setExitCode(1);
83
+ } finally {
84
+ if (timer) clearTimeout(timer);
85
+ controller?.abort();
86
+ }
87
+ });
88
+ }
89
+
90
+ function requireRecipient(recipient: string | undefined, fallback?: () => string): string {
91
+ const resolved = recipient ?? fallback?.();
92
+ if (!resolved) throw new Error("recipient instance is required outside an agent session");
93
+ return resolved;
44
94
  }
45
95
 
46
96
  function requireInboxService(service?: HarneryInboxService): HarneryInboxService {
@@ -1,9 +1,10 @@
1
1
  import { spawnSync } from "node:child_process";
2
- import { existsSync } from "node:fs";
3
- import { isAbsolute, relative, resolve, sep } from "node:path";
2
+ import { existsSync, lstatSync, readFileSync, realpathSync } from "node:fs";
3
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
4
4
  import type { Command } from "commander";
5
5
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
6
6
  import { resolveCoordRoot } from "../core/agents/coord-client.ts";
7
+ import { artifactsRoot, parseArtifactManifest } from "../core/artifacts/index.ts";
7
8
  import {
8
9
  readCoordinationViewV3,
9
10
  requireAuthoritySafeCoordinationViewV3,
@@ -25,6 +26,15 @@ export function registerRmCommand(
25
26
  "Allowed directory; each target must be strictly inside it",
26
27
  )
27
28
  .option("-r, --recursive", "Permit directory removal after inspecting its complete contents")
29
+ .option(
30
+ "--clean-clone",
31
+ "Remove only clean standalone clones inside one owned artifact; refuse local-only commits and worktrees",
32
+ )
33
+ .option(
34
+ "--entry-limit <n>",
35
+ "Maximum inspected entries, 1-100000 (default 10000); larger limits require one current caller-owned managed artifact as --root",
36
+ (value: string) => Number(value),
37
+ )
28
38
  .option(
29
39
  "--allow-protected <path>",
30
40
  "Remove this one protected path (Git metadata, credentials, Harnery state, keys) inside a target; repeat per path",
@@ -65,7 +75,34 @@ export function registerRmCommand(
65
75
  const liveRoots = [coordRoot, gitTopLevel(process.cwd())].filter((dir): dir is string =>
66
76
  Boolean(dir),
67
77
  );
68
- const report = guardedRemove(paths, { ...options, liveRoots }, checkClaims);
78
+ const assertOwnedArtifact = (root: string): void => {
79
+ if (!coordRoot || dirname(root) !== realpathSync(artifactsRoot(coordRoot)))
80
+ throw new Error("Copied state requires --root to name one owned managed artifact");
81
+ const view = requireAuthoritySafeCoordinationViewV3(readCoordinationViewV3(coordRoot));
82
+ const owner = resolveOwner({ payload: null, coordRoot });
83
+ const ownId = owner ? liveInstanceIdV3(owner.instance_id) : undefined;
84
+ if (!ownId || !view.instances[ownId]?.authority_eligible)
85
+ throw new Error("Copied state requires an authenticated current artifact owner");
86
+ const record = resolve(root, ".harnery-artifact.json");
87
+ const stat = lstatSync(record);
88
+ if (!stat.isFile() || stat.isSymbolicLink())
89
+ throw new Error("An owned artifact requires a regular manifest file");
90
+ const parsed = parseArtifactManifest(JSON.parse(readFileSync(record, "utf8")));
91
+ if (
92
+ !parsed.ok ||
93
+ parsed.manifest.released_at ||
94
+ !parsed.manifest.created_by?.instance_id ||
95
+ liveInstanceIdV3(parsed.manifest.created_by.instance_id) !== ownId
96
+ )
97
+ throw new Error(
98
+ "Copied state requires a valid unreleased artifact owned by the caller",
99
+ );
100
+ };
101
+ const report = guardedRemove(
102
+ paths,
103
+ { ...options, liveRoots, assertOwnedArtifact },
104
+ checkClaims,
105
+ );
69
106
  if (options.json) {
70
107
  emit.config({ format: "json" });
71
108
  emit.data(report);
@@ -76,7 +113,9 @@ export function registerRmCommand(
76
113
  ];
77
114
  if (report.protected.length)
78
115
  lines.push(
79
- "Protected paths named with --allow-protected:",
116
+ options.cleanClone
117
+ ? "Verified clone metadata:"
118
+ : "Protected paths named with --allow-protected:",
80
119
  ...report.protected.map((path) => ` ${path}`),
81
120
  );
82
121
  if (report.links.length)
@@ -1,5 +1,4 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { hostname } from "node:os";
3
2
  import type { Command } from "commander";
4
3
  import type { EmitContext, HarneryProgramContext } from "../commander.ts";
5
4
  import { resolveCoordRoot } from "../core/agents/coord-client.ts";
@@ -111,22 +110,13 @@ export function registerServersCommand(
111
110
  const preview = Boolean(options.type) && !options.yes;
112
111
  const results: Array<StopResult | { id: string; would_stop: true }> = [];
113
112
  for (const stray of strays) {
114
- results.push(
115
- await stopServer(
116
- {
117
- schema_version: 1,
118
- id: `pid-${stray.pid}`,
119
- kind: "session",
120
- type: "unregistered",
121
- label: stray.command,
122
- pid: stray.pid,
123
- host: hostname(),
124
- started_at: new Date().toISOString(),
125
- registered_at: new Date().toISOString(),
126
- },
127
- { coordRoot },
128
- ),
129
- );
113
+ const receipt = adoptServer(stray.pid, {
114
+ coordRoot,
115
+ type: "unregistered",
116
+ owner: "none",
117
+ dependencies: { listServers: () => report },
118
+ });
119
+ results.push(await stopServer(receipt, { coordRoot }));
130
120
  }
131
121
  for (const view of selected) {
132
122
  if (preview && !ids.includes(view.record.id))
@@ -207,10 +197,20 @@ export function registerServersCommand(
207
197
  emit.config({ format: "json" });
208
198
  emit.data({ applied: Boolean(options.yes), pruned: report.pruned, decisions, results });
209
199
  } else {
210
- const lines = decisions.map(
211
- (d) =>
212
- `${d.action === "stop" ? (options.yes ? "stopped" : "would stop") : "keep"} ${d.id} ${d.reason}`,
213
- );
200
+ const lines = decisions.map((decision) => {
201
+ const result = results.find((item) => item.id === decision.id);
202
+ const action =
203
+ decision.action === "keep"
204
+ ? "keep"
205
+ : !options.yes
206
+ ? "would stop"
207
+ : result?.stopped
208
+ ? "stopped"
209
+ : result?.method === "not-running"
210
+ ? "already stopped"
211
+ : "could not stop";
212
+ return `${action} ${decision.id} ${result?.detail ?? decision.reason}`;
213
+ });
214
214
  if (report.pruned.length)
215
215
  lines.push(`Removed records of exited servers: ${report.pruned.join(", ")}`);
216
216
  if (!decisions.length) lines.push("No registered servers.");
@@ -218,6 +218,7 @@ export function registerServersCommand(
218
218
  lines.push("Preview only. Re-run with --yes to stop.");
219
219
  emit.text(lines.join("\n"));
220
220
  }
221
+ if (results.some((result) => result.method === "refused")) emit.setExitCode(1);
221
222
  } catch (error) {
222
223
  fail(error);
223
224
  }
@@ -396,14 +397,14 @@ export function registerServersCommand(
396
397
 
397
398
  cmd
398
399
  .command("unregister <id>")
399
- .description("Remove a server's record (with --pid, only if the record still names that pid)")
400
+ .description("Remove the exact observed server registration (optionally require --pid)")
400
401
  .option("--pid <pid>", "Only remove when the record names this pid")
401
402
  .action((id: string, options: { pid?: string }) => {
402
403
  try {
403
- const removed = unregisterServer(id, {
404
- coordRoot: root(),
405
- ...(options.pid ? { pid: integer(options.pid, "--pid") } : {}),
406
- });
404
+ const coordRoot = root();
405
+ const record = readServer(id, { coordRoot });
406
+ const matchesPid = !options.pid || record?.pid === integer(options.pid, "--pid");
407
+ const removed = !!record && matchesPid && unregisterServer(record, { coordRoot });
407
408
  emit.config({ format: "json" });
408
409
  emit.data({ id, removed });
409
410
  } catch (error) {
@@ -83,11 +83,16 @@ export function registerSupervisorCommand(
83
83
  .action(async (options: { root?: string }) => {
84
84
  try {
85
85
  const root = resolve(options.root ?? coordRoot(context));
86
- let status = requestSupervisorStop(root);
86
+ const requested = requestSupervisorStop(root);
87
+ let status = requested;
87
88
  const deadline = Date.now() + 5_000;
88
89
  while (status.running && Date.now() < deadline) {
89
90
  await new Promise((done) => setTimeout(done, 50));
90
- status = readSupervisorStatus(root);
91
+ status = {
92
+ ...readSupervisorStatus(root),
93
+ signal_sent: requested.signal_sent,
94
+ signal_verdict: requested.signal_verdict,
95
+ };
91
96
  }
92
97
  emit.data(status);
93
98
  } catch (error) {
@@ -172,17 +172,25 @@ function renderToc(r: TocResult): string {
172
172
  return lines.join("\n");
173
173
  }
174
174
 
175
- async function runSection(file: string, query: string, opts: SectionOpts): Promise<SectionResult> {
175
+ export async function runSection(
176
+ file: string,
177
+ query: string,
178
+ opts: SectionOpts,
179
+ ): Promise<SectionResult> {
176
180
  if (!query.trim()) throw new Error("header query required");
177
181
  const tocResult = await runToc(file, {});
178
182
  const absPath = resolve(file);
179
183
  const lines = readFileSync(absPath, "utf8").split("\n");
180
184
 
181
- const matches = tocResult.headers.filter((h) =>
182
- opts.caseSensitive
183
- ? h.text.includes(query)
184
- : h.text.toLowerCase().includes(query.toLowerCase()),
185
- );
185
+ const same = (a: string, b: string) =>
186
+ opts.caseSensitive ? a === b : a.toLowerCase() === b.toLowerCase();
187
+ const contains = (a: string, b: string) =>
188
+ opts.caseSensitive ? a.includes(b) : a.toLowerCase().includes(b.toLowerCase());
189
+ // A header that equals the query wins over headers that merely contain it,
190
+ // so "Media" can still select "## Media" beside "## Media recipes".
191
+ const exact = tocResult.headers.filter((h) => same(h.text, query.trim()));
192
+ const matches =
193
+ exact.length > 0 ? exact : tocResult.headers.filter((h) => contains(h.text, query));
186
194
  if (matches.length === 0) {
187
195
  throw new Error(
188
196
  `no header matching "${query}". Run \`${resolveBinName()} toc ${file}\` to list available headers.`,