sparkecoder 0.1.22 → 0.1.23

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 (290) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +1308 -212
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +1933 -454
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +20 -3
  7. package/dist/db/index.js +97 -0
  8. package/dist/db/index.js.map +1 -1
  9. package/dist/{index-CNwLFGiZ.d.ts → index-BblbmG_0.d.ts} +19 -4
  10. package/dist/index.d.ts +6 -6
  11. package/dist/index.js +1913 -434
  12. package/dist/index.js.map +1 -1
  13. package/dist/{schema-Df7MU3nM.d.ts → schema-D_8A4k01.d.ts} +246 -2
  14. package/dist/search-ybREg7F_.d.ts +254 -0
  15. package/dist/server/index.js +1918 -439
  16. package/dist/server/index.js.map +1 -1
  17. package/dist/tools/index.d.ts +7 -56
  18. package/dist/tools/index.js +894 -27
  19. package/dist/tools/index.js.map +1 -1
  20. package/package.json +5 -1
  21. package/web/.next/BUILD_ID +1 -1
  22. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/app-path-routes-manifest.json +4 -0
  24. package/web/.next/standalone/web/.next/build-manifest.json +7 -6
  25. package/web/.next/standalone/web/.next/prerender-manifest.json +99 -3
  26. package/web/.next/standalone/web/.next/required-server-files.json +28 -4
  27. package/web/.next/standalone/web/.next/routes-manifest.json +24 -0
  28. package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +5 -4
  29. package/web/.next/standalone/web/.next/server/app/(main)/page.js +2 -2
  30. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  31. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  32. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/build-manifest.json +5 -4
  33. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +2 -2
  34. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  35. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  36. package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +5 -4
  37. package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
  38. package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
  39. package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  40. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  41. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  42. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  43. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  44. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  45. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  46. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  47. package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +5 -4
  48. package/web/.next/standalone/web/.next/server/app/_not-found/page.js +2 -2
  49. package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
  50. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  51. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  52. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  53. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  54. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  56. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  59. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  60. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  61. package/web/.next/standalone/web/.next/server/app/docs/installation/page/app-paths-manifest.json +3 -0
  62. package/web/.next/standalone/web/.next/server/app/docs/installation/page/build-manifest.json +18 -0
  63. package/web/.next/standalone/web/.next/server/app/docs/installation/page/next-font-manifest.json +11 -0
  64. package/web/.next/standalone/web/.next/server/app/docs/installation/page/react-loadable-manifest.json +1 -0
  65. package/web/.next/standalone/web/.next/server/app/docs/installation/page/server-reference-manifest.json +4 -0
  66. package/web/.next/standalone/web/.next/server/app/docs/installation/page.js +21 -0
  67. package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.map +5 -0
  68. package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.nft.json +1 -0
  69. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +2 -0
  70. package/web/.next/standalone/web/.next/server/app/docs/installation.html +86 -0
  71. package/web/.next/standalone/web/.next/server/app/docs/installation.meta +16 -0
  72. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +36 -0
  73. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +36 -0
  74. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +6 -0
  75. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +7 -0
  76. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +3 -0
  77. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +22 -0
  78. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +4 -0
  79. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +5 -0
  80. package/web/.next/standalone/web/.next/server/app/docs/page/app-paths-manifest.json +3 -0
  81. package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +18 -0
  82. package/web/.next/standalone/web/.next/server/app/docs/page/next-font-manifest.json +11 -0
  83. package/web/.next/standalone/web/.next/server/app/docs/page/react-loadable-manifest.json +1 -0
  84. package/web/.next/standalone/web/.next/server/app/docs/page/server-reference-manifest.json +4 -0
  85. package/web/.next/standalone/web/.next/server/app/docs/page.js +21 -0
  86. package/web/.next/standalone/web/.next/server/app/docs/page.js.map +5 -0
  87. package/web/.next/standalone/web/.next/server/app/docs/page.js.nft.json +1 -0
  88. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +2 -0
  89. package/web/.next/standalone/web/.next/server/app/docs/skills/page/app-paths-manifest.json +3 -0
  90. package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +18 -0
  91. package/web/.next/standalone/web/.next/server/app/docs/skills/page/next-font-manifest.json +11 -0
  92. package/web/.next/standalone/web/.next/server/app/docs/skills/page/react-loadable-manifest.json +1 -0
  93. package/web/.next/standalone/web/.next/server/app/docs/skills/page/server-reference-manifest.json +4 -0
  94. package/web/.next/standalone/web/.next/server/app/docs/skills/page.js +21 -0
  95. package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.map +5 -0
  96. package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.nft.json +1 -0
  97. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +2 -0
  98. package/web/.next/standalone/web/.next/server/app/docs/skills.html +268 -0
  99. package/web/.next/standalone/web/.next/server/app/docs/skills.meta +16 -0
  100. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +82 -0
  101. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +82 -0
  102. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +6 -0
  103. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +7 -0
  104. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +3 -0
  105. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +66 -0
  106. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +4 -0
  107. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +5 -0
  108. package/web/.next/standalone/web/.next/server/app/docs/tools/page/app-paths-manifest.json +3 -0
  109. package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +18 -0
  110. package/web/.next/standalone/web/.next/server/app/docs/tools/page/next-font-manifest.json +11 -0
  111. package/web/.next/standalone/web/.next/server/app/docs/tools/page/react-loadable-manifest.json +1 -0
  112. package/web/.next/standalone/web/.next/server/app/docs/tools/page/server-reference-manifest.json +4 -0
  113. package/web/.next/standalone/web/.next/server/app/docs/tools/page.js +21 -0
  114. package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.map +5 -0
  115. package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.nft.json +1 -0
  116. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +2 -0
  117. package/web/.next/standalone/web/.next/server/app/docs/tools.html +242 -0
  118. package/web/.next/standalone/web/.next/server/app/docs/tools.meta +16 -0
  119. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +87 -0
  120. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +87 -0
  121. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +6 -0
  122. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +7 -0
  123. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +3 -0
  124. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +72 -0
  125. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +4 -0
  126. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +5 -0
  127. package/web/.next/standalone/web/.next/server/app/docs.html +74 -0
  128. package/web/.next/standalone/web/.next/server/app/docs.meta +15 -0
  129. package/web/.next/standalone/web/.next/server/app/docs.rsc +34 -0
  130. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +34 -0
  131. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +6 -0
  132. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +7 -0
  133. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +3 -0
  134. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +20 -0
  135. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +5 -0
  136. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  137. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  138. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  139. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  140. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  141. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  142. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  143. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  144. package/web/.next/standalone/web/.next/server/app-paths-manifest.json +4 -0
  145. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5f58fd73._.js → 2374f_244589df._.js} +1 -1
  146. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_84859a94._.js → 2374f_41a27541._.js} +1 -1
  147. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_65fcfd95._.js → 2374f_47c9e2d5._.js} +1 -1
  148. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f1038f7c._.js → 2374f_4bf2df9d._.js} +1 -1
  149. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_663d1038._.js +3 -0
  150. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_954e49c0._.js +3 -0
  151. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_387a1437._.js → 2374f_c33b095a._.js} +1 -1
  152. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cfd0137a._.js → 2374f_fa61fbb2._.js} +1 -1
  153. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_741f6b67._.js → 2374f_fb82ac0d._.js} +1 -1
  154. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_96bca05b._.js → 2374f_next_dist_bbe64674._.js} +2 -2
  155. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7f04455b._.js → [root-of-the-server]__1e06ddf7._.js} +2 -2
  156. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2b151e1c._.js +3 -0
  157. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2dbf511a._.js +9 -0
  158. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__397fadd4._.js +3 -0
  159. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__44bd8bd1._.js +3 -0
  160. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__70cecda8._.js +3 -0
  161. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9fdf9974._.js +3 -0
  162. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f18f92f4._.js → [root-of-the-server]__b050bb8f._.js} +2 -2
  163. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d3034cd2._.js +3 -0
  164. package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__c3a1e22c._.js → [root-of-the-server]__ef2713cf._.js} +2 -2
  165. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f764bebe._.js +3 -0
  166. package/web/.next/standalone/web/.next/server/chunks/ssr/web_046bf7db._.js +3 -0
  167. package/web/.next/standalone/web/.next/server/chunks/ssr/web_656c1e45._.js +7 -0
  168. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_c7618534._.js → web_76ccf09f._.js} +4 -4
  169. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_installation_page_actions_52cc0648.js +3 -0
  170. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_page_actions_4fe77da8.js +3 -0
  171. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_skills_page_actions_251df2e1.js +3 -0
  172. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_tools_page_actions_3e6382b0.js +3 -0
  173. package/web/.next/standalone/web/.next/server/chunks/ssr/web_a565dc94._.js +4 -0
  174. package/web/.next/standalone/web/.next/server/chunks/ssr/web_b1cce0b7._.js +4 -0
  175. package/web/.next/standalone/web/.next/server/chunks/ssr/web_b42ed1be._.js +3 -0
  176. package/web/.next/standalone/web/.next/server/chunks/ssr/web_c0c2bee4._.js +4 -0
  177. package/web/.next/standalone/web/.next/server/chunks/ssr/web_eea9c122._.js +3 -0
  178. package/web/.next/standalone/web/.next/server/chunks/ssr/web_ff00a5c3._.js +4 -0
  179. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +3 -0
  180. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +3 -0
  181. package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +5 -4
  182. package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
  183. package/web/.next/standalone/web/.next/server/next-font-manifest.json +16 -0
  184. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  185. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  186. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  187. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  188. package/web/.next/standalone/web/.next/static/chunks/0cc382a66266188e.js +7 -0
  189. package/web/.next/standalone/web/.next/static/chunks/0fda34e553582102.js +1 -0
  190. package/web/.next/standalone/web/.next/static/{static/chunks/5e5b485d77ac0d8f.js → chunks/6407c045dfc908fe.js} +3 -3
  191. package/web/.next/standalone/web/.next/static/chunks/651e187cc15d66de.js +1 -0
  192. package/web/.next/standalone/web/.next/static/chunks/862ced58ce21a270.js +4 -0
  193. package/web/.next/standalone/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
  194. package/web/.next/standalone/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
  195. package/web/.next/standalone/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
  196. package/web/.next/standalone/web/.next/static/chunks/af22745850132107.css +1 -0
  197. package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
  198. package/web/.next/standalone/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
  199. package/web/.next/standalone/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
  200. package/web/.next/standalone/web/.next/static/static/chunks/0cc382a66266188e.js +7 -0
  201. package/web/.next/standalone/web/.next/static/static/chunks/0fda34e553582102.js +1 -0
  202. package/web/.next/{static/chunks/5e5b485d77ac0d8f.js → standalone/web/.next/static/static/chunks/6407c045dfc908fe.js} +3 -3
  203. package/web/.next/standalone/web/.next/static/static/chunks/651e187cc15d66de.js +1 -0
  204. package/web/.next/standalone/web/.next/static/static/chunks/862ced58ce21a270.js +4 -0
  205. package/web/.next/standalone/web/.next/static/static/chunks/89bc21c0443670f4.js +1 -0
  206. package/web/.next/standalone/web/.next/static/static/chunks/8f4edf22ededc29b.js +7 -0
  207. package/web/.next/standalone/web/.next/static/static/chunks/ad6b9dbb257d62cc.js +1 -0
  208. package/web/.next/standalone/web/.next/static/static/chunks/af22745850132107.css +1 -0
  209. package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +1 -0
  210. package/web/.next/standalone/web/.next/static/static/chunks/db9b22c844a35e20.js +5 -0
  211. package/web/.next/standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
  212. package/web/.next/standalone/web/mdx-components.tsx +119 -0
  213. package/web/.next/standalone/web/next.config.ts +15 -1
  214. package/web/.next/standalone/web/package-lock.json +559 -4
  215. package/web/.next/standalone/web/package.json +4 -0
  216. package/web/.next/standalone/web/runtime-config.json +1 -1
  217. package/web/.next/standalone/web/server.js +1 -1
  218. package/web/.next/standalone/web/src/app/(main)/page.tsx +127 -5
  219. package/web/.next/standalone/web/src/app/docs/installation/page.mdx +128 -0
  220. package/web/.next/standalone/web/src/app/docs/layout.tsx +74 -0
  221. package/web/.next/standalone/web/src/app/docs/page.mdx +90 -0
  222. package/web/.next/standalone/web/src/app/docs/skills/page.mdx +334 -0
  223. package/web/.next/standalone/web/src/app/docs/tools/page.mdx +300 -0
  224. package/web/.next/standalone/web/src/components/ai-elements/mention-input.tsx +809 -0
  225. package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +400 -0
  226. package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +275 -0
  227. package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +19 -5
  228. package/web/.next/standalone/web/src/components/chat-interface.tsx +525 -71
  229. package/web/.next/standalone/web/src/hooks/use-workspace-files.ts +108 -0
  230. package/web/.next/standalone/web/src/lib/api.ts +90 -4
  231. package/web/.next/static/chunks/0cc382a66266188e.js +7 -0
  232. package/web/.next/static/chunks/0fda34e553582102.js +1 -0
  233. package/web/.next/{standalone/web/.next/static/chunks/5e5b485d77ac0d8f.js → static/chunks/6407c045dfc908fe.js} +3 -3
  234. package/web/.next/static/chunks/651e187cc15d66de.js +1 -0
  235. package/web/.next/static/chunks/862ced58ce21a270.js +4 -0
  236. package/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
  237. package/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
  238. package/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
  239. package/web/.next/static/chunks/af22745850132107.css +1 -0
  240. package/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
  241. package/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
  242. package/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
  243. package/web/package.json +4 -0
  244. package/dist/bash-CGAqW7HR.d.ts +0 -80
  245. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_814be2c9._.js +0 -3
  246. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +0 -3
  247. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3ec22171._.js +0 -9
  248. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__513c6b45._.js +0 -3
  249. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +0 -3
  250. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +0 -7
  251. package/web/.next/standalone/web/.next/server/chunks/ssr/web_e6034803._.js +0 -3
  252. package/web/.next/standalone/web/.next/static/chunks/03d4169891280e04.js +0 -7
  253. package/web/.next/standalone/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
  254. package/web/.next/standalone/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
  255. package/web/.next/standalone/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
  256. package/web/.next/standalone/web/.next/static/chunks/77e4bf0421481629.js +0 -1
  257. package/web/.next/standalone/web/.next/static/chunks/beb9625c4a470042.js +0 -1
  258. package/web/.next/standalone/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
  259. package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
  260. package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
  261. package/web/.next/standalone/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  262. package/web/.next/standalone/web/.next/static/static/chunks/03d4169891280e04.js +0 -7
  263. package/web/.next/standalone/web/.next/static/static/chunks/2d5da0cfc011b8d9.js +0 -1
  264. package/web/.next/standalone/web/.next/static/static/chunks/3bb454ca848ec78e.js +0 -7
  265. package/web/.next/standalone/web/.next/static/static/chunks/634fd97fab9ed4e4.js +0 -4
  266. package/web/.next/standalone/web/.next/static/static/chunks/77e4bf0421481629.js +0 -1
  267. package/web/.next/standalone/web/.next/static/static/chunks/beb9625c4a470042.js +0 -1
  268. package/web/.next/standalone/web/.next/static/static/chunks/c2244168e74b8c78.js +0 -1
  269. package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +0 -5
  270. package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +0 -1
  271. package/web/.next/standalone/web/.next/static/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  272. package/web/.next/static/chunks/03d4169891280e04.js +0 -7
  273. package/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
  274. package/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
  275. package/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
  276. package/web/.next/static/chunks/77e4bf0421481629.js +0 -1
  277. package/web/.next/static/chunks/beb9625c4a470042.js +0 -1
  278. package/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
  279. package/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
  280. package/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
  281. package/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  282. /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  283. /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  284. /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
  285. /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  286. /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  287. /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
  288. /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  289. /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  290. /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
package/dist/index.js CHANGED
@@ -1,19 +1,426 @@
1
1
  var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
2
6
  var __export = (target, all) => {
3
7
  for (var name in all)
4
8
  __defProp(target, name, { get: all[name], enumerable: true });
5
9
  };
6
10
 
