@swarmify/agents-cli 1.12.0 → 1.13.1

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 (539) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +212 -232
  4. package/dist/commands/__tests__/sessions.test.js +90 -20
  5. package/dist/commands/__tests__/sessions.test.js.map +1 -1
  6. package/dist/commands/cloud.d.ts +11 -0
  7. package/dist/commands/cloud.d.ts.map +1 -0
  8. package/dist/commands/cloud.js +363 -0
  9. package/dist/commands/cloud.js.map +1 -0
  10. package/dist/commands/commands.d.ts +9 -0
  11. package/dist/commands/commands.d.ts.map +1 -1
  12. package/dist/commands/commands.js +330 -139
  13. package/dist/commands/commands.js.map +1 -1
  14. package/dist/commands/daemon.d.ts +8 -0
  15. package/dist/commands/daemon.d.ts.map +1 -1
  16. package/dist/commands/daemon.js +38 -222
  17. package/dist/commands/daemon.js.map +1 -1
  18. package/dist/commands/drive.d.ts +8 -0
  19. package/dist/commands/drive.d.ts.map +1 -1
  20. package/dist/commands/drive.js +70 -7
  21. package/dist/commands/drive.js.map +1 -1
  22. package/dist/commands/exec.d.ts +9 -1
  23. package/dist/commands/exec.d.ts.map +1 -1
  24. package/dist/commands/exec.js +198 -24
  25. package/dist/commands/exec.js.map +1 -1
  26. package/dist/commands/factory.d.ts +11 -0
  27. package/dist/commands/factory.d.ts.map +1 -0
  28. package/dist/commands/factory.js +445 -0
  29. package/dist/commands/factory.js.map +1 -0
  30. package/dist/commands/fork.d.ts +8 -0
  31. package/dist/commands/fork.d.ts.map +1 -1
  32. package/dist/commands/fork.js +38 -4
  33. package/dist/commands/fork.js.map +1 -1
  34. package/dist/commands/hooks.d.ts +9 -0
  35. package/dist/commands/hooks.d.ts.map +1 -1
  36. package/dist/commands/hooks.js +252 -18
  37. package/dist/commands/hooks.js.map +1 -1
  38. package/dist/commands/init.d.ts +15 -0
  39. package/dist/commands/init.d.ts.map +1 -0
  40. package/dist/commands/init.js +137 -0
  41. package/dist/commands/init.js.map +1 -0
  42. package/dist/commands/mcp.d.ts +9 -0
  43. package/dist/commands/mcp.d.ts.map +1 -1
  44. package/dist/commands/mcp.js +250 -169
  45. package/dist/commands/mcp.js.map +1 -1
  46. package/dist/commands/models.d.ts +11 -0
  47. package/dist/commands/models.d.ts.map +1 -0
  48. package/dist/commands/models.js +170 -0
  49. package/dist/commands/models.js.map +1 -0
  50. package/dist/commands/packages.d.ts +8 -0
  51. package/dist/commands/packages.d.ts.map +1 -1
  52. package/dist/commands/packages.js +155 -14
  53. package/dist/commands/packages.js.map +1 -1
  54. package/dist/commands/permissions.d.ts +9 -0
  55. package/dist/commands/permissions.d.ts.map +1 -1
  56. package/dist/commands/permissions.js +72 -13
  57. package/dist/commands/permissions.js.map +1 -1
  58. package/dist/commands/plugins.d.ts +8 -0
  59. package/dist/commands/plugins.d.ts.map +1 -1
  60. package/dist/commands/plugins.js +265 -44
  61. package/dist/commands/plugins.js.map +1 -1
  62. package/dist/commands/profiles.d.ts +12 -0
  63. package/dist/commands/profiles.d.ts.map +1 -0
  64. package/dist/commands/profiles.js +255 -0
  65. package/dist/commands/profiles.js.map +1 -0
  66. package/dist/commands/pty.d.ts +1 -0
  67. package/dist/commands/pty.d.ts.map +1 -1
  68. package/dist/commands/pty.js +133 -22
  69. package/dist/commands/pty.js.map +1 -1
  70. package/dist/commands/pull.d.ts +8 -0
  71. package/dist/commands/pull.d.ts.map +1 -1
  72. package/dist/commands/pull.js +103 -14
  73. package/dist/commands/pull.js.map +1 -1
  74. package/dist/commands/push.d.ts +8 -0
  75. package/dist/commands/push.d.ts.map +1 -1
  76. package/dist/commands/push.js +37 -3
  77. package/dist/commands/push.js.map +1 -1
  78. package/dist/commands/refresh-memory.d.ts +16 -0
  79. package/dist/commands/refresh-memory.d.ts.map +1 -0
  80. package/dist/commands/refresh-memory.js +52 -0
  81. package/dist/commands/refresh-memory.js.map +1 -0
  82. package/dist/commands/resource-view.d.ts +39 -0
  83. package/dist/commands/resource-view.d.ts.map +1 -0
  84. package/dist/commands/resource-view.js +197 -0
  85. package/dist/commands/resource-view.js.map +1 -0
  86. package/dist/commands/routines.d.ts +8 -0
  87. package/dist/commands/routines.d.ts.map +1 -1
  88. package/dist/commands/routines.js +163 -34
  89. package/dist/commands/routines.js.map +1 -1
  90. package/dist/commands/rules.d.ts +9 -0
  91. package/dist/commands/rules.d.ts.map +1 -1
  92. package/dist/commands/rules.js +83 -12
  93. package/dist/commands/rules.js.map +1 -1
  94. package/dist/commands/secrets.d.ts +11 -0
  95. package/dist/commands/secrets.d.ts.map +1 -0
  96. package/dist/commands/secrets.js +352 -0
  97. package/dist/commands/secrets.js.map +1 -0
  98. package/dist/commands/sessions-picker.d.ts +18 -0
  99. package/dist/commands/sessions-picker.d.ts.map +1 -0
  100. package/dist/commands/sessions-picker.js +265 -0
  101. package/dist/commands/sessions-picker.js.map +1 -0
  102. package/dist/commands/sessions.d.ts +15 -0
  103. package/dist/commands/sessions.d.ts.map +1 -1
  104. package/dist/commands/sessions.js +700 -263
  105. package/dist/commands/sessions.js.map +1 -1
  106. package/dist/commands/skills.d.ts +9 -0
  107. package/dist/commands/skills.d.ts.map +1 -1
  108. package/dist/commands/skills.js +355 -233
  109. package/dist/commands/skills.js.map +1 -1
  110. package/dist/commands/status.d.ts +7 -0
  111. package/dist/commands/status.d.ts.map +1 -1
  112. package/dist/commands/status.js +7 -0
  113. package/dist/commands/status.js.map +1 -1
  114. package/dist/commands/subagents.d.ts +8 -0
  115. package/dist/commands/subagents.d.ts.map +1 -1
  116. package/dist/commands/subagents.js +214 -74
  117. package/dist/commands/subagents.js.map +1 -1
  118. package/dist/commands/sync.d.ts +8 -0
  119. package/dist/commands/sync.d.ts.map +1 -1
  120. package/dist/commands/sync.js +15 -7
  121. package/dist/commands/sync.js.map +1 -1
  122. package/dist/commands/teams-picker.d.ts +18 -0
  123. package/dist/commands/teams-picker.d.ts.map +1 -0
  124. package/dist/commands/teams-picker.js +290 -0
  125. package/dist/commands/teams-picker.js.map +1 -0
  126. package/dist/commands/teams.d.ts +18 -0
  127. package/dist/commands/teams.d.ts.map +1 -0
  128. package/dist/commands/teams.js +1098 -0
  129. package/dist/commands/teams.js.map +1 -0
  130. package/dist/commands/utils.d.ts +20 -0
  131. package/dist/commands/utils.d.ts.map +1 -1
  132. package/dist/commands/utils.js +34 -0
  133. package/dist/commands/utils.js.map +1 -1
  134. package/dist/commands/versions.d.ts +8 -0
  135. package/dist/commands/versions.d.ts.map +1 -1
  136. package/dist/commands/versions.js +71 -8
  137. package/dist/commands/versions.js.map +1 -1
  138. package/dist/commands/view.d.ts +36 -1
  139. package/dist/commands/view.d.ts.map +1 -1
  140. package/dist/commands/view.js +375 -15
  141. package/dist/commands/view.js.map +1 -1
  142. package/dist/index.d.ts +6 -0
  143. package/dist/index.d.ts.map +1 -1
  144. package/dist/index.js +136 -51
  145. package/dist/index.js.map +1 -1
  146. package/dist/lib/__tests__/bugfixes.test.js +3 -3
  147. package/dist/lib/__tests__/bugfixes.test.js.map +1 -1
  148. package/dist/lib/__tests__/exec.test.js +125 -19
  149. package/dist/lib/__tests__/exec.test.js.map +1 -1
  150. package/dist/lib/__tests__/hooks.test.d.ts +2 -0
  151. package/dist/lib/__tests__/hooks.test.d.ts.map +1 -0
  152. package/dist/lib/__tests__/hooks.test.js +203 -0
  153. package/dist/lib/__tests__/hooks.test.js.map +1 -0
  154. package/dist/lib/__tests__/memory-compile.test.d.ts +2 -0
  155. package/dist/lib/__tests__/memory-compile.test.d.ts.map +1 -0
  156. package/dist/lib/__tests__/memory-compile.test.js +95 -0
  157. package/dist/lib/__tests__/memory-compile.test.js.map +1 -0
  158. package/dist/lib/__tests__/models.test.d.ts +2 -0
  159. package/dist/lib/__tests__/models.test.d.ts.map +1 -0
  160. package/dist/lib/__tests__/models.test.js +239 -0
  161. package/dist/lib/__tests__/models.test.js.map +1 -0
  162. package/dist/lib/__tests__/rotate.test.d.ts +2 -0
  163. package/dist/lib/__tests__/rotate.test.d.ts.map +1 -0
  164. package/dist/lib/__tests__/rotate.test.js +80 -0
  165. package/dist/lib/__tests__/rotate.test.js.map +1 -0
  166. package/dist/lib/__tests__/secrets-bundles.test.d.ts +2 -0
  167. package/dist/lib/__tests__/secrets-bundles.test.d.ts.map +1 -0
  168. package/dist/lib/__tests__/secrets-bundles.test.js +104 -0
  169. package/dist/lib/__tests__/secrets-bundles.test.js.map +1 -0
  170. package/dist/lib/__tests__/secrets.test.d.ts +2 -0
  171. package/dist/lib/__tests__/secrets.test.d.ts.map +1 -0
  172. package/dist/lib/__tests__/secrets.test.js +90 -0
  173. package/dist/lib/__tests__/secrets.test.js.map +1 -0
  174. package/dist/lib/__tests__/shims.test.d.ts +2 -0
  175. package/dist/lib/__tests__/shims.test.d.ts.map +1 -0
  176. package/dist/lib/__tests__/shims.test.js +39 -0
  177. package/dist/lib/__tests__/shims.test.js.map +1 -0
  178. package/dist/lib/__tests__/usage.test.js +4 -2
  179. package/dist/lib/__tests__/usage.test.js.map +1 -1
  180. package/dist/lib/__tests__/versions.test.d.ts +2 -0
  181. package/dist/lib/__tests__/versions.test.d.ts.map +1 -0
  182. package/dist/lib/__tests__/versions.test.js +63 -0
  183. package/dist/lib/__tests__/versions.test.js.map +1 -0
  184. package/dist/lib/agents.d.ts +53 -1
  185. package/dist/lib/agents.d.ts.map +1 -1
  186. package/dist/lib/agents.js +178 -37
  187. package/dist/lib/agents.js.map +1 -1
  188. package/dist/lib/artifact-actions.d.ts +8 -3
  189. package/dist/lib/artifact-actions.d.ts.map +1 -1
  190. package/dist/lib/artifact-actions.js +8 -5
  191. package/dist/lib/artifact-actions.js.map +1 -1
  192. package/dist/lib/cloud/codex.d.ts +26 -0
  193. package/dist/lib/cloud/codex.d.ts.map +1 -0
  194. package/dist/lib/cloud/codex.js +237 -0
  195. package/dist/lib/cloud/codex.js.map +1 -0
  196. package/dist/lib/cloud/factory.d.ts +32 -0
  197. package/dist/lib/cloud/factory.d.ts.map +1 -0
  198. package/dist/lib/cloud/factory.js +43 -0
  199. package/dist/lib/cloud/factory.js.map +1 -0
  200. package/dist/lib/cloud/registry.d.ts +16 -0
  201. package/dist/lib/cloud/registry.d.ts.map +1 -0
  202. package/dist/lib/cloud/registry.js +68 -0
  203. package/dist/lib/cloud/registry.js.map +1 -0
  204. package/dist/lib/cloud/rush.d.ts +37 -0
  205. package/dist/lib/cloud/rush.d.ts.map +1 -0
  206. package/dist/lib/cloud/rush.js +230 -0
  207. package/dist/lib/cloud/rush.js.map +1 -0
  208. package/dist/lib/cloud/rush.test.d.ts +2 -0
  209. package/dist/lib/cloud/rush.test.d.ts.map +1 -0
  210. package/dist/lib/cloud/rush.test.js +63 -0
  211. package/dist/lib/cloud/rush.test.js.map +1 -0
  212. package/dist/lib/cloud/store.d.ts +23 -0
  213. package/dist/lib/cloud/store.d.ts.map +1 -0
  214. package/dist/lib/cloud/store.js +116 -0
  215. package/dist/lib/cloud/store.js.map +1 -0
  216. package/dist/lib/cloud/stream.d.ts +24 -0
  217. package/dist/lib/cloud/stream.d.ts.map +1 -0
  218. package/dist/lib/cloud/stream.js +145 -0
  219. package/dist/lib/cloud/stream.js.map +1 -0
  220. package/dist/lib/cloud/types.d.ts +109 -0
  221. package/dist/lib/cloud/types.d.ts.map +1 -0
  222. package/dist/lib/cloud/types.js +33 -0
  223. package/dist/lib/cloud/types.js.map +1 -0
  224. package/dist/lib/cloud/types.test.d.ts +2 -0
  225. package/dist/lib/cloud/types.test.d.ts.map +1 -0
  226. package/dist/lib/cloud/types.test.js +41 -0
  227. package/dist/lib/cloud/types.test.js.map +1 -0
  228. package/dist/lib/commands.d.ts +67 -0
  229. package/dist/lib/commands.d.ts.map +1 -1
  230. package/dist/lib/commands.js +161 -2
  231. package/dist/lib/commands.js.map +1 -1
  232. package/dist/lib/convert.d.ts +10 -0
  233. package/dist/lib/convert.d.ts.map +1 -1
  234. package/dist/lib/convert.js +9 -0
  235. package/dist/lib/convert.js.map +1 -1
  236. package/dist/lib/daemon.d.ts +21 -0
  237. package/dist/lib/daemon.d.ts.map +1 -1
  238. package/dist/lib/daemon.js +21 -0
  239. package/dist/lib/daemon.js.map +1 -1
  240. package/dist/lib/drive-sync.d.ts +18 -0
  241. package/dist/lib/drive-sync.d.ts.map +1 -1
  242. package/dist/lib/drive-sync.js +16 -0
  243. package/dist/lib/drive-sync.js.map +1 -1
  244. package/dist/lib/exec.d.ts +64 -3
  245. package/dist/lib/exec.d.ts.map +1 -1
  246. package/dist/lib/exec.js +218 -80
  247. package/dist/lib/exec.js.map +1 -1
  248. package/dist/lib/factory/__tests__/config.test.d.ts +2 -0
  249. package/dist/lib/factory/__tests__/config.test.d.ts.map +1 -0
  250. package/dist/lib/factory/__tests__/config.test.js +128 -0
  251. package/dist/lib/factory/__tests__/config.test.js.map +1 -0
  252. package/dist/lib/factory/config.d.ts +49 -0
  253. package/dist/lib/factory/config.d.ts.map +1 -0
  254. package/dist/lib/factory/config.js +127 -0
  255. package/dist/lib/factory/config.js.map +1 -0
  256. package/dist/lib/factory.js +1 -1
  257. package/dist/lib/factory.js.map +1 -1
  258. package/dist/lib/git.d.ts +16 -1
  259. package/dist/lib/git.d.ts.map +1 -1
  260. package/dist/lib/git.js +33 -4
  261. package/dist/lib/git.js.map +1 -1
  262. package/dist/lib/help.d.ts +7 -0
  263. package/dist/lib/help.d.ts.map +1 -1
  264. package/dist/lib/help.js +3 -0
  265. package/dist/lib/help.js.map +1 -1
  266. package/dist/lib/hooks.d.ts +59 -3
  267. package/dist/lib/hooks.d.ts.map +1 -1
  268. package/dist/lib/hooks.js +413 -33
  269. package/dist/lib/hooks.js.map +1 -1
  270. package/dist/lib/ledger/__tests__/local.test.d.ts +2 -0
  271. package/dist/lib/ledger/__tests__/local.test.d.ts.map +1 -0
  272. package/dist/lib/ledger/__tests__/local.test.js +177 -0
  273. package/dist/lib/ledger/__tests__/local.test.js.map +1 -0
  274. package/dist/lib/ledger/__tests__/sync.test.d.ts +2 -0
  275. package/dist/lib/ledger/__tests__/sync.test.d.ts.map +1 -0
  276. package/dist/lib/ledger/__tests__/sync.test.js +117 -0
  277. package/dist/lib/ledger/__tests__/sync.test.js.map +1 -0
  278. package/dist/lib/ledger/index.d.ts +18 -0
  279. package/dist/lib/ledger/index.d.ts.map +1 -0
  280. package/dist/lib/ledger/index.js +32 -0
  281. package/dist/lib/ledger/index.js.map +1 -0
  282. package/dist/lib/ledger/local.d.ts +22 -0
  283. package/dist/lib/ledger/local.d.ts.map +1 -0
  284. package/dist/lib/ledger/local.js +333 -0
  285. package/dist/lib/ledger/local.js.map +1 -0
  286. package/dist/lib/ledger/r2.d.ts +41 -0
  287. package/dist/lib/ledger/r2.d.ts.map +1 -0
  288. package/dist/lib/ledger/r2.js +335 -0
  289. package/dist/lib/ledger/r2.js.map +1 -0
  290. package/dist/lib/ledger/sync.d.ts +33 -0
  291. package/dist/lib/ledger/sync.d.ts.map +1 -0
  292. package/dist/lib/ledger/sync.js +106 -0
  293. package/dist/lib/ledger/sync.js.map +1 -0
  294. package/dist/lib/ledger/types.d.ts +100 -0
  295. package/dist/lib/ledger/types.d.ts.map +1 -0
  296. package/dist/lib/ledger/types.js +21 -0
  297. package/dist/lib/ledger/types.js.map +1 -0
  298. package/dist/lib/manifest.d.ts +6 -0
  299. package/dist/lib/manifest.d.ts.map +1 -1
  300. package/dist/lib/manifest.js +12 -0
  301. package/dist/lib/manifest.js.map +1 -1
  302. package/dist/lib/markdown.d.ts.map +1 -1
  303. package/dist/lib/markdown.js +6 -0
  304. package/dist/lib/markdown.js.map +1 -1
  305. package/dist/lib/mcp.d.ts +0 -9
  306. package/dist/lib/mcp.d.ts.map +1 -1
  307. package/dist/lib/mcp.js +0 -20
  308. package/dist/lib/mcp.js.map +1 -1
  309. package/dist/lib/memory-compile.d.ts +65 -0
  310. package/dist/lib/memory-compile.d.ts.map +1 -0
  311. package/dist/lib/memory-compile.js +174 -0
  312. package/dist/lib/memory-compile.js.map +1 -0
  313. package/dist/lib/memory.d.ts +8 -0
  314. package/dist/lib/memory.d.ts.map +1 -1
  315. package/dist/lib/memory.js +8 -0
  316. package/dist/lib/memory.js.map +1 -1
  317. package/dist/lib/models.d.ts +98 -0
  318. package/dist/lib/models.d.ts.map +1 -0
  319. package/dist/lib/models.js +728 -0
  320. package/dist/lib/models.js.map +1 -0
  321. package/dist/lib/permissions.d.ts +24 -1
  322. package/dist/lib/permissions.d.ts.map +1 -1
  323. package/dist/lib/permissions.js +52 -3
  324. package/dist/lib/permissions.js.map +1 -1
  325. package/dist/lib/picker.d.ts +27 -0
  326. package/dist/lib/picker.d.ts.map +1 -0
  327. package/dist/lib/picker.js +110 -0
  328. package/dist/lib/picker.js.map +1 -0
  329. package/dist/lib/plugins.d.ts +22 -0
  330. package/dist/lib/plugins.d.ts.map +1 -1
  331. package/dist/lib/plugins.js +114 -0
  332. package/dist/lib/plugins.js.map +1 -1
  333. package/dist/lib/profiles-keychain.d.ts +11 -0
  334. package/dist/lib/profiles-keychain.d.ts.map +1 -0
  335. package/dist/lib/profiles-keychain.js +14 -0
  336. package/dist/lib/profiles-keychain.js.map +1 -0
  337. package/dist/lib/profiles-presets.d.ts +25 -0
  338. package/dist/lib/profiles-presets.d.ts.map +1 -0
  339. package/dist/lib/profiles-presets.js +104 -0
  340. package/dist/lib/profiles-presets.js.map +1 -0
  341. package/dist/lib/profiles.d.ts +70 -0
  342. package/dist/lib/profiles.d.ts.map +1 -0
  343. package/dist/lib/profiles.js +145 -0
  344. package/dist/lib/profiles.js.map +1 -0
  345. package/dist/lib/pty-client.d.ts +1 -0
  346. package/dist/lib/pty-client.d.ts.map +1 -1
  347. package/dist/lib/pty-client.js.map +1 -1
  348. package/dist/lib/pty-server.d.ts +5 -0
  349. package/dist/lib/pty-server.d.ts.map +1 -1
  350. package/dist/lib/pty-server.js +5 -0
  351. package/dist/lib/pty-server.js.map +1 -1
  352. package/dist/lib/registry.d.ts +17 -0
  353. package/dist/lib/registry.d.ts.map +1 -1
  354. package/dist/lib/registry.js +17 -0
  355. package/dist/lib/registry.js.map +1 -1
  356. package/dist/lib/resources.d.ts +5 -0
  357. package/dist/lib/resources.d.ts.map +1 -1
  358. package/dist/lib/resources.js.map +1 -1
  359. package/dist/lib/rotate.d.ts +58 -0
  360. package/dist/lib/rotate.d.ts.map +1 -0
  361. package/dist/lib/rotate.js +93 -0
  362. package/dist/lib/rotate.js.map +1 -0
  363. package/dist/lib/routines.d.ts +30 -1
  364. package/dist/lib/routines.d.ts.map +1 -1
  365. package/dist/lib/routines.js +31 -4
  366. package/dist/lib/routines.js.map +1 -1
  367. package/dist/lib/runner.d.ts +14 -0
  368. package/dist/lib/runner.d.ts.map +1 -1
  369. package/dist/lib/runner.js +45 -11
  370. package/dist/lib/runner.js.map +1 -1
  371. package/dist/lib/sandbox.d.ts +16 -0
  372. package/dist/lib/sandbox.d.ts.map +1 -1
  373. package/dist/lib/sandbox.js +19 -2
  374. package/dist/lib/sandbox.js.map +1 -1
  375. package/dist/lib/scheduler.d.ts +8 -0
  376. package/dist/lib/scheduler.d.ts.map +1 -1
  377. package/dist/lib/scheduler.js +8 -0
  378. package/dist/lib/scheduler.js.map +1 -1
  379. package/dist/lib/secrets-bundles.d.ts +38 -0
  380. package/dist/lib/secrets-bundles.d.ts.map +1 -0
  381. package/dist/lib/secrets-bundles.js +176 -0
  382. package/dist/lib/secrets-bundles.js.map +1 -0
  383. package/dist/lib/secrets.d.ts +53 -0
  384. package/dist/lib/secrets.d.ts.map +1 -0
  385. package/dist/lib/secrets.js +140 -0
  386. package/dist/lib/secrets.js.map +1 -0
  387. package/dist/lib/session/__tests__/db.test.d.ts +2 -0
  388. package/dist/lib/session/__tests__/db.test.d.ts.map +1 -0
  389. package/dist/lib/session/__tests__/db.test.js +54 -0
  390. package/dist/lib/session/__tests__/db.test.js.map +1 -0
  391. package/dist/lib/session/__tests__/discover.test.js +54 -91
  392. package/dist/lib/session/__tests__/discover.test.js.map +1 -1
  393. package/dist/lib/session/__tests__/prompt.test.d.ts +2 -0
  394. package/dist/lib/session/__tests__/prompt.test.d.ts.map +1 -0
  395. package/dist/lib/session/__tests__/prompt.test.js +44 -0
  396. package/dist/lib/session/__tests__/prompt.test.js.map +1 -0
  397. package/dist/lib/session/__tests__/render.test.d.ts +2 -0
  398. package/dist/lib/session/__tests__/render.test.d.ts.map +1 -0
  399. package/dist/lib/session/__tests__/render.test.js +602 -0
  400. package/dist/lib/session/__tests__/render.test.js.map +1 -0
  401. package/dist/lib/session/artifacts.d.ts +15 -0
  402. package/dist/lib/session/artifacts.d.ts.map +1 -0
  403. package/dist/lib/session/artifacts.js +86 -0
  404. package/dist/lib/session/artifacts.js.map +1 -0
  405. package/dist/lib/session/db.d.ts +140 -0
  406. package/dist/lib/session/db.d.ts.map +1 -0
  407. package/dist/lib/session/db.js +599 -0
  408. package/dist/lib/session/db.js.map +1 -0
  409. package/dist/lib/session/discover.d.ts +44 -32
  410. package/dist/lib/session/discover.d.ts.map +1 -1
  411. package/dist/lib/session/discover.js +418 -464
  412. package/dist/lib/session/discover.js.map +1 -1
  413. package/dist/lib/session/parse.d.ts +11 -0
  414. package/dist/lib/session/parse.d.ts.map +1 -1
  415. package/dist/lib/session/parse.js +50 -0
  416. package/dist/lib/session/parse.js.map +1 -1
  417. package/dist/lib/session/prompt.d.ts +10 -0
  418. package/dist/lib/session/prompt.d.ts.map +1 -1
  419. package/dist/lib/session/prompt.js +38 -2
  420. package/dist/lib/session/prompt.js.map +1 -1
  421. package/dist/lib/session/prompt.test.d.ts +2 -0
  422. package/dist/lib/session/prompt.test.d.ts.map +1 -0
  423. package/dist/lib/session/prompt.test.js +57 -0
  424. package/dist/lib/session/prompt.test.js.map +1 -0
  425. package/dist/lib/session/render.d.ts +91 -10
  426. package/dist/lib/session/render.d.ts.map +1 -1
  427. package/dist/lib/session/render.js +708 -180
  428. package/dist/lib/session/render.js.map +1 -1
  429. package/dist/lib/session/team-filter.d.ts +35 -0
  430. package/dist/lib/session/team-filter.d.ts.map +1 -0
  431. package/dist/lib/session/team-filter.js +75 -0
  432. package/dist/lib/session/team-filter.js.map +1 -0
  433. package/dist/lib/session/team-filter.test.d.ts +2 -0
  434. package/dist/lib/session/team-filter.test.d.ts.map +1 -0
  435. package/dist/lib/session/team-filter.test.js +157 -0
  436. package/dist/lib/session/team-filter.test.js.map +1 -0
  437. package/dist/lib/session/types.d.ts +48 -6
  438. package/dist/lib/session/types.d.ts.map +1 -1
  439. package/dist/lib/session/types.js +9 -0
  440. package/dist/lib/session/types.js.map +1 -1
  441. package/dist/lib/shims.d.ts +101 -2
  442. package/dist/lib/shims.d.ts.map +1 -1
  443. package/dist/lib/shims.js +282 -25
  444. package/dist/lib/shims.js.map +1 -1
  445. package/dist/lib/skills.d.ts +68 -0
  446. package/dist/lib/skills.d.ts.map +1 -1
  447. package/dist/lib/skills.js +267 -1
  448. package/dist/lib/skills.js.map +1 -1
  449. package/dist/lib/state.d.ts +41 -2
  450. package/dist/lib/state.d.ts.map +1 -1
  451. package/dist/lib/state.js +63 -4
  452. package/dist/lib/state.js.map +1 -1
  453. package/dist/lib/subagents.d.ts +9 -0
  454. package/dist/lib/subagents.d.ts.map +1 -1
  455. package/dist/lib/subagents.js +9 -1
  456. package/dist/lib/subagents.js.map +1 -1
  457. package/dist/lib/teams/__tests__/oracle.test.d.ts +2 -0
  458. package/dist/lib/teams/__tests__/oracle.test.d.ts.map +1 -0
  459. package/dist/lib/teams/__tests__/oracle.test.js +89 -0
  460. package/dist/lib/teams/__tests__/oracle.test.js.map +1 -0
  461. package/dist/lib/teams/__tests__/supervisor.test.d.ts +2 -0
  462. package/dist/lib/teams/__tests__/supervisor.test.d.ts.map +1 -0
  463. package/dist/lib/teams/__tests__/supervisor.test.js +179 -0
  464. package/dist/lib/teams/__tests__/supervisor.test.js.map +1 -0
  465. package/dist/lib/teams/agents.d.ts +247 -0
  466. package/dist/lib/teams/agents.d.ts.map +1 -0
  467. package/dist/lib/teams/agents.js +1244 -0
  468. package/dist/lib/teams/agents.js.map +1 -0
  469. package/dist/lib/teams/api.d.ts +91 -0
  470. package/dist/lib/teams/api.d.ts.map +1 -0
  471. package/dist/lib/teams/api.js +239 -0
  472. package/dist/lib/teams/api.js.map +1 -0
  473. package/dist/lib/teams/cloud.d.ts +11 -0
  474. package/dist/lib/teams/cloud.d.ts.map +1 -0
  475. package/dist/lib/teams/cloud.js +169 -0
  476. package/dist/lib/teams/cloud.js.map +1 -0
  477. package/dist/lib/teams/debug.d.ts +8 -0
  478. package/dist/lib/teams/debug.d.ts.map +1 -0
  479. package/dist/lib/teams/debug.js +12 -0
  480. package/dist/lib/teams/debug.js.map +1 -0
  481. package/dist/lib/teams/file_ops.d.ts +13 -0
  482. package/dist/lib/teams/file_ops.d.ts.map +1 -0
  483. package/dist/lib/teams/file_ops.js +66 -0
  484. package/dist/lib/teams/file_ops.js.map +1 -0
  485. package/dist/lib/teams/index.d.ts +16 -0
  486. package/dist/lib/teams/index.d.ts.map +1 -0
  487. package/dist/lib/teams/index.js +15 -0
  488. package/dist/lib/teams/index.js.map +1 -0
  489. package/dist/lib/teams/oracle.d.ts +20 -0
  490. package/dist/lib/teams/oracle.d.ts.map +1 -0
  491. package/dist/lib/teams/oracle.js +59 -0
  492. package/dist/lib/teams/oracle.js.map +1 -0
  493. package/dist/lib/teams/parsers.d.ts +9 -0
  494. package/dist/lib/teams/parsers.d.ts.map +1 -0
  495. package/dist/lib/teams/parsers.js +837 -0
  496. package/dist/lib/teams/parsers.js.map +1 -0
  497. package/dist/lib/teams/persistence.d.ts +43 -0
  498. package/dist/lib/teams/persistence.d.ts.map +1 -0
  499. package/dist/lib/teams/persistence.js +299 -0
  500. package/dist/lib/teams/persistence.js.map +1 -0
  501. package/dist/lib/teams/ralph.d.ts +8 -0
  502. package/dist/lib/teams/ralph.d.ts.map +1 -0
  503. package/dist/lib/teams/ralph.js +59 -0
  504. package/dist/lib/teams/ralph.js.map +1 -0
  505. package/dist/lib/teams/registry.d.ts +18 -0
  506. package/dist/lib/teams/registry.d.ts.map +1 -0
  507. package/dist/lib/teams/registry.js +68 -0
  508. package/dist/lib/teams/registry.js.map +1 -0
  509. package/dist/lib/teams/summarizer.d.ts +73 -0
  510. package/dist/lib/teams/summarizer.d.ts.map +1 -0
  511. package/dist/lib/teams/summarizer.js +780 -0
  512. package/dist/lib/teams/summarizer.js.map +1 -0
  513. package/dist/lib/teams/supervisor.d.ts +49 -0
  514. package/dist/lib/teams/supervisor.d.ts.map +1 -0
  515. package/dist/lib/teams/supervisor.js +74 -0
  516. package/dist/lib/teams/supervisor.js.map +1 -0
  517. package/dist/lib/template.d.ts +8 -5
  518. package/dist/lib/template.d.ts.map +1 -1
  519. package/dist/lib/template.js +8 -5
  520. package/dist/lib/template.js.map +1 -1
  521. package/dist/lib/types.d.ts +58 -1
  522. package/dist/lib/types.d.ts.map +1 -1
  523. package/dist/lib/types.js +16 -1
  524. package/dist/lib/types.js.map +1 -1
  525. package/dist/lib/usage.d.ts +48 -0
  526. package/dist/lib/usage.d.ts.map +1 -1
  527. package/dist/lib/usage.js +106 -14
  528. package/dist/lib/usage.js.map +1 -1
  529. package/dist/lib/versions.d.ts +12 -1
  530. package/dist/lib/versions.d.ts.map +1 -1
  531. package/dist/lib/versions.js +124 -41
  532. package/dist/lib/versions.js.map +1 -1
  533. package/package.json +20 -6
  534. package/scripts/postinstall.js +1 -1
  535. package/scripts/rebuild-sqlite.sh +46 -0
  536. package/dist/commands/sessions.test.d.ts +0 -2
  537. package/dist/commands/sessions.test.d.ts.map +0 -1
  538. package/dist/commands/sessions.test.js +0 -53
  539. package/dist/commands/sessions.test.js.map +0 -1
