harnery 0.0.1 → 0.2.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 (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 +494 -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 +32 -0
  316. package/dist/lib/readability/client.d.ts.map +1 -0
  317. package/dist/lib/readability/client.js +119 -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 +564 -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 +156 -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,100 @@
1
+ /**
2
+ * Harness-aware hook output JSON. Each adapter target has its own protocol
3
+ * shape, so encode it once here so post-emit handlers don't have to branch on
4
+ * `harness` everywhere.
5
+ *
6
+ * Shapes (verified against live dispatchers + each harness's upstream hooks docs):
7
+ *
8
+ * - **Claude Code**: `{hookSpecificOutput: {hookEventName, additionalContext}}`
9
+ * for SessionStart / UserPromptSubmit / SubagentStart; deny uses
10
+ * `{hookSpecificOutput: {hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason}}`.
11
+ * - **Cursor**: `{additional_context, env?}` flat for sessionStart /
12
+ * beforeSubmitPrompt; deny uses `{permission: "deny", agent_message, user_message}`.
13
+ * - **Codex**: structurally identical to Claude Code (confirmed by Phase 0
14
+ * probe + the matching shape in the Codex harness adapter).
15
+ *
16
+ * Every helper writes to process.stdout + newline-terminates so callers can
17
+ * fire-and-forget. Empty text → no-op (no JSON written).
18
+ */
19
+
20
+ import type { Harness } from "../events/schema.ts";
21
+
22
+ export type SystemEvent = "SessionStart" | "UserPromptSubmit" | "SubagentStart";
23
+
24
+ /** Emit a context-injection (peer table, wiring check, council pending, …). */
25
+ export function emitContext(harness: Harness, event: SystemEvent, text: string): void {
26
+ if (!text || text.length === 0) return;
27
+ const json = buildContextJson(harness, event, text);
28
+ process.stdout.write(`${JSON.stringify(json)}\n`);
29
+ }
30
+
31
+ /** Emit a PreToolUse deny: blocks the tool call with `reason` shown to the model. */
32
+ export function emitDeny(harness: Harness, reason: string): void {
33
+ if (!reason) return;
34
+ const json = buildDenyJson(harness, reason);
35
+ process.stdout.write(`${JSON.stringify(json)}\n`);
36
+ }
37
+
38
+ /**
39
+ * Emit a Stop-hook block in the firing harness's enforcement channel and return
40
+ * the process exit code the caller should use.
41
+ *
42
+ * The verdict (allow/block + reason) is computed harness-agnostically in
43
+ * agents/rules/stop-hook.ts; this function only shapes *how the block is
44
+ * communicated back*, because each harness has a different mechanism:
45
+ *
46
+ * - **Claude Code / Codex** honor `exit 2` + a stderr reason as a turn block,
47
+ * and the harness re-prompts the model with the stderr text.
48
+ * - **Cursor** ignores stop-hook exit codes (non-zero = fail-open, the turn
49
+ * proceeds) and re-prompts ONLY via a `followup_message` field in stdout
50
+ * JSON, which it auto-submits as the next user message: the sanctioned
51
+ * "iterate until a goal is met" channel, capped by `loop_limit` (default 5).
52
+ * We exit 0 so Cursor treats the run as a success and honors the output.
53
+ * (Confirmed against cursor.com/docs/agent/hooks.)
54
+ */
55
+ export function emitStopBlock(harness: Harness, verdict: { reason?: string; rule: string }): 0 | 2 {
56
+ const message = `${
57
+ verdict.reason ?? "End-of-turn coordination ritual incomplete."
58
+ }\n[agent-hook stop]: rule=${verdict.rule}`;
59
+ if (harness === "cursor") {
60
+ process.stdout.write(`${JSON.stringify({ followup_message: message })}\n`);
61
+ return 0;
62
+ }
63
+ process.stderr.write(`${message}\n`);
64
+ return 2;
65
+ }
66
+
67
+ function buildContextJson(
68
+ harness: Harness,
69
+ event: SystemEvent,
70
+ text: string,
71
+ ): Record<string, unknown> {
72
+ if (harness === "cursor") {
73
+ // Cursor uses a flat top-level key + an env block that survives across the
74
+ // session. The dispatcher historically also wrote
75
+ // `env: {HARNERY_AGENT_COORD_HARNESS, HARNERY_AGENT_COORD_PLATFORM}`. These are
76
+ // observer hints, not load-bearing; agent-hook + agent-coord recover the
77
+ // harness from event metadata. Drop them.
78
+ return { additional_context: text };
79
+ }
80
+ // Claude Code + Codex share the `hookSpecificOutput` envelope.
81
+ return {
82
+ hookSpecificOutput: {
83
+ hookEventName: event,
84
+ additionalContext: text,
85
+ },
86
+ };
87
+ }
88
+
89
+ function buildDenyJson(harness: Harness, reason: string): Record<string, unknown> {
90
+ if (harness === "cursor") {
91
+ return { permission: "deny", agent_message: reason, user_message: reason };
92
+ }
93
+ return {
94
+ hookSpecificOutput: {
95
+ hookEventName: "PreToolUse",
96
+ permissionDecision: "deny",
97
+ permissionDecisionReason: reason,
98
+ },
99
+ };
100
+ }
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Harness payload parser. One file because all three harnesses (CC, Cursor,
3
+ * Codex) share most of their PreToolUse / PostToolUse field names (Codex's
4
+ * field names match CC's directly) and Cursor's deltas are small enough to
5
+ * branch inline.
6
+ *
7
+ * The parser is intentionally tolerant: every field reads through `pickStr`,
8
+ * `pickNum`, etc., so a missing key returns `undefined` instead of throwing.
9
+ * Phase 2 ship criterion is "parser correctness across thousands of real
10
+ * events without affecting behavior": fail-soft beats fail-hard.
11
+ */
12
+
13
+ import type { Harness } from "../events/schema.ts";
14
+
15
+ export interface ParsedPayload {
16
+ hook_event_name?: string;
17
+ session_id?: string;
18
+ agent_id?: string;
19
+ subagent_id?: string;
20
+ conversation_id?: string;
21
+ parent_session_id?: string;
22
+ turn_id?: string;
23
+ parent_turn_id?: string;
24
+ transcript_path?: string;
25
+ cwd?: string;
26
+ pid?: number;
27
+ model?: string;
28
+ source?: string; // SessionStart: "startup" | "resume" | …
29
+ prompt?: string; // UserPromptSubmit / beforeSubmitPrompt
30
+ tool_name?: string; // Pre/PostToolUse
31
+ tool_input?: unknown; // Pre/PostToolUse: the model's call arguments
32
+ tool_response?: unknown; // PostToolUse: the tool's output (string or object)
33
+ tool_use_id?: string; // CC ties pre/post via this; Codex echoes it too
34
+ stop_hook_active?: boolean; // Stop
35
+ clean_exit?: boolean; // SessionEnd
36
+ exit_status?: string; // SubagentStop
37
+ reason?: string; // SubagentStop / StopFailure
38
+ /** original parsed object, preserved for callers that need a field we didn't pluck. */
39
+ raw: Record<string, unknown>;
40
+ }
41
+
42
+ /**
43
+ * Parse the raw stdin payload string for any harness. Returns null when JSON
44
+ * parse fails (Cursor occasionally fires hooks with no payload).
45
+ */
46
+ export function parsePayload(raw: string, _harness: Harness): ParsedPayload | null {
47
+ if (!raw || raw.trim().length === 0) return null;
48
+ let json: Record<string, unknown>;
49
+ try {
50
+ json = JSON.parse(raw) as Record<string, unknown>;
51
+ } catch {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ hook_event_name: pickStr(json, "hook_event_name"),
57
+ session_id: pickStr(json, "session_id"),
58
+ agent_id: pickStr(json, "agent_id"),
59
+ subagent_id: pickStr(json, "subagent_id"),
60
+ conversation_id: pickStr(json, "conversation_id"),
61
+ parent_session_id: pickStr(json, "parent_session_id"),
62
+ turn_id: pickStr(json, "turn_id"),
63
+ parent_turn_id: pickStr(json, "parent_turn_id"),
64
+ transcript_path: pickStr(json, "transcript_path"),
65
+ cwd: pickStr(json, "cwd"),
66
+ pid: pickNum(json, "pid"),
67
+ model: pickStr(json, "model"),
68
+ source: pickStr(json, "source"),
69
+ prompt: pickStr(json, "prompt"),
70
+ tool_name: pickStr(json, "tool_name"),
71
+ tool_input: json.tool_input,
72
+ tool_response: json.tool_response,
73
+ tool_use_id: pickStr(json, "tool_use_id"),
74
+ stop_hook_active: pickBool(json, "stop_hook_active"),
75
+ clean_exit: pickBool(json, "clean_exit"),
76
+ exit_status: pickStr(json, "exit_status"),
77
+ reason: pickStr(json, "reason"),
78
+ raw: json,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Pull the bash command string out of a Bash/Shell tool_input. Returns
84
+ * undefined for non-shell tools.
85
+ */
86
+ export function extractBashCommand(
87
+ toolName: string | undefined,
88
+ toolInput: unknown,
89
+ ): string | undefined {
90
+ if (!toolName) return undefined;
91
+ if (toolName !== "Bash" && toolName !== "Shell") return undefined;
92
+ if (toolInput && typeof toolInput === "object") {
93
+ const t = toolInput as Record<string, unknown>;
94
+ const cmd = t.command;
95
+ if (typeof cmd === "string") return cmd;
96
+ }
97
+ return undefined;
98
+ }
99
+
100
+ /**
101
+ * Pull the model's description string out of a tool_input. Only Claude Code's
102
+ * Bash tool requires this field; falls back to undefined elsewhere.
103
+ */
104
+ export function extractToolDescription(toolInput: unknown): string | undefined {
105
+ if (toolInput && typeof toolInput === "object") {
106
+ const t = toolInput as Record<string, unknown>;
107
+ const d = t.description;
108
+ if (typeof d === "string") return d;
109
+ }
110
+ return undefined;
111
+ }
112
+
113
+ function pickStr(o: Record<string, unknown>, k: string): string | undefined {
114
+ const v = o[k];
115
+ return typeof v === "string" && v.length > 0 ? v : undefined;
116
+ }
117
+
118
+ function pickNum(o: Record<string, unknown>, k: string): number | undefined {
119
+ const v = o[k];
120
+ return typeof v === "number" && Number.isFinite(v) ? v : undefined;
121
+ }
122
+
123
+ function pickBool(o: Record<string, unknown>, k: string): boolean | undefined {
124
+ const v = o[k];
125
+ return typeof v === "boolean" ? v : undefined;
126
+ }
127
+
128
+ // ── Event-name normalization ────────────────────────────────────────────────
129
+
130
+ /**
131
+ * Each harness uses a slightly different name for the "same" lifecycle event.
132
+ * Map the CLI-arg event-name (kebab-case, set by us in the wiring) to one of
133
+ * the canonical event_types. Phase 2's CLI passes the kebab event
134
+ * name; this returns the canonical event_type or null when the event has no
135
+ * canonical equivalent (e.g. Cursor's before-shell-execution duplicates
136
+ * pre-tool-use semantically, so we route both to `tool.pre_use`).
137
+ */
138
+ export function normalizeEventName(
139
+ eventName: string,
140
+ ): { event_type: NormalizedEventType; intra_turn: boolean } | null {
141
+ switch (eventName) {
142
+ case "session-start":
143
+ return { event_type: "session.start", intra_turn: false };
144
+ case "session-end":
145
+ return { event_type: "session.end", intra_turn: false };
146
+ case "user-prompt-submit":
147
+ return { event_type: "user_prompt.submit", intra_turn: false };
148
+ case "stop":
149
+ return { event_type: "turn.stop", intra_turn: false };
150
+ case "stop-failure":
151
+ // Phase 2: a failed stop is still a turn boundary; emit turn.stop and
152
+ // attach the failure signal in `data`. Phase 5 may introduce a
153
+ // dedicated `turn.stop_failure` event if the projector needs to branch.
154
+ return { event_type: "turn.stop", intra_turn: false };
155
+ case "sub-agent-start":
156
+ return { event_type: "subagent.start", intra_turn: false };
157
+ case "sub-agent-stop":
158
+ return { event_type: "subagent.stop", intra_turn: false };
159
+ case "pre-tool-use":
160
+ case "before-shell-execution":
161
+ return { event_type: "tool.pre_use", intra_turn: true };
162
+ case "post-tool-use":
163
+ return { event_type: "tool.post_use", intra_turn: true };
164
+ case "post-tool-use-failure":
165
+ return { event_type: "tool.post_use_failure", intra_turn: true };
166
+ default:
167
+ return null;
168
+ }
169
+ }
170
+
171
+ export type NormalizedEventType =
172
+ | "session.start"
173
+ | "session.end"
174
+ | "user_prompt.submit"
175
+ | "turn.stop"
176
+ | "subagent.start"
177
+ | "subagent.stop"
178
+ | "tool.pre_use"
179
+ | "tool.post_use"
180
+ | "tool.post_use_failure";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * agent-hooks library exports.
3
+ *
4
+ * Phase 1 (skeleton): exposes only the canonical event schema types. Parsers,
5
+ * resolvers, and emit helpers ship in Phase 2.
6
+ */
7
+
8
+ export {
9
+ type Event,
10
+ type EventEnvelope,
11
+ type EventType,
12
+ type Harness,
13
+ type RedactionMarker,
14
+ SCHEMA_VERSION,
15
+ type Source,
16
+ } from "./events/schema.ts";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Pure pid-map anchor selection: picks which PID in a process's ppid chain
3
+ * the heartbeat pid-map row should be keyed to, so the agent's later shell
4
+ * tool calls (which ppid-walk up to find it) resolve their own identity.
5
+ *
6
+ * Split out of cli.ts's `findHarnessAnchorPid` so the comm-matching logic is
7
+ * unit-testable against the real Phase 0 Cursor probe chains without needing a
8
+ * live /proc tree.
9
+ *
10
+ * Why the cursor `node` fallback exists: Cursor's WSL/Linux process tree has
11
+ * NO process named `cursor`: the chain is `bash → bash → node → node → sh →
12
+ * Relay → init`. The `node` ancestors are stable across every hook + tool event
13
+ * in one IDE-window session, so anchoring there gives the agent's shell tool
14
+ * calls a reachable, stable pid-map row. This faithfully restores the previous
15
+ * cursor anchor behavior (match `cursor` first, then fall back to `node`) that
16
+ * was dropped in the Phase 4-6 TS refactor, the same regression class as the
17
+ * heartbeat-platform + pidmap-self-heal losses. Scoped to `harness === "cursor"`
18
+ * so Claude Code / Codex never
19
+ * mis-anchor on an unrelated `node` ancestor (they match their own comm token
20
+ * directly).
21
+ *
22
+ * Known limitation (matches the bash original): two Cursor chats sharing one
23
+ * IDE window share their `node` ancestor, so the pid-map row is last-writer-
24
+ * wins between them. Single-chat-per-window (the common case) is correct;
25
+ * concurrent same-window chats disambiguate via `--session-id`.
26
+ */
27
+
28
+ /** Harness-binary comm names. CC's tool calls descend from `claude`, Codex's
29
+ * from `codex`; matched directly so neither needs the `node` fallback. */
30
+ const PRIMARY_COMM_TOKENS = new Set(["claude", "claude-code", "cursor", "codex"]);
31
+
32
+ /**
33
+ * Pick the anchor PID from a ppid chain ordered nearest-first (the resolving
34
+ * process at index 0, walking up toward init). Returns the first ancestor
35
+ * matching a harness comm token; for cursor, falls back to the first `node`
36
+ * ancestor; otherwise undefined (caller falls back to process.ppid).
37
+ */
38
+ export function selectAnchorPid(
39
+ chain: ReadonlyArray<{ pid: number; comm: string }>,
40
+ harness?: string,
41
+ ): number | undefined {
42
+ for (const hop of chain) {
43
+ if (PRIMARY_COMM_TOKENS.has(hop.comm)) return hop.pid;
44
+ }
45
+ if (harness === "cursor") {
46
+ for (const hop of chain) {
47
+ if (hop.comm === "node") return hop.pid;
48
+ }
49
+ }
50
+ return undefined;
51
+ }
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { coordEnv } from "../../../lib/env.ts";
4
+
5
+ /**
6
+ * Walk up from `start` looking for a directory containing `.harnery/`. The
7
+ * single coord-root resolution so every adapter agrees on the same root.
8
+ */
9
+ export function findCoordRoot(start: string = process.cwd()): string | null {
10
+ // HARNERY_COORD_ROOT_OVERRIDE: test-mode escape hatch matched by agent-coord
11
+ // and the bash side. Lets the sandboxed coord-test suite run against a
12
+ // temp directory rather than the real monorepo root. agent-hook's
13
+ // session.start handler may be the FIRST thing to create .harnery/ in a
14
+ // fresh sandbox, so the override is honored unconditionally (we don't
15
+ // require .harnery/ to pre-exist).
16
+ const override = coordEnv("COORD_ROOT_OVERRIDE");
17
+ if (override) return override;
18
+ let dir = resolve(start);
19
+ while (true) {
20
+ if (existsSync(join(dir, ".harnery"))) return dir;
21
+ const parent = dirname(dir);
22
+ if (parent === dir) return null;
23
+ dir = parent;
24
+ }
25
+ }
@@ -0,0 +1,89 @@
1
+ import { existsSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
4
+ /**
5
+ * Resolve the model-declared intent for the current tool call. Three-source
6
+ * precedence:
7
+ *
8
+ * 1. `# intent: X` comment on the first line of the bash command.
9
+ * 2. `description` field on the tool input (Claude Code only).
10
+ * 3. `<intent>...</intent>` tag in the most-recent assistant prose, read
11
+ * from `.harnery/.last-intent.<instance_id>` (the bash bash-tap is the
12
+ * only thing that does transcript scanning today; agent-hook stamps the
13
+ * file on precedence 1/2 so the bash-tap can fall back to it).
14
+ *
15
+ * Phase 8 (2026-05-27): agent-hook writes the stamp file itself on precedence
16
+ * 1/2 (replacing coord-intent-stamp.sh's command/description branches). The
17
+ * transcript-tag branch is now the only thing left in coord-intent-stamp.sh.
18
+ */
19
+ export function resolveIntent(opts: {
20
+ coordRoot: string;
21
+ instanceId: string;
22
+ /** First-line `# intent: X` comment from the bash command (preferred fresh source). */
23
+ commandIntentComment?: string;
24
+ /** `description` field on the tool input (Claude Code only). */
25
+ description?: string;
26
+ }): { intent: string; source: "command-comment" | "description" | "stamp" | "none" } {
27
+ const stampPath = join(opts.coordRoot, ".harnery", `.last-intent.${opts.instanceId}`);
28
+
29
+ // 1. Fresh from the command itself (most reliable, cross-harness).
30
+ if (opts.commandIntentComment) {
31
+ const cleaned = clamp(opts.commandIntentComment);
32
+ if (cleaned) {
33
+ writeStamp(stampPath, cleaned);
34
+ return { intent: cleaned, source: "command-comment" };
35
+ }
36
+ }
37
+
38
+ // 2. Fresh from the tool input description (Claude Code only).
39
+ if (opts.description) {
40
+ const cleaned = clamp(opts.description);
41
+ if (cleaned) {
42
+ writeStamp(stampPath, cleaned);
43
+ return { intent: cleaned, source: "description" };
44
+ }
45
+ }
46
+
47
+ // 3. Whatever the bash-tap stamped from a transcript <intent> tag.
48
+ if (existsSync(stampPath)) {
49
+ try {
50
+ const stamped = readFileSync(stampPath, "utf8").trim();
51
+ if (stamped && stamped !== "(no intent)") {
52
+ return { intent: stamped, source: "stamp" };
53
+ }
54
+ } catch {
55
+ /* fallthrough */
56
+ }
57
+ }
58
+
59
+ return { intent: "(no intent)", source: "none" };
60
+ }
61
+
62
+ function writeStamp(path: string, value: string): void {
63
+ try {
64
+ const tmp = `${path}.${process.pid}.tmp`;
65
+ writeFileSync(tmp, value, "utf8");
66
+ renameSync(tmp, path);
67
+ } catch {
68
+ /* best-effort */
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Match `# intent: X` at the start of a command (after optional leading
74
+ * whitespace on the first line). Mirrors `extract_command_intent_comment` in
75
+ * coord-intent-stamp.sh.
76
+ */
77
+ export function extractIntentComment(command: string | undefined): string | undefined {
78
+ if (!command) return undefined;
79
+ const firstLine = command.split("\n", 1)[0];
80
+ const m = firstLine?.match(/^[ \t]*#[ \t]*intent:[ \t]*(.+)$/);
81
+ if (!m) return undefined;
82
+ return m[1]?.trimEnd();
83
+ }
84
+
85
+ function clamp(v: string): string {
86
+ const trimmed = v.trim();
87
+ if (trimmed.length <= 200) return trimmed;
88
+ return `${trimmed.slice(0, 197)}...`;
89
+ }
@@ -0,0 +1,140 @@
1
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { coordEnv } from "../../../lib/env.ts";
4
+
5
+ /**
6
+ * Resolve the canonical `instance_id` for the current hook invocation.
7
+ *
8
+ * Precedence:
9
+ *
10
+ * 1. `HARNERY_AGENT_COORD_OWNER` env var. Set by harness adapters when they know
11
+ * the owner identity at spawn time (Codex's apply_patch tool path uses
12
+ * this).
13
+ * 2. Hook payload fields, in order: `agent_id` → `subagent_id` →
14
+ * `session_id` → `conversation_id`. agent_id wins for CC subagent
15
+ * events; session_id is the parent-shape default.
16
+ * 3. PID-map lookup at `.harnery/pid-map/<pid>` for our own pid, then ppid
17
+ * chain (up to 20 hops).
18
+ *
19
+ * Returns null when nothing resolves. Callers must treat null as "no owner"
20
+ * and skip the event (Phase 2 fail-safe; Phase 3 will mint a temporary owner
21
+ * for orphan events).
22
+ */
23
+ export function resolveOwner(opts: {
24
+ payload: Record<string, unknown> | null;
25
+ coordRoot: string;
26
+ }): { instance_id: string; source: "env" | "payload" | "pidmap-self" | "pidmap-ancestor" } | null {
27
+ const env = coordEnv("AGENT_COORD_OWNER");
28
+ if (env && env.length > 0) {
29
+ return { instance_id: env, source: "env" };
30
+ }
31
+
32
+ if (opts.payload) {
33
+ for (const key of ["agent_id", "subagent_id", "session_id", "conversation_id"] as const) {
34
+ const v = opts.payload[key];
35
+ if (typeof v === "string" && v.length > 0) {
36
+ return { instance_id: v, source: "payload" };
37
+ }
38
+ }
39
+ }
40
+
41
+ // Pid-map ancestor walk. Start at own pid (the bash wrapper's bun child),
42
+ // walk up through ppids. The pid-map is stamped keyed by the harness PID, so
43
+ // we'll usually find it 1-3 hops up.
44
+ const pidmap = join(opts.coordRoot, ".harnery", "pid-map");
45
+ if (existsSync(pidmap)) {
46
+ let pid = process.pid;
47
+ let hops = 0;
48
+ while (hops < 20) {
49
+ const file = join(pidmap, String(pid));
50
+ if (existsSync(file)) {
51
+ try {
52
+ const row = readFileSync(file, "utf8").trim();
53
+ // Row shape: "<instance_id>" or "<instance_id>\t<platform>"
54
+ const owner = row.split("\t")[0];
55
+ if (owner && owner.length > 0) {
56
+ return {
57
+ instance_id: owner,
58
+ source: hops === 0 ? "pidmap-self" : "pidmap-ancestor",
59
+ };
60
+ }
61
+ } catch {
62
+ /* keep walking */
63
+ }
64
+ }
65
+ const ppid = readPpid(pid);
66
+ if (!ppid || ppid === 0 || ppid === 1) break;
67
+ pid = ppid;
68
+ hops++;
69
+ }
70
+ }
71
+
72
+ return null;
73
+ }
74
+
75
+ function readPpid(pid: number): number | null {
76
+ // Linux/WSL: /proc/<pid>/status carries `PPid:`. Falls back to null on
77
+ // macOS or any read failure; ancestor walk just terminates.
78
+ try {
79
+ const status = readFileSync(`/proc/${pid}/status`, "utf8");
80
+ for (const line of status.split("\n")) {
81
+ if (line.startsWith("PPid:")) {
82
+ const n = Number(line.split(/\s+/)[1]);
83
+ return Number.isFinite(n) ? n : null;
84
+ }
85
+ }
86
+ } catch {
87
+ /* fallthrough */
88
+ }
89
+ return null;
90
+ }
91
+
92
+ /**
93
+ * Find the parent owner for a subagent invocation. The per-shell marker at
94
+ * `.harnery/shells/<pid>` is set by `sub-agent-start` and removed by
95
+ * `sub-agent-stop`.
96
+ *
97
+ * Phase 2 stub: the marker file isn't written yet, so we return null in most
98
+ * cases. Phase 2 callers can pass through.
99
+ */
100
+ export function readShellMarker(coordRoot: string, pid: number): string | null {
101
+ const path = join(coordRoot, ".harnery", "shells", String(pid));
102
+ if (!existsSync(path)) return null;
103
+ try {
104
+ return readFileSync(path, "utf8").trim() || null;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ /** Read the heartbeat-recorded `agent_id` for an owner if it exists. */
111
+ export function readAgentIdForOwner(coordRoot: string, instanceId: string): string | null {
112
+ const path = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
113
+ if (!existsSync(path)) return null;
114
+ try {
115
+ const body = readFileSync(path, "utf8");
116
+ const data = JSON.parse(body) as { agent_id?: string };
117
+ return data.agent_id ?? null;
118
+ } catch {
119
+ return null;
120
+ }
121
+ }
122
+
123
+ /** Diagnostic: list of pid-map entries (for debugging). */
124
+ export function listPidmap(coordRoot: string): Array<{ pid: number; owner: string }> {
125
+ const dir = join(coordRoot, ".harnery", "pid-map");
126
+ if (!existsSync(dir)) return [];
127
+ const out: Array<{ pid: number; owner: string }> = [];
128
+ for (const f of readdirSync(dir)) {
129
+ const pid = Number(f);
130
+ if (!Number.isFinite(pid)) continue;
131
+ try {
132
+ const row = readFileSync(join(dir, f), "utf8").trim();
133
+ const owner = row.split("\t")[0];
134
+ if (owner) out.push({ pid, owner });
135
+ } catch {
136
+ /* skip */
137
+ }
138
+ }
139
+ return out;
140
+ }
@@ -0,0 +1,72 @@
1
+ import { existsSync, statSync } from "node:fs";
2
+
3
+ /**
4
+ * Scan a CC-style JSONL transcript for the `┌─ agent-` status-box prefix in
5
+ * the most-recent assistant turn. Used by `turn.stop` events to populate
6
+ * `status_box_present`.
7
+ *
8
+ * Cheap by default: caps the read at 256KB tailed from the file, since the
9
+ * status box (if present) is always near the end of the most-recent turn.
10
+ * Phase 2 doesn't try to fight the flush race: if the last assistant block
11
+ * hasn't been written yet at Stop-hook time, status_box_present is `false`
12
+ * and Phase 5 verdict path catches the race via a single retry.
13
+ */
14
+ export function scanStatusBoxPresent(transcriptPath: string | undefined): boolean {
15
+ if (!transcriptPath || !existsSync(transcriptPath)) return false;
16
+ const text = tailText(transcriptPath);
17
+ if (text === undefined) return false;
18
+ // The box is rendered as a text content block by the assistant; we look
19
+ // for the prefix on any line of the trailing window.
20
+ return text.includes("┌─ agent-");
21
+ }
22
+
23
+ /**
24
+ * Resolve the agent's model from a CC-style JSONL transcript by reading the
25
+ * most-recent assistant message's `message.model`. Claude Code's SessionStart
26
+ * payload omits `model` (Codex + Cursor supply it directly), so this is the
27
+ * fallback that lets `session.start` / `turn.stop` populate the heartbeat's
28
+ * model field once the transcript has at least one assistant turn.
29
+ *
30
+ * Tail-reads the same 256KB window as the status-box scan and walks lines from
31
+ * the end, returning the first real model id found. Synthetic placeholders
32
+ * (`<synthetic>`) and empty values are skipped. Returns undefined when the
33
+ * transcript is missing/empty (e.g. a fresh session's first SessionStart).
34
+ */
35
+ export function scanTranscriptModel(transcriptPath: string | undefined): string | undefined {
36
+ if (!transcriptPath || !existsSync(transcriptPath)) return undefined;
37
+ const text = tailText(transcriptPath);
38
+ if (!text) return undefined;
39
+ const lines = text.split("\n");
40
+ for (let i = lines.length - 1; i >= 0; i--) {
41
+ const line = lines[i]?.trim();
42
+ if (!line?.includes('"model"')) continue;
43
+ try {
44
+ const obj = JSON.parse(line) as {
45
+ message?: { model?: unknown };
46
+ model?: unknown;
47
+ };
48
+ const model = obj.message?.model ?? obj.model;
49
+ if (typeof model === "string" && model.length > 0 && !model.startsWith("<")) {
50
+ return model;
51
+ }
52
+ } catch {
53
+ // Partial/truncated first line of the tail window; skip it.
54
+ }
55
+ }
56
+ return undefined;
57
+ }
58
+
59
+ /** Tail-read up to 256KB from the end of a file as UTF-8, or undefined on error. */
60
+ function tailText(path: string): string | undefined {
61
+ try {
62
+ const size = statSync(path).size;
63
+ const start = Math.max(0, size - 256 * 1024);
64
+ const fd = require("node:fs").openSync(path, "r");
65
+ const buf = Buffer.alloc(size - start);
66
+ require("node:fs").readSync(fd, buf, 0, buf.length, start);
67
+ require("node:fs").closeSync(fd);
68
+ return buf.toString("utf8");
69
+ } catch {
70
+ return undefined;
71
+ }
72
+ }
File without changes