11
+ // src/config/types.ts
12
+ import { z } from "zod";
13
+ var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, SparkcoderConfigSchema;
14
+ var init_types = __esm({
15
+ "src/config/types.ts"() {
16
+ "use strict";
17
+ ToolApprovalConfigSchema = z.object({
18
+ bash: z.boolean().optional().default(true),
19
+ write_file: z.boolean().optional().default(false),
20
+ read_file: z.boolean().optional().default(false),
21
+ load_skill: z.boolean().optional().default(false),
22
+ todo: z.boolean().optional().default(false)
23
+ });
24
+ SkillMetadataSchema = z.object({
25
+ name: z.string(),
26
+ description: z.string(),
27
+ // Whether to always inject this skill into context (vs on-demand loading)
28
+ alwaysApply: z.boolean().optional().default(false),
29
+ // Glob patterns - auto-inject when working with matching files
30
+ globs: z.array(z.string()).optional().default([])
31
+ });
32
+ SessionConfigSchema = z.object({
33
+ toolApprovals: z.record(z.string(), z.boolean()).optional(),
34
+ approvalWebhook: z.string().url().optional(),
35
+ skillsDirectory: z.string().optional(),
36
+ maxContextChars: z.number().optional().default(2e5)
37
+ });
38
+ SparkcoderConfigSchema = z.object({
39
+ // Default model to use (Vercel AI Gateway format)
40
+ defaultModel: z.string().default("anthropic/claude-opus-4-5"),
41
+ // Working directory for file operations
42
+ workingDirectory: z.string().optional(),
43
+ // Tool approval settings
44
+ toolApprovals: ToolApprovalConfigSchema.optional().default({}),
45
+ // Approval webhook URL (called when approval is needed)
46
+ approvalWebhook: z.string().url().optional(),
47
+ // Skills configuration
48
+ skills: z.object({
49
+ // Directory containing skill files
50
+ directory: z.string().optional().default("./skills"),
51
+ // Additional skill directories to include
52
+ additionalDirectories: z.array(z.string()).optional().default([])
53
+ }).optional().default({}),
54
+ // Context management
55
+ context: z.object({
56
+ // Maximum context size before summarization (in characters)
57
+ maxChars: z.number().optional().default(2e5),
58
+ // Enable automatic summarization
59
+ autoSummarize: z.boolean().optional().default(true),
60
+ // Number of recent messages to keep after summarization
61
+ keepRecentMessages: z.number().optional().default(10)
62
+ }).optional().default({}),
63
+ // Server configuration
64
+ server: z.object({
65
+ port: z.number().default(3141),
66
+ host: z.string().default("127.0.0.1"),
67
+ // Public URL for web UI to connect to API (for Docker/remote access)
68
+ // If not set, defaults to http://{host}:{port}
69
+ publicUrl: z.string().url().optional()
70
+ }).default({ port: 3141, host: "127.0.0.1" }),
71
+ // Database path
72
+ databasePath: z.string().optional().default("./sparkecoder.db")
73
+ });
74
+ }
75
+ });
76
+
77
+ // src/skills/index.ts
78
+ var skills_exports = {};
79
+ __export(skills_exports, {
80
+ formatAgentsMdContent: () => formatAgentsMdContent,
81
+ formatAlwaysLoadedSkills: () => formatAlwaysLoadedSkills,
82
+ formatGlobMatchedSkills: () => formatGlobMatchedSkills,
83
+ formatSkillsForContext: () => formatSkillsForContext,
84
+ getGlobMatchedSkills: () => getGlobMatchedSkills,
85
+ loadAgentsMd: () => loadAgentsMd,
86
+ loadAllSkills: () => loadAllSkills,
87
+ loadAllSkillsFromDiscovered: () => loadAllSkillsFromDiscovered,
88
+ loadSkillContent: () => loadSkillContent,
89
+ loadSkillsFromDirectory: () => loadSkillsFromDirectory
90
+ });
91
+ import { readFile as readFile6, readdir } from "fs/promises";
92
+ import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
93
+ import { existsSync as existsSync8 } from "fs";
94
+ import { minimatch } from "minimatch";
95
+ function parseSkillFrontmatter(content) {
96
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
97
+ if (!frontmatterMatch) {
98
+ return null;
99
+ }
100
+ const [, frontmatter, body] = frontmatterMatch;
101
+ try {
102
+ const lines = frontmatter.split("\n");
103
+ const data = {};
104
+ let currentArray = null;
105
+ let currentArrayKey = null;
106
+ for (const line of lines) {
107
+ if (currentArrayKey && line.trim().startsWith("-")) {
108
+ let value = line.trim().slice(1).trim();
109
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
110
+ value = value.slice(1, -1);
111
+ }
112
+ currentArray?.push(value);
113
+ continue;
114
+ }
115
+ if (currentArrayKey && currentArray) {
116
+ data[currentArrayKey] = currentArray;
117
+ currentArray = null;
118
+ currentArrayKey = null;
119
+ }
120
+ const colonIndex = line.indexOf(":");
121
+ if (colonIndex > 0) {
122
+ const key = line.slice(0, colonIndex).trim();
123
+ let value = line.slice(colonIndex + 1).trim();
124
+ if (value === "" || value === "[]") {
125
+ currentArrayKey = key;
126
+ currentArray = [];
127
+ continue;
128
+ }
129
+ if (value.startsWith("[") && value.endsWith("]")) {
130
+ const arrayContent = value.slice(1, -1);
131
+ const items = arrayContent.split(",").map((item) => {
132
+ let trimmed = item.trim();
133
+ if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
134
+ trimmed = trimmed.slice(1, -1);
135
+ }
136
+ return trimmed;
137
+ }).filter((item) => item.length > 0);
138
+ data[key] = items;
139
+ continue;
140
+ }
141
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
142
+ value = value.slice(1, -1);
143
+ }
144
+ if (value === "true") {
145
+ data[key] = true;
146
+ } else if (value === "false") {
147
+ data[key] = false;
148
+ } else {
149
+ data[key] = value;
150
+ }
151
+ }
152
+ }
153
+ if (currentArrayKey && currentArray) {
154
+ data[currentArrayKey] = currentArray;
155
+ }
156
+ const metadata = SkillMetadataSchema.parse(data);
157
+ return { metadata, body: body.trim() };
158
+ } catch {
159
+ return null;
160
+ }
161
+ }
162
+ function getSkillNameFromPath(filePath) {
163
+ return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
164
+ }
165
+ async function loadSkillsFromDirectory(directory, options = {}) {
166
+ const {
167
+ priority = 50,
168
+ defaultLoadType = "on_demand",
169
+ forceAlwaysApply = false
170
+ } = options;
171
+ if (!existsSync8(directory)) {
172
+ return [];
173
+ }
174
+ const skills = [];
175
+ const entries = await readdir(directory, { withFileTypes: true });
176
+ for (const entry of entries) {
177
+ let filePath;
178
+ let fileName;
179
+ if (entry.isDirectory()) {
180
+ const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
181
+ if (existsSync8(skillMdPath)) {
182
+ filePath = skillMdPath;
183
+ fileName = entry.name;
184
+ } else {
185
+ continue;
186
+ }
187
+ } else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc")) {
188
+ filePath = resolve6(directory, entry.name);
189
+ fileName = entry.name;
190
+ } else {
191
+ continue;
192
+ }
193
+ const content = await readFile6(filePath, "utf-8");
194
+ const parsed = parseSkillFrontmatter(content);
195
+ if (parsed) {
196
+ const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
197
+ const loadType = alwaysApply ? "always" : defaultLoadType;
198
+ skills.push({
199
+ name: parsed.metadata.name,
200
+ description: parsed.metadata.description,
201
+ filePath,
202
+ alwaysApply,
203
+ globs: parsed.metadata.globs,
204
+ loadType,
205
+ priority,
206
+ sourceDir: directory
207
+ });
208
+ } else {
209
+ const name = getSkillNameFromPath(filePath);
210
+ const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
211
+ skills.push({
212
+ name,
213
+ description: firstParagraph.replace(/^#\s*/, "").trim(),
214
+ filePath,
215
+ alwaysApply: forceAlwaysApply,
216
+ globs: [],
217
+ loadType: forceAlwaysApply ? "always" : defaultLoadType,
218
+ priority,
219
+ sourceDir: directory
220
+ });
221
+ }
222
+ }
223
+ return skills;
224
+ }
225
+ async function loadAllSkills(directories) {
226
+ const allSkills = [];
227
+ const seenNames = /* @__PURE__ */ new Set();
228
+ for (const dir of directories) {
229
+ const skills = await loadSkillsFromDirectory(dir);
230
+ for (const skill of skills) {
231
+ if (!seenNames.has(skill.name.toLowerCase())) {
232
+ seenNames.add(skill.name.toLowerCase());
233
+ allSkills.push(skill);
234
+ }
235
+ }
236
+ }
237
+ return allSkills;
238
+ }
239
+ async function loadAllSkillsFromDiscovered(discovered) {
240
+ const allSkills = [];
241
+ const seenNames = /* @__PURE__ */ new Set();
242
+ for (const { path, priority } of discovered.alwaysLoadedDirs) {
243
+ const skills = await loadSkillsFromDirectory(path, {
244
+ priority,
245
+ defaultLoadType: "always",
246
+ forceAlwaysApply: true
247
+ });
248
+ for (const skill of skills) {
249
+ if (!seenNames.has(skill.name.toLowerCase())) {
250
+ seenNames.add(skill.name.toLowerCase());
251
+ allSkills.push(skill);
252
+ }
253
+ }
254
+ }
255
+ for (const { path, priority } of discovered.onDemandDirs) {
256
+ const skills = await loadSkillsFromDirectory(path, {
257
+ priority,
258
+ defaultLoadType: "on_demand",
259
+ forceAlwaysApply: false
260
+ });
261
+ for (const skill of skills) {
262
+ if (!seenNames.has(skill.name.toLowerCase())) {
263
+ seenNames.add(skill.name.toLowerCase());
264
+ allSkills.push(skill);
265
+ }
266
+ }
267
+ }
268
+ const alwaysSkills = allSkills.filter((s) => s.alwaysApply || s.loadType === "always");
269
+ const onDemandSkills = allSkills.filter((s) => !s.alwaysApply && s.loadType !== "always");
270
+ const alwaysWithContent = await Promise.all(
271
+ alwaysSkills.map(async (skill) => {
272
+ const content = await readFile6(skill.filePath, "utf-8");
273
+ const parsed = parseSkillFrontmatter(content);
274
+ return {
275
+ ...skill,
276
+ content: parsed ? parsed.body : content
277
+ };
278
+ })
279
+ );
280
+ return {
281
+ always: alwaysWithContent,
282
+ onDemand: onDemandSkills,
283
+ all: allSkills
284
+ };
285
+ }
286
+ async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
287
+ if (activeFiles.length === 0) {
288
+ return [];
289
+ }
290
+ const relativeFiles = activeFiles.map((f) => {
291
+ if (f.startsWith(workingDirectory)) {
292
+ return relative4(workingDirectory, f);
293
+ }
294
+ return f;
295
+ });
296
+ const matchedSkills = skills.filter((skill) => {
297
+ if (skill.alwaysApply || skill.loadType === "always") {
298
+ return false;
299
+ }
300
+ if (!skill.globs || skill.globs.length === 0) {
301
+ return false;
302
+ }
303
+ return relativeFiles.some(
304
+ (file) => skill.globs.some((pattern) => minimatch(file, pattern, { matchBase: true }))
305
+ );
306
+ });
307
+ const matchedWithContent = await Promise.all(
308
+ matchedSkills.map(async (skill) => {
309
+ const content = await readFile6(skill.filePath, "utf-8");
310
+ const parsed = parseSkillFrontmatter(content);
311
+ return {
312
+ ...skill,
313
+ content: parsed ? parsed.body : content,
314
+ loadType: "glob_matched"
315
+ };
316
+ })
317
+ );
318
+ return matchedWithContent;
319
+ }
320
+ async function loadAgentsMd(agentsMdPath) {
321
+ if (!agentsMdPath || !existsSync8(agentsMdPath)) {
322
+ return null;
323
+ }
324
+ const content = await readFile6(agentsMdPath, "utf-8");
325
+ return content;
326
+ }
327
+ async function loadSkillContent(skillName, directories) {
328
+ const allSkills = await loadAllSkills(directories);
329
+ const skill = allSkills.find(
330
+ (s) => s.name.toLowerCase() === skillName.toLowerCase()
331
+ );
332
+ if (!skill) {
333
+ return null;
334
+ }
335
+ const content = await readFile6(skill.filePath, "utf-8");
336
+ const parsed = parseSkillFrontmatter(content);
337
+ return {
338
+ ...skill,
339
+ content: parsed ? parsed.body : content
340
+ };
341
+ }
342
+ function formatSkillsForContext(skills) {
343
+ const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
344
+ if (onDemandSkills.length === 0) {
345
+ return "No on-demand skills available.";
346
+ }
347
+ const lines = ["Available skills (use load_skill tool to load into context):"];
348
+ for (const skill of onDemandSkills) {
349
+ const globInfo = skill.globs?.length ? ` [auto-loads for: ${skill.globs.join(", ")}]` : "";
350
+ lines.push(`- ${skill.name}: ${skill.description}${globInfo}`);
351
+ }
352
+ return lines.join("\n");
353
+ }
354
+ function formatAlwaysLoadedSkills(skills) {
355
+ if (skills.length === 0) {
356
+ return "";
357
+ }
358
+ const sections = [];
359
+ for (const skill of skills) {
360
+ sections.push(`### ${skill.name}
361
+
362
+ ${skill.content}`);
363
+ }
364
+ return `## Active Rules & Skills (Always Loaded)
365
+
366
+ ${sections.join("\n\n---\n\n")}`;
367
+ }
368
+ function formatGlobMatchedSkills(skills) {
369
+ if (skills.length === 0) {
370
+ return "";
371
+ }
372
+ const sections = [];
373
+ for (const skill of skills) {
374
+ sections.push(`### ${skill.name}
375
+
376
+ ${skill.content}`);
377
+ }
378
+ return `## Context-Relevant Skills (Auto-loaded based on active files)
379
+
380
+ ${sections.join("\n\n---\n\n")}`;
381
+ }
382
+ function formatAgentsMdContent(content) {
383
+ if (!content) {
384
+ return "";
385
+ }
386
+ return `## Project Instructions (AGENTS.md)
387
+
388
+ ${content}`;
389
+ }
390
+ var init_skills = __esm({
391
+ "src/skills/index.ts"() {
392
+ "use strict";
393
+ init_types();
394
+ }
395
+ });
396
+
7
397
  // src/agent/index.ts
8
398
  import {
9
- streamText,
10
- generateText as generateText2,
11
- tool as tool7,
12
- stepCountIs
399
+ streamText as streamText2,
400
+ generateText as generateText3,
401
+ tool as tool9,
402
+ stepCountIs as stepCountIs2
13
403
  } from "ai";
14
- import { gateway as gateway2 } from "@ai-sdk/gateway";
15
- import { z as z8 } from "zod";
16
- import { nanoid as nanoid3 } from "nanoid";
404
+
405
+ // src/agent/model.ts
406
+ import { gateway } from "@ai-sdk/gateway";
407
+ var ANTHROPIC_PREFIX = "anthropic/";
408
+ function isAnthropicModel(modelId) {
409
+ const normalized = modelId.trim().toLowerCase();
410
+ return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
411
+ }
412
+ function resolveModel(modelId) {
413
+ return gateway(modelId.trim());
414
+ }
415
+ var SUBAGENT_MODELS = {
416
+ search: "google/gemini-2.0-flash",
417
+ analyze: "google/gemini-2.0-flash",
418
+ default: "google/gemini-2.0-flash"
419
+ };
420
+
421
+ // src/agent/index.ts
422
+ import { z as z10 } from "zod";
423
+ import { nanoid as nanoid4 } from "nanoid";
17
424
 
18
425
  // src/db/index.ts
19
426
  import Database from "better-sqlite3";
@@ -30,6 +437,7 @@ __export(schema_exports, {
30
437
  loadedSkills: () => loadedSkills,
31
438
  messages: () => messages,
32
439
  sessions: () => sessions,
440
+ subagentExecutions: () => subagentExecutions,
33
441
  terminals: () => terminals,
34
442
  todoItems: () => todoItems,
35
443
  toolExecutions: () => toolExecutions
@@ -133,6 +541,26 @@ var fileBackups = sqliteTable("file_backups", {
133
541
  existed: integer("existed", { mode: "boolean" }).notNull().default(true),
134
542
  createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
135
543
  });
544
+ var subagentExecutions = sqliteTable("subagent_executions", {
545
+ id: text("id").primaryKey(),
546
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
547
+ toolCallId: text("tool_call_id").notNull(),
548
+ // The tool call that spawned this subagent
549
+ subagentType: text("subagent_type").notNull(),
550
+ // e.g., 'search', 'analyze', etc.
551
+ task: text("task").notNull(),
552
+ // The task/query given to the subagent
553
+ model: text("model").notNull(),
554
+ // The model used (e.g., 'gemini-2.0-flash')
555
+ status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
556
+ // Steps taken by the subagent (stored as JSON array)
557
+ steps: text("steps", { mode: "json" }).$type().default([]),
558
+ // Final result/output
559
+ result: text("result", { mode: "json" }),
560
+ error: text("error"),
561
+ startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
562
+ completedAt: integer("completed_at", { mode: "timestamp" })
563
+ });
136
564
 
137
565
  // src/db/index.ts
138
566
  var db = null;
@@ -237,6 +665,22 @@ function initDatabase(dbPath) {
237
665
  created_at INTEGER NOT NULL
238
666
  );
239
667
 
668
+ -- Subagent executions table - tracks subagent runs
669
+ CREATE TABLE IF NOT EXISTS subagent_executions (
670
+ id TEXT PRIMARY KEY,
671
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
672
+ tool_call_id TEXT NOT NULL,
673
+ subagent_type TEXT NOT NULL,
674
+ task TEXT NOT NULL,
675
+ model TEXT NOT NULL,
676
+ status TEXT NOT NULL DEFAULT 'running',
677
+ steps TEXT DEFAULT '[]',
678
+ result TEXT,
679
+ error TEXT,
680
+ started_at INTEGER NOT NULL,
681
+ completed_at INTEGER
682
+ );
683
+
240
684
  CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
241
685
  CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);
242
686
  CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);
@@ -246,6 +690,8 @@ function initDatabase(dbPath) {
246
690
  CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
247
691
  CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);
248
692
  CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);
