harnery 0.0.1 → 0.2.1

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 (445) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -2
  3. package/bin/agent-coord +42 -0
  4. package/bin/agent-hook +44 -0
  5. package/bin/harn +40 -0
  6. package/dist/cli.d.ts +9 -0
  7. package/dist/cli.d.ts.map +1 -0
  8. package/dist/cli.js +18 -0
  9. package/dist/commander.d.ts +128 -0
  10. package/dist/commander.d.ts.map +1 -0
  11. package/dist/commander.js +126 -0
  12. package/dist/commands/agents.d.ts +18 -0
  13. package/dist/commands/agents.d.ts.map +1 -0
  14. package/dist/commands/agents.js +3946 -0
  15. package/dist/commands/backup.d.ts +22 -0
  16. package/dist/commands/backup.d.ts.map +1 -0
  17. package/dist/commands/backup.js +262 -0
  18. package/dist/commands/browse-ai.d.ts +4 -0
  19. package/dist/commands/browse-ai.d.ts.map +1 -0
  20. package/dist/commands/browse-ai.js +156 -0
  21. package/dist/commands/browse.d.ts +4 -0
  22. package/dist/commands/browse.d.ts.map +1 -0
  23. package/dist/commands/browse.js +590 -0
  24. package/dist/commands/callers.d.ts +4 -0
  25. package/dist/commands/callers.d.ts.map +1 -0
  26. package/dist/commands/callers.js +276 -0
  27. package/dist/commands/completion.d.ts +17 -0
  28. package/dist/commands/completion.d.ts.map +1 -0
  29. package/dist/commands/completion.js +158 -0
  30. package/dist/commands/config-get.d.ts +4 -0
  31. package/dist/commands/config-get.d.ts.map +1 -0
  32. package/dist/commands/config-get.js +131 -0
  33. package/dist/commands/context.d.ts +11 -0
  34. package/dist/commands/context.d.ts.map +1 -0
  35. package/dist/commands/context.js +185 -0
  36. package/dist/commands/cookies.d.ts +4 -0
  37. package/dist/commands/cookies.d.ts.map +1 -0
  38. package/dist/commands/cookies.js +140 -0
  39. package/dist/commands/docs.d.ts +4 -0
  40. package/dist/commands/docs.d.ts.map +1 -0
  41. package/dist/commands/docs.js +137 -0
  42. package/dist/commands/doctor.d.ts +25 -0
  43. package/dist/commands/doctor.d.ts.map +1 -0
  44. package/dist/commands/doctor.js +200 -0
  45. package/dist/commands/edit-batch.d.ts +18 -0
  46. package/dist/commands/edit-batch.d.ts.map +1 -0
  47. package/dist/commands/edit-batch.js +172 -0
  48. package/dist/commands/eml.d.ts +4 -0
  49. package/dist/commands/eml.d.ts.map +1 -0
  50. package/dist/commands/eml.js +428 -0
  51. package/dist/commands/env.d.ts +4 -0
  52. package/dist/commands/env.d.ts.map +1 -0
  53. package/dist/commands/env.js +201 -0
  54. package/dist/commands/fetch.d.ts +4 -0
  55. package/dist/commands/fetch.d.ts.map +1 -0
  56. package/dist/commands/fetch.js +99 -0
  57. package/dist/commands/file-history.d.ts +4 -0
  58. package/dist/commands/file-history.d.ts.map +1 -0
  59. package/dist/commands/file-history.js +152 -0
  60. package/dist/commands/grep.d.ts +4 -0
  61. package/dist/commands/grep.d.ts.map +1 -0
  62. package/dist/commands/grep.js +317 -0
  63. package/dist/commands/init.d.ts +82 -0
  64. package/dist/commands/init.d.ts.map +1 -0
  65. package/dist/commands/init.js +288 -0
  66. package/dist/commands/outline.d.ts +4 -0
  67. package/dist/commands/outline.d.ts.map +1 -0
  68. package/dist/commands/outline.js +509 -0
  69. package/dist/commands/presence.d.ts +12 -0
  70. package/dist/commands/presence.d.ts.map +1 -0
  71. package/dist/commands/presence.js +123 -0
  72. package/dist/commands/read.d.ts +7 -0
  73. package/dist/commands/read.d.ts.map +1 -0
  74. package/dist/commands/read.js +46 -0
  75. package/dist/commands/scratch.d.ts +4 -0
  76. package/dist/commands/scratch.d.ts.map +1 -0
  77. package/dist/commands/scratch.js +426 -0
  78. package/dist/commands/session.d.ts +4 -0
  79. package/dist/commands/session.d.ts.map +1 -0
  80. package/dist/commands/session.js +162 -0
  81. package/dist/commands/sync.d.ts +24 -0
  82. package/dist/commands/sync.d.ts.map +1 -0
  83. package/dist/commands/sync.js +275 -0
  84. package/dist/commands/toc.d.ts +5 -0
  85. package/dist/commands/toc.d.ts.map +1 -0
  86. package/dist/commands/toc.js +153 -0
  87. package/dist/commands/tokens.d.ts +4 -0
  88. package/dist/commands/tokens.d.ts.map +1 -0
  89. package/dist/commands/tokens.js +48 -0
  90. package/dist/commands/tunnel.d.ts +4 -0
  91. package/dist/commands/tunnel.d.ts.map +1 -0
  92. package/dist/commands/tunnel.js +513 -0
  93. package/dist/commands/uninstall.d.ts +22 -0
  94. package/dist/commands/uninstall.d.ts.map +1 -0
  95. package/dist/commands/uninstall.js +126 -0
  96. package/dist/commands/web.d.ts +4 -0
  97. package/dist/commands/web.d.ts.map +1 -0
  98. package/dist/commands/web.js +165 -0
  99. package/dist/core/agents/canonical-emit.d.ts +27 -0
  100. package/dist/core/agents/canonical-emit.d.ts.map +1 -0
  101. package/dist/core/agents/canonical-emit.js +72 -0
  102. package/dist/core/agents/cli-emit.d.ts +27 -0
  103. package/dist/core/agents/cli-emit.d.ts.map +1 -0
  104. package/dist/core/agents/cli-emit.js +57 -0
  105. package/dist/core/agents/cli.d.ts +10 -0
  106. package/dist/core/agents/cli.d.ts.map +1 -0
  107. package/dist/core/agents/cli.js +757 -0
  108. package/dist/core/agents/codex-replay.d.ts +29 -0
  109. package/dist/core/agents/codex-replay.d.ts.map +1 -0
  110. package/dist/core/agents/codex-replay.js +138 -0
  111. package/dist/core/agents/coord-client.d.ts +98 -0
  112. package/dist/core/agents/coord-client.d.ts.map +1 -0
  113. package/dist/core/agents/coord-client.js +212 -0
  114. package/dist/core/agents/events/consume.d.ts +59 -0
  115. package/dist/core/agents/events/consume.d.ts.map +1 -0
  116. package/dist/core/agents/events/consume.js +147 -0
  117. package/dist/core/agents/events/emit.d.ts +42 -0
  118. package/dist/core/agents/events/emit.d.ts.map +1 -0
  119. package/dist/core/agents/events/emit.js +70 -0
  120. package/dist/core/agents/events/ulid.d.ts +11 -0
  121. package/dist/core/agents/events/ulid.d.ts.map +1 -0
  122. package/dist/core/agents/events/ulid.js +47 -0
  123. package/dist/core/agents/index.d.ts +14 -0
  124. package/dist/core/agents/index.d.ts.map +1 -0
  125. package/dist/core/agents/index.js +13 -0
  126. package/dist/core/agents/paths.d.ts +6 -0
  127. package/dist/core/agents/paths.d.ts.map +1 -0
  128. package/dist/core/agents/paths.js +17 -0
  129. package/dist/core/agents/render/prompt-context.d.ts +43 -0
  130. package/dist/core/agents/render/prompt-context.d.ts.map +1 -0
  131. package/dist/core/agents/render/prompt-context.js +335 -0
  132. package/dist/core/agents/render/session-context.d.ts +39 -0
  133. package/dist/core/agents/render/session-context.d.ts.map +1 -0
  134. package/dist/core/agents/render/session-context.js +283 -0
  135. package/dist/core/agents/rules/claim-conflict.d.ts +35 -0
  136. package/dist/core/agents/rules/claim-conflict.d.ts.map +1 -0
  137. package/dist/core/agents/rules/claim-conflict.js +244 -0
  138. package/dist/core/agents/rules/commit-conflict.d.ts +59 -0
  139. package/dist/core/agents/rules/commit-conflict.d.ts.map +1 -0
  140. package/dist/core/agents/rules/commit-conflict.js +244 -0
  141. package/dist/core/agents/rules/stop-hook.d.ts +44 -0
  142. package/dist/core/agents/rules/stop-hook.d.ts.map +1 -0
  143. package/dist/core/agents/rules/stop-hook.js +161 -0
  144. package/dist/core/agents/session-events.d.ts +41 -0
  145. package/dist/core/agents/session-events.d.ts.map +1 -0
  146. package/dist/core/agents/session-events.js +205 -0
  147. package/dist/core/agents/state/activity-log.d.ts +18 -0
  148. package/dist/core/agents/state/activity-log.d.ts.map +1 -0
  149. package/dist/core/agents/state/activity-log.js +34 -0
  150. package/dist/core/agents/state/council.d.ts +39 -0
  151. package/dist/core/agents/state/council.d.ts.map +1 -0
  152. package/dist/core/agents/state/council.js +216 -0
  153. package/dist/core/agents/state/heartbeat-projector.d.ts +59 -0
  154. package/dist/core/agents/state/heartbeat-projector.d.ts.map +1 -0
  155. package/dist/core/agents/state/heartbeat-projector.js +436 -0
  156. package/dist/core/agents/state/heartbeat-writer.d.ts +64 -0
  157. package/dist/core/agents/state/heartbeat-writer.d.ts.map +1 -0
  158. package/dist/core/agents/state/heartbeat-writer.js +271 -0
  159. package/dist/core/agents/state/names.d.ts +35 -0
  160. package/dist/core/agents/state/names.d.ts.map +1 -0
  161. package/dist/core/agents/state/names.js +376 -0
  162. package/dist/core/agents/state/pidmap.d.ts +11 -0
  163. package/dist/core/agents/state/pidmap.d.ts.map +1 -0
  164. package/dist/core/agents/state/pidmap.js +32 -0
  165. package/dist/core/agents/state/scratch.d.ts +27 -0
  166. package/dist/core/agents/state/scratch.d.ts.map +1 -0
  167. package/dist/core/agents/state/scratch.js +90 -0
  168. package/dist/core/agents/state/shell-mutation.d.ts +17 -0
  169. package/dist/core/agents/state/shell-mutation.d.ts.map +1 -0
  170. package/dist/core/agents/state/shell-mutation.js +41 -0
  171. package/dist/core/agents/state/stale-sweep.d.ts +16 -0
  172. package/dist/core/agents/state/stale-sweep.d.ts.map +1 -0
  173. package/dist/core/agents/state/stale-sweep.js +166 -0
  174. package/dist/core/config.d.ts +29 -0
  175. package/dist/core/config.d.ts.map +1 -0
  176. package/dist/core/config.js +108 -0
  177. package/dist/core/hooks/cli.d.ts +21 -0
  178. package/dist/core/hooks/cli.d.ts.map +1 -0
  179. package/dist/core/hooks/cli.js +1123 -0
  180. package/dist/core/hooks/effects/image-capture.d.ts +43 -0
  181. package/dist/core/hooks/effects/image-capture.d.ts.map +1 -0
  182. package/dist/core/hooks/effects/image-capture.js +288 -0
  183. package/dist/core/hooks/effects/index.d.ts +64 -0
  184. package/dist/core/hooks/effects/index.d.ts.map +1 -0
  185. package/dist/core/hooks/effects/index.js +197 -0
  186. package/dist/core/hooks/events/emit.d.ts +31 -0
  187. package/dist/core/hooks/events/emit.d.ts.map +1 -0
  188. package/dist/core/hooks/events/emit.js +89 -0
  189. package/dist/core/hooks/events/schema.d.ts +235 -0
  190. package/dist/core/hooks/events/schema.d.ts.map +1 -0
  191. package/dist/core/hooks/events/schema.js +12 -0
  192. package/dist/core/hooks/events/ulid.d.ts +10 -0
  193. package/dist/core/hooks/events/ulid.d.ts.map +1 -0
  194. package/dist/core/hooks/events/ulid.js +47 -0
  195. package/dist/core/hooks/harness/detect.d.ts +9 -0
  196. package/dist/core/hooks/harness/detect.d.ts.map +1 -0
  197. package/dist/core/hooks/harness/detect.js +29 -0
  198. package/dist/core/hooks/harness/events.d.ts +45 -0
  199. package/dist/core/hooks/harness/events.d.ts.map +1 -0
  200. package/dist/core/hooks/harness/events.js +71 -0
  201. package/dist/core/hooks/harness/output.d.ts +46 -0
  202. package/dist/core/hooks/harness/output.d.ts.map +1 -0
  203. package/dist/core/hooks/harness/output.js +87 -0
  204. package/dist/core/hooks/harness/parse.d.ts +67 -0
  205. package/dist/core/hooks/harness/parse.d.ts.map +1 -0
  206. package/dist/core/hooks/harness/parse.js +132 -0
  207. package/dist/core/hooks/index.d.ts +8 -0
  208. package/dist/core/hooks/index.d.ts.map +1 -0
  209. package/dist/core/hooks/index.js +7 -0
  210. package/dist/core/hooks/resolve/anchor.d.ts +37 -0
  211. package/dist/core/hooks/resolve/anchor.d.ts.map +1 -0
  212. package/dist/core/hooks/resolve/anchor.js +48 -0
  213. package/dist/core/hooks/resolve/coord-root.d.ts +6 -0
  214. package/dist/core/hooks/resolve/coord-root.d.ts.map +1 -0
  215. package/dist/core/hooks/resolve/coord-root.js +27 -0
  216. package/dist/core/hooks/resolve/intent.d.ts +33 -0
  217. package/dist/core/hooks/resolve/intent.d.ts.map +1 -0
  218. package/dist/core/hooks/resolve/intent.js +79 -0
  219. package/dist/core/hooks/resolve/owner.d.ts +42 -0
  220. package/dist/core/hooks/resolve/owner.d.ts.map +1 -0
  221. package/dist/core/hooks/resolve/owner.js +140 -0
  222. package/dist/core/hooks/resolve/transcript.d.ts +26 -0
  223. package/dist/core/hooks/resolve/transcript.d.ts.map +1 -0
  224. package/dist/core/hooks/resolve/transcript.js +73 -0
  225. package/dist/index.d.ts +15 -0
  226. package/dist/index.d.ts.map +1 -0
  227. package/dist/index.js +13 -0
  228. package/dist/lib/agent-browser/client.d.ts +99 -0
  229. package/dist/lib/agent-browser/client.d.ts.map +1 -0
  230. package/dist/lib/agent-browser/client.js +177 -0
  231. package/dist/lib/agent-browser/index.d.ts +2 -0
  232. package/dist/lib/agent-browser/index.d.ts.map +1 -0
  233. package/dist/lib/agent-browser/index.js +1 -0
  234. package/dist/lib/browser/client.d.ts +193 -0
  235. package/dist/lib/browser/client.d.ts.map +1 -0
  236. package/dist/lib/browser/client.js +325 -0
  237. package/dist/lib/browser/dev-overlay.d.ts +23 -0
  238. package/dist/lib/browser/dev-overlay.d.ts.map +1 -0
  239. package/dist/lib/browser/dev-overlay.js +153 -0
  240. package/dist/lib/browser/index.d.ts +5 -0
  241. package/dist/lib/browser/index.d.ts.map +1 -0
  242. package/dist/lib/browser/index.js +2 -0
  243. package/dist/lib/browser/layout.d.ts +79 -0
  244. package/dist/lib/browser/layout.d.ts.map +1 -0
  245. package/dist/lib/browser/layout.js +220 -0
  246. package/dist/lib/browser/visibility.d.ts +86 -0
  247. package/dist/lib/browser/visibility.d.ts.map +1 -0
  248. package/dist/lib/browser/visibility.js +333 -0
  249. package/dist/lib/browser/visual-diff.d.ts +38 -0
  250. package/dist/lib/browser/visual-diff.d.ts.map +1 -0
  251. package/dist/lib/browser/visual-diff.js +107 -0
  252. package/dist/lib/completion/bash.d.ts +25 -0
  253. package/dist/lib/completion/bash.d.ts.map +1 -0
  254. package/dist/lib/completion/bash.js +284 -0
  255. package/dist/lib/completion/fish.d.ts +16 -0
  256. package/dist/lib/completion/fish.d.ts.map +1 -0
  257. package/dist/lib/completion/fish.js +118 -0
  258. package/dist/lib/completion/index.d.ts +5 -0
  259. package/dist/lib/completion/index.d.ts.map +1 -0
  260. package/dist/lib/completion/index.js +4 -0
  261. package/dist/lib/completion/walk.d.ts +68 -0
  262. package/dist/lib/completion/walk.d.ts.map +1 -0
  263. package/dist/lib/completion/walk.js +102 -0
  264. package/dist/lib/completion/zsh.d.ts +13 -0
  265. package/dist/lib/completion/zsh.d.ts.map +1 -0
  266. package/dist/lib/completion/zsh.js +249 -0
  267. package/dist/lib/context/index.d.ts +107 -0
  268. package/dist/lib/context/index.d.ts.map +1 -0
  269. package/dist/lib/context/index.js +275 -0
  270. package/dist/lib/cookies/client.d.ts +131 -0
  271. package/dist/lib/cookies/client.d.ts.map +1 -0
  272. package/dist/lib/cookies/client.js +239 -0
  273. package/dist/lib/cookies/index.d.ts +2 -0
  274. package/dist/lib/cookies/index.d.ts.map +1 -0
  275. package/dist/lib/cookies/index.js +1 -0
  276. package/dist/lib/council/index.d.ts +266 -0
  277. package/dist/lib/council/index.d.ts.map +1 -0
  278. package/dist/lib/council/index.js +674 -0
  279. package/dist/lib/docs-index.d.ts +28 -0
  280. package/dist/lib/docs-index.d.ts.map +1 -0
  281. package/dist/lib/docs-index.js +169 -0
  282. package/dist/lib/docs-lint.d.ts +26 -0
  283. package/dist/lib/docs-lint.d.ts.map +1 -0
  284. package/dist/lib/docs-lint.js +378 -0
  285. package/dist/lib/docs-sweep.d.ts +34 -0
  286. package/dist/lib/docs-sweep.d.ts.map +1 -0
  287. package/dist/lib/docs-sweep.js +304 -0
  288. package/dist/lib/docs.d.ts +27 -0
  289. package/dist/lib/docs.d.ts.map +1 -0
  290. package/dist/lib/docs.js +142 -0
  291. package/dist/lib/env.d.ts +11 -0
  292. package/dist/lib/env.d.ts.map +1 -0
  293. package/dist/lib/env.js +12 -0
  294. package/dist/lib/exec.d.ts +32 -0
  295. package/dist/lib/exec.d.ts.map +1 -0
  296. package/dist/lib/exec.js +54 -0
  297. package/dist/lib/format.d.ts +29 -0
  298. package/dist/lib/format.d.ts.map +1 -0
  299. package/dist/lib/format.js +139 -0
  300. package/dist/lib/http/client.d.ts +56 -0
  301. package/dist/lib/http/client.d.ts.map +1 -0
  302. package/dist/lib/http/client.js +160 -0
  303. package/dist/lib/http/index.d.ts +2 -0
  304. package/dist/lib/http/index.d.ts.map +1 -0
  305. package/dist/lib/http/index.js +1 -0
  306. package/dist/lib/identities/index.d.ts +77 -0
  307. package/dist/lib/identities/index.d.ts.map +1 -0
  308. package/dist/lib/identities/index.js +190 -0
  309. package/dist/lib/machine.d.ts +19 -0
  310. package/dist/lib/machine.d.ts.map +1 -0
  311. package/dist/lib/machine.js +61 -0
  312. package/dist/lib/presence.d.ts +48 -0
  313. package/dist/lib/presence.d.ts.map +1 -0
  314. package/dist/lib/presence.js +123 -0
  315. package/dist/lib/readability/client.d.ts +39 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +121 -0
  318. package/dist/lib/readability/index.d.ts +2 -0
  319. package/dist/lib/readability/index.d.ts.map +1 -0
  320. package/dist/lib/readability/index.js +1 -0
  321. package/dist/lib/scratch/index.d.ts +74 -0
  322. package/dist/lib/scratch/index.d.ts.map +1 -0
  323. package/dist/lib/scratch/index.js +393 -0
  324. package/dist/lib/tunnel/gate.d.ts +12 -0
  325. package/dist/lib/tunnel/gate.d.ts.map +1 -0
  326. package/dist/lib/tunnel/gate.js +101 -0
  327. package/dist/lib/tunnel/state.d.ts +34 -0
  328. package/dist/lib/tunnel/state.d.ts.map +1 -0
  329. package/dist/lib/tunnel/state.js +132 -0
  330. package/package.json +160 -8
  331. package/schemas/.gitkeep +0 -0
  332. package/schemas/config.schema.json +109 -0
  333. package/src/cli.ts +22 -0
  334. package/src/commander.ts +242 -0
  335. package/src/commands/.gitkeep +0 -0
  336. package/src/commands/agents.ts +4567 -0
  337. package/src/commands/backup.ts +305 -0
  338. package/src/commands/browse-ai.ts +198 -0
  339. package/src/commands/browse.ts +849 -0
  340. package/src/commands/callers.ts +363 -0
  341. package/src/commands/completion.ts +193 -0
  342. package/src/commands/config-get.ts +161 -0
  343. package/src/commands/context.ts +209 -0
  344. package/src/commands/cookies.ts +198 -0
  345. package/src/commands/docs.ts +174 -0
  346. package/src/commands/doctor.ts +231 -0
  347. package/src/commands/edit-batch.ts +233 -0
  348. package/src/commands/eml.ts +519 -0
  349. package/src/commands/env.ts +254 -0
  350. package/src/commands/fetch.ts +136 -0
  351. package/src/commands/file-history.ts +202 -0
  352. package/src/commands/grep.ts +371 -0
  353. package/src/commands/init.ts +335 -0
  354. package/src/commands/outline.ts +583 -0
  355. package/src/commands/presence.ts +152 -0
  356. package/src/commands/read.ts +64 -0
  357. package/src/commands/scratch.ts +445 -0
  358. package/src/commands/session.ts +187 -0
  359. package/src/commands/sync.ts +306 -0
  360. package/src/commands/toc.ts +218 -0
  361. package/src/commands/tokens.ts +79 -0
  362. package/src/commands/tunnel.ts +633 -0
  363. package/src/commands/uninstall.ts +144 -0
  364. package/src/commands/web.ts +193 -0
  365. package/src/core/agents/canonical-emit.ts +77 -0
  366. package/src/core/agents/cli-emit.ts +64 -0
  367. package/src/core/agents/cli.ts +838 -0
  368. package/src/core/agents/codex-replay.ts +163 -0
  369. package/src/core/agents/coord-client.ts +249 -0
  370. package/src/core/agents/events/consume.ts +196 -0
  371. package/src/core/agents/events/emit.ts +108 -0
  372. package/src/core/agents/events/ulid.ts +51 -0
  373. package/src/core/agents/index.ts +14 -0
  374. package/src/core/agents/paths.ts +16 -0
  375. package/src/core/agents/render/prompt-context.ts +401 -0
  376. package/src/core/agents/render/session-context.ts +341 -0
  377. package/src/core/agents/rules/claim-conflict.ts +282 -0
  378. package/src/core/agents/rules/commit-conflict.ts +303 -0
  379. package/src/core/agents/rules/stop-hook.ts +229 -0
  380. package/src/core/agents/session-events.ts +228 -0
  381. package/src/core/agents/state/activity-log.ts +33 -0
  382. package/src/core/agents/state/council.ts +265 -0
  383. package/src/core/agents/state/heartbeat-projector.ts +488 -0
  384. package/src/core/agents/state/heartbeat-writer.ts +333 -0
  385. package/src/core/agents/state/names.ts +399 -0
  386. package/src/core/agents/state/pidmap.ts +38 -0
  387. package/src/core/agents/state/scratch.ts +121 -0
  388. package/src/core/agents/state/shell-mutation.ts +44 -0
  389. package/src/core/agents/state/stale-sweep.ts +190 -0
  390. package/src/core/config.ts +111 -0
  391. package/src/core/hooks/cli.ts +1247 -0
  392. package/src/core/hooks/effects/image-capture.ts +330 -0
  393. package/src/core/hooks/effects/index.ts +210 -0
  394. package/src/core/hooks/events/emit.ts +120 -0
  395. package/src/core/hooks/events/schema.ts +430 -0
  396. package/src/core/hooks/events/ulid.ts +51 -0
  397. package/src/core/hooks/harness/detect.ts +30 -0
  398. package/src/core/hooks/harness/events.ts +102 -0
  399. package/src/core/hooks/harness/output.ts +100 -0
  400. package/src/core/hooks/harness/parse.ts +180 -0
  401. package/src/core/hooks/index.ts +16 -0
  402. package/src/core/hooks/resolve/anchor.ts +51 -0
  403. package/src/core/hooks/resolve/coord-root.ts +25 -0
  404. package/src/core/hooks/resolve/intent.ts +89 -0
  405. package/src/core/hooks/resolve/owner.ts +140 -0
  406. package/src/core/hooks/resolve/transcript.ts +72 -0
  407. package/src/hooks/.gitkeep +0 -0
  408. package/src/index.ts +15 -0
  409. package/src/lib/agent-browser/client.ts +239 -0
  410. package/src/lib/agent-browser/index.ts +1 -0
  411. package/src/lib/browser/client.ts +449 -0
  412. package/src/lib/browser/dev-overlay.ts +207 -0
  413. package/src/lib/browser/index.ts +24 -0
  414. package/src/lib/browser/layout.ts +288 -0
  415. package/src/lib/browser/visibility.ts +419 -0
  416. package/src/lib/browser/visual-diff.ts +150 -0
  417. package/src/lib/completion/bash.ts +291 -0
  418. package/src/lib/completion/fish.ts +134 -0
  419. package/src/lib/completion/index.ts +10 -0
  420. package/src/lib/completion/walk.ts +184 -0
  421. package/src/lib/completion/zsh.ts +262 -0
  422. package/src/lib/context/index.ts +386 -0
  423. package/src/lib/cookies/client.ts +301 -0
  424. package/src/lib/cookies/index.ts +13 -0
  425. package/src/lib/council/index.ts +803 -0
  426. package/src/lib/docs-index.ts +216 -0
  427. package/src/lib/docs-lint.ts +413 -0
  428. package/src/lib/docs-sweep.ts +348 -0
  429. package/src/lib/docs.ts +199 -0
  430. package/src/lib/env.ts +12 -0
  431. package/src/lib/exec.ts +74 -0
  432. package/src/lib/format.ts +147 -0
  433. package/src/lib/http/client.ts +211 -0
  434. package/src/lib/http/index.ts +1 -0
  435. package/src/lib/identities/index.ts +210 -0
  436. package/src/lib/machine.ts +61 -0
  437. package/src/lib/presence.ts +154 -0
  438. package/src/lib/readability/client.ts +169 -0
  439. package/src/lib/readability/index.ts +5 -0
  440. package/src/lib/readability/turndown-plugin-gfm.d.ts +10 -0
  441. package/src/lib/scratch/index.ts +470 -0
  442. package/src/lib/tunnel/gate.ts +113 -0
  443. package/src/lib/tunnel/state.ts +167 -0
  444. package/src/web/.gitkeep +0 -0
  445. package/index.js +0 -1
