@ag-eco/agentplate-cli 0.13.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 (455) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +462 -0
  3. package/agents/ap-co-creation.md +90 -0
  4. package/agents/builder.md +144 -0
  5. package/agents/coordinator.md +377 -0
  6. package/agents/lead.md +435 -0
  7. package/agents/merger.md +164 -0
  8. package/agents/monitor.md +214 -0
  9. package/agents/orchestrator.md +239 -0
  10. package/agents/reviewer.md +140 -0
  11. package/agents/scout.md +125 -0
  12. package/agents/supervisor.md +427 -0
  13. package/package.json +66 -0
  14. package/src/agents/capabilities.test.ts +85 -0
  15. package/src/agents/capabilities.ts +125 -0
  16. package/src/agents/checkpoint.test.ts +88 -0
  17. package/src/agents/checkpoint.ts +101 -0
  18. package/src/agents/copilot-hooks-deployer.test.ts +162 -0
  19. package/src/agents/copilot-hooks-deployer.ts +93 -0
  20. package/src/agents/guard-rules.test.ts +372 -0
  21. package/src/agents/guard-rules.ts +97 -0
  22. package/src/agents/headless-mail-injector.test.ts +709 -0
  23. package/src/agents/headless-mail-injector.ts +377 -0
  24. package/src/agents/headless-prompt.test.ts +102 -0
  25. package/src/agents/headless-prompt.ts +68 -0
  26. package/src/agents/hooks-deployer.test.ts +3119 -0
  27. package/src/agents/hooks-deployer.ts +804 -0
  28. package/src/agents/identity.test.ts +604 -0
  29. package/src/agents/identity.ts +384 -0
  30. package/src/agents/lifecycle.test.ts +196 -0
  31. package/src/agents/lifecycle.ts +183 -0
  32. package/src/agents/mail-poll-detect.test.ts +153 -0
  33. package/src/agents/mail-poll-detect.ts +73 -0
  34. package/src/agents/manifest.test.ts +1026 -0
  35. package/src/agents/manifest.ts +376 -0
  36. package/src/agents/overlay.test.ts +1058 -0
  37. package/src/agents/overlay.ts +490 -0
  38. package/src/agents/scope-detect.test.ts +190 -0
  39. package/src/agents/scope-detect.ts +146 -0
  40. package/src/agents/turn-lock.test.ts +181 -0
  41. package/src/agents/turn-lock.ts +235 -0
  42. package/src/agents/turn-runner-dispatch.test.ts +182 -0
  43. package/src/agents/turn-runner-dispatch.ts +105 -0
  44. package/src/agents/turn-runner.test.ts +2312 -0
  45. package/src/agents/turn-runner.ts +1383 -0
  46. package/src/beads/client.test.ts +217 -0
  47. package/src/beads/client.ts +230 -0
  48. package/src/beads/molecules.test.ts +338 -0
  49. package/src/beads/molecules.ts +198 -0
  50. package/src/commands/agents.test.ts +328 -0
  51. package/src/commands/agents.ts +299 -0
  52. package/src/commands/clean.test.ts +797 -0
  53. package/src/commands/clean.ts +791 -0
  54. package/src/commands/completions.test.ts +348 -0
  55. package/src/commands/completions.ts +981 -0
  56. package/src/commands/coordinator.test.ts +2975 -0
  57. package/src/commands/coordinator.ts +1841 -0
  58. package/src/commands/costs.test.ts +1183 -0
  59. package/src/commands/costs.ts +599 -0
  60. package/src/commands/dashboard.test.ts +954 -0
  61. package/src/commands/dashboard.ts +1212 -0
  62. package/src/commands/discover.test.ts +288 -0
  63. package/src/commands/discover.ts +202 -0
  64. package/src/commands/doctor.test.ts +303 -0
  65. package/src/commands/doctor.ts +311 -0
  66. package/src/commands/ecosystem.test.ts +226 -0
  67. package/src/commands/ecosystem.ts +248 -0
  68. package/src/commands/errors.test.ts +654 -0
  69. package/src/commands/errors.ts +197 -0
  70. package/src/commands/feed.test.ts +709 -0
  71. package/src/commands/feed.ts +260 -0
  72. package/src/commands/group.test.ts +475 -0
  73. package/src/commands/group.ts +546 -0
  74. package/src/commands/hooks.test.ts +458 -0
  75. package/src/commands/hooks.ts +263 -0
  76. package/src/commands/init.test.ts +1011 -0
  77. package/src/commands/init.ts +967 -0
  78. package/src/commands/inspect.test.ts +1239 -0
  79. package/src/commands/inspect.ts +648 -0
  80. package/src/commands/log.test.ts +1913 -0
  81. package/src/commands/log.ts +958 -0
  82. package/src/commands/logs.test.ts +801 -0
  83. package/src/commands/logs.ts +483 -0
  84. package/src/commands/mail.test.ts +1501 -0
  85. package/src/commands/mail.ts +848 -0
  86. package/src/commands/merge.test.ts +864 -0
  87. package/src/commands/merge.ts +381 -0
  88. package/src/commands/metrics.test.ts +458 -0
  89. package/src/commands/metrics.ts +129 -0
  90. package/src/commands/monitor.test.ts +191 -0
  91. package/src/commands/monitor.ts +409 -0
  92. package/src/commands/nudge.test.ts +579 -0
  93. package/src/commands/nudge.ts +646 -0
  94. package/src/commands/orchestrator.ts +42 -0
  95. package/src/commands/prime.test.ts +612 -0
  96. package/src/commands/prime.ts +359 -0
  97. package/src/commands/replay.test.ts +757 -0
  98. package/src/commands/replay.ts +231 -0
  99. package/src/commands/run.test.ts +469 -0
  100. package/src/commands/run.ts +353 -0
  101. package/src/commands/serve/agent-actions.test.ts +210 -0
  102. package/src/commands/serve/agent-actions.ts +192 -0
  103. package/src/commands/serve/build.test.ts +202 -0
  104. package/src/commands/serve/build.ts +206 -0
  105. package/src/commands/serve/coordinator-actions.test.ts +339 -0
  106. package/src/commands/serve/coordinator-actions.ts +410 -0
  107. package/src/commands/serve/dev.test.ts +168 -0
  108. package/src/commands/serve/dev.ts +117 -0
  109. package/src/commands/serve/mail-actions.test.ts +312 -0
  110. package/src/commands/serve/mail-actions.ts +167 -0
  111. package/src/commands/serve/rest.test.ts +1680 -0
  112. package/src/commands/serve/rest.ts +1130 -0
  113. package/src/commands/serve/static.ts +51 -0
  114. package/src/commands/serve/ws.test.ts +361 -0
  115. package/src/commands/serve/ws.ts +332 -0
  116. package/src/commands/serve.test.ts +459 -0
  117. package/src/commands/serve.ts +654 -0
  118. package/src/commands/sling.test.ts +1583 -0
  119. package/src/commands/sling.ts +1351 -0
  120. package/src/commands/spec.test.ts +179 -0
  121. package/src/commands/spec.ts +105 -0
  122. package/src/commands/status.test.ts +614 -0
  123. package/src/commands/status.ts +403 -0
  124. package/src/commands/stop.test.ts +964 -0
  125. package/src/commands/stop.ts +319 -0
  126. package/src/commands/supervisor.test.ts +185 -0
  127. package/src/commands/supervisor.ts +537 -0
  128. package/src/commands/trace.test.ts +762 -0
  129. package/src/commands/trace.ts +205 -0
  130. package/src/commands/update.test.ts +466 -0
  131. package/src/commands/update.ts +263 -0
  132. package/src/commands/upgrade.test.ts +48 -0
  133. package/src/commands/upgrade.ts +240 -0
  134. package/src/commands/watch.test.ts +257 -0
  135. package/src/commands/watch.ts +308 -0
  136. package/src/commands/worktree.test.ts +1297 -0
  137. package/src/commands/worktree.ts +451 -0
  138. package/src/config.test.ts +1535 -0
  139. package/src/config.ts +1064 -0
  140. package/src/doctor/agents.test.ts +523 -0
  141. package/src/doctor/agents.ts +399 -0
  142. package/src/doctor/config-check.test.ts +191 -0
  143. package/src/doctor/config-check.ts +183 -0
  144. package/src/doctor/consistency.test.ts +807 -0
  145. package/src/doctor/consistency.ts +347 -0
  146. package/src/doctor/databases.test.ts +350 -0
  147. package/src/doctor/databases.ts +243 -0
  148. package/src/doctor/dependencies.test.ts +296 -0
  149. package/src/doctor/dependencies.ts +272 -0
  150. package/src/doctor/ecosystem.test.ts +308 -0
  151. package/src/doctor/ecosystem.ts +156 -0
  152. package/src/doctor/logs.test.ts +253 -0
  153. package/src/doctor/logs.ts +295 -0
  154. package/src/doctor/merge-queue.test.ts +315 -0
  155. package/src/doctor/merge-queue.ts +167 -0
  156. package/src/doctor/providers.test.ts +409 -0
  157. package/src/doctor/providers.ts +250 -0
  158. package/src/doctor/serve.test.ts +95 -0
  159. package/src/doctor/serve.ts +86 -0
  160. package/src/doctor/structure.test.ts +423 -0
  161. package/src/doctor/structure.ts +285 -0
  162. package/src/doctor/types.ts +43 -0
  163. package/src/doctor/version.test.ts +241 -0
  164. package/src/doctor/version.ts +132 -0
  165. package/src/doctor/watchdog.test.ts +167 -0
  166. package/src/doctor/watchdog.ts +214 -0
  167. package/src/e2e/init-sling-lifecycle.test.ts +283 -0
  168. package/src/errors.test.ts +350 -0
  169. package/src/errors.ts +217 -0
  170. package/src/events/store.test.ts +660 -0
  171. package/src/events/store.ts +369 -0
  172. package/src/events/tailer.test.ts +719 -0
  173. package/src/events/tailer.ts +332 -0
  174. package/src/events/tool-filter.test.ts +330 -0
  175. package/src/events/tool-filter.ts +126 -0
  176. package/src/index.ts +533 -0
  177. package/src/insights/analyzer.test.ts +466 -0
  178. package/src/insights/analyzer.ts +203 -0
  179. package/src/insights/quality-gates.test.ts +141 -0
  180. package/src/insights/quality-gates.ts +156 -0
  181. package/src/json.test.ts +72 -0
  182. package/src/json.ts +53 -0
  183. package/src/loam/client.test.ts +752 -0
  184. package/src/loam/client.ts +664 -0
  185. package/src/logging/color.test.ts +252 -0
  186. package/src/logging/color.ts +105 -0
  187. package/src/logging/format.test.ts +110 -0
  188. package/src/logging/format.ts +255 -0
  189. package/src/logging/logger.test.ts +814 -0
  190. package/src/logging/logger.ts +266 -0
  191. package/src/logging/reporter.test.ts +259 -0
  192. package/src/logging/reporter.ts +110 -0
  193. package/src/logging/sanitizer.test.ts +190 -0
  194. package/src/logging/sanitizer.ts +57 -0
  195. package/src/logging/theme.ts +140 -0
  196. package/src/mail/broadcast.test.ts +204 -0
  197. package/src/mail/broadcast.ts +92 -0
  198. package/src/mail/client.test.ts +774 -0
  199. package/src/mail/client.ts +236 -0
  200. package/src/mail/store.test.ts +898 -0
  201. package/src/mail/store.ts +425 -0
  202. package/src/merge/lock.test.ts +149 -0
  203. package/src/merge/lock.ts +140 -0
  204. package/src/merge/predict.test.ts +387 -0
  205. package/src/merge/predict.ts +249 -0
  206. package/src/merge/queue.test.ts +426 -0
  207. package/src/merge/queue.ts +246 -0
  208. package/src/merge/resolver.test.ts +1993 -0
  209. package/src/merge/resolver.ts +926 -0
  210. package/src/metrics/pricing.test.ts +258 -0
  211. package/src/metrics/pricing.ts +135 -0
  212. package/src/metrics/store.test.ts +978 -0
  213. package/src/metrics/store.ts +501 -0
  214. package/src/metrics/summary.test.ts +398 -0
  215. package/src/metrics/summary.ts +178 -0
  216. package/src/metrics/transcript.test.ts +483 -0
  217. package/src/metrics/transcript.ts +114 -0
  218. package/src/runtimes/__fixtures__/claude-stream-fixture.ts +22 -0
  219. package/src/runtimes/aider.test.ts +124 -0
  220. package/src/runtimes/aider.ts +147 -0
  221. package/src/runtimes/amp.test.ts +164 -0
  222. package/src/runtimes/amp.ts +154 -0
  223. package/src/runtimes/claude.test.ts +1474 -0
  224. package/src/runtimes/claude.ts +579 -0
  225. package/src/runtimes/codex.test.ts +805 -0
  226. package/src/runtimes/codex.ts +273 -0
  227. package/src/runtimes/connections.test.ts +214 -0
  228. package/src/runtimes/connections.ts +103 -0
  229. package/src/runtimes/copilot.test.ts +707 -0
  230. package/src/runtimes/copilot.ts +316 -0
  231. package/src/runtimes/cursor.test.ts +497 -0
  232. package/src/runtimes/cursor.ts +205 -0
  233. package/src/runtimes/gemini.test.ts +537 -0
  234. package/src/runtimes/gemini.ts +243 -0
  235. package/src/runtimes/goose.test.ts +133 -0
  236. package/src/runtimes/goose.ts +157 -0
  237. package/src/runtimes/headless-connection.test.ts +264 -0
  238. package/src/runtimes/headless-connection.ts +158 -0
  239. package/src/runtimes/opencode.test.ts +325 -0
  240. package/src/runtimes/opencode.ts +188 -0
  241. package/src/runtimes/pi-guards.test.ts +486 -0
  242. package/src/runtimes/pi-guards.ts +367 -0
  243. package/src/runtimes/pi.test.ts +789 -0
  244. package/src/runtimes/pi.ts +305 -0
  245. package/src/runtimes/registry.test.ts +196 -0
  246. package/src/runtimes/registry.ts +99 -0
  247. package/src/runtimes/sapling.test.ts +1267 -0
  248. package/src/runtimes/sapling.ts +710 -0
  249. package/src/runtimes/types.ts +266 -0
  250. package/src/schema-consistency.test.ts +246 -0
  251. package/src/sessions/compat.test.ts +281 -0
  252. package/src/sessions/compat.ts +105 -0
  253. package/src/sessions/store.test.ts +1748 -0
  254. package/src/sessions/store.ts +858 -0
  255. package/src/test-helpers.test.ts +124 -0
  256. package/src/test-helpers.ts +145 -0
  257. package/src/test-setup.test.ts +31 -0
  258. package/src/test-setup.ts +28 -0
  259. package/src/tools/loam/api.ts +368 -0
  260. package/src/tools/loam/cli.ts +278 -0
  261. package/src/tools/loam/commands/add.ts +52 -0
  262. package/src/tools/loam/commands/archive.ts +214 -0
  263. package/src/tools/loam/commands/audit.ts +276 -0
  264. package/src/tools/loam/commands/compact.ts +1062 -0
  265. package/src/tools/loam/commands/completions.ts +79 -0
  266. package/src/tools/loam/commands/config.ts +381 -0
  267. package/src/tools/loam/commands/delete-domain.ts +121 -0
  268. package/src/tools/loam/commands/delete.ts +316 -0
  269. package/src/tools/loam/commands/diff.ts +200 -0
  270. package/src/tools/loam/commands/doctor.ts +1113 -0
  271. package/src/tools/loam/commands/edit.ts +226 -0
  272. package/src/tools/loam/commands/init.ts +31 -0
  273. package/src/tools/loam/commands/learn.ts +179 -0
  274. package/src/tools/loam/commands/move.ts +323 -0
  275. package/src/tools/loam/commands/onboard.ts +374 -0
  276. package/src/tools/loam/commands/outcome.ts +185 -0
  277. package/src/tools/loam/commands/prime.ts +688 -0
  278. package/src/tools/loam/commands/prune.ts +614 -0
  279. package/src/tools/loam/commands/query.ts +218 -0
  280. package/src/tools/loam/commands/rank.ts +180 -0
  281. package/src/tools/loam/commands/ready.ts +189 -0
  282. package/src/tools/loam/commands/record.ts +1210 -0
  283. package/src/tools/loam/commands/restore.ts +166 -0
  284. package/src/tools/loam/commands/search.ts +327 -0
  285. package/src/tools/loam/commands/setup.ts +887 -0
  286. package/src/tools/loam/commands/status.ts +103 -0
  287. package/src/tools/loam/commands/sync.ts +298 -0
  288. package/src/tools/loam/commands/update.ts +19 -0
  289. package/src/tools/loam/commands/upgrade.ts +93 -0
  290. package/src/tools/loam/commands/validate.ts +190 -0
  291. package/src/tools/loam/index.ts +62 -0
  292. package/src/tools/loam/log.ts +127 -0
  293. package/src/tools/loam/registry/builtins.ts +409 -0
  294. package/src/tools/loam/registry/custom.ts +431 -0
  295. package/src/tools/loam/registry/init.ts +55 -0
  296. package/src/tools/loam/registry/template.ts +40 -0
  297. package/src/tools/loam/registry/type-registry.ts +113 -0
  298. package/src/tools/loam/schemas/config-schema.ts +489 -0
  299. package/src/tools/loam/schemas/config.ts +245 -0
  300. package/src/tools/loam/schemas/index.ts +18 -0
  301. package/src/tools/loam/schemas/record-schema.ts +191 -0
  302. package/src/tools/loam/schemas/record.ts +115 -0
  303. package/src/tools/loam/utils/active-work.ts +205 -0
  304. package/src/tools/loam/utils/anchor-validity.ts +80 -0
  305. package/src/tools/loam/utils/archive.ts +146 -0
  306. package/src/tools/loam/utils/audit.ts +667 -0
  307. package/src/tools/loam/utils/bm25.ts +238 -0
  308. package/src/tools/loam/utils/budget.ts +142 -0
  309. package/src/tools/loam/utils/config.ts +344 -0
  310. package/src/tools/loam/utils/dir-anchors.ts +62 -0
  311. package/src/tools/loam/utils/domain-rules.ts +114 -0
  312. package/src/tools/loam/utils/expertise.ts +393 -0
  313. package/src/tools/loam/utils/format-helpers.ts +96 -0
  314. package/src/tools/loam/utils/format.ts +1234 -0
  315. package/src/tools/loam/utils/git-context.ts +50 -0
  316. package/src/tools/loam/utils/git.ts +183 -0
  317. package/src/tools/loam/utils/hooks.ts +299 -0
  318. package/src/tools/loam/utils/index.ts +52 -0
  319. package/src/tools/loam/utils/json-output.ts +13 -0
  320. package/src/tools/loam/utils/lock.ts +76 -0
  321. package/src/tools/loam/utils/markers.ts +48 -0
  322. package/src/tools/loam/utils/numeric-flags.ts +20 -0
  323. package/src/tools/loam/utils/palette.ts +44 -0
  324. package/src/tools/loam/utils/prime-ranking.ts +135 -0
  325. package/src/tools/loam/utils/recipe-discovery.ts +195 -0
  326. package/src/tools/loam/utils/runtime-flags.ts +28 -0
  327. package/src/tools/loam/utils/scoring.ts +94 -0
  328. package/src/tools/loam/utils/version.ts +116 -0
  329. package/src/tools/sprout/commands/block.ts +64 -0
  330. package/src/tools/sprout/commands/blocked.ts +86 -0
  331. package/src/tools/sprout/commands/close.ts +129 -0
  332. package/src/tools/sprout/commands/completions.ts +198 -0
  333. package/src/tools/sprout/commands/config.ts +238 -0
  334. package/src/tools/sprout/commands/create.ts +164 -0
  335. package/src/tools/sprout/commands/dep.ts +148 -0
  336. package/src/tools/sprout/commands/doctor.ts +979 -0
  337. package/src/tools/sprout/commands/init.ts +83 -0
  338. package/src/tools/sprout/commands/label.ts +178 -0
  339. package/src/tools/sprout/commands/list.ts +210 -0
  340. package/src/tools/sprout/commands/migrate.ts +133 -0
  341. package/src/tools/sprout/commands/onboard.ts +207 -0
  342. package/src/tools/sprout/commands/plan-show.ts +278 -0
  343. package/src/tools/sprout/commands/plan.ts +2526 -0
  344. package/src/tools/sprout/commands/prime.ts +399 -0
  345. package/src/tools/sprout/commands/ready.ts +245 -0
  346. package/src/tools/sprout/commands/search.ts +221 -0
  347. package/src/tools/sprout/commands/show.ts +277 -0
  348. package/src/tools/sprout/commands/stats.ts +146 -0
  349. package/src/tools/sprout/commands/sync.ts +134 -0
  350. package/src/tools/sprout/commands/tpl.ts +364 -0
  351. package/src/tools/sprout/commands/unblock.ts +115 -0
  352. package/src/tools/sprout/commands/update.ts +257 -0
  353. package/src/tools/sprout/commands/upgrade.ts +91 -0
  354. package/src/tools/sprout/config-schema.ts +152 -0
  355. package/src/tools/sprout/config.ts +355 -0
  356. package/src/tools/sprout/filter.ts +107 -0
  357. package/src/tools/sprout/format.ts +43 -0
  358. package/src/tools/sprout/id.ts +22 -0
  359. package/src/tools/sprout/index.ts +204 -0
  360. package/src/tools/sprout/log.ts +76 -0
  361. package/src/tools/sprout/markers.ts +22 -0
  362. package/src/tools/sprout/output.ts +121 -0
  363. package/src/tools/sprout/plan-backref.ts +93 -0
  364. package/src/tools/sprout/plan-context.ts +81 -0
  365. package/src/tools/sprout/plan-domain.ts +139 -0
  366. package/src/tools/sprout/plan-lifecycle.ts +65 -0
  367. package/src/tools/sprout/plan-loam.ts +207 -0
  368. package/src/tools/sprout/plan-schema.ts +209 -0
  369. package/src/tools/sprout/sort.ts +31 -0
  370. package/src/tools/sprout/store.ts +172 -0
  371. package/src/tools/sprout/types.ts +118 -0
  372. package/src/tools/sprout/validation.ts +119 -0
  373. package/src/tools/sprout/version.ts +1 -0
  374. package/src/tools/sprout/yaml.ts +387 -0
  375. package/src/tools/trellis/commands/archive.ts +87 -0
  376. package/src/tools/trellis/commands/completions.ts +610 -0
  377. package/src/tools/trellis/commands/config.ts +382 -0
  378. package/src/tools/trellis/commands/create.ts +252 -0
  379. package/src/tools/trellis/commands/diff.ts +150 -0
  380. package/src/tools/trellis/commands/doctor.ts +771 -0
  381. package/src/tools/trellis/commands/emit.ts +365 -0
  382. package/src/tools/trellis/commands/history.ts +83 -0
  383. package/src/tools/trellis/commands/import.ts +198 -0
  384. package/src/tools/trellis/commands/init.ts +81 -0
  385. package/src/tools/trellis/commands/list.ts +103 -0
  386. package/src/tools/trellis/commands/onboard.ts +156 -0
  387. package/src/tools/trellis/commands/pin.ts +172 -0
  388. package/src/tools/trellis/commands/prime.ts +193 -0
  389. package/src/tools/trellis/commands/render.ts +122 -0
  390. package/src/tools/trellis/commands/schema.ts +353 -0
  391. package/src/tools/trellis/commands/show.ts +115 -0
  392. package/src/tools/trellis/commands/stats.ts +65 -0
  393. package/src/tools/trellis/commands/sync.ts +112 -0
  394. package/src/tools/trellis/commands/tree.ts +123 -0
  395. package/src/tools/trellis/commands/update.ts +330 -0
  396. package/src/tools/trellis/commands/upgrade.ts +95 -0
  397. package/src/tools/trellis/commands/validate.ts +166 -0
  398. package/src/tools/trellis/config-schema.ts +81 -0
  399. package/src/tools/trellis/config.ts +108 -0
  400. package/src/tools/trellis/frontmatter.ts +348 -0
  401. package/src/tools/trellis/id.ts +24 -0
  402. package/src/tools/trellis/index.ts +209 -0
  403. package/src/tools/trellis/markers.ts +28 -0
  404. package/src/tools/trellis/output.ts +84 -0
  405. package/src/tools/trellis/render.ts +212 -0
  406. package/src/tools/trellis/store.ts +144 -0
  407. package/src/tools/trellis/types.ts +82 -0
  408. package/src/tools/trellis/validate.ts +199 -0
  409. package/src/tools/trellis/yaml.ts +309 -0
  410. package/src/tracker/beads.test.ts +454 -0
  411. package/src/tracker/beads.ts +56 -0
  412. package/src/tracker/factory.test.ts +90 -0
  413. package/src/tracker/factory.ts +65 -0
  414. package/src/tracker/sprout.test.ts +461 -0
  415. package/src/tracker/sprout.ts +182 -0
  416. package/src/tracker/types.ts +52 -0
  417. package/src/trellis/client.test.ts +107 -0
  418. package/src/trellis/client.ts +179 -0
  419. package/src/types.ts +970 -0
  420. package/src/utils/bin.test.ts +10 -0
  421. package/src/utils/bin.ts +37 -0
  422. package/src/utils/browser.test.ts +49 -0
  423. package/src/utils/browser.ts +48 -0
  424. package/src/utils/fs.test.ts +119 -0
  425. package/src/utils/fs.ts +62 -0
  426. package/src/utils/pid.test.ts +152 -0
  427. package/src/utils/pid.ts +130 -0
  428. package/src/utils/process-scan.test.ts +53 -0
  429. package/src/utils/process-scan.ts +76 -0
  430. package/src/utils/time.test.ts +43 -0
  431. package/src/utils/time.ts +37 -0
  432. package/src/utils/version.test.ts +33 -0
  433. package/src/utils/version.ts +70 -0
  434. package/src/version.ts +5 -0
  435. package/src/watchdog/daemon.test.ts +3721 -0
  436. package/src/watchdog/daemon.ts +1257 -0
  437. package/src/watchdog/health.test.ts +830 -0
  438. package/src/watchdog/health.ts +434 -0
  439. package/src/watchdog/triage.test.ts +205 -0
  440. package/src/watchdog/triage.ts +205 -0
  441. package/src/worktree/manager.test.ts +720 -0
  442. package/src/worktree/manager.ts +405 -0
  443. package/src/worktree/process.test.ts +172 -0
  444. package/src/worktree/process.ts +131 -0
  445. package/src/worktree/tmux.test.ts +1616 -0
  446. package/src/worktree/tmux.ts +721 -0
  447. package/templates/CLAUDE.md.tmpl +100 -0
  448. package/templates/copilot-hooks.json.tmpl +13 -0
  449. package/templates/hooks.json.tmpl +109 -0
  450. package/templates/overlay.md.tmpl +88 -0
  451. package/ui/dist/apple-touch-icon-bdy6teep.png +0 -0
  452. package/ui/dist/chunk-8s31f05k.css +1 -0
  453. package/ui/dist/chunk-vm5rz679.js +300 -0
  454. package/ui/dist/favicon-nzb39vza.svg +4 -0
  455. package/ui/dist/index.html +17 -0