693
+ CREATE INDEX IF NOT EXISTS idx_subagent_executions_session ON subagent_executions(session_id);
694
+ CREATE INDEX IF NOT EXISTS idx_subagent_executions_tool_call ON subagent_executions(tool_call_id);
249
695
  `);
250
696
  return db;
251
697
  }
@@ -639,74 +1085,121 @@ var fileBackupQueries = {
639
1085
  return result.changes;
640
1086
  }
641
1087
  };
1088
+ var subagentQueries = {
1089
+ create(data) {
1090
+ const id = nanoid();
1091
+ const result = getDb().insert(subagentExecutions).values({
1092
+ id,
1093
+ sessionId: data.sessionId,
1094
+ toolCallId: data.toolCallId,
1095
+ subagentType: data.subagentType,
1096
+ task: data.task,
1097
+ model: data.model,
1098
+ status: "running",
1099
+ steps: [],
1100
+ startedAt: /* @__PURE__ */ new Date()
1101
+ }).returning().get();
1102
+ return result;
1103
+ },
1104
+ getById(id) {
1105
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.id, id)).get();
1106
+ },
1107
+ getByToolCallId(toolCallId) {
1108
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.toolCallId, toolCallId)).get();
1109
+ },
1110
+ getBySession(sessionId) {
1111
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).orderBy(desc(subagentExecutions.startedAt)).all();
1112
+ },
1113
+ addStep(id, step) {
1114
+ const existing = this.getById(id);
1115
+ if (!existing) return void 0;
1116
+ const currentSteps = existing.steps || [];
1117
+ const newSteps = [...currentSteps, step];
1118
+ return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
1119
+ },
1120
+ complete(id, result) {
1121
+ return getDb().update(subagentExecutions).set({
1122
+ status: "completed",
1123
+ result,
1124
+ completedAt: /* @__PURE__ */ new Date()
1125
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1126
+ },
1127
+ markError(id, error) {
1128
+ return getDb().update(subagentExecutions).set({
1129
+ status: "error",
1130
+ error,
1131
+ completedAt: /* @__PURE__ */ new Date()
1132
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1133
+ },
1134
+ cancel(id) {
1135
+ return getDb().update(subagentExecutions).set({
1136
+ status: "cancelled",
1137
+ completedAt: /* @__PURE__ */ new Date()
1138
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1139
+ },
1140
+ deleteBySession(sessionId) {
1141
+ const result = getDb().delete(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).run();
1142
+ return result.changes;
1143
+ }
1144
+ };
642
1145
 
643
1146
  // src/config/index.ts
1147
+ init_types();
1148
+ init_types();
644
1149
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
645
1150
  import { resolve, dirname, join } from "path";
646
1151
  import { homedir, platform } from "os";
647
-
648
- // src/config/types.ts
649
- import { z } from "zod";
650
- var ToolApprovalConfigSchema = z.object({
651
- bash: z.boolean().optional().default(true),
652
- write_file: z.boolean().optional().default(false),
653
- read_file: z.boolean().optional().default(false),
654
- load_skill: z.boolean().optional().default(false),
655
- todo: z.boolean().optional().default(false)
656
- });
657
- var SkillMetadataSchema = z.object({
658
- name: z.string(),
659
- description: z.string()
660
- });
661
- var SessionConfigSchema = z.object({
662
- toolApprovals: z.record(z.string(), z.boolean()).optional(),
663
- approvalWebhook: z.string().url().optional(),
664
- skillsDirectory: z.string().optional(),
665
- maxContextChars: z.number().optional().default(2e5)
666
- });
667
- var SparkcoderConfigSchema = z.object({
668
- // Default model to use (Vercel AI Gateway format)
669
- defaultModel: z.string().default("anthropic/claude-opus-4-5"),
670
- // Working directory for file operations
671
- workingDirectory: z.string().optional(),
672
- // Tool approval settings
673
- toolApprovals: ToolApprovalConfigSchema.optional().default({}),
674
- // Approval webhook URL (called when approval is needed)
675
- approvalWebhook: z.string().url().optional(),
676
- // Skills configuration
677
- skills: z.object({
678
- // Directory containing skill files
679
- directory: z.string().optional().default("./skills"),
680
- // Additional skill directories to include
681
- additionalDirectories: z.array(z.string()).optional().default([])
682
- }).optional().default({}),
683
- // Context management
684
- context: z.object({
685
- // Maximum context size before summarization (in characters)
686
- maxChars: z.number().optional().default(2e5),
687
- // Enable automatic summarization
688
- autoSummarize: z.boolean().optional().default(true),
689
- // Number of recent messages to keep after summarization
690
- keepRecentMessages: z.number().optional().default(10)
691
- }).optional().default({}),
692
- // Server configuration
693
- server: z.object({
694
- port: z.number().default(3141),
695
- host: z.string().default("127.0.0.1"),
696
- // Public URL for web UI to connect to API (for Docker/remote access)
697
- // If not set, defaults to http://{host}:{port}
698
- publicUrl: z.string().url().optional()
699
- }).default({ port: 3141, host: "127.0.0.1" }),
700
- // Database path
701
- databasePath: z.string().optional().default("./sparkecoder.db")
702
- });
703
-
704
- // src/config/index.ts
705
1152
  var CONFIG_FILE_NAMES = [
706
1153
  "sparkecoder.config.json",
707
1154
  "sparkecoder.json",
708
1155
  ".sparkecoder.json"
709
1156
  ];
1157
+ function discoverSkillDirectories(workingDir) {
1158
+ const alwaysLoadedDirs = [];
1159
+ const onDemandDirs = [];
1160
+ const allDirectories = [];
1161
+ let agentsMdPath = null;
1162
+ const sparkRulesDir = join(workingDir, ".sparkecoder", "rules");
1163
+ if (existsSync(sparkRulesDir)) {
1164
+ alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });
1165
+ allDirectories.push(sparkRulesDir);
1166
+ }
1167
+ const sparkSkillsDir = join(workingDir, ".sparkecoder", "skills");
1168
+ if (existsSync(sparkSkillsDir)) {
1169
+ onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });
1170
+ allDirectories.push(sparkSkillsDir);
1171
+ }
1172
+ const cursorRulesDir = join(workingDir, ".cursor", "rules");
1173
+ if (existsSync(cursorRulesDir)) {
1174
+ onDemandDirs.push({ path: cursorRulesDir, priority: 3 });
1175
+ allDirectories.push(cursorRulesDir);
1176
+ }
1177
+ const claudeSkillsDir = join(workingDir, ".claude", "skills");
1178
+ if (existsSync(claudeSkillsDir)) {
1179
+ onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });
1180
+ allDirectories.push(claudeSkillsDir);
1181
+ }
1182
+ const legacySkillsDir = join(workingDir, "skills");
1183
+ if (existsSync(legacySkillsDir)) {
1184
+ onDemandDirs.push({ path: legacySkillsDir, priority: 5 });
1185
+ allDirectories.push(legacySkillsDir);
1186
+ }
1187
+ const agentsMd = join(workingDir, "AGENTS.md");
1188
+ if (existsSync(agentsMd)) {
1189
+ agentsMdPath = agentsMd;
1190
+ }
1191
+ const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
1192
+ if (existsSync(builtInSkillsDir)) {
1193
+ onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
1194
+ allDirectories.push(builtInSkillsDir);
1195
+ }
1196
+ return {
1197
+ alwaysLoadedDirs,
1198
+ onDemandDirs,
1199
+ agentsMdPath,
1200
+ allDirectories
1201
+ };
1202
+ }
710
1203
  function getAppDataDirectory() {
711
1204
  const appName = "sparkecoder";
712
1205
  switch (platform()) {
@@ -786,20 +1279,12 @@ function loadConfig(configPath, workingDirectory) {
786
1279
  } else {
787
1280
  resolvedWorkingDirectory = process.cwd();
788
1281
  }
1282
+ const discovered = discoverSkillDirectories(resolvedWorkingDirectory);
1283
+ const additionalDirs = (config.skills?.additionalDirectories || []).map((dir) => resolve(configDir, dir)).filter((dir) => existsSync(dir));
789
1284
  const resolvedSkillsDirectories = [
790
- resolve(configDir, config.skills?.directory || "./skills"),
791
- // Built-in skills
792
- resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default"),
793
- ...(config.skills?.additionalDirectories || []).map(
794
- (dir) => resolve(configDir, dir)
795
- )
796
- ].filter((dir) => {
797
- try {
798
- return existsSync(dir);
799
- } catch {
800
- return false;
801
- }
802
- });
1285
+ ...discovered.allDirectories,
1286
+ ...additionalDirs
1287
+ ];
803
1288
  let resolvedDatabasePath;
804
1289
  if (config.databasePath && config.databasePath !== "./sparkecoder.db") {
805
1290
  resolvedDatabasePath = resolve(configDir, config.databasePath);
@@ -816,7 +1301,8 @@ function loadConfig(configPath, workingDirectory) {
816
1301
  },
817
1302
  resolvedWorkingDirectory,
818
1303
  resolvedSkillsDirectories,
819
- resolvedDatabasePath
1304
+ resolvedDatabasePath,
1305
+ discoveredSkills: discovered
820
1306
  };
821
1307
  cachedConfig = resolved;
822
1308
  return resolved;
@@ -1234,8 +1720,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
1234
1720
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
1235
1721
  const terminals3 = [];
1236
1722
  try {
1237
- const { readdir: readdir3 } = await import("fs/promises");
1238
- const entries = await readdir3(terminalsDir, { withFileTypes: true });
1723
+ const { readdir: readdir5 } = await import("fs/promises");
1724
+ const entries = await readdir5(terminalsDir, { withFileTypes: true });
1239
1725
  for (const entry of entries) {
1240
1726
  if (entry.isDirectory()) {
1241
1727
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -1839,12 +2325,12 @@ function findNearestRoot(startDir, markers) {
1839
2325
  }
1840
2326
  async function commandExists(cmd) {
1841
2327
  try {
1842
- const { exec: exec5 } = await import("child_process");
1843
- const { promisify: promisify5 } = await import("util");
1844
- const execAsync5 = promisify5(exec5);
2328
+ const { exec: exec6 } = await import("child_process");
2329
+ const { promisify: promisify6 } = await import("util");
2330
+ const execAsync6 = promisify6(exec6);
1845
2331
  const isWindows = process.platform === "win32";
1846
2332
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
1847
- await execAsync5(checkCmd);
2333
+ await execAsync6(checkCmd);
1848
2334
  return true;
1849
2335
  } catch {
1850
2336
  return false;
@@ -2134,7 +2620,7 @@ async function createClient(serverId, handle, root) {
2134
2620
  },
2135
2621
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
2136
2622
  const normalized = normalizePath(filePath);
2137
- return new Promise((resolve9) => {
2623
+ return new Promise((resolve10) => {
2138
2624
  const startTime = Date.now();
2139
2625
  let debounceTimer;
2140
2626
  let resolved = false;
@@ -2153,7 +2639,7 @@ async function createClient(serverId, handle, root) {
2153
2639
  if (resolved) return;
2154
2640
  resolved = true;
2155
2641
  cleanup();
2156
- resolve9(diagnostics.get(normalized) || []);
2642
+ resolve10(diagnostics.get(normalized) || []);
2157
2643
  };
2158
2644
  const onDiagnostic = () => {
2159
2645
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -2311,6 +2797,7 @@ function isSupported(filePath) {
2311
2797
  }
2312
2798
 
2313
2799
  // src/tools/write-file.ts
2800
+ var MAX_PROGRESS_CHUNK_SIZE = 16 * 1024;
2314
2801
  var writeFileInputSchema = z4.object({
2315
2802
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
2316
2803
  mode: z4.enum(["full", "str_replace"]).describe('Write mode: "full" for complete file write, "str_replace" for targeted string replacement'),
@@ -2354,24 +2841,76 @@ Working directory: ${options.workingDirectory}`,
2354
2841
  error: 'Content is required for "full" mode'
2355
2842
  };
2356
2843
  }
2357
- await backupFile(options.sessionId, options.workingDirectory, absolutePath);
2358
- const dir = dirname5(absolutePath);
2844
+ const existed = existsSync7(absolutePath);
2845
+ const action = existed ? "replaced" : "created";
2846
+ console.log("[WRITE-FILE] onProgress callback exists:", !!options.onProgress);
2847
+ console.log("[WRITE-FILE] Emitting started event for:", relativePath);
2848
+ options.onProgress?.({
2849
+ path: absolutePath,
2850
+ relativePath,
2851
+ mode: "full",
2852
+ status: "started",
2853
+ action,
2854
+ totalLength: content.length
2855
+ });
2856
+ if (content.length <= MAX_PROGRESS_CHUNK_SIZE) {
2857
+ options.onProgress?.({
2858
+ path: absolutePath,
2859
+ relativePath,
2860
+ mode: "full",
2861
+ status: "content",
2862
+ content,
2863
+ action,
2864
+ totalLength: content.length
2865
+ });
2866
+ } else {
2867
+ const chunkCount = Math.ceil(content.length / MAX_PROGRESS_CHUNK_SIZE);
2868
+ for (let i = 0; i < chunkCount; i += 1) {
2869
+ const chunkStart = i * MAX_PROGRESS_CHUNK_SIZE;
2870
+ const chunk = content.slice(chunkStart, chunkStart + MAX_PROGRESS_CHUNK_SIZE);
2871
+ options.onProgress?.({
2872
+ path: absolutePath,
2873
+ relativePath,
2874
+ mode: "full",
2875
+ status: "content",
2876
+ content: chunk,
2877
+ action,
2878
+ totalLength: content.length,
2879
+ chunkIndex: i,
2880
+ chunkCount,
2881
+ chunkStart,
2882
+ isChunked: true
2883
+ });
2884
+ if (chunkCount > 1) {
2885
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
2886
+ }
2887
+ }
2888
+ }
2889
+ await backupFile(options.sessionId, options.workingDirectory, absolutePath);
2890
+ const dir = dirname5(absolutePath);
2359
2891
  if (!existsSync7(dir)) {
2360
2892
  await mkdir3(dir, { recursive: true });
2361
2893
  }
2362
- const existed = existsSync7(absolutePath);
2363
2894
  await writeFile3(absolutePath, content, "utf-8");
2364
2895
  let diagnosticsOutput = "";
2365
2896
  if (options.enableLSP !== false && isSupported(absolutePath)) {
2366
2897
  await touchFile(absolutePath, true);
2367
2898
  diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2368
2899
  }
2900
+ options.onProgress?.({
2901
+ path: absolutePath,
2902
+ relativePath,
2903
+ mode: "full",
2904
+ status: "completed",
2905
+ action,
2906
+ totalLength: content.length
2907
+ });
2369
2908
  return {
2370
2909
  success: true,
2371
2910
  path: absolutePath,
2372
- relativePath: relative3(options.workingDirectory, absolutePath),
2911
+ relativePath,
2373
2912
  mode: "full",
2374
- action: existed ? "replaced" : "created",
2913
+ action,
2375
2914
  bytesWritten: Buffer.byteLength(content, "utf-8"),
2376
2915
  lineCount: content.split("\n").length,
2377
2916
  ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
@@ -2389,6 +2928,22 @@ Working directory: ${options.workingDirectory}`,
2389
2928
  error: `File not found: ${path}. Use "full" mode to create new files.`
2390
2929
  };
2391
2930
  }
2931
+ options.onProgress?.({
2932
+ path: absolutePath,
2933
+ relativePath,
2934
+ mode: "str_replace",
2935
+ status: "started",
2936
+ action: "edited"
2937
+ });
2938
+ options.onProgress?.({
2939
+ path: absolutePath,
2940
+ relativePath,
2941
+ mode: "str_replace",
2942
+ status: "content",
2943
+ oldString: old_string,
2944
+ newString: new_string,
2945
+ action: "edited"
2946
+ });
2392
2947
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
2393
2948
  const currentContent = await readFile5(absolutePath, "utf-8");
2394
2949
  if (!currentContent.includes(old_string)) {
@@ -2419,10 +2974,17 @@ Working directory: ${options.workingDirectory}`,
2419
2974
  await touchFile(absolutePath, true);
2420
2975
  diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2421
2976
  }