@@ -0,0 +1,335 @@
1
+ /**
2
+ * UserPromptSubmit UX renderer. Combines the peer-refresh dedup, the
3
+ * council-pending hash-dedup, and the Cursor set-task staleness nudge.
4
+ * agent-hook's user_prompt.submit post-emit handler calls this
5
+ * and forwards the result as the harness-shaped additionalContext payload.
6
+ *
7
+ * Three hash-dedup'd subsections combined into one additionalContext payload:
8
+ * 1. Peer table: semantically-relevant peer fields hashed; only re-emits
9
+ * when peers change (name + instance_id + session_id + kind + started_at
10
+ * + sorted(files_touched) + platform, sorted by instance_id).
11
+ * 2. Council pending: pending open-council IDs hashed; re-emits when the
12
+ * ID set changes.
13
+ * 3. Task staleness nudge (cursor only, since CC enforces via the Stop hook
14
+ * transcript scan). Fires when `task` is null or `task_updated_at` is
15
+ * older than HARNERY_TASK_STALE_SECONDS (default 1800 = 30 min). Hash-deduped
16
+ * against the previous nudge state.
17
+ *
18
+ * Hash files live at:
19
+ * .harnery/.last-peer-hash.<instance_id>
20
+ * .harnery/.last-council-hash.<instance_id>
21
+ * .harnery/.last-task-nudge-hash.<instance_id>
22
+ *
23
+ * First call always emits (hash files don't exist).
24
+ */
25
+ import { createHash } from "node:crypto";
26
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { coordEnv } from "../../../lib/env.js";
29
+ import { formatPendingCouncils } from "./session-context.js";
30
+ /**
31
+ * Build the combined UserPromptSubmit additionalContext string. Returns "" when
32
+ * nothing has changed since the last call (so the caller can skip the JSON emit).
33
+ *
34
+ * Side effects: updates `.harnery/.last-peer-hash.<id>` and `.harnery/.last-council-hash.<id>`
35
+ * when their respective sections re-emit (or removes the council hash when no
36
+ * councils are pending, matching the bash behavior).
37
+ */
38
+ export function renderPromptContext(opts) {
39
+ const { coordRoot, instanceId, sessionId, agentName, taskNudge } = opts;
40
+ const sections = [];
41
+ // 1. Peer table with hash dedup.
42
+ const peerTable = computePeerTableIfChanged(coordRoot, instanceId, sessionId);
43
+ if (peerTable)
44
+ sections.push(peerTable);
45
+ // 2. Council pending with hash dedup.
46
+ if (agentName) {
47
+ const councilMsg = computeCouncilPendingIfChanged(coordRoot, instanceId, agentName);
48
+ if (councilMsg)
49
+ sections.push(councilMsg);
50
+ }
51
+ // 3. Task staleness nudge (cursor only).
52
+ if (taskNudge) {
53
+ const nudgeMsg = computeTaskNudgeIfChanged(coordRoot, instanceId);
54
+ if (nudgeMsg)
55
+ sections.push(nudgeMsg);
56
+ }
57
+ return sections.join("\n\n");
58
+ }
59
+ /** The set-task staleness nudge for Cursor.
60
+ * Emits a one-line reminder when `task` is null or `task_updated_at` is older
61
+ * than HARNERY_TASK_STALE_SECONDS (default 1800). Hash-deduped against previous
62
+ * nudge state. */
63
+ function computeTaskNudgeIfChanged(coordRoot, selfInstanceId) {
64
+ const hbPath = join(coordRoot, ".harnery", "active", `${selfInstanceId}.json`);
65
+ if (!existsSync(hbPath))
66
+ return "";
67
+ let hb;
68
+ try {
69
+ hb = JSON.parse(readFileSync(hbPath, "utf8"));
70
+ }
71
+ catch {
72
+ return "";
73
+ }
74
+ const threshold = Number.parseInt(coordEnv("TASK_STALE_SECONDS") ?? "1800", 10);
75
+ const taskValue = hb.task ?? "";
76
+ let needsNudge = false;
77
+ let message = "";
78
+ if (!taskValue) {
79
+ needsNudge = true;
80
+ message =
81
+ "Heads up: your `task` field is unset. Run `agents set-task \"<short focus>\"` so peers + the coord dashboard can see what you're working on. (Cursor sessions can't enforce this from the Stop hook the way Claude Code does, so this is a one-time soft reminder per staleness state.)";
82
+ }
83
+ else if (hb.task_updated_at) {
84
+ const updatedSec = Math.floor(Date.parse(hb.task_updated_at) / 1000);
85
+ const nowSec = Math.floor(Date.now() / 1000);
86
+ if (Number.isFinite(updatedSec) && updatedSec > 0) {
87
+ const ageSec = nowSec - updatedSec;
88
+ if (ageSec > threshold) {
89
+ needsNudge = true;
90
+ message = `Heads up: your \`task\` field hasn't changed in ${ageSec}s (threshold ${threshold}s). If you've moved on from "${taskValue.slice(0, 60)}", update via \`agents set-task "<new focus>"\`. Pass an empty string to clear.`;
91
+ }
92
+ }
93
+ }
94
+ const hashFile = join(coordRoot, ".harnery", `.last-task-nudge-hash.${selfInstanceId}`);
95
+ if (!needsNudge) {
96
+ try {
97
+ if (existsSync(hashFile))
98
+ rmSync(hashFile, { force: true });
99
+ }
100
+ catch {
101
+ /* swallow */
102
+ }
103
+ return "";
104
+ }
105
+ // Dedup on a state-hash (task value + threshold) so same-state turns don't re-nudge.
106
+ const state = `${taskValue}|stale=1|threshold=${threshold}`;
107
+ const newHash = sha256Hex16(state);
108
+ const oldHash = safeRead(hashFile);
109
+ if (oldHash && oldHash === newHash)
110
+ return "";
111
+ writeHashFile(hashFile, newHash);
112
+ return message;
113
+ }
114
+ function computePeerTableIfChanged(coordRoot, selfInstanceId, selfSessionIdFallback) {
115
+ const activeDir = join(coordRoot, ".harnery", "active");
116
+ if (!existsSync(activeDir))
117
+ return "";
118
+ // Read self heartbeat for session_id (group key); fall back to the caller's hint.
119
+ let mySessionId = selfSessionIdFallback;
120
+ const selfHb = readHeartbeat(join(activeDir, `${selfInstanceId}.json`));
121
+ if (selfHb?.session_id)
122
+ mySessionId = selfHb.session_id;
123
+ if (!mySessionId)
124
+ return "";
125
+ // Collect peer heartbeats.
126
+ const peers = [];
127
+ for (const f of readdirSync(activeDir)) {
128
+ if (!f.endsWith(".json"))
129
+ continue;
130
+ const hb = readHeartbeat(join(activeDir, f));
131
+ if (!hb?.instance_id || hb.instance_id === selfInstanceId)
132
+ continue;
133
+ peers.push(hb);
134
+ }
135
+ if (peers.length === 0)
136
+ return "";
137
+ // Build hash basis: sorted-by-instance_id projection of semantically-relevant fields.
138
+ const basis = peers
139
+ .map((p) => ({
140
+ name: p.name ?? null,
141
+ instance_id: p.instance_id ?? null,
142
+ session_id: p.session_id ?? null,
143
+ kind: p.kind ?? null,
144
+ started_at: p.started_at ?? null,
145
+ files_touched: Array.from(p.files_touched ?? []).sort(),
146
+ platform: p.platform ?? null,
147
+ }))
148
+ .sort((a, b) => (a.instance_id ?? "").localeCompare(b.instance_id ?? ""));
149
+ const newHash = sha256Hex16(JSON.stringify(basis));
150
+ const hashFile = join(coordRoot, ".harnery", `.last-peer-hash.${selfInstanceId}`);
151
+ const oldHash = safeRead(hashFile);
152
+ if (oldHash && oldHash === newHash)
153
+ return "";
154
+ // Render peer table via the same formatter used at SessionStart.
155
+ const table = formatPeerTable(peers, mySessionId);
156
+ if (!table)
157
+ return "";
158
+ // Persist hash atomically (temp + rename, same convention as other coord writes).
159
+ writeHashFile(hashFile, newHash);
160
+ return table;
161
+ }
162
+ function computeCouncilPendingIfChanged(coordRoot, selfInstanceId, agentName) {
163
+ const councilsDir = join(coordRoot, ".harnery", "councils");
164
+ if (!existsSync(councilsDir))
165
+ return "";
166
+ const canonicalName = agentName.startsWith("agent-") ? agentName : `agent-${agentName}`;
167
+ // Collect pending council IDs (open councils where I'm a member and haven't contributed).
168
+ const pendingIds = [];
169
+ try {
170
+ for (const f of readdirSync(councilsDir)) {
171
+ if (!f.endsWith(".json"))
172
+ continue;
173
+ try {
174
+ const m = JSON.parse(readFileSync(join(councilsDir, f), "utf8"));
175
+ if (m.status !== "active" || m.round_status !== "open")
176
+ continue;
177
+ if (!m.council_id || !m.members?.includes(canonicalName))
178
+ continue;
179
+ const round = m.current_round ?? 1;
180
+ const contributionPath = join(councilsDir, m.council_id, `round-${round}`, `${canonicalName}.md`);
181
+ if (existsSync(contributionPath))
182
+ continue;
183
+ pendingIds.push(m.council_id);
184
+ }
185
+ catch {
186
+ /* skip */
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ return "";
192
+ }
193
+ pendingIds.sort();
194
+ const hashFile = join(coordRoot, ".harnery", `.last-council-hash.${selfInstanceId}`);
195
+ const newHash = sha256Hex16(pendingIds.join("\n"));
196
+ const oldHash = safeRead(hashFile);
197
+ // Clear hash file when no councils pending, matching bash behavior.
198
+ if (pendingIds.length === 0) {
199
+ try {
200
+ if (existsSync(hashFile))
201
+ rmSync(hashFile, { force: true });
202
+ }
203
+ catch {
204
+ /* swallow */
205
+ }
206
+ return "";
207
+ }
208
+ // Always update hash file when pending councils exist (eager-rewrite, matches bash).
209
+ writeHashFile(hashFile, newHash);
210
+ if (oldHash && oldHash === newHash)
211
+ return "";
212
+ return formatPendingCouncils(coordRoot, agentName);
213
+ }
214
+ /* ---------- helpers ---------- */
215
+ function readHeartbeat(path) {
216
+ try {
217
+ return JSON.parse(readFileSync(path, "utf8"));
218
+ }
219
+ catch {
220
+ return null;
221
+ }
222
+ }
223
+ function safeRead(path) {
224
+ try {
225
+ return readFileSync(path, "utf8").trim();
226
+ }
227
+ catch {
228
+ return "";
229
+ }
230
+ }
231
+ function sha256Hex16(input) {
232
+ return createHash("sha256").update(input).digest("hex").slice(0, 16);
233
+ }
234
+ function writeHashFile(path, value) {
235
+ try {
236
+ mkdirSync(join(path, ".."), { recursive: true });
237
+ const tmp = `${path}.${process.pid}.tmp`;
238
+ writeFileSync(tmp, value, "utf8");
239
+ renameSync(tmp, path);
240
+ }
241
+ catch {
242
+ /* swallow */
243
+ }
244
+ }
245
+ /* The peer-table formatter is a near-duplicate of session-context.ts's, but
246
+ * intentionally co-located here so the prompt-context renderer is self-contained
247
+ * (no cross-file imports of formatting internals). If both renderers diverge,
248
+ * pull the shared bits into a small util module. */
249
+ function formatPeerTable(peers, mySessionId) {
250
+ if (peers.length === 0)
251
+ return "";
252
+ const nowSec = Math.floor(Date.now() / 1000);
253
+ const fold = {};
254
+ for (const p of peers) {
255
+ const kind = p.kind ?? "unknown";
256
+ if (kind === "transient" && p.session_id) {
257
+ fold[p.session_id] = (fold[p.session_id] ?? []).concat(p.files_touched ?? []);
258
+ }
259
+ }
260
+ const rows = peers
261
+ .filter((p) => (p.kind ?? "unknown") !== "transient")
262
+ .map((p) => {
263
+ const folded = fold[p.instance_id ?? ""] ?? [];
264
+ const display = Array.from(new Set([...(p.files_touched ?? []), ...folded])).sort();
265
+ return { ...p, display_files: display };
266
+ });
267
+ const blocking = rows.filter((p) => p.session_id !== mySessionId).sort(byStartedAt);
268
+ const group = rows.filter((p) => p.session_id === mySessionId).sort(byStartedAt);
269
+ const out = [];
270
+ const blk = renderSubtable(blocking, "Other agent groups active (their files block you):", nowSec);
271
+ if (blk)
272
+ out.push(blk);
273
+ const grp = renderSubtable(group, "Your group (subagents / parent / siblings; no mutual block):", nowSec);
274
+ if (grp)
275
+ out.push(grp);
276
+ return out.join("\n\n");
277
+ }
278
+ function byStartedAt(a, b) {
279
+ return (a.started_at ?? "").localeCompare(b.started_at ?? "");
280
+ }
281
+ function renderSubtable(rows, header, nowSec) {
282
+ if (rows.length === 0)
283
+ return "";
284
+ const first = rows.slice(0, 10).map((r) => formatRow(r, nowSec));
285
+ const overflow = rows.length > 10 ? `\n +${rows.length - 10} more` : "";
286
+ return `${header}\n${first.join("\n")}${overflow}`;
287
+ }
288
+ function formatRow(r, nowSec) {
289
+ const taskPart = r.task ? ` "${r.task.slice(0, 60)}"` : "";
290
+ // Fall back started_at → last_heartbeat; if neither is a valid timestamp,
291
+ // show "age unknown" rather than the epoch-derived "20608d ago" ghost.
292
+ const startedSec = parseIsoSec(r.started_at) ?? parseIsoSec(r.last_heartbeat);
293
+ const ageFrom = startedSec === null ? "age unknown" : fmtAge(nowSec - startedSec);
294
+ const filesPart = fmtFiles(r.display_files);
295
+ const lastActivity = fmtLastActivity(r, nowSec);
296
+ const turnSummary = r.turn_summary ? `\n last turn: ${r.turn_summary.slice(0, 80)}` : "";
297
+ // Prefer a short instance_id over a bare "unknown" so an incomplete row is
298
+ // still identifiable.
299
+ const label = r.name ?? (r.instance_id ? r.instance_id.slice(0, 8) : "unknown");
300
+ return ` - agent-${label}${taskPart} (${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
301
+ }
302
+ function fmtFiles(files) {
303
+ if (files.length === 0)
304
+ return "nothing yet";
305
+ if (files.length <= 3)
306
+ return `holds: ${files.join(", ")}`;
307
+ return `holds: ${files.slice(0, 3).join(", ")}, +${files.length - 3} more`;
308
+ }
309
+ function fmtLastActivity(r, nowSec) {
310
+ if (!r.last_tool)
311
+ return "";
312
+ const lastTs = parseIsoSec(r.last_heartbeat) ?? parseIsoSec(r.started_at);
313
+ const tail = r.last_tool_target ? ` ${r.last_tool_target.slice(0, 60)}` : "";
314
+ // No valid timestamp → render the tool without an absurd age.
315
+ if (lastTs === null)
316
+ return `, last: ${r.last_tool}${tail}`;
317
+ return `, last: ${r.last_tool}${tail} ${fmtAge(nowSec - lastTs)}`;
318
+ }
319
+ /** Epoch-seconds for an ISO string, or null when missing/unparseable (so callers
320
+ * can render "age unknown" instead of an epoch-derived absurd age). */
321
+ function parseIsoSec(iso) {
322
+ if (!iso)
323
+ return null;
324
+ const ms = Date.parse(iso);
325
+ return Number.isFinite(ms) ? Math.floor(ms / 1000) : null;
326
+ }
327
+ function fmtAge(secs) {
328
+ if (secs < 60)
329
+ return `${Math.floor(secs)}s ago`;
330
+ if (secs < 3600)
331
+ return `${Math.floor(secs / 60)}m ago`;
332
+ if (secs < 86400)
333
+ return `${Math.floor(secs / 3600)}h ago`;
334
+ return `${Math.floor(secs / 86400)}d ago`;
335
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * SessionStart UX renderer: combines the peer table, the wiring check, and
3
+ * the pending-council formatter so agent-hook session.start can emit the
4
+ * combined `systemMessage` JSON directly.
5
+ *
6
+ * Outputs a Claude Code SessionStart hookSpecificOutput.additionalContext
7
+ * string.
8
+ */
9
+ import { statSync } from "node:fs";
10
+ export interface RenderOpts {
11
+ coordRoot: string;
12
+ instanceId: string;
13
+ sessionId: string;
14
+ agentName?: string;
15
+ /** Harness label rendered in parens after the self-name (e.g. "Cursor", "Codex").
16
+ * Claude Code omits it. */
17
+ platformLabel?: string;
18
+ }
19
+ /**
20
+ * Build the combined SessionStart systemMessage. Returns the additionalContext
21
+ * string (or "" if there's nothing to say).
22
+ */
23
+ export declare function renderSessionContext(opts: RenderOpts): string;
24
+ /**
25
+ * Returns a list of wiring issues (empty when everything's wired). Checks
26
+ * parent core.hooksPath + one representative submodule.
27
+ */
28
+ export declare function checkWiring(coordRoot: string): string[];
29
+ /**
30
+ * Returns the formatted council invite reminder
31
+ * (or "" when no councils await input).
32
+ */
33
+ export declare function formatPendingCouncils(coordRoot: string, agentName: string): string;
34
+ /**
35
+ * Suppress unused import warning when statSync isn't used in this file
36
+ * (kept exported for future renderers, e.g. heartbeat-freshness coloring).
37
+ */
38
+ export declare const _ensureStatSyncImported: typeof statSync;
39
+ //# sourceMappingURL=session-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-context.d.ts","sourceRoot":"","sources":["../../../../src/core/agents/render/session-context.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAyC,QAAQ,EAAE,MAAM,SAAS,CAAC;AAmB1E,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;+BAC2B;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAoC7D;AAwHD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAiCvD;AA6CD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAiDlF;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,iBAAW,CAAC"}
@@ -0,0 +1,283 @@
1
+ /**
2
+ * SessionStart UX renderer: combines the peer table, the wiring check, and
3
+ * the pending-council formatter so agent-hook session.start can emit the
4
+ * combined `systemMessage` JSON directly.
5
+ *
6
+ * Outputs a Claude Code SessionStart hookSpecificOutput.additionalContext
7
+ * string.
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { resolveBinName } from "../../config.js";
13
+ /**
14
+ * Build the combined SessionStart systemMessage. Returns the additionalContext
15
+ * string (or "" if there's nothing to say).
16
+ */
17
+ export function renderSessionContext(opts) {
18
+ const { coordRoot, instanceId, sessionId, agentName, platformLabel } = opts;
19
+ const messages = [];
20
+ // 1. Self-name line + peer table (folded if peers present)
21
+ const peers = readActivePeers(coordRoot, instanceId);
22
+ const peerTable = formatPeerTable(peers, sessionId);
23
+ if (agentName) {
24
+ const suffix = platformLabel ? ` (${platformLabel})` : "";
25
+ const selfLine = `You are agent-${agentName}${suffix}.`;
26
+ messages.push(peerTable ? `${selfLine}\n\n${peerTable}` : selfLine);
27
+ }
28
+ else if (peerTable) {
29
+ messages.push(peerTable);
30
+ }
31
+ // 2. Linked-worktree detection
32
+ if (isLinkedWorktree(coordRoot)) {
33
+ messages.push(`Running inside worktree ${process.cwd()}. The coord layer is scoped to this worktree only; use \`${resolveBinName(coordRoot)} worktree diff\` to check for conflicts against sibling worktrees.`);
34
+ }
35
+ // 3. Council invites
36
+ if (agentName) {
37
+ const councilMsg = formatPendingCouncils(coordRoot, agentName);
38
+ if (councilMsg)
39
+ messages.push(councilMsg);
40
+ }
41
+ // 4. Wiring check
42
+ const wiringIssues = checkWiring(coordRoot);
43
+ if (wiringIssues.length > 0) {
44
+ const wiringSummary = `Coordination hooks are NOT wired: the E-guard will not block conflicting commits, and post-commit claim pruning will not run. Run \`scripts/setup-hooks.sh\` to fix. Detected:\n${wiringIssues.map((i) => ` - ${i}`).join("\n")}`;
45
+ messages.push(wiringSummary);
46
+ }
47
+ return messages.join("\n\n");
48
+ }
49
+ /** Read all peer heartbeats from .harnery/active/, excluding self. */
50
+ function readActivePeers(coordRoot, selfInstanceId) {
51
+ const out = [];
52
+ const dir = join(coordRoot, ".harnery", "active");
53
+ if (!existsSync(dir))
54
+ return out;
55
+ for (const f of readdirSync(dir)) {
56
+ if (!f.endsWith(".json"))
57
+ continue;
58
+ try {
59
+ const hb = JSON.parse(readFileSync(join(dir, f), "utf8"));
60
+ if (!hb.instance_id || hb.instance_id === selfInstanceId)
61
+ continue;
62
+ out.push(hb);
63
+ }
64
+ catch {
65
+ /* skip */
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+ /**
71
+ * Renders the peer table as
72
+ * two subsections: "Other agent groups active" (blocking) and "Your group"
73
+ * (subagents/siblings, no mutual block). Folds transient subagents' files
74
+ * into their parent session.
75
+ */
76
+ function formatPeerTable(peers, mySessionId) {
77
+ if (peers.length === 0)
78
+ return "";
79
+ const nowSec = Math.floor(Date.now() / 1000);
80
+ // Fold transient peers' files into their session_id parent.
81
+ const fold = {};
82
+ for (const p of peers) {
83
+ const kind = p.kind ?? "unknown";
84
+ if (kind === "transient" && p.session_id) {
85
+ fold[p.session_id] = (fold[p.session_id] ?? []).concat(p.files_touched ?? []);
86
+ }
87
+ }
88
+ const rows = peers
89
+ .filter((p) => (p.kind ?? "unknown") !== "transient")
90
+ .map((p) => {
91
+ const folded = fold[p.instance_id ?? ""] ?? [];
92
+ const display = Array.from(new Set([...(p.files_touched ?? []), ...folded])).sort();
93
+ return { ...p, display_files: display };
94
+ });
95
+ const blocking = rows.filter((p) => p.session_id !== mySessionId).sort(byStartedAt);
96
+ const group = rows.filter((p) => p.session_id === mySessionId).sort(byStartedAt);
97
+ const sections = [];
98
+ const blockingSection = renderSubtable(blocking, "Other agent groups active (their files block you):", nowSec);
99
+ if (blockingSection)
100
+ sections.push(blockingSection);
101
+ const groupSection = renderSubtable(group, "Your group (subagents / parent / siblings; no mutual block):", nowSec);
102
+ if (groupSection)
103
+ sections.push(groupSection);
104
+ return sections.join("\n\n");
105
+ }
106
+ function byStartedAt(a, b) {
107
+ return (a.started_at ?? "").localeCompare(b.started_at ?? "");
108
+ }
109
+ function renderSubtable(rows, header, nowSec) {
110
+ if (rows.length === 0)
111
+ return "";
112
+ const first = rows.slice(0, 10).map((r) => formatRow(r, nowSec));
113
+ const overflow = rows.length > 10 ? `\n +${rows.length - 10} more` : "";
114
+ return `${header}\n${first.join("\n")}${overflow}`;
115
+ }
116
+ function formatRow(r, nowSec) {
117
+ const taskPart = r.task ? ` "${r.task.slice(0, 60)}"` : "";
118
+ const ageFrom = fmtAge(nowSec - parseIsoSec(r.started_at));
119
+ const filesPart = fmtFiles(r.display_files);
120
+ const lastActivity = fmtLastActivity(r, nowSec);
121
+ const turnSummary = r.turn_summary ? `\n last turn: ${r.turn_summary.slice(0, 80)}` : "";
122
+ return ` - agent-${r.name ?? "unknown"}${taskPart} (${ageFrom}, ${filesPart}${lastActivity})${turnSummary}`;
123
+ }
124
+ function fmtFiles(files) {
125
+ if (files.length === 0)
126
+ return "nothing yet";
127
+ if (files.length <= 3)
128
+ return `holds: ${files.join(", ")}`;
129
+ return `holds: ${files.slice(0, 3).join(", ")}, +${files.length - 3} more`;
130
+ }
131
+ function fmtLastActivity(r, nowSec) {
132
+ if (!r.last_tool)
133
+ return "";
134
+ const lastTs = parseIsoSec(r.last_heartbeat ?? r.started_at);
135
+ const ageSec = nowSec - lastTs;
136
+ const tail = r.last_tool_target ? ` ${r.last_tool_target.slice(0, 60)}` : "";
137
+ return `, last: ${r.last_tool}${tail} ${fmtAge(ageSec)}`;
138
+ }
139
+ function parseIsoSec(iso) {
140
+ if (!iso)
141
+ return 0;
142
+ const ms = Date.parse(iso);
143
+ return Number.isFinite(ms) ? Math.floor(ms / 1000) : 0;
144
+ }
145
+ function fmtAge(secs) {
146
+ if (secs < 60)
147
+ return `${Math.floor(secs)}s ago`;
148
+ if (secs < 3600)
149
+ return `${Math.floor(secs / 60)}m ago`;
150
+ if (secs < 86400)
151
+ return `${Math.floor(secs / 3600)}h ago`;
152
+ return `${Math.floor(secs / 86400)}d ago`;
153
+ }
154
+ /**
155
+ * Returns a list of wiring issues (empty when everything's wired). Checks
156
+ * parent core.hooksPath + one representative submodule.
157
+ */
158
+ export function checkWiring(coordRoot) {
159
+ const expected = join(coordRoot, "scripts", "hooks");
160
+ const issues = [];
161
+ // Parent repo
162
+ const parentHp = gitConfig(coordRoot, "core.hooksPath");
163
+ const parentResolved = resolveHooksPath(coordRoot, parentHp);
164
+ if (parentResolved !== expected) {
165
+ issues.push(`parent core.hooksPath=${parentHp || "<unset>"} (resolves to ${parentResolved}, expected ${expected})`);
166
+ }
167
+ // One representative submodule
168
+ const gitmodules = join(coordRoot, ".gitmodules");
169
+ if (existsSync(gitmodules)) {
170
+ const sampleSub = extractFirstSubmodule(gitmodules);
171
+ if (sampleSub) {
172
+ const subPath = join(coordRoot, sampleSub);
173
+ const subGitDir = join(subPath, ".git");
174
+ if (existsSync(subGitDir)) {
175
+ const subHp = gitConfig(subPath, "core.hooksPath");
176
+ const subResolved = resolveSubmoduleHooksPath(coordRoot, sampleSub, subHp);
177
+ if (subResolved !== expected) {
178
+ issues.push(`submodule ${sampleSub} core.hooksPath=${subHp || "<unset>"} (resolves to ${subResolved}; other submodules likely affected too)`);
179
+ }
180
+ }
181
+ }
182
+ }
183
+ return issues;
184
+ }
185
+ function gitConfig(cwd, key) {
186
+ const result = spawnSync("git", ["-C", cwd, "config", "--get", key], { encoding: "utf8" });
187
+ if (result.status !== 0)
188
+ return "";
189
+ return result.stdout.trim();
190
+ }
191
+ function resolveHooksPath(root, hp) {
192
+ if (!hp)
193
+ return join(root, ".git", "hooks");
194
+ if (hp.startsWith("/"))
195
+ return hp.replace(/\/$/, "");
196
+ return join(root, hp).replace(/\/$/, "");
197
+ }
198
+ function resolveSubmoduleHooksPath(root, sub, hp) {
199
+ if (!hp)
200
+ return join(root, sub, ".git", "hooks");
201
+ if (hp.startsWith("/"))
202
+ return hp.replace(/\/$/, "");
203
+ return join(root, sub, hp).replace(/\/$/, "");
204
+ }
205
+ function extractFirstSubmodule(gitmodulesPath) {
206
+ try {
207
+ const content = readFileSync(gitmodulesPath, "utf8");
208
+ const match = content.match(/^\s*path\s*=\s*(.+)$/m);
209
+ return match ? match[1].trim() : null;
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ /**
216
+ * Detect linked-worktree environment (`git worktree` created from the
217
+ * superproject) via a `git rev-parse --git-dir` vs `--git-common-dir` check.
218
+ */
219
+ function isLinkedWorktree(coordRoot) {
220
+ const dir = spawnSync("git", ["-C", coordRoot, "rev-parse", "--git-dir"], { encoding: "utf8" });
221
+ const common = spawnSync("git", ["-C", coordRoot, "rev-parse", "--git-common-dir"], {
222
+ encoding: "utf8",
223
+ });
224
+ if (dir.status !== 0 || common.status !== 0)
225
+ return false;
226
+ const d = dir.stdout.trim();
227
+ const c = common.stdout.trim();
228
+ return d !== "" && c !== "" && d !== c;
229
+ }
230
+ /**
231
+ * Returns the formatted council invite reminder
232
+ * (or "" when no councils await input).
233
+ */
234
+ export function formatPendingCouncils(coordRoot, agentName) {
235
+ const councilsDir = join(coordRoot, ".harnery", "councils");
236
+ if (!existsSync(councilsDir))
237
+ return "";
238
+ const canonicalName = agentName.startsWith("agent-") ? agentName : `agent-${agentName}`;
239
+ const pending = [];
240
+ try {
241
+ for (const f of readdirSync(councilsDir)) {
242
+ if (!f.endsWith(".json"))
243
+ continue;
244
+ const manifestPath = join(councilsDir, f);
245
+ try {
246
+ const m = JSON.parse(readFileSync(manifestPath, "utf8"));
247
+ if (m.status !== "active" || m.round_status !== "open")
248
+ continue;
249
+ if (!m.members?.includes(canonicalName))
250
+ continue;
251
+ const round = m.current_round ?? 1;
252
+ const contributionPath = join(councilsDir, m.council_id ?? "", `round-${round}`, `${canonicalName}.md`);
253
+ if (existsSync(contributionPath))
254
+ continue; // already contributed
255
+ if (m.council_id)
256
+ pending.push(m.council_id);
257
+ }
258
+ catch {
259
+ /* skip */
260
+ }
261
+ }
262
+ }
263
+ catch {
264
+ /* skip */
265
+ }
266
+ if (pending.length === 0)
267
+ return "";
268
+ const bin = resolveBinName(coordRoot);
269
+ const firstThree = pending.slice(0, 3);
270
+ const tail = pending.length > 3 ? `, +${pending.length - 3} more` : "";
271
+ const list = firstThree.join(", ") + tail;
272
+ if (pending.length === 1) {
273
+ return (`Council waiting on your input: \`${pending[0]}\`. ` +
274
+ `Run \`${bin} agents council show ${pending[0]}\` for the brief and ` +
275
+ `\`${bin} agents council contribute ${pending[0]} --message "<your take>"\` to weigh in.`);
276
+ }
277
+ return `Councils waiting on your input (${pending.length} open): ${list}. Run \`${bin} agents council list --mine\` to see all of them, then \`${bin} agents council show <id>\` for any brief.`;
278
+ }
279
+ /**
280
+ * Suppress unused import warning when statSync isn't used in this file
281
+ * (kept exported for future renderers, e.g. heartbeat-freshness coloring).
282
+ */
283
+ export const _ensureStatSyncImported = statSync;