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
@@ -0,0 +1,274 @@
1
+ import type { Config } from '../config/index.js'
2
+ import { resetTokenUsage } from '../config/tokens.js'
3
+ import { fmtErr } from '../tools/errors.js'
4
+ import { resetGlobalEventBus } from './event-bus.js'
5
+ import type { LLMMessage } from './llm.js'
6
+ import { ensurePromptsDir, loadPromptTemplates } from './prompt-templates.js'
7
+ import { Session } from './session.js'
8
+ import { ensureSkillsDir, loadSkills } from './skills.js'
9
+
10
+ export interface CommandUI {
11
+ write(text: string): void
12
+ replaceContent(text: string): void
13
+ onStateChange(): void
14
+ onNewSession(session: Session): void
15
+ }
16
+
17
+ export interface CommandEnv {
18
+ session: Session
19
+ config: Config
20
+ ui: CommandUI
21
+ isRunning: boolean
22
+ }
23
+
24
+ export function handleNew(env: CommandEnv): Session {
25
+ env.session.stop()
26
+ Session.clearSavedSession(env.config.cwd)
27
+ resetTokenUsage(env.config.cwd)
28
+ resetGlobalEventBus()
29
+ const newSession = new Session(env.config, env.session.output)
30
+ newSession.onPlanWritten = env.session.onPlanWritten
31
+ env.ui.replaceContent('')
32
+ env.ui.onNewSession(newSession)
33
+ env.ui.onStateChange()
34
+ return newSession
35
+ }
36
+
37
+ export async function handleMode(env: CommandEnv, mode: string): Promise<boolean> {
38
+ if (mode !== 'code' && mode !== 'plan' && mode !== 'ask' && mode !== 'loop') {
39
+ env.ui.write(`Usage: /mode code|plan|ask|loop (current: ${env.session.config.mode})`)
40
+ return true
41
+ }
42
+ await env.session.setMode(mode)
43
+ env.ui.write(`Switched to ${mode} mode`)
44
+ env.ui.onStateChange()
45
+ return true
46
+ }
47
+
48
+ export function handleModel(env: CommandEnv, model: string): boolean {
49
+ if (!model) {
50
+ env.ui.write(`Current model: ${env.session.config.model}`)
51
+ return true
52
+ }
53
+ env.session.config.model = model
54
+ // Trigger system prompt rebuild by re-setting mode
55
+ env.session.setMode(env.session.config.mode)
56
+ env.ui.write(`Model switched to ${model}`)
57
+ env.ui.onStateChange()
58
+ return true
59
+ }
60
+
61
+ export function handlePrompts(env: CommandEnv): boolean {
62
+ const templates = loadPromptTemplates(env.config.cwd)
63
+ if (templates.length === 0) {
64
+ env.ui.write('No prompt templates found. Create .md files in .lonny/prompts/')
65
+ } else {
66
+ let msg = `Prompt Templates (${templates.length}):\n`
67
+ for (const t of templates) {
68
+ msg += ` \u2022 ${t.name}`
69
+ if (t.description) msg += ` \u2014 ${t.description}`
70
+ msg += '\n'
71
+ }
72
+ env.ui.write(msg.trimEnd())
73
+ }
74
+ return true
75
+ }
76
+
77
+ export function handleSkills(env: CommandEnv): boolean {
78
+ const skills = loadSkills(env.config.cwd)
79
+ if (skills.length === 0) {
80
+ env.ui.write('No skills loaded. Create .md files in .lonny/skills/')
81
+ } else {
82
+ let msg = `Active Skills (${skills.length}):\n`
83
+ for (const s of skills) {
84
+ msg += ` \u2022 ${s.name}`
85
+ if (s.description) msg += ` \u2014 ${s.description}`
86
+ msg += '\n'
87
+ }
88
+ env.ui.write(msg.trimEnd())
89
+ }
90
+ return true
91
+ }
92
+
93
+ export function handleSessions(env: CommandEnv): boolean {
94
+ const allSessions = Session.listSessions()
95
+ if (allSessions.length === 0) {
96
+ env.ui.write('No saved sessions found.')
97
+ return true
98
+ }
99
+ let msg = `Saved Sessions (${allSessions.length}):\n`
100
+ msg +=
101
+ ' ID Title Mode Messages Tokens Updated\n'
102
+ for (const s of allSessions) {
103
+ const id = s.id.padEnd(9)
104
+ const title = (s.title || '(untitled)').slice(0, 42).padEnd(43)
105
+ const mode = s.mode.padEnd(7)
106
+ const msgs = String(s.messageCount).padEnd(9)
107
+ const tokens = String(s.totalInputTokens + s.totalOutputTokens).padEnd(9)
108
+ const date = s.updatedAt.slice(0, 10)
109
+ msg += ` ${id} ${title} ${mode} ${msgs} ${tokens} ${date}\n`
110
+ }
111
+ msg += '\n Use /session delete <id> to delete a session'
112
+ env.ui.write(msg)
113
+ return true
114
+ }
115
+
116
+ export async function handleSessionCommand(env: CommandEnv, arg: string): Promise<boolean> {
117
+ if (arg === 'delete' || arg.startsWith('delete ')) {
118
+ const id = arg.slice(arg.startsWith('delete ') ? 7 : 6).trim()
119
+ if (id) {
120
+ const deleted = Session.deleteSession(id)
121
+ if (deleted) {
122
+ env.ui.write(`Deleted session ${id}`)
123
+ } else {
124
+ env.ui.write(`Session not found: ${id}`)
125
+ }
126
+ } else {
127
+ env.ui.write('Usage: /session delete <id>')
128
+ }
129
+ return true
130
+ }
131
+
132
+ if (arg.startsWith('title ')) {
133
+ const title = arg.slice(6).trim()
134
+ if (title) {
135
+ env.session.sessionTitle = title
136
+ env.session.save()
137
+ env.ui.write(`Session titled: ${title}`)
138
+ } else {
139
+ env.ui.write('Usage: /session title <name>')
140
+ }
141
+ return true
142
+ }
143
+
144
+ if (arg === 'export') {
145
+ try {
146
+ const filePath = env.session.exportSession()
147
+ env.ui.write(`Session exported to ${filePath}`)
148
+ } catch (err) {
149
+ env.ui.write(`Export failed: ${fmtErr(err)}`)
150
+ }
151
+ return true
152
+ }
153
+
154
+ if (arg === 'switch' || arg.startsWith('switch ')) {
155
+ const id = arg.startsWith('switch ') ? arg.slice(7).trim() : ''
156
+ if (!id) {
157
+ env.ui.write('Usage: /session switch <id>. Use /sessions to list session IDs.')
158
+ return true
159
+ }
160
+ if (env.isRunning) {
161
+ env.ui.write('Cannot switch session while agent is running. Wait or use /stop first.')
162
+ return true
163
+ }
164
+ const switched = await Session.loadById(id, env.config, env.session.output)
165
+ if (switched) {
166
+ switched.onPlanWritten = env.session.onPlanWritten
167
+ const preview = lastUserQuestion(switched.messages)
168
+ let msg = `Switched to session ${switched.sessionTitle || switched.sessionId}`
169
+ if (preview) msg += ` Last question: ${preview}`
170
+ env.ui.replaceContent(msg + '\n')
171
+ env.ui.onNewSession(switched)
172
+ env.ui.onStateChange()
173
+ } else {
174
+ env.ui.write(`Session not found: ${id}`)
175
+ }
176
+ return true
177
+ }
178
+
179
+ // Default: show current session info
180
+ const id = env.session.sessionId
181
+ const title = env.session.sessionTitle || '(untitled)'
182
+ const mode = env.session.config.mode
183
+ const model = env.session.config.model
184
+ const provider = env.session.config.provider
185
+ const msgs = env.session.messages.length
186
+ const totalIn = env.session.totalInputTokens
187
+ const totalOut = env.session.totalOutputTokens
188
+ const totalApi = env.session.totalApiCalls
189
+ const created = env.session.sessionCreatedAt.slice(0, 10)
190
+ const info =
191
+ '' +
192
+ `ID: ${id}\n` +
193
+ `Title: ${title}\n` +
194
+ `Mode: ${mode}\n` +
195
+ `Model: ${model} (${provider})\n` +
196
+ `Messages: ${msgs}\n` +
197
+ `Tokens: ${totalIn + totalOut} (in: ${totalIn}, out: ${totalOut})\n` +
198
+ `API Calls: ${totalApi}\n` +
199
+ `Created: ${created}\n`
200
+ env.ui.write(info.trimEnd())
201
+ return true
202
+ }
203
+
204
+ export async function handleFork(env: CommandEnv): Promise<boolean> {
205
+ if (env.isRunning) {
206
+ env.ui.write('Cannot fork while agent is running. Wait or use /stop first.')
207
+ return true
208
+ }
209
+ if (env.session.messages.length <= 1) {
210
+ env.ui.write('No conversation to fork. Start a conversation first.')
211
+ return true
212
+ }
213
+ const forked = env.session.fork()
214
+ forked.onPlanWritten = env.session.onPlanWritten
215
+ const baseTitle = forked.sessionTitle || 'forked session'
216
+ env.ui.write(`Forked new session: ${baseTitle}`)
217
+ env.ui.write(` ID: ${forked.sessionId}`)
218
+ env.ui.onNewSession(forked)
219
+ env.ui.onStateChange()
220
+ return true
221
+ }
222
+
223
+ export function handleInit(env: CommandEnv): boolean {
224
+ ensureSkillsDir(env.config.cwd)
225
+ ensurePromptsDir(env.config.cwd)
226
+ env.ui.write('Initialized .lonny/skills/ and .lonny/prompts/')
227
+ return true
228
+ }
229
+
230
+ export function handleStop(env: CommandEnv): boolean {
231
+ if (!env.isRunning) {
232
+ env.ui.write('Agent is not running.')
233
+ return true
234
+ }
235
+ env.session.stop()
236
+ env.ui.write('Stopping agent...')
237
+ return true
238
+ }
239
+
240
+ function lastUserQuestion(messages: LLMMessage[]): string | null {
241
+ const lastUserMsg = [...messages].reverse().find(m => m.role === 'user')
242
+ if (!lastUserMsg || typeof lastUserMsg.content !== 'string') return null
243
+ return lastUserMsg.content.length > 80
244
+ ? `${lastUserMsg.content.slice(0, 80)}\u2026`
245
+ : lastUserMsg.content
246
+ }
247
+
248
+ export async function dispatchCommand(env: CommandEnv, cmd: string, arg: string): Promise<boolean> {
249
+ switch (cmd) {
250
+ case 'new':
251
+ handleNew(env)
252
+ return true
253
+ case 'mode':
254
+ return handleMode(env, arg)
255
+ case 'model':
256
+ return handleModel(env, arg)
257
+ case 'prompts':
258
+ return handlePrompts(env)
259
+ case 'skills':
260
+ return handleSkills(env)
261
+ case 'sessions':
262
+ return handleSessions(env)
263
+ case 'session':
264
+ return handleSessionCommand(env, arg)
265
+ case 'fork':
266
+ return handleFork(env)
267
+ case 'init':
268
+ return handleInit(env)
269
+ case 'stop':
270
+ return handleStop(env)
271
+ default:
272
+ return false
273
+ }
274
+ }
@@ -186,12 +186,19 @@ function buildSummary(messages: LLMMessage[]): string {
186
186
 
187
187
  function extractEditPaths(input: Record<string, unknown>): string[] {
188
188
  const paths: string[] = []
189
+ // Accumulate paths from legacy input format: { edits: [{ file_path: '...' }, ...] }
189
190
  if (Array.isArray(input.edits)) {
190
191
  for (const e of input.edits) {
191
192
  if (typeof e === 'object' && e && 'file_path' in e) {
192
- paths.push(e.file_path as string)
193
+ paths.push((e as Record<string, unknown>).file_path as string)
193
194
  }
194
195
  }
195
196
  }
196
- return paths
197
+ // Also support direct input format used by tests: { file_path: '...' }
198
+ const direct = input as Record<string, unknown>
199
+ if (typeof direct.file_path === 'string') {
200
+ paths.push(direct.file_path)
201
+ }
202
+ // Deduplicate to avoid reporting duplicates
203
+ return Array.from(new Set(paths))
197
204
  }
@@ -1,25 +1,30 @@
1
- import * as readline from 'node:readline'
2
- import type { Config } from '../config/index.js'
3
- import { Session, type SessionOutput } from './session.js'
4
-
5
- export async function runAgent(prompt: string, config: Config): Promise<void> {
6
- const output: SessionOutput = {
7
- write: text => process.stdout.write(text),
8
- confirmTool: async toolCalls => {
9
- console.log('\nAllow these tool calls?')
10
- for (const tc of toolCalls) {
11
- const input = JSON.stringify(tc.input)
12
- console.log(` \u2022 ${tc.name}: ${input.slice(0, 120)}`)
13
- }
14
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
15
- return new Promise(resolve => {
16
- rl.question(' (y/N) ', answer => {
17
- rl.close()
18
- resolve(answer.trim().toLowerCase() === 'y')
19
- })
20
- })
21
- },
22
- }
23
- const session = (await Session.load(config, output)) || new Session(config, output)
24
- await session.chat(prompt)
25
- }
1
+ import * as readline from 'node:readline'
2
+ import type { Config } from '../config/index.js'
3
+ import { Session, type SessionOutput } from './session.js'
4
+
5
+ export async function runAgent(
6
+ prompt: string,
7
+ config: Config,
8
+ preloadedSession?: Session,
9
+ ): Promise<void> {
10
+ const output: SessionOutput = {
11
+ write: text => process.stdout.write(text),
12
+ confirmTool: async toolCalls => {
13
+ console.log('\nAllow these tool calls?')
14
+ for (const tc of toolCalls) {
15
+ const input = JSON.stringify(tc.input)
16
+ console.log(` \u2022 ${tc.name}: ${input.slice(0, 120)}`)
17
+ }
18
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
19
+ return new Promise(resolve => {
20
+ rl.question(' (y/N) ', answer => {
21
+ rl.close()
22
+ resolve(answer.trim().toLowerCase() === 'y')
23
+ })
24
+ })
25
+ },
26
+ }
27
+ const session =
28
+ preloadedSession ?? (await Session.load(config, output)) ?? new Session(config, output)
29
+ await session.chat(prompt)
30
+ }
@@ -0,0 +1,60 @@
1
+ import * as fs from 'node:fs'
2
+ import * as path from 'node:path'
3
+
4
+ export interface MemoryEntry {
5
+ id: string
6
+ createdAt: string
7
+ content: string
8
+ tags?: string[]
9
+ }
10
+
11
+ const MEM_DIR = '.lonny/memory'
12
+
13
+ export function getMemoryDir(cwd: string): string {
14
+ return path.resolve(cwd, MEM_DIR)
15
+ }
16
+
17
+ export function ensureMemoryDir(cwd: string): void {
18
+ const d = getMemoryDir(cwd)
19
+ if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true })
20
+ }
21
+
22
+ export function loadMemory(cwd: string): MemoryEntry[] {
23
+ const d = getMemoryDir(cwd)
24
+ try {
25
+ if (!fs.existsSync(d)) return []
26
+ const files = fs.readdirSync(d).filter(f => f.endsWith('.json'))
27
+ const out: MemoryEntry[] = []
28
+ for (const f of files) {
29
+ try {
30
+ const data = JSON.parse(fs.readFileSync(path.join(d, f), 'utf-8'))
31
+ out.push(data as MemoryEntry)
32
+ } catch (e) {
33
+ // ignore corrupted file
34
+ }
35
+ }
36
+ // sort by createdAt
37
+ out.sort((a, b) => a.createdAt.localeCompare(b.createdAt))
38
+ return out
39
+ } catch (e) {
40
+ return []
41
+ }
42
+ }
43
+
44
+ export function saveMemory(cwd: string, entry: MemoryEntry): void {
45
+ ensureMemoryDir(cwd)
46
+ const id = entry.id || String(Date.now())
47
+ const file = path.join(getMemoryDir(cwd), `${id}.json`)
48
+ fs.writeFileSync(file, JSON.stringify(entry, null, 2), 'utf-8')
49
+ }
50
+
51
+ export function formatMemoryForPrompt(memories: MemoryEntry[]): string {
52
+ if (!memories || memories.length === 0) return ''
53
+ const parts: string[] = []
54
+ for (const m of memories) {
55
+ parts.push(
56
+ `- [${m.createdAt}] ${m.content.slice(0, 400).replace(/\n/g, ' ')}${m.content.length > 400 ? '…' : ''}`,
57
+ )
58
+ }
59
+ return parts.join('\n')
60
+ }
@@ -1,13 +1,26 @@
1
1
  import * as os from 'node:os'