@@ -1,259 +1,786 @@
1
+ /**
2
+ * Session rendering: summary, markdown conversation, and JSON output.
3
+ *
4
+ * Provides the display layer for `agents sessions <id>`. The summary renderer
5
+ * produces a chalk-formatted activity overview (modified files, commands,
6
+ * errors, final message). The markdown renderer emits a full conversation
7
+ * transcript. Filtering by role and turn slicing is handled here as well.
8
+ */
9
+ import chalk from 'chalk';
1
10
  import { summarizeToolUse } from './parse.js';
2
11
  import { cleanSessionPrompt, extractSessionTopic } from './prompt.js';
12
+ import { renderMarkdown } from '../markdown.js';
13
+ // ── Path helpers ──────────────────────────────────────────────────────────────
3
14
  /**
4
- * Render session as a conversation transcript.
5
- * Shows user messages, assistant text, and tool calls as one-liners.
15
+ * Return absPath relative to cwd; fall back to ~/… then absolute.
6
16
  */
7
- export function renderTranscript(events) {
8
- const lines = [];
9
- let lastRole = null;
10
- for (const event of events) {
11
- if (event.type === 'message') {
12
- // Add separator between turns
13
- if (lastRole && lastRole !== event.role) {
14
- lines.push('');
15
- }
16
- if (event.role === 'user') {
17
- lines.push(`> ${event.content}`);
18
- lines.push('');
19
- }
20
- else {
21
- lines.push(event.content || '');
17
+ export function relativeToCwd(absPath, cwd) {
18
+ if (cwd && (absPath === cwd || absPath.startsWith(cwd + '/'))) {
19
+ const rel = absPath.slice(cwd.length + 1);
20
+ return rel || '.';
21
+ }
22
+ const home = process.env.HOME || '';
23
+ if (home && (absPath === home || absPath.startsWith(home + '/'))) {
24
+ return '~' + absPath.slice(home.length);
25
+ }
26
+ return absPath;
27
+ }
28
+ /**
29
+ * Wrap label in an OSC 8 hyperlink when the terminal supports it.
30
+ * Degrades to plain label otherwise.
31
+ */
32
+ export function linkPath(absPath, label) {
33
+ if (process.stdout.isTTY &&
34
+ (process.env.TERM_PROGRAM ||
35
+ process.env.WT_SESSION ||
36
+ process.env.KITTY_WINDOW_ID ||
37
+ process.env.WEZTERM_PANE)) {
38
+ return `\x1b]8;;file://${absPath}\x1b\\${label}\x1b]8;;\x1b\\`;
39
+ }
40
+ return label;
41
+ }
42
+ // ── Command grouping ──────────────────────────────────────────────────────────
43
+ /**
44
+ * Unwrap wrapper prefixes to find the actual executable.
45
+ */
46
+ export function unwrapCommand(cmd) {
47
+ const ssh = cmd.match(/^ssh\s+\S+\s+"(.+)"\s*(?:\|.*)?$/);
48
+ if (ssh)
49
+ return unwrapCommand(ssh[1]);
50
+ const lead = cmd.match(/^(?:sudo|env\s+\S+=\S+|time)\s+(.+)/);
51
+ if (lead)
52
+ return unwrapCommand(lead[1]);
53
+ // Strip shell-style leading env assignments: `PATH=/x CMD ...`, `FOO=bar BAR=baz CMD ...`
54
+ const shellEnv = cmd.match(/^(?:[A-Z_][A-Z0-9_]*=\S+\s+)+(\S.*)$/);
55
+ if (shellEnv)
56
+ return unwrapCommand(shellEnv[1]);
57
+ const cd = cmd.match(/^cd\s+\S+\s*&&\s*(.+)/);
58
+ if (cd)
59
+ return unwrapCommand(cd[1]);
60
+ const npx = cmd.match(/^npx\s+(.+)/);
61
+ if (npx)
62
+ return unwrapCommand(npx[1]);
63
+ return cmd;
64
+ }
65
+ /**
66
+ * Normalize a command so trivial flag/pipe variations collapse to the same key.
67
+ */
68
+ export function normalizeForDedup(cmd) {
69
+ let s = cmd.trim();
70
+ s = s.replace(/\s+-[a-zA-Z]+/g, '');
71
+ s = s.replace(/\s+--[a-zA-Z][-a-zA-Z0-9]*(?:=\S+)?/g, '');
72
+ s = s.replace(/\s*\|\s*(head|tail|wc|less|more|cat)(\s+\S+)?.*$/, '');
73
+ s = s.replace(/\s*2>&1\s*$/, '');
74
+ s = s.replace(/\s*;\s*echo\b.*$/, '');
75
+ const home = process.env.HOME ?? '';
76
+ if (home) {
77
+ s = s.replace(new RegExp('^' + home.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')), '~');
78
+ }
79
+ return s.trim();
80
+ }
81
+ /** Command classification categories with signal levels for summary rendering. */
82
+ const CATEGORIES = [
83
+ { name: 'Probes', match: t => ['ls', 'cat', 'head', 'tail', 'wc', 'stat', 'file', 'which', 'tree', 'pwd'].includes(t), signal: 'low' },
84
+ { name: 'Search', match: t => ['grep', 'rg', 'ag', 'fd', 'find'].includes(t), signal: 'low' },
85
+ { name: 'Build/test', match: t => ['make', 'cargo', 'pytest', 'go', 'bun', 'npm', 'pnpm', 'yarn', 'tsc', 'vitest', 'tsx', 'node', 'python', 'python3', 'jest'].includes(t), signal: 'high' },
86
+ { name: 'Install', match: t => ['brew', 'pip', 'apt', 'apk'].includes(t), signal: 'high' },
87
+ { name: 'VCS', match: t => ['git', 'gh'].includes(t), signal: 'mid' },
88
+ { name: 'HTTP', match: t => ['curl', 'wget', 'rush', 'http'].includes(t), signal: 'mid' },
89
+ { name: 'Remote', match: t => ['ssh', 'scp', 'rsync'].includes(t), signal: 'mid' },
90
+ { name: 'Shell', match: t => ['rm', 'mv', 'cp', 'mkdir', 'touch', 'echo', 'printf', 'chmod', 'ln', 'awk', 'sed', 'tee', 'xargs', 'for'].includes(t), signal: 'low' },
91
+ { name: 'Wait', match: t => ['sleep', 'wait'].includes(t), signal: 'low' },
92
+ ];
93
+ /** CLI tools whose subcommand (second token) is included in the bucket key. */
94
+ const TWO_LEVEL_TOKENS = new Set([
95
+ 'git', 'gh', 'bun', 'npm', 'cargo', 'docker', 'kubectl', 'rush', 'openclaw', 'pnpm', 'yarn',
96
+ ]);
97
+ /**
98
+ * Return the bucket key for a command (used for grouping within a category).
99
+ */
100
+ export function bucketKey(cmd) {
101
+ const unwrapped = unwrapCommand(cmd);
102
+ const tokens = unwrapped.trim().split(/\s+/);
103
+ const first = tokens[0] ?? 'other';
104
+ const isRemote = cmd.trim().startsWith('ssh ') || cmd.trim().startsWith('scp ');
105
+ if (TWO_LEVEL_TOKENS.has(first) && tokens[1]) {
106
+ const key = `${first} ${tokens[1]}`;
107
+ return isRemote ? `ssh\u2192${key}` : key;
108
+ }
109
+ return isRemote ? `ssh\u2192${first}` : first;
110
+ }
111
+ function categoryOf(cmd) {
112
+ const rawFirst = cmd.trim().split(/\s+/)[0]?.toLowerCase() ?? '';
113
+ // Remote wrappers: classify as Remote regardless of inner command.
114
+ if (['ssh', 'scp', 'rsync'].includes(rawFirst)) {
115
+ return CATEGORIES.find(c => c.name === 'Remote') ?? null;
116
+ }
117
+ const unwrapped = unwrapCommand(cmd);
118
+ const first = unwrapped.trim().split(/\s+/)[0]?.toLowerCase() ?? '';
119
+ return CATEGORIES.find(c => c.match(first)) ?? null;
120
+ }
121
+ /**
122
+ * Collapse consecutive same-normalized commands within a 60-second window
123
+ * when they appear 3+ times. Fewer than 3 stay as separate entries.
124
+ */
125
+ export function collapseRetries(commands) {
126
+ const groups = [];
127
+ for (const { cmd, ts } of commands) {
128
+ const normalized = normalizeForDedup(unwrapCommand(cmd));
129
+ const last = groups[groups.length - 1];
130
+ if (last && last.normalized === normalized && ts - last.lastTs <= 60_000) {
131
+ last.count++;
132
+ last.lastTs = ts;
133
+ }
134
+ else {
135
+ groups.push({ normalized, raw: cmd, firstTs: ts, lastTs: ts, count: 1 });
136
+ }
137
+ }
138
+ // Expand groups with count < 3 back to individual entries
139
+ const result = [];
140
+ for (const g of groups) {
141
+ if (g.count >= 3) {
142
+ result.push(g);
143
+ }
144
+ else {
145
+ for (let i = 0; i < g.count; i++) {
146
+ result.push({ normalized: g.normalized, raw: g.raw, firstTs: g.firstTs, lastTs: g.lastTs, count: 1 });
22
147
  }
23
- lastRole = event.role || null;
24
148
  }
25
- else if (event.type === 'tool_use') {
26
- const summary = summarizeToolUse(event.tool || 'unknown', event.args);
27
- lines.push(` [${summary}]`);
28
- lastRole = 'tool';
149
+ }
150
+ return result;
151
+ }
152
+ /** Compute aggregate statistics (turns, tools, tokens, duration) from session events. */
153
+ export function computeSummaryStats(events) {
154
+ const modelSet = new Set();
155
+ let userTurns = 0;
156
+ let assistantTurns = 0;
157
+ let toolCount = 0;
158
+ let errorCount = 0;
159
+ let outputTokens = 0;
160
+ let cacheReadTokens = 0;
161
+ let firstTs = Infinity;
162
+ let lastTs = -Infinity;
163
+ for (const e of events) {
164
+ const ts = new Date(e.timestamp).getTime();
165
+ if (!isNaN(ts)) {
166
+ if (ts < firstTs)
167
+ firstTs = ts;
168
+ if (ts > lastTs)
169
+ lastTs = ts;
29
170
  }
30
- else if (event.type === 'error') {
31
- lines.push(` [ERROR: ${event.content || event.tool || 'unknown'}]`);
32
- lastRole = 'error';
171
+ if (e.type === 'message') {
172
+ if (e.role === 'user')
173
+ userTurns++;
174
+ else if (e.role === 'assistant')
175
+ assistantTurns++;
176
+ }
177
+ else if (e.type === 'tool_use' && !e._local) {
178
+ toolCount++;
179
+ }
180
+ else if (e.type === 'error') {
181
+ errorCount++;
182
+ }
183
+ else if (e.type === 'usage') {
184
+ if (e.model)
185
+ modelSet.add(shortenModel(e.model));
186
+ outputTokens += e.outputTokens ?? 0;
187
+ cacheReadTokens += e.cacheReadTokens ?? 0;
33
188
  }
34
- // Skip: thinking, tool_result, init, result (too noisy for transcript)
35
189
  }
36
- return lines.join('\n');
190
+ return {
191
+ models: Array.from(modelSet),
192
+ userTurns,
193
+ assistantTurns,
194
+ toolCount,
195
+ errorCount,
196
+ outputTokens,
197
+ cacheReadTokens,
198
+ firstTs: firstTs === Infinity ? 0 : firstTs,
199
+ lastTs: lastTs === -Infinity ? 0 : lastTs,
200
+ };
201
+ }
202
+ /** Strip the 'claude-' prefix and date suffix from a model identifier. */
203
+ function shortenModel(model) {
204
+ return model.replace(/^claude-/, '').replace(/-\d{8}$/, '');
205
+ }
206
+ /** Format a token count as a human-readable string (e.g. 67.5K, 1.2M). */
207
+ function formatTokenCount(n) {
208
+ if (n === 0)
209
+ return '0';
210
+ if (n < 1000)
211
+ return String(n);
212
+ if (n < 1_000_000) {
213
+ const k = n / 1000;
214
+ return (k >= 100 ? Math.round(k) : parseFloat(k.toFixed(1))) + 'K';
215
+ }
216
+ const m = n / 1_000_000;
217
+ return (m >= 100 ? Math.round(m) : parseFloat(m.toFixed(1))) + 'M';
218
+ }
219
+ /** Format a duration in milliseconds as a human-readable string (e.g. '12 min', '2h 30min'). */
220
+ function formatDuration(ms) {
221
+ const totalMin = Math.round(ms / 60_000);
222
+ if (totalMin < 1)
223
+ return 'under 1 min';
224
+ if (totalMin < 60)
225
+ return `${totalMin} min`;
226
+ const hrs = Math.floor(totalMin / 60);
227
+ const mins = totalMin % 60;
228
+ return mins > 0 ? `${hrs}h ${mins}min` : `${hrs}h`;
37
229
  }
38
230
  /**
39
- * Render session as a markdown activity summary.
40
- * Groups files by directory, shows commands in a code block,
41
- * and gives the final message room to breathe.
231
+ * Return the stats line for a session summary header.
232
+ * e.g. "221 turns · 198 tools (10 errors) · 67.5M cached / 361K out · 12 min"
42
233
  */
43
- export function renderSummary(events) {
44
- const filesRead = new Set();
45
- const filesModified = new Set();
46
- const commands = [];
47
- let firstUserMessage = '';
48
- let lastAssistantMessage = '';
49
- for (const event of events) {
50
- if (event.type === 'message') {
51
- if (event.role === 'user' && !firstUserMessage) {
52
- const content = event.content || '';
53
- // Skip local command caveat messages (user ran them, not agent)
54
- if (!/^\s*<local-command-caveat>/i.test(content)) {
55
- const topic = extractSessionTopic(content);
56
- if (topic) {
57
- firstUserMessage = content;
234
+ export function renderSummaryHeader(stats) {
235
+ const turns = stats.userTurns + stats.assistantTurns;
236
+ const parts = [];
237
+ parts.push(`${turns} turn${turns !== 1 ? 's' : ''}`);
238
+ if (stats.toolCount > 0) {
239
+ const toolPart = stats.errorCount > 0
240
+ ? `${stats.toolCount} tools (${stats.errorCount} error${stats.errorCount !== 1 ? 's' : ''})`
241
+ : `${stats.toolCount} tools`;
242
+ parts.push(toolPart);
243
+ }
244
+ if (stats.cacheReadTokens > 0 || stats.outputTokens > 0) {
245
+ const tokenPart = stats.cacheReadTokens > 0
246
+ ? `${formatTokenCount(stats.cacheReadTokens)} cached / ${formatTokenCount(stats.outputTokens)} out`
247
+ : `${formatTokenCount(stats.outputTokens)} out`;
248
+ parts.push(tokenPart);
249
+ }
250
+ if (stats.lastTs > stats.firstTs) {
251
+ parts.push(formatDuration(stats.lastTs - stats.firstTs));
252
+ }
253
+ return parts.join(' · ');
254
+ }
255
+ // ── Prompt reference extraction ───────────────────────────────────────────────
256
+ /** Extract @-mentions, slash paths, and ~/... references from a prompt string. */
257
+ function extractReferences(text) {
258
+ const refs = new Set();
259
+ for (const m of text.matchAll(/@[\w/.-]+/g))
260
+ refs.add(m[0]);
261
+ for (const m of text.matchAll(/(?:^|\s)(\/[\w/.-]{3,})/gm))
262
+ refs.add(m[1]);
263
+ for (const m of text.matchAll(/~\/[\w/.-]+/g))
264
+ refs.add(m[0]);
265
+ return Array.from(refs);
266
+ }
267
+ /** Render the Commands section of the summary, grouping by category and collapsing retries. */
268
+ function renderCommandsSection(cmds, lines) {
269
+ if (cmds.length === 0)
270
+ return;
271
+ const runs = collapseRetries(cmds);
272
+ // Group runs by category → key → {count, samples}
273
+ const catMap = new Map();
274
+ let otherCount = 0;
275
+ const otherKeys = new Map();
276
+ for (const run of runs) {
277
+ const cat = categoryOf(run.raw);
278
+ const key = bucketKey(run.raw);
279
+ if (cat) {
280
+ let catEntry = catMap.get(cat.name);
281
+ if (!catEntry) {
282
+ catEntry = { signal: cat.signal, keys: new Map() };
283
+ catMap.set(cat.name, catEntry);
284
+ }
285
+ const existing = catEntry.keys.get(key) ?? { count: 0, samples: [] };
286
+ existing.count += run.count;
287
+ if (existing.samples.length < 5 && !existing.samples.some(s => sharesPrefix(s, run.raw, 30))) {
288
+ existing.samples.push(run.raw);
289
+ }
290
+ catEntry.keys.set(key, existing);
291
+ }
292
+ else {
293
+ otherCount += run.count;
294
+ const existing = otherKeys.get(key) ?? { count: 0, samples: [] };
295
+ existing.count += run.count;
296
+ if (existing.samples.length < 3)
297
+ existing.samples.push(run.raw);
298
+ otherKeys.set(key, existing);
299
+ }
300
+ }
301
+ // Total command count (sum of all run counts)
302
+ const total = runs.reduce((sum, r) => sum + r.count, 0);
303
+ lines.push(chalk.bold('Commands') + chalk.gray(` (${total})`));
304
+ // Sort categories: high-signal first, then mid, then low, by total count desc. Other last.
305
+ const SIGNAL_ORDER = { high: 0, mid: 1, low: 2 };
306
+ const sortedCats = Array.from(catMap.entries()).sort((a, b) => {
307
+ const sigA = SIGNAL_ORDER[a[1].signal] ?? 3;
308
+ const sigB = SIGNAL_ORDER[b[1].signal] ?? 3;
309
+ if (sigA !== sigB)
310
+ return sigA - sigB;
311
+ const countA = Array.from(a[1].keys.values()).reduce((s, v) => s + v.count, 0);
312
+ const countB = Array.from(b[1].keys.values()).reduce((s, v) => s + v.count, 0);
313
+ return countB - countA;
314
+ });
315
+ for (const [catName, catEntry] of sortedCats) {
316
+ const catTotal = Array.from(catEntry.keys.values()).reduce((s, v) => s + v.count, 0);
317
+ if (catEntry.signal === 'low') {
318
+ // Single inline line: category name + top 5 first tokens
319
+ const topTokens = Array.from(catEntry.keys.keys()).slice(0, 5).join(', ');
320
+ lines.push(` ${chalk.dim(catName)} ${chalk.gray(`(${catTotal})`)} ${chalk.gray('— ' + topTokens)}`);
321
+ }
322
+ else {
323
+ lines.push(` ${chalk.dim(catName)} ${chalk.gray(`(${catTotal})`)}`);
324
+ const keysSorted = Array.from(catEntry.keys.entries()).sort((a, b) => b[1].count - a[1].count);
325
+ const limit = catEntry.signal === 'high' ? Infinity : 3;
326
+ let shown = 0;
327
+ for (const [key, v] of keysSorted) {
328
+ if (shown >= limit)
329
+ break;
330
+ if (catEntry.signal === 'mid') {
331
+ // Mid signal: display the bucket key (e.g. ssh→openclaw browser) with aggregate count
332
+ const countSuffix = v.count > 1 ? chalk.gray(` × ${v.count}`) : '';
333
+ lines.push(` ${chalk.cyan(truncateCmd(key, 80))}${countSuffix}`);
334
+ }
335
+ else {
336
+ // High signal: display distinct raw sample commands
337
+ const distinctSamples = pickDistinct(v.samples, 3);
338
+ for (const sample of distinctSamples) {
339
+ const countSuffix = v.count > 1 ? chalk.gray(` × ${v.count}`) : '';
340
+ lines.push(` ${chalk.cyan(truncateCmd(sample, 80))}${countSuffix}`);
58
341
  }
59
342
  }
343
+ shown++;
60
344
  }
61
- if (event.role === 'assistant' && event.content) {
62
- lastAssistantMessage = event.content;
345
+ }
346
+ }
347
+ if (otherCount > 0) {
348
+ lines.push(` ${chalk.dim('Other')} ${chalk.gray(`(${otherCount})`)}`);
349
+ for (const [, v] of Array.from(otherKeys.entries()).slice(0, 5)) {
350
+ const countSuffix = v.count > 1 ? chalk.gray(` × ${v.count}`) : '';
351
+ lines.push(` ${chalk.cyan(truncateCmd(v.samples[0] ?? '', 80))}${countSuffix}`);
352
+ }
353
+ }
354
+ lines.push('');
355
+ }
356
+ function sharesPrefix(a, b, len) {
357
+ return a.slice(0, len) === b.slice(0, len);
358
+ }
359
+ function pickDistinct(samples, max) {
360
+ const result = [];
361
+ for (const s of samples) {
362
+ if (result.length >= max)
363
+ break;
364
+ if (!result.some(r => sharesPrefix(r, s, 30)))
365
+ result.push(s);
366
+ }
367
+ return result.length > 0 ? result : samples.slice(0, max);
368
+ }
369
+ function truncateCmd(cmd, max) {
370
+ return cmd.length <= max ? cmd : cmd.slice(0, max - 1) + '…';
371
+ }
372
+ // ── File grouping ─────────────────────────────────────────────────────────────
373
+ /** Group file paths by their parent directory, relative to cwd. */
374
+ function groupByParentDir(paths, cwd) {
375
+ const groups = new Map();
376
+ for (const p of paths) {
377
+ const rel = relativeToCwd(p, cwd);
378
+ const slashIdx = rel.lastIndexOf('/');
379
+ const dir = slashIdx >= 0 ? rel.slice(0, slashIdx) : '.';
380
+ const base = slashIdx >= 0 ? rel.slice(slashIdx + 1) : rel;
381
+ const arr = groups.get(dir) ?? [];
382
+ arr.push(base);
383
+ groups.set(dir, arr);
384
+ }
385
+ return new Map(Array.from(groups.entries()).sort((a, b) => b[1].length - a[1].length));
386
+ }
387
+ /** Render grouped file paths as indented, clickable terminal lines. */
388
+ function renderFileGroup(lines, groups, absPathMap) {
389
+ if (groups.size === 1) {
390
+ const [dir, files] = Array.from(groups.entries())[0];
391
+ for (const f of files) {
392
+ const abs = absPathMap.get(dir === '.' ? f : `${dir}/${f}`) ?? '';
393
+ const label = dir === '.' ? f : `${dir}/${f}`;
394
+ lines.push(' ' + chalk.cyan(abs ? linkPath(abs, label) : label));
395
+ }
396
+ }
397
+ else {
398
+ for (const [dir, files] of groups) {
399
+ lines.push(` ${chalk.dim(dir + '/')}`);
400
+ for (const f of files) {
401
+ const abs = absPathMap.get(dir === '.' ? f : `${dir}/${f}`) ?? '';
402
+ const label = f;
403
+ lines.push(' ' + chalk.cyan(abs ? linkPath(abs, label) : label));
63
404
  }
64
405
  }
65
- else if (event.type === 'tool_use') {
66
- // Skip local command tool_use events (user commands, not agent actions)
406
+ }
407
+ }
408
+ // ── Main summary renderer ─────────────────────────────────────────────────────
409
+ /**
410
+ * Render session as an activity summary.
411
+ * Returns a chalk-formatted string (not markdown) for direct terminal output.
412
+ */
413
+ export function renderSummary(events, cwd) {
414
+ // ── Collect data in a single chronological pass ───────────────────────────
415
+ let firstUserMessage = '';
416
+ const attachments = [];
417
+ let lastAssistantMessage = '';
418
+ // File paths (absolute) for grouping — split by whether they're inside cwd
419
+ const filesModifiedAbs = new Set();
420
+ const filesReadAbs = new Set();
421
+ const filesModifiedExternal = new Set();
422
+ // Commands with timestamps
423
+ const cmdList = [];
424
+ // Plan items
425
+ const todoItems = [];
426
+ let exitPlanContent = null;
427
+ // Subagent spawns
428
+ const subagents = [];
429
+ // Errors
430
+ const errors = [];
431
+ // Assistant message count (used to decide whether the session produced any narration)
432
+ let assistantCount = 0;
433
+ const isInsideCwd = (p) => !!(cwd && p.startsWith(cwd + '/'));
434
+ for (const event of events) {
435
+ const ts = new Date(event.timestamp).getTime() || 0;
436
+ if (event.type === 'tool_use') {
67
437
  if (event._local)
68
438
  continue;
69
439
  const tool = event.tool || '';
70
- const p = event.path || event.args?.file_path || event.args?.path || '';
440
+ const args = event.args || {};
441
+ const p = event.path || args.file_path || args.path || '';
71
442
  if (['Read', 'read_file', 'view_file', 'cat_file', 'get_file'].includes(tool)) {
72
443
  if (p)
73
- filesRead.add(p);
444
+ filesReadAbs.add(p);
74
445
  }
75
446
  else if (['Write', 'Edit', 'write_file', 'edit_file', 'create_file', 'replace', 'patch'].includes(tool)) {
76
447
  if (p)
77
- filesModified.add(p);
448
+ (isInsideCwd(p) || !cwd ? filesModifiedAbs : filesModifiedExternal).add(p);
78
449
  }
79
450
  if (event.command) {
80
451
  const cmd = event.command.replace(/\n/g, ' ').trim();
81
452
  if (cmd)
82
- commands.push(cmd.length <= 80 ? cmd : cmd.slice(0, 77) + '...');
453
+ cmdList.push({ cmd, ts });
454
+ }
455
+ // Plan items: TodoWrite items + TaskCreate descriptions (project's task tracker)
456
+ if (tool === 'TodoWrite' && Array.isArray(args.todos)) {
457
+ for (const item of args.todos) {
458
+ const text = item.content || item.text || String(item);
459
+ if (text && !todoItems.includes(text))
460
+ todoItems.push(text);
461
+ }
462
+ }
463
+ if (tool === 'TaskCreate' && (args.description || args.prompt)) {
464
+ const text = String(args.description || args.prompt || '').slice(0, 140);
465
+ if (text && !todoItems.includes(text))
466
+ todoItems.push(text);
467
+ }
468
+ if (tool === 'ExitPlanMode') {
469
+ exitPlanContent = args.result || args.plan || args.content || null;
470
+ }
471
+ // Subagent spawns
472
+ if ((tool === 'Agent' || tool === 'Task') && (args.description || args.prompt)) {
473
+ subagents.push({
474
+ description: String(args.description || args.prompt || '').slice(0, 120),
475
+ subagentType: String(args.subagent_type || ''),
476
+ });
83
477
  }
84
478
  }
479
+ else if (event.type === 'error') {
480
+ errors.push({
481
+ tool: event.tool || 'unknown',
482
+ cmd: event.args?.command ? String(event.args.command).slice(0, 80) : undefined,
483
+ content: event.content?.slice(0, 120),
484
+ });
485
+ }
486
+ else if (event.type === 'message') {
487
+ if (event.role === 'user') {
488
+ if (!firstUserMessage) {
489
+ const content = event.content || '';
490
+ if (!/^\s*<local-command-caveat>/i.test(content)) {
491
+ const topic = extractSessionTopic(content);
492
+ if (topic)
493
+ firstUserMessage = content;
494
+ }
495
+ }
496
+ }
497
+ else if (event.role === 'assistant' && event.content) {
498
+ lastAssistantMessage = event.content;
499
+ assistantCount++;
500
+ }
501
+ }
502
+ else if (event.type === 'attachment') {
503
+ attachments.push({ mediaType: event.mediaType || 'image/png' });
504
+ }
85
505
  }
86
- const md = [];
87
- // Prompt -- clean up agent preambles, show the human-written part
506
+ // Dedupe: files in Modified should not appear in Read
507
+ for (const p of filesModifiedAbs)
508
+ filesReadAbs.delete(p);
509
+ for (const p of filesModifiedExternal)
510
+ filesReadAbs.delete(p);
511
+ // Build abs→rel mapping for linkPath
512
+ const buildAbsMap = (absSet) => {
513
+ const m = new Map();
514
+ for (const abs of absSet) {
515
+ const rel = relativeToCwd(abs, cwd);
516
+ m.set(rel, abs);
517
+ }
518
+ return m;
519
+ };
520
+ const modifiedAbsMap = buildAbsMap(filesModifiedAbs);
521
+ const readAbsMap = buildAbsMap(filesReadAbs);
522
+ // ── Render sections ───────────────────────────────────────────────────────
523
+ const lines = [''];
524
+ // 1. Prompt
88
525
  if (firstUserMessage) {
89
526
  const cleaned = cleanSessionPrompt(firstUserMessage);
90
527
  if (cleaned) {
91
- const promptText = cleaned.length > 300 ? cleaned.slice(0, 297) + '...' : cleaned;
92
- md.push(`**Prompt:** ${promptText.split('\n')[0]}`);
93
- const secondLine = promptText.split('\n')[1]?.trim();
528
+ lines.push(chalk.bold('Prompt:') + ' ' + cleaned.split('\n')[0]);
529
+ const secondLine = cleaned.split('\n')[1]?.trim();
94
530
  if (secondLine)
95
- md.push(secondLine);
96
- md.push('');
531
+ lines.push(' ' + secondLine);
532
+ const refs = extractReferences(cleaned);
533
+ if (refs.length > 0) {
534
+ lines.push(chalk.gray(' Referenced: ' + refs.join(', ')));
535
+ }
97
536
  }
98
537
  }
99
- const hasActivity = filesModified.size > 0 || filesRead.size > 0 || commands.length > 0;
100
- // Files modified (most important -- what changed)
101
- if (filesModified.size > 0) {
102
- md.push(`**Modified** (${filesModified.size})`);
103
- const grouped = groupByDirectory(filesModified);
104
- formatFileGroups(md, grouped);
105
- md.push('');
538
+ // Attachments (images/documents in the first user turn)
539
+ if (attachments.length > 0) {
540
+ const mediaTypes = [...new Set(attachments.map(a => a.mediaType))].join(', ');
541
+ lines.push(chalk.gray(` + ${attachments.length} screenshot${attachments.length !== 1 ? 's' : ''} (${mediaTypes})`));
106
542
  }
107
- // Files read -- compact, just show count + dirs
108
- if (filesRead.size > 0) {
109
- if (filesRead.size <= 5) {
110
- md.push(`**Read** (${filesRead.size})`);
111
- const grouped = groupByDirectory(filesRead);
112
- formatFileGroups(md, grouped);
543
+ if (firstUserMessage || attachments.length > 0)
544
+ lines.push('');
545
+ // 2. Plan
546
+ if (todoItems.length > 0 || exitPlanContent) {
547
+ lines.push(chalk.bold('Plan'));
548
+ if (exitPlanContent) {
549
+ const planLines = exitPlanContent.split('\n').slice(0, 10);
550
+ for (const l of planLines)
551
+ lines.push(' ' + l);
113
552
  }
114
553
  else {
115
- // For many files, just list the directories
116
- const grouped = groupByDirectory(filesRead);
117
- const dirList = Array.from(grouped.keys()).map(d => `\`${d}/\``).join(', ');
118
- md.push(`**Read** ${filesRead.size} files across ${dirList}`);
119
- }
120
- md.push('');
121
- }
122
- // Commands -- compact code block
123
- if (commands.length > 0) {
124
- md.push(`**Commands** (${commands.length})`);
125
- md.push('```');
126
- const shown = commands.slice(0, 10);
127
- for (const cmd of shown)
128
- md.push(cmd);
129
- if (commands.length > 10)
130
- md.push(`# ... +${commands.length - 10} more`);
131
- md.push('```');
132
- md.push('');
133
- }
134
- // Final message -- the most important part
554
+ for (const item of todoItems.slice(0, 20)) {
555
+ lines.push(' · ' + item);
556
+ }
557
+ }
558
+ lines.push('');
559
+ }
560
+ // 3. Subagents
561
+ if (subagents.length > 0) {
562
+ lines.push(chalk.bold('Subagents') + chalk.gray(` (${subagents.length})`));
563
+ for (const s of subagents) {
564
+ const typeSuffix = s.subagentType ? chalk.gray(` (${s.subagentType})`) : '';
565
+ lines.push(' Task: ' + s.description + typeSuffix);
566
+ }
567
+ lines.push('');
568
+ }
569
+ // 4. Modified files
570
+ if (filesModifiedAbs.size > 0) {
571
+ lines.push(chalk.bold('Modified') + chalk.gray(` (${filesModifiedAbs.size})`));
572
+ const groups = groupByParentDir(filesModifiedAbs, cwd);
573
+ renderFileGroup(lines, groups, modifiedAbsMap);
574
+ lines.push('');
575
+ }
576
+ // 4b. External edits (files edited outside the project root — typically /tmp)
577
+ if (filesModifiedExternal.size > 0) {
578
+ const externalList = [...filesModifiedExternal].sort();
579
+ const home = process.env.HOME ?? '';
580
+ const display = externalList.slice(0, 3).map(p => home && p.startsWith(home) ? p.replace(home, '~') : p);
581
+ const more = externalList.length > 3 ? chalk.gray(` +${externalList.length - 3} more`) : '';
582
+ lines.push(chalk.gray(`External edits (${filesModifiedExternal.size}): ${display.join(', ')}${more}`));
583
+ lines.push('');
584
+ }
585
+ // 5. Read files
586
+ if (filesReadAbs.size > 0) {
587
+ if (filesReadAbs.size <= 5) {
588
+ lines.push(chalk.bold('Read') + chalk.gray(` (${filesReadAbs.size})`));
589
+ const groups = groupByParentDir(filesReadAbs, cwd);
590
+ renderFileGroup(lines, groups, readAbsMap);
591
+ }
592
+ else {
593
+ lines.push(chalk.bold('Read') + chalk.gray(` ${filesReadAbs.size} other files`));
594
+ }
595
+ lines.push('');
596
+ }
597
+ // 6. Commands
598
+ renderCommandsSection(cmdList, lines);
599
+ // 7. Errors
600
+ if (errors.length > 0) {
601
+ const first = errors[0];
602
+ const firstDesc = first.cmd
603
+ ? `${first.tool} "${first.cmd.slice(0, 60)}"`
604
+ : first.content
605
+ ? `${first.tool}: ${first.content.slice(0, 60)}`
606
+ : first.tool;
607
+ lines.push(chalk.red(chalk.bold('Errors')) +
608
+ chalk.gray(`: ${errors.length} failure${errors.length !== 1 ? 's' : ''} — first: ${firstDesc}`));
609
+ lines.push('');
610
+ }
611
+ // 9. Final message
135
612
  if (lastAssistantMessage) {
136
- if (hasActivity)
137
- md.push('---');
138
- md.push('');
139
- const truncated = lastAssistantMessage.length > 600
140
- ? lastAssistantMessage.slice(0, 597) + '...'
613
+ const hasActivity = filesModifiedAbs.size > 0 || filesReadAbs.size > 0 || cmdList.length > 0;
614
+ if (hasActivity || errors.length > 0)
615
+ lines.push(chalk.gray(''.repeat(60)));
616
+ lines.push('');
617
+ const truncated = lastAssistantMessage.length > 3000
618
+ ? lastAssistantMessage.slice(0, 2997) + '...'
141
619
  : lastAssistantMessage;
142
- md.push(truncated);
143
- md.push('');
620
+ lines.push(renderMarkdown(truncated).trimEnd());
621
+ lines.push('');
144
622
  }
145
- else if (!hasActivity) {
146
- md.push('*No activity recorded in this session.*');
147
- md.push('');
623
+ else if (filesModifiedAbs.size === 0 &&
624
+ filesReadAbs.size === 0 &&
625
+ cmdList.length === 0 &&
626
+ assistantCount === 0) {
627
+ lines.push(chalk.gray('No activity recorded in this session.'));
628
+ lines.push('');
148
629
  }
149
- return md.join('\n');
630
+ return lines.join('\n');
150
631
  }
151
- function formatFileGroups(md, grouped) {
152
- if (grouped.size === 1) {
153
- // Single directory -- inline list
154
- const [dir, files] = Array.from(grouped.entries())[0];
155
- for (const f of files)
156
- md.push(`- \`${dir}/${f}\``);
632
+ // ── Event filters ─────────────────────────────────────────────────────────────
633
+ /** Allowed values for --include/--exclude role filters. */
634
+ export const VALID_ROLE_VALUES = ['user', 'assistant', 'thinking', 'tools'];
635
+ /**
636
+ * Parse a comma-separated role list (e.g. "user,assistant") into typed values.
637
+ * Throws with a clear message listing valid values on any unknown entry.
638
+ */
639
+ export function parseRoleList(raw, flag) {
640
+ const parts = raw.split(',').map(s => s.trim()).filter(Boolean);
641
+ if (parts.length === 0) {
642
+ throw new Error(`${flag} requires at least one role. Valid values: ${VALID_ROLE_VALUES.join(', ')}`);
157
643
  }
158
- else {
159
- // Multiple directories -- group with bold headers
160
- grouped.forEach((files, dir) => {
161
- md.push(` **${dir}/** ${files.map(f => '\`' + f + '\`').join(', ')}`);
644
+ for (const p of parts) {
645
+ if (!VALID_ROLE_VALUES.includes(p)) {
646
+ throw new Error(`Invalid value "${p}" for ${flag}. Valid values: ${VALID_ROLE_VALUES.join(', ')}`);
647
+ }
648
+ }
649
+ return parts;
650
+ }
651
+ function roleOfEvent(e) {
652
+ if (e.type === 'message' && e.role === 'user')
653
+ return 'user';
654
+ if (e.type === 'message' && e.role === 'assistant')
655
+ return 'assistant';
656
+ if (e.type === 'thinking')
657
+ return 'thinking';
658
+ if (e.type === 'tool_use' || e.type === 'tool_result')
659
+ return 'tools';
660
+ return null;
661
+ }
662
+ /**
663
+ * Keep events whose role is in `include` (whitelist) or whose role is not in
664
+ * `exclude` (blacklist). Non-role events (errors, usage, attachments, init,
665
+ * result) are preserved unless explicitly constrained by `include` — that
666
+ * matches the user model: "include user" means "only user".
667
+ */
668
+ function applyRoleFilter(events, opts) {
669
+ if (opts.include && opts.include.length > 0) {
670
+ const set = new Set(opts.include);
671
+ return events.filter(e => {
672
+ const role = roleOfEvent(e);
673
+ return role !== null && set.has(role);
674
+ });
675
+ }
676
+ if (opts.exclude && opts.exclude.length > 0) {
677
+ const set = new Set(opts.exclude);
678
+ return events.filter(e => {
679
+ const role = roleOfEvent(e);
680
+ return role === null || !set.has(role);
162
681
  });
163
682
  }
683
+ return events;
164
684
  }
165
685
  /**
166
- * Group file paths by their parent directory.
167
- * Returns Map<shortDir, basename[]> sorted by most files first.
686
+ * A "turn" starts at each user message. `--first N` keeps events through the
687
+ * end of the Nth user turn; `--last N` keeps events from the start of the
688
+ * (M-N+1)th user turn to the end. If the session has no user messages, every
689
+ * assistant message counts as a turn instead.
168
690
  */
169
- function groupByDirectory(paths) {
170
- const groups = new Map();
171
- const home = process.env.HOME || '';
172
- paths.forEach(p => {
173
- // Find a meaningful short directory name
174
- const dir = extractShortDir(p, home);
175
- const basename = p.split('/').pop() || p;
176
- const existing = groups.get(dir) || [];
177
- existing.push(basename);
178
- groups.set(dir, existing);
179
- });
180
- // Sort by count descending
181
- const sorted = new Map(Array.from(groups.entries()).sort((a, b) => b[1].length - a[1].length));
182
- return sorted;
691
+ function applyTurnSlice(events, opts) {
692
+ if (opts.first === undefined && opts.last === undefined)
693
+ return events;
694
+ if (opts.first !== undefined && opts.last !== undefined) {
695
+ throw new Error('--first and --last are mutually exclusive');
696
+ }
697
+ const n = (opts.first ?? opts.last);
698
+ if (!Number.isFinite(n) || n <= 0) {
699
+ throw new Error(`Turn count must be a positive integer, got ${n}`);
700
+ }
701
+ const isTurnStart = (e) => e.type === 'message' && e.role === 'user';
702
+ const turnStartIdx = [];
703
+ for (let i = 0; i < events.length; i++)
704
+ if (isTurnStart(events[i]))
705
+ turnStartIdx.push(i);
706
+ // Fallback: no user messages — treat assistant messages as turn boundaries.
707
+ if (turnStartIdx.length === 0) {
708
+ for (let i = 0; i < events.length; i++) {
709
+ if (events[i].type === 'message' && events[i].role === 'assistant')
710
+ turnStartIdx.push(i);
711
+ }
712
+ }
713
+ if (turnStartIdx.length === 0)
714
+ return events;
715
+ if (opts.first !== undefined) {
716
+ if (n >= turnStartIdx.length)
717
+ return events;
718
+ const endIdx = turnStartIdx[n]; // exclusive
719
+ return events.slice(0, endIdx);
720
+ }
721
+ // --last
722
+ if (n >= turnStartIdx.length)
723
+ return events;
724
+ const startIdx = turnStartIdx[turnStartIdx.length - n];
725
+ return events.slice(startIdx);
183
726
  }
184
727
  /**
185
- * Extract a short, meaningful directory label from a full path.
186
- * Uses the session's cwd (project root) to strip the prefix, falling back
187
- * to heuristics if cwd isn't available.
188
- *
189
- * /Users/me/src/github.com/user/project/src/lib/session/types.ts
190
- * with cwd=/Users/me/src/github.com/user/project -> src/lib/session
728
+ * Apply include/exclude/first/last. Turn slicing runs first so role filters
729
+ * operate on the sliced window (natural semantics: "last 3 turns, user only").
191
730
  */
192
- function extractShortDir(fullPath, home) {
193
- let p = fullPath;
194
- // Strip home prefix
195
- if (home && p.startsWith(home + '/'))
196
- p = p.slice(home.length + 1);
197
- const parts = p.split('/');
198
- parts.pop(); // remove filename
199
- // Walk from the end to find a code-structure directory (src/, lib/, tests/, etc.)
200
- // Skip the outermost 'src' if it's a Go-style path (src/github.com/...)
201
- const codeMarkers = new Set(['src', 'lib', 'tests', 'test', 'cmd', 'pkg', 'internal', 'app', 'components', 'scripts']);
202
- for (let i = parts.length - 1; i >= 0; i--) {
203
- if (!codeMarkers.has(parts[i]))
204
- continue;
205
- // Skip if next segment looks like a domain (github.com, golang.org, etc.)
206
- if (i + 1 < parts.length && parts[i + 1].includes('.'))
207
- continue;
208
- return parts.slice(i).join('/');
209
- }
210
- // No code marker found -- show just the last dir (project name)
211
- return parts.length > 0 ? parts[parts.length - 1] : '.';
731
+ export function filterEvents(events, opts) {
732
+ if (opts.include && opts.include.length > 0 && opts.exclude && opts.exclude.length > 0) {
733
+ throw new Error('--include and --exclude are mutually exclusive');
734
+ }
735
+ const sliced = applyTurnSlice(events, opts);
736
+ return applyRoleFilter(sliced, opts);
212
737
  }
738
+ // ── Conversation renderers ────────────────────────────────────────────────────
213
739
  /**
214
- * Render session as a markdown trace (for GitHub gist uploads).
215
- * Structure: Agent Reasoning section + Full Conversation section.
740
+ * Build the conversation as a single markdown string: user / assistant
741
+ * messages, inline thinking blocks, tool calls, and errors. Emitted in event
742
+ * order so reasoning sits where it actually occurred relative to the assistant
743
+ * reply.
216
744
  */
217
- export function renderTrace(events) {
218
- const reasoning = [];
219
- const conversation = [];
745
+ export function renderConversationMarkdown(events) {
746
+ const parts = [];
220
747
  for (const event of events) {
221
- if (event.type === 'thinking' && event.content) {
222
- reasoning.push(event.content);
223
- }
224
748
  if (event.type === 'message') {
225
749
  if (event.role === 'user') {
226
- conversation.push(`## User\n\n${event.content}`);
750
+ parts.push(`## User\n\n${event.content ?? ''}`);
227
751
  }
228
- else if (event.role === 'assistant' && event.content) {
229
- conversation.push(`## Agent\n\n${event.content}`);
752
+ else if (event.role === 'assistant') {
753
+ parts.push(`## Assistant\n\n${event.content ?? ''}`);
230
754
  }
231
755
  }
756
+ else if (event.type === 'thinking') {
757
+ if (event.content)
758
+ parts.push(`### Thinking\n\n${event.content}`);
759
+ }
232
760
  else if (event.type === 'tool_use') {
233
761
  const tool = event.tool || 'unknown';
234
762
  if (event.command) {
235
- conversation.push(`## Tool: ${tool}\n\n\`\`\`bash\n${event.command}\n\`\`\``);
763
+ parts.push(`### Tool: ${tool}\n\n\`\`\`bash\n${event.command}\n\`\`\``);
236
764
  }
237
765
  else if (event.path) {
238
- conversation.push(`## Tool: ${tool}\n\n\`${shortenPath(event.path)}\``);
766
+ parts.push(`### Tool: ${tool}\n\n\`${shortenPathTrace(event.path)}\``);
239
767
  }
240
768
  else {
241
769
  const summary = summarizeToolUse(tool, event.args);
242
- conversation.push(`## Tool: ${tool}\n\n${summary}`);
770
+ parts.push(`### Tool: ${tool}\n\n${summary}`);
771
+ }
772
+ }
773
+ else if (event.type === 'tool_result') {
774
+ if (event.content) {
775
+ const body = event.content.length > 2000 ? event.content.slice(0, 2000) + '\n…' : event.content;
776
+ parts.push(`### Tool Result\n\n\`\`\`\n${body}\n\`\`\``);
243
777
  }
244
778
  }
245
779
  else if (event.type === 'error') {
246
- conversation.push(`## Error\n\n${event.content || 'Unknown error'}`);
780
+ parts.push(`### Error\n\n${event.content || event.tool || 'Unknown error'}`);
247
781
  }
248
782
  }
249
- const parts = [];
250
- if (reasoning.length > 0) {
251
- parts.push('# Agent Reasoning\n');
252
- parts.push(reasoning.join('\n\n---\n\n'));
253
- }
254
- parts.push('\n\n# Full Conversation\n');
255
- parts.push(conversation.join('\n\n'));
256
- return parts.join('\n');
783
+ return parts.join('\n\n');
257
784
  }
258
785
  /**
259
786
  * Render session as JSON (normalized events).
@@ -261,7 +788,8 @@ export function renderTrace(events) {
261
788
  export function renderJson(events) {
262
789
  return JSON.stringify(events, null, 2);
263
790
  }
264
- function shortenPath(p) {
791
+ /** Replace the home directory prefix with ~ for trace display. */
792
+ function shortenPathTrace(p) {
265
793
  const home = process.env.HOME || '';
266
794
  if (home && p.startsWith(home))
267
795
  return '~' + p.slice(home.length);