@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,332 @@
1
+ /**
2
+ * Background NDJSON event tailer for headless agent stdout logs.
3
+ *
4
+ * Headless agents (e.g. Sapling) write NDJSON events to a stdout.log file
5
+ * in .agentplate/logs/{agentName}/{timestamp}/stdout.log. After ap sling exits,
6
+ * nobody reads this stream — so ap status, ap dashboard, and ap feed cannot
7
+ * show live progress for headless agents.
8
+ *
9
+ * This module provides startEventTailer(), which polls the log file on a
10
+ * configurable interval, parses new NDJSON lines, and writes them into events.db
11
+ * via EventStore. The watchdog daemon starts a tailer for each headless agent
12
+ * session and stops it when the session completes or terminates.
13
+ */
14
+
15
+ import { readdir } from "node:fs/promises";
16
+ import { join } from "node:path";
17
+ import { createSessionStore, type SessionStore } from "../sessions/store.ts";
18
+ import type { EventStore, EventType } from "../types.ts";
19
+ import { createEventStore } from "./store.ts";
20
+
21
+ /**
22
+ * Handle to a running event tailer.
23
+ * Call stop() to halt polling and close the database connection.
24
+ */
25
+ export interface TailerHandle {
26
+ /** Agent name being tailed. */
27
+ readonly agentName: string;
28
+ /** Absolute path to the stdout.log file being tailed. */
29
+ readonly logPath: string;
30
+ /** Stop polling and release all resources. */
31
+ stop(): void;
32
+ }
33
+
34
+ /** Map NDJSON event type strings to EventStore EventType. */
35
+ function mapEventType(type: string): EventType {
36
+ switch (type) {
37
+ case "tool_start":
38
+ return "tool_start";
39
+ case "tool_end":
40
+ return "tool_end";
41
+ case "session_start":
42
+ return "session_start";
43
+ case "session_end":
44
+ return "session_end";
45
+ case "turn_start":
46
+ return "turn_start";
47
+ case "turn_end":
48
+ return "turn_end";
49
+ case "progress":
50
+ return "progress";
51
+ case "result":
52
+ return "result";
53
+ case "error":
54
+ return "error";
55
+ default:
56
+ return "custom";
57
+ }
58
+ }
59
+
60
+ /** Options for startEventTailer. */
61
+ export interface TailerOptions {
62
+ /** Absolute path to the stdout.log file to tail. */
63
+ stdoutLogPath: string;
64
+ /** Agent name for event attribution in events.db. */
65
+ agentName: string;
66
+ /** Run ID to associate events with, or null. */
67
+ runId: string | null;
68
+ /** Absolute path to events.db. The tailer opens its own connection. */
69
+ eventsDbPath: string;
70
+ /**
71
+ * Absolute path to sessions.db. When present and not equal to ":memory:",
72
+ * the tailer opens a dedicated SessionStore to persist the runtime-provided
73
+ * session_id (e.g. Claude stream-json `session_id`). Omit (or set to
74
+ * ":memory:") for tailers that should not write to SessionStore.
75
+ */
76
+ sessionsDbPath?: string;
77
+ /** Poll interval in milliseconds (default: 500). */
78
+ pollIntervalMs?: number;
79
+ /** DI: injected EventStore for testing (overrides eventsDbPath). */
80
+ _eventStore?: EventStore;
81
+ /** DI: injected SessionStore for testing (overrides sessionsDbPath). */
82
+ _sessionStore?: SessionStore;
83
+ /**
84
+ * DI: invoked exactly once per tailer when an observed session_id differs
85
+ * from the prior claudeSessionId stored in SessionStore. Receives the agent
86
+ * name, the prior (requested) id, and the newly observed id. Production
87
+ * code logs a warning to stderr instead.
88
+ */
89
+ _onResumeMismatch?: (agentName: string, requested: string, observed: string) => void;
90
+ }
91
+
92
+ /**
93
+ * Start a background event tailer for a headless agent's stdout.log.
94
+ *
95
+ * Polls the log file on a configurable interval, reads new bytes since the
96
+ * last poll using file.size as a byte cursor, parses NDJSON lines, and writes
97
+ * normalized events to events.db. Maintains its own SQLite connection so it
98
+ * can outlive the daemon tick that created it.
99
+ *
100
+ * All errors (file not found, parse failures, DB write failures) are swallowed
101
+ * silently — the tailer must never crash the watchdog daemon.
102
+ *
103
+ * @param opts - Tailer configuration (log path, agent, run, db path)
104
+ * @returns TailerHandle with stop() to halt polling and close resources
105
+ */
106
+ export function startEventTailer(opts: TailerOptions): TailerHandle {
107
+ const { stdoutLogPath, agentName, runId, eventsDbPath, pollIntervalMs = 500 } = opts;
108
+
109
+ // Open a dedicated EventStore for this tailer's lifetime (not tick-scoped).
110
+ // Injected _eventStore is used for testing without an actual DB file.
111
+ let eventStore: EventStore | null = opts._eventStore ?? null;
112
+ let ownedEventStore = false;
113
+ if (!eventStore) {
114
+ try {
115
+ eventStore = createEventStore(eventsDbPath);
116
+ ownedEventStore = true;
117
+ } catch {
118
+ // EventStore creation failed — return a no-op handle immediately.
119
+ // Scheduling a poll loop that silently discards every event would
120
+ // burn CPU and file descriptors for zero benefit.
121
+ return {
122
+ agentName,
123
+ logPath: stdoutLogPath,
124
+ stop() {},
125
+ };
126
+ }
127
+ }
128
+
129
+ // Open a dedicated SessionStore for this tailer's lifetime when a real
130
+ // sessionsDbPath is provided. Tailers that omit sessionsDbPath (or pass
131
+ // ":memory:") skip session_id persistence entirely — backward compat for
132
+ // callers that don't yet route through the watchdog wiring.
133
+ let sessionStore: SessionStore | null = opts._sessionStore ?? null;
134
+ let ownedSessionStore = false;
135
+ if (!sessionStore && opts.sessionsDbPath && opts.sessionsDbPath !== ":memory:") {
136
+ try {
137
+ sessionStore = createSessionStore(opts.sessionsDbPath);
138
+ ownedSessionStore = true;
139
+ } catch {
140
+ // SessionStore failure is non-fatal — events still flow.
141
+ sessionStore = null;
142
+ }
143
+ }
144
+
145
+ // Single-fire guard for session_id pinning. Mirrors claude.ts:312
146
+ // `sessionIdPinned` so that updateClaudeSessionId is called at most once
147
+ // per tailer lifetime, even if many system events stream by.
148
+ let sessionIdPinned = false;
149
+ const onResumeMismatch = opts._onResumeMismatch;
150
+
151
+ let stopped = false;
152
+ let byteOffset = 0;
153
+ let timer: ReturnType<typeof setTimeout> | null = null;
154
+
155
+ const poll = async (): Promise<void> => {
156
+ if (stopped) return;
157
+
158
+ try {
159
+ const file = Bun.file(stdoutLogPath);
160
+ const size = file.size;
161
+
162
+ if (size > byteOffset) {
163
+ // Read only new bytes since last poll — avoids re-processing old lines.
164
+ const newContent = await file.slice(byteOffset, size).text();
165
+ byteOffset = size;
166
+
167
+ const lines = newContent.split("\n");
168
+ for (const line of lines) {
169
+ const trimmed = line.trim();
170
+ if (!trimmed) continue;
171
+
172
+ let event: Record<string, unknown>;
173
+ try {
174
+ event = JSON.parse(trimmed) as Record<string, unknown>;
175
+ } catch {
176
+ // Skip malformed lines — partial writes or debug output.
177
+ continue;
178
+ }
179
+
180
+ const type = typeof event.type === "string" ? event.type : "custom";
181
+ const eventType = mapEventType(type);
182
+ const level = type === "error" ? "error" : "info";
183
+
184
+ // Extract tool name from various field names runtimes may use.
185
+ let toolName: string | null = null;
186
+ if (typeof event.tool === "string") {
187
+ toolName = event.tool;
188
+ } else if (typeof event.tool_name === "string") {
189
+ toolName = event.tool_name;
190
+ } else if (typeof event.toolName === "string") {
191
+ toolName = event.toolName;
192
+ }
193
+
194
+ const toolDurationMs = typeof event.duration_ms === "number" ? event.duration_ms : null;
195
+
196
+ // Extract session_id from stream-json system events (e.g. Claude Code
197
+ // emits `{type:"system", subtype:"init", session_id:"sess-..."}` on
198
+ // every spawn — including --resume spawns, which assign a fresh id).
199
+ // The "result" event also carries session_id; treat both as authoritative.
200
+ // Single-fire per tailer lifetime so we don't churn writes.
201
+ if (!sessionIdPinned && sessionStore !== null) {
202
+ const sid =
203
+ typeof event.session_id === "string" && event.session_id.length > 0
204
+ ? event.session_id
205
+ : null;
206
+ if (sid !== null && (type === "system" || type === "result")) {
207
+ sessionIdPinned = true;
208
+ let prior: string | null = null;
209
+ try {
210
+ prior = sessionStore.getByName(agentName)?.claudeSessionId ?? null;
211
+ } catch {
212
+ prior = null;
213
+ }
214
+ try {
215
+ sessionStore.updateClaudeSessionId(agentName, sid);
216
+ } catch {
217
+ // Non-fatal: SessionStore write failure must not break tailing.
218
+ }
219
+ // Resume mismatch: requested != observed. The observed id wins
220
+ // (claude assigns fresh ids on --resume), but operators need to
221
+ // know — log a warning, and call DI hook for tests.
222
+ if (prior !== null && prior !== sid) {
223
+ if (onResumeMismatch) {
224
+ try {
225
+ onResumeMismatch(agentName, prior, sid);
226
+ } catch {
227
+ // DI hook errors must not crash the tailer.
228
+ }
229
+ } else {
230
+ process.stderr.write(
231
+ `[tailer] resume mismatch for ${agentName}: requested=${prior} observed=${sid}\n`,
232
+ );
233
+ }
234
+ }
235
+ }
236
+ }
237
+
238
+ try {
239
+ eventStore?.insert({
240
+ runId,
241
+ agentName,
242
+ sessionId: null,
243
+ eventType,
244
+ toolName,
245
+ toolArgs: null,
246
+ toolDurationMs,
247
+ level,
248
+ data: JSON.stringify(event),
249
+ });
250
+ } catch {
251
+ // DB write failure is non-fatal.
252
+ }
253
+ }
254
+ }
255
+ } catch {
256
+ // File read failure is non-fatal — agent may not have started writing yet.
257
+ }
258
+
259
+ if (!stopped) {
260
+ timer = setTimeout(poll, pollIntervalMs);
261
+ }
262
+ };
263
+
264
+ // Schedule first poll.
265
+ timer = setTimeout(poll, pollIntervalMs);
266
+
267
+ return {
268
+ agentName,
269
+ logPath: stdoutLogPath,
270
+ stop() {
271
+ stopped = true;
272
+ if (timer !== null) {
273
+ clearTimeout(timer);
274
+ timer = null;
275
+ }
276
+ // Close only the EventStore this tailer owns (not the injected one).
277
+ if (ownedEventStore && eventStore) {
278
+ try {
279
+ eventStore.close();
280
+ } catch {
281
+ // Non-fatal.
282
+ }
283
+ eventStore = null;
284
+ }
285
+ // Close only the SessionStore this tailer owns.
286
+ if (ownedSessionStore && sessionStore) {
287
+ try {
288
+ sessionStore.close();
289
+ } catch {
290
+ // Non-fatal.
291
+ }
292
+ sessionStore = null;
293
+ }
294
+ },
295
+ };
296
+ }
297
+
298
+ /**
299
+ * Discover the most recent stdout.log path for a headless agent.
300
+ *
301
+ * Scans .agentplate/logs/{agentName}/ for timestamped session directories and
302
+ * returns the stdout.log path from the lexicographically last directory.
303
+ * Directories use ISO timestamps with `-` replacing `.` and `:`, which sort
304
+ * correctly in lexicographic order (e.g. 2026-03-05T14-52-26-089Z).
305
+ *
306
+ * Returns null if no log directory exists or no stdout.log is found.
307
+ *
308
+ * @param agentplateDir - Absolute path to .agentplate/
309
+ * @param agentName - Agent name to look up (matches .agentplate/logs/{agentName}/)
310
+ */
311
+ export async function findLatestStdoutLog(
312
+ agentplateDir: string,
313
+ agentName: string,
314
+ ): Promise<string | null> {
315
+ const agentLogsDir = join(agentplateDir, "logs", agentName);
316
+ try {
317
+ const entries = await readdir(agentLogsDir);
318
+ if (entries.length === 0) return null;
319
+
320
+ // Lexicographic sort: ISO timestamps sort correctly without parsing.
321
+ const sorted = entries.sort();
322
+ const latest = sorted[sorted.length - 1];
323
+ if (!latest) return null;
324
+
325
+ const logPath = join(agentLogsDir, latest, "stdout.log");
326
+ const file = Bun.file(logPath);
327
+ if (await file.exists()) return logPath;
328
+ return null;
329
+ } catch {
330
+ return null;
331
+ }
332
+ }
@@ -0,0 +1,330 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { filterToolArgs } from "./tool-filter.ts";
3
+
4
+ describe("filterToolArgs", () => {
5
+ describe("Bash", () => {
6
+ test("keeps command and description, drops timeout/run_in_background/dangerouslyDisableSandbox", () => {
7
+ const result = filterToolArgs("Bash", {
8
+ command: "bun test",
9
+ description: "Run tests",
10
+ timeout: 60000,
11
+ run_in_background: true,
12
+ dangerouslyDisableSandbox: false,
13
+ });
14
+ expect(result.args).toEqual({
15
+ command: "bun test",
16
+ description: "Run tests",
17
+ });
18
+ expect(result.args).not.toHaveProperty("timeout");
19
+ expect(result.args).not.toHaveProperty("run_in_background");
20
+ expect(result.args).not.toHaveProperty("dangerouslyDisableSandbox");
21
+ });
22
+
23
+ test("summary shows first 80 chars of command", () => {
24
+ const result = filterToolArgs("Bash", { command: "bun test" });
25
+ expect(result.summary).toBe("bash: bun test");
26
+ });
27
+
28
+ test("summary truncates long commands at 80 chars", () => {
29
+ const longCmd = "a".repeat(120);
30
+ const result = filterToolArgs("Bash", { command: longCmd });
31
+ expect(result.summary).toBe(`bash: ${"a".repeat(80)}...`);
32
+ });
33
+
34
+ test("handles missing command gracefully", () => {
35
+ const result = filterToolArgs("Bash", {});
36
+ expect(result.args).toEqual({});
37
+ expect(result.summary).toBe("bash: ");
38
+ });
39
+ });
40
+
41
+ describe("Read", () => {
42
+ test("keeps file_path, offset, limit", () => {
43
+ const result = filterToolArgs("Read", {
44
+ file_path: "/src/index.ts",
45
+ offset: 10,
46
+ limit: 50,
47
+ });
48
+ expect(result.args).toEqual({
49
+ file_path: "/src/index.ts",
50
+ offset: 10,
51
+ limit: 50,
52
+ });
53
+ });
54
+
55
+ test("summary with offset and limit shows line range", () => {
56
+ const result = filterToolArgs("Read", {
57
+ file_path: "/src/index.ts",
58
+ offset: 10,
59
+ limit: 50,
60
+ });
61
+ expect(result.summary).toBe("read: /src/index.ts (lines 10-60)");
62
+ });
63
+
64
+ test("summary with only offset shows from line", () => {
65
+ const result = filterToolArgs("Read", {
66
+ file_path: "/src/index.ts",
67
+ offset: 10,
68
+ });
69
+ expect(result.summary).toBe("read: /src/index.ts (from line 10)");
70
+ });
71
+
72
+ test("summary with only limit shows first N lines", () => {
73
+ const result = filterToolArgs("Read", {
74
+ file_path: "/src/index.ts",
75
+ limit: 50,
76
+ });
77
+ expect(result.summary).toBe("read: /src/index.ts (first 50 lines)");
78
+ });
79
+
80
+ test("summary without offset or limit shows just path", () => {
81
+ const result = filterToolArgs("Read", {
82
+ file_path: "/src/index.ts",
83
+ });
84
+ expect(result.summary).toBe("read: /src/index.ts");
85
+ });
86
+
87
+ test("handles missing file_path", () => {
88
+ const result = filterToolArgs("Read", {});
89
+ expect(result.summary).toBe("read: ");
90
+ });
91
+ });
92
+
93
+ describe("Write", () => {
94
+ test("keeps file_path, drops content", () => {
95
+ const result = filterToolArgs("Write", {
96
+ file_path: "/src/index.ts",
97
+ content: "const x = 1;\nconst y = 2;\n// lots of content...",
98
+ });
99
+ expect(result.args).toEqual({ file_path: "/src/index.ts" });
100
+ expect(result.args).not.toHaveProperty("content");
101
+ });
102
+
103
+ test("summary shows file path", () => {
104
+ const result = filterToolArgs("Write", {
105
+ file_path: "/src/index.ts",
106
+ content: "stuff",
107
+ });
108
+ expect(result.summary).toBe("write: /src/index.ts");
109
+ });
110
+
111
+ test("handles missing file_path", () => {
112
+ const result = filterToolArgs("Write", { content: "data" });
113
+ expect(result.args).toEqual({});
114
+ expect(result.summary).toBe("write: ");
115
+ });
116
+ });
117
+
118
+ describe("Edit", () => {
119
+ test("keeps file_path, drops old_string and new_string", () => {
120
+ const result = filterToolArgs("Edit", {
121
+ file_path: "/src/config.ts",
122
+ old_string: "const x = 1;",
123
+ new_string: "const x = 2;",
124
+ });
125
+ expect(result.args).toEqual({ file_path: "/src/config.ts" });
126
+ expect(result.args).not.toHaveProperty("old_string");
127
+ expect(result.args).not.toHaveProperty("new_string");
128
+ });
129
+
130
+ test("summary shows file path", () => {
131
+ const result = filterToolArgs("Edit", {
132
+ file_path: "/src/config.ts",
133
+ old_string: "a",
134
+ new_string: "b",
135
+ });
136
+ expect(result.summary).toBe("edit: /src/config.ts");
137
+ });
138
+ });
139
+
140
+ describe("Glob", () => {
141
+ test("keeps pattern and path", () => {
142
+ const result = filterToolArgs("Glob", {
143
+ pattern: "**/*.ts",
144
+ path: "/src",
145
+ });
146
+ expect(result.args).toEqual({ pattern: "**/*.ts", path: "/src" });
147
+ });
148
+
149
+ test("summary with path shows pattern in path", () => {
150
+ const result = filterToolArgs("Glob", {
151
+ pattern: "**/*.ts",
152
+ path: "/src",
153
+ });
154
+ expect(result.summary).toBe("glob: **/*.ts in /src");
155
+ });
156
+
157
+ test("summary without path shows only pattern", () => {
158
+ const result = filterToolArgs("Glob", { pattern: "**/*.ts" });
159
+ expect(result.summary).toBe("glob: **/*.ts");
160
+ });
161
+ });
162
+
163
+ describe("Grep", () => {
164
+ test("keeps pattern, path, glob, output_mode", () => {
165
+ const result = filterToolArgs("Grep", {
166
+ pattern: "function\\s+\\w+",
167
+ path: "/src",
168
+ glob: "*.ts",
169
+ output_mode: "content",
170
+ "-A": 3,
171
+ "-B": 2,
172
+ });
173
+ expect(result.args).toEqual({
174
+ pattern: "function\\s+\\w+",
175
+ path: "/src",
176
+ glob: "*.ts",
177
+ output_mode: "content",
178
+ });
179
+ expect(result.args).not.toHaveProperty("-A");
180
+ expect(result.args).not.toHaveProperty("-B");
181
+ });
182
+
183
+ test("summary with path shows pattern in path", () => {
184
+ const result = filterToolArgs("Grep", {
185
+ pattern: "TODO",
186
+ path: "/src",
187
+ });
188
+ expect(result.summary).toBe('grep: "TODO" in /src');
189
+ });
190
+
191
+ test("summary without path shows only pattern", () => {
192
+ const result = filterToolArgs("Grep", { pattern: "TODO" });
193
+ expect(result.summary).toBe('grep: "TODO"');
194
+ });
195
+ });
196
+
197
+ describe("WebFetch", () => {
198
+ test("keeps url, drops prompt", () => {
199
+ const result = filterToolArgs("WebFetch", {
200
+ url: "https://example.com/page",
201
+ prompt: "Extract the main content from this page",
202
+ });
203
+ expect(result.args).toEqual({ url: "https://example.com/page" });
204
+ expect(result.args).not.toHaveProperty("prompt");
205
+ });
206
+
207
+ test("summary shows url", () => {
208
+ const result = filterToolArgs("WebFetch", {
209
+ url: "https://example.com",
210
+ });
211
+ expect(result.summary).toBe("fetch: https://example.com");
212
+ });
213
+ });
214
+
215
+ describe("WebSearch", () => {
216
+ test("keeps query, drops domain filters", () => {
217
+ const result = filterToolArgs("WebSearch", {
218
+ query: "TypeScript strict mode",
219
+ allowed_domains: ["developer.mozilla.org"],
220
+ blocked_domains: ["w3schools.com"],
221
+ });
222
+ expect(result.args).toEqual({ query: "TypeScript strict mode" });
223
+ expect(result.args).not.toHaveProperty("allowed_domains");
224
+ expect(result.args).not.toHaveProperty("blocked_domains");
225
+ });
226
+
227
+ test("summary shows query", () => {
228
+ const result = filterToolArgs("WebSearch", {
229
+ query: "bun test runner",
230
+ });
231
+ expect(result.summary).toBe("search: bun test runner");
232
+ });
233
+ });
234
+
235
+ describe("Task", () => {
236
+ test("keeps description and subagent_type", () => {
237
+ const result = filterToolArgs("Task", {
238
+ description: "Analyze the codebase structure",
239
+ subagent_type: "research",
240
+ prompt: "Look at all the files and determine...",
241
+ });
242
+ expect(result.args).toEqual({
243
+ description: "Analyze the codebase structure",
244
+ subagent_type: "research",
245
+ });
246
+ expect(result.args).not.toHaveProperty("prompt");
247
+ });
248
+
249
+ test("summary with subagent_type shows description and type", () => {
250
+ const result = filterToolArgs("Task", {
251
+ description: "Find all config files",
252
+ subagent_type: "research",
253
+ });
254
+ expect(result.summary).toBe("task: Find all config files (research)");
255
+ });
256
+
257
+ test("summary without subagent_type shows only description", () => {
258
+ const result = filterToolArgs("Task", {
259
+ description: "Find all config files",
260
+ });
261
+ expect(result.summary).toBe("task: Find all config files");
262
+ });
263
+ });
264
+
265
+ describe("unknown tools", () => {
266
+ test("returns empty args and tool name as summary", () => {
267
+ const result = filterToolArgs("SomeUnknownTool", {
268
+ foo: "bar",
269
+ baz: 42,
270
+ });
271
+ expect(result.args).toEqual({});
272
+ expect(result.summary).toBe("SomeUnknownTool");
273
+ });
274
+
275
+ test("handles empty input for unknown tool", () => {
276
+ const result = filterToolArgs("Mystery", {});
277
+ expect(result.args).toEqual({});
278
+ expect(result.summary).toBe("Mystery");
279
+ });
280
+ });
281
+
282
+ describe("edge cases", () => {
283
+ test("handles empty input object for known tool", () => {
284
+ const result = filterToolArgs("Bash", {});
285
+ expect(result.args).toEqual({});
286
+ expect(result.summary).toBe("bash: ");
287
+ });
288
+
289
+ test("handles null values in input", () => {
290
+ const result = filterToolArgs("Read", {
291
+ file_path: null as unknown as string,
292
+ offset: null as unknown as number,
293
+ });
294
+ // null is not undefined, so file_path will be picked but summary treats it as non-string
295
+ expect(result.args).toHaveProperty("file_path");
296
+ expect(result.summary).toBe("read: ");
297
+ });
298
+
299
+ test("handles undefined values in input", () => {
300
+ const result = filterToolArgs("Read", {
301
+ file_path: undefined as unknown as string,
302
+ });
303
+ // undefined values should not appear in filtered args
304
+ expect(result.args).not.toHaveProperty("file_path");
305
+ expect(result.summary).toBe("read: ");
306
+ });
307
+
308
+ test("handles numeric values where strings expected", () => {
309
+ const result = filterToolArgs("Bash", {
310
+ command: 42 as unknown as string,
311
+ });
312
+ // Value is kept in args as-is, but summary treats non-strings as empty
313
+ expect(result.args).toEqual({ command: 42 });
314
+ expect(result.summary).toBe("bash: ");
315
+ });
316
+
317
+ test("preserves exact 80-char command without truncation", () => {
318
+ const cmd = "x".repeat(80);
319
+ const result = filterToolArgs("Bash", { command: cmd });
320
+ expect(result.summary).toBe(`bash: ${cmd}`);
321
+ expect(result.summary).not.toContain("...");
322
+ });
323
+
324
+ test("truncates 81-char command", () => {
325
+ const cmd = "y".repeat(81);
326
+ const result = filterToolArgs("Bash", { command: cmd });
327
+ expect(result.summary).toBe(`bash: ${"y".repeat(80)}...`);
328
+ });
329
+ });
330
+ });