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,228 @@
1
+ /**
2
+ * Command/narration event emitter for the coordination layer.
3
+ *
4
+ * `writeSessionEvent` emits command + narration events straight to the
5
+ * **canonical** `.harnery/events.ndjson` (alongside the hook events), which the
6
+ * `/live` web viewer reads. The exported surface (`writeSessionEvent`,
7
+ * `newCmdId`, `clampField`, `readLastIntent`) is stable so the session-tee
8
+ * middleware callers need no edits.
9
+ */
10
+
11
+ import { randomBytes } from "node:crypto";
12
+ import { existsSync, readFileSync, statSync } from "node:fs";
13
+ import { dirname, resolve } from "node:path";
14
+ // Kept dependency-light: vendored verbatim into a downstream consumer, so no coordEnv import.
15
+ import { normalizeHarness } from "./canonical-emit.ts";
16
+ import { emit } from "./events/emit.ts";
17
+
18
+ /** Event types accepted by `writeSessionEvent`. Only the command stream +
19
+ * narration are emitted canonically; the coord/state types are accepted for
20
+ * call-site compatibility but are no-ops (the agents CLI emits those itself). */
21
+ export type SessionEventType =
22
+ | "command_start"
23
+ | "output"
24
+ | "command_end"
25
+ | "end_of_turn"
26
+ | "hook_event"
27
+ | "set_task"
28
+ | "file_claim"
29
+ | "file_release"
30
+ | "peer_change"
31
+ | "narration";
32
+
33
+ /**
34
+ * Resolved path of the ndjson sidecar file. Lives inside `.harnery/` so a
35
+ * containerized reader can pick it up through a single bind mount.
36
+ */
37
+ export function sessionEventsPath(): string {
38
+ // Explicit override (tests + non-monorepo invocations).
39
+ const explicit = process.env.HARNERY_OUTPUT_SESSION_EVENTS;
40
+ if (explicit) return explicit;
41
+ // Walk up looking for an EXISTING .harnery/ directory; only match dirs
42
+ // (submodule .git is a file/gitlink, and CLAUDE.md / AGENTS.md can
43
+ // proliferate per-subdirectory in many monorepos, so neither is a safe
44
+ // anchor). Don't reintroduce CLAUDE.md / .git as anchors, which creates
45
+ // stray .harnery/ dirs under submodule subtrees.
46
+ let dir = process.cwd();
47
+ for (let i = 0; i < 10; i++) {
48
+ const candidate = resolve(dir, ".harnery");
49
+ try {
50
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
51
+ return resolve(candidate, "session-events.ndjson");
52
+ }
53
+ } catch {
54
+ /* keep walking */
55
+ }
56
+ const parent = dirname(dir);
57
+ if (parent === dir) break;
58
+ dir = parent;
59
+ }
60
+ return resolve(process.env.HOME || "/tmp", ".harnery", "session-events.ndjson");
61
+ }
62
+
63
+ /** Random 8-char hex id for grouping output lines under a single command. */
64
+ export function newCmdId(): string {
65
+ return randomBytes(4).toString("hex");
66
+ }
67
+
68
+ /**
69
+ * Read the model's most recent `<intent>...</intent>` declaration from the
70
+ * intent-stamp file written by the PreToolUse hook. Returns null when the
71
+ * file is missing, empty, or contains the explicit `(no intent)` sentinel.
72
+ * Callers fall back to whatever default they want in that case.
73
+ *
74
+ * Path: `.harnery/.last-intent.<instance_id>` next to the agent's heartbeat.
75
+ */
76
+ export function readLastIntent(instanceId?: string): string | null {
77
+ if (!instanceId) return null;
78
+ // Walk up from cwd looking for .harnery/, same resolution as
79
+ // sessionEventsPath() (only match existing directories).
80
+ let dir = process.cwd();
81
+ let agentsDir: string | null = null;
82
+ for (let i = 0; i < 10; i++) {
83
+ const candidate = resolve(dir, ".harnery");
84
+ try {
85
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
86
+ agentsDir = candidate;
87
+ break;
88
+ }
89
+ } catch {
90
+ /* keep walking */
91
+ }
92
+ const parent = dirname(dir);
93
+ if (parent === dir) break;
94
+ dir = parent;
95
+ }
96
+ if (!agentsDir) return null;
97
+ const intentPath = resolve(agentsDir, `.last-intent.${instanceId}`);
98
+ if (!existsSync(intentPath)) return null;
99
+ try {
100
+ const raw = readFileSync(intentPath, "utf8").trim();
101
+ if (!raw || raw === "(no intent)") return null;
102
+ return raw;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Dual-write: mirror command/narration session-events
110
+ * into the canonical `.harnery/events.ndjson` stream so the legacy
111
+ * session-events.ndjson writer + its web consumers can be retired.
112
+ * Only the command-stream + narration types migrate; the coord/state types
113
+ * (`set_task`, `file_claim`, `peer_change`, …) are already emitted canonically
114
+ * by the agents CLI, so re-emitting them here would double-count.
115
+ */
116
+ const CANONICAL_TYPE: Partial<Record<SessionEventType, string>> = {
117
+ command_start: "command.start",
118
+ output: "command.output",
119
+ command_end: "command.end",
120
+ narration: "narration",
121
+ };
122
+
123
+ interface HeartbeatEnrichment {
124
+ session_id: string;
125
+ harness: "claude-code" | "cursor" | "codex";
126
+ at: number;
127
+ }
128
+ /** Cache heartbeat-derived envelope fields per instance_id so a burst of
129
+ * `output` lines (one event per stdout line) doesn't re-read + re-parse the
130
+ * heartbeat JSON each time. Short TTL: picks up platform/session changes
131
+ * within a few seconds without hammering the disk on a chatty command. */
132
+ const enrichCache = new Map<string, HeartbeatEnrichment>();
133
+ const ENRICH_TTL_MS = 5000;
134
+
135
+ function enrichFromHeartbeat(coordRoot: string, instanceId: string): HeartbeatEnrichment | null {
136
+ const now = Date.now();
137
+ const cached = enrichCache.get(instanceId);
138
+ if (cached && now - cached.at < ENRICH_TTL_MS) return cached;
139
+ try {
140
+ const hbPath = resolve(coordRoot, ".harnery", "active", `${instanceId}.json`);
141
+ if (!existsSync(hbPath)) return null;
142
+ const hb = JSON.parse(readFileSync(hbPath, "utf8")) as {
143
+ session_id?: string;
144
+ platform?: string;
145
+ };
146
+ const enrichment: HeartbeatEnrichment = {
147
+ session_id: hb.session_id || instanceId,
148
+ harness: normalizeHarness(hb.platform),
149
+ at: now,
150
+ };
151
+ enrichCache.set(instanceId, enrichment);
152
+ return enrichment;
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+
158
+ /** Project the flat legacy `fields` into the canonical event's `data` shape.
159
+ * Unknown types never reach here, guarded by CANONICAL_TYPE. */
160
+ function canonicalData(
161
+ type: SessionEventType,
162
+ fields: Record<string, unknown>,
163
+ ): Record<string, unknown> {
164
+ switch (type) {
165
+ case "command_start":
166
+ return { cmd_id: fields.cmd_id, intent: fields.intent, cmd: fields.cmd };
167
+ case "output":
168
+ return { cmd_id: fields.cmd_id, stream: fields.stream, line: fields.line };
169
+ case "command_end":
170
+ return {
171
+ cmd_id: fields.cmd_id,
172
+ exit: fields.exit,
173
+ duration_ms: fields.duration_ms,
174
+ ...(fields.signal ? { signal: fields.signal } : {}),
175
+ };
176
+ case "narration":
177
+ return { message: fields.message };
178
+ default:
179
+ return {};
180
+ }
181
+ }
182
+
183
+ /** Emit a command/narration event to the canonical stream. Swallows every
184
+ * error and skips when identity can't be resolved: telemetry must never break
185
+ * (or slow down) a command. Non-command types return early. */
186
+ function emitCanonicalCommand(type: SessionEventType, fields: Record<string, unknown>): void {
187
+ const eventType = CANONICAL_TYPE[type];
188
+ if (!eventType) return;
189
+ const instanceId = typeof fields.instance_id === "string" ? fields.instance_id : undefined;
190
+ if (!instanceId) return;
191
+ try {
192
+ // coordRoot = the dir containing `.harnery/`; sessionEventsPath() anchors it.
193
+ const coordRoot = dirname(dirname(sessionEventsPath()));
194
+ const enrich = enrichFromHeartbeat(coordRoot, instanceId);
195
+ if (!enrich) return;
196
+ emit(coordRoot, {
197
+ event_type: eventType,
198
+ instance_id: instanceId,
199
+ session_id: enrich.session_id,
200
+ harness: enrich.harness,
201
+ data: canonicalData(type, fields),
202
+ });
203
+ } catch {
204
+ /* telemetry only, never break the command */
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Emit a session event. Command + narration events
210
+ * are written to the canonical `.harnery/events.ndjson`; the coord/state types
211
+ * are accepted for call-site compatibility but are no-ops here (the agents CLI
212
+ * emits those itself). Best-effort, never throws into the caller; a command
213
+ * must never break or slow on telemetry. The `agentName` arg is retained for
214
+ * the stable call signature (canonical events key on instance_id, not name).
215
+ */
216
+ export function writeSessionEvent(
217
+ type: SessionEventType,
218
+ _agentName: string,
219
+ fields: Record<string, unknown> = {},
220
+ ): void {
221
+ emitCanonicalCommand(type, fields);
222
+ }
223
+
224
+ /** Trim long values to keep individual events small. */
225
+ export function clampField(v: string, max = 1024): string {
226
+ if (v.length <= max) return v;
227
+ return `${v.slice(0, max - 1)}…`;
228
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Display-name resolution for the coord layer.
3
+ *
4
+ * This module once housed `coordLog`, the writer for a human-readable
5
+ * activity log. That log has been retired; the canonical
6
+ * `.harnery/events.ndjson` stream is the single source of truth, and the
7
+ * per-event telemetry that had consumers (heals → health.*, councils →
8
+ * council.*, shell-mutation candidates → decision.warn) is emitted there
9
+ * directly. `coordLog` and its call sites are gone; only `resolveShortName`
10
+ * remains (still used for display-name resolution).
11
+ */
12
+
13
+ import { existsSync, readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+
16
+ /**
17
+ * Resolve `agent-<name>` display string. Looks up the heartbeat's `name`
18
+ * field; falls back to `agent-<8-char-hex>` if name is empty (mirrors bash
19
+ * coord_owner_short). Returns `agent-unknown` when instanceId is null.
20
+ */
21
+ export function resolveShortName(coordRoot: string, instanceId: string | null): string {
22
+ if (!instanceId) return "agent-unknown";
23
+ const path = join(coordRoot, ".harnery", "active", `${instanceId}.json`);
24
+ if (existsSync(path)) {
25
+ try {
26
+ const hb = JSON.parse(readFileSync(path, "utf8")) as { name?: string };
27
+ if (hb.name && hb.name.length > 0) return `agent-${hb.name}`;
28
+ } catch {
29
+ /* fall through to short-id */
30
+ }
31
+ }
32
+ return `agent-${instanceId.slice(0, 8)}`;
33
+ }
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Council manifest mutations: lifecycle helpers for the council-* actions.
3
+ *
4
+ * A council is a multi-round debate among agents recorded at
5
+ * `.harnery/councils/<id>.json` (manifest) plus `.harnery/councils/<id>/round-N/<member>.md`
6
+ * (contributions). Archive moves both into `.harnery/councils/archive/`.
7
+ *
8
+ * This module owns: lifecycle (advance, close, archive, unarchive, delete)
9
+ * and steward reassignment. Council CREATION + contribution writes stay
10
+ * separate (operator-side, harness-agnostic).
11
+ */
12
+
13
+ import {
14
+ cpSync,
15
+ existsSync,
16
+ mkdirSync,
17
+ readFileSync,
18
+ renameSync,
19
+ rmSync,
20
+ writeFileSync,
21
+ } from "node:fs";
22
+ import { dirname, join } from "node:path";
23
+
24
+ interface CouncilManifest {
25
+ council_id: string;
26
+ status: "active" | "closed" | "archived";
27
+ current_round: number;
28
+ round_status: "open" | "closed";
29
+ members: string[];
30
+ /** schema_version 2: index-parallel to members; contributions are written as <member_id>.md. */
31
+ member_ids?: string[];
32
+ steward?: string;
33
+ steward_id?: string;
34
+ closed_at?: string;
35
+ archived_at?: string;
36
+ [extra: string]: unknown;
37
+ }
38
+
39
+ function nowIsoSeconds(): string {
40
+ return new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
41
+ }
42
+
43
+ function atomicWriteText(path: string, content: string): void {
44
+ mkdirSync(dirname(path), { recursive: true });
45
+ const tmp = `${path}.tmp.${process.pid}`;
46
+ writeFileSync(tmp, content, "utf8");
47
+ renameSync(tmp, path);
48
+ }
49
+
50
+ function councilsDir(coordRoot: string): string {
51
+ return join(coordRoot, ".harnery", "councils");
52
+ }
53
+
54
+ function archiveDir(coordRoot: string): string {
55
+ return join(councilsDir(coordRoot), "archive");
56
+ }
57
+
58
+ function readManifest(path: string): CouncilManifest | null {
59
+ if (!existsSync(path)) return null;
60
+ try {
61
+ return JSON.parse(readFileSync(path, "utf8")) as CouncilManifest;
62
+ } catch {
63
+ return null;
64
+ }
65
+ }
66
+
67
+ export function advanceCouncil(
68
+ coordRoot: string,
69
+ councilId: string,
70
+ opts: { force?: boolean } = {},
71
+ ): { ok: boolean; reason?: string; pendingMember?: string } {
72
+ const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
73
+ const manifest = readManifest(manifestPath);
74
+ if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
75
+ if (manifest.status !== "active") {
76
+ return { ok: false, reason: `council is ${manifest.status}, not active` };
77
+ }
78
+ const cur = manifest.current_round;
79
+
80
+ if (!opts.force) {
81
+ const roundDir = join(councilsDir(coordRoot), councilId, `round-${cur}`);
82
+ const memberIds = Array.isArray(manifest.member_ids) ? manifest.member_ids : [];
83
+ for (const [i, member] of manifest.members.entries()) {
84
+ // Contribution filename drifted across schema versions: v1 wrote
85
+ // <agent-Name>.md, v2 (member_ids in the manifest) writes <uuid>.md.
86
+ // Accept either so lifecycle works on both generations of council.
87
+ const candidates = [join(roundDir, `${member}.md`)];
88
+ if (memberIds[i]) candidates.push(join(roundDir, `${memberIds[i]}.md`));
89
+ if (!candidates.some((p) => existsSync(p))) {
90
+ return {
91
+ ok: false,
92
+ reason: `pending members in round ${cur} (incl. ${member}); pass --force to skip`,
93
+ pendingMember: member,
94
+ };
95
+ }
96
+ }
97
+ }
98
+
99
+ const next = cur + 1;
100
+ const nextDir = join(councilsDir(coordRoot), councilId, `round-${next}`);
101
+ mkdirSync(nextDir, { recursive: true });
102
+
103
+ const updated: CouncilManifest = {
104
+ ...manifest,
105
+ current_round: next,
106
+ round_status: "open",
107
+ };
108
+ atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
109
+ return { ok: true };
110
+ }
111
+
112
+ export function closeCouncil(
113
+ coordRoot: string,
114
+ councilId: string,
115
+ ): { ok: boolean; reason?: string } {
116
+ const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
117
+ const manifest = readManifest(manifestPath);
118
+ if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
119
+ if (manifest.status === "archived") return { ok: false, reason: "council is already archived" };
120
+
121
+ const updated: CouncilManifest = {
122
+ ...manifest,
123
+ status: "closed",
124
+ closed_at: nowIsoSeconds(),
125
+ };
126
+ atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
127
+ return { ok: true };
128
+ }
129
+
130
+ export function archiveCouncil(
131
+ coordRoot: string,
132
+ councilId: string,
133
+ ): { ok: boolean; reason?: string } {
134
+ const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
135
+ const manifest = readManifest(manifestPath);
136
+ if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
137
+
138
+ const archived: CouncilManifest = {
139
+ ...manifest,
140
+ status: "archived",
141
+ archived_at: nowIsoSeconds(),
142
+ };
143
+ const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
144
+ const archivedBody = join(archiveDir(coordRoot), councilId);
145
+ const activeBody = join(councilsDir(coordRoot), councilId);
146
+
147
+ mkdirSync(archiveDir(coordRoot), { recursive: true });
148
+ atomicWriteText(manifestPath, JSON.stringify(archived, null, 2));
149
+
150
+ try {
151
+ renameSync(manifestPath, archivedManifest);
152
+ } catch {
153
+ /* idempotent; manifest may already be in archive */
154
+ }
155
+ if (existsSync(activeBody)) {
156
+ if (existsSync(archivedBody)) {
157
+ rmSync(activeBody, { recursive: true, force: true });
158
+ } else {
159
+ try {
160
+ renameSync(activeBody, archivedBody);
161
+ } catch {
162
+ // cross-device or permissions: fall back to copy + remove.
163
+ cpSync(activeBody, archivedBody, { recursive: true });
164
+ rmSync(activeBody, { recursive: true, force: true });
165
+ }
166
+ }
167
+ }
168
+ return { ok: true };
169
+ }
170
+
171
+ export function unarchiveCouncil(
172
+ coordRoot: string,
173
+ councilId: string,
174
+ ): { ok: boolean; reason?: string } {
175
+ const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
176
+ const activeManifest = join(councilsDir(coordRoot), `${councilId}.json`);
177
+ if (!existsSync(archivedManifest)) {
178
+ return { ok: false, reason: `no archived manifest at ${archivedManifest}` };
179
+ }
180
+ if (existsSync(activeManifest)) {
181
+ return { ok: false, reason: "active manifest already exists; refusing to clobber" };
182
+ }
183
+ const manifest = readManifest(archivedManifest);
184
+ if (!manifest) return { ok: false, reason: `couldn't parse ${archivedManifest}` };
185
+
186
+ const restored: CouncilManifest = { ...manifest };
187
+ restored.archived_at = undefined;
188
+ restored.status = manifest.closed_at ? "closed" : "active";
189
+ atomicWriteText(archivedManifest, JSON.stringify(restored, null, 2));
190
+
191
+ try {
192
+ renameSync(archivedManifest, activeManifest);
193
+ } catch {
194
+ /* ignore; idempotent */
195
+ }
196
+ const archivedBody = join(archiveDir(coordRoot), councilId);
197
+ const activeBody = join(councilsDir(coordRoot), councilId);
198
+ if (existsSync(archivedBody)) {
199
+ if (existsSync(activeBody)) {
200
+ rmSync(archivedBody, { recursive: true, force: true });
201
+ } else {
202
+ try {
203
+ renameSync(archivedBody, activeBody);
204
+ } catch {
205
+ cpSync(archivedBody, activeBody, { recursive: true });
206
+ rmSync(archivedBody, { recursive: true, force: true });
207
+ }
208
+ }
209
+ }
210
+ return { ok: true };
211
+ }
212
+
213
+ export function deleteCouncil(
214
+ coordRoot: string,
215
+ councilId: string,
216
+ ): { ok: boolean; reason?: string } {
217
+ const archivedManifest = join(archiveDir(coordRoot), `${councilId}.json`);
218
+ const activeManifest = join(councilsDir(coordRoot), `${councilId}.json`);
219
+ if (existsSync(activeManifest)) {
220
+ return {
221
+ ok: false,
222
+ reason: `council ${councilId} is not archived; archive first`,
223
+ };
224
+ }
225
+ if (!existsSync(archivedManifest)) {
226
+ return { ok: false, reason: `no archived manifest at ${archivedManifest}` };
227
+ }
228
+ const archivedBody = join(archiveDir(coordRoot), councilId);
229
+
230
+ rmSync(archivedManifest, { force: true });
231
+ if (existsSync(archivedBody)) {
232
+ rmSync(archivedBody, { recursive: true, force: true });
233
+ }
234
+ return { ok: true };
235
+ }
236
+
237
+ export function setCouncilSteward(
238
+ coordRoot: string,
239
+ councilId: string,
240
+ newSteward: string,
241
+ newStewardId: string,
242
+ ): { ok: boolean; reason?: string } {
243
+ const manifestPath = join(councilsDir(coordRoot), `${councilId}.json`);
244
+ const manifest = readManifest(manifestPath);
245
+ if (!manifest) return { ok: false, reason: `no manifest at ${manifestPath}` };
246
+ if (manifest.status === "archived") {
247
+ return { ok: false, reason: "council is archived (read-only)" };
248
+ }
249
+ if (newSteward && !/^agent-[A-Za-z][A-Za-z0-9_-]*$/.test(newSteward)) {
250
+ return {
251
+ ok: false,
252
+ reason: `invalid steward "${newSteward}" (must match agent-[A-Za-z][A-Za-z0-9_-]*)`,
253
+ };
254
+ }
255
+ const updated: CouncilManifest = { ...manifest };
256
+ if (!newSteward) {
257
+ updated.steward = undefined;
258
+ updated.steward_id = undefined;
259
+ } else {
260
+ updated.steward = newSteward;
261
+ if (newStewardId) updated.steward_id = newStewardId;
262
+ }
263
+ atomicWriteText(manifestPath, JSON.stringify(updated, null, 2));
264
+ return { ok: true };
265
+ }