2977
+ options.onProgress?.({
2978
+ path: absolutePath,
2979
+ relativePath,
2980
+ mode: "str_replace",
2981
+ status: "completed",
2982
+ action: "edited"
2983
+ });
2422
2984
  return {
2423
2985
  success: true,
2424
2986
  path: absolutePath,
2425
- relativePath: relative3(options.workingDirectory, absolutePath),
2987
+ relativePath,
2426
2988
  mode: "str_replace",
2427
2989
  linesRemoved: oldLines,
2428
2990
  linesAdded: newLines,
@@ -2570,112 +3132,9 @@ function formatTodoItem(item) {
2570
3132
  }
2571
3133
 
2572
3134
  // src/tools/load-skill.ts
3135
+ init_skills();
2573
3136
  import { tool as tool5 } from "ai";
2574
3137
  import { z as z6 } from "zod";
2575
-
2576
- // src/skills/index.ts
2577
- import { readFile as readFile6, readdir } from "fs/promises";
2578
- import { resolve as resolve6, basename, extname as extname3 } from "path";
2579
- import { existsSync as existsSync8 } from "fs";
2580
- function parseSkillFrontmatter(content) {
2581
- const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
2582
- if (!frontmatterMatch) {
2583
- return null;
2584
- }
2585
- const [, frontmatter, body] = frontmatterMatch;
2586
- try {
2587
- const lines = frontmatter.split("\n");
2588
- const data = {};
2589
- for (const line of lines) {
2590
- const colonIndex = line.indexOf(":");
2591
- if (colonIndex > 0) {
2592
- const key = line.slice(0, colonIndex).trim();
2593
- let value = line.slice(colonIndex + 1).trim();
2594
- if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
2595
- value = value.slice(1, -1);
2596
- }
2597
- data[key] = value;
2598
- }
2599
- }
2600
- const metadata = SkillMetadataSchema.parse(data);
2601
- return { metadata, body: body.trim() };
2602
- } catch {
2603
- return null;
2604
- }
2605
- }
2606
- function getSkillNameFromPath(filePath) {
2607
- return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
2608
- }
2609
- async function loadSkillsFromDirectory(directory) {
2610
- if (!existsSync8(directory)) {
2611
- return [];
2612
- }
2613
- const skills = [];
2614
- const files = await readdir(directory);
2615
- for (const file of files) {
2616
- if (!file.endsWith(".md")) continue;
2617
- const filePath = resolve6(directory, file);
2618
- const content = await readFile6(filePath, "utf-8");
2619
- const parsed = parseSkillFrontmatter(content);
2620
- if (parsed) {
2621
- skills.push({
2622
- name: parsed.metadata.name,
2623
- description: parsed.metadata.description,
2624
- filePath
2625
- });
2626
- } else {
2627
- const name = getSkillNameFromPath(filePath);
2628
- const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
2629
- skills.push({
2630
- name,
2631
- description: firstParagraph.replace(/^#\s*/, "").trim(),
2632
- filePath
2633
- });
2634
- }
2635
- }
2636
- return skills;
2637
- }
2638
- async function loadAllSkills(directories) {
2639
- const allSkills = [];
2640
- const seenNames = /* @__PURE__ */ new Set();
2641
- for (const dir of directories) {
2642
- const skills = await loadSkillsFromDirectory(dir);
2643
- for (const skill of skills) {
2644
- if (!seenNames.has(skill.name.toLowerCase())) {
2645
- seenNames.add(skill.name.toLowerCase());
2646
- allSkills.push(skill);
2647
- }
2648
- }
2649
- }
2650
- return allSkills;
2651
- }
2652
- async function loadSkillContent(skillName, directories) {
2653
- const allSkills = await loadAllSkills(directories);
2654
- const skill = allSkills.find(
2655
- (s) => s.name.toLowerCase() === skillName.toLowerCase()
2656
- );
2657
- if (!skill) {
2658
- return null;
2659
- }
2660
- const content = await readFile6(skill.filePath, "utf-8");
2661
- const parsed = parseSkillFrontmatter(content);
2662
- return {
2663
- ...skill,
2664
- content: parsed ? parsed.body : content
2665
- };
2666
- }
2667
- function formatSkillsForContext(skills) {
2668
- if (skills.length === 0) {
2669
- return "No skills available.";
2670
- }
2671
- const lines = ["Available skills (use load_skill tool to load into context):"];
2672
- for (const skill of skills) {
2673
- lines.push(`- ${skill.name}: ${skill.description}`);
2674
- }
2675
- return lines.join("\n");
2676
- }
2677
-
2678
- // src/tools/load-skill.ts
2679
3138
  var loadSkillInputSchema = z6.object({
2680
3139
  action: z6.enum(["list", "load"]).describe('Action to perform: "list" to see available skills, "load" to load a skill'),
2681
3140
  skillName: z6.string().optional().describe('For "load" action: The name of the skill to load')
@@ -2758,7 +3217,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
2758
3217
  // src/tools/linter.ts
2759
3218
  import { tool as tool6 } from "ai";
2760
3219
  import { z as z7 } from "zod";
2761
- import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
3220
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
2762
3221
  import { existsSync as existsSync9 } from "fs";
2763
3222
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
2764
3223
  var linterInputSchema = z7.object({
@@ -2840,27 +3299,720 @@ Working directory: ${options.workingDirectory}`,
2840
3299
  }
2841
3300
  }
2842
3301
  }
2843
- if (filesToCheck.length === 0) {
3302
+ if (filesToCheck.length === 0) {
3303
+ return {
3304
+ success: true,
3305
+ message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
3306
+ files: [],
3307
+ totalErrors: 0,
3308
+ totalWarnings: 0
3309
+ };
3310
+ }
3311
+ await Promise.all(
3312
+ filesToCheck.map((file) => touchFile(file, true))
3313
+ );
3314
+ const diagnosticsMap = {};
3315
+ for (const file of filesToCheck) {
3316
+ const diagnostics = await getDiagnostics(file);
3317
+ if (diagnostics.length > 0) {
3318
+ diagnosticsMap[file] = diagnostics;
3319
+ }
3320
+ }
3321
+ return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
3322
+ } catch (error) {
3323
+ return {
3324
+ success: false,
3325
+ error: error.message
3326
+ };
3327
+ }
3328
+ }
3329
+ });
3330
+ }
3331
+ function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
3332
+ let totalErrors = 0;
3333
+ let totalWarnings = 0;
3334
+ let totalInfo = 0;
3335
+ const files = [];
3336
+ for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
3337
+ const relativePath = relative5(workingDirectory, filePath);
3338
+ let fileErrors = 0;
3339
+ let fileWarnings = 0;
3340
+ const formattedDiagnostics = diagnostics.map((d) => {
3341
+ const severity = getSeverityString(d.severity);
3342
+ if (d.severity === 1 /* Error */) {
3343
+ fileErrors++;
3344
+ totalErrors++;
3345
+ } else if (d.severity === 2 /* Warning */) {
3346
+ fileWarnings++;
3347
+ totalWarnings++;
3348
+ } else {
3349
+ totalInfo++;
3350
+ }
3351
+ return {
3352
+ severity,
3353
+ line: d.range.start.line + 1,
3354
+ column: d.range.start.character + 1,
3355
+ message: d.message,
3356
+ source: d.source,
3357
+ code: d.code
3358
+ };
3359
+ });
3360
+ files.push({
3361
+ path: filePath,
3362
+ relativePath,
3363
+ errors: fileErrors,
3364
+ warnings: fileWarnings,
3365
+ diagnostics: formattedDiagnostics
3366
+ });
3367
+ }
3368
+ files.sort((a, b) => b.errors - a.errors);
3369
+ const hasIssues = totalErrors > 0 || totalWarnings > 0;
3370
+ return {
3371
+ success: true,
3372
+ message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
3373
+ files,
3374
+ totalErrors,
3375
+ totalWarnings,
3376
+ totalInfo,
3377
+ summary: hasIssues ? formatSummary(files) : void 0
3378
+ };
3379
+ }
3380
+ function getSeverityString(severity) {
3381
+ switch (severity) {
3382
+ case 1 /* Error */:
3383
+ return "error";
3384
+ case 2 /* Warning */:
3385
+ return "warning";
3386
+ case 3 /* Information */:
3387
+ return "info";
3388
+ case 4 /* Hint */:
3389
+ return "hint";
3390
+ default:
3391
+ return "error";
3392
+ }
3393
+ }
3394
+ function formatSummary(files) {
3395
+ const lines = [];
3396
+ for (const file of files) {
3397
+ lines.push(`
3398
+ ${file.relativePath}:`);
3399
+ for (const d of file.diagnostics.slice(0, 10)) {
3400
+ const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
3401
+ lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
3402
+ }
3403
+ if (file.diagnostics.length > 10) {
3404
+ lines.push(` ... and ${file.diagnostics.length - 10} more`);
3405
+ }
3406
+ }
3407
+ return lines.join("\n");
3408
+ }
3409
+
3410
+ // src/tools/search.ts
3411
+ import { tool as tool8 } from "ai";
3412
+ import { z as z9 } from "zod";
3413
+
3414
+ // src/agent/subagent.ts
3415
+ import {
3416
+ generateText,
3417
+ stepCountIs
3418
+ } from "ai";
3419
+ import { nanoid as nanoid3 } from "nanoid";
3420
+ var Subagent = class {
3421
+ /** Model to use (defaults to gemini-2.0-flash) */
3422
+ model;
3423
+ /** Maximum steps before stopping */
3424
+ maxSteps = 20;
3425
+ constructor(model) {
3426
+ this.model = model || SUBAGENT_MODELS.default;
3427
+ }
3428
+ /**
3429
+ * Parse the final result from the subagent's output.
3430
+ * Override this to structure the result for your subagent type.
3431
+ */
3432
+ parseResult(text2, steps) {
3433
+ return { text: text2, steps };
3434
+ }
3435
+ /**
3436
+ * Run the subagent with streaming progress updates
3437
+ */
3438
+ async run(options) {
3439
+ const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
3440
+ const steps = [];
3441
+ const execution = subagentQueries.create({
3442
+ sessionId,
3443
+ toolCallId,
3444
+ subagentType: this.type,
3445
+ task,
3446
+ model: this.model
3447
+ });
3448
+ const addStep = async (step) => {
3449
+ const fullStep = {
3450
+ id: nanoid3(8),
3451
+ timestamp: Date.now(),
3452
+ ...step
3453
+ };
3454
+ steps.push(fullStep);
3455
+ subagentQueries.addStep(execution.id, fullStep);
3456
+ await onProgress?.({
3457
+ type: "step",
3458
+ subagentId: execution.id,
3459
+ subagentType: this.type,
3460
+ step: fullStep
3461
+ });
3462
+ };
3463
+ try {
3464
+ const tools = this.getTools(options);
3465
+ const systemPrompt = this.getSystemPrompt(options);
3466
+ const result = await generateText({
3467
+ model: resolveModel(this.model),
3468
+ system: systemPrompt,
3469
+ messages: [
3470
+ { role: "user", content: task }
3471
+ ],
3472
+ tools,
3473
+ stopWhen: stepCountIs(this.maxSteps),
3474
+ abortSignal,
3475
+ onStepFinish: async (step) => {
3476
+ if (step.text) {
3477
+ await addStep({
3478
+ type: "text",
3479
+ content: step.text
3480
+ });
3481
+ await onProgress?.({
3482
+ type: "text",
3483
+ subagentId: execution.id,
3484
+ subagentType: this.type,
3485
+ text: step.text
3486
+ });
3487
+ }
3488
+ if (step.toolCalls) {
3489
+ for (const toolCall of step.toolCalls) {
3490
+ await addStep({
3491
+ type: "tool_call",
3492
+ content: `Calling ${toolCall.toolName}`,
3493
+ toolName: toolCall.toolName,
3494
+ toolInput: toolCall.input
3495
+ });
3496
+ await onProgress?.({
3497
+ type: "tool_call",
3498
+ subagentId: execution.id,
3499
+ subagentType: this.type,
3500
+ toolName: toolCall.toolName,
3501
+ toolInput: toolCall.input
3502
+ });
3503
+ }
3504
+ }
3505
+ if (step.toolResults) {
3506
+ for (const toolResult of step.toolResults) {
3507
+ await addStep({
3508
+ type: "tool_result",
3509
+ content: `Result from ${toolResult.toolName}`,
3510
+ toolName: toolResult.toolName,
3511
+ toolOutput: toolResult.output
3512
+ });
3513
+ await onProgress?.({
3514
+ type: "tool_result",
3515
+ subagentId: execution.id,
3516
+ subagentType: this.type,
3517
+ toolName: toolResult.toolName,
3518
+ toolOutput: toolResult.output
3519
+ });
3520
+ }
3521
+ }
3522
+ }
3523
+ });
3524
+ const parsedResult = this.parseResult(result.text, steps);
3525
+ subagentQueries.complete(execution.id, parsedResult);
3526
+ await onProgress?.({
3527
+ type: "complete",
3528
+ subagentId: execution.id,
3529
+ subagentType: this.type,
3530
+ result: parsedResult
3531
+ });
3532
+ return {
3533
+ success: true,
3534
+ result: parsedResult,
3535
+ steps,
3536
+ executionId: execution.id
3537
+ };
3538
+ } catch (error) {
3539
+ const errorMessage = error.message || "Unknown error";
3540
+ subagentQueries.markError(execution.id, errorMessage);
3541
+ await onProgress?.({
3542
+ type: "error",
3543
+ subagentId: execution.id,
3544
+ subagentType: this.type,
3545
+ error: errorMessage
3546
+ });
3547
+ return {
3548
+ success: false,
3549
+ error: errorMessage,
3550
+ steps,
3551
+ executionId: execution.id
3552
+ };
3553
+ }
3554
+ }
3555
+ /**
3556
+ * Run with streaming (for real-time progress in UI)
3557
+ */
3558
+ async *stream(options) {
3559
+ const events = [];
3560
+ let resolveNext = null;
3561
+ let done = false;
3562
+ const eventQueue = [];
3563
+ const runPromise = this.run({
3564
+ ...options,
3565
+ onProgress: async (event) => {
3566
+ eventQueue.push(event);
3567
+ if (resolveNext) {
3568
+ resolveNext(eventQueue.shift());
3569
+ resolveNext = null;
3570
+ }
3571
+ }
3572
+ }).then((result) => {
3573
+ done = true;
3574
+ if (resolveNext) {
3575
+ resolveNext(null);
3576
+ }
3577
+ return result;
3578
+ });
3579
+ while (!done || eventQueue.length > 0) {
3580
+ if (eventQueue.length > 0) {
3581
+ yield eventQueue.shift();
3582
+ } else if (!done) {
3583
+ const event = await new Promise((resolve10) => {
3584
+ resolveNext = resolve10;
3585
+ });
3586
+ if (event) {
3587
+ yield event;
3588
+ }
3589
+ }
3590
+ }
3591
+ await runPromise;
3592
+ }
3593
+ };
3594
+
3595
+ // src/agent/subagents/search.ts
3596
+ import { tool as tool7 } from "ai";
3597
+ import { z as z8 } from "zod";
3598
+ import { exec as exec4 } from "child_process";
3599
+ import { promisify as promisify4 } from "util";
3600
+ import { readFile as readFile7, stat as stat3, readdir as readdir3 } from "fs/promises";
3601
+ import { resolve as resolve8, relative as relative6, isAbsolute as isAbsolute4 } from "path";
3602
+ import { existsSync as existsSync10 } from "fs";
3603
+ var execAsync4 = promisify4(exec4);
3604
+ var MAX_OUTPUT_CHARS4 = 2e4;
3605
+ var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
3606
+ var SearchSubagent = class extends Subagent {
3607
+ type = "search";
3608
+ name = "Search Agent";
3609
+ constructor(model) {
3610
+ super(model || SUBAGENT_MODELS.search);
3611
+ this.maxSteps = 15;
3612
+ }
3613
+ getSystemPrompt(options) {
3614
+ return `You are a specialized search agent for exploring codebases. Your job is to find relevant code, files, and patterns based on the user's query.
3615
+
3616
+ Working Directory: ${options.workingDirectory}
3617
+
3618
+ You have these tools available:
3619
+ - grep: Search for patterns in files using ripgrep (rg)
3620
+ - glob: Find files matching a pattern
3621
+ - read_file: Read contents of a specific file
3622
+ - list_dir: List directory contents
3623
+
3624
+ ## Strategy - Search in Parallel
3625
+
3626
+ IMPORTANT: When searching, run MULTIPLE searches in PARALLEL to cover different variations and related terms. Don't search sequentially - batch your searches together!
3627
+
3628
+ For example, if asked "how does authentication work":
3629
+ - Search for "auth", "login", "session", "jwt", "token" all at once
3630
+ - Search in different likely directories: src/auth/, lib/auth/, services/auth/
3631
+ - Look for common patterns: AuthProvider, useAuth, authenticate, isAuthenticated
3632
+
3633
+ **Parallel Search Patterns:**
3634
+ 1. Try multiple naming conventions at once:
3635
+ - camelCase: "getUserData", "handleAuth"
3636
+ - snake_case: "get_user_data", "handle_auth"
3637
+ - PascalCase: "UserService", "AuthProvider"
3638
+
3639
+ 2. Search for related concepts together:
3640
+ - For "database": search "db", "database", "query", "model", "schema"
3641
+ - For "api": search "endpoint", "route", "handler", "controller", "api"
3642
+
3643
+ 3. Use glob AND grep together:
3644
+ - Find files: \`*.auth.ts\`, \`*Auth*.tsx\`, \`auth/*.ts\`
3645
+ - Search content: patterns, function names, class names
3646
+
3647
+ ## Execution Flow
3648
+ 1. First, run 2-4 parallel searches covering different angles of the query
3649
+ 2. Review results and identify the most relevant files
3650
+ 3. Read the key files to understand the full context
3651
+ 4. Provide a clear summary with exact file paths and line numbers
3652
+
3653
+ Be efficient - you have limited steps. Maximize coverage with parallel tool calls.
3654
+
3655
+ ## Output Format
3656
+ When done, provide a summary with:
3657
+ - Key files/locations found (with full paths)
3658
+ - Relevant code snippets showing the important parts
3659
+ - How the pieces connect together
3660
+
3661
+ Keep your responses concise and focused on actionable information.`;
3662
+ }
3663
+ getTools(options) {
3664
+ const workingDirectory = options.workingDirectory;
3665
+ return {
3666
+ grep: tool7({
3667
+ description: "Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.",
3668
+ inputSchema: z8.object({
3669
+ pattern: z8.string().describe("The regex pattern to search for"),
3670
+ path: z8.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
3671
+ fileType: z8.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
3672
+ maxResults: z8.number().optional().default(50).describe("Maximum number of results to return")
3673
+ }),
3674
+ execute: async ({ pattern, path, fileType, maxResults }) => {
3675
+ try {
3676
+ const searchPath = path ? resolve8(workingDirectory, path) : workingDirectory;
3677
+ let args = ["rg", "--line-number", "--no-heading"];
3678
+ if (fileType) {
3679
+ args.push("--type", fileType);
3680
+ }
3681
+ args.push("--max-count", String(maxResults || 50));
3682
+ args.push("--", pattern, searchPath);
3683
+ const { stdout, stderr } = await execAsync4(args.join(" "), {
3684
+ cwd: workingDirectory,
3685
+ maxBuffer: 5 * 1024 * 1024,
3686
+ timeout: 3e4
3687
+ });
3688
+ const output = truncateOutput(stdout || "No matches found", MAX_OUTPUT_CHARS4);
3689
+ const matchCount = (stdout || "").split("\n").filter(Boolean).length;
3690
+ return {
3691
+ success: true,
3692
+ output,
3693
+ matchCount,
3694
+ pattern
3695
+ };
3696
+ } catch (error) {
3697
+ if (error.code === 1 && !error.stderr) {
3698
+ return {
3699
+ success: true,
3700
+ output: "No matches found",
3701
+ matchCount: 0,
3702
+ pattern
3703
+ };
3704
+ }
3705
+ return {
3706
+ success: false,
3707
+ error: error.message,
3708
+ pattern
3709
+ };
3710
+ }
3711
+ }
3712
+ }),
3713
+ glob: tool7({
3714
+ description: "Find files matching a glob pattern. Returns list of matching file paths.",
3715
+ inputSchema: z8.object({
3716
+ pattern: z8.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
3717
+ maxResults: z8.number().optional().default(100).describe("Maximum number of files to return")
3718
+ }),
3719
+ execute: async ({ pattern, maxResults }) => {
3720
+ try {
3721
+ const { stdout } = await execAsync4(
3722
+ `find . -type f -name "${pattern.replace("**/", "")}" 2>/dev/null | head -n ${maxResults || 100}`,
3723
+ {
3724
+ cwd: workingDirectory,
3725
+ timeout: 3e4
3726
+ }
3727
+ );
3728
+ const files = stdout.trim().split("\n").filter(Boolean);
3729
+ return {
3730
+ success: true,
3731
+ files,
3732
+ count: files.length,
3733
+ pattern
3734
+ };
3735
+ } catch (error) {
3736
+ return {
3737
+ success: false,
3738
+ error: error.message,
3739
+ pattern
3740
+ };
3741
+ }
3742
+ }
3743
+ }),
3744
+ read_file: tool7({
3745
+ description: "Read the contents of a file. Use this to examine specific files found in search.",
3746
+ inputSchema: z8.object({
3747
+ path: z8.string().describe("Path to the file (relative to working directory or absolute)"),
3748
+ startLine: z8.number().optional().describe("Start reading from this line (1-indexed)"),
3749
+ endLine: z8.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
3750
+ }),
3751
+ execute: async ({ path, startLine, endLine }) => {
3752
+ try {
3753
+ const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3754
+ if (!existsSync10(absolutePath)) {
3755
+ return {
3756
+ success: false,
3757
+ error: `File not found: ${path}`
3758
+ };
3759
+ }
3760
+ const stats = await stat3(absolutePath);
3761
+ if (stats.size > MAX_FILE_SIZE2) {
3762
+ return {
3763
+ success: false,
3764
+ error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
3765
+ };
3766
+ }
3767
+ let content = await readFile7(absolutePath, "utf-8");
3768
+ if (startLine !== void 0 || endLine !== void 0) {
3769
+ const lines = content.split("\n");
3770
+ const start = (startLine ?? 1) - 1;
3771
+ const end = endLine ?? lines.length;
3772
+ content = lines.slice(start, end).join("\n");
3773
+ }
3774
+ return {
3775
+ success: true,
3776
+ path: relative6(workingDirectory, absolutePath),
3777
+ content: truncateOutput(content, MAX_OUTPUT_CHARS4),
3778
+ lineCount: content.split("\n").length
3779
+ };
3780
+ } catch (error) {
3781
+ return {
3782
+ success: false,
3783
+ error: error.message
3784
+ };
3785
+ }
3786
+ }
3787
+ }),
3788
+ list_dir: tool7({
3789
+ description: "List contents of a directory. Shows files and subdirectories.",
3790
+ inputSchema: z8.object({
3791
+ path: z8.string().optional().default(".").describe("Directory path (relative to working directory)"),
3792
+ recursive: z8.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
3793
+ maxDepth: z8.number().optional().default(2).describe("Maximum depth for recursive listing")
3794
+ }),
3795
+ execute: async ({ path, recursive, maxDepth }) => {
3796
+ try {
3797
+ const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3798
+ if (!existsSync10(absolutePath)) {
3799
+ return {
3800
+ success: false,
3801
+ error: `Directory not found: ${path}`
3802
+ };
3803
+ }
3804
+ const stats = await stat3(absolutePath);
3805
+ if (!stats.isDirectory()) {
3806
+ return {
3807
+ success: false,
3808
+ error: `Not a directory: ${path}`
3809
+ };
3810
+ }
3811
+ if (recursive) {
3812
+ const { stdout } = await execAsync4(
3813
+ `find . -maxdepth ${maxDepth} -type f 2>/dev/null | head -n 200`,
3814
+ {
3815
+ cwd: absolutePath,
3816
+ timeout: 1e4
3817
+ }
3818
+ );
3819
+ const files = stdout.trim().split("\n").filter(Boolean);
3820
+ return {
3821
+ success: true,
3822
+ path: relative6(workingDirectory, absolutePath) || ".",
3823
+ files,
3824
+ count: files.length,
3825
+ recursive: true
3826
+ };
3827
+ } else {
3828
+ const entries = await readdir3(absolutePath, { withFileTypes: true });
3829
+ const items = entries.slice(0, 200).map((e) => ({
3830
+ name: e.name,
3831
+ type: e.isDirectory() ? "directory" : "file"
3832
+ }));
3833
+ return {
3834
+ success: true,
3835
+ path: relative6(workingDirectory, absolutePath) || ".",
3836
+ items,
3837
+ count: items.length
3838
+ };
3839
+ }
3840
+ } catch (error) {
3841
+ return {
3842
+ success: false,
3843
+ error: error.message
3844
+ };
3845
+ }
3846
+ }
3847
+ })
3848
+ };
3849
+ }
3850
+ parseResult(text2, steps) {
3851
+ const findings = [];
3852
+ let filesSearched = 0;
3853
+ let matchCount = 0;
3854
+ for (const step of steps) {
3855
+ if (step.type === "tool_result" && step.toolOutput) {
3856
+ const output = step.toolOutput;
3857
+ if (step.toolName === "grep" && output.success) {
3858
+ matchCount += output.matchCount || 0;
3859
+ const lines = (output.output || "").split("\n").filter(Boolean).slice(0, 10);
3860
+ for (const line of lines) {
3861
+ const match = line.match(/^([^:]+):(\d+):(.*)$/);
3862
+ if (match) {
3863
+ findings.push({
3864
+ type: "match",
3865
+ path: match[1],
3866
+ lineNumber: parseInt(match[2], 10),
3867
+ content: match[3].trim(),
3868
+ relevance: "high"
3869
+ });
3870
+ }
3871
+ }
3872
+ } else if (step.toolName === "glob" && output.success) {
3873
+ filesSearched += output.count || 0;
3874
+ for (const file of (output.files || []).slice(0, 5)) {
3875
+ findings.push({
3876
+ type: "file",
3877
+ path: file,
3878
+ relevance: "medium"
3879
+ });
3880
+ }
3881
+ } else if (step.toolName === "read_file" && output.success) {
3882
+ findings.push({
3883
+ type: "file",
3884
+ path: output.path,
3885
+ relevance: "high",
3886
+ context: `${output.lineCount} lines`
3887
+ });
3888
+ }
3889
+ }
3890
+ }
3891
+ const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
3892
+ return {
3893
+ query,
3894
+ summary: text2,
3895
+ findings: findings.slice(0, 20),
3896
+ // Limit findings
3897
+ filesSearched,
3898
+ matchCount
3899
+ };
3900
+ }
3901
+ };
3902
+ function createSearchSubagent(model) {
3903
+ return new SearchSubagent(model);
3904
+ }
3905
+
3906
+ // src/tools/search.ts
3907
+ var MAX_RESULT_CHARS = 8e3;
3908
+ function createSearchTool(options) {
3909
+ return tool8({
3910
+ description: `Delegate a search task to a specialized search agent. Use this when you need to:
3911
+ - Find files or code matching a pattern
3912
+ - Explore the codebase structure
3913
+ - Search for specific functions, classes, or variables
3914
+ - Understand how a feature is implemented
3915
+
3916
+ The search agent will explore the codebase and return a summary of findings.
3917
+ This is more thorough than a simple grep because it can follow references and understand context.
3918
+
3919
+ Examples:
3920
+ - "Find all React components that use the useState hook"
3921
+ - "Where is the authentication logic implemented?"
3922
+ - "Find all API routes and their handlers"
3923
+ - "Search for usages of the UserService class"`,
3924
+ inputSchema: z9.object({
3925
+ query: z9.string().describe("What to search for. Be specific about what you're looking for."),
3926
+ context: z9.string().optional().describe("Optional additional context about why you need this information.")
3927
+ }),
3928
+ execute: async ({ query, context }, toolOptions) => {
3929
+ const toolCallId = toolOptions.toolCallId || `search_${Date.now()}`;
3930
+ await options.onProgress?.({
3931
+ status: "started",
3932
+ subagentId: toolCallId
3933
+ });
3934
+ try {
3935
+ const subagent = createSearchSubagent();
3936
+ const fullTask = context ? `${query}
3937
+
3938
+ Context: ${context}` : query;
3939
+ const result = await subagent.run({
3940
+ task: fullTask,
3941
+ sessionId: options.sessionId,
3942
+ toolCallId,
3943
+ workingDirectory: options.workingDirectory,
3944
+ onProgress: async (event) => {
3945
+ if (event.type === "step" && event.step) {
3946
+ await options.onProgress?.({
3947
+ status: "step",
3948
+ subagentId: event.subagentId,
3949
+ stepType: event.step.type,
3950
+ stepContent: event.step.content,
3951
+ toolName: event.step.toolName,
3952
+ toolInput: event.step.toolInput,
3953
+ toolOutput: event.step.toolOutput
3954
+ });
3955
+ } else if (event.type === "complete") {
3956
+ await options.onProgress?.({
3957
+ status: "complete",
3958
+ subagentId: event.subagentId,
3959
+ result: event.result
3960
+ });
3961
+ } else if (event.type === "error") {
3962
+ await options.onProgress?.({
3963
+ status: "error",
3964
+ subagentId: event.subagentId,
3965
+ error: event.error
3966
+ });
3967
+ }
3968
+ }
3969
+ });
3970
+ if (!result.success) {
2844
3971
  return {
2845
- success: true,
2846
- message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
2847
- files: [],
2848
- totalErrors: 0,
2849
- totalWarnings: 0
3972
+ success: false,
3973
+ error: result.error || "Search failed",
3974
+ executionId: result.executionId
2850
3975
  };
2851
3976
  }
2852
- await Promise.all(
2853
- filesToCheck.map((file) => touchFile(file, true))
2854
- );
2855
- const diagnosticsMap = {};
2856
- for (const file of filesToCheck) {
2857
- const diagnostics = await getDiagnostics(file);
2858
- if (diagnostics.length > 0) {
2859
- diagnosticsMap[file] = diagnostics;
3977
+ const searchResult = result.result;
3978
+ let formattedResult = `## Search Results
3979
+
3980
+ `;
3981
+ formattedResult += `**Summary:** ${searchResult.summary}
3982
+
3983
+ `;
3984
+ if (searchResult.findings.length > 0) {
3985
+ formattedResult += `### Key Findings (${searchResult.findings.length} items)
3986
+
3987
+ `;
3988
+ for (const finding of searchResult.findings) {
3989
+ if (finding.type === "match") {
3990
+ formattedResult += `- **${finding.path}:${finding.lineNumber}** - ${truncateOutput(finding.content || "", 200)}
3991
+ `;
3992
+ } else if (finding.type === "file") {
3993
+ formattedResult += `- **${finding.path}** ${finding.context ? `(${finding.context})` : ""}
3994
+ `;
3995
+ }
2860
3996
  }
2861
3997
  }
2862
- return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
3998
+ formattedResult += `
3999
+ **Stats:** ${searchResult.matchCount} matches across ${searchResult.filesSearched} files searched`;
4000
+ return {
4001
+ success: true,
4002
+ query: searchResult.query,
4003
+ summary: searchResult.summary,
4004
+ findings: searchResult.findings,
4005
+ matchCount: searchResult.matchCount,
4006
+ filesSearched: searchResult.filesSearched,
4007
+ formattedResult: truncateOutput(formattedResult, MAX_RESULT_CHARS),
4008
+ executionId: result.executionId,
4009
+ stepsCount: result.steps.length
4010
+ };
2863
4011
  } catch (error) {
4012
+ await options.onProgress?.({
4013
+ status: "error",
4014
+ error: error.message
4015
+ });
2864
4016
  return {
2865
4017
  success: false,
2866
4018
  error: error.message
@@ -2869,84 +4021,6 @@ Working directory: ${options.workingDirectory}`,
2869
4021
  }
2870
4022
  });
2871
4023
  }
2872
- function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
2873
- let totalErrors = 0;
2874
- let totalWarnings = 0;
2875
- let totalInfo = 0;
2876
- const files = [];
2877
- for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
2878
- const relativePath = relative4(workingDirectory, filePath);
2879
- let fileErrors = 0;
2880
- let fileWarnings = 0;
2881
- const formattedDiagnostics = diagnostics.map((d) => {
2882
- const severity = getSeverityString(d.severity);
2883
- if (d.severity === 1 /* Error */) {
2884
- fileErrors++;
2885
- totalErrors++;
2886
- } else if (d.severity === 2 /* Warning */) {
2887
- fileWarnings++;
2888
- totalWarnings++;
2889
- } else {
2890
- totalInfo++;
2891
- }
2892
- return {
2893
- severity,
2894
- line: d.range.start.line + 1,
2895
- column: d.range.start.character + 1,
2896
- message: d.message,
2897
- source: d.source,
2898
- code: d.code
2899
- };
2900
- });
2901
- files.push({
2902
- path: filePath,
2903
- relativePath,
2904
- errors: fileErrors,
2905
- warnings: fileWarnings,
2906
- diagnostics: formattedDiagnostics
2907
- });
2908
- }
2909
- files.sort((a, b) => b.errors - a.errors);
2910
- const hasIssues = totalErrors > 0 || totalWarnings > 0;
2911
- return {
2912
- success: true,
2913
- message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
2914
- files,
2915
- totalErrors,
2916
- totalWarnings,
2917
- totalInfo,
2918
- summary: hasIssues ? formatSummary(files) : void 0
2919
- };
2920
- }
2921
- function getSeverityString(severity) {
2922
- switch (severity) {
2923
- case 1 /* Error */:
2924
- return "error";
2925
- case 2 /* Warning */:
2926
- return "warning";
2927
- case 3 /* Information */:
2928
- return "info";
2929
- case 4 /* Hint */:
2930
- return "hint";
2931
- default:
2932
- return "error";
2933
- }
2934
- }
2935
- function formatSummary(files) {
2936
- const lines = [];
2937
- for (const file of files) {
2938
- lines.push(`
2939
- ${file.relativePath}:`);
2940
- for (const d of file.diagnostics.slice(0, 10)) {
2941
- const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
2942
- lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
2943
- }
2944
- if (file.diagnostics.length > 10) {
2945
- lines.push(` ... and ${file.diagnostics.length - 10} more`);
2946
- }
2947
- }
2948
- return lines.join("\n");
2949
- }
2950
4024
 
2951
4025
  // src/tools/index.ts
2952
4026
  function createTools(options) {
@@ -2963,7 +4037,8 @@ function createTools(options) {
2963
4037
  write_file: createWriteFileTool({
2964
4038
  workingDirectory: options.workingDirectory,
2965
4039
  sessionId: options.sessionId,
2966
- enableLSP: options.enableLSP ?? true
4040
+ enableLSP: options.enableLSP ?? true,
4041
+ onProgress: options.onWriteFileProgress
2967
4042
  }),
2968
4043
  todo: createTodoTool({
2969
4044
  sessionId: options.sessionId
@@ -2974,15 +4049,20 @@ function createTools(options) {
2974
4049
  }),
2975
4050
  linter: createLinterTool({
2976
4051
  workingDirectory: options.workingDirectory
4052
+ }),
4053
+ search: createSearchTool({
4054
+ sessionId: options.sessionId,
4055
+ workingDirectory: options.workingDirectory,
4056
+ onProgress: options.onSearchProgress
2977
4057
  })
2978
4058
  };
2979
4059
  }
2980
4060
 
2981
4061
  // src/agent/context.ts
2982
- import { generateText } from "ai";
2983
- import { gateway } from "@ai-sdk/gateway";
4062
+ import { generateText as generateText2 } from "ai";
2984
4063
 
2985
4064
  // src/agent/prompts.ts
4065
+ init_skills();
2986
4066
  import os from "os";
2987
4067
  function getSearchInstructions() {
2988
4068
  const platform3 = process.platform;
@@ -3001,9 +4081,33 @@ function getSearchInstructions() {
3001
4081
  - **If ripgrep (\`rg\`) is installed**: \`rg "pattern" -t ts src/\` - faster and respects .gitignore`;
3002
4082
  }
3003
4083
  async function buildSystemPrompt(options) {
3004
- const { workingDirectory, skillsDirectories, sessionId, customInstructions } = options;
3005
- const skills = await loadAllSkills(skillsDirectories);
3006
- const skillsContext = formatSkillsForContext(skills);
4084
+ const {
4085
+ workingDirectory,
4086
+ skillsDirectories,
4087
+ sessionId,
4088
+ discoveredSkills,
4089
+ activeFiles = [],
4090
+ customInstructions
4091
+ } = options;
4092
+ let alwaysLoadedContent = "";
4093
+ let globMatchedContent = "";
4094
+ let agentsMdContent = "";
4095
+ let onDemandSkillsContext = "";
4096
+ if (discoveredSkills) {
4097
+ const { always, onDemand, all } = await loadAllSkillsFromDiscovered(discoveredSkills);
4098
+ alwaysLoadedContent = formatAlwaysLoadedSkills(always);
4099
+ onDemandSkillsContext = formatSkillsForContext(onDemand);
4100
+ const agentsMd = await loadAgentsMd(discoveredSkills.agentsMdPath);
4101
+ agentsMdContent = formatAgentsMdContent(agentsMd);
4102
+ if (activeFiles.length > 0) {
4103
+ const globMatched = await getGlobMatchedSkills(all, activeFiles, workingDirectory);
4104
+ globMatchedContent = formatGlobMatchedSkills(globMatched);
4105
+ }
4106
+ } else {
4107
+ const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
4108
+ const skills = await loadAllSkills2(skillsDirectories);
4109
+ onDemandSkillsContext = formatSkillsForContext(skills);
4110
+ }
3007
4111
  const todos = todoQueries.getBySession(sessionId);
3008
4112
  const todosContext = formatTodosForContext(todos);
3009
4113
  const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
@@ -3024,6 +4128,7 @@ You have access to powerful tools for:
3024
4128
  - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
3025
4129
  - **todo**: Manage your task list to track progress on complex operations
3026
4130
  - **load_skill**: Load specialized knowledge documents for specific tasks
4131
+ - **search**: Semantic search using a subagent - for exploratory questions and finding code by meaning
3027
4132
 
3028
4133
 
3029
4134
  IMPORTANT: If you have zero context of where you are working, always explore it first to understand the structure before doing things for the user.
@@ -3100,6 +4205,9 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3100
4205
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
3101
4206
  - After making changes, use the \`linter\` tool to check for type errors and lint issues
3102
4207
  - The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
4208
+ - If the user asks to write/create a file, always use \`write_file\` rather than printing the full contents
4209
+ - If the user requests a file but does not provide a path, choose a sensible default (e.g. \`index.html\`) and proceed
4210
+ - For large content (hundreds of lines), avoid placing it in chat output; write to a file instead
3103
4211
 
3104
4212
  ### Linter Tool
3105
4213
  The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
@@ -3111,6 +4219,30 @@ linter({ paths: ["src/"] }) // Check all files in a directory
3111
4219
  Use this proactively after making code changes to catch errors early.
3112
4220
 
3113
4221
  ### Searching and Exploration
4222
+
4223
+ **Choose the right search approach:**
4224
+
4225
+ 1. **Use the \`search\` tool (subagent)** for:
4226
+ - Semantic/exploratory questions: "How does authentication work?", "Where is user data processed?"
4227
+ - Finding code by meaning or concept, not exact text
4228
+ - Understanding how features are implemented across multiple files
4229
+ - Exploring unfamiliar parts of the codebase
4230
+ - Questions like "where", "how", "what does X do"
4231
+
4232
+ The search subagent is a mini-agent that intelligently explores the codebase, reads relevant files, and returns a summary of what it found. It's best for understanding and discovery.
4233
+
4234
+ 2. **Use direct commands (grep/rg, find)** for:
4235
+ - Exact string matches: \`rg "functionName"\`, \`rg "class MyClass"\`
4236
+ - Finding files by name: \`find . -name "*.config.ts"\`
4237
+ - Simple pattern matching when you know exactly what you're looking for
4238
+ - Counting occurrences or listing all matches
4239
+
4240
+ **Examples:**
4241
+ - "Where is the API authentication handled?" \u2192 Use \`search\` tool
4242
+ - "Find all usages of getUserById" \u2192 Use \`rg "getUserById"\`
4243
+ - "How does the payment flow work?" \u2192 Use \`search\` tool
4244
+ - "Find files named config" \u2192 Use \`find . -name "*config*"\`
4245
+
3114
4246
  ${searchInstructions}
3115
4247
 
3116
4248
  ###Follow these principles when designing and implementing software:
@@ -3133,11 +4265,11 @@ ${searchInstructions}
3133
4265
  16. **Diversity** \u2014 Distrust all claims for "one true way"
3134
4266
  17. **Extensibility** \u2014 Design for the future, because it will be here sooner than you think
3135
4267
 
3136
- ###Follow these ruls to be a good agent for the user:
4268
+ ### Follow these rules to be a good agent for the user:
3137
4269
 
3138
- 1. Understand first - Read relevant files before making any changes. Use search tools to explore the codebase and understand the existing patterns and structure.
4270
+ 1. Understand first - Read relevant files before making any changes. Use the \`search\` tool for exploratory questions about how things work, and direct searches (grep/rg) for finding exact strings or file names.
3139
4271
  2. Plan for complexity - If the task involves 3+ steps or has meaningful trade-offs, create a todo list to track progress before implementing.
3140
- 3. Use the right tools - Have specialized tools for reading files, editing code, searching by pattern , running terminal commands, and more. Prefer these over raw shell commands.
4272
+ 3. Use the right tools - Have specialized tools for reading files, editing code, semantic search via subagents, and running terminal commands. Prefer these over raw shell commands.
3141
4273
  4. Work efficiently - When need to do multiple independent things (like reading several files), do them in parallel rather than one at a time.
3142
4274
  5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.
3143
4275
  6. Verify my work - After making changes, check for linter errors and fix any introduced.
@@ -3150,8 +4282,14 @@ ${searchInstructions}
3150
4282
  - Ask clarifying questions when requirements are ambiguous
3151
4283
  - Report progress on multi-step tasks
3152
4284
 
3153
- ## Skills
3154
- ${skillsContext}
4285
+ ${agentsMdContent}
4286
+
4287
+ ${alwaysLoadedContent}
4288
+
4289
+ ${globMatchedContent}
4290
+
4291
+ ## On-Demand Skills
4292
+ ${onDemandSkillsContext}
3155
4293
 
3156
4294
  ## Current Task List
3157
4295
  ${todosContext}
@@ -3246,8 +4384,8 @@ ${this.summary}`
3246
4384
  try {
3247
4385
  const config = getConfig();
3248
4386
  const summaryPrompt = createSummaryPrompt(historyText);
3249
- const result = await generateText({
3250
- model: gateway(config.defaultModel),
4387
+ const result = await generateText2({
4388
+ model: resolveModel(config.defaultModel),
3251
4389
  prompt: summaryPrompt
3252
4390
  });
3253
4391
  this.summary = result.text;
@@ -3317,7 +4455,9 @@ var Agent = class _Agent {
3317
4455
  sessionId: this.session.id,
3318
4456
  workingDirectory: this.session.workingDirectory,
3319
4457
  skillsDirectories: config.resolvedSkillsDirectories,
3320
- onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0
4458
+ onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
4459
+ onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
4460
+ onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "search", data: progress }) : void 0
3321
4461
  });
3322
4462
  }
3323
4463
  /**
@@ -3426,28 +4566,33 @@ ${prompt}` });
3426
4566
  const systemPrompt = await buildSystemPrompt({
3427
4567
  workingDirectory: this.session.workingDirectory,
3428
4568
  skillsDirectories: config.resolvedSkillsDirectories,
3429
- sessionId: this.session.id
4569
+ sessionId: this.session.id,
4570
+ discoveredSkills: config.discoveredSkills,
4571
+ // TODO: Pass activeFiles from client for glob matching
4572
+ activeFiles: []
3430
4573
  });
3431
4574
  const messages2 = await this.context.getMessages();
3432
4575
  const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
3433
4576
  const wrappedTools = this.wrapToolsWithApproval(options, tools);
3434
- const stream = streamText({
3435
- model: gateway2(this.session.model),
4577
+ const useAnthropic = isAnthropicModel(this.session.model);
4578
+ const stream = streamText2({
4579
+ model: resolveModel(this.session.model),
3436
4580
  system: systemPrompt,
3437
4581
  messages: messages2,
3438
4582
  tools: wrappedTools,
3439
- stopWhen: stepCountIs(500),
4583
+ stopWhen: stepCountIs2(500),
3440
4584
  // Forward abort signal if provided
3441
4585
  abortSignal: options.abortSignal,
3442
4586
  // Enable extended thinking/reasoning for models that support it
3443
- providerOptions: {
4587
+ providerOptions: useAnthropic ? {
3444
4588
  anthropic: {
4589
+ toolStreaming: true,
3445
4590
  thinking: {
3446
4591
  type: "enabled",
3447
4592
  budgetTokens: 1e4
3448
4593
  }
3449
4594
  }
3450
- },
4595
+ } : void 0,
3451
4596
  onStepFinish: async (step) => {
3452
4597
  options.onStepFinish?.(step);
3453
4598
  },
@@ -3477,26 +4622,29 @@ ${prompt}` });
3477
4622
  const systemPrompt = await buildSystemPrompt({
3478
4623
  workingDirectory: this.session.workingDirectory,
3479
4624
  skillsDirectories: config.resolvedSkillsDirectories,
3480
- sessionId: this.session.id
4625
+ sessionId: this.session.id,
4626
+ discoveredSkills: config.discoveredSkills,
4627
+ activeFiles: []
3481
4628
  });
3482
4629
  const messages2 = await this.context.getMessages();
3483
4630
  const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
3484
4631
  const wrappedTools = this.wrapToolsWithApproval(options, tools);
3485
- const result = await generateText2({
3486
- model: gateway2(this.session.model),
4632
+ const useAnthropic = isAnthropicModel(this.session.model);
4633
+ const result = await generateText3({
4634
+ model: resolveModel(this.session.model),
3487
4635
  system: systemPrompt,
3488
4636
  messages: messages2,
3489
4637
  tools: wrappedTools,
3490
- stopWhen: stepCountIs(500),
4638
+ stopWhen: stepCountIs2(500),
3491
4639
  // Enable extended thinking/reasoning for models that support it
3492
- providerOptions: {
4640
+ providerOptions: useAnthropic ? {
3493
4641
  anthropic: {
3494
4642
  thinking: {
3495
4643
  type: "enabled",
3496
4644
  budgetTokens: 1e4
3497
4645
  }
3498
4646
  }
3499
- }
4647
+ } : void 0
3500
4648
  });
3501
4649
  const responseMessages = result.response.messages;
3502
4650
  this.context.addResponseMessages(responseMessages);
@@ -3518,11 +4666,11 @@ ${prompt}` });
3518
4666
  wrappedTools[name] = originalTool;
3519
4667
  continue;
3520
4668
  }
3521
- wrappedTools[name] = tool7({
4669
+ wrappedTools[name] = tool9({
3522
4670
  description: originalTool.description || "",
3523
- inputSchema: originalTool.inputSchema || z8.object({}),
4671
+ inputSchema: originalTool.inputSchema || z10.object({}),
3524
4672
  execute: async (input, toolOptions) => {
3525
- const toolCallId = toolOptions.toolCallId || nanoid3();
4673
+ const toolCallId = toolOptions.toolCallId || nanoid4();
3526
4674
  const execution = toolExecutionQueries.create({
3527
4675
  sessionId: this.session.id,
3528
4676
  toolName: name,
@@ -3534,8 +4682,8 @@ ${prompt}` });
3534
4682
  this.pendingApprovals.set(toolCallId, execution);
3535
4683
  options.onApprovalRequired?.(execution);
3536
4684
  sessionQueries.updateStatus(this.session.id, "waiting");
3537
- const approved = await new Promise((resolve9) => {
3538
- approvalResolvers.set(toolCallId, { resolve: resolve9, sessionId: this.session.id });
4685
+ const approved = await new Promise((resolve10) => {
4686
+ approvalResolvers.set(toolCallId, { resolve: resolve10, sessionId: this.session.id });
3539
4687
  });
3540
4688
  const resolverData = approvalResolvers.get(toolCallId);
3541
4689
  approvalResolvers.delete(toolCallId);
@@ -3634,32 +4782,33 @@ import { Hono as Hono5 } from "hono";
3634
4782
  import { serve } from "@hono/node-server";
3635
4783
  import { cors } from "hono/cors";
3636
4784
  import { logger } from "hono/logger";
3637
- import { existsSync as existsSync12, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
3638
- import { resolve as resolve8, dirname as dirname6, join as join5 } from "path";
4785
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
4786
+ import { resolve as resolve9, dirname as dirname7, join as join7 } from "path";
3639
4787
  import { spawn as spawn2 } from "child_process";
3640
4788
  import { createServer as createNetServer } from "net";
3641
- import { fileURLToPath as fileURLToPath2 } from "url";
4789
+ import { fileURLToPath as fileURLToPath3 } from "url";
3642
4790
 
3643
4791
  // src/server/routes/sessions.ts
3644
4792
  import { Hono } from "hono";
3645
4793
  import { zValidator } from "@hono/zod-validator";
3646
- import { z as z9 } from "zod";
3647
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
3648
- import { join as join3, basename as basename2, extname as extname5 } from "path";
3649
- import { nanoid as nanoid4 } from "nanoid";
4794
+ import { z as z11 } from "zod";
4795
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
4796
+ import { readdir as readdir4 } from "fs/promises";
4797
+ import { join as join4, basename as basename2, extname as extname6, relative as relative7 } from "path";
4798
+ import { nanoid as nanoid5 } from "nanoid";
3650
4799
  var sessions2 = new Hono();
3651
- var createSessionSchema = z9.object({
3652
- name: z9.string().optional(),
3653
- workingDirectory: z9.string().optional(),
3654
- model: z9.string().optional(),
3655
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4800
+ var createSessionSchema = z11.object({
4801
+ name: z11.string().optional(),
4802
+ workingDirectory: z11.string().optional(),
4803
+ model: z11.string().optional(),
4804
+ toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
3656
4805
  });
3657
- var paginationQuerySchema = z9.object({
3658
- limit: z9.string().optional(),
3659
- offset: z9.string().optional()
4806
+ var paginationQuerySchema = z11.object({
4807
+ limit: z11.string().optional(),
4808
+ offset: z11.string().optional()
3660
4809
  });
3661
- var messagesQuerySchema = z9.object({
3662
- limit: z9.string().optional()
4810
+ var messagesQuerySchema = z11.object({
4811
+ limit: z11.string().optional()
3663
4812
  });
3664
4813
  sessions2.get(
3665
4814
  "/",
@@ -3798,10 +4947,10 @@ sessions2.get("/:id/tools", async (c) => {
3798
4947
  count: executions.length
3799
4948
  });
3800
4949
  });
3801
- var updateSessionSchema = z9.object({
3802
- model: z9.string().optional(),
3803
- name: z9.string().optional(),
3804
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4950
+ var updateSessionSchema = z11.object({
4951
+ model: z11.string().optional(),
4952
+ name: z11.string().optional(),
4953
+ toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
3805
4954
  });
3806
4955
  sessions2.patch(
3807
4956
  "/:id",
@@ -4000,11 +5149,11 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
4000
5149
  });
4001
5150
  function getAttachmentsDir(sessionId) {
4002
5151
  const appDataDir = getAppDataDirectory();
4003
- return join3(appDataDir, "attachments", sessionId);
5152
+ return join4(appDataDir, "attachments", sessionId);
4004
5153
  }
4005
5154
  function ensureAttachmentsDir(sessionId) {
4006
5155
  const dir = getAttachmentsDir(sessionId);
4007
- if (!existsSync10(dir)) {
5156
+ if (!existsSync11(dir)) {
4008
5157
  mkdirSync3(dir, { recursive: true });
4009
5158
  }
4010
5159
  return dir;
@@ -4016,12 +5165,12 @@ sessions2.get("/:id/attachments", async (c) => {
4016
5165
  return c.json({ error: "Session not found" }, 404);
4017
5166
  }
4018
5167
  const dir = getAttachmentsDir(sessionId);
4019
- if (!existsSync10(dir)) {
5168
+ if (!existsSync11(dir)) {
4020
5169
  return c.json({ sessionId, attachments: [], count: 0 });
4021
5170
  }
4022
5171
  const files = readdirSync(dir);
4023
5172
  const attachments = files.map((filename) => {
4024
- const filePath = join3(dir, filename);
5173
+ const filePath = join4(dir, filename);
4025
5174
  const stats = statSync(filePath);
4026
5175
  return {
4027
5176
  id: filename.split("_")[0],
@@ -4053,10 +5202,10 @@ sessions2.post("/:id/attachments", async (c) => {
4053
5202
  return c.json({ error: "No file provided" }, 400);
4054
5203
  }
4055
5204
  const dir = ensureAttachmentsDir(sessionId);
4056
- const id = nanoid4(10);
4057
- const ext = extname5(file.name) || "";
5205
+ const id = nanoid5(10);
5206
+ const ext = extname6(file.name) || "";
4058
5207
  const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4059
- const filePath = join3(dir, safeFilename);
5208
+ const filePath = join4(dir, safeFilename);
4060
5209
  const arrayBuffer = await file.arrayBuffer();
4061
5210
  writeFileSync2(filePath, Buffer.from(arrayBuffer));
4062
5211
  return c.json({
@@ -4079,10 +5228,10 @@ sessions2.post("/:id/attachments", async (c) => {
4079
5228
  return c.json({ error: "Missing filename or data" }, 400);
4080
5229
  }
4081
5230
  const dir = ensureAttachmentsDir(sessionId);
4082
- const id = nanoid4(10);
4083
- const ext = extname5(body.filename) || "";
5231
+ const id = nanoid5(10);
5232
+ const ext = extname6(body.filename) || "";
4084
5233
  const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
4085
- const filePath = join3(dir, safeFilename);
5234
+ const filePath = join4(dir, safeFilename);
4086
5235
  let base64Data = body.data;
4087
5236
  if (base64Data.includes(",")) {
4088
5237
  base64Data = base64Data.split(",")[1];
@@ -4111,7 +5260,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
4111
5260
  return c.json({ error: "Session not found" }, 404);
4112
5261
  }
4113
5262
  const dir = getAttachmentsDir(sessionId);
4114
- if (!existsSync10(dir)) {
5263
+ if (!existsSync11(dir)) {
4115
5264
  return c.json({ error: "Attachment not found" }, 404);
4116
5265
  }
4117
5266
  const files = readdirSync(dir);
@@ -4119,17 +5268,154 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
4119
5268
  if (!file) {
4120
5269
  return c.json({ error: "Attachment not found" }, 404);
4121
5270
  }
4122
- const filePath = join3(dir, file);
5271
+ const filePath = join4(dir, file);
4123
5272
  unlinkSync(filePath);
4124
5273
  return c.json({ success: true, id: attachmentId });
4125
5274
  });
5275
+ var filesQuerySchema = z11.object({
5276
+ query: z11.string().optional(),
5277
+ // Filter query (e.g., "src/com" to match "src/components")
5278
+ limit: z11.string().optional()
5279
+ // Max results (default 50)
5280
+ });
5281
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
5282
+ "node_modules",
5283
+ ".git",
5284
+ ".next",
5285
+ "dist",
5286
+ "build",
5287
+ ".turbo",
5288
+ ".cache",
5289
+ "coverage",
5290
+ "__pycache__",
5291
+ ".pytest_cache",
5292
+ "venv",
5293
+ ".venv",
5294
+ "target",
5295
+ // Rust
5296
+ ".idea",
5297
+ ".vscode"
5298
+ ]);
5299
+ var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
5300
+ ".pyc",
5301
+ ".pyo",
5302
+ ".class",
5303
+ ".o",
5304
+ ".obj",
5305
+ ".exe",
5306
+ ".dll",
5307
+ ".so",
5308
+ ".dylib"
5309
+ ]);
5310
+ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = []) {
5311
+ if (results.length >= limit) {
5312
+ return results;
5313
+ }
5314
+ try {
5315
+ const entries = await readdir4(currentDir, { withFileTypes: true });
5316
+ const queryLower = query.toLowerCase();
5317
+ for (const entry of entries) {
5318
+ if (results.length >= limit) break;
5319
+ const fullPath = join4(currentDir, entry.name);
5320
+ const relativePath = relative7(baseDir, fullPath);
5321
+ if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
5322
+ continue;
5323
+ }
5324
+ if (entry.name.startsWith(".")) {
5325
+ continue;
5326
+ }
5327
+ const ext = extname6(entry.name).toLowerCase();
5328
+ if (IGNORED_EXTENSIONS.has(ext)) {
5329
+ continue;
5330
+ }
5331
+ const matchesQuery = !query || relativePath.toLowerCase().includes(queryLower) || entry.name.toLowerCase().includes(queryLower);
5332
+ if (entry.isDirectory()) {
5333
+ if (matchesQuery) {
5334
+ results.push({
5335
+ path: relativePath,
5336
+ name: entry.name,
5337
+ type: "folder"
5338
+ });
5339
+ }
5340
+ const shouldRecurse = !query || relativePath.toLowerCase().startsWith(queryLower) || queryLower.startsWith(relativePath.toLowerCase());
5341
+ if (shouldRecurse && results.length < limit) {
5342
+ await listWorkspaceFiles(baseDir, fullPath, query, limit, results);
5343
+ }
5344
+ } else if (entry.isFile()) {
5345
+ if (matchesQuery) {
5346
+ results.push({
5347
+ path: relativePath,
5348
+ name: entry.name,
5349
+ type: "file",
5350
+ extension: ext || void 0
5351
+ });
5352
+ }
5353
+ }
5354
+ }
5355
+ } catch {
5356
+ }
5357
+ return results;
5358
+ }
5359
+ sessions2.get(
5360
+ "/:id/files",
5361
+ zValidator("query", filesQuerySchema),
5362
+ async (c) => {
5363
+ const sessionId = c.req.param("id");
5364
+ const { query = "", limit: limitStr = "50" } = c.req.valid("query");
5365
+ const limit = Math.min(parseInt(limitStr) || 50, 100);
5366
+ const session = sessionQueries.getById(sessionId);
5367
+ if (!session) {
5368
+ return c.json({ error: "Session not found" }, 404);
5369
+ }
5370
+ const workingDirectory = session.workingDirectory;
5371
+ if (!existsSync11(workingDirectory)) {
5372
+ return c.json({
5373
+ sessionId,
5374
+ workingDirectory,
5375
+ files: [],
5376
+ count: 0,
5377
+ error: "Working directory does not exist"
5378
+ });
5379
+ }
5380
+ try {
5381
+ const files = await listWorkspaceFiles(
5382
+ workingDirectory,
5383
+ workingDirectory,
5384
+ query,
5385
+ limit
5386
+ );
5387
+ files.sort((a, b) => {
5388
+ if (a.type !== b.type) {
5389
+ return a.type === "folder" ? -1 : 1;
5390
+ }
5391
+ return a.path.localeCompare(b.path);
5392
+ });
5393
+ return c.json({
5394
+ sessionId,
5395
+ workingDirectory,
5396
+ files,
5397
+ count: files.length,
5398
+ query
5399
+ });
5400
+ } catch (err) {
5401
+ console.error("Failed to list workspace files:", err);
5402
+ return c.json({
5403
+ error: "Failed to list files",
5404
+ sessionId,
5405
+ workingDirectory,
5406
+ files: [],
5407
+ count: 0
5408
+ }, 500);
5409
+ }
5410
+ }
5411
+ );
4126
5412
 
4127
5413
  // src/server/routes/agents.ts
4128
5414
  import { Hono as Hono2 } from "hono";
4129
5415
  import { zValidator as zValidator2 } from "@hono/zod-validator";
4130
- import { z as z10 } from "zod";
4131
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
4132
- import { join as join4 } from "path";
5416
+ import { z as z12 } from "zod";
5417
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
5418
+ import { join as join5 } from "path";
4133
5419
 
4134
5420
  // src/server/resumable-stream.ts
4135
5421
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -4204,41 +5490,107 @@ var streamContext = createResumableStreamContext({
4204
5490
  });
4205
5491
 
4206
5492
  // src/server/routes/agents.ts
4207
- import { nanoid as nanoid5 } from "nanoid";
5493
+ import { nanoid as nanoid6 } from "nanoid";
5494
+ var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
5495
+ var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
5496
+ var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
5497
+ function sanitizeToolInput(toolName, input) {
5498
+ if (toolName !== "write_file" || !input || typeof input !== "object") {
5499
+ return input;
5500
+ }
5501
+ const data = input;
5502
+ let changed = false;
5503
+ const next = { ...data };
5504
+ const content = typeof data.content === "string" ? data.content : void 0;
5505
+ if (content && content.length > MAX_TOOL_INPUT_LENGTH) {
5506
+ next.content = `${content.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5507
+ ... (truncated)`;
5508
+ next.contentLength = content.length;
5509
+ next.contentTruncated = true;
5510
+ changed = true;
5511
+ }
5512
+ const oldString = typeof data.old_string === "string" ? data.old_string : void 0;
5513
+ if (oldString && oldString.length > MAX_TOOL_INPUT_LENGTH) {
5514
+ next.old_string = `${oldString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5515
+ ... (truncated)`;
5516
+ next.oldStringLength = oldString.length;
5517
+ next.oldStringTruncated = true;
5518
+ changed = true;
5519
+ }
5520
+ const newString = typeof data.new_string === "string" ? data.new_string : void 0;
5521
+ if (newString && newString.length > MAX_TOOL_INPUT_LENGTH) {
5522
+ next.new_string = `${newString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5523
+ ... (truncated)`;
5524
+ next.newStringLength = newString.length;
5525
+ next.newStringTruncated = true;
5526
+ changed = true;
5527
+ }
5528
+ if (changed) {
5529
+ console.log("[TOOL-INPUT] Truncated write_file input for streaming payload size");
5530
+ }
5531
+ return changed ? next : input;
5532
+ }
5533
+ function buildToolArgsText(input) {
5534
+ try {
5535
+ return JSON.stringify(input ?? {});
5536
+ } catch {
5537
+ return "{}";
5538
+ }
5539
+ }
5540
+ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId, toolName, input) {
5541
+ if (toolCallStarts.has(toolCallId)) return;
5542
+ toolCallStarts.add(toolCallId);
5543
+ await writeSSE(JSON.stringify({
5544
+ type: "tool-input-start",
5545
+ toolCallId,
5546
+ toolName
5547
+ }));
5548
+ if (toolName !== "write_file") return;
5549
+ const argsText = buildToolArgsText(input);
5550
+ for (let i = 0; i < argsText.length; i += MAX_TOOL_ARGS_CHUNK) {
5551
+ const chunk = argsText.slice(i, i + MAX_TOOL_ARGS_CHUNK);
5552
+ await writeSSE(JSON.stringify({
5553
+ type: "tool-input-delta",
5554
+ toolCallId,
5555
+ argsTextDelta: chunk
5556
+ }));
5557
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
5558
+ }
5559
+ }
4208
5560
  var agents = new Hono2();
4209
- var attachmentSchema = z10.object({
4210
- type: z10.enum(["image", "file"]),
4211
- data: z10.string(),
5561
+ var attachmentSchema = z12.object({
5562
+ type: z12.enum(["image", "file"]),
5563
+ data: z12.string(),
4212
5564
  // base64 data URL or raw base64
4213
- mediaType: z10.string().optional(),
4214
- filename: z10.string().optional()
5565
+ mediaType: z12.string().optional(),
5566
+ filename: z12.string().optional()
4215
5567
  });
4216
- var runPromptSchema = z10.object({
4217
- prompt: z10.string(),
5568
+ var runPromptSchema = z12.object({
5569
+ prompt: z12.string(),
4218
5570
  // Can be empty if attachments are provided
4219
- attachments: z10.array(attachmentSchema).optional()
5571
+ attachments: z12.array(attachmentSchema).optional()
4220
5572
  }).refine(
4221
5573
  (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
4222
5574
  { message: "Either prompt or attachments must be provided" }
4223
5575
  );
4224
- var quickStartSchema = z10.object({
4225
- prompt: z10.string().min(1),
4226
- name: z10.string().optional(),
4227
- workingDirectory: z10.string().optional(),
4228
- model: z10.string().optional(),
4229
- toolApprovals: z10.record(z10.string(), z10.boolean()).optional()
5576
+ var quickStartSchema = z12.object({
5577
+ prompt: z12.string().min(1),
5578
+ name: z12.string().optional(),
5579
+ workingDirectory: z12.string().optional(),
5580
+ model: z12.string().optional(),
5581
+ toolApprovals: z12.record(z12.string(), z12.boolean()).optional()
4230
5582
  });
4231
- var rejectSchema = z10.object({
4232
- reason: z10.string().optional()
5583
+ var rejectSchema = z12.object({
5584
+ reason: z12.string().optional()
4233
5585
  }).optional();
4234
5586
  var streamAbortControllers = /* @__PURE__ */ new Map();
4235
5587
  function getAttachmentsDirectory(sessionId) {
4236
5588
  const appDataDir = getAppDataDirectory();
4237
- return join4(appDataDir, "attachments", sessionId);
5589
+ return join5(appDataDir, "attachments", sessionId);
4238
5590
  }
4239
5591
  function saveAttachmentToDisk(sessionId, attachment, index) {
4240
5592
  const attachmentsDir = getAttachmentsDirectory(sessionId);
4241
- if (!existsSync11(attachmentsDir)) {
5593
+ if (!existsSync12(attachmentsDir)) {
4242
5594
  mkdirSync4(attachmentsDir, { recursive: true });
4243
5595
  }
4244
5596
  let filename = attachment.filename;
@@ -4250,7 +5602,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
4250
5602
  if (base64Data.includes(",")) {
4251
5603
  base64Data = base64Data.split(",")[1];
4252
5604
  }
4253
- const filePath = join4(attachmentsDir, filename);
5605
+ const filePath = join5(attachmentsDir, filename);
4254
5606
  const buffer = Buffer.from(base64Data, "base64");
4255
5607
  writeFileSync3(filePath, buffer);
4256
5608
  return filePath;
@@ -4283,6 +5635,7 @@ function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
4283
5635
  const { readable, writable } = new TransformStream();
4284
5636
  const writer = writable.getWriter();
4285
5637
  let writerClosed = false;
5638
+ const toolCallStarts = /* @__PURE__ */ new Set();
4286
5639
  const abortController = new AbortController();
4287
5640
  streamAbortControllers.set(streamId, abortController);
4288
5641
  const writeSSE = async (data) => {
@@ -4391,11 +5744,32 @@ ${prompt}` });
4391
5744
  }));
4392
5745
  },
4393
5746
  onToolProgress: async (progress) => {
5747
+ const status = progress.data?.status || "no-status";
5748
+ const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
5749
+ const chunkIndex = progress.data?.chunkIndex;
5750
+ const chunkCount = progress.data?.chunkCount;
5751
+ console.log(
5752
+ "[TOOL-PROGRESS] Sending:",
5753
+ progress.toolName,
5754
+ status,
5755
+ contentLength !== void 0 ? `contentLength=${contentLength}` : "",
5756
+ chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
5757
+ );
4394
5758
  await writeSSE(JSON.stringify({
4395
5759
  type: "tool-progress",
4396
5760
  toolName: progress.toolName,
4397
5761
  data: progress.data
4398
5762
  }));
5763
+ if (progress.toolName === "write_file" && status === "content") {
5764
+ await writeSSE(JSON.stringify({
5765
+ type: "debug",
5766
+ label: "write-file-progress",
5767
+ contentLength,
5768
+ chunkIndex,
5769
+ chunkCount
5770
+ }));
5771
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
5772
+ }
4399
5773
  },
4400
5774
  onStepFinish: async () => {
4401
5775
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -4437,6 +5811,7 @@ ${prompt}` });
4437
5811
  toolCallId: p.toolCallId,
4438
5812
  toolName: p.toolName
4439
5813
  }));
5814
+ toolCallStarts.add(p.toolCallId);
4440
5815
  } else if (part.type === "tool-call-delta") {
4441
5816
  const p = part;
4442
5817
  await writeSSE(JSON.stringify({
@@ -4445,11 +5820,23 @@ ${prompt}` });
4445
5820
  argsTextDelta: p.argsTextDelta
4446
5821
  }));
4447
5822
  } else if (part.type === "tool-call") {
5823
+ await emitSyntheticToolStreaming(
5824
+ writeSSE,
5825
+ toolCallStarts,
5826
+ part.toolCallId,
5827
+ part.toolName,
5828
+ part.input
5829
+ );
4448
5830
  await writeSSE(JSON.stringify({
4449
5831
  type: "tool-input-available",
4450
5832
  toolCallId: part.toolCallId,
4451
5833
  toolName: part.toolName,
4452
- input: part.input
5834
+ input: sanitizeToolInput(part.toolName, part.input)
5835
+ }));
5836
+ await writeSSE(JSON.stringify({
5837
+ type: "debug",
5838
+ label: "tool-input-available",
5839
+ toolName: part.toolName
4453
5840
  }));
4454
5841
  } else if (part.type === "tool-result") {
4455
5842
  await writeSSE(JSON.stringify({
@@ -4568,7 +5955,7 @@ ${prompt}` });
4568
5955
  userMessageContent = prompt;
4569
5956
  }
4570
5957
  messageQueries.create(id, { role: "user", content: userMessageContent });
4571
- const streamId = `stream_${id}_${nanoid5(10)}`;
5958
+ const streamId = `stream_${id}_${nanoid6(10)}`;
4572
5959
  activeStreamQueries.create(id, streamId);
4573
5960
  const stream = await streamContext.resumableStream(
4574
5961
  streamId,
@@ -4765,13 +6152,14 @@ agents.post(
4765
6152
  sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
4766
6153
  });
4767
6154
  const session = agent.getSession();
4768
- const streamId = `stream_${session.id}_${nanoid5(10)}`;
6155
+ const streamId = `stream_${session.id}_${nanoid6(10)}`;
4769
6156
  await createCheckpoint(session.id, session.workingDirectory, 0);
4770
6157
  activeStreamQueries.create(session.id, streamId);
4771
6158
  const createQuickStreamProducer = () => {
4772
6159
  const { readable, writable } = new TransformStream();
4773
6160
  const writer = writable.getWriter();
4774
6161
  let writerClosed = false;
6162
+ const toolCallStarts = /* @__PURE__ */ new Set();
4775
6163
  const abortController = new AbortController();
4776
6164
  streamAbortControllers.set(streamId, abortController);
4777
6165
  const writeSSE = async (data) => {
@@ -4817,11 +6205,32 @@ agents.post(
4817
6205
  abortSignal: abortController.signal,
4818
6206
  // Use our managed abort controller, NOT client signal
4819
6207
  onToolProgress: async (progress) => {
6208
+ const status = progress.data?.status || "no-status";
6209
+ const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
6210
+ const chunkIndex = progress.data?.chunkIndex;
6211
+ const chunkCount = progress.data?.chunkCount;
6212
+ console.log(
6213
+ "[TOOL-PROGRESS] Sending:",
6214
+ progress.toolName,
6215
+ status,
6216
+ contentLength !== void 0 ? `contentLength=${contentLength}` : "",
6217
+ chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
6218
+ );
4820
6219
  await writeSSE(JSON.stringify({
4821
6220
  type: "tool-progress",
4822
6221
  toolName: progress.toolName,
4823
6222
  data: progress.data
4824
6223
  }));
6224
+ if (progress.toolName === "write_file" && status === "content") {
6225
+ await writeSSE(JSON.stringify({
6226
+ type: "debug",
6227
+ label: "write-file-progress",
6228
+ contentLength,
6229
+ chunkIndex,
6230
+ chunkCount
6231
+ }));
6232
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
6233
+ }
4825
6234
  },
4826
6235
  onStepFinish: async () => {
4827
6236
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -4863,6 +6272,7 @@ agents.post(
4863
6272
  toolCallId: p.toolCallId,
4864
6273
  toolName: p.toolName
4865
6274
  }));
6275
+ toolCallStarts.add(p.toolCallId);
4866
6276
  } else if (part.type === "tool-call-delta") {
4867
6277
  const p = part;
4868
6278
  await writeSSE(JSON.stringify({
@@ -4871,11 +6281,23 @@ agents.post(
4871
6281
  argsTextDelta: p.argsTextDelta
4872
6282
  }));
4873
6283
  } else if (part.type === "tool-call") {
6284
+ await emitSyntheticToolStreaming(
6285
+ writeSSE,
6286
+ toolCallStarts,
6287
+ part.toolCallId,
6288
+ part.toolName,
6289
+ part.input
6290
+ );
4874
6291
  await writeSSE(JSON.stringify({
4875
6292
  type: "tool-input-available",
4876
6293
  toolCallId: part.toolCallId,
4877
6294
  toolName: part.toolName,
4878
- input: part.input
6295
+ input: sanitizeToolInput(part.toolName, part.input)
6296
+ }));
6297
+ await writeSSE(JSON.stringify({
6298
+ type: "debug",
6299
+ label: "tool-input-available",
6300
+ toolName: part.toolName
4879
6301
  }));
4880
6302
  } else if (part.type === "tool-result") {
4881
6303
  await writeSSE(JSON.stringify({
@@ -4943,7 +6365,28 @@ agents.post(
4943
6365
  // src/server/routes/health.ts
4944
6366
  import { Hono as Hono3 } from "hono";
4945
6367
  import { zValidator as zValidator3 } from "@hono/zod-validator";
4946
- import { z as z11 } from "zod";
6368
+ import { z as z13 } from "zod";
6369
+ import { readFileSync as readFileSync3 } from "fs";
6370
+ import { fileURLToPath as fileURLToPath2 } from "url";
6371
+ import { dirname as dirname6, join as join6 } from "path";
6372
+ var __filename = fileURLToPath2(import.meta.url);
6373
+ var __dirname = dirname6(__filename);
6374
+ var packageJsonPath = join6(__dirname, "../../../package.json");
6375
+ var currentVersion = "0.0.0";
6376
+ var packageName = "sparkecoder";
6377
+ try {
6378
+ const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
6379
+ currentVersion = packageJson.version || "0.0.0";
6380
+ packageName = packageJson.name || "sparkecoder";
6381
+ } catch {
6382
+ try {
6383
+ const devPackageJsonPath = join6(__dirname, "../../package.json");
6384
+ const packageJson = JSON.parse(readFileSync3(devPackageJsonPath, "utf-8"));
6385
+ currentVersion = packageJson.version || "0.0.0";
6386
+ packageName = packageJson.name || "sparkecoder";
6387
+ } catch {
6388
+ }
6389
+ }
4947
6390
  var health = new Hono3();
4948
6391
  health.get("/", async (c) => {
4949
6392
  const config = getConfig();
@@ -4952,7 +6395,7 @@ health.get("/", async (c) => {
4952
6395
  const hasApiKey = gatewayKey?.configured ?? false;
4953
6396
  return c.json({
4954
6397
  status: "ok",
4955
- version: "0.1.0",
6398
+ version: currentVersion,
4956
6399
  uptime: process.uptime(),
4957
6400
  apiKeyConfigured: hasApiKey,
4958
6401
  config: {
@@ -4964,6 +6407,42 @@ health.get("/", async (c) => {
4964
6407
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4965
6408
  });
4966
6409
  });
6410
+ health.get("/version", async (c) => {
6411
+ let latestVersion = currentVersion;
6412
+ let updateAvailable = false;
6413
+ let error;
6414
+ try {
6415
+ const npmResponse = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
6416
+ headers: { "Accept": "application/json" },
6417
+ signal: AbortSignal.timeout(5e3)
6418
+ // 5 second timeout
6419
+ });
6420
+ if (npmResponse.ok) {
6421
+ const npmData = await npmResponse.json();
6422
+ latestVersion = npmData.version || currentVersion;
6423
+ const parseVersion = (v) => {
6424
+ const parts = v.replace(/^v/, "").split(".").map(Number);
6425
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
6426
+ };
6427
+ const current = parseVersion(currentVersion);
6428
+ const latest = parseVersion(latestVersion);
6429
+ updateAvailable = latest.major > current.major || latest.major === current.major && latest.minor > current.minor || latest.major === current.major && latest.minor === current.minor && latest.patch > current.patch;
6430
+ } else {
6431
+ error = `npm registry returned ${npmResponse.status}`;
6432
+ }
6433
+ } catch (err) {
6434
+ error = err instanceof Error ? err.message : "Failed to check for updates";
6435
+ }
6436
+ return c.json({
6437
+ packageName,
6438
+ currentVersion,
6439
+ latestVersion,
6440
+ updateAvailable,
6441
+ updateCommand: updateAvailable ? `npm install -g ${packageName}@latest` : null,
6442
+ error,
6443
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6444
+ });
6445
+ });
4967
6446
  health.get("/ready", async (c) => {
4968
6447
  try {
4969
6448
  getConfig();
@@ -4989,9 +6468,9 @@ health.get("/api-keys", async (c) => {
4989
6468
  supportedProviders: SUPPORTED_PROVIDERS
4990
6469
  });
4991
6470
  });
4992
- var setApiKeySchema = z11.object({
4993
- provider: z11.string(),
4994
- apiKey: z11.string().min(1)
6471
+ var setApiKeySchema = z13.object({
6472
+ provider: z13.string(),
6473
+ apiKey: z13.string().min(1)
4995
6474
  });
4996
6475
  health.post(
4997
6476
  "/api-keys",
@@ -5030,12 +6509,12 @@ health.delete("/api-keys/:provider", async (c) => {
5030
6509
  // src/server/routes/terminals.ts
5031
6510
  import { Hono as Hono4 } from "hono";
5032
6511
  import { zValidator as zValidator4 } from "@hono/zod-validator";
5033
- import { z as z12 } from "zod";
6512
+ import { z as z14 } from "zod";
5034
6513
  var terminals2 = new Hono4();
5035
- var spawnSchema = z12.object({
5036
- command: z12.string(),
5037
- cwd: z12.string().optional(),
5038
- name: z12.string().optional()
6514
+ var spawnSchema = z14.object({
6515
+ command: z14.string(),
6516
+ cwd: z14.string().optional(),
6517
+ name: z14.string().optional()
5039
6518
  });
5040
6519
  terminals2.post(
5041
6520
  "/:sessionId/terminals",
@@ -5116,8 +6595,8 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
5116
6595
  // We don't track exit codes in tmux mode
5117
6596
  });
5118
6597
  });
5119
- var logsQuerySchema = z12.object({
5120
- tail: z12.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
6598
+ var logsQuerySchema = z14.object({
6599
+ tail: z14.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
5121
6600
  });
5122
6601
  terminals2.get(
5123
6602
  "/:sessionId/terminals/:terminalId/logs",
@@ -5141,8 +6620,8 @@ terminals2.get(
5141
6620
  });
5142
6621
  }
5143
6622
  );
5144
- var killSchema = z12.object({
5145
- signal: z12.enum(["SIGTERM", "SIGKILL"]).optional()
6623
+ var killSchema = z14.object({
6624
+ signal: z14.enum(["SIGTERM", "SIGKILL"]).optional()
5146
6625
  });
5147
6626
  terminals2.post(
5148
6627
  "/:sessionId/terminals/:terminalId/kill",
@@ -5156,8 +6635,8 @@ terminals2.post(
5156
6635
  return c.json({ success: true, message: "Terminal killed" });
5157
6636
  }
5158
6637
  );
5159
- var writeSchema = z12.object({
5160
- input: z12.string()
6638
+ var writeSchema = z14.object({
6639
+ input: z14.string()
5161
6640
  });
5162
6641
  terminals2.post(
5163
6642
  "/:sessionId/terminals/:terminalId/write",
@@ -5339,10 +6818,10 @@ data: ${JSON.stringify({ status: "stopped" })}
5339
6818
  });
5340
6819
 
5341
6820
  // src/utils/dependencies.ts
5342
- import { exec as exec4 } from "child_process";
5343
- import { promisify as promisify4 } from "util";
6821
+ import { exec as exec5 } from "child_process";
6822
+ import { promisify as promisify5 } from "util";
5344
6823
  import { platform as platform2 } from "os";
5345
- var execAsync4 = promisify4(exec4);
6824
+ var execAsync5 = promisify5(exec5);
5346
6825
  function getInstallInstructions() {
5347
6826
  const os2 = platform2();
5348
6827
  if (os2 === "darwin") {
@@ -5375,7 +6854,7 @@ Install tmux:
5375
6854
  }
5376
6855
  async function checkTmux() {
5377
6856
  try {
5378
- const { stdout } = await execAsync4("tmux -V", { timeout: 5e3 });
6857
+ const { stdout } = await execAsync5("tmux -V", { timeout: 5e3 });
5379
6858
  const version = stdout.trim();
5380
6859
  return {
5381
6860
  available: true,
@@ -5422,13 +6901,13 @@ var DEFAULT_WEB_PORT = 6969;
5422
6901
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
5423
6902
  function getWebDirectory() {
5424
6903
  try {
5425
- const currentDir = dirname6(fileURLToPath2(import.meta.url));
5426
- const webDir = resolve8(currentDir, "..", "web");
5427
- if (existsSync12(webDir) && existsSync12(join5(webDir, "package.json"))) {
6904
+ const currentDir = dirname7(fileURLToPath3(import.meta.url));
6905
+ const webDir = resolve9(currentDir, "..", "web");
6906
+ if (existsSync13(webDir) && existsSync13(join7(webDir, "package.json"))) {
5428
6907
  return webDir;
5429
6908
  }
5430
- const altWebDir = resolve8(currentDir, "..", "..", "web");
5431
- if (existsSync12(altWebDir) && existsSync12(join5(altWebDir, "package.json"))) {
6909
+ const altWebDir = resolve9(currentDir, "..", "..", "web");
6910
+ if (existsSync13(altWebDir) && existsSync13(join7(altWebDir, "package.json"))) {
5432
6911
  return altWebDir;
5433
6912
  }
5434
6913
  return null;
@@ -5451,18 +6930,18 @@ async function isSparkcoderWebRunning(port) {
5451
6930
  }
5452
6931
  }
5453
6932
  function isPortInUse(port) {
5454
- return new Promise((resolve9) => {
6933
+ return new Promise((resolve10) => {
5455
6934
  const server = createNetServer();
5456
6935
  server.once("error", (err) => {
5457
6936
  if (err.code === "EADDRINUSE") {
5458
- resolve9(true);
6937
+ resolve10(true);
5459
6938
  } else {
5460
- resolve9(false);
6939
+ resolve10(false);
5461
6940
  }
5462
6941
  });
5463
6942
  server.once("listening", () => {
5464
6943
  server.close();
5465
- resolve9(false);
6944
+ resolve10(false);
5466
6945
  });
5467
6946
  server.listen(port, "0.0.0.0");
5468
6947
  });
@@ -5486,30 +6965,30 @@ async function findWebPort(preferredPort) {
5486
6965
  return { port: preferredPort, alreadyRunning: false };
5487
6966
  }
5488
6967
  function hasProductionBuild(webDir) {
5489
- const buildIdPath = join5(webDir, ".next", "BUILD_ID");
5490
- return existsSync12(buildIdPath);
6968
+ const buildIdPath = join7(webDir, ".next", "BUILD_ID");
6969
+ return existsSync13(buildIdPath);
5491
6970
  }
5492
6971
  function hasSourceFiles(webDir) {
5493
- const appDir = join5(webDir, "src", "app");
5494
- const pagesDir = join5(webDir, "src", "pages");
5495
- const rootAppDir = join5(webDir, "app");
5496
- const rootPagesDir = join5(webDir, "pages");
5497
- return existsSync12(appDir) || existsSync12(pagesDir) || existsSync12(rootAppDir) || existsSync12(rootPagesDir);
6972
+ const appDir = join7(webDir, "src", "app");
6973
+ const pagesDir = join7(webDir, "src", "pages");
6974
+ const rootAppDir = join7(webDir, "app");
6975
+ const rootPagesDir = join7(webDir, "pages");
6976
+ return existsSync13(appDir) || existsSync13(pagesDir) || existsSync13(rootAppDir) || existsSync13(rootPagesDir);
5498
6977
  }
5499
6978
  function getStandaloneServerPath(webDir) {
5500
6979
  const possiblePaths = [
5501
- join5(webDir, ".next", "standalone", "server.js"),
5502
- join5(webDir, ".next", "standalone", "web", "server.js")
6980
+ join7(webDir, ".next", "standalone", "server.js"),
6981
+ join7(webDir, ".next", "standalone", "web", "server.js")
5503
6982
  ];
5504
6983
  for (const serverPath of possiblePaths) {
5505
- if (existsSync12(serverPath)) {
6984
+ if (existsSync13(serverPath)) {
5506
6985
  return serverPath;
5507
6986
  }
5508
6987
  }
5509
6988
  return null;
5510
6989
  }
5511
6990
  function runCommand(command, args, cwd, env) {
5512
- return new Promise((resolve9) => {
6991
+ return new Promise((resolve10) => {
5513
6992
  const child = spawn2(command, args, {
5514
6993
  cwd,
5515
6994
  stdio: ["ignore", "pipe", "pipe"],
@@ -5524,10 +7003,10 @@ function runCommand(command, args, cwd, env) {
5524
7003
  output += data.toString();
5525
7004
  });
5526
7005
  child.on("close", (code) => {
5527
- resolve9({ success: code === 0, output });
7006
+ resolve10({ success: code === 0, output });
5528
7007
  });
5529
7008
  child.on("error", (err) => {
5530
- resolve9({ success: false, output: err.message });
7009
+ resolve10({ success: false, output: err.message });
5531
7010
  });
5532
7011
  });
5533
7012
  }
@@ -5542,13 +7021,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5542
7021
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
5543
7022
  return { process: null, port: actualPort };
5544
7023
  }
5545
- const usePnpm = existsSync12(join5(webDir, "pnpm-lock.yaml"));
5546
- const useNpm = !usePnpm && existsSync12(join5(webDir, "package-lock.json"));
7024
+ const usePnpm = existsSync13(join7(webDir, "pnpm-lock.yaml"));
7025
+ const useNpm = !usePnpm && existsSync13(join7(webDir, "package-lock.json"));
5547
7026
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
5548
7027
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
5549
7028
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
5550
7029
  const runtimeConfig = { apiBaseUrl: apiUrl };
5551
- const runtimeConfigPath = join5(webDir, "runtime-config.json");
7030
+ const runtimeConfigPath = join7(webDir, "runtime-config.json");
5552
7031
  try {
5553
7032
  writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5554
7033
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
@@ -5570,7 +7049,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5570
7049
  if (standaloneServerPath) {
5571
7050
  command = "node";
5572
7051
  args = ["server.js"];
5573
- cwd = dirname6(standaloneServerPath);
7052
+ cwd = dirname7(standaloneServerPath);
5574
7053
  webEnv.PORT = String(actualPort);
5575
7054
  webEnv.HOSTNAME = "0.0.0.0";
5576
7055
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
@@ -5611,10 +7090,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5611
7090
  let started = false;
5612
7091
  let exited = false;
5613
7092
  let exitCode = null;
5614
- const startedPromise = new Promise((resolve9) => {
7093
+ const startedPromise = new Promise((resolve10) => {
5615
7094
  const timeout = setTimeout(() => {
5616
7095
  if (!started && !exited) {
5617
- resolve9(false);
7096
+ resolve10(false);
5618
7097
  }
5619
7098
  }, startupTimeout);
5620
7099
  child.stdout?.on("data", (data) => {
@@ -5628,7 +7107,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5628
7107
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
5629
7108
  started = true;
5630
7109
  clearTimeout(timeout);
5631
- resolve9(true);
7110
+ resolve10(true);
5632
7111
  }
5633
7112
  });
5634
7113
  child.stderr?.on("data", (data) => {
@@ -5640,14 +7119,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5640
7119
  child.on("error", (err) => {
5641
7120
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
5642
7121
  clearTimeout(timeout);
5643
- resolve9(false);
7122
+ resolve10(false);
5644
7123
  });
5645
7124
  child.on("exit", (code) => {
5646
7125
  exited = true;
5647
7126
  exitCode = code;
5648
7127
  if (!started) {
5649
7128
  clearTimeout(timeout);
5650
- resolve9(false);
7129
+ resolve10(false);
5651
7130
  }
5652
7131
  webUIProcess = null;
5653
7132
  });
@@ -5740,7 +7219,7 @@ async function startServer(options = {}) {
5740
7219
  if (options.workingDirectory) {
5741
7220
  config.resolvedWorkingDirectory = options.workingDirectory;
5742
7221
  }
5743
- if (!existsSync12(config.resolvedWorkingDirectory)) {
7222
+ if (!existsSync13(config.resolvedWorkingDirectory)) {
5744
7223
  mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
5745
7224
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
5746
7225
  }