lonny-agent 0.2.6 → 0.2.7

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 (475) hide show
  1. package/.github/workflows/ci.yml +21 -0
  2. package/AGENTS.md +106 -0
  3. package/CHANGELOG.md +24 -0
  4. package/dist/agent/__tests__/session-restore.test.d.ts +2 -0
  5. package/dist/agent/__tests__/session-restore.test.d.ts.map +1 -0
  6. package/dist/agent/__tests__/session-restore.test.js +260 -0
  7. package/dist/agent/__tests__/session-restore.test.js.map +1 -0
  8. package/dist/agent/commands.d.ts +26 -0
  9. package/dist/agent/commands.d.ts.map +1 -0
  10. package/dist/agent/commands.js +253 -0
  11. package/dist/agent/commands.js.map +1 -0
  12. package/dist/agent/compaction.js +8 -1
  13. package/dist/agent/compaction.js.map +1 -1
  14. package/dist/agent/index.d.ts +2 -1
  15. package/dist/agent/index.d.ts.map +1 -1
  16. package/dist/agent/index.js +2 -2
  17. package/dist/agent/index.js.map +1 -1
  18. package/dist/agent/memory.d.ts +12 -0
  19. package/dist/agent/memory.d.ts.map +1 -0
  20. package/dist/agent/memory.js +51 -0
  21. package/dist/agent/memory.js.map +1 -0
  22. package/dist/agent/prompt-builder.d.ts +6 -1
  23. package/dist/agent/prompt-builder.d.ts.map +1 -1
  24. package/dist/agent/prompt-builder.js +89 -26
  25. package/dist/agent/prompt-builder.js.map +1 -1
  26. package/dist/agent/providers/openai.d.ts +1 -0
  27. package/dist/agent/providers/openai.d.ts.map +1 -1
  28. package/dist/agent/providers/openai.js +16 -3
  29. package/dist/agent/providers/openai.js.map +1 -1
  30. package/dist/agent/session-chat.d.ts +3 -0
  31. package/dist/agent/session-chat.d.ts.map +1 -0
  32. package/dist/agent/session-chat.js +384 -0
  33. package/dist/agent/session-chat.js.map +1 -0
  34. package/dist/agent/session-display.d.ts +29 -0
  35. package/dist/agent/session-display.d.ts.map +1 -0
  36. package/dist/agent/session-display.js +179 -0
  37. package/dist/agent/session-display.js.map +1 -0
  38. package/dist/agent/session-persistence.d.ts +55 -0
  39. package/dist/agent/session-persistence.d.ts.map +1 -0
  40. package/dist/agent/session-persistence.js +101 -0
  41. package/dist/agent/session-persistence.js.map +1 -0
  42. package/dist/agent/session-utils.d.ts +5 -0
  43. package/dist/agent/session-utils.d.ts.map +1 -0
  44. package/dist/agent/session-utils.js +51 -0
  45. package/dist/agent/session-utils.js.map +1 -0
  46. package/dist/agent/session.d.ts +15 -17
  47. package/dist/agent/session.d.ts.map +1 -1
  48. package/dist/agent/session.js +226 -608
  49. package/dist/agent/session.js.map +1 -1
  50. package/dist/cli/index.d.ts +4 -0
  51. package/dist/cli/index.d.ts.map +1 -1
  52. package/dist/cli/index.js +67 -2
  53. package/dist/cli/index.js.map +1 -1
  54. package/dist/config/index.d.ts +2 -2
  55. package/dist/config/index.d.ts.map +1 -1
  56. package/dist/index.js +24 -3
  57. package/dist/index.js.map +1 -1
  58. package/dist/tools/__tests__/bash.test.js +168 -1
  59. package/dist/tools/__tests__/bash.test.js.map +1 -1
  60. package/dist/tools/__tests__/edit.test.js +253 -6
  61. package/dist/tools/__tests__/edit.test.js.map +1 -1
  62. package/dist/tools/__tests__/fetch.test.js +1 -1
  63. package/dist/tools/__tests__/fetch.test.js.map +1 -1
  64. package/dist/tools/__tests__/glob.test.js +1 -1
  65. package/dist/tools/__tests__/glob.test.js.map +1 -1
  66. package/dist/tools/__tests__/grep.test.js +1 -1
  67. package/dist/tools/__tests__/grep.test.js.map +1 -1
  68. package/dist/tools/__tests__/ls.test.js +1 -1
  69. package/dist/tools/__tests__/ls.test.js.map +1 -1
  70. package/dist/tools/__tests__/read.test.js +1 -1
  71. package/dist/tools/__tests__/read.test.js.map +1 -1
  72. package/dist/tools/__tests__/registry.test.js +313 -0
  73. package/dist/tools/__tests__/registry.test.js.map +1 -1
  74. package/dist/tools/__tests__/write_plan.test.js +1 -1
  75. package/dist/tools/__tests__/write_plan.test.js.map +1 -1
  76. package/dist/tools/bash.d.ts.map +1 -1
  77. package/dist/tools/bash.js +659 -39
  78. package/dist/tools/bash.js.map +1 -1
  79. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts +2 -0
  80. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.d.ts.map +1 -0
  81. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js +7 -0
  82. package/dist/tools/codebase/ast/__tests__/ast-edit-demo.js.map +1 -0
  83. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts +6 -0
  84. package/dist/tools/codebase/ast/__tests__/ast-edit-final.d.ts.map +1 -0
  85. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js +11 -0
  86. package/dist/tools/codebase/ast/__tests__/ast-edit-final.js.map +1 -0
  87. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts +2 -0
  88. package/dist/tools/codebase/ast/__tests__/ast-edit-test.d.ts.map +1 -0
  89. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js +9 -0
  90. package/dist/tools/codebase/ast/__tests__/ast-edit-test.js.map +1 -0
  91. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts +2 -0
  92. package/dist/tools/codebase/ast/__tests__/integration.test.d.ts.map +1 -0
  93. package/dist/tools/codebase/ast/__tests__/integration.test.js +415 -0
  94. package/dist/tools/codebase/ast/__tests__/integration.test.js.map +1 -0
  95. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts +2 -0
  96. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.d.ts.map +1 -0
  97. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js +414 -0
  98. package/dist/tools/codebase/ast/__tests__/tree-sitter.test.js.map +1 -0
  99. package/dist/tools/codebase/ast/adapter.d.ts +23 -0
  100. package/dist/tools/codebase/ast/adapter.d.ts.map +1 -0
  101. package/dist/tools/codebase/ast/adapter.js +11 -0
  102. package/dist/tools/codebase/ast/adapter.js.map +1 -0
  103. package/dist/tools/codebase/ast/tools.d.ts +3 -0
  104. package/dist/tools/codebase/ast/tools.d.ts.map +1 -0
  105. package/dist/tools/codebase/ast/tools.js +286 -0
  106. package/dist/tools/codebase/ast/tools.js.map +1 -0
  107. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts +3 -0
  108. package/dist/tools/codebase/ast/tree-sitter-adapter.d.ts.map +1 -0
  109. package/dist/tools/codebase/ast/tree-sitter-adapter.js +412 -0
  110. package/dist/tools/codebase/ast/tree-sitter-adapter.js.map +1 -0
  111. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts +4 -0
  112. package/dist/tools/codebase/ast/tree-sitter-converters.d.ts.map +1 -0
  113. package/dist/tools/codebase/ast/tree-sitter-converters.js +447 -0
  114. package/dist/tools/codebase/ast/tree-sitter-converters.js.map +1 -0
  115. package/dist/tools/codebase/ast/types.d.ts +103 -0
  116. package/dist/tools/codebase/ast/types.d.ts.map +1 -0
  117. package/dist/tools/codebase/ast/types.js +2 -0
  118. package/dist/tools/codebase/ast/types.js.map +1 -0
  119. package/dist/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  120. package/dist/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  121. package/dist/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  122. package/dist/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  123. package/dist/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  124. package/dist/tools/codebase/find.d.ts +3 -0
  125. package/dist/tools/codebase/find.d.ts.map +1 -0
  126. package/dist/tools/codebase/find.js +61 -0
  127. package/dist/tools/codebase/find.js.map +1 -0
  128. package/dist/tools/codebase/glob.d.ts +3 -0
  129. package/dist/tools/codebase/glob.d.ts.map +1 -0
  130. package/dist/tools/codebase/glob.js +37 -0
  131. package/dist/tools/codebase/glob.js.map +1 -0
  132. package/dist/tools/codebase/grep.d.ts +3 -0
  133. package/dist/tools/codebase/grep.d.ts.map +1 -0
  134. package/dist/tools/codebase/grep.js +169 -0
  135. package/dist/tools/codebase/grep.js.map +1 -0
  136. package/dist/tools/codebase/ls.d.ts +3 -0
  137. package/dist/tools/codebase/ls.d.ts.map +1 -0
  138. package/dist/tools/codebase/ls.js +39 -0
  139. package/dist/tools/codebase/ls.js.map +1 -0
  140. package/dist/tools/codebase/read.d.ts +10 -0
  141. package/dist/tools/codebase/read.d.ts.map +1 -0
  142. package/dist/tools/codebase/read.js +81 -0
  143. package/dist/tools/codebase/read.js.map +1 -0
  144. package/dist/tools/delete_memory.d.ts +3 -0
  145. package/dist/tools/delete_memory.d.ts.map +1 -0
  146. package/dist/tools/delete_memory.js +46 -0
  147. package/dist/tools/delete_memory.js.map +1 -0
  148. package/dist/tools/edit/diff-compute.d.ts +4 -0
  149. package/dist/tools/edit/diff-compute.d.ts.map +1 -0
  150. package/dist/tools/edit/diff-compute.js +18 -0
  151. package/dist/tools/edit/diff-compute.js.map +1 -0
  152. package/dist/tools/edit/diff-render.d.ts +18 -0
  153. package/dist/tools/edit/diff-render.d.ts.map +1 -0
  154. package/dist/tools/edit/diff-render.js +128 -0
  155. package/dist/tools/edit/diff-render.js.map +1 -0
  156. package/dist/tools/edit/edit.d.ts +4 -0
  157. package/dist/tools/edit/edit.d.ts.map +1 -0
  158. package/dist/tools/edit/edit.js +440 -0
  159. package/dist/tools/edit/edit.js.map +1 -0
  160. package/dist/tools/edit/matcher.d.ts +24 -0
  161. package/dist/tools/edit/matcher.d.ts.map +1 -0
  162. package/dist/tools/edit/matcher.js +66 -0
  163. package/dist/tools/edit/matcher.js.map +1 -0
  164. package/dist/tools/edit/parser.d.ts +10 -0
  165. package/dist/tools/edit/parser.d.ts.map +1 -0
  166. package/dist/tools/edit/parser.js +120 -0
  167. package/dist/tools/edit/parser.js.map +1 -0
  168. package/dist/tools/edit/types.d.ts +16 -0
  169. package/dist/tools/edit/types.d.ts.map +1 -0
  170. package/dist/tools/edit/types.js +2 -0
  171. package/dist/tools/edit/types.js.map +1 -0
  172. package/dist/tools/edit/write_plan.d.ts +5 -0
  173. package/dist/tools/edit/write_plan.d.ts.map +1 -0
  174. package/dist/tools/edit/write_plan.js +98 -0
  175. package/dist/tools/edit/write_plan.js.map +1 -0
  176. package/dist/tools/edit.d.ts.map +1 -1
  177. package/dist/tools/edit.js +66 -1
  178. package/dist/tools/edit.js.map +1 -1
  179. package/dist/tools/execute/bash/constants.d.ts +24 -0
  180. package/dist/tools/execute/bash/constants.d.ts.map +1 -0
  181. package/dist/tools/execute/bash/constants.js +88 -0
  182. package/dist/tools/execute/bash/constants.js.map +1 -0
  183. package/dist/tools/execute/bash/errors.d.ts +39 -0
  184. package/dist/tools/execute/bash/errors.d.ts.map +1 -0
  185. package/dist/tools/execute/bash/errors.js +249 -0
  186. package/dist/tools/execute/bash/errors.js.map +1 -0
  187. package/dist/tools/execute/bash/execution.d.ts +18 -0
  188. package/dist/tools/execute/bash/execution.d.ts.map +1 -0
  189. package/dist/tools/execute/bash/execution.js +207 -0
  190. package/dist/tools/execute/bash/execution.js.map +1 -0
  191. package/dist/tools/execute/bash/index.d.ts +3 -0
  192. package/dist/tools/execute/bash/index.d.ts.map +1 -0
  193. package/dist/tools/execute/bash/index.js +117 -0
  194. package/dist/tools/execute/bash/index.js.map +1 -0
  195. package/dist/tools/execute/bash/platform.d.ts +6 -0
  196. package/dist/tools/execute/bash/platform.d.ts.map +1 -0
  197. package/dist/tools/execute/bash/platform.js +64 -0
  198. package/dist/tools/execute/bash/platform.js.map +1 -0
  199. package/dist/tools/execute/bash/security.d.ts +12 -0
  200. package/dist/tools/execute/bash/security.d.ts.map +1 -0
  201. package/dist/tools/execute/bash/security.js +26 -0
  202. package/dist/tools/execute/bash/security.js.map +1 -0
  203. package/dist/tools/execute/bash/validation.d.ts +16 -0
  204. package/dist/tools/execute/bash/validation.d.ts.map +1 -0
  205. package/dist/tools/execute/bash/validation.js +57 -0
  206. package/dist/tools/execute/bash/validation.js.map +1 -0
  207. package/dist/tools/execute/bash.d.ts +3 -0
  208. package/dist/tools/execute/bash.d.ts.map +1 -0
  209. package/dist/tools/execute/bash.js +728 -0
  210. package/dist/tools/execute/bash.js.map +1 -0
  211. package/dist/tools/execute/git.d.ts +3 -0
  212. package/dist/tools/execute/git.d.ts.map +1 -0
  213. package/dist/tools/execute/git.js +183 -0
  214. package/dist/tools/execute/git.js.map +1 -0
  215. package/dist/tools/execute/task_complete.d.ts +7 -0
  216. package/dist/tools/execute/task_complete.d.ts.map +1 -0
  217. package/dist/tools/execute/task_complete.js +38 -0
  218. package/dist/tools/execute/task_complete.js.map +1 -0
  219. package/dist/tools/fetch.d.ts.map +1 -1
  220. package/dist/tools/fetch.js +2 -0
  221. package/dist/tools/fetch.js.map +1 -1
  222. package/dist/tools/find.d.ts.map +1 -1
  223. package/dist/tools/find.js +2 -0
  224. package/dist/tools/find.js.map +1 -1
  225. package/dist/tools/git.d.ts.map +1 -1
  226. package/dist/tools/git.js +2 -0
  227. package/dist/tools/git.js.map +1 -1
  228. package/dist/tools/glob.d.ts.map +1 -1
  229. package/dist/tools/glob.js +2 -0
  230. package/dist/tools/glob.js.map +1 -1
  231. package/dist/tools/grep.d.ts.map +1 -1
  232. package/dist/tools/grep.js +2 -0
  233. package/dist/tools/grep.js.map +1 -1
  234. package/dist/tools/install/clawhub.d.ts +26 -0
  235. package/dist/tools/install/clawhub.d.ts.map +1 -0
  236. package/dist/tools/install/clawhub.js +182 -0
  237. package/dist/tools/install/clawhub.js.map +1 -0
  238. package/dist/tools/install/constants.d.ts +5 -0
  239. package/dist/tools/install/constants.d.ts.map +1 -0
  240. package/dist/tools/install/constants.js +5 -0
  241. package/dist/tools/install/constants.js.map +1 -0
  242. package/dist/tools/install/install_skill.d.ts +3 -0
  243. package/dist/tools/install/install_skill.d.ts.map +1 -0
  244. package/dist/tools/install/install_skill.js +132 -0
  245. package/dist/tools/install/install_skill.js.map +1 -0
  246. package/dist/tools/install/npm.d.ts +8 -0
  247. package/dist/tools/install/npm.d.ts.map +1 -0
  248. package/dist/tools/install/npm.js +52 -0
  249. package/dist/tools/install/npm.js.map +1 -0
  250. package/dist/tools/install/skill-content.d.ts +9 -0
  251. package/dist/tools/install/skill-content.d.ts.map +1 -0
  252. package/dist/tools/install/skill-content.js +89 -0
  253. package/dist/tools/install/skill-content.js.map +1 -0
  254. package/dist/tools/install_skill.d.ts.map +1 -1
  255. package/dist/tools/install_skill.js +2 -0
  256. package/dist/tools/install_skill.js.map +1 -1
  257. package/dist/tools/list_memory.d.ts +3 -0
  258. package/dist/tools/list_memory.d.ts.map +1 -0
  259. package/dist/tools/list_memory.js +55 -0
  260. package/dist/tools/list_memory.js.map +1 -0
  261. package/dist/tools/ls.d.ts.map +1 -1
  262. package/dist/tools/ls.js +2 -0
  263. package/dist/tools/ls.js.map +1 -1
  264. package/dist/tools/memory/delete_memory.d.ts +3 -0
  265. package/dist/tools/memory/delete_memory.d.ts.map +1 -0
  266. package/dist/tools/memory/delete_memory.js +46 -0
  267. package/dist/tools/memory/delete_memory.js.map +1 -0
  268. package/dist/tools/memory/list_memory.d.ts +3 -0
  269. package/dist/tools/memory/list_memory.d.ts.map +1 -0
  270. package/dist/tools/memory/list_memory.js +55 -0
  271. package/dist/tools/memory/list_memory.js.map +1 -0
  272. package/dist/tools/memory/save_memory.d.ts +3 -0
  273. package/dist/tools/memory/save_memory.d.ts.map +1 -0
  274. package/dist/tools/memory/save_memory.js +45 -0
  275. package/dist/tools/memory/save_memory.js.map +1 -0
  276. package/dist/tools/read.d.ts.map +1 -1
  277. package/dist/tools/read.js +2 -0
  278. package/dist/tools/read.js.map +1 -1
  279. package/dist/tools/registry.d.ts +7 -2
  280. package/dist/tools/registry.d.ts.map +1 -1
  281. package/dist/tools/registry.js +112 -17
  282. package/dist/tools/registry.js.map +1 -1
  283. package/dist/tools/save_memory.d.ts +3 -0
  284. package/dist/tools/save_memory.d.ts.map +1 -0
  285. package/dist/tools/save_memory.js +45 -0
  286. package/dist/tools/save_memory.js.map +1 -0
  287. package/dist/tools/search.d.ts.map +1 -1
  288. package/dist/tools/search.js +2 -0
  289. package/dist/tools/search.js.map +1 -1
  290. package/dist/tools/tree.d.ts +12 -0
  291. package/dist/tools/tree.d.ts.map +1 -0
  292. package/dist/tools/tree.js +87 -0
  293. package/dist/tools/tree.js.map +1 -0
  294. package/dist/tools/types.d.ts +12 -0
  295. package/dist/tools/types.d.ts.map +1 -1
  296. package/dist/tools/web/fetch.d.ts +3 -0
  297. package/dist/tools/web/fetch.d.ts.map +1 -0
  298. package/dist/tools/web/fetch.js +49 -0
  299. package/dist/tools/web/fetch.js.map +1 -0
  300. package/dist/tools/web/search.d.ts +3 -0
  301. package/dist/tools/web/search.d.ts.map +1 -0
  302. package/dist/tools/web/search.js +134 -0
  303. package/dist/tools/web/search.js.map +1 -0
  304. package/dist/tools/write_plan.d.ts.map +1 -1
  305. package/dist/tools/write_plan.js +2 -0
  306. package/dist/tools/write_plan.js.map +1 -1
  307. package/dist/tui/commands.d.ts +4 -0
  308. package/dist/tui/commands.d.ts.map +1 -0
  309. package/dist/tui/commands.js +118 -0
  310. package/dist/tui/commands.js.map +1 -0
  311. package/dist/tui/components/colors.d.ts +36 -0
  312. package/dist/tui/components/colors.d.ts.map +1 -0
  313. package/dist/tui/components/colors.js +48 -0
  314. package/dist/tui/components/colors.js.map +1 -0
  315. package/dist/tui/components/header-bar.d.ts +26 -0
  316. package/dist/tui/components/header-bar.d.ts.map +1 -0
  317. package/dist/tui/components/header-bar.js +78 -0
  318. package/dist/tui/components/header-bar.js.map +1 -0
  319. package/dist/tui/components/index.d.ts +9 -0
  320. package/dist/tui/components/index.d.ts.map +1 -0
  321. package/dist/tui/components/index.js +9 -0
  322. package/dist/tui/components/index.js.map +1 -0
  323. package/dist/tui/components/landing-screen.d.ts +12 -0
  324. package/dist/tui/components/landing-screen.d.ts.map +1 -0
  325. package/dist/tui/components/landing-screen.js +53 -0
  326. package/dist/tui/components/landing-screen.js.map +1 -0
  327. package/dist/tui/components/pixel-logo.d.ts +5 -0
  328. package/dist/tui/components/pixel-logo.d.ts.map +1 -0
  329. package/dist/tui/components/pixel-logo.js +27 -0
  330. package/dist/tui/components/pixel-logo.js.map +1 -0
  331. package/dist/tui/components/plan-utils.d.ts +11 -0
  332. package/dist/tui/components/plan-utils.d.ts.map +1 -0
  333. package/dist/tui/components/plan-utils.js +62 -0
  334. package/dist/tui/components/plan-utils.js.map +1 -0
  335. package/dist/tui/components/plans-list.d.ts +18 -0
  336. package/dist/tui/components/plans-list.d.ts.map +1 -0
  337. package/dist/tui/components/plans-list.js +46 -0
  338. package/dist/tui/components/plans-list.js.map +1 -0
  339. package/dist/tui/components/rich-footer.d.ts +29 -0
  340. package/dist/tui/components/rich-footer.d.ts.map +1 -0
  341. package/dist/tui/components/rich-footer.js +124 -0
  342. package/dist/tui/components/rich-footer.js.map +1 -0
  343. package/dist/tui/components/todo-panel.d.ts +14 -0
  344. package/dist/tui/components/todo-panel.d.ts.map +1 -0
  345. package/dist/tui/components/todo-panel.js +83 -0
  346. package/dist/tui/components/todo-panel.js.map +1 -0
  347. package/dist/tui/components.d.ts.map +1 -1
  348. package/dist/tui/components.js +14 -7
  349. package/dist/tui/components.js.map +1 -1
  350. package/dist/tui/index.d.ts +2 -1
  351. package/dist/tui/index.d.ts.map +1 -1
  352. package/dist/tui/index.js +99 -505
  353. package/dist/tui/index.js.map +1 -1
  354. package/dist/tui/overlays.d.ts +28 -0
  355. package/dist/tui/overlays.d.ts.map +1 -0
  356. package/dist/tui/overlays.js +154 -0
  357. package/dist/tui/overlays.js.map +1 -0
  358. package/dist/tui/themes.d.ts +5 -0
  359. package/dist/tui/themes.d.ts.map +1 -0
  360. package/dist/tui/themes.js +38 -0
  361. package/dist/tui/themes.js.map +1 -0
  362. package/dist/web/index.d.ts.map +1 -1
  363. package/dist/web/index.js +60 -122
  364. package/dist/web/index.js.map +1 -1
  365. package/dist/web/public/index.html +1 -1
  366. package/dist/web/public/messages.js +41 -25
  367. package/dist/web/public/state.js +72 -72
  368. package/dist/web/public/style.css +1126 -996
  369. package/dist/web/public/websocket.js +267 -267
  370. package/dist/web/session-bridge.d.ts.map +1 -1
  371. package/dist/web/session-bridge.js +82 -3
  372. package/dist/web/session-bridge.js.map +1 -1
  373. package/docs/ast-tools.md +117 -0
  374. package/package.json +60 -55
  375. package/scripts/check-line-count.mjs +53 -0
  376. package/scripts/copy-ast-wasm.mjs +32 -0
  377. package/src/agent/__tests__/session-restore.test.ts +284 -0
  378. package/src/agent/commands.ts +274 -0
  379. package/src/agent/compaction.ts +9 -2
  380. package/src/agent/index.ts +30 -25
  381. package/src/agent/memory.ts +60 -0
  382. package/src/agent/prompt-builder.ts +100 -26
  383. package/src/agent/providers/anthropic.ts +179 -179
  384. package/src/agent/providers/google.ts +210 -210
  385. package/src/agent/providers/ollama.ts +186 -186
  386. package/src/agent/providers/openai.ts +18 -3
  387. package/src/agent/session-chat.ts +480 -0
  388. package/src/agent/session-display.ts +195 -0
  389. package/src/agent/session-persistence.ts +146 -0
  390. package/src/agent/session-utils.ts +54 -0
  391. package/src/agent/session.ts +261 -718
  392. package/src/cli/index.ts +73 -4
  393. package/src/config/index.ts +342 -342
  394. package/src/index.ts +30 -3
  395. package/src/tools/__tests__/bash.test.ts +187 -1
  396. package/src/tools/__tests__/edit.test.ts +275 -11
  397. package/src/tools/__tests__/fetch.test.ts +1 -1
  398. package/src/tools/__tests__/glob.test.ts +1 -1
  399. package/src/tools/__tests__/grep.test.ts +1 -1
  400. package/src/tools/__tests__/ls.test.ts +1 -1
  401. package/src/tools/__tests__/read.test.ts +1 -1
  402. package/src/tools/__tests__/registry.test.ts +332 -0
  403. package/src/tools/__tests__/write_plan.test.ts +1 -1
  404. package/src/tools/codebase/ast/__tests__/integration.test.ts +452 -0
  405. package/src/tools/codebase/ast/__tests__/tree-sitter.test.ts +466 -0
  406. package/src/tools/codebase/ast/adapter.ts +44 -0
  407. package/src/tools/codebase/ast/tools.ts +322 -0
  408. package/src/tools/codebase/ast/tree-sitter-adapter.ts +468 -0
  409. package/src/tools/codebase/ast/tree-sitter-converters.ts +480 -0
  410. package/src/tools/codebase/ast/types.ts +102 -0
  411. package/src/tools/codebase/ast/wasm/tree-sitter-python.wasm +0 -0
  412. package/src/tools/codebase/ast/wasm/tree-sitter-tsx.wasm +0 -0
  413. package/src/tools/codebase/ast/wasm/tree-sitter-typescript.wasm +0 -0
  414. package/src/tools/codebase/ast/wasm/tree-sitter.wasm +0 -0
  415. package/src/tools/codebase/ast/wasm/web-tree-sitter.wasm +0 -0
  416. package/src/tools/{find.ts → codebase/find.ts} +4 -2
  417. package/src/tools/{glob.ts → codebase/glob.ts} +4 -2
  418. package/src/tools/{grep.ts → codebase/grep.ts} +4 -2
  419. package/src/tools/{ls.ts → codebase/ls.ts} +4 -2
  420. package/src/tools/{read.ts → codebase/read.ts} +5 -3
  421. package/src/tools/edit/diff-compute.ts +21 -0
  422. package/src/tools/edit/diff-render.ts +145 -0
  423. package/src/tools/edit/edit.ts +468 -0
  424. package/src/tools/edit/matcher.ts +70 -0
  425. package/src/tools/edit/parser.ts +131 -0
  426. package/src/tools/edit/types.ts +19 -0
  427. package/src/tools/{write_plan.ts → edit/write_plan.ts} +4 -2
  428. package/src/tools/execute/bash/constants.ts +93 -0
  429. package/src/tools/execute/bash/errors.ts +304 -0
  430. package/src/tools/execute/bash/execution.ts +211 -0
  431. package/src/tools/execute/bash/index.ts +131 -0
  432. package/src/tools/execute/bash/platform.ts +65 -0
  433. package/src/tools/execute/bash/security.ts +28 -0
  434. package/src/tools/execute/bash/validation.ts +78 -0
  435. package/src/tools/execute/git.ts +209 -0
  436. package/src/tools/execute/task_complete.ts +40 -0
  437. package/src/tools/install/clawhub.ts +222 -0
  438. package/src/tools/install/constants.ts +4 -0
  439. package/src/tools/install/install_skill.ts +150 -0
  440. package/src/tools/install/npm.ts +60 -0
  441. package/src/tools/install/skill-content.ts +108 -0
  442. package/src/tools/memory/delete_memory.ts +46 -0
  443. package/src/tools/memory/list_memory.ts +53 -0
  444. package/src/tools/memory/save_memory.ts +45 -0
  445. package/src/tools/registry.ts +119 -19
  446. package/src/tools/tree.ts +103 -0
  447. package/src/tools/types.ts +13 -0
  448. package/src/tools/{fetch.ts → web/fetch.ts} +4 -2
  449. package/src/tools/{search.ts → web/search.ts} +4 -2
  450. package/src/tui/commands.ts +135 -0
  451. package/src/tui/components/colors.ts +52 -0
  452. package/src/tui/components/header-bar.ts +88 -0
  453. package/src/tui/components/index.ts +8 -0
  454. package/src/tui/components/landing-screen.ts +67 -0
  455. package/src/tui/components/pixel-logo.ts +33 -0
  456. package/src/tui/components/plan-utils.ts +69 -0
  457. package/src/tui/components/plans-list.ts +55 -0
  458. package/src/tui/components/rich-footer.ts +146 -0
  459. package/src/tui/components/todo-panel.ts +95 -0
  460. package/src/tui/index.ts +335 -786
  461. package/src/tui/overlays.ts +203 -0
  462. package/src/tui/themes.ts +41 -0
  463. package/src/web/index.ts +64 -127
  464. package/src/web/public/index.html +1 -1
  465. package/src/web/public/messages.js +41 -25
  466. package/src/web/public/state.js +72 -72
  467. package/src/web/public/style.css +1126 -996
  468. package/src/web/public/websocket.js +267 -267
  469. package/src/web/session-bridge.ts +272 -193
  470. package/AGENT.md +0 -99
  471. package/src/tools/bash.ts +0 -125
  472. package/src/tools/edit.ts +0 -747
  473. package/src/tools/git.ts +0 -76
  474. package/src/tools/install_skill.ts +0 -539
  475. package/src/tui/components.ts +0 -635