2
2
  import type { Config } from '../config/index.js'
3
+ import { formatToolTreeForPrompt } from '../tools/tree.js'
4
+ import type { ToolDefinition } from '../tools/types.js'
5
+
6
+ const CORE_TOOL_NAMES = new Set(['read', 'edit', 'bash', 'glob', 'grep'])
7
+
8
+ import { formatMemoryForPrompt, loadMemory } from './memory.js'
3
9
  import { discoverProject, formatProjectContext } from './project.js'
4
10
  import { formatSkillsForPrompt, loadSkills } from './skills.js'
5
11
 
6
12
  /**
7
13
  * Build the system prompt for the current configuration.
8
14
  * Extracted from session.ts to keep module size manageable (<500 LoC target).
15
+ *
16
+ * @param config - Current configuration
17
+ * @param definitions - Optional tool definitions for dynamic tree generation.
18
+ * When provided, replaces the hardcoded tool lists with a hierarchical tree.
9
19
  */
10
- export async function buildSystemPrompt(config: Config): Promise<string> {
20
+ export async function buildSystemPrompt(
21
+ config: Config,
22
+ definitions?: ToolDefinition[],
23
+ ): Promise<string> {
11
24
  const platform = os.platform()
12
25
  const release = os.release()
13
26
  const shell = process.env.SHELL || process.env.ComSpec || 'unknown'
@@ -19,12 +32,33 @@ export async function buildSystemPrompt(config: Config): Promise<string> {
19
32
  const skills = loadSkills(cwd)
20
33
  const skillsSection = formatSkillsForPrompt(skills)
21
34
 
35
+ // ── Load long-term memory (persistent) ─────────────────────────────────
36
+ const memories = loadMemory(cwd)
37
+ const memorySection = formatMemoryForPrompt(memories)
38
+
22
39
  // ── Load project context ─────────────────────────────────────────────────
23
40
  const projectInfo = await discoverProject(cwd)
24
41
  const projectSection = formatProjectContext(projectInfo)
25
42
 
26
43
  // ── Mode-specific tool list ───────────────────────────────────────────
27
44
  function getToolListForMode(mode: string): string {
45
+ // When tool definitions are available, use the dynamic tree
46
+ if (definitions && definitions.length > 0) {
47
+ const header =
48
+ mode === 'plan'
49
+ ? 'Available tools (read-only investigation + write_plan):'
50
+ : mode === 'ask'
51
+ ? 'Available tools:'
52
+ : 'Available tools:'
53
+ const tree = formatToolTreeForPrompt(definitions, CORE_TOOL_NAMES)
54
+ const note =
55
+ mode !== 'ask'
56
+ ? '\n Direct access: read, edit, bash, glob, grep\n Extended tools: use `tool()` gateway (see tree above)\n'
57
+ : ''
58
+ return `${header}\n${tree}${note}\n`
59
+ }
60
+
61
+ // Fallback hardcoded lists (when definitions not available)
28
62
  if (mode === 'ask') {
29
63
  return `Available tools:
30
64
  - \`fetch\`: Fetch content from a URL
@@ -50,11 +84,12 @@ export async function buildSystemPrompt(config: Config): Promise<string> {
50
84
  - \`grep\`: Search file content by regex (pattern: string, include?: string, path?: string)
51
85
  - \`ls\`: List directory (path?: string)
52
86
  - \`bash\`: Execute a shell command — for running commands (NOT for creating or modifying files — use \`edit\` for that)
53
- - \`edit\`: Replace text in files using markdown code block format. Use: \`edit({ content: "\`\`\`edit\\nfile: path\\nold: |\\ntext\\nnew: |\\ntext\\n\`\`\`" })\`
54
- - \`install_skill\`: Install an npm package as a skill — fetches package info from npm, runs npm install, and creates a .lonny/skills/ file with usage instructions for the AI
55
- - \`find\`: Find files by name pattern (pattern: string, path?: string, maxResults?: number)
56
- - \`git\`: Run read-only git commands (command: string)
57
- - \`search\`: Search the web using Tavily (query: string, search_depth?: string, include_answer?: boolean, max_results?: number, topic?: string, days?: number)
87
+ - \`edit\`: Replace text in files using markdown code block format. Use: \`edit({ content: "\`\`\`edit\\nfile: path\\nold: |\\ntext\\nnew: |\\ntext\\n\`\`\`" })\`. NOTE: There is no "write" tool — always use \`edit\` to modify files.
88
+ - \`install_skill\`: Install an npm package as a skill — fetches package info from npm, runs npm install, and creates a .lonny/skills/ file with usage instructions for the AI
89
+ - \`save_memory\`: Save a memory entry to long-term memory (content: string, tags?: string[])
90
+ - \`find\`: Find files by name pattern (pattern: string, path?: string, maxResults?: number)
91
+ - \`git\`: Run read-only git commands (command: string)
92
+ - \`search\`: Search the web using Tavily (query: string, search_depth?: string, include_answer?: boolean, max_results?: number, topic?: string, days?: number)
58
93
  `
59
94
  }
60
95
 
@@ -63,14 +98,49 @@ export async function buildSystemPrompt(config: Config): Promise<string> {
63
98
  RULES:
64
99
  1. Read first: Use read/grep/glob tools to gather all context you need before making any edits.
65
100
  2. Be thorough: Explore the relevant parts of the codebase.
66
- 3. COST OPTIMIZATION (CRITICAL): Each API call costs money. You MUST maximize work per call. Use \`read(paths: [...])\` to read multiple files at once. Use \`edit({ content: "..." })\` with multiple \`\`\`edit blocks to edit multiple files at once.
101
+ 3. AST tools (\`ast_query\`, \`ast_edit\`) are available via the \`tool()\` gateway. Use \`ast_query\` to inspect code structure (functions, classes, imports) before editing. Use \`ast_edit\` for structure-aware edits that preserve formatting.
102
+ 4. **For JavaScript/TypeScript files, prefer AST tools over raw text tools**: Use \`ast_query\` (not \`read\`) to understand file structure — it returns structured function/class/import/export data with exact line numbers. Use \`ast_edit\` (not \`edit\`) to replace entire functions, classes, or variables — it avoids string-matching issues and preserves formatting. Reserve \`edit\` for small surgical changes to function bodies or single-line fixes.
103
+ 5. COST OPTIMIZATION (CRITICAL): Each API call costs money. You MUST maximize work per call. Use \`read(paths: [...])\` to read multiple files at once. Use \`edit({ content: "..." })\` with multiple \`\`\`edit blocks to edit multiple files at once.
104
+ 6. There is NO "write" tool. To modify files, use the \`edit\` tool (listed above). Calling \`write\` will fail with "Unknown tool".
105
+
106
+ ${getToolListForMode(config.mode)}
107
+ `
108
+ // ── Memory section (appended to system prompt to provide long-term context) ──
109
+ const memoryPromptSection = memorySection ? `\n## Long-term Memory\n\n${memorySection}` : ''
67
110
 
68
- ${getToolListForMode(config.mode)}`
111
+ // NOTE: Mention save_memory in loop/code mode tool lists only if the tool exists at runtime.
112
+ // The tool may be provided via plugin or future implementation.
69
113
 
70
114
  // ── Mode-specific instructions ───────────────────────────────────────────
71
115
  const modeInstructions =
72
- config.mode === 'plan'
73
- ? `You are a planning agent. Your ONLY job is to investigate the codebase and produce an actionable implementation plan with a todo list.
116
+ config.mode === 'loop'
117
+ ? `You are an autonomous coding agent operating in LOOP mode. You will CONTINUE working on the task automatically after each turn you do NOT need to ask for confirmation between steps.
118
+
119
+ ${getToolListForMode('loop')}
120
+ RULES (loop-specific):
121
+ 1. Read first: Use read/grep/glob tools to gather all context you need BEFORE making any edits. **For JS/TS files, prefer \`ast_query\` over \`read\`** — it returns structured function/class/import/export data with exact line numbers. Use \`tool({ name: "ast_query", params: { path: "file.ts", query: "structure" }})\` to inspect code structure via AST before editing. **For edits to whole functions/classes/variables in JS/TS, prefer \`ast_edit\` over \`edit\`** — use \`tool({ name: "ast_edit", params: { path: "file.ts", editType: "replace-node", targetLine: 5, newCode: "..." }})\` to avoid string-matching issues. Reserve \`edit\` for small surgical changes inside function bodies or single-line fixes. The \`read\` output prefixes each line with "<lineNumber>: " for easy reference. Do NOT include the "N: " prefix when copying text into \`edit\`.
122
+ 2. edit CALL FORMAT — use markdown code block format:
123
+ \`\`\`edit
124
+ file: src/file.ts
125
+ old: |
126
+ text to replace
127
+ new: |
128
+ replacement text
129
+ \`\`\`
130
+ Use separate \`\`\`edit blocks for multiple files.
131
+ 3. After making edits to a file, if you need to make ANOTHER edit to the SAME file, you MUST re-read it first to get the updated content.
132
+ 4. If \`edit\` reports \`old_string not found\`, do NOT retry with the same old_string — re-read the file immediately to see its actual current content, then retry with correctly-copied text.
133
+ 5. When copying old_string from \`read\` output, include 2-3 lines of context BEFORE and AFTER the target change to make the string unique in the file.
134
+ 6. On Windows, files may use CRLF (\\r\\n) line endings, but the \`edit\` tool normalizes them to LF (\\n). Always use \`\\n\` (not \`\\r\\n\`) in old_string/new_string.
135
+ 7. CRITICAL: old_string must be CONTIGUOUS — do NOT skip any lines between the old_string start and end. If you need to modify non-adjacent sections, use separate \`\`\`edit blocks.
136
+ 8. The \`|\` (pipe) after \`old:\` / \`new:\` supports chomping: \`|\` keeps trailing newline, \`|-\` strips it. Use \`|\` (not \`|-\`) when copying old_string — wrong chomping causes "old_string not found".
137
+ 9. COST OPTIMIZATION (CRITICAL): Each API call costs money. You MUST maximize work per call. Use \`read(paths: [...])\` to read multiple files at once. Use \`edit({ content: "..." })\` with multiple \`\`\`edit blocks to edit multiple files at once.
138
+ 10. TODO LIST MAINTENANCE: After completing a task item, update the corresponding plan file in \`.lonny/\` by checking off the TODO item (change \`- [ ]\` to \`- [x]\`). Use \`read\` to find the plan file, then \`edit\` to update the checkbox.
139
+ 11. LOOP BEHAVIOR: After this turn ends, you will AUTOMATICALLY receive a continuation prompt to continue working on the same task. You do NOT need to stop and wait for the user — keep going until the task is complete.
140
+ 12. If you believe the task is COMPLETE, end your response with a clear summary of what was accomplished. The system will detect this and stop the loop.
141
+ 13. You can use /stop at any time to halt execution.`
142
+ : config.mode === 'plan'
143
+ ? `You are a planning agent. Your ONLY job is to investigate the codebase and produce an actionable implementation plan with a todo list.
74
144
 
75
145
  You CANNOT edit files. You do NOT have access to edit, bash (write mode), or install_skill.
76
146
  Any attempt to call these tools will FAIL — they are simply unavailable in this mode.
@@ -84,6 +154,7 @@ RULES (plan-specific):
84
154
  6. If the plan is very long (or the todo list has many items), split into multiple files with descriptive names like \`backend-api.md\`, \`frontend-ui.md\`, \`database.md\`, etc.
85
155
  7. You MUST also include the todo list in your text response to the user (not just in the file).
86
156
  7. If the user asks you to modify files, run write commands, or install packages — refuse and explain they need to switch to code mode (\`/mode code\`).
157
+ 8. When the task involves writing code, your plan MUST follow Test-Driven Development: write tests first, then implement. Include a \`- [ ] Write tests for ...\` step BEFORE the implementation steps in the todo list.
87
158
 
88
159
  OUTPUT FORMAT (you MUST include both in write_plan AND in your response text):
89
160
 
@@ -99,18 +170,16 @@ A short, ordered description of the approach. Reference concrete files using \`p
99
170
  End your response by telling the user where the plan was saved and asking whether they want to switch to \`code\` mode to execute it. Use exactly: "Switch to code mode to implement this plan? (run \`/mode code\`)"
100
171
 
101
172
  If the user's request is a question rather than a change request, answer it directly and skip the plan/todo sections.`
102
- : config.mode === 'ask'
103
- ? `You are a Q&A assistant. You can ONLY use the following tools to search for information:
104
- - \`fetch\`: Fetch content from a URL
105
- - \`search\`: Search the web using Tavily
106
-
173
+ : config.mode === 'ask'
174
+ ? `You are a Q&A assistant. You can ONLY use the following tools to search for information:
175
+ ${getToolListForMode('ask')}
107
176
  You CANNOT execute any shell commands (\`bash\`), read local files, or make any changes to the codebase.
108
177
 
109
178
  RULES (ask-specific):
110
179
  1. Use \`fetch\` and \`search\` to find information and answer user questions.
111
180
  2. You CANNOT use \`bash\`, \`read\`, \`edit\`, \`write_plan\`, \`glob\`, \`grep\`, \`ls\`, \`find\`, or \`git\`.
112
181
  3. If the user wants you to modify code or run commands, explain you are in ask mode and suggest switching to code mode.`
113
- : `You are a coding agent optimized for per-call pricing.
182
+ : `You are a coding agent optimized for per-call pricing.
114
183
 
115
184
  RULES (code-specific):
116
185
  1. Read first: Use read/grep/glob tools to gather all context you need BEFORE making any edits. The \`read\` output prefixes each line with "<lineNumber>: " for easy reference. Do NOT include the "N: " prefix when copying text into \`edit\`.
@@ -125,15 +194,17 @@ RULES (code-specific):
125
194
  Use separate \`\`\`edit blocks for multiple files.
126
195
  3. After making edits to a file, if you need to make ANOTHER edit to the SAME file, you MUST re-read it first to get the updated content.
127
196
  4. If \`edit\` reports \`old_string not found\`, do NOT retry with the same old_string — re-read the file immediately to see its actual current content, then retry with correctly-copied text.
128
- 5. When copying old_string from \`read\` output, include 2-3 lines of context BEFORE and AFTER the target change to make the string unique in the file.
129
- 6. On Windows, files may use CRLF (\\r\\n) line endings, but the \`edit\` tool normalizes them to LF (\\n). Always use \`\\n\` (not \`\\r\\n\`) in old_string/new_string.
130
- 7. COST OPTIMIZATION (CRITICAL): Each API call costs money. You have a hard limit of ~5 API calls per task.
131
- 8. TODO LIST MAINTENANCE: After completing a task item, update the corresponding plan file in \`.lonny/\` by checking off the TODO item (change \`- [ ]\` to \`- [x]\`). Use \`read\` to find the plan file, then \`edit\` to update the checkbox.`
197
+ 5. When copying old_string from \`read\` output, include 2-3 lines of context BEFORE and AFTER the target change to make the string unique in the file.
198
+ 6. CRITICAL: old_string must be CONTIGUOUS do NOT skip any lines between the old_string start and end. If you need to modify non-adjacent sections, use separate \`\`\`edit blocks.
199
+ 7. The \`|\` (pipe) after \`old:\` / \`new:\` supports chomping: \`|\` keeps trailing newline, \`|-\` strips it. Use \`|\` (not \`|-\`) when copying old_string wrong chomping causes "old_string not found".
200
+ 8. On Windows, files may use CRLF (\\r\\n) line endings, but the \`edit\` tool normalizes them to LF (\\n). Always use \`\\n\` (not \`\\r\\n\`) in old_string/new_string.
201
+ 9. COST OPTIMIZATION (CRITICAL): Each API call costs money. You have a hard limit of ~5 API calls per task.
202
+ 10. TODO LIST MAINTENANCE: After completing a task item, update the corresponding plan file in \`.lonny/\` by checking off the TODO item (change \`- [ ]\` to \`- [x]\`). Use \`read\` to find the plan file, then \`edit\` to update the checkbox.`
132
203
 
133
204
  // ── Built-in development methodologies ─────────────────────────────────
134
205
  // Embedded directly from Superpowers — no skill files needed.
135
206
  const methodologySection =
136
- config.mode === 'code'
207
+ config.mode === 'code' || config.mode === 'loop'
137
208
  ? `
138
209
 
139
210
  ## Development Methodology
@@ -169,17 +240,20 @@ Before writing a plan, explore the user's request thoroughly:
169
240
  - Shell: ${shell}
170
241
  - Working directory: ${cwd}
171
242
  - OS: ${isWindows ? 'Windows' : 'Linux/macOS'}
172
- - Available shell commands: ${isWindows ? 'PowerShell (cmd is also available but PowerShell is preferred)' : 'bash'}
173
- ${isWindows ? ' - Use PowerShell. Do NOT use Unix commands like `cat`, `ls`, `grep`, `which`, `chmod`, `mv`, `cp`, `rm`, `touch`, `mkdir`, `uname`, etc.' : ''}
174
- ${isWindows ? ' - Use `type` instead of `cat`, `dir` instead of `ls`, `where` instead of `which`' : ''}`
243
+ - Available shell commands: ${isWindows ? 'PowerShell (cmd is also available but PowerShell is preferred)' : 'bash'}
244
+ ${isWindows ? ' ⚠️ THIS IS WINDOWS. Do NOT use Unix/Linux paths like `/workspace/...` or `/home/...`. The working directory is a Windows path (e.g. `C:\\Users\\...`).' : ''}
245
+ ${isWindows ? ' ⚠️ Do NOT use Unix commands: `find`, `cat`, `ls -la`, `which`, `cp`, `mv`, `rm`, `touch`, `chmod`, `mkdir`, `grep`, `head`, `tail`. They will ALL fail.' : ''}
246
+ ${isWindows ? ' - Use `type` instead of `cat`, `dir` instead of `ls`, `where` instead of `which`, `Select-Object -First N` instead of `head -N`' : ''}
247
+ ${isWindows ? ' - Use `;` (semicolon) instead of `&&` to chain commands' : ''}
248
+ ${isWindows ? ' - ⚠️ `Select-String` exits with code 1 when no match is found (e.g. `Select-String -Pattern "FAIL"` returns code 1 if no line contains FAIL). This is NORMAL — it does NOT mean the command failed. Append `; $LASTEXITCODE = 0` to suppress this.' : ''}`
175
249
 
176
250
  // Plan mode uses its own standalone tool list inside modeInstructions — skip sharedRules
177
251
  const rulesSection =
178
- config.mode === 'plan'
252
+ config.mode === 'plan' || config.mode === 'loop'
179
253
  ? ''
180
254
  : `
181
255
  ${sharedRules}`
182
256
  return `${modeInstructions}
183
257
 
184
- ${envSection}${rulesSection}${methodologySection}${projectSection}${skillsSection}`
258
+ ${envSection}${rulesSection}${methodologySection}${projectSection}${memoryPromptSection}${skillsSection}`
185
259
  }