@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,646 @@
1
+ /**
2
+ * CLI command: agentplate nudge <agent-name> [message]
3
+ *
4
+ * Sends a text nudge to an agent's interactive Claude Code session via
5
+ * tmux send-keys. Used to notify agents of new mail or relay urgent
6
+ * instructions mid-conversation.
7
+ *
8
+ * Includes retry logic (3 attempts) and debounce (500ms) to prevent
9
+ * rapid-fire nudges to the same agent.
10
+ */
11
+
12
+ import { join } from "node:path";
13
+ import { Command } from "commander";
14
+ import { encodeUserTurn } from "../agents/headless-prompt.ts";
15
+ import { createManifestLoader } from "../agents/manifest.ts";
16
+ import { type RunTurnOpts, runTurn, type TurnResult } from "../agents/turn-runner.ts";
17
+ import { buildRunTurnOptsFactory, isSpawnPerTurnAgent } from "../agents/turn-runner-dispatch.ts";
18
+ import { loadConfig } from "../config.ts";
19
+ import { AgentError } from "../errors.ts";
20
+ import { createEventStore } from "../events/store.ts";
21
+ import { jsonOutput } from "../json.ts";
22
+ import { printSuccess } from "../logging/color.ts";
23
+ import { getConnection } from "../runtimes/connections.ts";
24
+ import { hasNudge } from "../runtimes/headless-connection.ts";
25
+ import { openSessionStore } from "../sessions/compat.ts";
26
+ import type { AgentSession, EventStore } from "../types.ts";
27
+ import { capturePaneContent, isSessionAlive, sendKeys } from "../worktree/tmux.ts";
28
+
29
+ const DEFAULT_MESSAGE = "Check your mail inbox for new messages.";
30
+ const MAX_RETRIES = 3;
31
+ const RETRY_DELAY_MS = 500;
32
+ const DEBOUNCE_MS = 500;
33
+
34
+ /**
35
+ * Maximum total time (ms) to wait for a busy pane to become idle before
36
+ * giving up and reporting the nudge as deferred. Sized to ride out short
37
+ * tool calls without blocking long-running thinks.
38
+ */
39
+ const IDLE_WAIT_MS = 3000;
40
+ const IDLE_POLL_INTERVAL_MS = 250;
41
+
42
+ /**
43
+ * Heuristic: does the captured pane content indicate the agent is mid-think?
44
+ *
45
+ * Claude Code's TUI shows "esc to interrupt" alongside the streaming token
46
+ * counter while a turn is in flight. The phrase is absent in idle state, when
47
+ * tool output is rendered, and on the trust dialog — so its presence is a
48
+ * reliable busy signal. Returns true when the agent appears busy and a nudge
49
+ * sent via tmux send-keys would be queued into the in-flight prompt instead
50
+ * of starting a fresh user turn. (agentplate-8ff4)
51
+ */
52
+ export function paneAppearsBusy(paneContent: string): boolean {
53
+ return paneContent.includes("esc to interrupt");
54
+ }
55
+
56
+ /**
57
+ * Wait briefly for a tmux pane to leave the mid-think state.
58
+ *
59
+ * Polls capture-pane until the busy heuristic clears or the deadline elapses.
60
+ * Returns true if the pane became idle, false if it remained busy or pane
61
+ * capture failed throughout. Capture failures count as "not idle" so the
62
+ * caller defers the nudge rather than blasting send-keys into an unknown
63
+ * state. (agentplate-8ff4)
64
+ */
65
+ async function waitForPaneIdle(
66
+ tmuxSession: string,
67
+ maxWaitMs: number = IDLE_WAIT_MS,
68
+ pollIntervalMs: number = IDLE_POLL_INTERVAL_MS,
69
+ ): Promise<boolean> {
70
+ const deadline = Date.now() + maxWaitMs;
71
+ while (true) {
72
+ const content = await capturePaneContent(tmuxSession, 20);
73
+ if (content !== null && !paneAppearsBusy(content)) {
74
+ return true;
75
+ }
76
+ if (Date.now() >= deadline) {
77
+ return false;
78
+ }
79
+ await Bun.sleep(pollIntervalMs);
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Load the orchestrator's registered tmux session name.
85
+ *
86
+ * Written by `agentplate prime` at SessionStart when the orchestrator
87
+ * is running inside tmux. Enables agents to nudge the orchestrator
88
+ * even though it's not tracked in the SessionStore.
89
+ */
90
+ async function loadOrchestratorTmuxSession(projectRoot: string): Promise<string | null> {
91
+ const regPath = join(projectRoot, ".agentplate", "orchestrator-tmux.json");
92
+ const file = Bun.file(regPath);
93
+ if (!(await file.exists())) {
94
+ return null;
95
+ }
96
+ try {
97
+ const text = await file.text();
98
+ const reg = JSON.parse(text) as { tmuxSession?: string };
99
+ return reg.tmuxSession ?? null;
100
+ } catch {
101
+ return null;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Resolve the tmux session name for an agent.
107
+ *
108
+ * For regular agents, looks up the SessionStore.
109
+ * For "orchestrator", falls back to the orchestrator-tmux.json registration
110
+ * file written by `agentplate prime`.
111
+ *
112
+ * Returns the tmux session name on success, or a structured `null` result that
113
+ * captures the terminal-state diagnosis so callers can surface a helpful
114
+ * recovery hint instead of a generic "no active session" error (agentplate-629f).
115
+ */
116
+ type ResolveTargetResult =
117
+ | { kind: "found"; tmuxSession: string }
118
+ | { kind: "missing" }
119
+ | { kind: "terminal"; state: "completed" | "zombie"; capability: string; taskId: string };
120
+
121
+ async function resolveTargetSession(
122
+ projectRoot: string,
123
+ agentName: string,
124
+ ): Promise<ResolveTargetResult> {
125
+ const agentplateDir = join(projectRoot, ".agentplate");
126
+ const { store } = openSessionStore(agentplateDir);
127
+ let terminal: ResolveTargetResult | null = null;
128
+ try {
129
+ const session = store.getByName(agentName);
130
+ if (session) {
131
+ if (session.state !== "zombie" && session.state !== "completed") {
132
+ return { kind: "found", tmuxSession: session.tmuxSession };
133
+ }
134
+ terminal = {
135
+ kind: "terminal",
136
+ state: session.state,
137
+ capability: session.capability,
138
+ taskId: session.taskId,
139
+ };
140
+ }
141
+ } finally {
142
+ store.close();
143
+ }
144
+
145
+ // Fallback for orchestrator: check orchestrator-tmux.json
146
+ if (agentName === "orchestrator") {
147
+ const orchestratorTmux = await loadOrchestratorTmuxSession(projectRoot);
148
+ if (orchestratorTmux !== null) {
149
+ return { kind: "found", tmuxSession: orchestratorTmux };
150
+ }
151
+ }
152
+
153
+ return terminal ?? { kind: "missing" };
154
+ }
155
+
156
+ /**
157
+ * Build the operator-facing failure reason when a nudge cannot find a live
158
+ * session. Terminal-state agents get a recovery hint pointing at
159
+ * `ap sling --recover`; missing agents keep the generic message. (agentplate-629f)
160
+ */
161
+ export function buildMissingSessionReason(
162
+ agentName: string,
163
+ resolution: ResolveTargetResult,
164
+ ): string {
165
+ if (resolution.kind === "terminal") {
166
+ return (
167
+ `No active session for agent "${agentName}" (state: ${resolution.state}). ` +
168
+ `The agent has exited; re-dispatch with ` +
169
+ `'ap sling ${resolution.taskId} --capability ${resolution.capability} --recover'.`
170
+ );
171
+ }
172
+ return `No active session for agent "${agentName}"`;
173
+ }
174
+
175
+ /**
176
+ * Check debounce state for an agent. Returns true if a nudge was sent
177
+ * within the debounce window and should be skipped.
178
+ */
179
+ async function isDebounced(statePath: string, agentName: string): Promise<boolean> {
180
+ const file = Bun.file(statePath);
181
+ if (!(await file.exists())) {
182
+ return false;
183
+ }
184
+ try {
185
+ const text = await file.text();
186
+ const state = JSON.parse(text) as Record<string, number>;
187
+ const lastNudge = state[agentName];
188
+ if (lastNudge === undefined) {
189
+ return false;
190
+ }
191
+ return Date.now() - lastNudge < DEBOUNCE_MS;
192
+ } catch {
193
+ return false;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Record a nudge timestamp for debounce tracking.
199
+ */
200
+ async function recordNudge(statePath: string, agentName: string): Promise<void> {
201
+ let state: Record<string, number> = {};
202
+ const file = Bun.file(statePath);
203
+ if (await file.exists()) {
204
+ try {
205
+ const text = await file.text();
206
+ state = JSON.parse(text) as Record<string, number>;
207
+ } catch {
208
+ // Corrupt state file — start fresh
209
+ }
210
+ }
211
+ state[agentName] = Date.now();
212
+ await Bun.write(statePath, `${JSON.stringify(state, null, "\t")}\n`);
213
+ }
214
+
215
+ /** Outcome of a tmux nudge attempt. */
216
+ type SendNudgeResult =
217
+ | { kind: "delivered" }
218
+ | { kind: "deferred"; reason: string }
219
+ | { kind: "failed" };
220
+
221
+ /**
222
+ * Send a nudge to an agent's tmux session with retry logic.
223
+ *
224
+ * @param tmuxSession - The tmux session name
225
+ * @param message - The text to send
226
+ * @returns delivered on success, deferred when the agent stays mid-think
227
+ * beyond the idle window, failed when send-keys exhausts retries.
228
+ */
229
+ async function sendNudgeWithRetry(tmuxSession: string, message: string): Promise<SendNudgeResult> {
230
+ // Guard: never send-keys into a mid-think pane. Without this check, the
231
+ // nudge text is queued as input and corrupts the in-flight prompt.
232
+ // (agentplate-8ff4)
233
+ const idle = await waitForPaneIdle(tmuxSession);
234
+ if (!idle) {
235
+ return {
236
+ kind: "deferred",
237
+ reason: "Agent is mid-think (esc-to-interrupt visible) — nudge deferred",
238
+ };
239
+ }
240
+
241
+ for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
242
+ try {
243
+ await sendKeys(tmuxSession, message);
244
+ // Follow-up Enter after a short delay to ensure submission.
245
+ // Claude Code's TUI may consume the first Enter during re-render/focus
246
+ // events, leaving text visible but unsubmitted (agentplate-t62v).
247
+ // Same workaround as sling.ts and coordinator.ts.
248
+ await Bun.sleep(500);
249
+ await sendKeys(tmuxSession, "");
250
+ return { kind: "delivered" };
251
+ } catch {
252
+ if (attempt < MAX_RETRIES) {
253
+ await Bun.sleep(RETRY_DELAY_MS);
254
+ }
255
+ }
256
+ }
257
+ return { kind: "failed" };
258
+ }
259
+
260
+ /**
261
+ * Read the current run ID from current-run.txt, or null if no active run.
262
+ */
263
+ async function readCurrentRunId(agentplateDir: string): Promise<string | null> {
264
+ const path = join(agentplateDir, "current-run.txt");
265
+ const file = Bun.file(path);
266
+ if (!(await file.exists())) {
267
+ return null;
268
+ }
269
+ try {
270
+ const text = await file.text();
271
+ const trimmed = text.trim();
272
+ return trimmed.length > 0 ? trimmed : null;
273
+ } catch {
274
+ return null;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Fire-and-forget: record a nudge event to EventStore. Never throws.
280
+ */
281
+ function recordNudgeEvent(
282
+ eventStore: EventStore,
283
+ opts: {
284
+ runId: string | null;
285
+ agentName: string;
286
+ from: string;
287
+ message: string;
288
+ delivered: boolean;
289
+ },
290
+ ): void {
291
+ try {
292
+ eventStore.insert({
293
+ runId: opts.runId,
294
+ agentName: opts.agentName,
295
+ sessionId: null,
296
+ eventType: "custom",
297
+ toolName: null,
298
+ toolArgs: null,
299
+ toolDurationMs: null,
300
+ level: "info",
301
+ data: JSON.stringify({
302
+ type: "nudge",
303
+ from: opts.from,
304
+ message: opts.message,
305
+ delivered: opts.delivered,
306
+ }),
307
+ });
308
+ } catch {
309
+ // Fire-and-forget: event recording must never break nudge delivery
310
+ }
311
+ }
312
+
313
+ /** Test-only injection point for the spawn-per-turn dispatch path. */
314
+ export interface NudgeAgentDeps {
315
+ _runTurnFn?: (opts: RunTurnOpts) => Promise<TurnResult>;
316
+ _loadConfig?: typeof loadConfig;
317
+ }
318
+
319
+ /**
320
+ * Look up the agent's session row. Returns null when missing or terminal.
321
+ * Terminal sessions are filtered here so the spawn-per-turn dispatch path
322
+ * never re-spawns a completed builder.
323
+ */
324
+ function loadActiveSessionForNudge(projectRoot: string, agentName: string): AgentSession | null {
325
+ const agentplateDir = join(projectRoot, ".agentplate");
326
+ try {
327
+ const { store } = openSessionStore(agentplateDir);
328
+ try {
329
+ const session = store.getByName(agentName);
330
+ if (!session) return null;
331
+ if (session.state === "completed" || session.state === "zombie") return null;
332
+ return session;
333
+ } finally {
334
+ store.close();
335
+ }
336
+ } catch {
337
+ return null;
338
+ }
339
+ }
340
+
341
+ /** Best-effort: insert a nudge event into events.db. Never throws. */
342
+ function recordNudgeEventBestEffort(
343
+ agentplateDir: string,
344
+ agentName: string,
345
+ message: string,
346
+ delivered: boolean,
347
+ ): void {
348
+ try {
349
+ const eventsDbPath = join(agentplateDir, "events.db");
350
+ const eventStore = createEventStore(eventsDbPath);
351
+ try {
352
+ void readCurrentRunId(agentplateDir).then((runId) => {
353
+ try {
354
+ recordNudgeEvent(eventStore, {
355
+ runId,
356
+ agentName,
357
+ from: "orchestrator",
358
+ message,
359
+ delivered,
360
+ });
361
+ } finally {
362
+ try {
363
+ eventStore.close();
364
+ } catch {
365
+ // already closed
366
+ }
367
+ }
368
+ });
369
+ } catch {
370
+ try {
371
+ eventStore.close();
372
+ } catch {
373
+ // already closed
374
+ }
375
+ }
376
+ } catch {
377
+ // non-fatal
378
+ }
379
+ }
380
+
381
+ interface TryNudgeViaTurnRunnerInput {
382
+ agentName: string;
383
+ message: string;
384
+ agentplateDir: string;
385
+ projectRoot: string;
386
+ statePath: string;
387
+ deps: NudgeAgentDeps;
388
+ }
389
+
390
+ /**
391
+ * If the target agent is a Phase 2 spawn-per-turn builder, deliver `message`
392
+ * as a single user turn through `runTurn` and return the delivery result.
393
+ *
394
+ * Returns `null` when the agent is not eligible (flag off, non-builder,
395
+ * terminal state, missing session, runtime cannot direct-spawn). The caller
396
+ * falls back to the legacy FIFO/connection/tmux paths.
397
+ *
398
+ * The runTurn call is awaited synchronously: that lets the in-process
399
+ * turn-lock serialize against the mail dispatcher running in `ap serve`.
400
+ * Failures throw — the caller treats them as a delivery error.
401
+ */
402
+ async function tryNudgeViaTurnRunner(
403
+ input: TryNudgeViaTurnRunnerInput,
404
+ ): Promise<{ delivered: boolean; queued?: boolean; reason?: string } | null> {
405
+ const session = loadActiveSessionForNudge(input.projectRoot, input.agentName);
406
+ if (!session) return null;
407
+
408
+ const _load = input.deps._loadConfig ?? loadConfig;
409
+ let config: Awaited<ReturnType<typeof loadConfig>>;
410
+ try {
411
+ config = await _load(input.projectRoot);
412
+ } catch {
413
+ return null;
414
+ }
415
+
416
+ const manifestLoader = createManifestLoader(
417
+ join(config.project.root, config.agents.manifestPath),
418
+ join(config.project.root, config.agents.baseDir),
419
+ );
420
+ let manifest: Awaited<ReturnType<typeof manifestLoader.load>>;
421
+ try {
422
+ manifest = await manifestLoader.load();
423
+ } catch {
424
+ return null;
425
+ }
426
+
427
+ let factory: ReturnType<typeof buildRunTurnOptsFactory>;
428
+ try {
429
+ factory = buildRunTurnOptsFactory({
430
+ session,
431
+ config,
432
+ manifest,
433
+ agentplateDir: input.agentplateDir,
434
+ });
435
+ } catch {
436
+ return null;
437
+ }
438
+
439
+ if (!isSpawnPerTurnAgent(session, config, factory.runtime)) return null;
440
+
441
+ const runTurnFn = input.deps._runTurnFn ?? runTurn;
442
+ const opts = factory.build(encodeUserTurn(input.message));
443
+
444
+ try {
445
+ const result = await runTurnFn(opts);
446
+ await recordNudge(input.statePath, input.agentName);
447
+ // Mirror the FIFO branch's queued semantics: the message has been
448
+ // consumed by claude inside this turn, but follow-up turns may still
449
+ // observe it as "queued" if the agent didn't act on it immediately.
450
+ return {
451
+ delivered: true,
452
+ queued: result.cleanResult !== true,
453
+ };
454
+ } catch (err) {
455
+ return {
456
+ delivered: false,
457
+ reason: `Spawn-per-turn dispatch failed: ${err instanceof Error ? err.message : String(err)}`,
458
+ };
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Core nudge function. Exported for use by mail send auto-nudge.
464
+ *
465
+ * Routes through the registered RuntimeConnection when available (headless agents),
466
+ * or falls back to the tmux send-keys path (interactive agents).
467
+ *
468
+ * Headless nudges return queued=true because Claude Code does not reliably poll
469
+ * stdin while an API stream is in flight — the message sits in the pipe buffer
470
+ * until the current turn completes.
471
+ *
472
+ * For task-scoped headless Claude (Phase 3 spawn-per-turn), the nudge becomes
473
+ * a single user-turn delivered via `runTurn`. The call awaits the turn
474
+ * synchronously so the in-process turn-lock can serialize against concurrent
475
+ * mail dispatchers.
476
+ *
477
+ * @param projectRoot - Absolute path to the project root
478
+ * @param agentName - Name of the agent to nudge
479
+ * @param message - Text to send (defaults to mail check prompt)
480
+ * @param force - Skip debounce check
481
+ * @returns Object with delivery status; queued=true when headless and buffered
482
+ */
483
+ export async function nudgeAgent(
484
+ projectRoot: string,
485
+ agentName: string,
486
+ message: string = DEFAULT_MESSAGE,
487
+ force = false,
488
+ deps: NudgeAgentDeps = {},
489
+ ): Promise<{ delivered: boolean; queued?: boolean; reason?: string }> {
490
+ let result: { delivered: boolean; queued?: boolean; reason?: string } | undefined;
491
+
492
+ const statePath = join(projectRoot, ".agentplate", "nudge-state.json");
493
+
494
+ // Check debounce early — applies to both headless and tmux paths
495
+ if (!force) {
496
+ const debounced = await isDebounced(statePath, agentName);
497
+ if (debounced) {
498
+ return { delivered: false, reason: "Debounced: nudge sent too recently" };
499
+ }
500
+ }
501
+
502
+ const agentplateDir = join(projectRoot, ".agentplate");
503
+
504
+ // Runtime-agnostic delivery preference (mx-17830a):
505
+ // 1. Live in-process RuntimeConnection (Sapling RPC) → conn.nudge()
506
+ // 2. Spawn-per-turn task-scoped Claude → runTurn() (no live connection)
507
+ // 3. Tmux interactive agent → tmux send-keys
508
+ const inProcConn = getConnection(agentName);
509
+ if (inProcConn !== undefined && hasNudge(inProcConn)) {
510
+ // In-process RPC path (Sapling and friends).
511
+ const nudgeResult = await inProcConn.nudge(message);
512
+ await recordNudge(statePath, agentName);
513
+ result = { delivered: true, queued: nudgeResult.status === "Queued" };
514
+ } else {
515
+ // Spawn-per-turn dispatch for task-scoped headless Claude. When the
516
+ // agent is eligible, deliver the nudge as a user turn through `runTurn`.
517
+ // Returns null when ineligible (terminal state, persistent capability,
518
+ // flag off, etc.) and we fall through to the tmux path.
519
+ const spawnPerTurnResult = await tryNudgeViaTurnRunner({
520
+ agentName,
521
+ message,
522
+ agentplateDir,
523
+ projectRoot,
524
+ statePath,
525
+ deps,
526
+ });
527
+ if (spawnPerTurnResult !== null) {
528
+ recordNudgeEventBestEffort(agentplateDir, agentName, message, spawnPerTurnResult.delivered);
529
+ return spawnPerTurnResult;
530
+ }
531
+ // No live connection AND no spawn-per-turn eligibility — try tmux.
532
+ }
533
+
534
+ if (result === undefined) {
535
+ // Tmux path: resolve session name from SessionStore / orchestrator-tmux.json
536
+ const resolution = await resolveTargetSession(projectRoot, agentName);
537
+
538
+ if (resolution.kind !== "found") {
539
+ result = {
540
+ delivered: false,
541
+ reason: buildMissingSessionReason(agentName, resolution),
542
+ };
543
+ } else {
544
+ const tmuxSessionName = resolution.tmuxSession;
545
+ // Verify tmux session is alive
546
+ const alive = await isSessionAlive(tmuxSessionName);
547
+ if (!alive) {
548
+ result = {
549
+ delivered: false,
550
+ reason: `Tmux session "${tmuxSessionName}" is not alive`,
551
+ };
552
+ } else {
553
+ // Send with retry — sendNudgeWithRetry waits for an idle pane
554
+ // before attempting send-keys (agentplate-8ff4). It distinguishes
555
+ // "deferred" (agent mid-think) from "failed" (transient errors).
556
+ const sendResult = await sendNudgeWithRetry(tmuxSessionName, message);
557
+ if (sendResult.kind === "delivered") {
558
+ await recordNudge(statePath, agentName);
559
+ result = { delivered: true };
560
+ } else if (sendResult.kind === "deferred") {
561
+ result = { delivered: false, reason: sendResult.reason };
562
+ } else {
563
+ result = {
564
+ delivered: false,
565
+ reason: `Failed to send after ${MAX_RETRIES} attempts`,
566
+ };
567
+ }
568
+ }
569
+ }
570
+ }
571
+
572
+ // Record event to EventStore (fire-and-forget)
573
+ try {
574
+ const eventsDbPath = join(agentplateDir, "events.db");
575
+ const eventStore = createEventStore(eventsDbPath);
576
+ try {
577
+ const runId = await readCurrentRunId(agentplateDir);
578
+ recordNudgeEvent(eventStore, {
579
+ runId,
580
+ agentName,
581
+ from: "orchestrator",
582
+ message,
583
+ delivered: result.delivered,
584
+ });
585
+ } finally {
586
+ eventStore.close();
587
+ }
588
+ } catch {
589
+ // Event recording failure is non-fatal
590
+ }
591
+
592
+ return result;
593
+ }
594
+
595
+ /**
596
+ * Entry point for `agentplate nudge <agent-name> [message]`.
597
+ */
598
+ export async function nudgeCommand(args: string[]): Promise<void> {
599
+ const program = new Command();
600
+ program
601
+ .name("ap nudge")
602
+ .description("Send a text nudge to an agent")
603
+ .argument("<agent-name>", "Name of the agent to nudge")
604
+ .argument("[message...]", "Text to send (default: check mail prompt)")
605
+ .option("--from <name>", "Sender name", "orchestrator")
606
+ .option("--force", "Skip debounce check")
607
+ .option("--json", "Output result as JSON")
608
+ .exitOverride()
609
+ .action(
610
+ async (
611
+ agentName: string,
612
+ messageParts: string[],
613
+ opts: { from: string; force?: boolean; json?: boolean },
614
+ ) => {
615
+ // Build the nudge message: prefix with sender, use custom or default text
616
+ const customMessage = messageParts.join(" ");
617
+ const rawMessage = customMessage.length > 0 ? customMessage : DEFAULT_MESSAGE;
618
+ const message = `[NUDGE from ${opts.from}] ${rawMessage}`;
619
+
620
+ // Resolve project root
621
+ const { resolveProjectRoot } = await import("../config.ts");
622
+ const projectRoot = await resolveProjectRoot(process.cwd());
623
+
624
+ const result = await nudgeAgent(projectRoot, agentName, message, opts.force ?? false);
625
+
626
+ if (opts.json) {
627
+ jsonOutput("nudge", {
628
+ agentName,
629
+ delivered: result.delivered,
630
+ queued: result.queued,
631
+ reason: result.reason,
632
+ });
633
+ } else if (result.delivered) {
634
+ if (result.queued) {
635
+ printSuccess("Nudge queued (headless — will process after current turn)", agentName);
636
+ } else {
637
+ printSuccess("Nudge delivered", agentName);
638
+ }
639
+ } else {
640
+ throw new AgentError(`Nudge failed: ${result.reason}`, { agentName });
641
+ }
642
+ },
643
+ );
644
+
645
+ await program.parseAsync(["node", "agentplate-nudge", ...args]);
646
+ }
@@ -0,0 +1,42 @@
1
+ import type { Command } from "commander";
2
+ import {
3
+ type CoordinatorDeps,
4
+ createPersistentAgentCommand,
5
+ type PersistentAgentSpec,
6
+ persistentAgentCommand,
7
+ } from "./coordinator.ts";
8
+
9
+ const ORCHESTRATOR_SPEC: PersistentAgentSpec = {
10
+ commandName: "orchestrator",
11
+ displayName: "Orchestrator",
12
+ agentName: "orchestrator",
13
+ capability: "orchestrator",
14
+ agentDefFile: "orchestrator.md",
15
+ beaconBuilder: buildOrchestratorBeacon,
16
+ };
17
+
18
+ /**
19
+ * Build the startup beacon for the ecosystem-level orchestrator session.
20
+ */
21
+ export function buildOrchestratorBeacon(cliName = "bd"): string {
22
+ const timestamp = new Date().toISOString();
23
+ const parts = [
24
+ `[AGENTPLATE] orchestrator (orchestrator) ${timestamp}`,
25
+ "Depth: 0 | Parent: none | Role: persistent ecosystem orchestrator",
26
+ "HIERARCHY: You start per-repo coordinators with ap coordinator start --project <path>. Do NOT use ap sling directly.",
27
+ "DELEGATION: Work flows through sub-repo coordinators. Dispatch objectives by mail, then monitor coordinator progress and escalate only when needed.",
28
+ `Startup: run loam prime, check mail (ap mail check --agent orchestrator), check ${cliName} ready, inspect ecosystem status, then begin coordination`,
29
+ ];
30
+ return parts.join(" — ");
31
+ }
32
+
33
+ export function createOrchestratorCommand(deps: CoordinatorDeps = {}): Command {
34
+ return createPersistentAgentCommand(ORCHESTRATOR_SPEC, deps);
35
+ }
36
+
37
+ export async function orchestratorCommand(
38
+ args: string[],
39
+ deps: CoordinatorDeps = {},
40
+ ): Promise<void> {
41
+ await persistentAgentCommand(args, ORCHESTRATOR_SPEC, deps);
42
+ }