@@ -1,214 +1,17 @@
1
- import { createHash } from 'node:crypto';
2
1
  import * as fs from 'node:fs';
3
- import * as os from 'node:os';
4
2
  import * as path from 'node:path';
5
- import { saveTokenUsage } from '../config/tokens.js';
6
3
  import { FileReadTracker } from '../diff/apply.js';
7
- import { fmtErr } from '../tools/errors.js';
8
4
  import { ToolRegistry } from '../tools/registry.js';
9
- import { compact, shouldCompact } from './compaction.js';
10
- import { EventChannels, getGlobalEventBus } from './event-bus.js';
11
5
  import { buildSystemPrompt } from './prompt-builder.js';
12
6
  import { AnthropicProvider } from './providers/anthropic.js';
13
7
  import { GoogleProvider } from './providers/google.js';
14
8
  import { OllamaProvider } from './providers/ollama.js';
15
9
  import { OpenAIProvider } from './providers/openai.js';
16
- function getSessionDir() {
17
- return path.join(os.homedir(), '.lonny', 'sessions');
18
- }
19
- function getSessionFilePath(cwd) {
20
- const absPath = path.resolve(cwd);
21
- const hash = createHash('sha256').update(absPath, 'utf-8').digest('hex').slice(0, 12);
22
- const dirName = path.basename(absPath);
23
- const safeName = dirName.replace(/[<>:"/\\|?*]/g, '_');
24
- return path.join(getSessionDir(), `${safeName}-${hash}.json`);
25
- }
26
- function ensureDir(dir) {
27
- if (!fs.existsSync(dir)) {
28
- fs.mkdirSync(dir, { recursive: true });
29
- }
30
- }
31
- // ── Colors ─────────────────────────────────────────────────────────────────
32
- const CY = '\x1b[36m';
33
- const GR = '\x1b[32m';
34
- const YE = '\x1b[33m';
35
- const RE = '\x1b[31m';
36
- const MG = '\x1b[35m';
37
- const GY = '\x1b[90m';
38
- const RS = '\x1b[0m';
39
- const BLD = '\x1b[1m';
40
- const TH = '\x1b[48;2;22;22;32m\x1b[38;2;150;150;170m'; // dark bg + dim fg for thinking
41
- /** Get terminal width (columns), default to 80. */
42
- function termWidth() {
43
- return process.stdout.columns ?? 80;
44
- }
45
- /** Visible width of a string (strip ANSI codes). ASCII=1, CJK/non-ASCII=2. */
46
- function visibleWidth(s) {
47
- let w = 0;
48
- for (let i = 0; i < s.length; i++) {
49
- if (s.charCodeAt(i) === 0x1b) {
50
- // Skip past escape sequence
51
- while (i < s.length && s[i] !== 'm')
52
- i++;
53
- continue;
54
- }
55
- w += s.charCodeAt(i) > 0x7e ? 2 : 1;
56
- }
57
- return w;
58
- }
59
- /** Visible prefix width for thinking box lines: " │" = 3 */
60
- const THINK_PREFIX_WIDTH = 3;
61
- /** Build the top border of the thinking box */
62
- function thinkTopBorder() {
63
- return `\n ${GY}╭───────${RS}${TH} Think ${GY}────────────────────${RS}\n`;
64
- }
65
- /** Build the bottom border of the thinking box */
66
- function thinkBottomBorder() {
67
- return ` ${GY}╰${'─'.repeat(42)}${RS}\n\n`;
68
- }
69
- function writeOut(text, output) {
70
- if (output) {
71
- output.write(text);
72
- }
73
- else {
74
- process.stdout.write(text);
75
- }
76
- }
77
- function printUserMessage(prompt, output) {
78
- // When suppressToolOutput is true (Web UI mode), the frontend already
79
- // displays the user message, so skip sending it as a chunk.
80
- if (output?.suppressToolOutput)
81
- return;
82
- const line = ` ${GY}┃${RS} ${BLD}${CY}You${RS}`;
83
- writeOut(`\n${line} ${prompt}\n\n`, output);
84
- }
85
- function printToolInvocation(tc, output) {
86
- const detail = formatToolInput(tc);
87
- const isWrite = tc.name === 'write_plan' || tc.name === 'edit';
88
- const icon = isWrite ? `${YE}◆${RS}` : `${GR}◇${RS}`;
89
- const label = isWrite ? `${YE}${tc.name}${RS}` : `${GR}${tc.name}${RS}`;
90
- writeOut(`\n ${GY}│${RS} ${icon} ${label}${detail ? ` ${GY}${detail}${RS}` : ''}\n`, output);
91
- }
92
- function printToolResult(tc, result, output) {
93
- if (!result.success) {
94
- writeOut(` ${GY}│${RS} ${RE}✖${RS} ${RE}${result.error}${RS}\n`, output);
95
- return;
96
- }
97
- if (tc.name === 'read') {
98
- const fileCount = (result.output.match(/^=== /gm) || []).length;
99
- writeOut(` ${GY}│${RS} ${GR}✔${RS} read ${fileCount} file(s)\n`, output);
100
- for (const line of result.output.split('\n')) {
101
- if (line.startsWith('=== ')) {
102
- const fp = line.slice(4, line.includes(' ===') ? line.indexOf(' ===') + 4 : undefined);
103
- writeOut(` ${GY}│${RS} ${GY}${fp}${RS}\n`, output);
104
- }
105
- }
106
- }
107
- else if (tc.name === 'glob') {
108
- const count = result.output.split('\n').filter(l => l && !l.startsWith('No')).length;
109
- writeOut(` ${GY}│${RS} ${GR}✔${RS} glob ${count} match(es)\n`, output);
110
- }
111
- else if (tc.name === 'grep') {
112
- const count = result.output.split('\n').filter(l => l && !l.startsWith('No')).length;
113
- writeOut(` ${GY}│${RS} ${GR}✔${RS} grep ${count} match(es)\n`, output);
114
- }
115
- else if (tc.name === 'bash') {
116
- const outLines = result.output.split('\n');
117
- const summary = outLines.length > 1 ? `(${outLines.length} lines)` : '';
118
- writeOut(` ${GY}│${RS} ${GR}✔${RS} bash ${summary}\n`, output);
119
- }
120
- else if (tc.name === 'edit') {
121
- writeOut(` ${GY}│${RS} ${GR}✔${RS} edit\n`, output);
122
- if (result.output) {
123
- for (const l of result.output.split('\n')) {
124
- if (l.trim())
125
- writeOut(` ${GY}│${RS} ${l.trim()}\n`, output);
126
- }
127
- }
128
- }
129
- else if (tc.name === 'write_plan') {
130
- writeOut(` ${GY}│${RS} ${GR}✔${RS} ${result.output || tc.name}\n`, output);
131
- }
132
- else if (tc.name === 'search') {
133
- writeOut(` ${GY}│${RS} ${GR}✔${RS} search: ${String(tc.input.query || '').slice(0, 80)}\n`, output);
134
- }
135
- else {
136
- writeOut(` ${GY}│${RS} ${GR}✔${RS} ${tc.name}\n`, output);
137
- }
138
- }
139
- function isSingleEditShape(v) {
140
- return (typeof v === 'object' &&
141
- v !== null &&
142
- 'file_path' in v &&
143
- 'old_string' in v &&
144
- 'new_string' in v);
145
- }
146
- export function formatToolInput(tc) {
147
- const parts = [];
148
- if (tc.name === 'read' && Array.isArray(tc.input.paths)) {
149
- parts.push(tc.input.paths.join(', '));
150
- }
151
- else if (tc.name === 'glob' && typeof tc.input.pattern === 'string') {
152
- parts.push(tc.input.pattern);
153
- }
154
- else if (tc.name === 'grep') {
155
- if (typeof tc.input.pattern === 'string')
156
- parts.push(`/${tc.input.pattern}/`);
157
- if (typeof tc.input.include === 'string')
158
- parts.push(`in:${tc.input.include}`);
159
- }
160
- else if (tc.name === 'ls') {
161
- parts.push(typeof tc.input.path === 'string' ? tc.input.path : '.');
162
- }
163
- else if (tc.name === 'bash') {
164
- const cmd = typeof tc.input.command === 'string' ? tc.input.command : '';
165
- parts.push(cmd.length > 80 ? `${cmd.slice(0, 80)}\u2026` : cmd);
166
- }
167
- else if (tc.name === 'search') {
168
- if (typeof tc.input.query === 'string')
169
- parts.push(tc.input.query.slice(0, 120));
170
- }
171
- else if (tc.name === 'write_plan') {
172
- if (typeof tc.input.filename === 'string')
173
- parts.push(tc.input.filename);
174
- }
175
- else if (tc.name === 'edit') {
176
- if (Array.isArray(tc.input.edits) && tc.input.edits.every(isSingleEditShape)) {
177
- const paths = tc.input.edits.map(e => e.file_path);
178
- parts.push(paths.join(', '));
179
- }
180
- }
181
- return parts.join(' \u2502 ');
182
- }
183
- function printTokenStats(turnIn, turnOut, totalIn, totalOut, turnApi, totalApi, output, turnCacheHit, turnCacheMiss, totalCacheHit, totalCacheMiss) {
184
- const bus = getGlobalEventBus();
185
- bus.emit(EventChannels.TOKEN_STATS, {
186
- turnIn,
187
- turnOut,
188
- totalIn,
189
- totalOut,
190
- turnApi,
191
- totalApi,
192
- turnCacheHit,
193
- turnCacheMiss,
194
- totalCacheHit,
195
- totalCacheMiss,
196
- });
197
- // Skip terminal output in Web UI mode
198
- if (output?.suppressToolOutput)
199
- return;
200
- const total = totalIn + totalOut;
201
- let msg = ` ${GY}┃${RS} ${GY}${BLD}▴${RS}${GY}${turnIn}${RS} ${GY}${BLD}▾${RS}${GY}${turnOut}${RS} ${GY}total${RS} ${total} ${GY}calls${RS} ${turnApi}(${totalApi})`;
202
- // Show cache hit rate if available
203
- const cacheHit = totalCacheHit ?? 0;
204
- const cacheMiss = totalCacheMiss ?? 0;
205
- const cacheTotal = cacheHit + cacheMiss;
206
- if (cacheTotal > 0) {
207
- const pct = Math.round((cacheHit / cacheTotal) * 100);
208
- msg += ` ${GY}cached${RS} ${pct}%`;
209
- }
210
- writeOut(`\n${msg}\n`, output);
211
- }
10
+ import { runChat } from './session-chat.js';
11
+ import { ensureDir, findLegacySessionFile, generateId, getSessionDir, getSessionFilePath, getSessionFilesForCwd, migrateSessionData, } from './session-persistence.js';
12
+ import { sanitizeMessages } from './session-utils.js';
13
+ export { formatToolInput } from './session-display.js';
14
+ // ── Session class ────────────────────────────────────────────────────────────
212
15
  export class Session {
213
16
  messages;
214
17
  provider;
@@ -217,12 +20,9 @@ export class Session {
217
20
  config;
218
21
  output;
219
22
  _onPlanWritten;
220
- /** Set the plan-written callback and propagate to ToolRegistry */
221
23
  set onPlanWritten(cb) {
222
24
  this._onPlanWritten = cb;
223
- // Update context so setMode() picks it up too
224
25
  this.registry.updateContext({ onPlanWritten: cb });
225
- // Re-register write_plan tool with the new callback
226
26
  if (this.registry.has('write_plan')) {
227
27
  this.registry.reRegisterWritePlan(this.config.cwd, cb);
228
28
  }
@@ -265,23 +65,34 @@ export class Session {
265
65
  else {
266
66
  this.provider = new AnthropicProvider(config.apiKey, config.baseUrl, config.model);
267
67
  }
268
- // Placeholder until initSystemPrompt() is called
269
68
  this.messages = [{ role: 'system', content: '' }];
270
- // Initialize system prompt asynchronously
271
69
  this.initSystemPrompt(config);
272
70
  }
273
- /** Initialize the system prompt asynchronously */
274
71
  initSystemPrompt(config) {
275
- buildSystemPrompt(config).then(prompt => {
276
- this.messages = [{ role: 'system', content: prompt }];
72
+ buildSystemPrompt(config, this.registry.getDefinitions()).then(prompt => {
73
+ if (this.messages.length <= 1) {
74
+ this.messages = [{ role: 'system', content: prompt }];
75
+ }
277
76
  });
278
77
  }
279
- /** Persist the current session to ~/.lonny/sessions/ */
78
+ sessionId = generateId();
79
+ sessionTitle = '';
80
+ sessionCreatedAt = new Date().toISOString();
280
81
  save() {
281
82
  const dir = getSessionDir();
282
83
  ensureDir(dir);
283
- const filePath = getSessionFilePath(this.config.cwd);
84
+ const filePath = getSessionFilePath(this.config.cwd, this.sessionId);
85
+ const now = new Date().toISOString();
86
+ let title = this.sessionTitle;
87
+ if (!title) {
88
+ const firstUserMsg = this.messages.find(m => m.role === 'user');
89
+ if (firstUserMsg && typeof firstUserMsg.content === 'string') {
90
+ title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
91
+ }
92
+ }
284
93
  const data = {
94
+ id: this.sessionId,
95
+ title: title || undefined,
285
96
  cwd: path.resolve(this.config.cwd),
286
97
  messages: this.messages,
287
98
  totalInputTokens: this.totalInputTokens,
@@ -292,38 +103,90 @@ export class Session {
292
103
  mode: this.config.mode,
293
104
  model: this.config.model,
294
105
  provider: this.config.provider,
295
- updatedAt: new Date().toISOString(),
106
+ createdAt: this.sessionCreatedAt,
107
+ updatedAt: now,
296
108
  };
297
109
  fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
298
110
  }
299
- /** Try to load a saved session for the given cwd. Returns null if none exists. */
300
111
  static async load(config, output) {
301
- const filePath = getSessionFilePath(config.cwd);
302
- let data;
303
- try {
304
- data = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
112
+ const cwdSessions = getSessionFilesForCwd(config.cwd);
113
+ console.log(`[session] Found ${cwdSessions.length} session files for cwd "${config.cwd}":`, cwdSessions
114
+ .map(f => `${f.fileName} (updatedAt=${f.data.updatedAt?.slice(0, 19)}, messages=${f.data.messages?.length || 0})`)
115
+ .join(', '));
116
+ if (cwdSessions.length > 0) {
117
+ cwdSessions.sort((a, b) => {
118
+ const aLen = a.data.messages?.length || 0;
119
+ const bLen = b.data.messages?.length || 0;
120
+ if (aLen !== bLen)
121
+ return bLen - aLen;
122
+ return b.data.updatedAt.localeCompare(a.data.updatedAt);
123
+ });
124
+ console.log(`[session] Loading session file: ${cwdSessions[0].fileName}`);
125
+ return Session.loadFromData(cwdSessions[0].data, config, output);
305
126
  }
306
- catch {
127
+ const legacyPath = findLegacySessionFile(config.cwd);
128
+ if (legacyPath) {
129
+ try {
130
+ const raw = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
131
+ const data = migrateSessionData(raw);
132
+ const session = await Session.loadFromData(data, config, output);
133
+ session.save();
134
+ try {
135
+ fs.unlinkSync(legacyPath);
136
+ }
137
+ catch {
138
+ /* ignore */
139
+ }
140
+ return session;
141
+ }
142
+ catch {
143
+ return null;
144
+ }
145
+ }
146
+ return null;
147
+ }
148
+ static async loadById(sessionId, config, output) {
149
+ const allSessions = Session.listSessions();
150
+ const target = allSessions.find(s => s.id === sessionId);
151
+ if (!target)
307
152
  return null;
153
+ const legacyPath = findLegacySessionFile(config.cwd);
154
+ if (legacyPath) {
155
+ try {
156
+ const raw = JSON.parse(fs.readFileSync(legacyPath, 'utf-8'));
157
+ const data = migrateSessionData(raw);
158
+ if (data.id === sessionId) {
159
+ return Session.loadFromData(data, config, output);
160
+ }
161
+ }
162
+ catch {
163
+ /* ignore */
164
+ }
165
+ }
166
+ const filePath = getSessionFilePath(target.cwd, sessionId);
167
+ try {
168
+ const raw = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
169
+ return Session.loadFromData(migrateSessionData(raw), config, output);
308
170
  }
309
- // Verify the cwd matches (in case of hash collision or directory rename)
310
- const savedAbs = path.resolve(data.cwd);
311
- const currentAbs = path.resolve(config.cwd);
312
- if (savedAbs !== currentAbs) {
171
+ catch {
313
172
  return null;
314
173
  }
174
+ }
175
+ static async loadFromData(data, config, output) {
315
176
  const session = new Session(config, output);
316
- // Restore messages (replace the default system prompt with the saved one)
317
177
  session.messages = data.messages;
318
- // Refresh the system prompt only if config actually changed (model, mode, etc.)
319
- // Compare the saved data vs current config to decide
320
- if (data.model !== config.model ||
321
- data.provider !== config.provider ||
322
- data.mode !== config.mode) {
323
- const prompt = await buildSystemPrompt(config);
178
+ console.log(`[session] Loading session ${data.id}: ${data.messages.length} messages from disk`);
179
+ session.messages = sanitizeMessages(session.messages);
180
+ console.log(`[session] After sanitize: ${session.messages.length} messages (removed ${data.messages.length - session.messages.length})`);
181
+ session.sessionId = data.id;
182
+ session.sessionTitle = data.title || '';
183
+ session.sessionCreatedAt = data.createdAt;
184
+ config.mode = data.mode;
185
+ session.registry.setMode(data.mode);
186
+ if (data.model !== config.model || data.provider !== config.provider) {
187
+ const prompt = await buildSystemPrompt(config, session.registry.getDefinitions());
324
188
  session.messages[0] = { role: 'system', content: prompt };
325
189
  }
326
- // Restore token stats
327
190
  session.totalInputTokens = data.totalInputTokens;
328
191
  session.totalOutputTokens = data.totalOutputTokens;
329
192
  session.totalApiCalls = data.totalApiCalls;
@@ -331,406 +194,161 @@ export class Session {
331
194
  session.totalCacheMissTokens = data.totalCacheMissTokens ?? 0;
332
195
  return session;
333
196
  }
334
- /** Remove the saved session file for the given cwd. */
335
197
  static clearSavedSession(cwd) {
336
- const filePath = getSessionFilePath(cwd);
198
+ const cwdSessions = getSessionFilesForCwd(cwd);
199
+ for (const { fileName } of cwdSessions) {
200
+ try {
201
+ fs.unlinkSync(path.join(getSessionDir(), fileName));
202
+ }
203
+ catch {
204
+ /* ignore */
205
+ }
206
+ }
207
+ const legacyPath = findLegacySessionFile(cwd);
208
+ if (legacyPath) {
209
+ try {
210
+ fs.unlinkSync(legacyPath);
211
+ }
212
+ catch {
213
+ /* ignore */
214
+ }
215
+ }
216
+ }
217
+ static listSessions(maxCount) {
218
+ const dir = getSessionDir();
219
+ try {
220
+ if (!fs.existsSync(dir))
221
+ return [];
222
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
223
+ const sessions = [];
224
+ for (const fileName of files) {
225
+ try {
226
+ const raw = JSON.parse(fs.readFileSync(path.join(dir, fileName), 'utf-8'));
227
+ const data = migrateSessionData(raw);
228
+ const firstUserMsg = data.messages.find(m => m.role === 'user');
229
+ let title = data.title || '';
230
+ if (!title && firstUserMsg && typeof firstUserMsg.content === 'string') {
231
+ title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
232
+ }
233
+ sessions.push({
234
+ id: data.id,
235
+ cwd: data.cwd,
236
+ title,
237
+ messageCount: data.messages.length,
238
+ mode: data.mode,
239
+ model: data.model,
240
+ provider: data.provider,
241
+ totalInputTokens: data.totalInputTokens,
242
+ totalOutputTokens: data.totalOutputTokens,
243
+ totalApiCalls: data.totalApiCalls,
244
+ createdAt: data.createdAt,
245
+ updatedAt: data.updatedAt,
246
+ fileName,
247
+ });
248
+ }
249
+ catch {
250
+ // Skip corrupted files
251
+ }
252
+ }
253
+ sessions.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
254
+ if (maxCount && maxCount > 0) {
255
+ return sessions.slice(0, maxCount);
256
+ }
257
+ return sessions;
258
+ }
259
+ catch {
260
+ return [];
261
+ }
262
+ }
263
+ static deleteSession(id) {
264
+ const sessions = Session.listSessions();
265
+ const target = sessions.find(s => s.id === id);
266
+ if (!target)
267
+ return false;
337
268
  try {
338
- fs.unlinkSync(filePath);
269
+ fs.unlinkSync(path.join(getSessionDir(), target.fileName));
270
+ return true;
339
271
  }
340
272
  catch {
341
- // Ignore if file doesn't exist
273
+ return false;
342
274
  }
343
275
  }
276
+ clearSavedSession() {
277
+ Session.clearSavedSession(this.config.cwd);
278
+ }
279
+ fork() {
280
+ const forked = new Session(this.config, this.output);
281
+ forked.messages = [...this.messages];
282
+ forked.onPlanWritten = this.onPlanWritten;
283
+ const baseTitle = this.sessionTitle || 'forked session';
284
+ forked.sessionTitle = `${baseTitle} (fork)`;
285
+ forked.sessionId = generateId();
286
+ forked.sessionCreatedAt = new Date().toISOString();
287
+ forked.save();
288
+ return forked;
289
+ }
290
+ exportSession(filePath) {
291
+ const dir = filePath || path.join(getSessionDir(), `export-${this.sessionId}.json`);
292
+ const now = new Date().toISOString();
293
+ let title = this.sessionTitle;
294
+ if (!title) {
295
+ const firstUserMsg = this.messages.find(m => m.role === 'user');
296
+ if (firstUserMsg && typeof firstUserMsg.content === 'string') {
297
+ title = firstUserMsg.content.slice(0, 80).replace(/\n/g, ' ');
298
+ }
299
+ }
300
+ const exportData = {
301
+ id: this.sessionId,
302
+ title: title || '(untitled)',
303
+ cwd: path.resolve(this.config.cwd),
304
+ mode: this.config.mode,
305
+ model: this.config.model,
306
+ provider: this.config.provider,
307
+ totalInputTokens: this.totalInputTokens,
308
+ totalOutputTokens: this.totalOutputTokens,
309
+ totalApiCalls: this.totalApiCalls,
310
+ totalCacheHitTokens: this.totalCacheHitTokens,
311
+ totalCacheMissTokens: this.totalCacheMissTokens,
312
+ createdAt: this.sessionCreatedAt,
313
+ exportedAt: now,
314
+ messages: this.messages.map(m => ({
315
+ role: m.role,
316
+ content: typeof m.content === 'string' ? m.content : null,
317
+ tool_calls: m.tool_calls
318
+ ? m.tool_calls.map(tc => ({
319
+ id: tc.id,
320
+ name: tc.name,
321
+ input: tc.input,
322
+ }))
323
+ : undefined,
324
+ reasoning_content: m.reasoning_content,
325
+ })),
326
+ };
327
+ const fullPath = path.resolve(dir);
328
+ ensureDir(path.dirname(fullPath));
329
+ fs.writeFileSync(fullPath, JSON.stringify(exportData, null, 2), 'utf-8');
330
+ return fullPath;
331
+ }
344
332
  async setMode(mode) {
345
333
  this.config.mode = mode;
346
- const prompt = await buildSystemPrompt(this.config);
347
- this.messages[0] = { role: 'system', content: prompt };
348
334
  this.registry.setMode(mode);
335
+ const prompt = await buildSystemPrompt(this.config, this.registry.getDefinitions());
336
+ this.messages[0] = { role: 'system', content: prompt };
349
337
  this.save();
350
338
  }
351
- /** Stop the current conversation gracefully */
352
339
  stop() {
353
340
  this.stopped = true;
354
- // Abort any in-flight LLM stream to stop token consumption immediately
355
341
  this.abortController?.abort();
356
342
  }
357
- /** Check if the session was stopped */
358
343
  isStopped() {
359
344
  return this.stopped;
360
345
  }
361
- /** Reset the stopped flag for a new conversation */
362
346
  resetStopped() {
363
347
  this.stopped = false;
364
- // Create new AbortController for next conversation
365
348
  this.abortController = new AbortController();
366
349
  }
367
350
  async chat(userPrompt) {
368
- const bus = getGlobalEventBus();
369
- const out = this.output;
370
- printUserMessage(userPrompt, out);
371
- this.messages.push({ role: 'user', content: userPrompt });
372
- // Save immediately after user message so page refresh doesn't lose it
373
- this.save();
374
- // Reset per-turn counters
375
- this.turnInputTokens = 0;
376
- this.turnOutputTokens = 0;
377
- this.turnApiCalls = 0;
378
- this.turnCacheHitTokens = 0;
379
- this.turnCacheMissTokens = 0;
380
- let iterations = 0;
381
- const maxIterations = 30;
382
- // Reset stopped flag for new conversation
383
- this.resetStopped();
384
- // Create a new AbortController for this chat invocation
385
- // (a new controller is needed each time because abort() is one-shot)
386
- this.abortController = new AbortController();
387
- // Declare toolCalls outside the loop so we can reference it in stop check
388
- let toolCalls = [];
389
- while (iterations < maxIterations) {
390
- // Check if stop was requested
391
- if (this.isStopped()) {
392
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
393
- this.save();
394
- return;
395
- }
396
- iterations++;
397
- this.turnApiCalls++;
398
- this.totalApiCalls++;
399
- toolCalls = [];
400
- let fullResponse = '';
401
- let reasoningContent;
402
- let reasoningOutput = false;
403
- let reasoningLineStart = false;
404
- bus.emit(EventChannels.TURN_START, { prompt: userPrompt, iteration: iterations });
405
- bus.emit(EventChannels.LLM_STREAM_START, { iteration: iterations });
406
- const stream = this.provider.chat(this.messages, this.registry.getDefinitions(), this.abortController.signal);
407
- try {
408
- for await (const chunk of stream) {
409
- if (chunk.reasoning_content) {
410
- reasoningContent = chunk.reasoning_content;
411
- // Stream reasoning content in real-time (only when no text in same chunk)
412
- if (!chunk.text) {
413
- // Emit thinking via EventBus for Web UI
414
- bus.emit(EventChannels.THINKING, { text: chunk.reasoning_content });
415
- if (!reasoningOutput) {
416
- reasoningOutput = true;
417
- reasoningLineStart = true;
418
- if (!out?.suppressToolOutput) {
419
- writeOut(thinkTopBorder(), out);
420
- }
421
- }
422
- // Terminal display with box drawing (skip in Web UI mode, handled by EventBus)
423
- if (!out?.suppressToolOutput) {
424
- // Track column position on current line for wrapping
425
- let thinkCol = 0;
426
- // Handle newlines in streamed content - add left border on each new line
427
- // Also manually wrap long lines so wrapped lines keep the │ prefix.
428
- let remaining = chunk.reasoning_content;
429
- const maxContentWidth = termWidth() - THINK_PREFIX_WIDTH;
430
- while (remaining.length > 0) {
431
- if (reasoningLineStart) {
432
- writeOut(` ${GY}│${RS}${TH}`, out);
433
- reasoningLineStart = false;
434
- thinkCol = 0;
435
- }
436
- const nlIdx = remaining.indexOf('\n');
437
- if (nlIdx === -1) {
438
- // No newline — write as much as fits on current line, wrap if needed
439
- while (remaining.length > 0) {
440
- const segWidth = visibleWidth(remaining);
441
- const avail = maxContentWidth - thinkCol;
442
- if (segWidth <= avail) {
443
- // Fits entirely on current line
444
- writeOut(remaining, out);
445
- thinkCol += segWidth;
446
- remaining = '';
447
- }
448
- else if (avail <= 0) {
449
- // Current line is full, wrap to next
450
- writeOut(`${RS}\n`, out);
451
- writeOut(` ${GY}│${RS}${TH}`, out);
452
- thinkCol = 0;
453
- }
454
- else {
455
- // Write first part that fits, then wrap
456
- // Find character boundary that fits within avail
457
- let cut = avail;
458
- while (cut > 0 && visibleWidth(remaining.slice(0, cut)) > avail)
459
- cut--;
460
- if (cut <= 0)
461
- cut = 1;
462
- writeOut(remaining.slice(0, cut), out);
463
- writeOut(`${RS}\n`, out);
464
- writeOut(` ${GY}│${RS}${TH}`, out);
465
- thinkCol = 0;
466
- remaining = remaining.slice(cut);
467
- }
468
- }
469
- }
470
- else {
471
- // Has newline — process the segment up to newline
472
- const segment = remaining.slice(0, nlIdx);
473
- const segWidth = visibleWidth(segment);
474
- const avail = maxContentWidth - thinkCol;
475
- if (segWidth <= avail) {
476
- // Segment fits on current line
477
- writeOut(segment, out);
478
- writeOut(`${RS}\n`, out);
479
- reasoningLineStart = true;
480
- thinkCol = 0;
481
- }
482
- else {
483
- // Segment too long — write what fits, wrap, then rest
484
- let rest = segment;
485
- // Write remainder of current line
486
- if (avail > 0) {
487
- let cut = avail;
488
- while (cut > 0 && visibleWidth(rest.slice(0, cut)) > avail)
489
- cut--;
490
- if (cut <= 0)
491
- cut = 1;
492
- writeOut(rest.slice(0, cut), out);
493
- rest = rest.slice(cut);
494
- }
495
- writeOut(`${RS}\n`, out);
496
- reasoningLineStart = true;
497
- thinkCol = 0;
498
- // Write rest of segment on continuation line(s)
499
- if (rest.length > 0) {
500
- writeOut(` ${GY}│${RS}${TH}`, out);
501
- reasoningLineStart = false;
502
- while (rest.length > 0) {
503
- const rw = visibleWidth(rest);
504
- if (rw <= maxContentWidth) {
505
- writeOut(rest, out);
506
- thinkCol = rw;
507
- rest = '';
508
- }
509
- else {
510
- let cut = maxContentWidth;
511
- while (cut > 0 && visibleWidth(rest.slice(0, cut)) > maxContentWidth)
512
- cut--;
513
- if (cut <= 0)
514
- cut = 1;
515
- writeOut(rest.slice(0, cut), out);
516
- writeOut(`${RS}\n`, out);
517
- writeOut(` ${GY}│${RS}${TH}`, out);
518
- rest = rest.slice(cut);
519
- }
520
- }
521
- }
522
- writeOut(`${RS}\n`, out);
523
- reasoningLineStart = true;
524
- thinkCol = 0;
525
- }
526
- remaining = remaining.slice(nlIdx + 1);
527
- }
528
- }
529
- }
530
- }
531
- }
532
- if (chunk.type === 'text' && chunk.text) {
533
- if (reasoningOutput) {
534
- bus.emit(EventChannels.THINKING_END, {});
535
- if (!out?.suppressToolOutput) {
536
- writeOut(`${RS}\n`, out);
537
- writeOut(thinkBottomBorder(), out);
538
- }
539
- reasoningOutput = false;
540
- reasoningLineStart = false;
541
- }
542
- fullResponse += chunk.text;
543
- writeOut(chunk.text, out);
544
- }
545
- else if (chunk.type === 'tool_use' && chunk.tool_call) {
546
- toolCalls.push(chunk.tool_call);
547
- }
548
- else if (chunk.type === 'complete') {
549
- if (chunk.usage) {
550
- this.turnInputTokens += chunk.usage.input_tokens;
551
- this.turnOutputTokens += chunk.usage.output_tokens;
552
- this.totalInputTokens += chunk.usage.input_tokens;
553
- this.totalOutputTokens += chunk.usage.output_tokens;
554
- // Accumulate DeepSeek cache hit/miss tokens
555
- if (chunk.usage.prompt_cache_hit_tokens != null) {
556
- this.turnCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
557
- this.totalCacheHitTokens += chunk.usage.prompt_cache_hit_tokens;
558
- }
559
- if (chunk.usage.prompt_cache_miss_tokens != null) {
560
- this.turnCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
561
- this.totalCacheMissTokens += chunk.usage.prompt_cache_miss_tokens;
562
- }
563
- }
564
- if (chunk.finish_reason === 'stop' || chunk.finish_reason === 'end_turn') {
565
- if (toolCalls.length === 0) {
566
- const finalAssistantMsg = {
567
- role: 'assistant',
568
- content: fullResponse || null,
569
- reasoning_content: reasoningContent,
570
- };
571
- this.messages.push(finalAssistantMsg);
572
- printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
573
- writeOut('\n\n', out);
574
- saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
575
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
576
- this.save();
577
- return;
578
- }
579
- }
580
- }
581
- }
582
- }
583
- catch (e) {
584
- const errMsg = fmtErr(e);
585
- const partialContent = fullResponse ? fullResponse.slice(0, 500) : '(empty)';
586
- if (!out?.suppressToolOutput) {
587
- writeOut(`\n${RE}Stream error:${RS} ${errMsg}`, out);
588
- writeOut(`\n ${GY}┃${RS} Partial response: ${partialContent}\n`, out);
589
- }
590
- console.error('[session] Stream error:', errMsg, '| Partial response:', partialContent);
591
- if (reasoningOutput) {
592
- bus.emit(EventChannels.THINKING_END, {});
593
- if (!out?.suppressToolOutput) {
594
- writeOut(`${RS}\n`, out);
595
- writeOut(thinkBottomBorder(), out);
596
- }
597
- }
598
- bus.emit(EventChannels.LLM_STREAM_END, { iteration: iterations, toolCallCount: 0 });
599
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
600
- // If there are pending toolCalls that weren't executed due to abort,
601
- // add an assistant message so the model knows they weren't executed
602
- if (toolCalls.length > 0) {
603
- const interruptedMsg = {
604
- role: 'assistant',
605
- content: null,
606
- tool_calls: toolCalls,
607
- reasoning_content: reasoningContent,
608
- };
609
- this.messages.push(interruptedMsg);
610
- }
611
- saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
612
- this.save();
613
- return;
614
- }
615
- bus.emit(EventChannels.LLM_STREAM_END, {
616
- iteration: iterations,
617
- toolCallCount: toolCalls.length,
618
- });
619
- // Close reasoning display if still open (model ended with tool calls, no text)
620
- if (reasoningOutput) {
621
- bus.emit(EventChannels.THINKING_END, {});
622
- if (!out?.suppressToolOutput) {
623
- writeOut(`${RS}\n`, out);
624
- writeOut(thinkBottomBorder(), out);
625
- }
626
- reasoningOutput = false;
627
- reasoningLineStart = false;
628
- }
629
- if (toolCalls.length === 0) {
630
- if (fullResponse) {
631
- const finalAssistantMsg = {
632
- role: 'assistant',
633
- content: fullResponse,
634
- reasoning_content: reasoningContent,
635
- };
636
- this.messages.push(finalAssistantMsg);
637
- printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
638
- writeOut('\n\n', out);
639
- }
640
- saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
641
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: 0 });
642
- this.save();
643
- return;
644
- }
645
- const assistantMsg = {
646
- role: 'assistant',
647
- content: fullResponse || null,
648
- tool_calls: toolCalls,
649
- reasoning_content: reasoningContent,
650
- };
651
- this.messages.push(assistantMsg);
652
- this.save();
653
- // ── User confirmation for write-type tool calls ──
654
- if (!this.config.autoApprove && this.output?.confirmTool && toolCalls.length > 0) {
655
- const writeTools = ['edit', 'bash', 'write_plan', 'install_skill'];
656
- const needsConfirm = toolCalls.filter(tc => writeTools.includes(tc.name));
657
- if (needsConfirm.length > 0) {
658
- const approved = await this.output.confirmTool(needsConfirm);
659
- if (!approved) {
660
- const rejectMsg = {
661
- role: 'tool',
662
- content: 'USER_REJECTED: The user declined to execute the requested tool calls. Try a different approach.',
663
- tool_call_id: needsConfirm[0].id,
664
- name: 'user_feedback',
665
- };
666
- this.messages.push(rejectMsg);
667
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
668
- continue;
669
- }
670
- }
671
- }
672
- for (let i = 0; i < toolCalls.length; i++) {
673
- const tc = toolCalls[i];
674
- // Check if stop was requested after each tool call
675
- if (this.isStopped()) {
676
- // Add assistant message with remaining tool_calls so model knows they weren't executed
677
- const remainingToolCalls = toolCalls.slice(i);
678
- if (remainingToolCalls.length > 0) {
679
- const interruptedMsg = {
680
- role: 'assistant',
681
- content: null,
682
- tool_calls: remainingToolCalls,
683
- };
684
- this.messages.push(interruptedMsg);
685
- }
686
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
687
- this.save();
688
- return;
689
- }
690
- bus.emit(EventChannels.TOOL_CALL, { name: tc.name, input: tc.input, id: tc.id });
691
- if (!out?.suppressToolOutput) {
692
- printToolInvocation(tc, out);
693
- }
694
- const result = await this.registry.dispatch(tc);
695
- if (result.success) {
696
- bus.emit(EventChannels.TOOL_RESULT, { name: tc.name, id: tc.id, output: result.output });
697
- }
698
- else {
699
- bus.emit(EventChannels.TOOL_ERROR, { name: tc.name, id: tc.id, error: result.error });
700
- }
701
- if (!out?.suppressToolOutput) {
702
- printToolResult(tc, result, out);
703
- }
704
- const resultMsg = {
705
- role: 'tool',
706
- content: result.success ? result.output : `ERROR: ${result.error}`,
707
- tool_call_id: tc.id,
708
- name: tc.name,
709
- };
710
- this.messages.push(resultMsg);
711
- }
712
- this.save();
713
- // End the current turn before next iteration — frontend creates a new message
714
- bus.emit(EventChannels.TURN_END, { iterations, toolCallCount: toolCalls.length });
715
- // Check if compaction is needed
716
- if (shouldCompact(this.messages, this.config.contextWindow)) {
717
- const before = this.messages.length;
718
- const result = compact(this.messages, this.config.contextWindow);
719
- if (result.compressed) {
720
- this.messages = result.messages;
721
- bus.emit(EventChannels.COMPACTION_TRIGGERED, { before, after: result.newCount });
722
- if (out && !out.suppressToolOutput) {
723
- out.write(`\n ${GY}┃${RS} ${GY}📦 Compressed context: ${before} → ${result.newCount} messages${RS}\n`);
724
- }
725
- }
726
- }
727
- }
728
- if (iterations >= maxIterations) {
729
- printTokenStats(this.turnInputTokens, this.turnOutputTokens, this.totalInputTokens, this.totalOutputTokens, this.turnApiCalls, this.totalApiCalls, out, this.turnCacheHitTokens, this.turnCacheMissTokens, this.totalCacheHitTokens, this.totalCacheMissTokens);
730
- writeOut('\nAgent reached maximum iterations. Stopping.\n', out);
731
- }
732
- saveTokenUsage(this.config.cwd, this.turnInputTokens, this.turnOutputTokens, this.turnApiCalls);
733
- this.save();
351
+ return runChat(this, userPrompt);
734
352
  }
735
353
  }
736
354
  //# sourceMappingURL=session.js.map