sparkecoder 0.1.21 → 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 (292) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +1361 -215
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +2179 -349
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +20 -2
  7. package/dist/db/index.js +97 -0
  8. package/dist/db/index.js.map +1 -1
  9. package/dist/{index-BzedNBK-.d.ts → index-BblbmG_0.d.ts} +42 -6
  10. package/dist/index.d.ts +6 -6
  11. package/dist/index.js +2165 -335
  12. package/dist/index.js.map +1 -1
  13. package/dist/{schema-CkrIadxa.d.ts → schema-D_8A4k01.d.ts} +270 -3
  14. package/dist/search-ybREg7F_.d.ts +254 -0
  15. package/dist/server/index.js +2163 -333
  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_378282b1._.js → 2374f_244589df._.js} +1 -1
  146. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5de336d2._.js → 2374f_41a27541._.js} +1 -1
  147. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_30f9df13._.js → 2374f_47c9e2d5._.js} +1 -1
  148. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_d94c2b70._.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_1d78db71._.js → 2374f_c33b095a._.js} +1 -1
  152. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_bbc99511._.js → 2374f_fa61fbb2._.js} +1 -1
  153. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_8825dcc9._.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_76ccf09f._.js +8 -0
  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/5ec82ce8f3aabaf0.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/5ec82ce8f3aabaf0.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/speech-input.tsx +89 -36
  227. package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +275 -0
  228. package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +19 -5
  229. package/web/.next/standalone/web/src/components/chat-interface.tsx +820 -50
  230. package/web/.next/standalone/web/src/hooks/use-workspace-files.ts +108 -0
  231. package/web/.next/standalone/web/src/lib/api.ts +223 -6
  232. package/web/.next/static/chunks/0cc382a66266188e.js +7 -0
  233. package/web/.next/static/chunks/0fda34e553582102.js +1 -0
  234. package/web/.next/{standalone/web/.next/static/chunks/5ec82ce8f3aabaf0.js → static/chunks/6407c045dfc908fe.js} +3 -3
  235. package/web/.next/static/chunks/651e187cc15d66de.js +1 -0
  236. package/web/.next/static/chunks/862ced58ce21a270.js +4 -0
  237. package/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
  238. package/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
  239. package/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
  240. package/web/.next/static/chunks/af22745850132107.css +1 -0
  241. package/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
  242. package/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
  243. package/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
  244. package/web/package.json +4 -0
  245. package/dist/bash-CGAqW7HR.d.ts +0 -80
  246. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_9bf3c7f3._.js +0 -3
  247. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +0 -3
  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]__a984d933._.js +0 -9
  250. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +0 -3
  251. package/web/.next/standalone/web/.next/server/chunks/ssr/web_19b6934c._.js +0 -8
  252. package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +0 -7
  253. package/web/.next/standalone/web/.next/server/chunks/ssr/web_e6034803._.js +0 -3
  254. package/web/.next/standalone/web/.next/static/chunks/03d4169891280e04.js +0 -7
  255. package/web/.next/standalone/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
  256. package/web/.next/standalone/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
  257. package/web/.next/standalone/web/.next/static/chunks/77e4bf0421481629.js +0 -1
  258. package/web/.next/standalone/web/.next/static/chunks/a86053f0894587f2.js +0 -7
  259. package/web/.next/standalone/web/.next/static/chunks/beb9625c4a470042.js +0 -1
  260. package/web/.next/standalone/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
  261. package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
  262. package/web/.next/standalone/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  263. package/web/.next/standalone/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  264. package/web/.next/standalone/web/.next/static/static/chunks/03d4169891280e04.js +0 -7
  265. package/web/.next/standalone/web/.next/static/static/chunks/2d5da0cfc011b8d9.js +0 -1
  266. package/web/.next/standalone/web/.next/static/static/chunks/634fd97fab9ed4e4.js +0 -4
  267. package/web/.next/standalone/web/.next/static/static/chunks/77e4bf0421481629.js +0 -1
  268. package/web/.next/standalone/web/.next/static/static/chunks/a86053f0894587f2.js +0 -7
  269. package/web/.next/standalone/web/.next/static/static/chunks/beb9625c4a470042.js +0 -1
  270. package/web/.next/standalone/web/.next/static/static/chunks/c2244168e74b8c78.js +0 -1
  271. package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +0 -5
  272. package/web/.next/standalone/web/.next/static/static/chunks/d0a69c59b1c0d99c.css +0 -1
  273. package/web/.next/standalone/web/.next/static/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  274. package/web/.next/static/chunks/03d4169891280e04.js +0 -7
  275. package/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
  276. package/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
  277. package/web/.next/static/chunks/77e4bf0421481629.js +0 -1
  278. package/web/.next/static/chunks/a86053f0894587f2.js +0 -7
  279. package/web/.next/static/chunks/beb9625c4a470042.js +0 -1
  280. package/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
  281. package/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
  282. package/web/.next/static/chunks/d0a69c59b1c0d99c.css +0 -1
  283. package/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
  284. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → static/uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  285. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → static/uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  286. /package/web/.next/standalone/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → static/uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
  287. /package/web/.next/standalone/web/.next/static/{static/kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  288. /package/web/.next/standalone/web/.next/static/{static/kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  289. /package/web/.next/standalone/web/.next/static/{static/kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
  290. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
  291. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
  292. /package/web/.next/static/{kABnAk0Y1tlcrUKDlM8UT → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
@@ -1,25 +1,419 @@
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/server/index.ts
8
398
  import "dotenv/config";
9
399
  import { Hono as Hono5 } from "hono";
10
400
  import { serve } from "@hono/node-server";
11
401
  import { cors } from "hono/cors";
12
402
  import { logger } from "hono/logger";
13
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
14
- import { resolve as resolve8, dirname as dirname6, join as join3 } from "path";
403
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
404
+ import { resolve as resolve9, dirname as dirname7, join as join7 } from "path";
15
405
  import { spawn as spawn2 } from "child_process";
16
406
  import { createServer as createNetServer } from "net";
17
- import { fileURLToPath as fileURLToPath2 } from "url";
407
+ import { fileURLToPath as fileURLToPath3 } from "url";
18
408
 
19
409
  // src/server/routes/sessions.ts
20
410
  import { Hono } from "hono";
21
411
  import { zValidator } from "@hono/zod-validator";
22
- import { z as z9 } from "zod";
412
+ import { z as z11 } from "zod";
413
+ import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
414
+ import { readdir as readdir4 } from "fs/promises";
415
+ import { join as join4, basename as basename2, extname as extname6, relative as relative7 } from "path";
416
+ import { nanoid as nanoid5 } from "nanoid";
23
417
 
24
418
  // src/db/index.ts
25
419
  import Database from "better-sqlite3";
@@ -36,6 +430,7 @@ __export(schema_exports, {
36
430
  loadedSkills: () => loadedSkills,
37
431
  messages: () => messages,
38
432
  sessions: () => sessions,
433
+ subagentExecutions: () => subagentExecutions,
39
434
  terminals: () => terminals,
40
435
  todoItems: () => todoItems,
41
436
  toolExecutions: () => toolExecutions
@@ -139,6 +534,26 @@ var fileBackups = sqliteTable("file_backups", {
139
534
  existed: integer("existed", { mode: "boolean" }).notNull().default(true),
140
535
  createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
141
536
  });
537
+ var subagentExecutions = sqliteTable("subagent_executions", {
538
+ id: text("id").primaryKey(),
539
+ sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
540
+ toolCallId: text("tool_call_id").notNull(),
541
+ // The tool call that spawned this subagent
542
+ subagentType: text("subagent_type").notNull(),
543
+ // e.g., 'search', 'analyze', etc.
544
+ task: text("task").notNull(),
545
+ // The task/query given to the subagent
546
+ model: text("model").notNull(),
547
+ // The model used (e.g., 'gemini-2.0-flash')
548
+ status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
549
+ // Steps taken by the subagent (stored as JSON array)
550
+ steps: text("steps", { mode: "json" }).$type().default([]),
551
+ // Final result/output
552
+ result: text("result", { mode: "json" }),
553
+ error: text("error"),
554
+ startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
555
+ completedAt: integer("completed_at", { mode: "timestamp" })
556
+ });
142
557
 
143
558
  // src/db/index.ts
144
559
  var db = null;
@@ -243,6 +658,22 @@ function initDatabase(dbPath) {
243
658
  created_at INTEGER NOT NULL
244
659
  );
245
660
 
661
+ -- Subagent executions table - tracks subagent runs
662
+ CREATE TABLE IF NOT EXISTS subagent_executions (
663
+ id TEXT PRIMARY KEY,
664
+ session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
665
+ tool_call_id TEXT NOT NULL,
666
+ subagent_type TEXT NOT NULL,
667
+ task TEXT NOT NULL,
668
+ model TEXT NOT NULL,
669
+ status TEXT NOT NULL DEFAULT 'running',
670
+ steps TEXT DEFAULT '[]',
671
+ result TEXT,
672
+ error TEXT,
673
+ started_at INTEGER NOT NULL,
674
+ completed_at INTEGER
675
+ );
676
+
246
677
  CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
247
678
  CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);
248
679
  CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);
@@ -252,6 +683,8 @@ function initDatabase(dbPath) {
252
683
  CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
253
684
  CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);
254
685
  CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);
686
+ CREATE INDEX IF NOT EXISTS idx_subagent_executions_session ON subagent_executions(session_id);
687
+ CREATE INDEX IF NOT EXISTS idx_subagent_executions_tool_call ON subagent_executions(tool_call_id);
255
688
  `);
256
689
  return db;
257
690
  }
@@ -645,85 +1078,149 @@ var fileBackupQueries = {
645
1078
  return result.changes;
646
1079
  }
647
1080
  };
1081
+ var subagentQueries = {
1082
+ create(data) {
1083
+ const id = nanoid();
1084
+ const result = getDb().insert(subagentExecutions).values({
1085
+ id,
1086
+ sessionId: data.sessionId,
1087
+ toolCallId: data.toolCallId,
1088
+ subagentType: data.subagentType,
1089
+ task: data.task,
1090
+ model: data.model,
1091
+ status: "running",
1092
+ steps: [],
1093
+ startedAt: /* @__PURE__ */ new Date()
1094
+ }).returning().get();
1095
+ return result;
1096
+ },
1097
+ getById(id) {
1098
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.id, id)).get();
1099
+ },
1100
+ getByToolCallId(toolCallId) {
1101
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.toolCallId, toolCallId)).get();
1102
+ },
1103
+ getBySession(sessionId) {
1104
+ return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).orderBy(desc(subagentExecutions.startedAt)).all();
1105
+ },
1106
+ addStep(id, step) {
1107
+ const existing = this.getById(id);
1108
+ if (!existing) return void 0;
1109
+ const currentSteps = existing.steps || [];
1110
+ const newSteps = [...currentSteps, step];
1111
+ return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
1112
+ },
1113
+ complete(id, result) {
1114
+ return getDb().update(subagentExecutions).set({
1115
+ status: "completed",
1116
+ result,
1117
+ completedAt: /* @__PURE__ */ new Date()
1118
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1119
+ },
1120
+ markError(id, error) {
1121
+ return getDb().update(subagentExecutions).set({
1122
+ status: "error",
1123
+ error,
1124
+ completedAt: /* @__PURE__ */ new Date()
1125
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1126
+ },
1127
+ cancel(id) {
1128
+ return getDb().update(subagentExecutions).set({
1129
+ status: "cancelled",
1130
+ completedAt: /* @__PURE__ */ new Date()
1131
+ }).where(eq(subagentExecutions.id, id)).returning().get();
1132
+ },
1133
+ deleteBySession(sessionId) {
1134
+ const result = getDb().delete(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).run();
1135
+ return result.changes;
1136
+ }
1137
+ };
648
1138
 
649
1139
  // src/agent/index.ts
650
1140
  import {
651
- streamText,
652
- generateText as generateText2,
653
- tool as tool7,
654
- stepCountIs
1141
+ streamText as streamText2,
1142
+ generateText as generateText3,
1143
+ tool as tool9,
1144
+ stepCountIs as stepCountIs2
655
1145
  } from "ai";
656
- import { gateway as gateway2 } from "@ai-sdk/gateway";
657
- import { z as z8 } from "zod";
658
- import { nanoid as nanoid3 } from "nanoid";
1146
+
1147
+ // src/agent/model.ts
1148
+ import { gateway } from "@ai-sdk/gateway";
1149
+ var ANTHROPIC_PREFIX = "anthropic/";
1150
+ function isAnthropicModel(modelId) {
1151
+ const normalized = modelId.trim().toLowerCase();
1152
+ return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
1153
+ }
1154
+ function resolveModel(modelId) {
1155
+ return gateway(modelId.trim());
1156
+ }
1157
+ var SUBAGENT_MODELS = {
1158
+ search: "google/gemini-2.0-flash",
1159
+ analyze: "google/gemini-2.0-flash",
1160
+ default: "google/gemini-2.0-flash"
1161
+ };
1162
+
1163
+ // src/agent/index.ts
1164
+ import { z as z10 } from "zod";
1165
+ import { nanoid as nanoid4 } from "nanoid";
659
1166
 
660
1167
  // src/config/index.ts
1168
+ init_types();
1169
+ init_types();
661
1170
  import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
662
1171
  import { resolve, dirname, join } from "path";
663
1172
  import { homedir, platform } from "os";
664
-
665
- // src/config/types.ts
666
- import { z } from "zod";
667
- var ToolApprovalConfigSchema = z.object({
668
- bash: z.boolean().optional().default(true),
669
- write_file: z.boolean().optional().default(false),
670
- read_file: z.boolean().optional().default(false),
671
- load_skill: z.boolean().optional().default(false),
672
- todo: z.boolean().optional().default(false)
673
- });
674
- var SkillMetadataSchema = z.object({
675
- name: z.string(),
676
- description: z.string()
677
- });
678
- var SessionConfigSchema = z.object({
679
- toolApprovals: z.record(z.string(), z.boolean()).optional(),
680
- approvalWebhook: z.string().url().optional(),
681
- skillsDirectory: z.string().optional(),
682
- maxContextChars: z.number().optional().default(2e5)
683
- });
684
- var SparkcoderConfigSchema = z.object({
685
- // Default model to use (Vercel AI Gateway format)
686
- defaultModel: z.string().default("anthropic/claude-opus-4-5"),
687
- // Working directory for file operations
688
- workingDirectory: z.string().optional(),
689
- // Tool approval settings
690
- toolApprovals: ToolApprovalConfigSchema.optional().default({}),
691
- // Approval webhook URL (called when approval is needed)
692
- approvalWebhook: z.string().url().optional(),
693
- // Skills configuration
694
- skills: z.object({
695
- // Directory containing skill files
696
- directory: z.string().optional().default("./skills"),
697
- // Additional skill directories to include
698
- additionalDirectories: z.array(z.string()).optional().default([])
699
- }).optional().default({}),
700
- // Context management
701
- context: z.object({
702
- // Maximum context size before summarization (in characters)
703
- maxChars: z.number().optional().default(2e5),
704
- // Enable automatic summarization
705
- autoSummarize: z.boolean().optional().default(true),
706
- // Number of recent messages to keep after summarization
707
- keepRecentMessages: z.number().optional().default(10)
708
- }).optional().default({}),
709
- // Server configuration
710
- server: z.object({
711
- port: z.number().default(3141),
712
- host: z.string().default("127.0.0.1"),
713
- // Public URL for web UI to connect to API (for Docker/remote access)
714
- // If not set, defaults to http://{host}:{port}
715
- publicUrl: z.string().url().optional()
716
- }).default({ port: 3141, host: "127.0.0.1" }),
717
- // Database path
718
- databasePath: z.string().optional().default("./sparkecoder.db")
719
- });
720
-
721
- // src/config/index.ts
722
1173
  var CONFIG_FILE_NAMES = [
723
1174
  "sparkecoder.config.json",
724
1175
  "sparkecoder.json",
725
1176
  ".sparkecoder.json"
726
1177
  ];
1178
+ function discoverSkillDirectories(workingDir) {
1179
+ const alwaysLoadedDirs = [];
1180
+ const onDemandDirs = [];
1181
+ const allDirectories = [];
1182
+ let agentsMdPath = null;
1183
+ const sparkRulesDir = join(workingDir, ".sparkecoder", "rules");
1184
+ if (existsSync(sparkRulesDir)) {
1185
+ alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });
1186
+ allDirectories.push(sparkRulesDir);
1187
+ }
1188
+ const sparkSkillsDir = join(workingDir, ".sparkecoder", "skills");
1189
+ if (existsSync(sparkSkillsDir)) {
1190
+ onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });
1191
+ allDirectories.push(sparkSkillsDir);
1192
+ }
1193
+ const cursorRulesDir = join(workingDir, ".cursor", "rules");
1194
+ if (existsSync(cursorRulesDir)) {
1195
+ onDemandDirs.push({ path: cursorRulesDir, priority: 3 });
1196
+ allDirectories.push(cursorRulesDir);
1197
+ }
1198
+ const claudeSkillsDir = join(workingDir, ".claude", "skills");
1199
+ if (existsSync(claudeSkillsDir)) {
1200
+ onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });
1201
+ allDirectories.push(claudeSkillsDir);
1202
+ }
1203
+ const legacySkillsDir = join(workingDir, "skills");
1204
+ if (existsSync(legacySkillsDir)) {
1205
+ onDemandDirs.push({ path: legacySkillsDir, priority: 5 });
1206
+ allDirectories.push(legacySkillsDir);
1207
+ }
1208
+ const agentsMd = join(workingDir, "AGENTS.md");
1209
+ if (existsSync(agentsMd)) {
1210
+ agentsMdPath = agentsMd;
1211
+ }
1212
+ const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
1213
+ if (existsSync(builtInSkillsDir)) {
1214
+ onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
1215
+ allDirectories.push(builtInSkillsDir);
1216
+ }
1217
+ return {
1218
+ alwaysLoadedDirs,
1219
+ onDemandDirs,
1220
+ agentsMdPath,
1221
+ allDirectories
1222
+ };
1223
+ }
727
1224
  function getAppDataDirectory() {
728
1225
  const appName = "sparkecoder";
729
1226
  switch (platform()) {
@@ -803,20 +1300,12 @@ function loadConfig(configPath, workingDirectory) {
803
1300
  } else {
804
1301
  resolvedWorkingDirectory = process.cwd();
805
1302
  }
1303
+ const discovered = discoverSkillDirectories(resolvedWorkingDirectory);
1304
+ const additionalDirs = (config.skills?.additionalDirectories || []).map((dir) => resolve(configDir, dir)).filter((dir) => existsSync(dir));
806
1305
  const resolvedSkillsDirectories = [
807
- resolve(configDir, config.skills?.directory || "./skills"),
808
- // Built-in skills
809
- resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default"),
810
- ...(config.skills?.additionalDirectories || []).map(
811
- (dir) => resolve(configDir, dir)
812
- )
813
- ].filter((dir) => {
814
- try {
815
- return existsSync(dir);
816
- } catch {
817
- return false;
818
- }
819
- });
1306
+ ...discovered.allDirectories,
1307
+ ...additionalDirs
1308
+ ];
820
1309
  let resolvedDatabasePath;
821
1310
  if (config.databasePath && config.databasePath !== "./sparkecoder.db") {
822
1311
  resolvedDatabasePath = resolve(configDir, config.databasePath);
@@ -833,7 +1322,8 @@ function loadConfig(configPath, workingDirectory) {
833
1322
  },
834
1323
  resolvedWorkingDirectory,
835
1324
  resolvedSkillsDirectories,
836
- resolvedDatabasePath
1325
+ resolvedDatabasePath,
1326
+ discoveredSkills: discovered
837
1327
  };
838
1328
  cachedConfig = resolved;
839
1329
  return resolved;
@@ -1234,8 +1724,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
1234
1724
  const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
1235
1725
  const terminals3 = [];
1236
1726
  try {
1237
- const { readdir: readdir3 } = await import("fs/promises");
1238
- const entries = await readdir3(terminalsDir, { withFileTypes: true });
1727
+ const { readdir: readdir5 } = await import("fs/promises");
1728
+ const entries = await readdir5(terminalsDir, { withFileTypes: true });
1239
1729
  for (const entry of entries) {
1240
1730
  if (entry.isDirectory()) {
1241
1731
  const meta = await getMeta(entry.name, workingDirectory, sessionId);
@@ -1839,12 +2329,12 @@ function findNearestRoot(startDir, markers) {
1839
2329
  }
1840
2330
  async function commandExists(cmd) {
1841
2331
  try {
1842
- const { exec: exec5 } = await import("child_process");
1843
- const { promisify: promisify5 } = await import("util");
1844
- const execAsync5 = promisify5(exec5);
2332
+ const { exec: exec6 } = await import("child_process");
2333
+ const { promisify: promisify6 } = await import("util");
2334
+ const execAsync6 = promisify6(exec6);
1845
2335
  const isWindows = process.platform === "win32";
1846
2336
  const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
1847
- await execAsync5(checkCmd);
2337
+ await execAsync6(checkCmd);
1848
2338
  return true;
1849
2339
  } catch {
1850
2340
  return false;
@@ -2134,7 +2624,7 @@ async function createClient(serverId, handle, root) {
2134
2624
  },
2135
2625
  async waitForDiagnostics(filePath, timeoutMs = 5e3) {
2136
2626
  const normalized = normalizePath(filePath);
2137
- return new Promise((resolve9) => {
2627
+ return new Promise((resolve10) => {
2138
2628
  const startTime = Date.now();
2139
2629
  let debounceTimer;
2140
2630
  let resolved = false;
@@ -2153,7 +2643,7 @@ async function createClient(serverId, handle, root) {
2153
2643
  if (resolved) return;
2154
2644
  resolved = true;
2155
2645
  cleanup();
2156
- resolve9(diagnostics.get(normalized) || []);
2646
+ resolve10(diagnostics.get(normalized) || []);
2157
2647
  };
2158
2648
  const onDiagnostic = () => {
2159
2649
  if (debounceTimer) clearTimeout(debounceTimer);
@@ -2311,6 +2801,7 @@ function isSupported(filePath) {
2311
2801
  }
2312
2802
 
2313
2803
  // src/tools/write-file.ts
2804
+ var MAX_PROGRESS_CHUNK_SIZE = 16 * 1024;
2314
2805
  var writeFileInputSchema = z4.object({
2315
2806
  path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
2316
2807
  mode: z4.enum(["full", "str_replace"]).describe('Write mode: "full" for complete file write, "str_replace" for targeted string replacement'),
@@ -2354,24 +2845,76 @@ Working directory: ${options.workingDirectory}`,
2354
2845
  error: 'Content is required for "full" mode'
2355
2846
  };
2356
2847
  }
2848
+ const existed = existsSync7(absolutePath);
2849
+ const action = existed ? "replaced" : "created";
2850
+ console.log("[WRITE-FILE] onProgress callback exists:", !!options.onProgress);
2851
+ console.log("[WRITE-FILE] Emitting started event for:", relativePath);
2852
+ options.onProgress?.({
2853
+ path: absolutePath,
2854
+ relativePath,
2855
+ mode: "full",
2856
+ status: "started",
2857
+ action,
2858
+ totalLength: content.length
2859
+ });
2860
+ if (content.length <= MAX_PROGRESS_CHUNK_SIZE) {
2861
+ options.onProgress?.({
2862
+ path: absolutePath,
2863
+ relativePath,
2864
+ mode: "full",
2865
+ status: "content",
2866
+ content,
2867
+ action,
2868
+ totalLength: content.length
2869
+ });
2870
+ } else {
2871
+ const chunkCount = Math.ceil(content.length / MAX_PROGRESS_CHUNK_SIZE);
2872
+ for (let i = 0; i < chunkCount; i += 1) {
2873
+ const chunkStart = i * MAX_PROGRESS_CHUNK_SIZE;
2874
+ const chunk = content.slice(chunkStart, chunkStart + MAX_PROGRESS_CHUNK_SIZE);
2875
+ options.onProgress?.({
2876
+ path: absolutePath,
2877
+ relativePath,
2878
+ mode: "full",
2879
+ status: "content",
2880
+ content: chunk,
2881
+ action,
2882
+ totalLength: content.length,
2883
+ chunkIndex: i,
2884
+ chunkCount,
2885
+ chunkStart,
2886
+ isChunked: true
2887
+ });
2888
+ if (chunkCount > 1) {
2889
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
2890
+ }
2891
+ }
2892
+ }
2357
2893
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
2358
2894
  const dir = dirname5(absolutePath);
2359
2895
  if (!existsSync7(dir)) {
2360
2896
  await mkdir3(dir, { recursive: true });
2361
2897
  }
2362
- const existed = existsSync7(absolutePath);
2363
2898
  await writeFile3(absolutePath, content, "utf-8");
2364
2899
  let diagnosticsOutput = "";
2365
2900
  if (options.enableLSP !== false && isSupported(absolutePath)) {
2366
2901
  await touchFile(absolutePath, true);
2367
2902
  diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2368
2903
  }
2904
+ options.onProgress?.({
2905
+ path: absolutePath,
2906
+ relativePath,
2907
+ mode: "full",
2908
+ status: "completed",
2909
+ action,
2910
+ totalLength: content.length
2911
+ });
2369
2912
  return {
2370
2913
  success: true,
2371
2914
  path: absolutePath,
2372
- relativePath: relative3(options.workingDirectory, absolutePath),
2915
+ relativePath,
2373
2916
  mode: "full",
2374
- action: existed ? "replaced" : "created",
2917
+ action,
2375
2918
  bytesWritten: Buffer.byteLength(content, "utf-8"),
2376
2919
  lineCount: content.split("\n").length,
2377
2920
  ...diagnosticsOutput && { diagnostics: diagnosticsOutput }
@@ -2389,6 +2932,22 @@ Working directory: ${options.workingDirectory}`,
2389
2932
  error: `File not found: ${path}. Use "full" mode to create new files.`
2390
2933
  };
2391
2934
  }
2935
+ options.onProgress?.({
2936
+ path: absolutePath,
2937
+ relativePath,
2938
+ mode: "str_replace",
2939
+ status: "started",
2940
+ action: "edited"
2941
+ });
2942
+ options.onProgress?.({
2943
+ path: absolutePath,
2944
+ relativePath,
2945
+ mode: "str_replace",
2946
+ status: "content",
2947
+ oldString: old_string,
2948
+ newString: new_string,
2949
+ action: "edited"
2950
+ });
2392
2951
  await backupFile(options.sessionId, options.workingDirectory, absolutePath);
2393
2952
  const currentContent = await readFile5(absolutePath, "utf-8");
2394
2953
  if (!currentContent.includes(old_string)) {
@@ -2419,10 +2978,17 @@ Working directory: ${options.workingDirectory}`,
2419
2978
  await touchFile(absolutePath, true);
2420
2979
  diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
2421
2980
  }
2981
+ options.onProgress?.({
2982
+ path: absolutePath,
2983
+ relativePath,
2984
+ mode: "str_replace",
2985
+ status: "completed",
2986
+ action: "edited"
2987
+ });
2422
2988
  return {
2423
2989
  success: true,
2424
2990
  path: absolutePath,
2425
- relativePath: relative3(options.workingDirectory, absolutePath),
2991
+ relativePath,
2426
2992
  mode: "str_replace",
2427
2993
  linesRemoved: oldLines,
2428
2994
  linesAdded: newLines,
@@ -2570,112 +3136,9 @@ function formatTodoItem(item) {
2570
3136
  }
2571
3137
 
2572
3138
  // src/tools/load-skill.ts
3139
+ init_skills();
2573
3140
  import { tool as tool5 } from "ai";
2574
3141
  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
3142
  var loadSkillInputSchema = z6.object({
2680
3143
  action: z6.enum(["list", "load"]).describe('Action to perform: "list" to see available skills, "load" to load a skill'),
2681
3144
  skillName: z6.string().optional().describe('For "load" action: The name of the skill to load')
@@ -2758,7 +3221,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
2758
3221
  // src/tools/linter.ts
2759
3222
  import { tool as tool6 } from "ai";
2760
3223
  import { z as z7 } from "zod";
2761
- import { resolve as resolve7, relative as relative4, isAbsolute as isAbsolute3, extname as extname4 } from "path";
3224
+ import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
2762
3225
  import { existsSync as existsSync9 } from "fs";
2763
3226
  import { readdir as readdir2, stat as stat2 } from "fs/promises";
2764
3227
  var linterInputSchema = z7.object({
@@ -2875,7 +3338,7 @@ function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
2875
3338
  let totalInfo = 0;
2876
3339
  const files = [];
2877
3340
  for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
2878
- const relativePath = relative4(workingDirectory, filePath);
3341
+ const relativePath = relative5(workingDirectory, filePath);
2879
3342
  let fileErrors = 0;
2880
3343
  let fileWarnings = 0;
2881
3344
  const formattedDiagnostics = diagnostics.map((d) => {
@@ -2945,7 +3408,622 @@ ${file.relativePath}:`);
2945
3408
  lines.push(` ... and ${file.diagnostics.length - 10} more`);
2946
3409
  }
2947
3410
  }
2948
- return lines.join("\n");
3411
+ return lines.join("\n");
3412
+ }
3413
+
3414
+ // src/tools/search.ts
3415
+ import { tool as tool8 } from "ai";
3416
+ import { z as z9 } from "zod";
3417
+
3418
+ // src/agent/subagent.ts
3419
+ import {
3420
+ generateText,
3421
+ stepCountIs
3422
+ } from "ai";
3423
+ import { nanoid as nanoid3 } from "nanoid";
3424
+ var Subagent = class {
3425
+ /** Model to use (defaults to gemini-2.0-flash) */
3426
+ model;
3427
+ /** Maximum steps before stopping */
3428
+ maxSteps = 20;
3429
+ constructor(model) {
3430
+ this.model = model || SUBAGENT_MODELS.default;
3431
+ }
3432
+ /**
3433
+ * Parse the final result from the subagent's output.
3434
+ * Override this to structure the result for your subagent type.
3435
+ */
3436
+ parseResult(text2, steps) {
3437
+ return { text: text2, steps };
3438
+ }
3439
+ /**
3440
+ * Run the subagent with streaming progress updates
3441
+ */
3442
+ async run(options) {
3443
+ const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
3444
+ const steps = [];
3445
+ const execution = subagentQueries.create({
3446
+ sessionId,
3447
+ toolCallId,
3448
+ subagentType: this.type,
3449
+ task,
3450
+ model: this.model
3451
+ });
3452
+ const addStep = async (step) => {
3453
+ const fullStep = {
3454
+ id: nanoid3(8),
3455
+ timestamp: Date.now(),
3456
+ ...step
3457
+ };
3458
+ steps.push(fullStep);
3459
+ subagentQueries.addStep(execution.id, fullStep);
3460
+ await onProgress?.({
3461
+ type: "step",
3462
+ subagentId: execution.id,
3463
+ subagentType: this.type,
3464
+ step: fullStep
3465
+ });
3466
+ };
3467
+ try {
3468
+ const tools = this.getTools(options);
3469
+ const systemPrompt = this.getSystemPrompt(options);
3470
+ const result = await generateText({
3471
+ model: resolveModel(this.model),
3472
+ system: systemPrompt,
3473
+ messages: [
3474
+ { role: "user", content: task }
3475
+ ],
3476
+ tools,
3477
+ stopWhen: stepCountIs(this.maxSteps),
3478
+ abortSignal,
3479
+ onStepFinish: async (step) => {
3480
+ if (step.text) {
3481
+ await addStep({
3482
+ type: "text",
3483
+ content: step.text
3484
+ });
3485
+ await onProgress?.({
3486
+ type: "text",
3487
+ subagentId: execution.id,
3488
+ subagentType: this.type,
3489
+ text: step.text
3490
+ });
3491
+ }
3492
+ if (step.toolCalls) {
3493
+ for (const toolCall of step.toolCalls) {
3494
+ await addStep({
3495
+ type: "tool_call",
3496
+ content: `Calling ${toolCall.toolName}`,
3497
+ toolName: toolCall.toolName,
3498
+ toolInput: toolCall.input
3499
+ });
3500
+ await onProgress?.({
3501
+ type: "tool_call",
3502
+ subagentId: execution.id,
3503
+ subagentType: this.type,
3504
+ toolName: toolCall.toolName,
3505
+ toolInput: toolCall.input
3506
+ });
3507
+ }
3508
+ }
3509
+ if (step.toolResults) {
3510
+ for (const toolResult of step.toolResults) {
3511
+ await addStep({
3512
+ type: "tool_result",
3513
+ content: `Result from ${toolResult.toolName}`,
3514
+ toolName: toolResult.toolName,
3515
+ toolOutput: toolResult.output
3516
+ });
3517
+ await onProgress?.({
3518
+ type: "tool_result",
3519
+ subagentId: execution.id,
3520
+ subagentType: this.type,
3521
+ toolName: toolResult.toolName,
3522
+ toolOutput: toolResult.output
3523
+ });
3524
+ }
3525
+ }
3526
+ }
3527
+ });
3528
+ const parsedResult = this.parseResult(result.text, steps);
3529
+ subagentQueries.complete(execution.id, parsedResult);
3530
+ await onProgress?.({
3531
+ type: "complete",
3532
+ subagentId: execution.id,
3533
+ subagentType: this.type,
3534
+ result: parsedResult
3535
+ });
3536
+ return {
3537
+ success: true,
3538
+ result: parsedResult,
3539
+ steps,
3540
+ executionId: execution.id
3541
+ };
3542
+ } catch (error) {
3543
+ const errorMessage = error.message || "Unknown error";
3544
+ subagentQueries.markError(execution.id, errorMessage);
3545
+ await onProgress?.({
3546
+ type: "error",
3547
+ subagentId: execution.id,
3548
+ subagentType: this.type,
3549
+ error: errorMessage
3550
+ });
3551
+ return {
3552
+ success: false,
3553
+ error: errorMessage,
3554
+ steps,
3555
+ executionId: execution.id
3556
+ };
3557
+ }
3558
+ }
3559
+ /**
3560
+ * Run with streaming (for real-time progress in UI)
3561
+ */
3562
+ async *stream(options) {
3563
+ const events = [];
3564
+ let resolveNext = null;
3565
+ let done = false;
3566
+ const eventQueue = [];
3567
+ const runPromise = this.run({
3568
+ ...options,
3569
+ onProgress: async (event) => {
3570
+ eventQueue.push(event);
3571
+ if (resolveNext) {
3572
+ resolveNext(eventQueue.shift());
3573
+ resolveNext = null;
3574
+ }
3575
+ }
3576
+ }).then((result) => {
3577
+ done = true;
3578
+ if (resolveNext) {
3579
+ resolveNext(null);
3580
+ }
3581
+ return result;
3582
+ });
3583
+ while (!done || eventQueue.length > 0) {
3584
+ if (eventQueue.length > 0) {
3585
+ yield eventQueue.shift();
3586
+ } else if (!done) {
3587
+ const event = await new Promise((resolve10) => {
3588
+ resolveNext = resolve10;
3589
+ });
3590
+ if (event) {
3591
+ yield event;
3592
+ }
3593
+ }
3594
+ }
3595
+ await runPromise;
3596
+ }
3597
+ };
3598
+
3599
+ // src/agent/subagents/search.ts
3600
+ import { tool as tool7 } from "ai";
3601
+ import { z as z8 } from "zod";
3602
+ import { exec as exec4 } from "child_process";
3603
+ import { promisify as promisify4 } from "util";
3604
+ import { readFile as readFile7, stat as stat3, readdir as readdir3 } from "fs/promises";
3605
+ import { resolve as resolve8, relative as relative6, isAbsolute as isAbsolute4 } from "path";
3606
+ import { existsSync as existsSync10 } from "fs";
3607
+ var execAsync4 = promisify4(exec4);
3608
+ var MAX_OUTPUT_CHARS4 = 2e4;
3609
+ var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
3610
+ var SearchSubagent = class extends Subagent {
3611
+ type = "search";
3612
+ name = "Search Agent";
3613
+ constructor(model) {
3614
+ super(model || SUBAGENT_MODELS.search);
3615
+ this.maxSteps = 15;
3616
+ }
3617
+ getSystemPrompt(options) {
3618
+ 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.
3619
+
3620
+ Working Directory: ${options.workingDirectory}
3621
+
3622
+ You have these tools available:
3623
+ - grep: Search for patterns in files using ripgrep (rg)
3624
+ - glob: Find files matching a pattern
3625
+ - read_file: Read contents of a specific file
3626
+ - list_dir: List directory contents
3627
+
3628
+ ## Strategy - Search in Parallel
3629
+
3630
+ IMPORTANT: When searching, run MULTIPLE searches in PARALLEL to cover different variations and related terms. Don't search sequentially - batch your searches together!
3631
+
3632
+ For example, if asked "how does authentication work":
3633
+ - Search for "auth", "login", "session", "jwt", "token" all at once
3634
+ - Search in different likely directories: src/auth/, lib/auth/, services/auth/
3635
+ - Look for common patterns: AuthProvider, useAuth, authenticate, isAuthenticated
3636
+
3637
+ **Parallel Search Patterns:**
3638
+ 1. Try multiple naming conventions at once:
3639
+ - camelCase: "getUserData", "handleAuth"
3640
+ - snake_case: "get_user_data", "handle_auth"
3641
+ - PascalCase: "UserService", "AuthProvider"
3642
+
3643
+ 2. Search for related concepts together:
3644
+ - For "database": search "db", "database", "query", "model", "schema"
3645
+ - For "api": search "endpoint", "route", "handler", "controller", "api"
3646
+
3647
+ 3. Use glob AND grep together:
3648
+ - Find files: \`*.auth.ts\`, \`*Auth*.tsx\`, \`auth/*.ts\`
3649
+ - Search content: patterns, function names, class names
3650
+
3651
+ ## Execution Flow
3652
+ 1. First, run 2-4 parallel searches covering different angles of the query
3653
+ 2. Review results and identify the most relevant files
3654
+ 3. Read the key files to understand the full context
3655
+ 4. Provide a clear summary with exact file paths and line numbers
3656
+
3657
+ Be efficient - you have limited steps. Maximize coverage with parallel tool calls.
3658
+
3659
+ ## Output Format
3660
+ When done, provide a summary with:
3661
+ - Key files/locations found (with full paths)
3662
+ - Relevant code snippets showing the important parts
3663
+ - How the pieces connect together
3664
+
3665
+ Keep your responses concise and focused on actionable information.`;
3666
+ }
3667
+ getTools(options) {
3668
+ const workingDirectory = options.workingDirectory;
3669
+ return {
3670
+ grep: tool7({
3671
+ description: "Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.",
3672
+ inputSchema: z8.object({
3673
+ pattern: z8.string().describe("The regex pattern to search for"),
3674
+ path: z8.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
3675
+ fileType: z8.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
3676
+ maxResults: z8.number().optional().default(50).describe("Maximum number of results to return")
3677
+ }),
3678
+ execute: async ({ pattern, path, fileType, maxResults }) => {
3679
+ try {
3680
+ const searchPath = path ? resolve8(workingDirectory, path) : workingDirectory;
3681
+ let args = ["rg", "--line-number", "--no-heading"];
3682
+ if (fileType) {
3683
+ args.push("--type", fileType);
3684
+ }
3685
+ args.push("--max-count", String(maxResults || 50));
3686
+ args.push("--", pattern, searchPath);
3687
+ const { stdout, stderr } = await execAsync4(args.join(" "), {
3688
+ cwd: workingDirectory,
3689
+ maxBuffer: 5 * 1024 * 1024,
3690
+ timeout: 3e4
3691
+ });
3692
+ const output = truncateOutput(stdout || "No matches found", MAX_OUTPUT_CHARS4);
3693
+ const matchCount = (stdout || "").split("\n").filter(Boolean).length;
3694
+ return {
3695
+ success: true,
3696
+ output,
3697
+ matchCount,
3698
+ pattern
3699
+ };
3700
+ } catch (error) {
3701
+ if (error.code === 1 && !error.stderr) {
3702
+ return {
3703
+ success: true,
3704
+ output: "No matches found",
3705
+ matchCount: 0,
3706
+ pattern
3707
+ };
3708
+ }
3709
+ return {
3710
+ success: false,
3711
+ error: error.message,
3712
+ pattern
3713
+ };
3714
+ }
3715
+ }
3716
+ }),
3717
+ glob: tool7({
3718
+ description: "Find files matching a glob pattern. Returns list of matching file paths.",
3719
+ inputSchema: z8.object({
3720
+ pattern: z8.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
3721
+ maxResults: z8.number().optional().default(100).describe("Maximum number of files to return")
3722
+ }),
3723
+ execute: async ({ pattern, maxResults }) => {
3724
+ try {
3725
+ const { stdout } = await execAsync4(
3726
+ `find . -type f -name "${pattern.replace("**/", "")}" 2>/dev/null | head -n ${maxResults || 100}`,
3727
+ {
3728
+ cwd: workingDirectory,
3729
+ timeout: 3e4
3730
+ }
3731
+ );
3732
+ const files = stdout.trim().split("\n").filter(Boolean);
3733
+ return {
3734
+ success: true,
3735
+ files,
3736
+ count: files.length,
3737
+ pattern
3738
+ };
3739
+ } catch (error) {
3740
+ return {
3741
+ success: false,
3742
+ error: error.message,
3743
+ pattern
3744
+ };
3745
+ }
3746
+ }
3747
+ }),
3748
+ read_file: tool7({
3749
+ description: "Read the contents of a file. Use this to examine specific files found in search.",
3750
+ inputSchema: z8.object({
3751
+ path: z8.string().describe("Path to the file (relative to working directory or absolute)"),
3752
+ startLine: z8.number().optional().describe("Start reading from this line (1-indexed)"),
3753
+ endLine: z8.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
3754
+ }),
3755
+ execute: async ({ path, startLine, endLine }) => {
3756
+ try {
3757
+ const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3758
+ if (!existsSync10(absolutePath)) {
3759
+ return {
3760
+ success: false,
3761
+ error: `File not found: ${path}`
3762
+ };
3763
+ }
3764
+ const stats = await stat3(absolutePath);
3765
+ if (stats.size > MAX_FILE_SIZE2) {
3766
+ return {
3767
+ success: false,
3768
+ error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
3769
+ };
3770
+ }
3771
+ let content = await readFile7(absolutePath, "utf-8");
3772
+ if (startLine !== void 0 || endLine !== void 0) {
3773
+ const lines = content.split("\n");
3774
+ const start = (startLine ?? 1) - 1;
3775
+ const end = endLine ?? lines.length;
3776
+ content = lines.slice(start, end).join("\n");
3777
+ }
3778
+ return {
3779
+ success: true,
3780
+ path: relative6(workingDirectory, absolutePath),
3781
+ content: truncateOutput(content, MAX_OUTPUT_CHARS4),
3782
+ lineCount: content.split("\n").length
3783
+ };
3784
+ } catch (error) {
3785
+ return {
3786
+ success: false,
3787
+ error: error.message
3788
+ };
3789
+ }
3790
+ }
3791
+ }),
3792
+ list_dir: tool7({
3793
+ description: "List contents of a directory. Shows files and subdirectories.",
3794
+ inputSchema: z8.object({
3795
+ path: z8.string().optional().default(".").describe("Directory path (relative to working directory)"),
3796
+ recursive: z8.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
3797
+ maxDepth: z8.number().optional().default(2).describe("Maximum depth for recursive listing")
3798
+ }),
3799
+ execute: async ({ path, recursive, maxDepth }) => {
3800
+ try {
3801
+ const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
3802
+ if (!existsSync10(absolutePath)) {
3803
+ return {
3804
+ success: false,
3805
+ error: `Directory not found: ${path}`
3806
+ };
3807
+ }
3808
+ const stats = await stat3(absolutePath);
3809
+ if (!stats.isDirectory()) {
3810
+ return {
3811
+ success: false,
3812
+ error: `Not a directory: ${path}`
3813
+ };
3814
+ }
3815
+ if (recursive) {
3816
+ const { stdout } = await execAsync4(
3817
+ `find . -maxdepth ${maxDepth} -type f 2>/dev/null | head -n 200`,
3818
+ {
3819
+ cwd: absolutePath,
3820
+ timeout: 1e4
3821
+ }
3822
+ );
3823
+ const files = stdout.trim().split("\n").filter(Boolean);
3824
+ return {
3825
+ success: true,
3826
+ path: relative6(workingDirectory, absolutePath) || ".",
3827
+ files,
3828
+ count: files.length,
3829
+ recursive: true
3830
+ };
3831
+ } else {
3832
+ const entries = await readdir3(absolutePath, { withFileTypes: true });
3833
+ const items = entries.slice(0, 200).map((e) => ({
3834
+ name: e.name,
3835
+ type: e.isDirectory() ? "directory" : "file"
3836
+ }));
3837
+ return {
3838
+ success: true,
3839
+ path: relative6(workingDirectory, absolutePath) || ".",
3840
+ items,
3841
+ count: items.length
3842
+ };
3843
+ }
3844
+ } catch (error) {
3845
+ return {
3846
+ success: false,
3847
+ error: error.message
3848
+ };
3849
+ }
3850
+ }
3851
+ })
3852
+ };
3853
+ }
3854
+ parseResult(text2, steps) {
3855
+ const findings = [];
3856
+ let filesSearched = 0;
3857
+ let matchCount = 0;
3858
+ for (const step of steps) {
3859
+ if (step.type === "tool_result" && step.toolOutput) {
3860
+ const output = step.toolOutput;
3861
+ if (step.toolName === "grep" && output.success) {
3862
+ matchCount += output.matchCount || 0;
3863
+ const lines = (output.output || "").split("\n").filter(Boolean).slice(0, 10);
3864
+ for (const line of lines) {
3865
+ const match = line.match(/^([^:]+):(\d+):(.*)$/);
3866
+ if (match) {
3867
+ findings.push({
3868
+ type: "match",
3869
+ path: match[1],
3870
+ lineNumber: parseInt(match[2], 10),
3871
+ content: match[3].trim(),
3872
+ relevance: "high"
3873
+ });
3874
+ }
3875
+ }
3876
+ } else if (step.toolName === "glob" && output.success) {
3877
+ filesSearched += output.count || 0;
3878
+ for (const file of (output.files || []).slice(0, 5)) {
3879
+ findings.push({
3880
+ type: "file",
3881
+ path: file,
3882
+ relevance: "medium"
3883
+ });
3884
+ }
3885
+ } else if (step.toolName === "read_file" && output.success) {
3886
+ findings.push({
3887
+ type: "file",
3888
+ path: output.path,
3889
+ relevance: "high",
3890
+ context: `${output.lineCount} lines`
3891
+ });
3892
+ }
3893
+ }
3894
+ }
3895
+ const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
3896
+ return {
3897
+ query,
3898
+ summary: text2,
3899
+ findings: findings.slice(0, 20),
3900
+ // Limit findings
3901
+ filesSearched,
3902
+ matchCount
3903
+ };
3904
+ }
3905
+ };
3906
+ function createSearchSubagent(model) {
3907
+ return new SearchSubagent(model);
3908
+ }
3909
+
3910
+ // src/tools/search.ts
3911
+ var MAX_RESULT_CHARS = 8e3;
3912
+ function createSearchTool(options) {
3913
+ return tool8({
3914
+ description: `Delegate a search task to a specialized search agent. Use this when you need to:
3915
+ - Find files or code matching a pattern
3916
+ - Explore the codebase structure
3917
+ - Search for specific functions, classes, or variables
3918
+ - Understand how a feature is implemented
3919
+
3920
+ The search agent will explore the codebase and return a summary of findings.
3921
+ This is more thorough than a simple grep because it can follow references and understand context.
3922
+
3923
+ Examples:
3924
+ - "Find all React components that use the useState hook"
3925
+ - "Where is the authentication logic implemented?"
3926
+ - "Find all API routes and their handlers"
3927
+ - "Search for usages of the UserService class"`,
3928
+ inputSchema: z9.object({
3929
+ query: z9.string().describe("What to search for. Be specific about what you're looking for."),
3930
+ context: z9.string().optional().describe("Optional additional context about why you need this information.")
3931
+ }),
3932
+ execute: async ({ query, context }, toolOptions) => {
3933
+ const toolCallId = toolOptions.toolCallId || `search_${Date.now()}`;
3934
+ await options.onProgress?.({
3935
+ status: "started",
3936
+ subagentId: toolCallId
3937
+ });
3938
+ try {
3939
+ const subagent = createSearchSubagent();
3940
+ const fullTask = context ? `${query}
3941
+
3942
+ Context: ${context}` : query;
3943
+ const result = await subagent.run({
3944
+ task: fullTask,
3945
+ sessionId: options.sessionId,
3946
+ toolCallId,
3947
+ workingDirectory: options.workingDirectory,
3948
+ onProgress: async (event) => {
3949
+ if (event.type === "step" && event.step) {
3950
+ await options.onProgress?.({
3951
+ status: "step",
3952
+ subagentId: event.subagentId,
3953
+ stepType: event.step.type,
3954
+ stepContent: event.step.content,
3955
+ toolName: event.step.toolName,
3956
+ toolInput: event.step.toolInput,
3957
+ toolOutput: event.step.toolOutput
3958
+ });
3959
+ } else if (event.type === "complete") {
3960
+ await options.onProgress?.({
3961
+ status: "complete",
3962
+ subagentId: event.subagentId,
3963
+ result: event.result
3964
+ });
3965
+ } else if (event.type === "error") {
3966
+ await options.onProgress?.({
3967
+ status: "error",
3968
+ subagentId: event.subagentId,
3969
+ error: event.error
3970
+ });
3971
+ }
3972
+ }
3973
+ });
3974
+ if (!result.success) {
3975
+ return {
3976
+ success: false,
3977
+ error: result.error || "Search failed",
3978
+ executionId: result.executionId
3979
+ };
3980
+ }
3981
+ const searchResult = result.result;
3982
+ let formattedResult = `## Search Results
3983
+
3984
+ `;
3985
+ formattedResult += `**Summary:** ${searchResult.summary}
3986
+
3987
+ `;
3988
+ if (searchResult.findings.length > 0) {
3989
+ formattedResult += `### Key Findings (${searchResult.findings.length} items)
3990
+
3991
+ `;
3992
+ for (const finding of searchResult.findings) {
3993
+ if (finding.type === "match") {
3994
+ formattedResult += `- **${finding.path}:${finding.lineNumber}** - ${truncateOutput(finding.content || "", 200)}
3995
+ `;
3996
+ } else if (finding.type === "file") {
3997
+ formattedResult += `- **${finding.path}** ${finding.context ? `(${finding.context})` : ""}
3998
+ `;
3999
+ }
4000
+ }
4001
+ }
4002
+ formattedResult += `
4003
+ **Stats:** ${searchResult.matchCount} matches across ${searchResult.filesSearched} files searched`;
4004
+ return {
4005
+ success: true,
4006
+ query: searchResult.query,
4007
+ summary: searchResult.summary,
4008
+ findings: searchResult.findings,
4009
+ matchCount: searchResult.matchCount,
4010
+ filesSearched: searchResult.filesSearched,
4011
+ formattedResult: truncateOutput(formattedResult, MAX_RESULT_CHARS),
4012
+ executionId: result.executionId,
4013
+ stepsCount: result.steps.length
4014
+ };
4015
+ } catch (error) {
4016
+ await options.onProgress?.({
4017
+ status: "error",
4018
+ error: error.message
4019
+ });
4020
+ return {
4021
+ success: false,
4022
+ error: error.message
4023
+ };
4024
+ }
4025
+ }
4026
+ });
2949
4027
  }
2950
4028
 
2951
4029
  // src/tools/index.ts
@@ -2963,7 +4041,8 @@ function createTools(options) {
2963
4041
  write_file: createWriteFileTool({
2964
4042
  workingDirectory: options.workingDirectory,
2965
4043
  sessionId: options.sessionId,
2966
- enableLSP: options.enableLSP ?? true
4044
+ enableLSP: options.enableLSP ?? true,
4045
+ onProgress: options.onWriteFileProgress
2967
4046
  }),
2968
4047
  todo: createTodoTool({
2969
4048
  sessionId: options.sessionId
@@ -2974,15 +4053,20 @@ function createTools(options) {
2974
4053
  }),
2975
4054
  linter: createLinterTool({
2976
4055
  workingDirectory: options.workingDirectory
4056
+ }),
4057
+ search: createSearchTool({
4058
+ sessionId: options.sessionId,
4059
+ workingDirectory: options.workingDirectory,
4060
+ onProgress: options.onSearchProgress
2977
4061
  })
2978
4062
  };
2979
4063
  }
2980
4064
 
2981
4065
  // src/agent/context.ts
2982
- import { generateText } from "ai";
2983
- import { gateway } from "@ai-sdk/gateway";
4066
+ import { generateText as generateText2 } from "ai";
2984
4067
 
2985
4068
  // src/agent/prompts.ts
4069
+ init_skills();
2986
4070
  import os from "os";
2987
4071
  function getSearchInstructions() {
2988
4072
  const platform3 = process.platform;
@@ -3001,9 +4085,33 @@ function getSearchInstructions() {
3001
4085
  - **If ripgrep (\`rg\`) is installed**: \`rg "pattern" -t ts src/\` - faster and respects .gitignore`;
3002
4086
  }
3003
4087
  async function buildSystemPrompt(options) {
3004
- const { workingDirectory, skillsDirectories, sessionId, customInstructions } = options;
3005
- const skills = await loadAllSkills(skillsDirectories);
3006
- const skillsContext = formatSkillsForContext(skills);
4088
+ const {
4089
+ workingDirectory,
4090
+ skillsDirectories,
4091
+ sessionId,
4092
+ discoveredSkills,
4093
+ activeFiles = [],
4094
+ customInstructions
4095
+ } = options;
4096
+ let alwaysLoadedContent = "";
4097
+ let globMatchedContent = "";
4098
+ let agentsMdContent = "";
4099
+ let onDemandSkillsContext = "";
4100
+ if (discoveredSkills) {
4101
+ const { always, onDemand, all } = await loadAllSkillsFromDiscovered(discoveredSkills);
4102
+ alwaysLoadedContent = formatAlwaysLoadedSkills(always);
4103
+ onDemandSkillsContext = formatSkillsForContext(onDemand);
4104
+ const agentsMd = await loadAgentsMd(discoveredSkills.agentsMdPath);
4105
+ agentsMdContent = formatAgentsMdContent(agentsMd);
4106
+ if (activeFiles.length > 0) {
4107
+ const globMatched = await getGlobMatchedSkills(all, activeFiles, workingDirectory);
4108
+ globMatchedContent = formatGlobMatchedSkills(globMatched);
4109
+ }
4110
+ } else {
4111
+ const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
4112
+ const skills = await loadAllSkills2(skillsDirectories);
4113
+ onDemandSkillsContext = formatSkillsForContext(skills);
4114
+ }
3007
4115
  const todos = todoQueries.getBySession(sessionId);
3008
4116
  const todosContext = formatTodosForContext(todos);
3009
4117
  const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
@@ -3024,6 +4132,7 @@ You have access to powerful tools for:
3024
4132
  - **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
3025
4133
  - **todo**: Manage your task list to track progress on complex operations
3026
4134
  - **load_skill**: Load specialized knowledge documents for specific tasks
4135
+ - **search**: Semantic search using a subagent - for exploratory questions and finding code by meaning
3027
4136
 
3028
4137
 
3029
4138
  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 +4209,9 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
3100
4209
  - Use \`write_file\` with mode "full" only for new files or complete rewrites
3101
4210
  - After making changes, use the \`linter\` tool to check for type errors and lint issues
3102
4211
  - The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
4212
+ - If the user asks to write/create a file, always use \`write_file\` rather than printing the full contents
4213
+ - If the user requests a file but does not provide a path, choose a sensible default (e.g. \`index.html\`) and proceed
4214
+ - For large content (hundreds of lines), avoid placing it in chat output; write to a file instead
3103
4215
 
3104
4216
  ### Linter Tool
3105
4217
  The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
@@ -3111,6 +4223,30 @@ linter({ paths: ["src/"] }) // Check all files in a directory
3111
4223
  Use this proactively after making code changes to catch errors early.
3112
4224
 
3113
4225
  ### Searching and Exploration
4226
+
4227
+ **Choose the right search approach:**
4228
+
4229
+ 1. **Use the \`search\` tool (subagent)** for:
4230
+ - Semantic/exploratory questions: "How does authentication work?", "Where is user data processed?"
4231
+ - Finding code by meaning or concept, not exact text
4232
+ - Understanding how features are implemented across multiple files
4233
+ - Exploring unfamiliar parts of the codebase
4234
+ - Questions like "where", "how", "what does X do"
4235
+
4236
+ 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.
4237
+
4238
+ 2. **Use direct commands (grep/rg, find)** for:
4239
+ - Exact string matches: \`rg "functionName"\`, \`rg "class MyClass"\`
4240
+ - Finding files by name: \`find . -name "*.config.ts"\`
4241
+ - Simple pattern matching when you know exactly what you're looking for
4242
+ - Counting occurrences or listing all matches
4243
+
4244
+ **Examples:**
4245
+ - "Where is the API authentication handled?" \u2192 Use \`search\` tool
4246
+ - "Find all usages of getUserById" \u2192 Use \`rg "getUserById"\`
4247
+ - "How does the payment flow work?" \u2192 Use \`search\` tool
4248
+ - "Find files named config" \u2192 Use \`find . -name "*config*"\`
4249
+
3114
4250
  ${searchInstructions}
3115
4251
 
3116
4252
  ###Follow these principles when designing and implementing software:
@@ -3133,11 +4269,11 @@ ${searchInstructions}
3133
4269
  16. **Diversity** \u2014 Distrust all claims for "one true way"
3134
4270
  17. **Extensibility** \u2014 Design for the future, because it will be here sooner than you think
3135
4271
 
3136
- ###Follow these ruls to be a good agent for the user:
4272
+ ### Follow these rules to be a good agent for the user:
3137
4273
 
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.
4274
+ 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
4275
  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.
4276
+ 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
4277
  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
4278
  5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.
3143
4279
  6. Verify my work - After making changes, check for linter errors and fix any introduced.
@@ -3150,8 +4286,14 @@ ${searchInstructions}
3150
4286
  - Ask clarifying questions when requirements are ambiguous
3151
4287
  - Report progress on multi-step tasks
3152
4288
 
3153
- ## Skills
3154
- ${skillsContext}
4289
+ ${agentsMdContent}
4290
+
4291
+ ${alwaysLoadedContent}
4292
+
4293
+ ${globMatchedContent}
4294
+
4295
+ ## On-Demand Skills
4296
+ ${onDemandSkillsContext}
3155
4297
 
3156
4298
  ## Current Task List
3157
4299
  ${todosContext}
@@ -3246,8 +4388,8 @@ ${this.summary}`
3246
4388
  try {
3247
4389
  const config = getConfig();
3248
4390
  const summaryPrompt = createSummaryPrompt(historyText);
3249
- const result = await generateText({
3250
- model: gateway(config.defaultModel),
4391
+ const result = await generateText2({
4392
+ model: resolveModel(config.defaultModel),
3251
4393
  prompt: summaryPrompt
3252
4394
  });
3253
4395
  this.summary = result.text;
@@ -3260,11 +4402,12 @@ ${this.summary}`
3260
4402
  }
3261
4403
  /**
3262
4404
  * Add a user message to the context
4405
+ * Content can be a string or an array of content parts (for messages with images/files)
3263
4406
  */
3264
- addUserMessage(text2) {
4407
+ addUserMessage(content) {
3265
4408
  const userMessage = {
3266
4409
  role: "user",
3267
- content: text2
4410
+ content
3268
4411
  };
3269
4412
  messageQueries.create(this.sessionId, userMessage);
3270
4413
  }
@@ -3316,7 +4459,9 @@ var Agent = class _Agent {
3316
4459
  sessionId: this.session.id,
3317
4460
  workingDirectory: this.session.workingDirectory,
3318
4461
  skillsDirectories: config.resolvedSkillsDirectories,
3319
- onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0
4462
+ onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
4463
+ onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
4464
+ onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "search", data: progress }) : void 0
3320
4465
  });
3321
4466
  }
3322
4467
  /**
@@ -3364,40 +4509,94 @@ var Agent = class _Agent {
3364
4509
  getSession() {
3365
4510
  return this.session;
3366
4511
  }
4512
+ /**
4513
+ * Build user message content from prompt and attachments
4514
+ */
4515
+ buildUserMessageContent(prompt, attachments) {
4516
+ if (!attachments || attachments.length === 0) {
4517
+ return prompt;
4518
+ }
4519
+ const contentParts = [];
4520
+ const attachmentDescriptions = attachments.map((a, i) => {
4521
+ const name = a.filename || `attachment_${i + 1}`;
4522
+ const typeLabel = a.type === "image" ? "Image" : "File";
4523
+ const location = a.savedPath || "(path unknown)";
4524
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
4525
+ }).join("\n");
4526
+ contentParts.push({
4527
+ type: "text",
4528
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
4529
+ ${attachmentDescriptions}
4530
+
4531
+ You can reference these files by their paths above. The file contents are also shown inline below.`
4532
+ });
4533
+ if (prompt) {
4534
+ contentParts.push({ type: "text", text: `
4535
+ [USER MESSAGE]
4536
+ ${prompt}` });
4537
+ }
4538
+ for (const attachment of attachments) {
4539
+ if (attachment.type === "image") {
4540
+ contentParts.push({
4541
+ type: "image",
4542
+ image: attachment.data,
4543
+ // base64 data URL or raw base64
4544
+ mediaType: attachment.mediaType,
4545
+ filename: attachment.filename,
4546
+ savedPath: attachment.savedPath
4547
+ });
4548
+ } else {
4549
+ contentParts.push({
4550
+ type: "file",
4551
+ data: attachment.data,
4552
+ mediaType: attachment.mediaType || "application/octet-stream",
4553
+ filename: attachment.filename,
4554
+ savedPath: attachment.savedPath
4555
+ });
4556
+ }
4557
+ }
4558
+ return contentParts;
4559
+ }
3367
4560
  /**
3368
4561
  * Run the agent with a prompt (streaming)
3369
4562
  */
3370
4563
  async stream(options) {
3371
4564
  const config = getConfig();
4565
+ const userContent = this.buildUserMessageContent(options.prompt, options.attachments);
3372
4566
  if (!options.skipSaveUserMessage) {
3373
- this.context.addUserMessage(options.prompt);
4567
+ this.context.addUserMessage(userContent);
3374
4568
  }
3375
4569
  sessionQueries.updateStatus(this.session.id, "active");
3376
4570
  const systemPrompt = await buildSystemPrompt({
3377
4571
  workingDirectory: this.session.workingDirectory,
3378
4572
  skillsDirectories: config.resolvedSkillsDirectories,
3379
- sessionId: this.session.id
4573
+ sessionId: this.session.id,
4574
+ discoveredSkills: config.discoveredSkills,
4575
+ // TODO: Pass activeFiles from client for glob matching
4576
+ activeFiles: []
3380
4577
  });
3381
4578
  const messages2 = await this.context.getMessages();
3382
4579
  const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
3383
4580
  const wrappedTools = this.wrapToolsWithApproval(options, tools);
3384
- const stream = streamText({
3385
- model: gateway2(this.session.model),
4581
+ const useAnthropic = isAnthropicModel(this.session.model);
4582
+ const stream = streamText2({
4583
+ model: resolveModel(this.session.model),
3386
4584
  system: systemPrompt,
3387
4585
  messages: messages2,
3388
4586
  tools: wrappedTools,
3389
- stopWhen: stepCountIs(500),
4587
+ stopWhen: stepCountIs2(500),
3390
4588
  // Forward abort signal if provided
3391
4589
  abortSignal: options.abortSignal,
3392
4590
  // Enable extended thinking/reasoning for models that support it
3393
- providerOptions: {
4591
+ providerOptions: useAnthropic ? {
3394
4592
  anthropic: {
4593
+ toolStreaming: true,
3395
4594
  thinking: {
3396
4595
  type: "enabled",
3397
4596
  budgetTokens: 1e4
3398
4597
  }
3399
4598
  }
3400
- },
4599
+ } : void 0,
3401
4600
  onStepFinish: async (step) => {
3402
4601
  options.onStepFinish?.(step);
3403
4602
  },
@@ -3427,26 +4626,29 @@ var Agent = class _Agent {
3427
4626
  const systemPrompt = await buildSystemPrompt({
3428
4627
  workingDirectory: this.session.workingDirectory,
3429
4628
  skillsDirectories: config.resolvedSkillsDirectories,
3430
- sessionId: this.session.id
4629
+ sessionId: this.session.id,
4630
+ discoveredSkills: config.discoveredSkills,
4631
+ activeFiles: []
3431
4632
  });
3432
4633
  const messages2 = await this.context.getMessages();
3433
4634
  const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
3434
4635
  const wrappedTools = this.wrapToolsWithApproval(options, tools);
3435
- const result = await generateText2({
3436
- model: gateway2(this.session.model),
4636
+ const useAnthropic = isAnthropicModel(this.session.model);
4637
+ const result = await generateText3({
4638
+ model: resolveModel(this.session.model),
3437
4639
  system: systemPrompt,
3438
4640
  messages: messages2,
3439
4641
  tools: wrappedTools,
3440
- stopWhen: stepCountIs(500),
4642
+ stopWhen: stepCountIs2(500),
3441
4643
  // Enable extended thinking/reasoning for models that support it
3442
- providerOptions: {
4644
+ providerOptions: useAnthropic ? {
3443
4645
  anthropic: {
3444
4646
  thinking: {
3445
4647
  type: "enabled",
3446
4648
  budgetTokens: 1e4
3447
4649
  }
3448
4650
  }
3449
- }
4651
+ } : void 0
3450
4652
  });
3451
4653
  const responseMessages = result.response.messages;
3452
4654
  this.context.addResponseMessages(responseMessages);
@@ -3468,11 +4670,11 @@ var Agent = class _Agent {
3468
4670
  wrappedTools[name] = originalTool;
3469
4671
  continue;
3470
4672
  }
3471
- wrappedTools[name] = tool7({
4673
+ wrappedTools[name] = tool9({
3472
4674
  description: originalTool.description || "",
3473
- inputSchema: originalTool.inputSchema || z8.object({}),
4675
+ inputSchema: originalTool.inputSchema || z10.object({}),
3474
4676
  execute: async (input, toolOptions) => {
3475
- const toolCallId = toolOptions.toolCallId || nanoid3();
4677
+ const toolCallId = toolOptions.toolCallId || nanoid4();
3476
4678
  const execution = toolExecutionQueries.create({
3477
4679
  sessionId: this.session.id,
3478
4680
  toolName: name,
@@ -3484,8 +4686,8 @@ var Agent = class _Agent {
3484
4686
  this.pendingApprovals.set(toolCallId, execution);
3485
4687
  options.onApprovalRequired?.(execution);
3486
4688
  sessionQueries.updateStatus(this.session.id, "waiting");
3487
- const approved = await new Promise((resolve9) => {
3488
- approvalResolvers.set(toolCallId, { resolve: resolve9, sessionId: this.session.id });
4689
+ const approved = await new Promise((resolve10) => {
4690
+ approvalResolvers.set(toolCallId, { resolve: resolve10, sessionId: this.session.id });
3489
4691
  });
3490
4692
  const resolverData = approvalResolvers.get(toolCallId);
3491
4693
  approvalResolvers.delete(toolCallId);
@@ -3580,18 +4782,18 @@ var Agent = class _Agent {
3580
4782
 
3581
4783
  // src/server/routes/sessions.ts
3582
4784
  var sessions2 = new Hono();
3583
- var createSessionSchema = z9.object({
3584
- name: z9.string().optional(),
3585
- workingDirectory: z9.string().optional(),
3586
- model: z9.string().optional(),
3587
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4785
+ var createSessionSchema = z11.object({
4786
+ name: z11.string().optional(),
4787
+ workingDirectory: z11.string().optional(),
4788
+ model: z11.string().optional(),
4789
+ toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
3588
4790
  });
3589
- var paginationQuerySchema = z9.object({
3590
- limit: z9.string().optional(),
3591
- offset: z9.string().optional()
4791
+ var paginationQuerySchema = z11.object({
4792
+ limit: z11.string().optional(),
4793
+ offset: z11.string().optional()
3592
4794
  });
3593
- var messagesQuerySchema = z9.object({
3594
- limit: z9.string().optional()
4795
+ var messagesQuerySchema = z11.object({
4796
+ limit: z11.string().optional()
3595
4797
  });
3596
4798
  sessions2.get(
3597
4799
  "/",
@@ -3730,10 +4932,10 @@ sessions2.get("/:id/tools", async (c) => {
3730
4932
  count: executions.length
3731
4933
  });
3732
4934
  });
3733
- var updateSessionSchema = z9.object({
3734
- model: z9.string().optional(),
3735
- name: z9.string().optional(),
3736
- toolApprovals: z9.record(z9.string(), z9.boolean()).optional()
4935
+ var updateSessionSchema = z11.object({
4936
+ model: z11.string().optional(),
4937
+ name: z11.string().optional(),
4938
+ toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
3737
4939
  });
3738
4940
  sessions2.patch(
3739
4941
  "/:id",
@@ -3930,11 +5132,275 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
3930
5132
  currentContent: fileDiff.currentContent
3931
5133
  });
3932
5134
  });
5135
+ function getAttachmentsDir(sessionId) {
5136
+ const appDataDir = getAppDataDirectory();
5137
+ return join4(appDataDir, "attachments", sessionId);
5138
+ }
5139
+ function ensureAttachmentsDir(sessionId) {
5140
+ const dir = getAttachmentsDir(sessionId);
5141
+ if (!existsSync11(dir)) {
5142
+ mkdirSync3(dir, { recursive: true });
5143
+ }
5144
+ return dir;
5145
+ }
5146
+ sessions2.get("/:id/attachments", async (c) => {
5147
+ const sessionId = c.req.param("id");
5148
+ const session = sessionQueries.getById(sessionId);
5149
+ if (!session) {
5150
+ return c.json({ error: "Session not found" }, 404);
5151
+ }
5152
+ const dir = getAttachmentsDir(sessionId);
5153
+ if (!existsSync11(dir)) {
5154
+ return c.json({ sessionId, attachments: [], count: 0 });
5155
+ }
5156
+ const files = readdirSync(dir);
5157
+ const attachments = files.map((filename) => {
5158
+ const filePath = join4(dir, filename);
5159
+ const stats = statSync(filePath);
5160
+ return {
5161
+ id: filename.split("_")[0],
5162
+ // Extract the nanoid prefix
5163
+ filename,
5164
+ path: filePath,
5165
+ size: stats.size,
5166
+ createdAt: stats.birthtime.toISOString()
5167
+ };
5168
+ });
5169
+ return c.json({
5170
+ sessionId,
5171
+ attachments,
5172
+ count: attachments.length
5173
+ });
5174
+ });
5175
+ sessions2.post("/:id/attachments", async (c) => {
5176
+ const sessionId = c.req.param("id");
5177
+ const session = sessionQueries.getById(sessionId);
5178
+ if (!session) {
5179
+ return c.json({ error: "Session not found" }, 404);
5180
+ }
5181
+ const contentType = c.req.header("content-type") || "";
5182
+ if (contentType.includes("multipart/form-data")) {
5183
+ try {
5184
+ const formData = await c.req.formData();
5185
+ const file = formData.get("file");
5186
+ if (!file || !(file instanceof File)) {
5187
+ return c.json({ error: "No file provided" }, 400);
5188
+ }
5189
+ const dir = ensureAttachmentsDir(sessionId);
5190
+ const id = nanoid5(10);
5191
+ const ext = extname6(file.name) || "";
5192
+ const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
5193
+ const filePath = join4(dir, safeFilename);
5194
+ const arrayBuffer = await file.arrayBuffer();
5195
+ writeFileSync2(filePath, Buffer.from(arrayBuffer));
5196
+ return c.json({
5197
+ id,
5198
+ filename: file.name,
5199
+ storedAs: safeFilename,
5200
+ path: filePath,
5201
+ size: file.size,
5202
+ mediaType: file.type,
5203
+ sessionId
5204
+ }, 201);
5205
+ } catch (err) {
5206
+ console.error("Failed to upload attachment:", err);
5207
+ return c.json({ error: "Failed to upload file" }, 500);
5208
+ }
5209
+ }
5210
+ try {
5211
+ const body = await c.req.json();
5212
+ if (!body.filename || !body.data) {
5213
+ return c.json({ error: "Missing filename or data" }, 400);
5214
+ }
5215
+ const dir = ensureAttachmentsDir(sessionId);
5216
+ const id = nanoid5(10);
5217
+ const ext = extname6(body.filename) || "";
5218
+ const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
5219
+ const filePath = join4(dir, safeFilename);
5220
+ let base64Data = body.data;
5221
+ if (base64Data.includes(",")) {
5222
+ base64Data = base64Data.split(",")[1];
5223
+ }
5224
+ const buffer = Buffer.from(base64Data, "base64");
5225
+ writeFileSync2(filePath, buffer);
5226
+ return c.json({
5227
+ id,
5228
+ filename: body.filename,
5229
+ storedAs: safeFilename,
5230
+ path: filePath,
5231
+ size: buffer.length,
5232
+ mediaType: body.mediaType,
5233
+ sessionId
5234
+ }, 201);
5235
+ } catch (err) {
5236
+ console.error("Failed to upload attachment:", err);
5237
+ return c.json({ error: "Failed to upload file" }, 500);
5238
+ }
5239
+ });
5240
+ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
5241
+ const sessionId = c.req.param("id");
5242
+ const attachmentId = c.req.param("attachmentId");
5243
+ const session = sessionQueries.getById(sessionId);
5244
+ if (!session) {
5245
+ return c.json({ error: "Session not found" }, 404);
5246
+ }
5247
+ const dir = getAttachmentsDir(sessionId);
5248
+ if (!existsSync11(dir)) {
5249
+ return c.json({ error: "Attachment not found" }, 404);
5250
+ }
5251
+ const files = readdirSync(dir);
5252
+ const file = files.find((f) => f.startsWith(attachmentId + "_"));
5253
+ if (!file) {
5254
+ return c.json({ error: "Attachment not found" }, 404);
5255
+ }
5256
+ const filePath = join4(dir, file);
5257
+ unlinkSync(filePath);
5258
+ return c.json({ success: true, id: attachmentId });
5259
+ });
5260
+ var filesQuerySchema = z11.object({
5261
+ query: z11.string().optional(),
5262
+ // Filter query (e.g., "src/com" to match "src/components")
5263
+ limit: z11.string().optional()
5264
+ // Max results (default 50)
5265
+ });
5266
+ var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
5267
+ "node_modules",
5268
+ ".git",
5269
+ ".next",
5270
+ "dist",
5271
+ "build",
5272
+ ".turbo",
5273
+ ".cache",
5274
+ "coverage",
5275
+ "__pycache__",
5276
+ ".pytest_cache",
5277
+ "venv",
5278
+ ".venv",
5279
+ "target",
5280
+ // Rust
5281
+ ".idea",
5282
+ ".vscode"
5283
+ ]);
5284
+ var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
5285
+ ".pyc",
5286
+ ".pyo",
5287
+ ".class",
5288
+ ".o",
5289
+ ".obj",
5290
+ ".exe",
5291
+ ".dll",
5292
+ ".so",
5293
+ ".dylib"
5294
+ ]);
5295
+ async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = []) {
5296
+ if (results.length >= limit) {
5297
+ return results;
5298
+ }
5299
+ try {
5300
+ const entries = await readdir4(currentDir, { withFileTypes: true });
5301
+ const queryLower = query.toLowerCase();
5302
+ for (const entry of entries) {
5303
+ if (results.length >= limit) break;
5304
+ const fullPath = join4(currentDir, entry.name);
5305
+ const relativePath = relative7(baseDir, fullPath);
5306
+ if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
5307
+ continue;
5308
+ }
5309
+ if (entry.name.startsWith(".")) {
5310
+ continue;
5311
+ }
5312
+ const ext = extname6(entry.name).toLowerCase();
5313
+ if (IGNORED_EXTENSIONS.has(ext)) {
5314
+ continue;
5315
+ }
5316
+ const matchesQuery = !query || relativePath.toLowerCase().includes(queryLower) || entry.name.toLowerCase().includes(queryLower);
5317
+ if (entry.isDirectory()) {
5318
+ if (matchesQuery) {
5319
+ results.push({
5320
+ path: relativePath,
5321
+ name: entry.name,
5322
+ type: "folder"
5323
+ });
5324
+ }
5325
+ const shouldRecurse = !query || relativePath.toLowerCase().startsWith(queryLower) || queryLower.startsWith(relativePath.toLowerCase());
5326
+ if (shouldRecurse && results.length < limit) {
5327
+ await listWorkspaceFiles(baseDir, fullPath, query, limit, results);
5328
+ }
5329
+ } else if (entry.isFile()) {
5330
+ if (matchesQuery) {
5331
+ results.push({
5332
+ path: relativePath,
5333
+ name: entry.name,
5334
+ type: "file",
5335
+ extension: ext || void 0
5336
+ });
5337
+ }
5338
+ }
5339
+ }
5340
+ } catch {
5341
+ }
5342
+ return results;
5343
+ }
5344
+ sessions2.get(
5345
+ "/:id/files",
5346
+ zValidator("query", filesQuerySchema),
5347
+ async (c) => {
5348
+ const sessionId = c.req.param("id");
5349
+ const { query = "", limit: limitStr = "50" } = c.req.valid("query");
5350
+ const limit = Math.min(parseInt(limitStr) || 50, 100);
5351
+ const session = sessionQueries.getById(sessionId);
5352
+ if (!session) {
5353
+ return c.json({ error: "Session not found" }, 404);
5354
+ }
5355
+ const workingDirectory = session.workingDirectory;
5356
+ if (!existsSync11(workingDirectory)) {
5357
+ return c.json({
5358
+ sessionId,
5359
+ workingDirectory,
5360
+ files: [],
5361
+ count: 0,
5362
+ error: "Working directory does not exist"
5363
+ });
5364
+ }
5365
+ try {
5366
+ const files = await listWorkspaceFiles(
5367
+ workingDirectory,
5368
+ workingDirectory,
5369
+ query,
5370
+ limit
5371
+ );
5372
+ files.sort((a, b) => {
5373
+ if (a.type !== b.type) {
5374
+ return a.type === "folder" ? -1 : 1;
5375
+ }
5376
+ return a.path.localeCompare(b.path);
5377
+ });
5378
+ return c.json({
5379
+ sessionId,
5380
+ workingDirectory,
5381
+ files,
5382
+ count: files.length,
5383
+ query
5384
+ });
5385
+ } catch (err) {
5386
+ console.error("Failed to list workspace files:", err);
5387
+ return c.json({
5388
+ error: "Failed to list files",
5389
+ sessionId,
5390
+ workingDirectory,
5391
+ files: [],
5392
+ count: 0
5393
+ }, 500);
5394
+ }
5395
+ }
5396
+ );
3933
5397
 
3934
5398
  // src/server/routes/agents.ts
3935
5399
  import { Hono as Hono2 } from "hono";
3936
5400
  import { zValidator as zValidator2 } from "@hono/zod-validator";
3937
- import { z as z10 } from "zod";
5401
+ import { z as z12 } from "zod";
5402
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
5403
+ import { join as join5 } from "path";
3938
5404
 
3939
5405
  // src/server/resumable-stream.ts
3940
5406
  import { createResumableStreamContext } from "resumable-stream/generic";
@@ -4009,27 +5475,152 @@ var streamContext = createResumableStreamContext({
4009
5475
  });
4010
5476
 
4011
5477
  // src/server/routes/agents.ts
4012
- import { nanoid as nanoid4 } from "nanoid";
5478
+ import { nanoid as nanoid6 } from "nanoid";
5479
+ var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
5480
+ var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
5481
+ var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
5482
+ function sanitizeToolInput(toolName, input) {
5483
+ if (toolName !== "write_file" || !input || typeof input !== "object") {
5484
+ return input;
5485
+ }
5486
+ const data = input;
5487
+ let changed = false;
5488
+ const next = { ...data };
5489
+ const content = typeof data.content === "string" ? data.content : void 0;
5490
+ if (content && content.length > MAX_TOOL_INPUT_LENGTH) {
5491
+ next.content = `${content.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5492
+ ... (truncated)`;
5493
+ next.contentLength = content.length;
5494
+ next.contentTruncated = true;
5495
+ changed = true;
5496
+ }
5497
+ const oldString = typeof data.old_string === "string" ? data.old_string : void 0;
5498
+ if (oldString && oldString.length > MAX_TOOL_INPUT_LENGTH) {
5499
+ next.old_string = `${oldString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5500
+ ... (truncated)`;
5501
+ next.oldStringLength = oldString.length;
5502
+ next.oldStringTruncated = true;
5503
+ changed = true;
5504
+ }
5505
+ const newString = typeof data.new_string === "string" ? data.new_string : void 0;
5506
+ if (newString && newString.length > MAX_TOOL_INPUT_LENGTH) {
5507
+ next.new_string = `${newString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
5508
+ ... (truncated)`;
5509
+ next.newStringLength = newString.length;
5510
+ next.newStringTruncated = true;
5511
+ changed = true;
5512
+ }
5513
+ if (changed) {
5514
+ console.log("[TOOL-INPUT] Truncated write_file input for streaming payload size");
5515
+ }
5516
+ return changed ? next : input;
5517
+ }
5518
+ function buildToolArgsText(input) {
5519
+ try {
5520
+ return JSON.stringify(input ?? {});
5521
+ } catch {
5522
+ return "{}";
5523
+ }
5524
+ }
5525
+ async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId, toolName, input) {
5526
+ if (toolCallStarts.has(toolCallId)) return;
5527
+ toolCallStarts.add(toolCallId);
5528
+ await writeSSE(JSON.stringify({
5529
+ type: "tool-input-start",
5530
+ toolCallId,
5531
+ toolName
5532
+ }));
5533
+ if (toolName !== "write_file") return;
5534
+ const argsText = buildToolArgsText(input);
5535
+ for (let i = 0; i < argsText.length; i += MAX_TOOL_ARGS_CHUNK) {
5536
+ const chunk = argsText.slice(i, i + MAX_TOOL_ARGS_CHUNK);
5537
+ await writeSSE(JSON.stringify({
5538
+ type: "tool-input-delta",
5539
+ toolCallId,
5540
+ argsTextDelta: chunk
5541
+ }));
5542
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
5543
+ }
5544
+ }
4013
5545
  var agents = new Hono2();
4014
- var runPromptSchema = z10.object({
4015
- prompt: z10.string().min(1)
5546
+ var attachmentSchema = z12.object({
5547
+ type: z12.enum(["image", "file"]),
5548
+ data: z12.string(),
5549
+ // base64 data URL or raw base64
5550
+ mediaType: z12.string().optional(),
5551
+ filename: z12.string().optional()
4016
5552
  });
4017
- var quickStartSchema = z10.object({
4018
- prompt: z10.string().min(1),
4019
- name: z10.string().optional(),
4020
- workingDirectory: z10.string().optional(),
4021
- model: z10.string().optional(),
4022
- toolApprovals: z10.record(z10.string(), z10.boolean()).optional()
5553
+ var runPromptSchema = z12.object({
5554
+ prompt: z12.string(),
5555
+ // Can be empty if attachments are provided
5556
+ attachments: z12.array(attachmentSchema).optional()
5557
+ }).refine(
5558
+ (data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
5559
+ { message: "Either prompt or attachments must be provided" }
5560
+ );
5561
+ var quickStartSchema = z12.object({
5562
+ prompt: z12.string().min(1),
5563
+ name: z12.string().optional(),
5564
+ workingDirectory: z12.string().optional(),
5565
+ model: z12.string().optional(),
5566
+ toolApprovals: z12.record(z12.string(), z12.boolean()).optional()
4023
5567
  });
4024
- var rejectSchema = z10.object({
4025
- reason: z10.string().optional()
5568
+ var rejectSchema = z12.object({
5569
+ reason: z12.string().optional()
4026
5570
  }).optional();
4027
5571
  var streamAbortControllers = /* @__PURE__ */ new Map();
4028
- function createAgentStreamProducer(sessionId, prompt, streamId) {
5572
+ function getAttachmentsDirectory(sessionId) {
5573
+ const appDataDir = getAppDataDirectory();
5574
+ return join5(appDataDir, "attachments", sessionId);
5575
+ }
5576
+ function saveAttachmentToDisk(sessionId, attachment, index) {
5577
+ const attachmentsDir = getAttachmentsDirectory(sessionId);
5578
+ if (!existsSync12(attachmentsDir)) {
5579
+ mkdirSync4(attachmentsDir, { recursive: true });
5580
+ }
5581
+ let filename = attachment.filename;
5582
+ if (!filename) {
5583
+ const ext = getExtensionFromMediaType(attachment.mediaType, attachment.type);
5584
+ filename = `attachment_${index + 1}${ext}`;
5585
+ }
5586
+ let base64Data = attachment.data;
5587
+ if (base64Data.includes(",")) {
5588
+ base64Data = base64Data.split(",")[1];
5589
+ }
5590
+ const filePath = join5(attachmentsDir, filename);
5591
+ const buffer = Buffer.from(base64Data, "base64");
5592
+ writeFileSync3(filePath, buffer);
5593
+ return filePath;
5594
+ }
5595
+ function getExtensionFromMediaType(mediaType, type) {
5596
+ if (!mediaType) {
5597
+ return type === "image" ? ".png" : ".bin";
5598
+ }
5599
+ const mimeToExt = {
5600
+ "image/png": ".png",
5601
+ "image/jpeg": ".jpg",
5602
+ "image/jpg": ".jpg",
5603
+ "image/gif": ".gif",
5604
+ "image/webp": ".webp",
5605
+ "image/svg+xml": ".svg",
5606
+ "application/pdf": ".pdf",
5607
+ "text/plain": ".txt",
5608
+ "text/markdown": ".md",
5609
+ "application/json": ".json",
5610
+ "application/javascript": ".js",
5611
+ "text/javascript": ".js",
5612
+ "text/typescript": ".ts",
5613
+ "text/html": ".html",
5614
+ "text/css": ".css"
5615
+ };
5616
+ return mimeToExt[mediaType] || ".bin";
5617
+ }
5618
+ function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
4029
5619
  return () => {
4030
5620
  const { readable, writable } = new TransformStream();
4031
5621
  const writer = writable.getWriter();
4032
5622
  let writerClosed = false;
5623
+ const toolCallStarts = /* @__PURE__ */ new Set();
4033
5624
  const abortController = new AbortController();
4034
5625
  streamAbortControllers.set(streamId, abortController);
4035
5626
  const writeSSE = async (data) => {
@@ -4058,9 +5649,53 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4058
5649
  try {
4059
5650
  const agent = await Agent.create({ sessionId });
4060
5651
  await writeSSE(JSON.stringify({ type: "data-stream-id", streamId }));
5652
+ let broadcastContent;
5653
+ if (attachments && attachments.length > 0) {
5654
+ const contentParts = [];
5655
+ const attachmentDescriptions = attachments.map((a, i) => {
5656
+ const name = a.filename || `attachment_${i + 1}`;
5657
+ const typeLabel = a.type === "image" ? "Image" : "File";
5658
+ const location = a.savedPath || "(path unknown)";
5659
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
5660
+ }).join("\n");
5661
+ contentParts.push({
5662
+ type: "text",
5663
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
5664
+ ${attachmentDescriptions}
5665
+
5666
+ You can reference these files by their paths above. The file contents are also shown inline below.`
5667
+ });
5668
+ if (prompt) {
5669
+ contentParts.push({ type: "text", text: `
5670
+ [USER MESSAGE]
5671
+ ${prompt}` });
5672
+ }
5673
+ for (const attachment of attachments) {
5674
+ if (attachment.type === "image") {
5675
+ contentParts.push({
5676
+ type: "image",
5677
+ image: attachment.data,
5678
+ mediaType: attachment.mediaType,
5679
+ filename: attachment.filename,
5680
+ savedPath: attachment.savedPath
5681
+ });
5682
+ } else {
5683
+ contentParts.push({
5684
+ type: "file",
5685
+ data: attachment.data,
5686
+ mediaType: attachment.mediaType || "application/octet-stream",
5687
+ filename: attachment.filename,
5688
+ savedPath: attachment.savedPath
5689
+ });
5690
+ }
5691
+ }
5692
+ broadcastContent = contentParts;
5693
+ } else {
5694
+ broadcastContent = prompt;
5695
+ }
4061
5696
  await writeSSE(JSON.stringify({
4062
5697
  type: "data-user-message",
4063
- data: { id: `user_${Date.now()}`, content: prompt }
5698
+ data: { id: `user_${Date.now()}`, content: broadcastContent }
4064
5699
  }));
4065
5700
  const messageId = `msg_${Date.now()}`;
4066
5701
  await writeSSE(JSON.stringify({ type: "start", messageId }));
@@ -4068,6 +5703,8 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4068
5703
  let textStarted = false;
4069
5704
  const result = await agent.stream({
4070
5705
  prompt,
5706
+ attachments,
5707
+ // Pass attachments to agent
4071
5708
  abortSignal: abortController.signal,
4072
5709
  // Use our managed abort controller, NOT client signal
4073
5710
  skipSaveUserMessage: true,
@@ -4092,11 +5729,32 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4092
5729
  }));
4093
5730
  },
4094
5731
  onToolProgress: async (progress) => {
5732
+ const status = progress.data?.status || "no-status";
5733
+ const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
5734
+ const chunkIndex = progress.data?.chunkIndex;
5735
+ const chunkCount = progress.data?.chunkCount;
5736
+ console.log(
5737
+ "[TOOL-PROGRESS] Sending:",
5738
+ progress.toolName,
5739
+ status,
5740
+ contentLength !== void 0 ? `contentLength=${contentLength}` : "",
5741
+ chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
5742
+ );
4095
5743
  await writeSSE(JSON.stringify({
4096
5744
  type: "tool-progress",
4097
5745
  toolName: progress.toolName,
4098
5746
  data: progress.data
4099
5747
  }));
5748
+ if (progress.toolName === "write_file" && status === "content") {
5749
+ await writeSSE(JSON.stringify({
5750
+ type: "debug",
5751
+ label: "write-file-progress",
5752
+ contentLength,
5753
+ chunkIndex,
5754
+ chunkCount
5755
+ }));
5756
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
5757
+ }
4100
5758
  },
4101
5759
  onStepFinish: async () => {
4102
5760
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -4138,6 +5796,7 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4138
5796
  toolCallId: p.toolCallId,
4139
5797
  toolName: p.toolName
4140
5798
  }));
5799
+ toolCallStarts.add(p.toolCallId);
4141
5800
  } else if (part.type === "tool-call-delta") {
4142
5801
  const p = part;
4143
5802
  await writeSSE(JSON.stringify({
@@ -4146,11 +5805,23 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4146
5805
  argsTextDelta: p.argsTextDelta
4147
5806
  }));
4148
5807
  } else if (part.type === "tool-call") {
5808
+ await emitSyntheticToolStreaming(
5809
+ writeSSE,
5810
+ toolCallStarts,
5811
+ part.toolCallId,
5812
+ part.toolName,
5813
+ part.input
5814
+ );
4149
5815
  await writeSSE(JSON.stringify({
4150
5816
  type: "tool-input-available",
4151
5817
  toolCallId: part.toolCallId,
4152
5818
  toolName: part.toolName,
4153
- input: part.input
5819
+ input: sanitizeToolInput(part.toolName, part.input)
5820
+ }));
5821
+ await writeSSE(JSON.stringify({
5822
+ type: "debug",
5823
+ label: "tool-input-available",
5824
+ toolName: part.toolName
4154
5825
  }));
4155
5826
  } else if (part.type === "tool-result") {
4156
5827
  await writeSSE(JSON.stringify({
@@ -4177,14 +5848,20 @@ function createAgentStreamProducer(sessionId, prompt, streamId) {
4177
5848
  } else {
4178
5849
  await writeSSE(JSON.stringify({ type: "finish" }));
4179
5850
  }
4180
- activeStreamQueries.finish(streamId);
5851
+ try {
5852
+ activeStreamQueries.finish(streamId);
5853
+ } catch {
5854
+ }
4181
5855
  } catch (error) {
4182
5856
  if (error.name === "AbortError" || error.message?.includes("aborted")) {
4183
5857
  await writeSSE(JSON.stringify({ type: "abort" }));
4184
5858
  } else {
4185
5859
  console.error("Agent error:", error);
4186
5860
  await writeSSE(JSON.stringify({ type: "error", errorText: error.message }));
4187
- activeStreamQueries.markError(streamId);
5861
+ try {
5862
+ activeStreamQueries.markError(streamId);
5863
+ } catch {
5864
+ }
4188
5865
  }
4189
5866
  } finally {
4190
5867
  cleanupAbortController();
@@ -4200,19 +5877,74 @@ agents.post(
4200
5877
  zValidator2("json", runPromptSchema),
4201
5878
  async (c) => {
4202
5879
  const id = c.req.param("id");
4203
- const { prompt } = c.req.valid("json");
5880
+ const { prompt, attachments } = c.req.valid("json");
4204
5881
  const session = sessionQueries.getById(id);
4205
5882
  if (!session) {
4206
5883
  return c.json({ error: "Session not found" }, 404);
4207
5884
  }
4208
5885
  const nextSequence = messageQueries.getNextSequence(id);
4209
5886
  await createCheckpoint(id, session.workingDirectory, nextSequence);
4210
- messageQueries.create(id, { role: "user", content: prompt });
4211
- const streamId = `stream_${id}_${nanoid4(10)}`;
5887
+ let userMessageContent;
5888
+ const streamAttachments = attachments;
5889
+ if (streamAttachments && streamAttachments.length > 0) {
5890
+ for (let i = 0; i < streamAttachments.length; i++) {
5891
+ const attachment = streamAttachments[i];
5892
+ try {
5893
+ const savedPath = saveAttachmentToDisk(id, attachment, i);
5894
+ attachment.savedPath = savedPath;
5895
+ } catch (err) {
5896
+ console.error(`Failed to save attachment ${i}:`, err);
5897
+ }
5898
+ }
5899
+ const contentParts = [];
5900
+ const attachmentDescriptions = streamAttachments.map((a, i) => {
5901
+ const name = a.filename || `attachment_${i + 1}`;
5902
+ const typeLabel = a.type === "image" ? "Image" : "File";
5903
+ const location = a.savedPath || "(path unknown)";
5904
+ return `${i + 1}. ${typeLabel}: "${name}" saved at: ${location}`;
5905
+ }).join("\n");
5906
+ contentParts.push({
5907
+ type: "text",
5908
+ text: `[FILE ATTACHMENTS - The user has attached the following files which are saved on disk]
5909
+ ${attachmentDescriptions}
5910
+
5911
+ You can reference these files by their paths above. The file contents are also shown inline below.`
5912
+ });
5913
+ if (prompt) {
5914
+ contentParts.push({ type: "text", text: `
5915
+ [USER MESSAGE]
5916
+ ${prompt}` });
5917
+ }
5918
+ for (const attachment of streamAttachments) {
5919
+ if (attachment.type === "image") {
5920
+ contentParts.push({
5921
+ type: "image",
5922
+ image: attachment.data,
5923
+ // base64 data URL or raw base64
5924
+ mediaType: attachment.mediaType,
5925
+ filename: attachment.filename,
5926
+ savedPath: attachment.savedPath
5927
+ });
5928
+ } else {
5929
+ contentParts.push({
5930
+ type: "file",
5931
+ data: attachment.data,
5932
+ mediaType: attachment.mediaType || "application/octet-stream",
5933
+ filename: attachment.filename,
5934
+ savedPath: attachment.savedPath
5935
+ });
5936
+ }
5937
+ }
5938
+ userMessageContent = contentParts;
5939
+ } else {
5940
+ userMessageContent = prompt;
5941
+ }
5942
+ messageQueries.create(id, { role: "user", content: userMessageContent });
5943
+ const streamId = `stream_${id}_${nanoid6(10)}`;
4212
5944
  activeStreamQueries.create(id, streamId);
4213
5945
  const stream = await streamContext.resumableStream(
4214
5946
  streamId,
4215
- createAgentStreamProducer(id, prompt, streamId)
5947
+ createAgentStreamProducer(id, prompt, streamId, streamAttachments)
4216
5948
  );
4217
5949
  if (!stream) {
4218
5950
  return c.json({ error: "Failed to create stream" }, 500);
@@ -4405,13 +6137,14 @@ agents.post(
4405
6137
  sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
4406
6138
  });
4407
6139
  const session = agent.getSession();
4408
- const streamId = `stream_${session.id}_${nanoid4(10)}`;
6140
+ const streamId = `stream_${session.id}_${nanoid6(10)}`;
4409
6141
  await createCheckpoint(session.id, session.workingDirectory, 0);
4410
6142
  activeStreamQueries.create(session.id, streamId);
4411
6143
  const createQuickStreamProducer = () => {
4412
6144
  const { readable, writable } = new TransformStream();
4413
6145
  const writer = writable.getWriter();
4414
6146
  let writerClosed = false;
6147
+ const toolCallStarts = /* @__PURE__ */ new Set();
4415
6148
  const abortController = new AbortController();
4416
6149
  streamAbortControllers.set(streamId, abortController);
4417
6150
  const writeSSE = async (data) => {
@@ -4457,11 +6190,32 @@ agents.post(
4457
6190
  abortSignal: abortController.signal,
4458
6191
  // Use our managed abort controller, NOT client signal
4459
6192
  onToolProgress: async (progress) => {
6193
+ const status = progress.data?.status || "no-status";
6194
+ const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
6195
+ const chunkIndex = progress.data?.chunkIndex;
6196
+ const chunkCount = progress.data?.chunkCount;
6197
+ console.log(
6198
+ "[TOOL-PROGRESS] Sending:",
6199
+ progress.toolName,
6200
+ status,
6201
+ contentLength !== void 0 ? `contentLength=${contentLength}` : "",
6202
+ chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
6203
+ );
4460
6204
  await writeSSE(JSON.stringify({
4461
6205
  type: "tool-progress",
4462
6206
  toolName: progress.toolName,
4463
6207
  data: progress.data
4464
6208
  }));
6209
+ if (progress.toolName === "write_file" && status === "content") {
6210
+ await writeSSE(JSON.stringify({
6211
+ type: "debug",
6212
+ label: "write-file-progress",
6213
+ contentLength,
6214
+ chunkIndex,
6215
+ chunkCount
6216
+ }));
6217
+ await new Promise((resolve10) => setTimeout(resolve10, 0));
6218
+ }
4465
6219
  },
4466
6220
  onStepFinish: async () => {
4467
6221
  await writeSSE(JSON.stringify({ type: "finish-step" }));
@@ -4503,6 +6257,7 @@ agents.post(
4503
6257
  toolCallId: p.toolCallId,
4504
6258
  toolName: p.toolName
4505
6259
  }));
6260
+ toolCallStarts.add(p.toolCallId);
4506
6261
  } else if (part.type === "tool-call-delta") {
4507
6262
  const p = part;
4508
6263
  await writeSSE(JSON.stringify({
@@ -4511,11 +6266,23 @@ agents.post(
4511
6266
  argsTextDelta: p.argsTextDelta
4512
6267
  }));
4513
6268
  } else if (part.type === "tool-call") {
6269
+ await emitSyntheticToolStreaming(
6270
+ writeSSE,
6271
+ toolCallStarts,
6272
+ part.toolCallId,
6273
+ part.toolName,
6274
+ part.input
6275
+ );
4514
6276
  await writeSSE(JSON.stringify({
4515
6277
  type: "tool-input-available",
4516
6278
  toolCallId: part.toolCallId,
4517
6279
  toolName: part.toolName,
4518
- input: part.input
6280
+ input: sanitizeToolInput(part.toolName, part.input)
6281
+ }));
6282
+ await writeSSE(JSON.stringify({
6283
+ type: "debug",
6284
+ label: "tool-input-available",
6285
+ toolName: part.toolName
4519
6286
  }));
4520
6287
  } else if (part.type === "tool-result") {
4521
6288
  await writeSSE(JSON.stringify({
@@ -4583,7 +6350,28 @@ agents.post(
4583
6350
  // src/server/routes/health.ts
4584
6351
  import { Hono as Hono3 } from "hono";
4585
6352
  import { zValidator as zValidator3 } from "@hono/zod-validator";
4586
- import { z as z11 } from "zod";
6353
+ import { z as z13 } from "zod";
6354
+ import { readFileSync as readFileSync3 } from "fs";
6355
+ import { fileURLToPath as fileURLToPath2 } from "url";
6356
+ import { dirname as dirname6, join as join6 } from "path";
6357
+ var __filename = fileURLToPath2(import.meta.url);
6358
+ var __dirname = dirname6(__filename);
6359
+ var packageJsonPath = join6(__dirname, "../../../package.json");
6360
+ var currentVersion = "0.0.0";
6361
+ var packageName = "sparkecoder";
6362
+ try {
6363
+ const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
6364
+ currentVersion = packageJson.version || "0.0.0";
6365
+ packageName = packageJson.name || "sparkecoder";
6366
+ } catch {
6367
+ try {
6368
+ const devPackageJsonPath = join6(__dirname, "../../package.json");
6369
+ const packageJson = JSON.parse(readFileSync3(devPackageJsonPath, "utf-8"));
6370
+ currentVersion = packageJson.version || "0.0.0";
6371
+ packageName = packageJson.name || "sparkecoder";
6372
+ } catch {
6373
+ }
6374
+ }
4587
6375
  var health = new Hono3();
4588
6376
  health.get("/", async (c) => {
4589
6377
  const config = getConfig();
@@ -4592,7 +6380,7 @@ health.get("/", async (c) => {
4592
6380
  const hasApiKey = gatewayKey?.configured ?? false;
4593
6381
  return c.json({
4594
6382
  status: "ok",
4595
- version: "0.1.0",
6383
+ version: currentVersion,
4596
6384
  uptime: process.uptime(),
4597
6385
  apiKeyConfigured: hasApiKey,
4598
6386
  config: {
@@ -4604,6 +6392,42 @@ health.get("/", async (c) => {
4604
6392
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4605
6393
  });
4606
6394
  });
6395
+ health.get("/version", async (c) => {
6396
+ let latestVersion = currentVersion;
6397
+ let updateAvailable = false;
6398
+ let error;
6399
+ try {
6400
+ const npmResponse = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
6401
+ headers: { "Accept": "application/json" },
6402
+ signal: AbortSignal.timeout(5e3)
6403
+ // 5 second timeout
6404
+ });
6405
+ if (npmResponse.ok) {
6406
+ const npmData = await npmResponse.json();
6407
+ latestVersion = npmData.version || currentVersion;
6408
+ const parseVersion = (v) => {
6409
+ const parts = v.replace(/^v/, "").split(".").map(Number);
6410
+ return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
6411
+ };
6412
+ const current = parseVersion(currentVersion);
6413
+ const latest = parseVersion(latestVersion);
6414
+ 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;
6415
+ } else {
6416
+ error = `npm registry returned ${npmResponse.status}`;
6417
+ }
6418
+ } catch (err) {
6419
+ error = err instanceof Error ? err.message : "Failed to check for updates";
6420
+ }
6421
+ return c.json({
6422
+ packageName,
6423
+ currentVersion,
6424
+ latestVersion,
6425
+ updateAvailable,
6426
+ updateCommand: updateAvailable ? `npm install -g ${packageName}@latest` : null,
6427
+ error,
6428
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
6429
+ });
6430
+ });
4607
6431
  health.get("/ready", async (c) => {
4608
6432
  try {
4609
6433
  getConfig();
@@ -4629,9 +6453,9 @@ health.get("/api-keys", async (c) => {
4629
6453
  supportedProviders: SUPPORTED_PROVIDERS
4630
6454
  });
4631
6455
  });
4632
- var setApiKeySchema = z11.object({
4633
- provider: z11.string(),
4634
- apiKey: z11.string().min(1)
6456
+ var setApiKeySchema = z13.object({
6457
+ provider: z13.string(),
6458
+ apiKey: z13.string().min(1)
4635
6459
  });
4636
6460
  health.post(
4637
6461
  "/api-keys",
@@ -4670,12 +6494,12 @@ health.delete("/api-keys/:provider", async (c) => {
4670
6494
  // src/server/routes/terminals.ts
4671
6495
  import { Hono as Hono4 } from "hono";
4672
6496
  import { zValidator as zValidator4 } from "@hono/zod-validator";
4673
- import { z as z12 } from "zod";
6497
+ import { z as z14 } from "zod";
4674
6498
  var terminals2 = new Hono4();
4675
- var spawnSchema = z12.object({
4676
- command: z12.string(),
4677
- cwd: z12.string().optional(),
4678
- name: z12.string().optional()
6499
+ var spawnSchema = z14.object({
6500
+ command: z14.string(),
6501
+ cwd: z14.string().optional(),
6502
+ name: z14.string().optional()
4679
6503
  });
4680
6504
  terminals2.post(
4681
6505
  "/:sessionId/terminals",
@@ -4756,8 +6580,8 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
4756
6580
  // We don't track exit codes in tmux mode
4757
6581
  });
4758
6582
  });
4759
- var logsQuerySchema = z12.object({
4760
- tail: z12.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
6583
+ var logsQuerySchema = z14.object({
6584
+ tail: z14.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
4761
6585
  });
4762
6586
  terminals2.get(
4763
6587
  "/:sessionId/terminals/:terminalId/logs",
@@ -4781,8 +6605,8 @@ terminals2.get(
4781
6605
  });
4782
6606
  }
4783
6607
  );
4784
- var killSchema = z12.object({
4785
- signal: z12.enum(["SIGTERM", "SIGKILL"]).optional()
6608
+ var killSchema = z14.object({
6609
+ signal: z14.enum(["SIGTERM", "SIGKILL"]).optional()
4786
6610
  });
4787
6611
  terminals2.post(
4788
6612
  "/:sessionId/terminals/:terminalId/kill",
@@ -4796,8 +6620,8 @@ terminals2.post(
4796
6620
  return c.json({ success: true, message: "Terminal killed" });
4797
6621
  }
4798
6622
  );
4799
- var writeSchema = z12.object({
4800
- input: z12.string()
6623
+ var writeSchema = z14.object({
6624
+ input: z14.string()
4801
6625
  });
4802
6626
  terminals2.post(
4803
6627
  "/:sessionId/terminals/:terminalId/write",
@@ -4979,10 +6803,10 @@ data: ${JSON.stringify({ status: "stopped" })}
4979
6803
  });
4980
6804
 
4981
6805
  // src/utils/dependencies.ts
4982
- import { exec as exec4 } from "child_process";
4983
- import { promisify as promisify4 } from "util";
6806
+ import { exec as exec5 } from "child_process";
6807
+ import { promisify as promisify5 } from "util";
4984
6808
  import { platform as platform2 } from "os";
4985
- var execAsync4 = promisify4(exec4);
6809
+ var execAsync5 = promisify5(exec5);
4986
6810
  function getInstallInstructions() {
4987
6811
  const os2 = platform2();
4988
6812
  if (os2 === "darwin") {
@@ -5015,7 +6839,7 @@ Install tmux:
5015
6839
  }
5016
6840
  async function checkTmux() {
5017
6841
  try {
5018
- const { stdout } = await execAsync4("tmux -V", { timeout: 5e3 });
6842
+ const { stdout } = await execAsync5("tmux -V", { timeout: 5e3 });
5019
6843
  const version = stdout.trim();
5020
6844
  return {
5021
6845
  available: true,
@@ -5062,13 +6886,13 @@ var DEFAULT_WEB_PORT = 6969;
5062
6886
  var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
5063
6887
  function getWebDirectory() {
5064
6888
  try {
5065
- const currentDir = dirname6(fileURLToPath2(import.meta.url));
5066
- const webDir = resolve8(currentDir, "..", "web");
5067
- if (existsSync10(webDir) && existsSync10(join3(webDir, "package.json"))) {
6889
+ const currentDir = dirname7(fileURLToPath3(import.meta.url));
6890
+ const webDir = resolve9(currentDir, "..", "web");
6891
+ if (existsSync13(webDir) && existsSync13(join7(webDir, "package.json"))) {
5068
6892
  return webDir;
5069
6893
  }
5070
- const altWebDir = resolve8(currentDir, "..", "..", "web");
5071
- if (existsSync10(altWebDir) && existsSync10(join3(altWebDir, "package.json"))) {
6894
+ const altWebDir = resolve9(currentDir, "..", "..", "web");
6895
+ if (existsSync13(altWebDir) && existsSync13(join7(altWebDir, "package.json"))) {
5072
6896
  return altWebDir;
5073
6897
  }
5074
6898
  return null;
@@ -5091,18 +6915,18 @@ async function isSparkcoderWebRunning(port) {
5091
6915
  }
5092
6916
  }
5093
6917
  function isPortInUse(port) {
5094
- return new Promise((resolve9) => {
6918
+ return new Promise((resolve10) => {
5095
6919
  const server = createNetServer();
5096
6920
  server.once("error", (err) => {
5097
6921
  if (err.code === "EADDRINUSE") {
5098
- resolve9(true);
6922
+ resolve10(true);
5099
6923
  } else {
5100
- resolve9(false);
6924
+ resolve10(false);
5101
6925
  }
5102
6926
  });
5103
6927
  server.once("listening", () => {
5104
6928
  server.close();
5105
- resolve9(false);
6929
+ resolve10(false);
5106
6930
  });
5107
6931
  server.listen(port, "0.0.0.0");
5108
6932
  });
@@ -5126,30 +6950,30 @@ async function findWebPort(preferredPort) {
5126
6950
  return { port: preferredPort, alreadyRunning: false };
5127
6951
  }
5128
6952
  function hasProductionBuild(webDir) {
5129
- const buildIdPath = join3(webDir, ".next", "BUILD_ID");
5130
- return existsSync10(buildIdPath);
6953
+ const buildIdPath = join7(webDir, ".next", "BUILD_ID");
6954
+ return existsSync13(buildIdPath);
5131
6955
  }
5132
6956
  function hasSourceFiles(webDir) {
5133
- const appDir = join3(webDir, "src", "app");
5134
- const pagesDir = join3(webDir, "src", "pages");
5135
- const rootAppDir = join3(webDir, "app");
5136
- const rootPagesDir = join3(webDir, "pages");
5137
- return existsSync10(appDir) || existsSync10(pagesDir) || existsSync10(rootAppDir) || existsSync10(rootPagesDir);
6957
+ const appDir = join7(webDir, "src", "app");
6958
+ const pagesDir = join7(webDir, "src", "pages");
6959
+ const rootAppDir = join7(webDir, "app");
6960
+ const rootPagesDir = join7(webDir, "pages");
6961
+ return existsSync13(appDir) || existsSync13(pagesDir) || existsSync13(rootAppDir) || existsSync13(rootPagesDir);
5138
6962
  }
5139
6963
  function getStandaloneServerPath(webDir) {
5140
6964
  const possiblePaths = [
5141
- join3(webDir, ".next", "standalone", "server.js"),
5142
- join3(webDir, ".next", "standalone", "web", "server.js")
6965
+ join7(webDir, ".next", "standalone", "server.js"),
6966
+ join7(webDir, ".next", "standalone", "web", "server.js")
5143
6967
  ];
5144
6968
  for (const serverPath of possiblePaths) {
5145
- if (existsSync10(serverPath)) {
6969
+ if (existsSync13(serverPath)) {
5146
6970
  return serverPath;
5147
6971
  }
5148
6972
  }
5149
6973
  return null;
5150
6974
  }
5151
6975
  function runCommand(command, args, cwd, env) {
5152
- return new Promise((resolve9) => {
6976
+ return new Promise((resolve10) => {
5153
6977
  const child = spawn2(command, args, {
5154
6978
  cwd,
5155
6979
  stdio: ["ignore", "pipe", "pipe"],
@@ -5164,10 +6988,10 @@ function runCommand(command, args, cwd, env) {
5164
6988
  output += data.toString();
5165
6989
  });
5166
6990
  child.on("close", (code) => {
5167
- resolve9({ success: code === 0, output });
6991
+ resolve10({ success: code === 0, output });
5168
6992
  });
5169
6993
  child.on("error", (err) => {
5170
- resolve9({ success: false, output: err.message });
6994
+ resolve10({ success: false, output: err.message });
5171
6995
  });
5172
6996
  });
5173
6997
  }
@@ -5182,15 +7006,15 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5182
7006
  if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
5183
7007
  return { process: null, port: actualPort };
5184
7008
  }
5185
- const usePnpm = existsSync10(join3(webDir, "pnpm-lock.yaml"));
5186
- const useNpm = !usePnpm && existsSync10(join3(webDir, "package-lock.json"));
7009
+ const usePnpm = existsSync13(join7(webDir, "pnpm-lock.yaml"));
7010
+ const useNpm = !usePnpm && existsSync13(join7(webDir, "package-lock.json"));
5187
7011
  const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
5188
7012
  const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
5189
7013
  const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
5190
7014
  const runtimeConfig = { apiBaseUrl: apiUrl };
5191
- const runtimeConfigPath = join3(webDir, "runtime-config.json");
7015
+ const runtimeConfigPath = join7(webDir, "runtime-config.json");
5192
7016
  try {
5193
- writeFileSync2(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
7017
+ writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
5194
7018
  if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
5195
7019
  } catch (err) {
5196
7020
  if (!quiet) console.warn(` \u26A0 Could not write runtime config: ${err}`);
@@ -5210,13 +7034,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5210
7034
  if (standaloneServerPath) {
5211
7035
  command = "node";
5212
7036
  args = ["server.js"];
5213
- cwd = dirname6(standaloneServerPath);
7037
+ cwd = dirname7(standaloneServerPath);
5214
7038
  webEnv.PORT = String(actualPort);
5215
7039
  webEnv.HOSTNAME = "0.0.0.0";
5216
7040
  if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
5217
7041
  } else if (hasBuild && (isProduction || !hasSource)) {
5218
7042
  command = pkgManager;
5219
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
7043
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5220
7044
  } else if (hasSource) {
5221
7045
  if (isProduction && !hasBuild) {
5222
7046
  if (!quiet) console.log(" \u{1F4E6} Building Web UI for production...");
@@ -5228,10 +7052,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5228
7052
  }
5229
7053
  if (!quiet) console.log(" \u2713 Web UI build complete");
5230
7054
  command = pkgManager;
5231
- args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start", "--", "-p", String(actualPort)];
7055
+ args = pkgManager === "npx" ? ["next", "start", "-p", String(actualPort)] : ["run", "start"];
5232
7056
  } else {
5233
7057
  command = pkgManager;
5234
- args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev", "--", "-p", String(actualPort)];
7058
+ args = pkgManager === "npx" ? ["next", "dev", "-p", String(actualPort)] : ["run", "dev"];
5235
7059
  }
5236
7060
  } else {
5237
7061
  if (!quiet) {
@@ -5251,37 +7075,43 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
5251
7075
  let started = false;
5252
7076
  let exited = false;
5253
7077
  let exitCode = null;
5254
- const startedPromise = new Promise((resolve9) => {
7078
+ const startedPromise = new Promise((resolve10) => {
5255
7079
  const timeout = setTimeout(() => {
5256
7080
  if (!started && !exited) {
5257
- resolve9(false);
7081
+ resolve10(false);
5258
7082
  }
5259
7083
  }, startupTimeout);
5260
7084
  child.stdout?.on("data", (data) => {
5261
7085
  const output = data.toString();
7086
+ if (!quiet) {
7087
+ const lines = output.trim().split("\n").filter((l) => l.trim());
7088
+ for (const line of lines) {
7089
+ console.log(` Web UI: ${line}`);
7090
+ }
7091
+ }
5262
7092
  if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
5263
7093
  started = true;
5264
7094
  clearTimeout(timeout);
5265
- resolve9(true);
7095
+ resolve10(true);
5266
7096
  }
5267
7097
  });
5268
7098
  child.stderr?.on("data", (data) => {
5269
- const output = data.toString();
5270
- if (output.toLowerCase().includes("error")) {
5271
- if (!quiet) console.error(` Web UI error: ${output.trim().slice(0, 200)}`);
7099
+ const output = data.toString().trim();
7100
+ if (!quiet && output) {
7101
+ console.error(` Web UI: ${output.slice(0, 500)}`);
5272
7102
  }
5273
7103
  });
5274
7104
  child.on("error", (err) => {
5275
7105
  if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
5276
7106
  clearTimeout(timeout);
5277
- resolve9(false);
7107
+ resolve10(false);
5278
7108
  });
5279
7109
  child.on("exit", (code) => {
5280
7110
  exited = true;
5281
7111
  exitCode = code;
5282
7112
  if (!started) {
5283
7113
  clearTimeout(timeout);
5284
- resolve9(false);
7114
+ resolve10(false);
5285
7115
  }
5286
7116
  webUIProcess = null;
5287
7117
  });
@@ -5374,8 +7204,8 @@ async function startServer(options = {}) {
5374
7204
  if (options.workingDirectory) {
5375
7205
  config.resolvedWorkingDirectory = options.workingDirectory;
5376
7206
  }
5377
- if (!existsSync10(config.resolvedWorkingDirectory)) {
5378
- mkdirSync3(config.resolvedWorkingDirectory, { recursive: true });
7207
+ if (!existsSync13(config.resolvedWorkingDirectory)) {
7208
+ mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
5379
7209
  if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
5380
7210
  }
5381
7211
  initDatabase(config.resolvedDatabasePath);