@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,1239 @@
1
+ /**
2
+ * Tests for `agentplate inspect` command.
3
+ *
4
+ * Uses real bun:sqlite (temp files) to test the inspect command end-to-end.
5
+ * Captures process.stdout.write to verify output formatting.
6
+ *
7
+ * Real implementations used for: filesystem (temp dirs), SQLite (EventStore,
8
+ * SessionStore, MetricsStore). No mocks needed -- all dependencies are cheap and local.
9
+ */
10
+
11
+ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
+ import { mkdir, mkdtemp } from "node:fs/promises";
13
+ import { tmpdir } from "node:os";
14
+ import { join } from "node:path";
15
+ import { ValidationError } from "../errors.ts";
16
+ import { createEventStore } from "../events/store.ts";
17
+ import { createMetricsStore } from "../metrics/store.ts";
18
+ import { createSessionStore } from "../sessions/store.ts";
19
+ import { cleanupTempDir } from "../test-helpers.ts";
20
+ import type { InsertEvent, SessionMetrics } from "../types.ts";
21
+ import { gatherInspectData, inspectCommand, printInspectData } from "./inspect.ts";
22
+
23
+ /** Helper to create an InsertEvent with sensible defaults. */
24
+ function makeEvent(overrides: Partial<InsertEvent> = {}): InsertEvent {
25
+ return {
26
+ runId: "run-001",
27
+ agentName: "builder-1",
28
+ sessionId: "sess-abc",
29
+ eventType: "tool_start",
30
+ toolName: "Read",
31
+ toolArgs: '{"file_path": "src/index.ts"}',
32
+ toolDurationMs: null,
33
+ level: "info",
34
+ data: null,
35
+ ...overrides,
36
+ };
37
+ }
38
+
39
+ /** Helper to create a SessionMetrics with sensible defaults. */
40
+ function makeMetrics(overrides: Partial<SessionMetrics> = {}): SessionMetrics {
41
+ return {
42
+ agentName: "builder-1",
43
+ taskId: "agentplate-001",
44
+ capability: "builder",
45
+ startedAt: new Date().toISOString(),
46
+ completedAt: null,
47
+ durationMs: 0,
48
+ exitCode: null,
49
+ mergeResult: null,
50
+ parentAgent: null,
51
+ inputTokens: 1000,
52
+ outputTokens: 500,
53
+ cacheReadTokens: 200,
54
+ cacheCreationTokens: 100,
55
+ estimatedCostUsd: 0.025,
56
+ modelUsed: "claude-sonnet-4-5-20250929",
57
+ runId: null,
58
+ ...overrides,
59
+ };
60
+ }
61
+
62
+ describe("inspectCommand", () => {
63
+ let chunks: string[];
64
+ let originalWrite: typeof process.stdout.write;
65
+ let tempDir: string;
66
+ let originalCwd: string;
67
+
68
+ beforeEach(async () => {
69
+ // Spy on stdout
70
+ chunks = [];
71
+ originalWrite = process.stdout.write;
72
+ process.stdout.write = ((chunk: string) => {
73
+ chunks.push(chunk);
74
+ return true;
75
+ }) as typeof process.stdout.write;
76
+
77
+ // Create temp dir with .agentplate/config.yaml structure
78
+ tempDir = await mkdtemp(join(tmpdir(), "inspect-test-"));
79
+ const agentplateDir = join(tempDir, ".agentplate");
80
+ await Bun.write(
81
+ join(agentplateDir, "config.yaml"),
82
+ `project:\n name: test\n root: ${tempDir}\n canonicalBranch: main\n`,
83
+ );
84
+
85
+ // Change to temp dir so loadConfig() works
86
+ originalCwd = process.cwd();
87
+ process.chdir(tempDir);
88
+ });
89
+
90
+ afterEach(async () => {
91
+ process.stdout.write = originalWrite;
92
+ process.chdir(originalCwd);
93
+ await cleanupTempDir(tempDir);
94
+ });
95
+
96
+ function output(): string {
97
+ return chunks.join("");
98
+ }
99
+
100
+ // === Help flag ===
101
+
102
+ describe("help flag", () => {
103
+ test("--help shows help text", async () => {
104
+ await inspectCommand(["--help"]);
105
+ const out = output();
106
+ expect(out).toContain("inspect");
107
+ expect(out).toContain("--json");
108
+ expect(out).toContain("--follow");
109
+ expect(out).toContain("--limit");
110
+ expect(out).toContain("--no-tmux");
111
+ });
112
+
113
+ test("-h shows help text", async () => {
114
+ await inspectCommand(["-h"]);
115
+ const out = output();
116
+ expect(out).toContain("inspect");
117
+ });
118
+ });
119
+
120
+ // === Validation errors ===
121
+
122
+ describe("validation", () => {
123
+ test("throws if no agent name provided", async () => {
124
+ await expect(inspectCommand([])).rejects.toThrow(ValidationError);
125
+ });
126
+
127
+ test("throws if agent not found", async () => {
128
+ const agentplateDir = join(tempDir, ".agentplate");
129
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
130
+ const store = createSessionStore(sessionsDbPath);
131
+ store.close();
132
+
133
+ await expect(inspectCommand(["nonexistent-agent"])).rejects.toThrow(ValidationError);
134
+ });
135
+
136
+ test("throws if --interval is invalid", async () => {
137
+ const agentplateDir = join(tempDir, ".agentplate");
138
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
139
+ const store = createSessionStore(sessionsDbPath);
140
+ store.upsert({
141
+ id: "sess-1",
142
+ agentName: "builder-1",
143
+ capability: "builder",
144
+ worktreePath: "/tmp/wt",
145
+ branchName: "agentplate/builder-1/test",
146
+ taskId: "agentplate-001",
147
+ tmuxSession: "agentplate-test-builder-1",
148
+ state: "working",
149
+ pid: 12345,
150
+ parentAgent: null,
151
+ depth: 0,
152
+ runId: null,
153
+ startedAt: new Date().toISOString(),
154
+ lastActivity: new Date().toISOString(),
155
+ escalationLevel: 0,
156
+ stalledSince: null,
157
+ transcriptPath: null,
158
+ });
159
+ store.close();
160
+
161
+ await expect(inspectCommand(["builder-1", "--interval", "abc"])).rejects.toThrow(
162
+ ValidationError,
163
+ );
164
+ await expect(inspectCommand(["builder-1", "--interval", "100"])).rejects.toThrow(
165
+ ValidationError,
166
+ );
167
+ });
168
+
169
+ test("throws if --limit is invalid", async () => {
170
+ const agentplateDir = join(tempDir, ".agentplate");
171
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
172
+ const store = createSessionStore(sessionsDbPath);
173
+ store.upsert({
174
+ id: "sess-1",
175
+ agentName: "builder-1",
176
+ capability: "builder",
177
+ worktreePath: "/tmp/wt",
178
+ branchName: "agentplate/builder-1/test",
179
+ taskId: "agentplate-001",
180
+ tmuxSession: "agentplate-test-builder-1",
181
+ state: "working",
182
+ pid: 12345,
183
+ parentAgent: null,
184
+ depth: 0,
185
+ runId: null,
186
+ startedAt: new Date().toISOString(),
187
+ lastActivity: new Date().toISOString(),
188
+ escalationLevel: 0,
189
+ stalledSince: null,
190
+ transcriptPath: null,
191
+ });
192
+ store.close();
193
+
194
+ await expect(inspectCommand(["builder-1", "--limit", "abc"])).rejects.toThrow(
195
+ ValidationError,
196
+ );
197
+ await expect(inspectCommand(["builder-1", "--limit", "0"])).rejects.toThrow(ValidationError);
198
+ });
199
+ });
200
+
201
+ // === gatherInspectData ===
202
+
203
+ describe("gatherInspectData", () => {
204
+ test("gathers basic session data", async () => {
205
+ const agentplateDir = join(tempDir, ".agentplate");
206
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
207
+ const store = createSessionStore(sessionsDbPath);
208
+
209
+ const startedAt = new Date(Date.now() - 60_000).toISOString(); // 60s ago
210
+ store.upsert({
211
+ id: "sess-1",
212
+ agentName: "builder-1",
213
+ capability: "builder",
214
+ worktreePath: "/tmp/wt",
215
+ branchName: "agentplate/builder-1/test",
216
+ taskId: "agentplate-001",
217
+ tmuxSession: "agentplate-test-builder-1",
218
+ state: "working",
219
+ pid: 12345,
220
+ parentAgent: "orchestrator",
221
+ depth: 1,
222
+ runId: "run-001",
223
+ startedAt,
224
+ lastActivity: new Date(Date.now() - 5_000).toISOString(),
225
+ escalationLevel: 0,
226
+ stalledSince: null,
227
+ transcriptPath: null,
228
+ });
229
+ store.close();
230
+
231
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
232
+
233
+ expect(data.session.agentName).toBe("builder-1");
234
+ expect(data.session.capability).toBe("builder");
235
+ expect(data.session.state).toBe("working");
236
+ expect(data.session.taskId).toBe("agentplate-001");
237
+ expect(data.timeSinceLastActivity).toBeGreaterThan(4000);
238
+ expect(data.timeSinceLastActivity).toBeLessThan(10000);
239
+ });
240
+
241
+ test("extracts current file from recent Edit tool_start event", async () => {
242
+ const agentplateDir = join(tempDir, ".agentplate");
243
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
244
+ const eventsDbPath = join(agentplateDir, "events.db");
245
+
246
+ const store = createSessionStore(sessionsDbPath);
247
+ store.upsert({
248
+ id: "sess-1",
249
+ agentName: "builder-1",
250
+ capability: "builder",
251
+ worktreePath: "/tmp/wt",
252
+ branchName: "agentplate/builder-1/test",
253
+ taskId: "agentplate-001",
254
+ tmuxSession: "agentplate-test-builder-1",
255
+ state: "working",
256
+ pid: 12345,
257
+ parentAgent: null,
258
+ depth: 0,
259
+ runId: null,
260
+ startedAt: new Date().toISOString(),
261
+ lastActivity: new Date().toISOString(),
262
+ escalationLevel: 0,
263
+ stalledSince: null,
264
+ transcriptPath: null,
265
+ });
266
+ store.close();
267
+
268
+ const eventStore = createEventStore(eventsDbPath);
269
+ eventStore.insert(makeEvent({ toolName: "Read", toolArgs: '{"file_path": "src/a.ts"}' }));
270
+ eventStore.insert(
271
+ makeEvent({ toolName: "Edit", toolArgs: '{"file_path": "src/commands/inspect.ts"}' }),
272
+ );
273
+ eventStore.insert(makeEvent({ toolName: "Bash", toolArgs: '{"command": "bun test"}' }));
274
+ eventStore.close();
275
+
276
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
277
+
278
+ expect(data.currentFile).toBe("src/commands/inspect.ts");
279
+ });
280
+
281
+ test("extracts current file from Write tool_start with path field", async () => {
282
+ const agentplateDir = join(tempDir, ".agentplate");
283
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
284
+ const eventsDbPath = join(agentplateDir, "events.db");
285
+
286
+ const store = createSessionStore(sessionsDbPath);
287
+ store.upsert({
288
+ id: "sess-1",
289
+ agentName: "builder-1",
290
+ capability: "builder",
291
+ worktreePath: "/tmp/wt",
292
+ branchName: "agentplate/builder-1/test",
293
+ taskId: "agentplate-001",
294
+ tmuxSession: "agentplate-test-builder-1",
295
+ state: "working",
296
+ pid: 12345,
297
+ parentAgent: null,
298
+ depth: 0,
299
+ runId: null,
300
+ startedAt: new Date().toISOString(),
301
+ lastActivity: new Date().toISOString(),
302
+ escalationLevel: 0,
303
+ stalledSince: null,
304
+ transcriptPath: null,
305
+ });
306
+ store.close();
307
+
308
+ const eventStore = createEventStore(eventsDbPath);
309
+ eventStore.insert(makeEvent({ toolName: "Write", toolArgs: '{"path": "src/new-file.ts"}' }));
310
+ eventStore.close();
311
+
312
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
313
+
314
+ expect(data.currentFile).toBe("src/new-file.ts");
315
+ });
316
+
317
+ test("returns null current file if no Edit/Write/Read events", async () => {
318
+ const agentplateDir = join(tempDir, ".agentplate");
319
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
320
+ const eventsDbPath = join(agentplateDir, "events.db");
321
+
322
+ const store = createSessionStore(sessionsDbPath);
323
+ store.upsert({
324
+ id: "sess-1",
325
+ agentName: "builder-1",
326
+ capability: "builder",
327
+ worktreePath: "/tmp/wt",
328
+ branchName: "agentplate/builder-1/test",
329
+ taskId: "agentplate-001",
330
+ tmuxSession: "agentplate-test-builder-1",
331
+ state: "working",
332
+ pid: 12345,
333
+ parentAgent: null,
334
+ depth: 0,
335
+ runId: null,
336
+ startedAt: new Date().toISOString(),
337
+ lastActivity: new Date().toISOString(),
338
+ escalationLevel: 0,
339
+ stalledSince: null,
340
+ transcriptPath: null,
341
+ });
342
+ store.close();
343
+
344
+ const eventStore = createEventStore(eventsDbPath);
345
+ eventStore.insert(makeEvent({ toolName: "Bash", toolArgs: '{"command": "bun test"}' }));
346
+ eventStore.close();
347
+
348
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
349
+
350
+ expect(data.currentFile).toBeNull();
351
+ });
352
+
353
+ test("gathers recent tool calls (respects limit)", async () => {
354
+ const agentplateDir = join(tempDir, ".agentplate");
355
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
356
+ const eventsDbPath = join(agentplateDir, "events.db");
357
+
358
+ const store = createSessionStore(sessionsDbPath);
359
+ store.upsert({
360
+ id: "sess-1",
361
+ agentName: "builder-1",
362
+ capability: "builder",
363
+ worktreePath: "/tmp/wt",
364
+ branchName: "agentplate/builder-1/test",
365
+ taskId: "agentplate-001",
366
+ tmuxSession: "agentplate-test-builder-1",
367
+ state: "working",
368
+ pid: 12345,
369
+ parentAgent: null,
370
+ depth: 0,
371
+ runId: null,
372
+ startedAt: new Date().toISOString(),
373
+ lastActivity: new Date().toISOString(),
374
+ escalationLevel: 0,
375
+ stalledSince: null,
376
+ transcriptPath: null,
377
+ });
378
+ store.close();
379
+
380
+ const eventStore = createEventStore(eventsDbPath);
381
+ for (let i = 0; i < 30; i++) {
382
+ eventStore.insert(
383
+ makeEvent({
384
+ toolName: "Read",
385
+ toolArgs: `{"file_path": "src/file${i}.ts"}`,
386
+ toolDurationMs: 10 + i,
387
+ }),
388
+ );
389
+ }
390
+ eventStore.close();
391
+
392
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true, limit: 5 });
393
+
394
+ expect(data.recentToolCalls.length).toBe(5);
395
+ expect(data.recentToolCalls[0]?.toolName).toBe("Read");
396
+ });
397
+
398
+ test("gathers tool stats", async () => {
399
+ const agentplateDir = join(tempDir, ".agentplate");
400
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
401
+ const eventsDbPath = join(agentplateDir, "events.db");
402
+
403
+ const store = createSessionStore(sessionsDbPath);
404
+ store.upsert({
405
+ id: "sess-1",
406
+ agentName: "builder-1",
407
+ capability: "builder",
408
+ worktreePath: "/tmp/wt",
409
+ branchName: "agentplate/builder-1/test",
410
+ taskId: "agentplate-001",
411
+ tmuxSession: "agentplate-test-builder-1",
412
+ state: "working",
413
+ pid: 12345,
414
+ parentAgent: null,
415
+ depth: 0,
416
+ runId: null,
417
+ startedAt: new Date().toISOString(),
418
+ lastActivity: new Date().toISOString(),
419
+ escalationLevel: 0,
420
+ stalledSince: null,
421
+ transcriptPath: null,
422
+ });
423
+ store.close();
424
+
425
+ const eventStore = createEventStore(eventsDbPath);
426
+ for (let i = 0; i < 10; i++) {
427
+ eventStore.insert(makeEvent({ toolName: "Read", toolDurationMs: 100 }));
428
+ }
429
+ for (let i = 0; i < 5; i++) {
430
+ eventStore.insert(makeEvent({ toolName: "Edit", toolDurationMs: 200 }));
431
+ }
432
+ eventStore.close();
433
+
434
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
435
+
436
+ expect(data.toolStats.length).toBeGreaterThan(0);
437
+ const readStats = data.toolStats.find((s) => s.toolName === "Read");
438
+ expect(readStats?.count).toBe(10);
439
+ expect(readStats?.avgDurationMs).toBe(100);
440
+ });
441
+
442
+ test("gathers token usage from metrics", async () => {
443
+ const agentplateDir = join(tempDir, ".agentplate");
444
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
445
+ const metricsDbPath = join(agentplateDir, "metrics.db");
446
+
447
+ const store = createSessionStore(sessionsDbPath);
448
+ store.upsert({
449
+ id: "sess-1",
450
+ agentName: "builder-1",
451
+ capability: "builder",
452
+ worktreePath: "/tmp/wt",
453
+ branchName: "agentplate/builder-1/test",
454
+ taskId: "agentplate-001",
455
+ tmuxSession: "agentplate-test-builder-1",
456
+ state: "working",
457
+ pid: 12345,
458
+ parentAgent: null,
459
+ depth: 0,
460
+ runId: null,
461
+ startedAt: new Date().toISOString(),
462
+ lastActivity: new Date().toISOString(),
463
+ escalationLevel: 0,
464
+ stalledSince: null,
465
+ transcriptPath: null,
466
+ });
467
+ store.close();
468
+
469
+ const metricsStore = createMetricsStore(metricsDbPath);
470
+ metricsStore.recordSession(
471
+ makeMetrics({
472
+ inputTokens: 5000,
473
+ outputTokens: 3000,
474
+ cacheReadTokens: 1000,
475
+ cacheCreationTokens: 500,
476
+ estimatedCostUsd: 0.123,
477
+ modelUsed: "claude-sonnet-4-5-20250929",
478
+ }),
479
+ );
480
+ metricsStore.close();
481
+
482
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
483
+
484
+ expect(data.tokenUsage).not.toBeNull();
485
+ expect(data.tokenUsage?.inputTokens).toBe(5000);
486
+ expect(data.tokenUsage?.outputTokens).toBe(3000);
487
+ expect(data.tokenUsage?.cacheReadTokens).toBe(1000);
488
+ expect(data.tokenUsage?.cacheCreationTokens).toBe(500);
489
+ expect(data.tokenUsage?.estimatedCostUsd).toBe(0.123);
490
+ expect(data.tokenUsage?.modelUsed).toBe("claude-sonnet-4-5-20250929");
491
+ });
492
+
493
+ test("handles missing databases gracefully", async () => {
494
+ const agentplateDir = join(tempDir, ".agentplate");
495
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
496
+
497
+ const store = createSessionStore(sessionsDbPath);
498
+ store.upsert({
499
+ id: "sess-1",
500
+ agentName: "builder-1",
501
+ capability: "builder",
502
+ worktreePath: "/tmp/wt",
503
+ branchName: "agentplate/builder-1/test",
504
+ taskId: "agentplate-001",
505
+ tmuxSession: "agentplate-test-builder-1",
506
+ state: "working",
507
+ pid: 12345,
508
+ parentAgent: null,
509
+ depth: 0,
510
+ runId: null,
511
+ startedAt: new Date().toISOString(),
512
+ lastActivity: new Date().toISOString(),
513
+ escalationLevel: 0,
514
+ stalledSince: null,
515
+ transcriptPath: null,
516
+ });
517
+ store.close();
518
+
519
+ // Don't create events.db or metrics.db
520
+ const data = await gatherInspectData(tempDir, "builder-1", { noTmux: true });
521
+
522
+ expect(data.recentToolCalls).toEqual([]);
523
+ expect(data.currentFile).toBeNull();
524
+ expect(data.toolStats).toEqual([]);
525
+ expect(data.tokenUsage).toBeNull();
526
+ });
527
+ });
528
+
529
+ // === JSON output ===
530
+
531
+ describe("json output", () => {
532
+ test("--json outputs valid JSON", async () => {
533
+ const agentplateDir = join(tempDir, ".agentplate");
534
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
535
+ const store = createSessionStore(sessionsDbPath);
536
+ store.upsert({
537
+ id: "sess-1",
538
+ agentName: "builder-1",
539
+ capability: "builder",
540
+ worktreePath: "/tmp/wt",
541
+ branchName: "agentplate/builder-1/test",
542
+ taskId: "agentplate-001",
543
+ tmuxSession: "agentplate-test-builder-1",
544
+ state: "working",
545
+ pid: 12345,
546
+ parentAgent: null,
547
+ depth: 0,
548
+ runId: null,
549
+ startedAt: new Date().toISOString(),
550
+ lastActivity: new Date().toISOString(),
551
+ escalationLevel: 0,
552
+ stalledSince: null,
553
+ transcriptPath: null,
554
+ });
555
+ store.close();
556
+
557
+ await inspectCommand(["builder-1", "--json", "--no-tmux"]);
558
+ const out = output();
559
+
560
+ const parsed = JSON.parse(out);
561
+ expect(parsed.success).toBe(true);
562
+ expect(parsed.command).toBe("inspect");
563
+ expect(parsed.session.agentName).toBe("builder-1");
564
+ expect(parsed.timeSinceLastActivity).toBeGreaterThan(0);
565
+ });
566
+ });
567
+
568
+ // === Headless agent support ===
569
+
570
+ describe("headless agent support", () => {
571
+ test("gatherInspectData skips tmux capture for headless agents (empty tmuxSession)", async () => {
572
+ const agentplateDir = join(tempDir, ".agentplate");
573
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
574
+ const store = createSessionStore(sessionsDbPath);
575
+ store.upsert({
576
+ id: "sess-h1",
577
+ agentName: "headless-agent",
578
+ capability: "builder",
579
+ worktreePath: "/tmp/wt",
580
+ branchName: "agentplate/headless/task-1",
581
+ taskId: "agentplate-h01",
582
+ tmuxSession: "", // headless
583
+ state: "working",
584
+ pid: process.pid,
585
+ parentAgent: null,
586
+ depth: 0,
587
+ runId: null,
588
+ startedAt: new Date().toISOString(),
589
+ lastActivity: new Date().toISOString(),
590
+ escalationLevel: 0,
591
+ stalledSince: null,
592
+ transcriptPath: null,
593
+ });
594
+ store.close();
595
+
596
+ // noTmux=false but tmuxSession="" — should skip tmux capture without error
597
+ const data = await gatherInspectData(tempDir, "headless-agent", { noTmux: false });
598
+ // tmuxOutput is null (no tmux) and no events yet → no fallback either
599
+ expect(data.session.agentName).toBe("headless-agent");
600
+ expect(data.session.tmuxSession).toBe("");
601
+ // tmuxOutput may be null (no events) or a string (fallback) — must not throw
602
+ });
603
+
604
+ test("gatherInspectData provides event-based output for headless agents with tool calls", async () => {
605
+ const agentplateDir = join(tempDir, ".agentplate");
606
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
607
+ const eventsDbPath = join(agentplateDir, "events.db");
608
+
609
+ const store = createSessionStore(sessionsDbPath);
610
+ store.upsert({
611
+ id: "sess-h2",
612
+ agentName: "headless-events",
613
+ capability: "builder",
614
+ worktreePath: "/tmp/wt",
615
+ branchName: "agentplate/headless/task-2",
616
+ taskId: "agentplate-h02",
617
+ tmuxSession: "", // headless
618
+ state: "working",
619
+ pid: process.pid,
620
+ parentAgent: null,
621
+ depth: 0,
622
+ runId: null,
623
+ startedAt: new Date().toISOString(),
624
+ lastActivity: new Date().toISOString(),
625
+ escalationLevel: 0,
626
+ stalledSince: null,
627
+ transcriptPath: null,
628
+ });
629
+ store.close();
630
+
631
+ const eventStore = createEventStore(eventsDbPath);
632
+ eventStore.insert(
633
+ makeEvent({ agentName: "headless-events", toolName: "Read", toolDurationMs: 50 }),
634
+ );
635
+ eventStore.insert(
636
+ makeEvent({ agentName: "headless-events", toolName: "Edit", toolDurationMs: 100 }),
637
+ );
638
+ eventStore.close();
639
+
640
+ const data = await gatherInspectData(tempDir, "headless-events", { noTmux: false });
641
+
642
+ // Should have fallback output
643
+ expect(data.tmuxOutput).not.toBeNull();
644
+ expect(data.tmuxOutput).toContain("Headless agent");
645
+ expect(data.tmuxOutput).toContain("Read");
646
+ });
647
+
648
+ test("printInspectData shows PID instead of tmux session for headless agents", () => {
649
+ const data = {
650
+ session: {
651
+ id: "sess-h3",
652
+ agentName: "headless-display",
653
+ capability: "builder",
654
+ worktreePath: "/tmp/wt",
655
+ branchName: "agentplate/headless/task-3",
656
+ taskId: "agentplate-h03",
657
+ tmuxSession: "", // headless
658
+ state: "working" as const,
659
+ pid: 99999,
660
+ parentAgent: null,
661
+ depth: 0,
662
+ runId: null,
663
+ startedAt: new Date().toISOString(),
664
+ lastActivity: new Date().toISOString(),
665
+ escalationLevel: 0,
666
+ stalledSince: null,
667
+ transcriptPath: null,
668
+ },
669
+ timeSinceLastActivity: 5000,
670
+ recentToolCalls: [],
671
+ currentFile: null,
672
+ toolStats: [],
673
+ tokenUsage: null,
674
+ tmuxOutput: null,
675
+ headlessTurnInfo: null,
676
+ };
677
+
678
+ printInspectData(data);
679
+
680
+ const out = output();
681
+ expect(out).toContain("Process: PID");
682
+ expect(out).toContain("99999");
683
+ expect(out).toContain("headless");
684
+ expect(out).not.toContain("Tmux:");
685
+ });
686
+
687
+ test("printInspectData shows Recent Activity header for headless agents with tmuxOutput", () => {
688
+ const data = {
689
+ session: {
690
+ id: "sess-h4",
691
+ agentName: "headless-activity",
692
+ capability: "builder",
693
+ worktreePath: "/tmp/wt",
694
+ branchName: "agentplate/headless/task-4",
695
+ taskId: "agentplate-h04",
696
+ tmuxSession: "", // headless
697
+ state: "working" as const,
698
+ pid: 99998,
699
+ parentAgent: null,
700
+ depth: 0,
701
+ runId: null,
702
+ startedAt: new Date().toISOString(),
703
+ lastActivity: new Date().toISOString(),
704
+ escalationLevel: 0,
705
+ stalledSince: null,
706
+ transcriptPath: null,
707
+ },
708
+ timeSinceLastActivity: 5000,
709
+ recentToolCalls: [],
710
+ currentFile: null,
711
+ toolStats: [],
712
+ tokenUsage: null,
713
+ tmuxOutput: "[Headless agent — showing recent tool events]",
714
+ headlessTurnInfo: null,
715
+ };
716
+
717
+ printInspectData(data);
718
+
719
+ const out = output();
720
+ expect(out).toContain("Recent Activity (headless)");
721
+ expect(out).not.toContain("Live Tmux Output");
722
+ });
723
+ });
724
+
725
+ // === stdout.log fallback (headless agents) ===
726
+
727
+ describe("stdout.log fallback", () => {
728
+ /** Create a headless session in SessionStore and return the agentplateDir. */
729
+ async function setupHeadlessSession(
730
+ agentName: string,
731
+ worktreePathVal = "/tmp/wt",
732
+ ): Promise<string> {
733
+ const agentplateDir = join(tempDir, ".agentplate");
734
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
735
+ const store = createSessionStore(sessionsDbPath);
736
+ store.upsert({
737
+ id: `sess-${agentName}`,
738
+ agentName,
739
+ capability: "builder",
740
+ worktreePath: worktreePathVal,
741
+ branchName: `agentplate/headless/${agentName}`,
742
+ taskId: "agentplate-h10",
743
+ tmuxSession: "", // headless
744
+ state: "working",
745
+ pid: process.pid,
746
+ parentAgent: null,
747
+ depth: 0,
748
+ runId: null,
749
+ startedAt: new Date().toISOString(),
750
+ lastActivity: new Date().toISOString(),
751
+ escalationLevel: 0,
752
+ stalledSince: null,
753
+ transcriptPath: null,
754
+ });
755
+ store.close();
756
+ return agentplateDir;
757
+ }
758
+
759
+ /** Write NDJSON events to stdout.log in the agent's log dir. */
760
+ async function writeStdoutLog(
761
+ agentplateDir: string,
762
+ agentName: string,
763
+ events: Record<string, unknown>[],
764
+ ): Promise<void> {
765
+ const logDir = join(agentplateDir, "logs", agentName, "2026-03-05T14-30-00-000Z");
766
+ await mkdir(logDir, { recursive: true });
767
+ const ndjson = `${events.map((e) => JSON.stringify(e)).join("\n")}\n`;
768
+ await Bun.write(join(logDir, "stdout.log"), ndjson);
769
+ }
770
+
771
+ test("populates recentToolCalls from stdout.log when events.db is empty", async () => {
772
+ const agentplateDir = await setupHeadlessSession("stdout-tools");
773
+ await writeStdoutLog(agentplateDir, "stdout-tools", [
774
+ {
775
+ type: "tool_start",
776
+ timestamp: "2026-03-05T14:30:01.000Z",
777
+ toolName: "Read",
778
+ argsSummary: "src/index.ts",
779
+ },
780
+ {
781
+ type: "tool_end",
782
+ timestamp: "2026-03-05T14:30:01.050Z",
783
+ toolName: "Read",
784
+ success: true,
785
+ durationMs: 50,
786
+ },
787
+ {
788
+ type: "tool_start",
789
+ timestamp: "2026-03-05T14:30:02.000Z",
790
+ toolName: "Edit",
791
+ argsSummary: "src/commands/inspect.ts",
792
+ },
793
+ {
794
+ type: "tool_end",
795
+ timestamp: "2026-03-05T14:30:02.200Z",
796
+ toolName: "Edit",
797
+ success: true,
798
+ durationMs: 200,
799
+ },
800
+ ]);
801
+
802
+ const data = await gatherInspectData(tempDir, "stdout-tools", { noTmux: true });
803
+
804
+ expect(data.recentToolCalls.length).toBe(2);
805
+ expect(data.recentToolCalls[0]?.toolName).toBe("Read");
806
+ expect(data.recentToolCalls[0]?.durationMs).toBe(50);
807
+ expect(data.recentToolCalls[0]?.args).toBe("src/index.ts");
808
+ expect(data.recentToolCalls[1]?.toolName).toBe("Edit");
809
+ expect(data.recentToolCalls[1]?.durationMs).toBe(200);
810
+ });
811
+
812
+ test("populates tokenUsage from turn_end events when metrics.db is absent", async () => {
813
+ const agentplateDir = await setupHeadlessSession("stdout-tokens");
814
+ await writeStdoutLog(agentplateDir, "stdout-tokens", [
815
+ {
816
+ type: "turn_start",
817
+ timestamp: "2026-03-05T14:30:00.000Z",
818
+ turn: 1,
819
+ },
820
+ {
821
+ type: "turn_end",
822
+ timestamp: "2026-03-05T14:30:05.000Z",
823
+ inputTokens: 1000,
824
+ outputTokens: 500,
825
+ cacheReadTokens: 200,
826
+ model: "claude-sonnet-4-6",
827
+ contextUtilization: 0.3,
828
+ },
829
+ {
830
+ type: "turn_start",
831
+ timestamp: "2026-03-05T14:30:06.000Z",
832
+ turn: 2,
833
+ },
834
+ {
835
+ type: "turn_end",
836
+ timestamp: "2026-03-05T14:30:10.000Z",
837
+ inputTokens: 800,
838
+ outputTokens: 300,
839
+ cacheReadTokens: 150,
840
+ model: "claude-sonnet-4-6",
841
+ contextUtilization: 0.45,
842
+ },
843
+ ]);
844
+
845
+ const data = await gatherInspectData(tempDir, "stdout-tokens", { noTmux: true });
846
+
847
+ // Token usage should be cumulative across turn_end events
848
+ expect(data.tokenUsage).not.toBeNull();
849
+ expect(data.tokenUsage?.inputTokens).toBe(1800);
850
+ expect(data.tokenUsage?.outputTokens).toBe(800);
851
+ expect(data.tokenUsage?.cacheReadTokens).toBe(350);
852
+ expect(data.tokenUsage?.modelUsed).toBe("claude-sonnet-4-6");
853
+ expect(data.tokenUsage?.cacheCreationTokens).toBe(0);
854
+ expect(data.tokenUsage?.estimatedCostUsd).toBeNull();
855
+ });
856
+
857
+ test("populates headlessTurnInfo with turn number, context utilization, and isMidTool", async () => {
858
+ const agentplateDir = await setupHeadlessSession("stdout-turn-info");
859
+ await writeStdoutLog(agentplateDir, "stdout-turn-info", [
860
+ {
861
+ type: "turn_start",
862
+ timestamp: "2026-03-05T14:30:00.000Z",
863
+ turn: 3,
864
+ },
865
+ {
866
+ type: "tool_start",
867
+ timestamp: "2026-03-05T14:30:01.000Z",
868
+ toolName: "Bash",
869
+ argsSummary: "bun test",
870
+ },
871
+ // No tool_end — still mid-tool
872
+ ]);
873
+
874
+ const data = await gatherInspectData(tempDir, "stdout-turn-info", { noTmux: true });
875
+
876
+ expect(data.headlessTurnInfo).not.toBeNull();
877
+ expect(data.headlessTurnInfo?.currentTurn).toBe(3);
878
+ expect(data.headlessTurnInfo?.isMidTool).toBe(true);
879
+ });
880
+
881
+ test("isMidTool is false when last event is not tool_start", async () => {
882
+ const agentplateDir = await setupHeadlessSession("stdout-between-turns");
883
+ await writeStdoutLog(agentplateDir, "stdout-between-turns", [
884
+ {
885
+ type: "turn_start",
886
+ timestamp: "2026-03-05T14:30:00.000Z",
887
+ turn: 2,
888
+ },
889
+ {
890
+ type: "turn_end",
891
+ timestamp: "2026-03-05T14:30:05.000Z",
892
+ inputTokens: 500,
893
+ outputTokens: 200,
894
+ cacheReadTokens: 0,
895
+ model: "claude-sonnet-4-6",
896
+ contextUtilization: 0.2,
897
+ },
898
+ ]);
899
+
900
+ const data = await gatherInspectData(tempDir, "stdout-between-turns", { noTmux: true });
901
+
902
+ expect(data.headlessTurnInfo?.isMidTool).toBe(false);
903
+ expect(data.headlessTurnInfo?.contextUtilization).toBeCloseTo(0.2);
904
+ });
905
+
906
+ test("does not overwrite tokenUsage from metrics.db with stdout.log data", async () => {
907
+ const agentplateDir = await setupHeadlessSession("stdout-no-override");
908
+ const metricsDbPath = join(agentplateDir, "metrics.db");
909
+
910
+ // Metrics DB has authoritative data
911
+ const metricsStore = createMetricsStore(metricsDbPath);
912
+ metricsStore.recordSession(
913
+ makeMetrics({
914
+ agentName: "stdout-no-override",
915
+ inputTokens: 9999,
916
+ outputTokens: 8888,
917
+ modelUsed: "claude-opus-4-6",
918
+ }),
919
+ );
920
+ metricsStore.close();
921
+
922
+ // stdout.log also has token data
923
+ await writeStdoutLog(agentplateDir, "stdout-no-override", [
924
+ {
925
+ type: "turn_end",
926
+ timestamp: "2026-03-05T14:30:05.000Z",
927
+ inputTokens: 100,
928
+ outputTokens: 50,
929
+ cacheReadTokens: 0,
930
+ model: "claude-sonnet-4-6",
931
+ contextUtilization: 0.1,
932
+ },
933
+ ]);
934
+
935
+ const data = await gatherInspectData(tempDir, "stdout-no-override", { noTmux: true });
936
+
937
+ // metrics.db data wins
938
+ expect(data.tokenUsage?.inputTokens).toBe(9999);
939
+ expect(data.tokenUsage?.modelUsed).toBe("claude-opus-4-6");
940
+ });
941
+
942
+ test("gracefully handles missing stdout.log", async () => {
943
+ const agentplateDir = await setupHeadlessSession("stdout-missing");
944
+ // Create log dir but no stdout.log inside it
945
+ await mkdir(join(agentplateDir, "logs", "stdout-missing", "2026-03-05T14-30-00-000Z"), {
946
+ recursive: true,
947
+ });
948
+
949
+ const data = await gatherInspectData(tempDir, "stdout-missing", { noTmux: true });
950
+
951
+ expect(data.recentToolCalls).toEqual([]);
952
+ expect(data.tokenUsage).toBeNull();
953
+ expect(data.headlessTurnInfo).toBeNull();
954
+ });
955
+
956
+ test("gracefully handles no log dir at all", async () => {
957
+ await setupHeadlessSession("stdout-no-log-dir");
958
+ // Don't create any log dir
959
+
960
+ const data = await gatherInspectData(tempDir, "stdout-no-log-dir", { noTmux: true });
961
+
962
+ expect(data.recentToolCalls).toEqual([]);
963
+ expect(data.headlessTurnInfo).toBeNull();
964
+ });
965
+
966
+ test("respects limit when populating recentToolCalls from stdout.log", async () => {
967
+ const agentplateDir = await setupHeadlessSession("stdout-limit");
968
+ const events: Record<string, unknown>[] = [];
969
+ for (let i = 0; i < 10; i++) {
970
+ events.push({
971
+ type: "tool_start",
972
+ timestamp: `2026-03-05T14:30:0${i}.000Z`,
973
+ toolName: "Read",
974
+ argsSummary: `src/file${i}.ts`,
975
+ });
976
+ events.push({
977
+ type: "tool_end",
978
+ timestamp: `2026-03-05T14:30:0${i}.050Z`,
979
+ toolName: "Read",
980
+ success: true,
981
+ durationMs: 50,
982
+ });
983
+ }
984
+ await writeStdoutLog(agentplateDir, "stdout-limit", events);
985
+
986
+ const data = await gatherInspectData(tempDir, "stdout-limit", {
987
+ noTmux: true,
988
+ limit: 3,
989
+ });
990
+
991
+ expect(data.recentToolCalls.length).toBe(3);
992
+ });
993
+
994
+ test("printInspectData shows Turn Progress section when headlessTurnInfo is set", () => {
995
+ const data = {
996
+ session: {
997
+ id: "sess-tp",
998
+ agentName: "headless-turn-progress",
999
+ capability: "builder",
1000
+ worktreePath: "/tmp/wt",
1001
+ branchName: "agentplate/headless/tp",
1002
+ taskId: "agentplate-tp",
1003
+ tmuxSession: "",
1004
+ state: "working" as const,
1005
+ pid: 12345,
1006
+ parentAgent: null,
1007
+ depth: 0,
1008
+ runId: null,
1009
+ startedAt: new Date().toISOString(),
1010
+ lastActivity: new Date().toISOString(),
1011
+ escalationLevel: 0,
1012
+ stalledSince: null,
1013
+ transcriptPath: null,
1014
+ },
1015
+ timeSinceLastActivity: 1000,
1016
+ recentToolCalls: [],
1017
+ currentFile: null,
1018
+ toolStats: [],
1019
+ tokenUsage: null,
1020
+ tmuxOutput: null,
1021
+ headlessTurnInfo: {
1022
+ currentTurn: 5,
1023
+ contextUtilization: 0.625,
1024
+ isMidTool: false,
1025
+ },
1026
+ };
1027
+
1028
+ printInspectData(data);
1029
+
1030
+ const out = output();
1031
+ expect(out).toContain("Turn Progress");
1032
+ expect(out).toContain("5");
1033
+ expect(out).toContain("62.5%");
1034
+ expect(out).toContain("between turns");
1035
+ });
1036
+
1037
+ test("printInspectData shows executing tool status when isMidTool is true", () => {
1038
+ const data = {
1039
+ session: {
1040
+ id: "sess-mid",
1041
+ agentName: "headless-mid-tool",
1042
+ capability: "builder",
1043
+ worktreePath: "/tmp/wt",
1044
+ branchName: "agentplate/headless/mid",
1045
+ taskId: "agentplate-mid",
1046
+ tmuxSession: "",
1047
+ state: "working" as const,
1048
+ pid: 12345,
1049
+ parentAgent: null,
1050
+ depth: 0,
1051
+ runId: null,
1052
+ startedAt: new Date().toISOString(),
1053
+ lastActivity: new Date().toISOString(),
1054
+ escalationLevel: 0,
1055
+ stalledSince: null,
1056
+ transcriptPath: null,
1057
+ },
1058
+ timeSinceLastActivity: 500,
1059
+ recentToolCalls: [],
1060
+ currentFile: null,
1061
+ toolStats: [],
1062
+ tokenUsage: null,
1063
+ tmuxOutput: null,
1064
+ headlessTurnInfo: {
1065
+ currentTurn: 2,
1066
+ contextUtilization: null,
1067
+ isMidTool: true,
1068
+ },
1069
+ };
1070
+
1071
+ printInspectData(data);
1072
+
1073
+ const out = output();
1074
+ expect(out).toContain("Turn Progress");
1075
+ expect(out).toContain("executing tool");
1076
+ });
1077
+
1078
+ test("uses latest log dir when multiple exist", async () => {
1079
+ const agentplateDir = await setupHeadlessSession("stdout-multi-dir");
1080
+ const agentLogsDir = join(agentplateDir, "logs", "stdout-multi-dir");
1081
+
1082
+ // Create two log dirs — the later one has the important data
1083
+ const oldDir = join(agentLogsDir, "2026-03-05T10-00-00-000Z");
1084
+ const newDir = join(agentLogsDir, "2026-03-05T14-30-00-000Z");
1085
+ await mkdir(oldDir, { recursive: true });
1086
+ await mkdir(newDir, { recursive: true });
1087
+
1088
+ // Old dir: no useful data
1089
+ await Bun.write(join(oldDir, "stdout.log"), "");
1090
+
1091
+ // New dir: has turn data
1092
+ const events = [
1093
+ {
1094
+ type: "turn_start",
1095
+ timestamp: "2026-03-05T14:30:00.000Z",
1096
+ turn: 7,
1097
+ },
1098
+ {
1099
+ type: "turn_end",
1100
+ timestamp: "2026-03-05T14:30:05.000Z",
1101
+ inputTokens: 2000,
1102
+ outputTokens: 700,
1103
+ cacheReadTokens: 300,
1104
+ model: "claude-sonnet-4-6",
1105
+ contextUtilization: 0.55,
1106
+ },
1107
+ ];
1108
+ await Bun.write(
1109
+ join(newDir, "stdout.log"),
1110
+ `${events.map((e) => JSON.stringify(e)).join("\n")}\n`,
1111
+ );
1112
+
1113
+ const data = await gatherInspectData(tempDir, "stdout-multi-dir", { noTmux: true });
1114
+
1115
+ expect(data.headlessTurnInfo?.currentTurn).toBe(7);
1116
+ expect(data.tokenUsage?.inputTokens).toBe(2000);
1117
+ });
1118
+ });
1119
+
1120
+ // === Human-readable output ===
1121
+
1122
+ describe("human-readable output", () => {
1123
+ test("displays agent metadata", async () => {
1124
+ const agentplateDir = join(tempDir, ".agentplate");
1125
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
1126
+ const store = createSessionStore(sessionsDbPath);
1127
+ store.upsert({
1128
+ id: "sess-1",
1129
+ agentName: "builder-1",
1130
+ capability: "builder",
1131
+ worktreePath: "/tmp/wt",
1132
+ branchName: "agentplate/builder-1/test",
1133
+ taskId: "agentplate-001",
1134
+ tmuxSession: "agentplate-test-builder-1",
1135
+ state: "working",
1136
+ pid: 12345,
1137
+ parentAgent: "orchestrator",
1138
+ depth: 1,
1139
+ runId: null,
1140
+ startedAt: new Date().toISOString(),
1141
+ lastActivity: new Date().toISOString(),
1142
+ escalationLevel: 0,
1143
+ stalledSince: null,
1144
+ transcriptPath: null,
1145
+ });
1146
+ store.close();
1147
+
1148
+ await inspectCommand(["builder-1", "--no-tmux"]);
1149
+ const out = output();
1150
+
1151
+ expect(out).toContain("builder-1");
1152
+ expect(out).toContain("working");
1153
+ expect(out).toContain("agentplate-001");
1154
+ expect(out).toContain("builder");
1155
+ expect(out).toContain("agentplate/builder-1/test");
1156
+ expect(out).toContain("orchestrator");
1157
+ });
1158
+
1159
+ test("displays token usage", async () => {
1160
+ const agentplateDir = join(tempDir, ".agentplate");
1161
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
1162
+ const metricsDbPath = join(agentplateDir, "metrics.db");
1163
+
1164
+ const store = createSessionStore(sessionsDbPath);
1165
+ store.upsert({
1166
+ id: "sess-1",
1167
+ agentName: "builder-1",
1168
+ capability: "builder",
1169
+ worktreePath: "/tmp/wt",
1170
+ branchName: "agentplate/builder-1/test",
1171
+ taskId: "agentplate-001",
1172
+ tmuxSession: "agentplate-test-builder-1",
1173
+ state: "working",
1174
+ pid: 12345,
1175
+ parentAgent: null,
1176
+ depth: 0,
1177
+ runId: null,
1178
+ startedAt: new Date().toISOString(),
1179
+ lastActivity: new Date().toISOString(),
1180
+ escalationLevel: 0,
1181
+ stalledSince: null,
1182
+ transcriptPath: null,
1183
+ });
1184
+ store.close();
1185
+
1186
+ const metricsStore = createMetricsStore(metricsDbPath);
1187
+ metricsStore.recordSession(makeMetrics({ estimatedCostUsd: 0.123 }));
1188
+ metricsStore.close();
1189
+
1190
+ await inspectCommand(["builder-1", "--no-tmux"]);
1191
+ const out = output();
1192
+
1193
+ expect(out).toContain("Token Usage");
1194
+ expect(out).toContain("1,000");
1195
+ expect(out).toContain("$0.1230");
1196
+ });
1197
+
1198
+ test("displays tool stats and recent calls", async () => {
1199
+ const agentplateDir = join(tempDir, ".agentplate");
1200
+ const sessionsDbPath = join(agentplateDir, "sessions.db");
1201
+ const eventsDbPath = join(agentplateDir, "events.db");
1202
+
1203
+ const store = createSessionStore(sessionsDbPath);
1204
+ store.upsert({
1205
+ id: "sess-1",
1206
+ agentName: "builder-1",
1207
+ capability: "builder",
1208
+ worktreePath: "/tmp/wt",
1209
+ branchName: "agentplate/builder-1/test",
1210
+ taskId: "agentplate-001",
1211
+ tmuxSession: "agentplate-test-builder-1",
1212
+ state: "working",
1213
+ pid: 12345,
1214
+ parentAgent: null,
1215
+ depth: 0,
1216
+ runId: null,
1217
+ startedAt: new Date().toISOString(),
1218
+ lastActivity: new Date().toISOString(),
1219
+ escalationLevel: 0,
1220
+ stalledSince: null,
1221
+ transcriptPath: null,
1222
+ });
1223
+ store.close();
1224
+
1225
+ const eventStore = createEventStore(eventsDbPath);
1226
+ eventStore.insert(makeEvent({ toolName: "Read", toolDurationMs: 100 }));
1227
+ eventStore.insert(makeEvent({ toolName: "Edit", toolDurationMs: 200 }));
1228
+ eventStore.close();
1229
+
1230
+ await inspectCommand(["builder-1", "--no-tmux"]);
1231
+ const out = output();
1232
+
1233
+ expect(out).toContain("Tool Usage");
1234
+ expect(out).toContain("Recent Tool Calls");
1235
+ expect(out).toContain("Read");
1236
+ expect(out).toContain("Edit");
1237
+ });
1238
+ });
1239
+ });