@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,579 @@
1
+ // Claude Code runtime adapter for agentplate's AgentRuntime interface.
2
+ // Pure extraction — no new behavior. All implementation delegates to existing code.
3
+ // Phase 0: file exists and compiles. Callers are not rewired until Phase 2.
4
+
5
+ import { mkdir } from "node:fs/promises";
6
+ import { join } from "node:path";
7
+ import { deployHooks } from "../agents/hooks-deployer.ts";
8
+ import { estimateCost } from "../metrics/pricing.ts";
9
+ import { parseTranscriptUsage } from "../metrics/transcript.ts";
10
+ import type { ResolvedModel } from "../types.ts";
11
+ import type {
12
+ AgentEvent,
13
+ AgentRuntime,
14
+ DirectSpawnOpts,
15
+ HooksDef,
16
+ OverlayContent,
17
+ ReadyState,
18
+ SpawnOpts,
19
+ TranscriptSummary,
20
+ } from "./types.ts";
21
+
22
+ /**
23
+ * Claude Code runtime adapter.
24
+ *
25
+ * Implements AgentRuntime for the `claude` CLI (Anthropic's Claude Code).
26
+ * All methods delegate to existing agentplate subsystems — this adapter
27
+ * only provides the runtime-agnostic interface layer.
28
+ *
29
+ * Phase 0: file exists, compiles, and exports the class.
30
+ * Phase 2 will rewire callers (sling.ts, coordinator.ts, etc.) to use this adapter.
31
+ */
32
+ export class ClaudeRuntime implements AgentRuntime {
33
+ /** Unique identifier for this runtime. */
34
+ readonly id = "claude";
35
+
36
+ /** Stability level. Claude Code is the primary runtime. */
37
+ readonly stability = "stable" as const;
38
+
39
+ /** Relative path to the instruction file within a worktree. */
40
+ readonly instructionPath = ".claude/CLAUDE.md";
41
+
42
+ /**
43
+ * Build the shell command string to spawn an interactive Claude Code agent.
44
+ *
45
+ * Maps SpawnOpts to the `claude` CLI flags:
46
+ * - `model` → `--model <model>`
47
+ * - `permissionMode` → `--permission-mode <mode>`
48
+ * - "bypass" maps to "bypassPermissions"
49
+ * - "ask" maps to "default"
50
+ * - `appendSystemPrompt` → `--append-system-prompt '<escaped>'`
51
+ *
52
+ * The returned string is passed directly to tmux as the initial command.
53
+ * The `cwd` and `env` fields of SpawnOpts are handled by the tmux session
54
+ * creator, not embedded in the command string.
55
+ *
56
+ * @param opts - Spawn options (model, permissionMode, appendSystemPrompt)
57
+ * @returns Shell command string suitable for tmux new-session -c
58
+ */
59
+ buildSpawnCommand(opts: SpawnOpts): string {
60
+ const permMode = opts.permissionMode === "bypass" ? "bypassPermissions" : "default";
61
+ let cmd = `claude --model ${opts.model} --permission-mode ${permMode}`;
62
+
63
+ if (opts.appendSystemPromptFile) {
64
+ // Read from file at shell expansion time — avoids tmux IPC message size
65
+ // limits (~8-16KB) that cause "command too long" errors when large agent
66
+ // definitions are inlined. The $(cat ...) expands inside the tmux pane's
67
+ // shell, so the tmux IPC message only carries the short command string.
68
+ const escaped = opts.appendSystemPromptFile.replace(/'/g, "'\\''");
69
+ cmd += ` --append-system-prompt "$(cat '${escaped}')"`;
70
+ } else if (opts.appendSystemPrompt) {
71
+ // Single-quote the content for safe shell expansion.
72
+ // POSIX single-quoted strings cannot contain single quotes, so escape
73
+ // them using the standard technique: end quote, escaped quote, start quote.
74
+ const escaped = opts.appendSystemPrompt.replace(/'/g, "'\\''");
75
+ cmd += ` --append-system-prompt '${escaped}'`;
76
+ }
77
+
78
+ return cmd;
79
+ }
80
+
81
+ /**
82
+ * Build the argv array for a headless one-shot Claude invocation.
83
+ *
84
+ * Returns an argv array suitable for `Bun.spawn()`. The `--print` flag
85
+ * causes Claude Code to run the prompt and exit, writing output to stdout.
86
+ *
87
+ * Used by merge/resolver.ts (AI-assisted conflict resolution) and
88
+ * watchdog/triage.ts (AI-assisted failure classification).
89
+ *
90
+ * @param prompt - The prompt to pass via `-p`
91
+ * @param model - Optional model override (omit to use Claude Code's default)
92
+ * @returns Argv array for Bun.spawn
93
+ */
94
+ buildPrintCommand(prompt: string, model?: string): string[] {
95
+ const cmd = ["claude", "--print", "-p", prompt];
96
+ if (model !== undefined) {
97
+ cmd.push("--model", model);
98
+ }
99
+ return cmd;
100
+ }
101
+
102
+ /**
103
+ * Deploy per-agent instructions and guards to a worktree.
104
+ *
105
+ * For Claude Code this means writes to the worktree's `.claude/` directory:
106
+ * 1. `CLAUDE.md` — the agent's task-specific overlay (generated by ap sling).
107
+ * Skipped when overlay is undefined (hooks-only deployment for coordinator/supervisor/monitor).
108
+ * 2. `settings.local.json` — Claude Code hooks for security guards
109
+ *
110
+ * The `overlay.content` is written verbatim when provided. The hooks are generated by
111
+ * `deployHooks()` from `src/agents/hooks-deployer.ts`.
112
+ *
113
+ * @param worktreePath - Absolute path to the agent's git worktree
114
+ * @param overlay - Overlay content to write as CLAUDE.md, or undefined for hooks-only deployment
115
+ * @param hooks - Hook definition used by deployHooks
116
+ * @throws {AgentError} If the hooks template is missing or writes fail
117
+ */
118
+ async deployConfig(
119
+ worktreePath: string,
120
+ overlay: OverlayContent | undefined,
121
+ hooks: HooksDef,
122
+ ): Promise<void> {
123
+ if (overlay) {
124
+ const claudeDir = join(worktreePath, ".claude");
125
+ await mkdir(claudeDir, { recursive: true });
126
+
127
+ const claudeMdPath = join(claudeDir, "CLAUDE.md");
128
+ await Bun.write(claudeMdPath, overlay.content);
129
+ }
130
+
131
+ // Always deploy hooks — headless Claude Code DOES dispatch settings.local.json
132
+ // PreToolUse hooks (verified empirically against `claude -p --output-format stream-json`).
133
+ // The original design (agentplate-1c32 / docs/headless-hooks-design.md Q6) wrongly assumed
134
+ // hooks don't fire in headless mode and skipped deployment, leaving headless agents with
135
+ // none of the destructive-command guards. agentplate-e24b reverses that: in headless mode
136
+ // we deploy a settings.local.json containing only PreToolUse security guards (path boundary,
137
+ // capability blocks, bash danger patterns, tracker close, lead close gate). The other
138
+ // hook types are dropped because they have headless equivalents already wired up
139
+ // (initial stdin prompt, serve mail injection loop, stream-json event capture).
140
+ await deployHooks(
141
+ hooks.worktreePath,
142
+ hooks.agentName,
143
+ hooks.capability,
144
+ hooks.qualityGates,
145
+ hooks.isHeadless ?? false,
146
+ );
147
+ }
148
+
149
+ /**
150
+ * Detect Claude Code TUI readiness from a tmux pane content snapshot.
151
+ *
152
+ * Uses the same heuristics as `waitForTuiReady()` in `src/worktree/tmux.ts`,
153
+ * but operates on a pre-captured pane string rather than polling tmux directly.
154
+ * The caller is responsible for capturing pane content and acting on the result
155
+ * (e.g. sending "Enter" to dismiss a trust dialog).
156
+ *
157
+ * Detection phases:
158
+ * - Trust dialog: "trust this folder" detected → `{ phase: "dialog", action: "Enter" }`
159
+ * - Ready: prompt indicator (❯ or 'Try "') AND status bar ("bypass permissions")
160
+ * both present → `{ phase: "ready" }`
161
+ * - Otherwise → `{ phase: "loading" }`
162
+ *
163
+ * @param paneContent - Captured tmux pane content to analyze
164
+ * @returns Current readiness phase
165
+ */
166
+ detectReady(paneContent: string): ReadyState {
167
+ // Claude Code v2.1.71+ shows a dedicated bypass confirmation screen.
168
+ // It already contains both a prompt marker and the phrase "bypass permissions",
169
+ // so it must be detected before the normal ready heuristics.
170
+ if (
171
+ paneContent.includes("WARNING: Claude Code running in Bypass Permissions mode") &&
172
+ paneContent.includes("1. No, exit") &&
173
+ paneContent.includes("2. Yes, I accept")
174
+ ) {
175
+ return { phase: "dialog", action: "type:2" };
176
+ }
177
+
178
+ // Trust dialog takes precedence — it replaces the normal TUI temporarily.
179
+ // The caller should send the action key to dismiss it.
180
+ if (paneContent.includes("trust this folder")) {
181
+ return { phase: "dialog", action: "Enter" };
182
+ }
183
+
184
+ // Phase 1: prompt indicator confirms Claude Code has started.
185
+ // ❯ is the claude prompt character; 'Try "' appears in the welcome banner.
186
+ const hasPrompt = paneContent.includes("\u276f") || paneContent.includes('Try "');
187
+
188
+ // Phase 2: status bar text confirms full TUI render.
189
+ // Only match 'bypass permissions' — 'shift+tab' appears in ALL Claude Code sessions
190
+ // regardless of permission mode and would cause false-positive ready detection.
191
+ const hasStatusBar = paneContent.includes("bypass permissions");
192
+
193
+ if (hasPrompt && hasStatusBar) {
194
+ return { phase: "ready" };
195
+ }
196
+
197
+ return { phase: "loading" };
198
+ }
199
+
200
+ /**
201
+ * Parse a Claude Code transcript JSONL file into normalized token usage.
202
+ *
203
+ * Reads the JSONL file at `path` and aggregates token usage across all
204
+ * assistant turns. Returns null if the file does not exist or cannot be read.
205
+ *
206
+ * Delegates to `parseTranscriptUsage()` and `estimateCost()` from
207
+ * `src/metrics/transcript.ts`. The `estimatedCostUsd` is computed but
208
+ * not exposed here because `TranscriptSummary` only carries the three
209
+ * core fields (inputTokens, outputTokens, model). Cost data is available
210
+ * via `src/metrics/transcript.ts` directly for callers that need it.
211
+ *
212
+ * @param path - Absolute path to the transcript JSONL file
213
+ * @returns Aggregated token usage, or null if unavailable
214
+ */
215
+ async parseTranscript(path: string): Promise<TranscriptSummary | null> {
216
+ const file = Bun.file(path);
217
+ if (!(await file.exists())) {
218
+ return null;
219
+ }
220
+
221
+ try {
222
+ const usage = await parseTranscriptUsage(path);
223
+ // estimateCost is called to validate the model is recognized,
224
+ // though the result is not surfaced in TranscriptSummary.
225
+ if (usage.modelUsed !== null) {
226
+ estimateCost(usage);
227
+ }
228
+ return {
229
+ inputTokens: usage.inputTokens,
230
+ outputTokens: usage.outputTokens,
231
+ model: usage.modelUsed ?? "",
232
+ };
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Build the argv array for Bun.spawn() to launch a Claude Code agent in headless mode.
240
+ *
241
+ * Returns the exact flags Multica uses for headless Claude Code sessions:
242
+ * `-p --output-format stream-json --input-format stream-json --verbose
243
+ * --strict-mcp-config --permission-mode bypassPermissions [--model <m>]`
244
+ *
245
+ * Claude Code reads `.claude/CLAUDE.md` from cwd automatically — do NOT
246
+ * pass `--append-system-prompt` or consume `opts.instructionPath`.
247
+ * The initial stdin prompt is the caller's responsibility.
248
+ *
249
+ * @param opts - Direct spawn options; only `model` is consumed
250
+ * @returns Argv array for Bun.spawn — do not shell-interpolate
251
+ */
252
+ buildDirectSpawn(opts: DirectSpawnOpts): string[] {
253
+ const argv = [
254
+ "claude",
255
+ "-p",
256
+ "--output-format",
257
+ "stream-json",
258
+ "--input-format",
259
+ "stream-json",
260
+ "--verbose",
261
+ "--strict-mcp-config",
262
+ "--permission-mode",
263
+ "bypassPermissions",
264
+ ];
265
+ if (opts.model !== undefined) {
266
+ argv.push("--model", opts.model);
267
+ }
268
+ // Phase 1 (agentplate-b835): emit --resume on follow-up spawns. Mirrors
269
+ // multica/server/pkg/agent/claude.go:434 (positional). Empty string and
270
+ // null are treated as "no resume" — only non-empty strings activate it.
271
+ if (typeof opts.resumeSessionId === "string" && opts.resumeSessionId.length > 0) {
272
+ argv.push("--resume", opts.resumeSessionId);
273
+ }
274
+ return argv;
275
+ }
276
+
277
+ /**
278
+ * Parse stream-json stdout from a Claude Code headless subprocess into typed AgentEvent objects.
279
+ *
280
+ * Reads the ReadableStream from Bun.spawn() stdout, buffers partial lines,
281
+ * and yields a typed AgentEvent for each complete JSON line. Malformed lines
282
+ * and unknown message types are silently skipped.
283
+ *
284
+ * Adjacent assistant_text deltas are coalesced into a single assistant_message
285
+ * event using a batching window. The batch flushes on timer expiry, size cap,
286
+ * any non-text event, or stream end — whichever comes first.
287
+ *
288
+ * Event mapping (Claude stream-json → AgentEvent):
289
+ * - assistant/text → buffered; emitted as one assistant_message per batch
290
+ * - assistant/tool_use → { type: "tool_use", callId, name, input }
291
+ * - assistant/thinking → (skipped)
292
+ * - user/tool_result → { type: "tool_result", toolUseId, content }
293
+ * - system → { type: "status", sessionId, subtype }
294
+ * - result → { type: "result", sessionId, result, isError, durationMs, numTurns }
295
+ *
296
+ * @param stream - ReadableStream<Uint8Array> from Bun.spawn stdout
297
+ * @param opts - Optional hooks and tuning:
298
+ * - `onSessionId` is invoked once, synchronously, on the first event that carries a
299
+ * non-empty `sessionId`. Consumer errors are swallowed so they cannot crash the parser.
300
+ * - `flushIntervalMs` — Max ms to buffer text before emitting (default 500).
301
+ * - `flushSizeBytes` — Max UTF-8 byte size of a text batch (default 4096).
302
+ * @yields Parsed AgentEvent objects in emission order
303
+ */
304
+ async *parseEvents(
305
+ stream: ReadableStream<Uint8Array>,
306
+ opts?: {
307
+ onSessionId?: (sessionId: string) => void;
308
+ flushIntervalMs?: number;
309
+ flushSizeBytes?: number;
310
+ },
311
+ ): AsyncIterable<AgentEvent> {
312
+ const flushIntervalMs = opts?.flushIntervalMs ?? 500;
313
+ const flushSizeBytes = opts?.flushSizeBytes ?? 4096;
314
+
315
+ const reader = stream.getReader();
316
+ const decoder = new TextDecoder();
317
+ let buffer = "";
318
+ let sessionIdPinned = false;
319
+
320
+ // Batch state for adjacent assistant_text deltas.
321
+ let pendingText: string[] = [];
322
+ let pendingByteSize = 0;
323
+ let pendingStartTs: string | null = null;
324
+ let pendingModel: string | undefined;
325
+ let pendingUsage: unknown;
326
+
327
+ // Returns batched assistant_message event and resets state; null when buffer is empty.
328
+ const flushText = (): AgentEvent | null => {
329
+ if (pendingText.length === 0) return null;
330
+ const text = pendingText.join("");
331
+ const event: AgentEvent = {
332
+ type: "assistant_message",
333
+ timestamp: pendingStartTs ?? new Date().toISOString(),
334
+ text,
335
+ };
336
+ if (pendingModel !== undefined) event.model = pendingModel;
337
+ if (pendingUsage !== undefined) event.usage = pendingUsage;
338
+ pendingText = [];
339
+ pendingByteSize = 0;
340
+ pendingStartTs = null;
341
+ pendingModel = undefined;
342
+ pendingUsage = undefined;
343
+ return event;
344
+ };
345
+
346
+ // Sync generator: parses one JSON line, yielding AgentEvents in order.
347
+ // Mutates batch state via closure — do not call concurrently.
348
+ function* processLine(line: string): Generator<AgentEvent> {
349
+ const trimmed = line.trim();
350
+ if (!trimmed) return;
351
+
352
+ let msg: Record<string, unknown>;
353
+ try {
354
+ msg = JSON.parse(trimmed) as Record<string, unknown>;
355
+ } catch {
356
+ return;
357
+ }
358
+
359
+ const timestamp = new Date().toISOString();
360
+
361
+ if (msg.type === "assistant") {
362
+ const message =
363
+ typeof msg.message === "object" && msg.message !== null
364
+ ? (msg.message as Record<string, unknown>)
365
+ : null;
366
+ if (!message) return;
367
+ const content = Array.isArray(message.content) ? message.content : [];
368
+ const model = typeof message.model === "string" ? message.model : undefined;
369
+ const usage = message.usage !== undefined ? message.usage : undefined;
370
+
371
+ for (const block of content) {
372
+ if (typeof block !== "object" || block === null) continue;
373
+ const b = block as Record<string, unknown>;
374
+ if (b.type === "text") {
375
+ const text = typeof b.text === "string" ? b.text : String(b.text);
376
+ const textByteSize = new TextEncoder().encode(text).byteLength;
377
+
378
+ // Size-cap: if appending would exceed cap and buffer is non-empty, flush first.
379
+ if (pendingByteSize > 0 && pendingByteSize + textByteSize > flushSizeBytes) {
380
+ const ev = flushText();
381
+ if (ev) yield ev;
382
+ }
383
+
384
+ // Record the start-of-batch timestamp on the first fragment.
385
+ if (pendingByteSize === 0) pendingStartTs = timestamp;
386
+
387
+ pendingText.push(text);
388
+ pendingByteSize += textByteSize;
389
+ // Latest contributing message wins for model/usage.
390
+ if (model !== undefined) pendingModel = model;
391
+ if (usage !== undefined) pendingUsage = usage;
392
+
393
+ // Immediate flush when a single fragment meets or exceeds the size cap.
394
+ if (pendingByteSize >= flushSizeBytes) {
395
+ const ev = flushText();
396
+ if (ev) yield ev;
397
+ }
398
+ } else if (b.type === "tool_use") {
399
+ // Non-text block: flush pending text first to preserve in-order delivery.
400
+ const ev = flushText();
401
+ if (ev) yield ev;
402
+ yield {
403
+ type: "tool_use",
404
+ timestamp,
405
+ callId: b.id,
406
+ name: b.name,
407
+ input: b.input,
408
+ };
409
+ }
410
+ // thinking and other block types → skip
411
+ }
412
+ } else if (msg.type === "user") {
413
+ const message =
414
+ typeof msg.message === "object" && msg.message !== null
415
+ ? (msg.message as Record<string, unknown>)
416
+ : null;
417
+ if (!message) return;
418
+ const content = Array.isArray(message.content) ? message.content : [];
419
+
420
+ const flushEv = flushText();
421
+ if (flushEv) yield flushEv;
422
+
423
+ for (const block of content) {
424
+ if (typeof block !== "object" || block === null) continue;
425
+ const b = block as Record<string, unknown>;
426
+ if (b.type === "tool_result") {
427
+ yield {
428
+ type: "tool_result",
429
+ timestamp,
430
+ toolUseId: b.tool_use_id,
431
+ content: b.content,
432
+ };
433
+ }
434
+ }
435
+ } else if (msg.type === "system") {
436
+ const flushEv = flushText();
437
+ if (flushEv) yield flushEv;
438
+ yield { type: "status", timestamp, sessionId: msg.session_id, subtype: msg.subtype };
439
+ } else if (msg.type === "result") {
440
+ const flushEv = flushText();
441
+ if (flushEv) yield flushEv;
442
+ yield {
443
+ type: "result",
444
+ timestamp,
445
+ sessionId: msg.session_id,
446
+ result: msg.result,
447
+ isError: msg.is_error,
448
+ durationMs: msg.duration_ms,
449
+ numTurns: msg.num_turns,
450
+ };
451
+ }
452
+ }
453
+
454
+ const maybePinSession = (event: AgentEvent): void => {
455
+ if (sessionIdPinned || !opts?.onSessionId) return;
456
+ const sid = event.sessionId;
457
+ if (typeof sid !== "string" || sid.length === 0) return;
458
+ sessionIdPinned = true;
459
+ try {
460
+ opts.onSessionId(sid);
461
+ } catch {
462
+ // Consumer errors must not crash the parser.
463
+ }
464
+ };
465
+
466
+ try {
467
+ // Use the inferred return type of reader.read() to stay compatible with
468
+ // both the standard Web Streams API and Bun's slightly divergent typings.
469
+ type ReadResult = Awaited<ReturnType<typeof reader.read>>;
470
+ type Race = { kind: "read"; result: ReadResult } | { kind: "timeout" };
471
+
472
+ let readPromise = reader.read();
473
+
474
+ while (true) {
475
+ if (pendingText.length > 0 && pendingStartTs !== null) {
476
+ // Race the next read chunk against the flush timer.
477
+ const elapsed = Date.now() - new Date(pendingStartTs).getTime();
478
+ const remaining = Math.max(0, flushIntervalMs - elapsed);
479
+
480
+ let timerId: ReturnType<typeof setTimeout> | undefined;
481
+ const wrappedRead: Promise<Race> = readPromise.then((result) => ({
482
+ kind: "read" as const,
483
+ result,
484
+ }));
485
+ const wrappedTimer = new Promise<Race>((resolve) => {
486
+ timerId = setTimeout(() => resolve({ kind: "timeout" }), remaining);
487
+ });
488
+
489
+ const winner = await Promise.race([wrappedRead, wrappedTimer]);
490
+ if (timerId !== undefined) clearTimeout(timerId);
491
+
492
+ if (winner.kind === "timeout") {
493
+ const ev = flushText();
494
+ if (ev) yield ev;
495
+ continue; // readPromise still in flight — re-enter loop without refreshing
496
+ }
497
+
498
+ const result = winner.result;
499
+ if (result.done) break;
500
+ buffer += decoder.decode(result.value, { stream: true });
501
+ const lines = buffer.split("\n");
502
+ buffer = lines.pop() ?? "";
503
+ for (const line of lines) {
504
+ for (const event of processLine(line)) {
505
+ maybePinSession(event);
506
+ yield event;
507
+ }
508
+ }
509
+ readPromise = reader.read();
510
+ } else {
511
+ const result = await readPromise;
512
+ if (result.done) break;
513
+ buffer += decoder.decode(result.value, { stream: true });
514
+ const lines = buffer.split("\n");
515
+ // Last element is either empty or an incomplete line — keep in buffer.
516
+ buffer = lines.pop() ?? "";
517
+ for (const line of lines) {
518
+ for (const event of processLine(line)) {
519
+ maybePinSession(event);
520
+ yield event;
521
+ }
522
+ }
523
+ readPromise = reader.read();
524
+ }
525
+ }
526
+
527
+ // Flush remaining buffer on clean stream end (no trailing newline).
528
+ if (buffer.trim()) {
529
+ for (const event of processLine(buffer)) {
530
+ maybePinSession(event);
531
+ yield event;
532
+ }
533
+ }
534
+
535
+ // Drain any remaining buffered text after the read loop exits.
536
+ const finalEv = flushText();
537
+ if (finalEv) yield finalEv;
538
+ } finally {
539
+ reader.releaseLock();
540
+ }
541
+ }
542
+
543
+ /**
544
+ * Build runtime-specific environment variables for model/provider routing.
545
+ *
546
+ * Returns the provider environment variables from the resolved model.
547
+ * For Anthropic native: may include ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL.
548
+ * For gateway providers: may include gateway-specific auth and routing vars.
549
+ *
550
+ * Returns an empty object if the resolved model has no provider env vars.
551
+ * Callers (sling.ts, coordinator.ts) merge this with AGENTPLATE_AGENT_NAME
552
+ * and AGENTPLATE_WORKTREE_PATH before passing to createSession().
553
+ *
554
+ * @param model - Resolved model with optional provider env vars
555
+ * @returns Environment variable map (may be empty)
556
+ */
557
+ buildEnv(model: ResolvedModel): Record<string, string> {
558
+ return model.env ?? {};
559
+ }
560
+
561
+ /**
562
+ * Return the Claude Code transcript directory for a given project root.
563
+ *
564
+ * Claude Code stores session transcripts at ~/.claude/projects/<projectKey>/
565
+ * where <projectKey> is the project root path with "/" replaced by "-".
566
+ *
567
+ * @param projectRoot - Absolute path to the project root
568
+ * @returns Absolute path to the transcript directory, or null if HOME is unavailable
569
+ */
570
+ getTranscriptDir(projectRoot: string): string | null {
571
+ const home = process.env.HOME ?? "";
572
+ if (home.length === 0) return null;
573
+ const projectKey = projectRoot.replace(/\//g, "-");
574
+ return join(home, ".claude", "projects", projectKey);
575
+ }
576
+ }
577
+
578
+ /** Singleton instance for use in callers that do not need DI. */
579
+ export const claudeRuntime = new ClaudeRuntime();