@@ -0,0 +1,377 @@
1
+ /**
2
+ * Server-side mail dispatcher for spawn-per-turn headless agents.
3
+ *
4
+ * In tmux mode, the UserPromptSubmit hook fires `ap mail check --inject` before
5
+ * each prompt, delivering new mail to the agent. In headless spawn-per-turn
6
+ * mode there is no persistent process — `ap serve` polls the mail store and,
7
+ * when unread mail appears for an agent, drives a fresh `runTurn` that spawns
8
+ * claude with `--resume <session-id>`, writes the batched user turn to a real
9
+ * stdin pipe, and exits when claude does.
10
+ *
11
+ * This module exports `startTurnRunnerMailLoop` (the dispatcher loop) and
12
+ * `_runTurnRunnerTick` (a single-tick variant for deterministic tests).
13
+ *
14
+ * State authority (agentplate-3087): this module does NOT write session state.
15
+ * The turn-runner (`src/agents/turn-runner.ts`) is the sole authority for
16
+ * `in_turn` ↔ `between_turns` transitions — it writes `in_turn` on the first
17
+ * parser event of a turn and settles to `between_turns` at end-of-turn when
18
+ * the agent did not deliver a terminal mail. Adding a duplicate writer here
19
+ * would race with the turn-runner under the per-agent turn lock and make
20
+ * the substate non-deterministic.
21
+ */
22
+
23
+ import { createMailStore } from "../mail/store.ts";
24
+ import type { MailMessage } from "../types.ts";
25
+ import { encodeUserTurn } from "./headless-prompt.ts";
26
+ import type { RunTurnOpts, TurnResult } from "./turn-runner.ts";
27
+
28
+ /**
29
+ * Escape characters that would otherwise corrupt the `[MAIL] From: ... | Subject: ... |
30
+ * Priority: ...\n\n<body>` framing. `|` is the field delimiter and `\n\n` separates
31
+ * metadata from body, so an unescaped pipe or newline in a metadata value would let a
32
+ * crafted subject inject a fake field or smuggle a fake body. Backslash is escaped
33
+ * first so the escape sequence itself is unambiguous (agentplate-2231).
34
+ */
35
+ function escapeMailMetadata(value: string): string {
36
+ return value
37
+ .replace(/\\/g, "\\\\")
38
+ .replace(/\|/g, "\\|")
39
+ .replace(/\r/g, "\\r")
40
+ .replace(/\n/g, "\\n");
41
+ }
42
+
43
+ /**
44
+ * Format a batch of unread messages into the user-turn text the agent receives.
45
+ * Metadata values are escaped so a hostile or human-authored subject can't break
46
+ * the line framing.
47
+ */
48
+ export function formatMailBatch(messages: readonly MailMessage[]): string {
49
+ return messages
50
+ .map(
51
+ (m) =>
52
+ `[MAIL] From: ${escapeMailMetadata(m.from)} | Subject: ${escapeMailMetadata(
53
+ m.subject,
54
+ )} | Priority: ${escapeMailMetadata(m.priority)}\n\n${m.body}`,
55
+ )
56
+ .join("\n\n---\n\n");
57
+ }
58
+
59
+ /**
60
+ * Build the runTurn opts for delivering a user turn (Phase 2 builder dispatcher).
61
+ *
62
+ * The injector polls mail for a single agent and only knows the agent name,
63
+ * the user-turn payload, and the mail database path. The remaining fields
64
+ * (worktree path, runtime, model, run id, etc.) are provided by the caller
65
+ * (typically `ap serve`) once at install time. This factory produces a
66
+ * `RunTurnOpts` for each batch by combining the static caller-provided
67
+ * fields with the per-batch payload.
68
+ */
69
+ export type TurnRunnerOptsFactory = (userTurnNdjson: string) => RunTurnOpts;
70
+
71
+ /** Function that drives a single agent turn end-to-end. Production passes `runTurn`. */
72
+ export type TurnRunnerFn = (opts: RunTurnOpts) => Promise<TurnResult>;
73
+
74
+ /**
75
+ * Outcome of a single dispatcher tick. Returned for testability so callers
76
+ * can assert delivery behavior without inspecting the runner internals.
77
+ */
78
+ export type TurnRunnerTickResult =
79
+ | { kind: "idle" }
80
+ | { kind: "in-flight" }
81
+ | { kind: "delivered"; result: TurnResult; messageIds: string[] }
82
+ | { kind: "error"; error: unknown; messageIds: string[] };
83
+
84
+ /**
85
+ * Start a server-side mail dispatcher that drives the spawn-per-turn engine.
86
+ *
87
+ * Phase 2 builder path. Polls the mail store every intervalMs milliseconds,
88
+ * batches unread messages into a single stream-json user turn, and invokes
89
+ * `runTurn(...)` to spawn one claude turn that consumes them. While a turn
90
+ * is in flight, subsequent ticks short-circuit — they never spawn a second
91
+ * claude process for the same agent. Per-agent serialization is also enforced
92
+ * cross-process by the turn-lock inside `runTurn`.
93
+ *
94
+ * Mark-as-read happens AFTER the runTurn returns successfully (`exitCode === 0`
95
+ * and no thrown error). On any failure, messages remain unread and will be
96
+ * retried on the next tick.
97
+ *
98
+ * @param agentName - Agentplate agent name (mail inbox address)
99
+ * @param optsFactory - Builds the RunTurnOpts from the per-batch user turn payload
100
+ * @param runTurnFn - Function that drives one turn (typically `runTurn` from turn-runner.ts)
101
+ * @param mailStorePath - Absolute path to the project's mail.db
102
+ * @param intervalMs - Poll interval in milliseconds (default: 2000)
103
+ * @param isAgentLive - Optional per-tick predicate. When provided and it returns
104
+ * false, the loop short-circuits (no mail dispatch) and self-terminates.
105
+ * This closes the gap between `ap stop` writing state=completed and the
106
+ * serve.ts rescan timer reaping this loop, which would otherwise keep
107
+ * ticking and dispatch a new turn against a stopped agent (agentplate-eb7c).
108
+ * @returns Cleanup function that stops the dispatcher
109
+ */
110
+ export function startTurnRunnerMailLoop(
111
+ agentName: string,
112
+ optsFactory: TurnRunnerOptsFactory,
113
+ runTurnFn: TurnRunnerFn,
114
+ mailStorePath: string,
115
+ intervalMs = 2000,
116
+ isAgentLive?: () => boolean,
117
+ ): () => void {
118
+ let stopped = false;
119
+ let inFlight = false;
120
+ let timer: ReturnType<typeof setInterval> | null = null;
121
+
122
+ const stop = (): void => {
123
+ stopped = true;
124
+ if (timer !== null) {
125
+ clearInterval(timer);
126
+ timer = null;
127
+ }
128
+ };
129
+
130
+ const tick = async (): Promise<TurnRunnerTickResult> => {
131
+ if (stopped) return { kind: "idle" };
132
+ if (inFlight) return { kind: "in-flight" };
133
+ // Per-tick state guard. `ap stop` flips state=completed and kills the
134
+ // in-flight claude, but until the rescan reaps this loop the next tick
135
+ // would otherwise dispatch a fresh turn against the stopped agent.
136
+ if (isAgentLive && !isAgentLive()) {
137
+ stop();
138
+ return { kind: "idle" };
139
+ }
140
+ const store = createMailStore(mailStorePath);
141
+ let messages: ReturnType<typeof store.getUnread>;
142
+ try {
143
+ messages = store.getUnread(agentName);
144
+ } finally {
145
+ store.close();
146
+ }
147
+ if (messages.length === 0) return { kind: "idle" };
148
+
149
+ const userTurnNdjson = encodeUserTurn(formatMailBatch(messages));
150
+ const ids = messages.map((m) => m.id);
151
+
152
+ inFlight = true;
153
+ try {
154
+ const result = await runTurnFn(optsFactory(userTurnNdjson));
155
+ // Mark read only on a clean turn — exit code 0 (or null on abort with
156
+ // no error) AND no thrown error. Failed turns leave messages unread
157
+ // so the next tick retries cleanly.
158
+ if (result.exitCode === 0) {
159
+ const markStore = createMailStore(mailStorePath);
160
+ try {
161
+ for (const id of ids) markStore.markRead(id);
162
+ } finally {
163
+ markStore.close();
164
+ }
165
+ }
166
+ return { kind: "delivered", result, messageIds: ids };
167
+ } catch (error) {
168
+ return { kind: "error", error, messageIds: ids };
169
+ } finally {
170
+ inFlight = false;
171
+ }
172
+ };
173
+
174
+ timer = setInterval(() => {
175
+ // Errors and rejections are absorbed inside tick; this layer just
176
+ // prevents an unhandled-rejection if tick itself throws synchronously.
177
+ tick().catch(() => {});
178
+ }, intervalMs);
179
+
180
+ return stop;
181
+ }
182
+
183
+ /**
184
+ * Mail dispatcher for persistent headless agents (coordinator/orchestrator/monitor).
185
+ *
186
+ * Persistent capabilities run as a single long-lived claude process — unlike
187
+ * task-scoped workers, there is no spawn-per-turn engine to reach them. Mail
188
+ * delivery instead writes directly to the live process's stdin via the
189
+ * registered HeadlessClaudeConnection. Without this loop, mail sent to a
190
+ * headless coordinator (e.g. operator messages from `ap serve`'s UI, worker
191
+ * `worker_done`/`merge_ready` mails) lands in `mail.db` but is never seen by
192
+ * the agent — `installMailInjectors`'s spawn-per-turn loop filters out
193
+ * persistent capabilities (agentplate-b03a).
194
+ *
195
+ * The loop polls every `intervalMs` ms. Each tick:
196
+ * 1. Resolve the live `RuntimeConnection` via `getConn(agentName)`. If the
197
+ * connection has been removed (agent stopped), self-terminate.
198
+ * 2. If `isAgentLive()` reports terminal state, self-terminate.
199
+ * 3. Read unread mail for the agent. If empty, return idle.
200
+ * 4. Format the batch as a stream-json user turn, write to the connection
201
+ * via `followUp()`, then mark messages read.
202
+ *
203
+ * The write is best-effort: Claude Code may not pick up stdin until the
204
+ * current model turn finishes (see `HeadlessClaudeConnection.nudge` notes),
205
+ * but the data sits in the pipe buffer and gets consumed at the next turn
206
+ * boundary. Marking-read happens unconditionally after a successful write —
207
+ * the agent processes batches in arrival order and we do not want to keep
208
+ * re-injecting the same messages on every tick.
209
+ */
210
+ export interface PersistentMailLoopDeps {
211
+ /** Resolve the live connection (returns undefined when none registered). */
212
+ getConn: (agentName: string) => { followUp(text: string): Promise<void> } | undefined;
213
+ /** Optional liveness predicate; when false, the loop stops itself. */
214
+ isAgentLive?: () => boolean;
215
+ /** Poll interval in ms. Default: 2000. */
216
+ intervalMs?: number;
217
+ }
218
+
219
+ export type PersistentMailTickResult =
220
+ | { kind: "idle" }
221
+ | { kind: "no-connection" }
222
+ | { kind: "agent-stopped" }
223
+ | { kind: "delivered"; messageIds: string[] }
224
+ | { kind: "error"; error: unknown; messageIds: string[] };
225
+
226
+ /**
227
+ * Start the persistent-mail dispatcher for one agent. Returns the stop function.
228
+ * The caller owns the connection lifecycle — when the agent terminates, the
229
+ * caller is responsible for invoking the returned stop function (or letting
230
+ * the `isAgentLive` predicate self-terminate the loop).
231
+ */
232
+ export function startPersistentMailLoop(
233
+ agentName: string,
234
+ mailStorePath: string,
235
+ deps: PersistentMailLoopDeps,
236
+ ): () => void {
237
+ let stopped = false;
238
+ let inFlight = false;
239
+ let timer: ReturnType<typeof setInterval> | null = null;
240
+ const intervalMs = deps.intervalMs ?? 2000;
241
+
242
+ const stop = (): void => {
243
+ stopped = true;
244
+ if (timer !== null) {
245
+ clearInterval(timer);
246
+ timer = null;
247
+ }
248
+ };
249
+
250
+ const tick = async (): Promise<PersistentMailTickResult> => {
251
+ if (stopped) return { kind: "idle" };
252
+ if (inFlight) return { kind: "idle" };
253
+ if (deps.isAgentLive && !deps.isAgentLive()) {
254
+ stop();
255
+ return { kind: "agent-stopped" };
256
+ }
257
+ const conn = deps.getConn(agentName);
258
+ if (conn === undefined) {
259
+ // Connection dropped — caller will reap us via session rescan, but
260
+ // short-circuit until then so we don't spin reading mail we can't
261
+ // deliver.
262
+ return { kind: "no-connection" };
263
+ }
264
+
265
+ const store = createMailStore(mailStorePath);
266
+ let messages: ReturnType<typeof store.getUnread>;
267
+ try {
268
+ messages = store.getUnread(agentName);
269
+ } finally {
270
+ store.close();
271
+ }
272
+ if (messages.length === 0) return { kind: "idle" };
273
+
274
+ const userTurn = encodeUserTurn(formatMailBatch(messages));
275
+ const ids = messages.map((m) => m.id);
276
+
277
+ inFlight = true;
278
+ try {
279
+ await conn.followUp(userTurn);
280
+ const markStore = createMailStore(mailStorePath);
281
+ try {
282
+ for (const id of ids) markStore.markRead(id);
283
+ } finally {
284
+ markStore.close();
285
+ }
286
+ return { kind: "delivered", messageIds: ids };
287
+ } catch (error) {
288
+ return { kind: "error", error, messageIds: ids };
289
+ } finally {
290
+ inFlight = false;
291
+ }
292
+ };
293
+
294
+ timer = setInterval(() => {
295
+ tick().catch(() => {});
296
+ }, intervalMs);
297
+
298
+ return stop;
299
+ }
300
+
301
+ /**
302
+ * Internal: single-tick variant of `startPersistentMailLoop` for deterministic tests.
303
+ */
304
+ export async function _runPersistentMailTick(
305
+ agentName: string,
306
+ mailStorePath: string,
307
+ deps: PersistentMailLoopDeps,
308
+ ): Promise<PersistentMailTickResult> {
309
+ if (deps.isAgentLive && !deps.isAgentLive()) {
310
+ return { kind: "agent-stopped" };
311
+ }
312
+ const conn = deps.getConn(agentName);
313
+ if (conn === undefined) return { kind: "no-connection" };
314
+
315
+ const store = createMailStore(mailStorePath);
316
+ let messages: ReturnType<typeof store.getUnread>;
317
+ try {
318
+ messages = store.getUnread(agentName);
319
+ } finally {
320
+ store.close();
321
+ }
322
+ if (messages.length === 0) return { kind: "idle" };
323
+
324
+ const userTurn = encodeUserTurn(formatMailBatch(messages));
325
+ const ids = messages.map((m) => m.id);
326
+
327
+ try {
328
+ await conn.followUp(userTurn);
329
+ const markStore = createMailStore(mailStorePath);
330
+ try {
331
+ for (const id of ids) markStore.markRead(id);
332
+ } finally {
333
+ markStore.close();
334
+ }
335
+ return { kind: "delivered", messageIds: ids };
336
+ } catch (error) {
337
+ return { kind: "error", error, messageIds: ids };
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Internal: run a single dispatcher tick. Exported for tests so they can
343
+ * drive the loop deterministically without setInterval timing.
344
+ */
345
+ export async function _runTurnRunnerTick(
346
+ agentName: string,
347
+ optsFactory: TurnRunnerOptsFactory,
348
+ runTurnFn: TurnRunnerFn,
349
+ mailStorePath: string,
350
+ ): Promise<TurnRunnerTickResult> {
351
+ const store = createMailStore(mailStorePath);
352
+ let messages: ReturnType<typeof store.getUnread>;
353
+ try {
354
+ messages = store.getUnread(agentName);
355
+ } finally {
356
+ store.close();
357
+ }
358
+ if (messages.length === 0) return { kind: "idle" };
359
+
360
+ const userTurnNdjson = encodeUserTurn(formatMailBatch(messages));
361
+ const ids = messages.map((m) => m.id);
362
+
363
+ try {
364
+ const result = await runTurnFn(optsFactory(userTurnNdjson));
365
+ if (result.exitCode === 0) {
366
+ const markStore = createMailStore(mailStorePath);
367
+ try {
368
+ for (const id of ids) markStore.markRead(id);
369
+ } finally {
370
+ markStore.close();
371
+ }
372
+ }
373
+ return { kind: "delivered", result, messageIds: ids };
374
+ } catch (error) {
375
+ return { kind: "error", error, messageIds: ids };
376
+ }
377
+ }
@@ -0,0 +1,102 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import {
3
+ buildInitialHeadlessPrompt,
4
+ encodeUserTurn,
5
+ formatMailSection,
6
+ } from "./headless-prompt.ts";
7
+
8
+ describe("encodeUserTurn", () => {
9
+ test("produces a valid NDJSON line", () => {
10
+ const line = encodeUserTurn("hello world");
11
+ expect(line).toEndWith("\n");
12
+ const parsed = JSON.parse(line.trim());
13
+ expect(parsed.type).toBe("user");
14
+ expect(parsed.message.role).toBe("user");
15
+ expect(parsed.message.content).toHaveLength(1);
16
+ expect(parsed.message.content[0].type).toBe("text");
17
+ expect(parsed.message.content[0].text).toBe("hello world");
18
+ });
19
+
20
+ test("handles multi-line text", () => {
21
+ const text = "line one\nline two\nline three";
22
+ const line = encodeUserTurn(text);
23
+ const parsed = JSON.parse(line.trim());
24
+ expect(parsed.message.content[0].text).toBe(text);
25
+ });
26
+ });
27
+
28
+ describe("formatMailSection", () => {
29
+ test("returns empty string for no messages", () => {
30
+ expect(formatMailSection([])).toBe("");
31
+ });
32
+
33
+ test("formats a single message", () => {
34
+ const result = formatMailSection([
35
+ { from: "coordinator", subject: "dispatch", priority: "normal", body: "Start working." },
36
+ ]);
37
+ expect(result).toContain("[MAIL] From: coordinator");
38
+ expect(result).toContain("Subject: dispatch");
39
+ expect(result).toContain("Start working.");
40
+ });
41
+
42
+ test("separates multiple messages with dividers", () => {
43
+ const result = formatMailSection([
44
+ { from: "lead", subject: "task-1", priority: "high", body: "First task." },
45
+ { from: "orchestrator", subject: "context", priority: "low", body: "Extra context." },
46
+ ]);
47
+ expect(result).toContain("---");
48
+ expect(result).toContain("First task.");
49
+ expect(result).toContain("Extra context.");
50
+ });
51
+ });
52
+
53
+ describe("buildInitialHeadlessPrompt", () => {
54
+ test("combines all three sections", () => {
55
+ const result = buildInitialHeadlessPrompt(
56
+ "## Prime Context\nExpertise here.",
57
+ "[MAIL] From: orchestrator | Subject: dispatch\n\nDo the thing.",
58
+ "Read your overlay and begin immediately.",
59
+ );
60
+ const parsed = JSON.parse(result.trim());
61
+ const text: string = parsed.message.content[0].text;
62
+ expect(text).toContain("Prime Context");
63
+ expect(text).toContain("[MAIL]");
64
+ expect(text).toContain("Read your overlay and begin immediately.");
65
+ expect(text).toContain("---");
66
+ });
67
+
68
+ test("omits primeContext when undefined", () => {
69
+ const result = buildInitialHeadlessPrompt(
70
+ undefined,
71
+ "[MAIL] From: lead | Subject: dispatch\n\nTask body.",
72
+ "Begin.",
73
+ );
74
+ const parsed = JSON.parse(result.trim());
75
+ const text: string = parsed.message.content[0].text;
76
+ expect(text).not.toContain("Prime Context");
77
+ expect(text).toContain("[MAIL]");
78
+ expect(text).toContain("Begin.");
79
+ });
80
+
81
+ test("omits dispatchMail when undefined", () => {
82
+ const result = buildInitialHeadlessPrompt("## Prime Context", undefined, "Begin.");
83
+ const parsed = JSON.parse(result.trim());
84
+ const text: string = parsed.message.content[0].text;
85
+ expect(text).toContain("Prime Context");
86
+ expect(text).not.toContain("[MAIL]");
87
+ expect(text).toContain("Begin.");
88
+ });
89
+
90
+ test("always includes beacon even when other sections are empty", () => {
91
+ const result = buildInitialHeadlessPrompt(undefined, undefined, "Start now.");
92
+ const parsed = JSON.parse(result.trim());
93
+ const text: string = parsed.message.content[0].text;
94
+ expect(text).toBe("Start now.");
95
+ });
96
+
97
+ test("output is valid NDJSON ending with newline", () => {
98
+ const result = buildInitialHeadlessPrompt("ctx", "mail", "go");
99
+ expect(result).toEndWith("\n");
100
+ expect(() => JSON.parse(result.trim())).not.toThrow();
101
+ });
102
+ });
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Build the initial stdin prompt for a headless Claude Code agent.
3
+ *
4
+ * In headless mode (--input-format stream-json), the orchestrator owns stdin.
5
+ * Rather than relying on SessionStart hooks (which don't fire in headless mode),
6
+ * the orchestrator writes the prime context, pending dispatch mail, and activation
7
+ * beacon as the agent's first user turn immediately after spawn.
8
+ */
9
+
10
+ /**
11
+ * Encode text as a stream-json user turn for Claude Code's --input-format stream-json.
12
+ *
13
+ * Format matches the Claude Code headless stdin protocol:
14
+ * {"type":"user","message":{"role":"user","content":[{"type":"text","text":"..."}]}}
15
+ */
16
+ export function encodeUserTurn(text: string): string {
17
+ const message = {
18
+ type: "user",
19
+ message: { role: "user", content: [{ type: "text", text }] },
20
+ };
21
+ return `${JSON.stringify(message)}\n`;
22
+ }
23
+
24
+ /**
25
+ * Build the initial stdin prompt for a headless Claude agent.
26
+ *
27
+ * Combines prime context (loam expertise, session state), pending dispatch mail,
28
+ * and the activation beacon into a single user turn. Replaces the SessionStart
29
+ * hook equivalents (ap prime + ap mail check --inject) for headless agents.
30
+ *
31
+ * Sections are separated by "---" dividers. Empty sections are omitted.
32
+ *
33
+ * @param primeContext - Output of `ap prime --agent <name>` (may be empty/undefined)
34
+ * @param dispatchMail - Pre-formatted dispatch mail body (may be empty/undefined)
35
+ * @param beacon - Activation phrase sent via tmux send-keys in interactive mode
36
+ * @returns NDJSON line ready to write to the agent's stdin
37
+ */
38
+ export function buildInitialHeadlessPrompt(
39
+ primeContext: string | undefined,
40
+ dispatchMail: string | undefined,
41
+ beacon: string,
42
+ ): string {
43
+ const parts: string[] = [];
44
+ if (primeContext) parts.push(primeContext);
45
+ if (dispatchMail) parts.push(dispatchMail);
46
+ parts.push(beacon);
47
+
48
+ const text = parts.join("\n\n---\n\n");
49
+ return encodeUserTurn(text);
50
+ }
51
+
52
+ /**
53
+ * Format a list of pending mail messages as a dispatch mail section.
54
+ *
55
+ * Used to inline pending inbox messages into the initial stdin prompt so
56
+ * the agent starts with all pre-dispatch mail already in context.
57
+ */
58
+ export function formatMailSection(
59
+ messages: ReadonlyArray<{ from: string; subject: string; priority: string; body: string }>,
60
+ ): string {
61
+ if (messages.length === 0) return "";
62
+ return messages
63
+ .map(
64
+ (m) =>
65
+ `[MAIL] From: ${m.from} | Subject: ${m.subject} | Priority: ${m.priority}\n\n${m.body}`,
66
+ )
67
+ .join("\n\n---\n\n");
68
+ }