sparkecoder 0.1.22 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent/index.d.ts +3 -3
- package/dist/agent/index.js +1308 -212
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +1933 -454
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +20 -3
- package/dist/db/index.js +97 -0
- package/dist/db/index.js.map +1 -1
- package/dist/{index-CNwLFGiZ.d.ts → index-BblbmG_0.d.ts} +19 -4
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1913 -434
- package/dist/index.js.map +1 -1
- package/dist/{schema-Df7MU3nM.d.ts → schema-D_8A4k01.d.ts} +246 -2
- package/dist/search-ybREg7F_.d.ts +254 -0
- package/dist/server/index.js +1918 -439
- package/dist/server/index.js.map +1 -1
- package/dist/tools/index.d.ts +7 -56
- package/dist/tools/index.js +894 -27
- package/dist/tools/index.js.map +1 -1
- package/package.json +5 -1
- package/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/BUILD_ID +1 -1
- package/web/.next/standalone/web/.next/app-path-routes-manifest.json +4 -0
- package/web/.next/standalone/web/.next/build-manifest.json +7 -6
- package/web/.next/standalone/web/.next/prerender-manifest.json +99 -3
- package/web/.next/standalone/web/.next/required-server-files.json +28 -4
- package/web/.next/standalone/web/.next/routes-manifest.json +24 -0
- package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/(main)/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
- package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +5 -4
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +86 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +36 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +36 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +22 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +268 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +82 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +82 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +66 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +242 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +87 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +87 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +72 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/docs.html +74 -0
- package/web/.next/standalone/web/.next/server/app/docs.meta +15 -0
- package/web/.next/standalone/web/.next/server/app/docs.rsc +34 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +34 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +3 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +20 -0
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app/index.html +1 -1
- package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
- package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app-paths-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_5f58fd73._.js → 2374f_244589df._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_84859a94._.js → 2374f_41a27541._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_65fcfd95._.js → 2374f_47c9e2d5._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f1038f7c._.js → 2374f_4bf2df9d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_663d1038._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_954e49c0._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_387a1437._.js → 2374f_c33b095a._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_cfd0137a._.js → 2374f_fa61fbb2._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_741f6b67._.js → 2374f_fb82ac0d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_96bca05b._.js → 2374f_next_dist_bbe64674._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__7f04455b._.js → [root-of-the-server]__1e06ddf7._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2b151e1c._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2dbf511a._.js +9 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__397fadd4._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__44bd8bd1._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__70cecda8._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9fdf9974._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__f18f92f4._.js → [root-of-the-server]__b050bb8f._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__d3034cd2._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{[root-of-the-server]__c3a1e22c._.js → [root-of-the-server]__ef2713cf._.js} +2 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__f764bebe._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_046bf7db._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_656c1e45._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_c7618534._.js → web_76ccf09f._.js} +4 -4
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_installation_page_actions_52cc0648.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_page_actions_4fe77da8.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_skills_page_actions_251df2e1.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_docs_tools_page_actions_3e6382b0.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_a565dc94._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_b1cce0b7._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_b42ed1be._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_c0c2bee4._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_eea9c122._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_ff00a5c3._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +3 -0
- package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +5 -4
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +16 -0
- package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
- package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
- package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
- package/web/.next/standalone/web/.next/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/standalone/web/.next/static/{static/chunks/5e5b485d77ac0d8f.js → chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/standalone/web/.next/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/standalone/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/af22745850132107.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/standalone/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/.next/standalone/web/.next/static/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/{static/chunks/5e5b485d77ac0d8f.js → standalone/web/.next/static/static/chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/standalone/web/.next/static/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/standalone/web/.next/static/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/af22745850132107.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/.next/standalone/web/mdx-components.tsx +119 -0
- package/web/.next/standalone/web/next.config.ts +15 -1
- package/web/.next/standalone/web/package-lock.json +559 -4
- package/web/.next/standalone/web/package.json +4 -0
- package/web/.next/standalone/web/runtime-config.json +1 -1
- package/web/.next/standalone/web/server.js +1 -1
- package/web/.next/standalone/web/src/app/(main)/page.tsx +127 -5
- package/web/.next/standalone/web/src/app/docs/installation/page.mdx +128 -0
- package/web/.next/standalone/web/src/app/docs/layout.tsx +74 -0
- package/web/.next/standalone/web/src/app/docs/page.mdx +90 -0
- package/web/.next/standalone/web/src/app/docs/skills/page.mdx +334 -0
- package/web/.next/standalone/web/src/app/docs/tools/page.mdx +300 -0
- package/web/.next/standalone/web/src/components/ai-elements/mention-input.tsx +809 -0
- package/web/.next/standalone/web/src/components/ai-elements/search-tool.tsx +400 -0
- package/web/.next/standalone/web/src/components/ai-elements/subagent-modal.tsx +275 -0
- package/web/.next/standalone/web/src/components/ai-elements/write-file-tool.tsx +19 -5
- package/web/.next/standalone/web/src/components/chat-interface.tsx +525 -71
- package/web/.next/standalone/web/src/hooks/use-workspace-files.ts +108 -0
- package/web/.next/standalone/web/src/lib/api.ts +90 -4
- package/web/.next/static/chunks/0cc382a66266188e.js +7 -0
- package/web/.next/static/chunks/0fda34e553582102.js +1 -0
- package/web/.next/{standalone/web/.next/static/chunks/5e5b485d77ac0d8f.js → static/chunks/6407c045dfc908fe.js} +3 -3
- package/web/.next/static/chunks/651e187cc15d66de.js +1 -0
- package/web/.next/static/chunks/862ced58ce21a270.js +4 -0
- package/web/.next/static/chunks/89bc21c0443670f4.js +1 -0
- package/web/.next/static/chunks/8f4edf22ededc29b.js +7 -0
- package/web/.next/static/chunks/ad6b9dbb257d62cc.js +1 -0
- package/web/.next/static/chunks/af22745850132107.css +1 -0
- package/web/.next/static/chunks/b9ad1584d4e11d12.js +1 -0
- package/web/.next/static/chunks/db9b22c844a35e20.js +5 -0
- package/web/.next/static/chunks/turbopack-597558bb7b6982f6.js +4 -0
- package/web/package.json +4 -0
- package/dist/bash-CGAqW7HR.d.ts +0 -80
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_814be2c9._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__0f6b5fa7._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__3ec22171._.js +0 -9
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__513c6b45._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de58a952._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_d7d3e40d._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_e6034803._.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/standalone/web/.next/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- package/web/.next/standalone/web/.next/static/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/standalone/web/.next/static/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- package/web/.next/static/chunks/03d4169891280e04.js +0 -7
- package/web/.next/static/chunks/2d5da0cfc011b8d9.js +0 -1
- package/web/.next/static/chunks/3bb454ca848ec78e.js +0 -7
- package/web/.next/static/chunks/634fd97fab9ed4e4.js +0 -4
- package/web/.next/static/chunks/77e4bf0421481629.js +0 -1
- package/web/.next/static/chunks/beb9625c4a470042.js +0 -1
- package/web/.next/static/chunks/c2244168e74b8c78.js +0 -1
- package/web/.next/static/chunks/c81c1aec4369c77f.js +0 -5
- package/web/.next/static/chunks/cb355fac10c6ad11.css +0 -1
- package/web/.next/static/chunks/turbopack-54bc7d566cd2d105.js +0 -4
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{n86r6x1RoUipFp6nLIk-R → static/uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_buildManifest.js +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{n86r6x1RoUipFp6nLIk-R → uXbuwS0U7fRElucaXbKUe}/_ssgManifest.js +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,400 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
3
7
|
var __export = (target, all) => {
|
|
4
8
|
for (var name in all)
|
|
5
9
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
6
10
|
};
|
|
7
11
|
|
|
12
|
+
// src/config/types.ts
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
var ToolApprovalConfigSchema, SkillMetadataSchema, SessionConfigSchema, SparkcoderConfigSchema;
|
|
15
|
+
var init_types = __esm({
|
|
16
|
+
"src/config/types.ts"() {
|
|
17
|
+
"use strict";
|
|
18
|
+
ToolApprovalConfigSchema = z.object({
|
|
19
|
+
bash: z.boolean().optional().default(true),
|
|
20
|
+
write_file: z.boolean().optional().default(false),
|
|
21
|
+
read_file: z.boolean().optional().default(false),
|
|
22
|
+
load_skill: z.boolean().optional().default(false),
|
|
23
|
+
todo: z.boolean().optional().default(false)
|
|
24
|
+
});
|
|
25
|
+
SkillMetadataSchema = z.object({
|
|
26
|
+
name: z.string(),
|
|
27
|
+
description: z.string(),
|
|
28
|
+
// Whether to always inject this skill into context (vs on-demand loading)
|
|
29
|
+
alwaysApply: z.boolean().optional().default(false),
|
|
30
|
+
// Glob patterns - auto-inject when working with matching files
|
|
31
|
+
globs: z.array(z.string()).optional().default([])
|
|
32
|
+
});
|
|
33
|
+
SessionConfigSchema = z.object({
|
|
34
|
+
toolApprovals: z.record(z.string(), z.boolean()).optional(),
|
|
35
|
+
approvalWebhook: z.string().url().optional(),
|
|
36
|
+
skillsDirectory: z.string().optional(),
|
|
37
|
+
maxContextChars: z.number().optional().default(2e5)
|
|
38
|
+
});
|
|
39
|
+
SparkcoderConfigSchema = z.object({
|
|
40
|
+
// Default model to use (Vercel AI Gateway format)
|
|
41
|
+
defaultModel: z.string().default("anthropic/claude-opus-4-5"),
|
|
42
|
+
// Working directory for file operations
|
|
43
|
+
workingDirectory: z.string().optional(),
|
|
44
|
+
// Tool approval settings
|
|
45
|
+
toolApprovals: ToolApprovalConfigSchema.optional().default({}),
|
|
46
|
+
// Approval webhook URL (called when approval is needed)
|
|
47
|
+
approvalWebhook: z.string().url().optional(),
|
|
48
|
+
// Skills configuration
|
|
49
|
+
skills: z.object({
|
|
50
|
+
// Directory containing skill files
|
|
51
|
+
directory: z.string().optional().default("./skills"),
|
|
52
|
+
// Additional skill directories to include
|
|
53
|
+
additionalDirectories: z.array(z.string()).optional().default([])
|
|
54
|
+
}).optional().default({}),
|
|
55
|
+
// Context management
|
|
56
|
+
context: z.object({
|
|
57
|
+
// Maximum context size before summarization (in characters)
|
|
58
|
+
maxChars: z.number().optional().default(2e5),
|
|
59
|
+
// Enable automatic summarization
|
|
60
|
+
autoSummarize: z.boolean().optional().default(true),
|
|
61
|
+
// Number of recent messages to keep after summarization
|
|
62
|
+
keepRecentMessages: z.number().optional().default(10)
|
|
63
|
+
}).optional().default({}),
|
|
64
|
+
// Server configuration
|
|
65
|
+
server: z.object({
|
|
66
|
+
port: z.number().default(3141),
|
|
67
|
+
host: z.string().default("127.0.0.1"),
|
|
68
|
+
// Public URL for web UI to connect to API (for Docker/remote access)
|
|
69
|
+
// If not set, defaults to http://{host}:{port}
|
|
70
|
+
publicUrl: z.string().url().optional()
|
|
71
|
+
}).default({ port: 3141, host: "127.0.0.1" }),
|
|
72
|
+
// Database path
|
|
73
|
+
databasePath: z.string().optional().default("./sparkecoder.db")
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// src/skills/index.ts
|
|
79
|
+
var skills_exports = {};
|
|
80
|
+
__export(skills_exports, {
|
|
81
|
+
formatAgentsMdContent: () => formatAgentsMdContent,
|
|
82
|
+
formatAlwaysLoadedSkills: () => formatAlwaysLoadedSkills,
|
|
83
|
+
formatGlobMatchedSkills: () => formatGlobMatchedSkills,
|
|
84
|
+
formatSkillsForContext: () => formatSkillsForContext,
|
|
85
|
+
getGlobMatchedSkills: () => getGlobMatchedSkills,
|
|
86
|
+
loadAgentsMd: () => loadAgentsMd,
|
|
87
|
+
loadAllSkills: () => loadAllSkills,
|
|
88
|
+
loadAllSkillsFromDiscovered: () => loadAllSkillsFromDiscovered,
|
|
89
|
+
loadSkillContent: () => loadSkillContent,
|
|
90
|
+
loadSkillsFromDirectory: () => loadSkillsFromDirectory
|
|
91
|
+
});
|
|
92
|
+
import { readFile as readFile6, readdir } from "fs/promises";
|
|
93
|
+
import { resolve as resolve6, basename, extname as extname3, relative as relative4 } from "path";
|
|
94
|
+
import { existsSync as existsSync8 } from "fs";
|
|
95
|
+
import { minimatch } from "minimatch";
|
|
96
|
+
function parseSkillFrontmatter(content) {
|
|
97
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
98
|
+
if (!frontmatterMatch) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const [, frontmatter, body] = frontmatterMatch;
|
|
102
|
+
try {
|
|
103
|
+
const lines = frontmatter.split("\n");
|
|
104
|
+
const data = {};
|
|
105
|
+
let currentArray = null;
|
|
106
|
+
let currentArrayKey = null;
|
|
107
|
+
for (const line of lines) {
|
|
108
|
+
if (currentArrayKey && line.trim().startsWith("-")) {
|
|
109
|
+
let value = line.trim().slice(1).trim();
|
|
110
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
111
|
+
value = value.slice(1, -1);
|
|
112
|
+
}
|
|
113
|
+
currentArray?.push(value);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (currentArrayKey && currentArray) {
|
|
117
|
+
data[currentArrayKey] = currentArray;
|
|
118
|
+
currentArray = null;
|
|
119
|
+
currentArrayKey = null;
|
|
120
|
+
}
|
|
121
|
+
const colonIndex = line.indexOf(":");
|
|
122
|
+
if (colonIndex > 0) {
|
|
123
|
+
const key = line.slice(0, colonIndex).trim();
|
|
124
|
+
let value = line.slice(colonIndex + 1).trim();
|
|
125
|
+
if (value === "" || value === "[]") {
|
|
126
|
+
currentArrayKey = key;
|
|
127
|
+
currentArray = [];
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (value.startsWith("[") && value.endsWith("]")) {
|
|
131
|
+
const arrayContent = value.slice(1, -1);
|
|
132
|
+
const items = arrayContent.split(",").map((item) => {
|
|
133
|
+
let trimmed = item.trim();
|
|
134
|
+
if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) {
|
|
135
|
+
trimmed = trimmed.slice(1, -1);
|
|
136
|
+
}
|
|
137
|
+
return trimmed;
|
|
138
|
+
}).filter((item) => item.length > 0);
|
|
139
|
+
data[key] = items;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
143
|
+
value = value.slice(1, -1);
|
|
144
|
+
}
|
|
145
|
+
if (value === "true") {
|
|
146
|
+
data[key] = true;
|
|
147
|
+
} else if (value === "false") {
|
|
148
|
+
data[key] = false;
|
|
149
|
+
} else {
|
|
150
|
+
data[key] = value;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (currentArrayKey && currentArray) {
|
|
155
|
+
data[currentArrayKey] = currentArray;
|
|
156
|
+
}
|
|
157
|
+
const metadata = SkillMetadataSchema.parse(data);
|
|
158
|
+
return { metadata, body: body.trim() };
|
|
159
|
+
} catch {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
function getSkillNameFromPath(filePath) {
|
|
164
|
+
return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
165
|
+
}
|
|
166
|
+
async function loadSkillsFromDirectory(directory, options = {}) {
|
|
167
|
+
const {
|
|
168
|
+
priority = 50,
|
|
169
|
+
defaultLoadType = "on_demand",
|
|
170
|
+
forceAlwaysApply = false
|
|
171
|
+
} = options;
|
|
172
|
+
if (!existsSync8(directory)) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const skills = [];
|
|
176
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
177
|
+
for (const entry of entries) {
|
|
178
|
+
let filePath;
|
|
179
|
+
let fileName;
|
|
180
|
+
if (entry.isDirectory()) {
|
|
181
|
+
const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
|
|
182
|
+
if (existsSync8(skillMdPath)) {
|
|
183
|
+
filePath = skillMdPath;
|
|
184
|
+
fileName = entry.name;
|
|
185
|
+
} else {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
} else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc")) {
|
|
189
|
+
filePath = resolve6(directory, entry.name);
|
|
190
|
+
fileName = entry.name;
|
|
191
|
+
} else {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
const content = await readFile6(filePath, "utf-8");
|
|
195
|
+
const parsed = parseSkillFrontmatter(content);
|
|
196
|
+
if (parsed) {
|
|
197
|
+
const alwaysApply = forceAlwaysApply || parsed.metadata.alwaysApply;
|
|
198
|
+
const loadType = alwaysApply ? "always" : defaultLoadType;
|
|
199
|
+
skills.push({
|
|
200
|
+
name: parsed.metadata.name,
|
|
201
|
+
description: parsed.metadata.description,
|
|
202
|
+
filePath,
|
|
203
|
+
alwaysApply,
|
|
204
|
+
globs: parsed.metadata.globs,
|
|
205
|
+
loadType,
|
|
206
|
+
priority,
|
|
207
|
+
sourceDir: directory
|
|
208
|
+
});
|
|
209
|
+
} else {
|
|
210
|
+
const name = getSkillNameFromPath(filePath);
|
|
211
|
+
const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
|
|
212
|
+
skills.push({
|
|
213
|
+
name,
|
|
214
|
+
description: firstParagraph.replace(/^#\s*/, "").trim(),
|
|
215
|
+
filePath,
|
|
216
|
+
alwaysApply: forceAlwaysApply,
|
|
217
|
+
globs: [],
|
|
218
|
+
loadType: forceAlwaysApply ? "always" : defaultLoadType,
|
|
219
|
+
priority,
|
|
220
|
+
sourceDir: directory
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return skills;
|
|
225
|
+
}
|
|
226
|
+
async function loadAllSkills(directories) {
|
|
227
|
+
const allSkills = [];
|
|
228
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
229
|
+
for (const dir of directories) {
|
|
230
|
+
const skills = await loadSkillsFromDirectory(dir);
|
|
231
|
+
for (const skill of skills) {
|
|
232
|
+
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
233
|
+
seenNames.add(skill.name.toLowerCase());
|
|
234
|
+
allSkills.push(skill);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return allSkills;
|
|
239
|
+
}
|
|
240
|
+
async function loadAllSkillsFromDiscovered(discovered) {
|
|
241
|
+
const allSkills = [];
|
|
242
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
243
|
+
for (const { path, priority } of discovered.alwaysLoadedDirs) {
|
|
244
|
+
const skills = await loadSkillsFromDirectory(path, {
|
|
245
|
+
priority,
|
|
246
|
+
defaultLoadType: "always",
|
|
247
|
+
forceAlwaysApply: true
|
|
248
|
+
});
|
|
249
|
+
for (const skill of skills) {
|
|
250
|
+
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
251
|
+
seenNames.add(skill.name.toLowerCase());
|
|
252
|
+
allSkills.push(skill);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
for (const { path, priority } of discovered.onDemandDirs) {
|
|
257
|
+
const skills = await loadSkillsFromDirectory(path, {
|
|
258
|
+
priority,
|
|
259
|
+
defaultLoadType: "on_demand",
|
|
260
|
+
forceAlwaysApply: false
|
|
261
|
+
});
|
|
262
|
+
for (const skill of skills) {
|
|
263
|
+
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
264
|
+
seenNames.add(skill.name.toLowerCase());
|
|
265
|
+
allSkills.push(skill);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const alwaysSkills = allSkills.filter((s) => s.alwaysApply || s.loadType === "always");
|
|
270
|
+
const onDemandSkills = allSkills.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
271
|
+
const alwaysWithContent = await Promise.all(
|
|
272
|
+
alwaysSkills.map(async (skill) => {
|
|
273
|
+
const content = await readFile6(skill.filePath, "utf-8");
|
|
274
|
+
const parsed = parseSkillFrontmatter(content);
|
|
275
|
+
return {
|
|
276
|
+
...skill,
|
|
277
|
+
content: parsed ? parsed.body : content
|
|
278
|
+
};
|
|
279
|
+
})
|
|
280
|
+
);
|
|
281
|
+
return {
|
|
282
|
+
always: alwaysWithContent,
|
|
283
|
+
onDemand: onDemandSkills,
|
|
284
|
+
all: allSkills
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
async function getGlobMatchedSkills(skills, activeFiles, workingDirectory) {
|
|
288
|
+
if (activeFiles.length === 0) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
const relativeFiles = activeFiles.map((f) => {
|
|
292
|
+
if (f.startsWith(workingDirectory)) {
|
|
293
|
+
return relative4(workingDirectory, f);
|
|
294
|
+
}
|
|
295
|
+
return f;
|
|
296
|
+
});
|
|
297
|
+
const matchedSkills = skills.filter((skill) => {
|
|
298
|
+
if (skill.alwaysApply || skill.loadType === "always") {
|
|
299
|
+
return false;
|
|
300
|
+
}
|
|
301
|
+
if (!skill.globs || skill.globs.length === 0) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
return relativeFiles.some(
|
|
305
|
+
(file) => skill.globs.some((pattern) => minimatch(file, pattern, { matchBase: true }))
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
const matchedWithContent = await Promise.all(
|
|
309
|
+
matchedSkills.map(async (skill) => {
|
|
310
|
+
const content = await readFile6(skill.filePath, "utf-8");
|
|
311
|
+
const parsed = parseSkillFrontmatter(content);
|
|
312
|
+
return {
|
|
313
|
+
...skill,
|
|
314
|
+
content: parsed ? parsed.body : content,
|
|
315
|
+
loadType: "glob_matched"
|
|
316
|
+
};
|
|
317
|
+
})
|
|
318
|
+
);
|
|
319
|
+
return matchedWithContent;
|
|
320
|
+
}
|
|
321
|
+
async function loadAgentsMd(agentsMdPath) {
|
|
322
|
+
if (!agentsMdPath || !existsSync8(agentsMdPath)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const content = await readFile6(agentsMdPath, "utf-8");
|
|
326
|
+
return content;
|
|
327
|
+
}
|
|
328
|
+
async function loadSkillContent(skillName, directories) {
|
|
329
|
+
const allSkills = await loadAllSkills(directories);
|
|
330
|
+
const skill = allSkills.find(
|
|
331
|
+
(s) => s.name.toLowerCase() === skillName.toLowerCase()
|
|
332
|
+
);
|
|
333
|
+
if (!skill) {
|
|
334
|
+
return null;
|
|
335
|
+
}
|
|
336
|
+
const content = await readFile6(skill.filePath, "utf-8");
|
|
337
|
+
const parsed = parseSkillFrontmatter(content);
|
|
338
|
+
return {
|
|
339
|
+
...skill,
|
|
340
|
+
content: parsed ? parsed.body : content
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
function formatSkillsForContext(skills) {
|
|
344
|
+
const onDemandSkills = skills.filter((s) => !s.alwaysApply && s.loadType !== "always");
|
|
345
|
+
if (onDemandSkills.length === 0) {
|
|
346
|
+
return "No on-demand skills available.";
|
|
347
|
+
}
|
|
348
|
+
const lines = ["Available skills (use load_skill tool to load into context):"];
|
|
349
|
+
for (const skill of onDemandSkills) {
|
|
350
|
+
const globInfo = skill.globs?.length ? ` [auto-loads for: ${skill.globs.join(", ")}]` : "";
|
|
351
|
+
lines.push(`- ${skill.name}: ${skill.description}${globInfo}`);
|
|
352
|
+
}
|
|
353
|
+
return lines.join("\n");
|
|
354
|
+
}
|
|
355
|
+
function formatAlwaysLoadedSkills(skills) {
|
|
356
|
+
if (skills.length === 0) {
|
|
357
|
+
return "";
|
|
358
|
+
}
|
|
359
|
+
const sections = [];
|
|
360
|
+
for (const skill of skills) {
|
|
361
|
+
sections.push(`### ${skill.name}
|
|
362
|
+
|
|
363
|
+
${skill.content}`);
|
|
364
|
+
}
|
|
365
|
+
return `## Active Rules & Skills (Always Loaded)
|
|
366
|
+
|
|
367
|
+
${sections.join("\n\n---\n\n")}`;
|
|
368
|
+
}
|
|
369
|
+
function formatGlobMatchedSkills(skills) {
|
|
370
|
+
if (skills.length === 0) {
|
|
371
|
+
return "";
|
|
372
|
+
}
|
|
373
|
+
const sections = [];
|
|
374
|
+
for (const skill of skills) {
|
|
375
|
+
sections.push(`### ${skill.name}
|
|
376
|
+
|
|
377
|
+
${skill.content}`);
|
|
378
|
+
}
|
|
379
|
+
return `## Context-Relevant Skills (Auto-loaded based on active files)
|
|
380
|
+
|
|
381
|
+
${sections.join("\n\n---\n\n")}`;
|
|
382
|
+
}
|
|
383
|
+
function formatAgentsMdContent(content) {
|
|
384
|
+
if (!content) {
|
|
385
|
+
return "";
|
|
386
|
+
}
|
|
387
|
+
return `## Project Instructions (AGENTS.md)
|
|
388
|
+
|
|
389
|
+
${content}`;
|
|
390
|
+
}
|
|
391
|
+
var init_skills = __esm({
|
|
392
|
+
"src/skills/index.ts"() {
|
|
393
|
+
"use strict";
|
|
394
|
+
init_types();
|
|
395
|
+
}
|
|
396
|
+
});
|
|
397
|
+
|
|
8
398
|
// src/cli.ts
|
|
9
399
|
import { Command } from "commander";
|
|
10
400
|
import chalk from "chalk";
|
|
@@ -18,19 +408,20 @@ import { Hono as Hono5 } from "hono";
|
|
|
18
408
|
import { serve } from "@hono/node-server";
|
|
19
409
|
import { cors } from "hono/cors";
|
|
20
410
|
import { logger } from "hono/logger";
|
|
21
|
-
import { existsSync as
|
|
22
|
-
import { resolve as
|
|
411
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
412
|
+
import { resolve as resolve9, dirname as dirname7, join as join7 } from "path";
|
|
23
413
|
import { spawn as spawn2 } from "child_process";
|
|
24
414
|
import { createServer as createNetServer } from "net";
|
|
25
|
-
import { fileURLToPath as
|
|
415
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
26
416
|
|
|
27
417
|
// src/server/routes/sessions.ts
|
|
28
418
|
import { Hono } from "hono";
|
|
29
419
|
import { zValidator } from "@hono/zod-validator";
|
|
30
|
-
import { z as
|
|
31
|
-
import { existsSync as
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
420
|
+
import { z as z11 } from "zod";
|
|
421
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readdirSync, statSync, unlinkSync } from "fs";
|
|
422
|
+
import { readdir as readdir4 } from "fs/promises";
|
|
423
|
+
import { join as join4, basename as basename2, extname as extname6, relative as relative7 } from "path";
|
|
424
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
34
425
|
|
|
35
426
|
// src/db/index.ts
|
|
36
427
|
import Database from "better-sqlite3";
|
|
@@ -47,6 +438,7 @@ __export(schema_exports, {
|
|
|
47
438
|
loadedSkills: () => loadedSkills,
|
|
48
439
|
messages: () => messages,
|
|
49
440
|
sessions: () => sessions,
|
|
441
|
+
subagentExecutions: () => subagentExecutions,
|
|
50
442
|
terminals: () => terminals,
|
|
51
443
|
todoItems: () => todoItems,
|
|
52
444
|
toolExecutions: () => toolExecutions
|
|
@@ -150,6 +542,26 @@ var fileBackups = sqliteTable("file_backups", {
|
|
|
150
542
|
existed: integer("existed", { mode: "boolean" }).notNull().default(true),
|
|
151
543
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date())
|
|
152
544
|
});
|
|
545
|
+
var subagentExecutions = sqliteTable("subagent_executions", {
|
|
546
|
+
id: text("id").primaryKey(),
|
|
547
|
+
sessionId: text("session_id").notNull().references(() => sessions.id, { onDelete: "cascade" }),
|
|
548
|
+
toolCallId: text("tool_call_id").notNull(),
|
|
549
|
+
// The tool call that spawned this subagent
|
|
550
|
+
subagentType: text("subagent_type").notNull(),
|
|
551
|
+
// e.g., 'search', 'analyze', etc.
|
|
552
|
+
task: text("task").notNull(),
|
|
553
|
+
// The task/query given to the subagent
|
|
554
|
+
model: text("model").notNull(),
|
|
555
|
+
// The model used (e.g., 'gemini-2.0-flash')
|
|
556
|
+
status: text("status", { enum: ["running", "completed", "error", "cancelled"] }).notNull().default("running"),
|
|
557
|
+
// Steps taken by the subagent (stored as JSON array)
|
|
558
|
+
steps: text("steps", { mode: "json" }).$type().default([]),
|
|
559
|
+
// Final result/output
|
|
560
|
+
result: text("result", { mode: "json" }),
|
|
561
|
+
error: text("error"),
|
|
562
|
+
startedAt: integer("started_at", { mode: "timestamp" }).notNull().$defaultFn(() => /* @__PURE__ */ new Date()),
|
|
563
|
+
completedAt: integer("completed_at", { mode: "timestamp" })
|
|
564
|
+
});
|
|
153
565
|
|
|
154
566
|
// src/db/index.ts
|
|
155
567
|
var db = null;
|
|
@@ -254,6 +666,22 @@ function initDatabase(dbPath) {
|
|
|
254
666
|
created_at INTEGER NOT NULL
|
|
255
667
|
);
|
|
256
668
|
|
|
669
|
+
-- Subagent executions table - tracks subagent runs
|
|
670
|
+
CREATE TABLE IF NOT EXISTS subagent_executions (
|
|
671
|
+
id TEXT PRIMARY KEY,
|
|
672
|
+
session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
|
|
673
|
+
tool_call_id TEXT NOT NULL,
|
|
674
|
+
subagent_type TEXT NOT NULL,
|
|
675
|
+
task TEXT NOT NULL,
|
|
676
|
+
model TEXT NOT NULL,
|
|
677
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
678
|
+
steps TEXT DEFAULT '[]',
|
|
679
|
+
result TEXT,
|
|
680
|
+
error TEXT,
|
|
681
|
+
started_at INTEGER NOT NULL,
|
|
682
|
+
completed_at INTEGER
|
|
683
|
+
);
|
|
684
|
+
|
|
257
685
|
CREATE INDEX IF NOT EXISTS idx_messages_session ON messages(session_id);
|
|
258
686
|
CREATE INDEX IF NOT EXISTS idx_tool_executions_session ON tool_executions(session_id);
|
|
259
687
|
CREATE INDEX IF NOT EXISTS idx_todo_items_session ON todo_items(session_id);
|
|
@@ -263,6 +691,8 @@ function initDatabase(dbPath) {
|
|
|
263
691
|
CREATE INDEX IF NOT EXISTS idx_checkpoints_session ON checkpoints(session_id);
|
|
264
692
|
CREATE INDEX IF NOT EXISTS idx_file_backups_checkpoint ON file_backups(checkpoint_id);
|
|
265
693
|
CREATE INDEX IF NOT EXISTS idx_file_backups_session ON file_backups(session_id);
|
|
694
|
+
CREATE INDEX IF NOT EXISTS idx_subagent_executions_session ON subagent_executions(session_id);
|
|
695
|
+
CREATE INDEX IF NOT EXISTS idx_subagent_executions_tool_call ON subagent_executions(tool_call_id);
|
|
266
696
|
`);
|
|
267
697
|
return db;
|
|
268
698
|
}
|
|
@@ -656,85 +1086,149 @@ var fileBackupQueries = {
|
|
|
656
1086
|
return result.changes;
|
|
657
1087
|
}
|
|
658
1088
|
};
|
|
1089
|
+
var subagentQueries = {
|
|
1090
|
+
create(data) {
|
|
1091
|
+
const id = nanoid();
|
|
1092
|
+
const result = getDb().insert(subagentExecutions).values({
|
|
1093
|
+
id,
|
|
1094
|
+
sessionId: data.sessionId,
|
|
1095
|
+
toolCallId: data.toolCallId,
|
|
1096
|
+
subagentType: data.subagentType,
|
|
1097
|
+
task: data.task,
|
|
1098
|
+
model: data.model,
|
|
1099
|
+
status: "running",
|
|
1100
|
+
steps: [],
|
|
1101
|
+
startedAt: /* @__PURE__ */ new Date()
|
|
1102
|
+
}).returning().get();
|
|
1103
|
+
return result;
|
|
1104
|
+
},
|
|
1105
|
+
getById(id) {
|
|
1106
|
+
return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.id, id)).get();
|
|
1107
|
+
},
|
|
1108
|
+
getByToolCallId(toolCallId) {
|
|
1109
|
+
return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.toolCallId, toolCallId)).get();
|
|
1110
|
+
},
|
|
1111
|
+
getBySession(sessionId) {
|
|
1112
|
+
return getDb().select().from(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).orderBy(desc(subagentExecutions.startedAt)).all();
|
|
1113
|
+
},
|
|
1114
|
+
addStep(id, step) {
|
|
1115
|
+
const existing = this.getById(id);
|
|
1116
|
+
if (!existing) return void 0;
|
|
1117
|
+
const currentSteps = existing.steps || [];
|
|
1118
|
+
const newSteps = [...currentSteps, step];
|
|
1119
|
+
return getDb().update(subagentExecutions).set({ steps: newSteps }).where(eq(subagentExecutions.id, id)).returning().get();
|
|
1120
|
+
},
|
|
1121
|
+
complete(id, result) {
|
|
1122
|
+
return getDb().update(subagentExecutions).set({
|
|
1123
|
+
status: "completed",
|
|
1124
|
+
result,
|
|
1125
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
1126
|
+
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
1127
|
+
},
|
|
1128
|
+
markError(id, error) {
|
|
1129
|
+
return getDb().update(subagentExecutions).set({
|
|
1130
|
+
status: "error",
|
|
1131
|
+
error,
|
|
1132
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
1133
|
+
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
1134
|
+
},
|
|
1135
|
+
cancel(id) {
|
|
1136
|
+
return getDb().update(subagentExecutions).set({
|
|
1137
|
+
status: "cancelled",
|
|
1138
|
+
completedAt: /* @__PURE__ */ new Date()
|
|
1139
|
+
}).where(eq(subagentExecutions.id, id)).returning().get();
|
|
1140
|
+
},
|
|
1141
|
+
deleteBySession(sessionId) {
|
|
1142
|
+
const result = getDb().delete(subagentExecutions).where(eq(subagentExecutions.sessionId, sessionId)).run();
|
|
1143
|
+
return result.changes;
|
|
1144
|
+
}
|
|
1145
|
+
};
|
|
659
1146
|
|
|
660
1147
|
// src/agent/index.ts
|
|
661
1148
|
import {
|
|
662
|
-
streamText,
|
|
663
|
-
generateText as
|
|
664
|
-
tool as
|
|
665
|
-
stepCountIs
|
|
1149
|
+
streamText as streamText2,
|
|
1150
|
+
generateText as generateText3,
|
|
1151
|
+
tool as tool9,
|
|
1152
|
+
stepCountIs as stepCountIs2
|
|
666
1153
|
} from "ai";
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
import {
|
|
1154
|
+
|
|
1155
|
+
// src/agent/model.ts
|
|
1156
|
+
import { gateway } from "@ai-sdk/gateway";
|
|
1157
|
+
var ANTHROPIC_PREFIX = "anthropic/";
|
|
1158
|
+
function isAnthropicModel(modelId) {
|
|
1159
|
+
const normalized = modelId.trim().toLowerCase();
|
|
1160
|
+
return normalized.startsWith(ANTHROPIC_PREFIX) || normalized.startsWith("claude-");
|
|
1161
|
+
}
|
|
1162
|
+
function resolveModel(modelId) {
|
|
1163
|
+
return gateway(modelId.trim());
|
|
1164
|
+
}
|
|
1165
|
+
var SUBAGENT_MODELS = {
|
|
1166
|
+
search: "google/gemini-2.0-flash",
|
|
1167
|
+
analyze: "google/gemini-2.0-flash",
|
|
1168
|
+
default: "google/gemini-2.0-flash"
|
|
1169
|
+
};
|
|
1170
|
+
|
|
1171
|
+
// src/agent/index.ts
|
|
1172
|
+
import { z as z10 } from "zod";
|
|
1173
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
670
1174
|
|
|
671
1175
|
// src/config/index.ts
|
|
1176
|
+
init_types();
|
|
1177
|
+
init_types();
|
|
672
1178
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from "fs";
|
|
673
1179
|
import { resolve, dirname, join } from "path";
|
|
674
1180
|
import { homedir, platform } from "os";
|
|
675
|
-
|
|
676
|
-
// src/config/types.ts
|
|
677
|
-
import { z } from "zod";
|
|
678
|
-
var ToolApprovalConfigSchema = z.object({
|
|
679
|
-
bash: z.boolean().optional().default(true),
|
|
680
|
-
write_file: z.boolean().optional().default(false),
|
|
681
|
-
read_file: z.boolean().optional().default(false),
|
|
682
|
-
load_skill: z.boolean().optional().default(false),
|
|
683
|
-
todo: z.boolean().optional().default(false)
|
|
684
|
-
});
|
|
685
|
-
var SkillMetadataSchema = z.object({
|
|
686
|
-
name: z.string(),
|
|
687
|
-
description: z.string()
|
|
688
|
-
});
|
|
689
|
-
var SessionConfigSchema = z.object({
|
|
690
|
-
toolApprovals: z.record(z.string(), z.boolean()).optional(),
|
|
691
|
-
approvalWebhook: z.string().url().optional(),
|
|
692
|
-
skillsDirectory: z.string().optional(),
|
|
693
|
-
maxContextChars: z.number().optional().default(2e5)
|
|
694
|
-
});
|
|
695
|
-
var SparkcoderConfigSchema = z.object({
|
|
696
|
-
// Default model to use (Vercel AI Gateway format)
|
|
697
|
-
defaultModel: z.string().default("anthropic/claude-opus-4-5"),
|
|
698
|
-
// Working directory for file operations
|
|
699
|
-
workingDirectory: z.string().optional(),
|
|
700
|
-
// Tool approval settings
|
|
701
|
-
toolApprovals: ToolApprovalConfigSchema.optional().default({}),
|
|
702
|
-
// Approval webhook URL (called when approval is needed)
|
|
703
|
-
approvalWebhook: z.string().url().optional(),
|
|
704
|
-
// Skills configuration
|
|
705
|
-
skills: z.object({
|
|
706
|
-
// Directory containing skill files
|
|
707
|
-
directory: z.string().optional().default("./skills"),
|
|
708
|
-
// Additional skill directories to include
|
|
709
|
-
additionalDirectories: z.array(z.string()).optional().default([])
|
|
710
|
-
}).optional().default({}),
|
|
711
|
-
// Context management
|
|
712
|
-
context: z.object({
|
|
713
|
-
// Maximum context size before summarization (in characters)
|
|
714
|
-
maxChars: z.number().optional().default(2e5),
|
|
715
|
-
// Enable automatic summarization
|
|
716
|
-
autoSummarize: z.boolean().optional().default(true),
|
|
717
|
-
// Number of recent messages to keep after summarization
|
|
718
|
-
keepRecentMessages: z.number().optional().default(10)
|
|
719
|
-
}).optional().default({}),
|
|
720
|
-
// Server configuration
|
|
721
|
-
server: z.object({
|
|
722
|
-
port: z.number().default(3141),
|
|
723
|
-
host: z.string().default("127.0.0.1"),
|
|
724
|
-
// Public URL for web UI to connect to API (for Docker/remote access)
|
|
725
|
-
// If not set, defaults to http://{host}:{port}
|
|
726
|
-
publicUrl: z.string().url().optional()
|
|
727
|
-
}).default({ port: 3141, host: "127.0.0.1" }),
|
|
728
|
-
// Database path
|
|
729
|
-
databasePath: z.string().optional().default("./sparkecoder.db")
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// src/config/index.ts
|
|
733
1181
|
var CONFIG_FILE_NAMES = [
|
|
734
1182
|
"sparkecoder.config.json",
|
|
735
1183
|
"sparkecoder.json",
|
|
736
1184
|
".sparkecoder.json"
|
|
737
1185
|
];
|
|
1186
|
+
function discoverSkillDirectories(workingDir) {
|
|
1187
|
+
const alwaysLoadedDirs = [];
|
|
1188
|
+
const onDemandDirs = [];
|
|
1189
|
+
const allDirectories = [];
|
|
1190
|
+
let agentsMdPath = null;
|
|
1191
|
+
const sparkRulesDir = join(workingDir, ".sparkecoder", "rules");
|
|
1192
|
+
if (existsSync(sparkRulesDir)) {
|
|
1193
|
+
alwaysLoadedDirs.push({ path: sparkRulesDir, priority: 1 });
|
|
1194
|
+
allDirectories.push(sparkRulesDir);
|
|
1195
|
+
}
|
|
1196
|
+
const sparkSkillsDir = join(workingDir, ".sparkecoder", "skills");
|
|
1197
|
+
if (existsSync(sparkSkillsDir)) {
|
|
1198
|
+
onDemandDirs.push({ path: sparkSkillsDir, priority: 2 });
|
|
1199
|
+
allDirectories.push(sparkSkillsDir);
|
|
1200
|
+
}
|
|
1201
|
+
const cursorRulesDir = join(workingDir, ".cursor", "rules");
|
|
1202
|
+
if (existsSync(cursorRulesDir)) {
|
|
1203
|
+
onDemandDirs.push({ path: cursorRulesDir, priority: 3 });
|
|
1204
|
+
allDirectories.push(cursorRulesDir);
|
|
1205
|
+
}
|
|
1206
|
+
const claudeSkillsDir = join(workingDir, ".claude", "skills");
|
|
1207
|
+
if (existsSync(claudeSkillsDir)) {
|
|
1208
|
+
onDemandDirs.push({ path: claudeSkillsDir, priority: 4 });
|
|
1209
|
+
allDirectories.push(claudeSkillsDir);
|
|
1210
|
+
}
|
|
1211
|
+
const legacySkillsDir = join(workingDir, "skills");
|
|
1212
|
+
if (existsSync(legacySkillsDir)) {
|
|
1213
|
+
onDemandDirs.push({ path: legacySkillsDir, priority: 5 });
|
|
1214
|
+
allDirectories.push(legacySkillsDir);
|
|
1215
|
+
}
|
|
1216
|
+
const agentsMd = join(workingDir, "AGENTS.md");
|
|
1217
|
+
if (existsSync(agentsMd)) {
|
|
1218
|
+
agentsMdPath = agentsMd;
|
|
1219
|
+
}
|
|
1220
|
+
const builtInSkillsDir = resolve(dirname(import.meta.url.replace("file://", "")), "../skills/default");
|
|
1221
|
+
if (existsSync(builtInSkillsDir)) {
|
|
1222
|
+
onDemandDirs.push({ path: builtInSkillsDir, priority: 100 });
|
|
1223
|
+
allDirectories.push(builtInSkillsDir);
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
alwaysLoadedDirs,
|
|
1227
|
+
onDemandDirs,
|
|
1228
|
+
agentsMdPath,
|
|
1229
|
+
allDirectories
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
738
1232
|
function getAppDataDirectory() {
|
|
739
1233
|
const appName = "sparkecoder";
|
|
740
1234
|
switch (platform()) {
|
|
@@ -814,20 +1308,12 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
814
1308
|
} else {
|
|
815
1309
|
resolvedWorkingDirectory = process.cwd();
|
|
816
1310
|
}
|
|
1311
|
+
const discovered = discoverSkillDirectories(resolvedWorkingDirectory);
|
|
1312
|
+
const additionalDirs = (config.skills?.additionalDirectories || []).map((dir) => resolve(configDir, dir)).filter((dir) => existsSync(dir));
|
|
817
1313
|
const resolvedSkillsDirectories = [
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
...(config.skills?.additionalDirectories || []).map(
|
|
822
|
-
(dir) => resolve(configDir, dir)
|
|
823
|
-
)
|
|
824
|
-
].filter((dir) => {
|
|
825
|
-
try {
|
|
826
|
-
return existsSync(dir);
|
|
827
|
-
} catch {
|
|
828
|
-
return false;
|
|
829
|
-
}
|
|
830
|
-
});
|
|
1314
|
+
...discovered.allDirectories,
|
|
1315
|
+
...additionalDirs
|
|
1316
|
+
];
|
|
831
1317
|
let resolvedDatabasePath;
|
|
832
1318
|
if (config.databasePath && config.databasePath !== "./sparkecoder.db") {
|
|
833
1319
|
resolvedDatabasePath = resolve(configDir, config.databasePath);
|
|
@@ -844,7 +1330,8 @@ function loadConfig(configPath, workingDirectory) {
|
|
|
844
1330
|
},
|
|
845
1331
|
resolvedWorkingDirectory,
|
|
846
1332
|
resolvedSkillsDirectories,
|
|
847
|
-
resolvedDatabasePath
|
|
1333
|
+
resolvedDatabasePath,
|
|
1334
|
+
discoveredSkills: discovered
|
|
848
1335
|
};
|
|
849
1336
|
cachedConfig = resolved;
|
|
850
1337
|
return resolved;
|
|
@@ -1272,8 +1759,8 @@ async function listSessionTerminals(sessionId, workingDirectory) {
|
|
|
1272
1759
|
const terminalsDir = join2(workingDirectory, LOG_BASE_DIR, sessionId, "terminals");
|
|
1273
1760
|
const terminals3 = [];
|
|
1274
1761
|
try {
|
|
1275
|
-
const { readdir:
|
|
1276
|
-
const entries = await
|
|
1762
|
+
const { readdir: readdir5 } = await import("fs/promises");
|
|
1763
|
+
const entries = await readdir5(terminalsDir, { withFileTypes: true });
|
|
1277
1764
|
for (const entry of entries) {
|
|
1278
1765
|
if (entry.isDirectory()) {
|
|
1279
1766
|
const meta = await getMeta(entry.name, workingDirectory, sessionId);
|
|
@@ -1877,12 +2364,12 @@ function findNearestRoot(startDir, markers) {
|
|
|
1877
2364
|
}
|
|
1878
2365
|
async function commandExists(cmd) {
|
|
1879
2366
|
try {
|
|
1880
|
-
const { exec:
|
|
1881
|
-
const { promisify:
|
|
1882
|
-
const
|
|
2367
|
+
const { exec: exec6 } = await import("child_process");
|
|
2368
|
+
const { promisify: promisify6 } = await import("util");
|
|
2369
|
+
const execAsync6 = promisify6(exec6);
|
|
1883
2370
|
const isWindows = process.platform === "win32";
|
|
1884
2371
|
const checkCmd = isWindows ? `where ${cmd}` : `which ${cmd}`;
|
|
1885
|
-
await
|
|
2372
|
+
await execAsync6(checkCmd);
|
|
1886
2373
|
return true;
|
|
1887
2374
|
} catch {
|
|
1888
2375
|
return false;
|
|
@@ -2172,7 +2659,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2172
2659
|
},
|
|
2173
2660
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
2174
2661
|
const normalized = normalizePath(filePath);
|
|
2175
|
-
return new Promise((
|
|
2662
|
+
return new Promise((resolve11) => {
|
|
2176
2663
|
const startTime = Date.now();
|
|
2177
2664
|
let debounceTimer;
|
|
2178
2665
|
let resolved = false;
|
|
@@ -2191,7 +2678,7 @@ async function createClient(serverId, handle, root) {
|
|
|
2191
2678
|
if (resolved) return;
|
|
2192
2679
|
resolved = true;
|
|
2193
2680
|
cleanup();
|
|
2194
|
-
|
|
2681
|
+
resolve11(diagnostics.get(normalized) || []);
|
|
2195
2682
|
};
|
|
2196
2683
|
const onDiagnostic = () => {
|
|
2197
2684
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -2349,6 +2836,7 @@ function isSupported(filePath) {
|
|
|
2349
2836
|
}
|
|
2350
2837
|
|
|
2351
2838
|
// src/tools/write-file.ts
|
|
2839
|
+
var MAX_PROGRESS_CHUNK_SIZE = 16 * 1024;
|
|
2352
2840
|
var writeFileInputSchema = z4.object({
|
|
2353
2841
|
path: z4.string().describe("The path to the file. Can be relative to working directory or absolute."),
|
|
2354
2842
|
mode: z4.enum(["full", "str_replace"]).describe('Write mode: "full" for complete file write, "str_replace" for targeted string replacement'),
|
|
@@ -2392,24 +2880,76 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2392
2880
|
error: 'Content is required for "full" mode'
|
|
2393
2881
|
};
|
|
2394
2882
|
}
|
|
2883
|
+
const existed = existsSync7(absolutePath);
|
|
2884
|
+
const action = existed ? "replaced" : "created";
|
|
2885
|
+
console.log("[WRITE-FILE] onProgress callback exists:", !!options.onProgress);
|
|
2886
|
+
console.log("[WRITE-FILE] Emitting started event for:", relativePath);
|
|
2887
|
+
options.onProgress?.({
|
|
2888
|
+
path: absolutePath,
|
|
2889
|
+
relativePath,
|
|
2890
|
+
mode: "full",
|
|
2891
|
+
status: "started",
|
|
2892
|
+
action,
|
|
2893
|
+
totalLength: content.length
|
|
2894
|
+
});
|
|
2895
|
+
if (content.length <= MAX_PROGRESS_CHUNK_SIZE) {
|
|
2896
|
+
options.onProgress?.({
|
|
2897
|
+
path: absolutePath,
|
|
2898
|
+
relativePath,
|
|
2899
|
+
mode: "full",
|
|
2900
|
+
status: "content",
|
|
2901
|
+
content,
|
|
2902
|
+
action,
|
|
2903
|
+
totalLength: content.length
|
|
2904
|
+
});
|
|
2905
|
+
} else {
|
|
2906
|
+
const chunkCount = Math.ceil(content.length / MAX_PROGRESS_CHUNK_SIZE);
|
|
2907
|
+
for (let i = 0; i < chunkCount; i += 1) {
|
|
2908
|
+
const chunkStart = i * MAX_PROGRESS_CHUNK_SIZE;
|
|
2909
|
+
const chunk = content.slice(chunkStart, chunkStart + MAX_PROGRESS_CHUNK_SIZE);
|
|
2910
|
+
options.onProgress?.({
|
|
2911
|
+
path: absolutePath,
|
|
2912
|
+
relativePath,
|
|
2913
|
+
mode: "full",
|
|
2914
|
+
status: "content",
|
|
2915
|
+
content: chunk,
|
|
2916
|
+
action,
|
|
2917
|
+
totalLength: content.length,
|
|
2918
|
+
chunkIndex: i,
|
|
2919
|
+
chunkCount,
|
|
2920
|
+
chunkStart,
|
|
2921
|
+
isChunked: true
|
|
2922
|
+
});
|
|
2923
|
+
if (chunkCount > 1) {
|
|
2924
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2395
2928
|
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
2396
2929
|
const dir = dirname5(absolutePath);
|
|
2397
2930
|
if (!existsSync7(dir)) {
|
|
2398
2931
|
await mkdir3(dir, { recursive: true });
|
|
2399
2932
|
}
|
|
2400
|
-
const existed = existsSync7(absolutePath);
|
|
2401
2933
|
await writeFile3(absolutePath, content, "utf-8");
|
|
2402
2934
|
let diagnosticsOutput = "";
|
|
2403
2935
|
if (options.enableLSP !== false && isSupported(absolutePath)) {
|
|
2404
2936
|
await touchFile(absolutePath, true);
|
|
2405
2937
|
diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
|
|
2406
2938
|
}
|
|
2939
|
+
options.onProgress?.({
|
|
2940
|
+
path: absolutePath,
|
|
2941
|
+
relativePath,
|
|
2942
|
+
mode: "full",
|
|
2943
|
+
status: "completed",
|
|
2944
|
+
action,
|
|
2945
|
+
totalLength: content.length
|
|
2946
|
+
});
|
|
2407
2947
|
return {
|
|
2408
2948
|
success: true,
|
|
2409
2949
|
path: absolutePath,
|
|
2410
|
-
relativePath
|
|
2950
|
+
relativePath,
|
|
2411
2951
|
mode: "full",
|
|
2412
|
-
action
|
|
2952
|
+
action,
|
|
2413
2953
|
bytesWritten: Buffer.byteLength(content, "utf-8"),
|
|
2414
2954
|
lineCount: content.split("\n").length,
|
|
2415
2955
|
...diagnosticsOutput && { diagnostics: diagnosticsOutput }
|
|
@@ -2427,6 +2967,22 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2427
2967
|
error: `File not found: ${path}. Use "full" mode to create new files.`
|
|
2428
2968
|
};
|
|
2429
2969
|
}
|
|
2970
|
+
options.onProgress?.({
|
|
2971
|
+
path: absolutePath,
|
|
2972
|
+
relativePath,
|
|
2973
|
+
mode: "str_replace",
|
|
2974
|
+
status: "started",
|
|
2975
|
+
action: "edited"
|
|
2976
|
+
});
|
|
2977
|
+
options.onProgress?.({
|
|
2978
|
+
path: absolutePath,
|
|
2979
|
+
relativePath,
|
|
2980
|
+
mode: "str_replace",
|
|
2981
|
+
status: "content",
|
|
2982
|
+
oldString: old_string,
|
|
2983
|
+
newString: new_string,
|
|
2984
|
+
action: "edited"
|
|
2985
|
+
});
|
|
2430
2986
|
await backupFile(options.sessionId, options.workingDirectory, absolutePath);
|
|
2431
2987
|
const currentContent = await readFile5(absolutePath, "utf-8");
|
|
2432
2988
|
if (!currentContent.includes(old_string)) {
|
|
@@ -2457,10 +3013,17 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2457
3013
|
await touchFile(absolutePath, true);
|
|
2458
3014
|
diagnosticsOutput = await formatDiagnosticsOutput(absolutePath);
|
|
2459
3015
|
}
|
|
3016
|
+
options.onProgress?.({
|
|
3017
|
+
path: absolutePath,
|
|
3018
|
+
relativePath,
|
|
3019
|
+
mode: "str_replace",
|
|
3020
|
+
status: "completed",
|
|
3021
|
+
action: "edited"
|
|
3022
|
+
});
|
|
2460
3023
|
return {
|
|
2461
3024
|
success: true,
|
|
2462
3025
|
path: absolutePath,
|
|
2463
|
-
relativePath
|
|
3026
|
+
relativePath,
|
|
2464
3027
|
mode: "str_replace",
|
|
2465
3028
|
linesRemoved: oldLines,
|
|
2466
3029
|
linesAdded: newLines,
|
|
@@ -2608,112 +3171,9 @@ function formatTodoItem(item) {
|
|
|
2608
3171
|
}
|
|
2609
3172
|
|
|
2610
3173
|
// src/tools/load-skill.ts
|
|
3174
|
+
init_skills();
|
|
2611
3175
|
import { tool as tool5 } from "ai";
|
|
2612
3176
|
import { z as z6 } from "zod";
|
|
2613
|
-
|
|
2614
|
-
// src/skills/index.ts
|
|
2615
|
-
import { readFile as readFile6, readdir } from "fs/promises";
|
|
2616
|
-
import { resolve as resolve6, basename, extname as extname3 } from "path";
|
|
2617
|
-
import { existsSync as existsSync8 } from "fs";
|
|
2618
|
-
function parseSkillFrontmatter(content) {
|
|
2619
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
2620
|
-
if (!frontmatterMatch) {
|
|
2621
|
-
return null;
|
|
2622
|
-
}
|
|
2623
|
-
const [, frontmatter, body] = frontmatterMatch;
|
|
2624
|
-
try {
|
|
2625
|
-
const lines = frontmatter.split("\n");
|
|
2626
|
-
const data = {};
|
|
2627
|
-
for (const line of lines) {
|
|
2628
|
-
const colonIndex = line.indexOf(":");
|
|
2629
|
-
if (colonIndex > 0) {
|
|
2630
|
-
const key = line.slice(0, colonIndex).trim();
|
|
2631
|
-
let value = line.slice(colonIndex + 1).trim();
|
|
2632
|
-
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
2633
|
-
value = value.slice(1, -1);
|
|
2634
|
-
}
|
|
2635
|
-
data[key] = value;
|
|
2636
|
-
}
|
|
2637
|
-
}
|
|
2638
|
-
const metadata = SkillMetadataSchema.parse(data);
|
|
2639
|
-
return { metadata, body: body.trim() };
|
|
2640
|
-
} catch {
|
|
2641
|
-
return null;
|
|
2642
|
-
}
|
|
2643
|
-
}
|
|
2644
|
-
function getSkillNameFromPath(filePath) {
|
|
2645
|
-
return basename(filePath, extname3(filePath)).replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
2646
|
-
}
|
|
2647
|
-
async function loadSkillsFromDirectory(directory) {
|
|
2648
|
-
if (!existsSync8(directory)) {
|
|
2649
|
-
return [];
|
|
2650
|
-
}
|
|
2651
|
-
const skills = [];
|
|
2652
|
-
const files = await readdir(directory);
|
|
2653
|
-
for (const file of files) {
|
|
2654
|
-
if (!file.endsWith(".md")) continue;
|
|
2655
|
-
const filePath = resolve6(directory, file);
|
|
2656
|
-
const content = await readFile6(filePath, "utf-8");
|
|
2657
|
-
const parsed = parseSkillFrontmatter(content);
|
|
2658
|
-
if (parsed) {
|
|
2659
|
-
skills.push({
|
|
2660
|
-
name: parsed.metadata.name,
|
|
2661
|
-
description: parsed.metadata.description,
|
|
2662
|
-
filePath
|
|
2663
|
-
});
|
|
2664
|
-
} else {
|
|
2665
|
-
const name = getSkillNameFromPath(filePath);
|
|
2666
|
-
const firstParagraph = content.split("\n\n")[0]?.slice(0, 200) || "No description";
|
|
2667
|
-
skills.push({
|
|
2668
|
-
name,
|
|
2669
|
-
description: firstParagraph.replace(/^#\s*/, "").trim(),
|
|
2670
|
-
filePath
|
|
2671
|
-
});
|
|
2672
|
-
}
|
|
2673
|
-
}
|
|
2674
|
-
return skills;
|
|
2675
|
-
}
|
|
2676
|
-
async function loadAllSkills(directories) {
|
|
2677
|
-
const allSkills = [];
|
|
2678
|
-
const seenNames = /* @__PURE__ */ new Set();
|
|
2679
|
-
for (const dir of directories) {
|
|
2680
|
-
const skills = await loadSkillsFromDirectory(dir);
|
|
2681
|
-
for (const skill of skills) {
|
|
2682
|
-
if (!seenNames.has(skill.name.toLowerCase())) {
|
|
2683
|
-
seenNames.add(skill.name.toLowerCase());
|
|
2684
|
-
allSkills.push(skill);
|
|
2685
|
-
}
|
|
2686
|
-
}
|
|
2687
|
-
}
|
|
2688
|
-
return allSkills;
|
|
2689
|
-
}
|
|
2690
|
-
async function loadSkillContent(skillName, directories) {
|
|
2691
|
-
const allSkills = await loadAllSkills(directories);
|
|
2692
|
-
const skill = allSkills.find(
|
|
2693
|
-
(s) => s.name.toLowerCase() === skillName.toLowerCase()
|
|
2694
|
-
);
|
|
2695
|
-
if (!skill) {
|
|
2696
|
-
return null;
|
|
2697
|
-
}
|
|
2698
|
-
const content = await readFile6(skill.filePath, "utf-8");
|
|
2699
|
-
const parsed = parseSkillFrontmatter(content);
|
|
2700
|
-
return {
|
|
2701
|
-
...skill,
|
|
2702
|
-
content: parsed ? parsed.body : content
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
function formatSkillsForContext(skills) {
|
|
2706
|
-
if (skills.length === 0) {
|
|
2707
|
-
return "No skills available.";
|
|
2708
|
-
}
|
|
2709
|
-
const lines = ["Available skills (use load_skill tool to load into context):"];
|
|
2710
|
-
for (const skill of skills) {
|
|
2711
|
-
lines.push(`- ${skill.name}: ${skill.description}`);
|
|
2712
|
-
}
|
|
2713
|
-
return lines.join("\n");
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
// src/tools/load-skill.ts
|
|
2717
3177
|
var loadSkillInputSchema = z6.object({
|
|
2718
3178
|
action: z6.enum(["list", "load"]).describe('Action to perform: "list" to see available skills, "load" to load a skill'),
|
|
2719
3179
|
skillName: z6.string().optional().describe('For "load" action: The name of the skill to load')
|
|
@@ -2796,7 +3256,7 @@ Once loaded, a skill's content will be available in the conversation context.`,
|
|
|
2796
3256
|
// src/tools/linter.ts
|
|
2797
3257
|
import { tool as tool6 } from "ai";
|
|
2798
3258
|
import { z as z7 } from "zod";
|
|
2799
|
-
import { resolve as resolve7, relative as
|
|
3259
|
+
import { resolve as resolve7, relative as relative5, isAbsolute as isAbsolute3, extname as extname4 } from "path";
|
|
2800
3260
|
import { existsSync as existsSync9 } from "fs";
|
|
2801
3261
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
2802
3262
|
var linterInputSchema = z7.object({
|
|
@@ -2868,37 +3328,730 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2868
3328
|
if (!existsSync9(absolutePath)) {
|
|
2869
3329
|
continue;
|
|
2870
3330
|
}
|
|
2871
|
-
const stats = await stat2(absolutePath);
|
|
2872
|
-
if (stats.isDirectory()) {
|
|
2873
|
-
const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
|
|
2874
|
-
filesToCheck.push(...dirFiles);
|
|
2875
|
-
} else if (stats.isFile()) {
|
|
2876
|
-
if (isSupported(absolutePath)) {
|
|
2877
|
-
filesToCheck.push(absolutePath);
|
|
3331
|
+
const stats = await stat2(absolutePath);
|
|
3332
|
+
if (stats.isDirectory()) {
|
|
3333
|
+
const dirFiles = await findSupportedFiles(absolutePath, options.workingDirectory);
|
|
3334
|
+
filesToCheck.push(...dirFiles);
|
|
3335
|
+
} else if (stats.isFile()) {
|
|
3336
|
+
if (isSupported(absolutePath)) {
|
|
3337
|
+
filesToCheck.push(absolutePath);
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
if (filesToCheck.length === 0) {
|
|
3342
|
+
return {
|
|
3343
|
+
success: true,
|
|
3344
|
+
message: "No supported files found to check. Supported extensions: " + getSupportedExtensions().join(", "),
|
|
3345
|
+
files: [],
|
|
3346
|
+
totalErrors: 0,
|
|
3347
|
+
totalWarnings: 0
|
|
3348
|
+
};
|
|
3349
|
+
}
|
|
3350
|
+
await Promise.all(
|
|
3351
|
+
filesToCheck.map((file) => touchFile(file, true))
|
|
3352
|
+
);
|
|
3353
|
+
const diagnosticsMap = {};
|
|
3354
|
+
for (const file of filesToCheck) {
|
|
3355
|
+
const diagnostics = await getDiagnostics(file);
|
|
3356
|
+
if (diagnostics.length > 0) {
|
|
3357
|
+
diagnosticsMap[file] = diagnostics;
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
return formatDiagnosticsResult(diagnosticsMap, options.workingDirectory);
|
|
3361
|
+
} catch (error) {
|
|
3362
|
+
return {
|
|
3363
|
+
success: false,
|
|
3364
|
+
error: error.message
|
|
3365
|
+
};
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
});
|
|
3369
|
+
}
|
|
3370
|
+
function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
|
|
3371
|
+
let totalErrors = 0;
|
|
3372
|
+
let totalWarnings = 0;
|
|
3373
|
+
let totalInfo = 0;
|
|
3374
|
+
const files = [];
|
|
3375
|
+
for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
|
|
3376
|
+
const relativePath = relative5(workingDirectory, filePath);
|
|
3377
|
+
let fileErrors = 0;
|
|
3378
|
+
let fileWarnings = 0;
|
|
3379
|
+
const formattedDiagnostics = diagnostics.map((d) => {
|
|
3380
|
+
const severity = getSeverityString(d.severity);
|
|
3381
|
+
if (d.severity === 1 /* Error */) {
|
|
3382
|
+
fileErrors++;
|
|
3383
|
+
totalErrors++;
|
|
3384
|
+
} else if (d.severity === 2 /* Warning */) {
|
|
3385
|
+
fileWarnings++;
|
|
3386
|
+
totalWarnings++;
|
|
3387
|
+
} else {
|
|
3388
|
+
totalInfo++;
|
|
3389
|
+
}
|
|
3390
|
+
return {
|
|
3391
|
+
severity,
|
|
3392
|
+
line: d.range.start.line + 1,
|
|
3393
|
+
column: d.range.start.character + 1,
|
|
3394
|
+
message: d.message,
|
|
3395
|
+
source: d.source,
|
|
3396
|
+
code: d.code
|
|
3397
|
+
};
|
|
3398
|
+
});
|
|
3399
|
+
files.push({
|
|
3400
|
+
path: filePath,
|
|
3401
|
+
relativePath,
|
|
3402
|
+
errors: fileErrors,
|
|
3403
|
+
warnings: fileWarnings,
|
|
3404
|
+
diagnostics: formattedDiagnostics
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
files.sort((a, b) => b.errors - a.errors);
|
|
3408
|
+
const hasIssues = totalErrors > 0 || totalWarnings > 0;
|
|
3409
|
+
return {
|
|
3410
|
+
success: true,
|
|
3411
|
+
message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
|
|
3412
|
+
files,
|
|
3413
|
+
totalErrors,
|
|
3414
|
+
totalWarnings,
|
|
3415
|
+
totalInfo,
|
|
3416
|
+
summary: hasIssues ? formatSummary(files) : void 0
|
|
3417
|
+
};
|
|
3418
|
+
}
|
|
3419
|
+
function getSeverityString(severity) {
|
|
3420
|
+
switch (severity) {
|
|
3421
|
+
case 1 /* Error */:
|
|
3422
|
+
return "error";
|
|
3423
|
+
case 2 /* Warning */:
|
|
3424
|
+
return "warning";
|
|
3425
|
+
case 3 /* Information */:
|
|
3426
|
+
return "info";
|
|
3427
|
+
case 4 /* Hint */:
|
|
3428
|
+
return "hint";
|
|
3429
|
+
default:
|
|
3430
|
+
return "error";
|
|
3431
|
+
}
|
|
3432
|
+
}
|
|
3433
|
+
function formatSummary(files) {
|
|
3434
|
+
const lines = [];
|
|
3435
|
+
for (const file of files) {
|
|
3436
|
+
lines.push(`
|
|
3437
|
+
${file.relativePath}:`);
|
|
3438
|
+
for (const d of file.diagnostics.slice(0, 10)) {
|
|
3439
|
+
const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
3440
|
+
lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
|
|
3441
|
+
}
|
|
3442
|
+
if (file.diagnostics.length > 10) {
|
|
3443
|
+
lines.push(` ... and ${file.diagnostics.length - 10} more`);
|
|
3444
|
+
}
|
|
3445
|
+
}
|
|
3446
|
+
return lines.join("\n");
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/tools/search.ts
|
|
3450
|
+
import { tool as tool8 } from "ai";
|
|
3451
|
+
import { z as z9 } from "zod";
|
|
3452
|
+
|
|
3453
|
+
// src/agent/subagent.ts
|
|
3454
|
+
import {
|
|
3455
|
+
generateText,
|
|
3456
|
+
stepCountIs
|
|
3457
|
+
} from "ai";
|
|
3458
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
3459
|
+
var Subagent = class {
|
|
3460
|
+
/** Model to use (defaults to gemini-2.0-flash) */
|
|
3461
|
+
model;
|
|
3462
|
+
/** Maximum steps before stopping */
|
|
3463
|
+
maxSteps = 20;
|
|
3464
|
+
constructor(model) {
|
|
3465
|
+
this.model = model || SUBAGENT_MODELS.default;
|
|
3466
|
+
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Parse the final result from the subagent's output.
|
|
3469
|
+
* Override this to structure the result for your subagent type.
|
|
3470
|
+
*/
|
|
3471
|
+
parseResult(text2, steps) {
|
|
3472
|
+
return { text: text2, steps };
|
|
3473
|
+
}
|
|
3474
|
+
/**
|
|
3475
|
+
* Run the subagent with streaming progress updates
|
|
3476
|
+
*/
|
|
3477
|
+
async run(options) {
|
|
3478
|
+
const { task, sessionId, toolCallId, onProgress, abortSignal } = options;
|
|
3479
|
+
const steps = [];
|
|
3480
|
+
const execution = subagentQueries.create({
|
|
3481
|
+
sessionId,
|
|
3482
|
+
toolCallId,
|
|
3483
|
+
subagentType: this.type,
|
|
3484
|
+
task,
|
|
3485
|
+
model: this.model
|
|
3486
|
+
});
|
|
3487
|
+
const addStep = async (step) => {
|
|
3488
|
+
const fullStep = {
|
|
3489
|
+
id: nanoid3(8),
|
|
3490
|
+
timestamp: Date.now(),
|
|
3491
|
+
...step
|
|
3492
|
+
};
|
|
3493
|
+
steps.push(fullStep);
|
|
3494
|
+
subagentQueries.addStep(execution.id, fullStep);
|
|
3495
|
+
await onProgress?.({
|
|
3496
|
+
type: "step",
|
|
3497
|
+
subagentId: execution.id,
|
|
3498
|
+
subagentType: this.type,
|
|
3499
|
+
step: fullStep
|
|
3500
|
+
});
|
|
3501
|
+
};
|
|
3502
|
+
try {
|
|
3503
|
+
const tools = this.getTools(options);
|
|
3504
|
+
const systemPrompt = this.getSystemPrompt(options);
|
|
3505
|
+
const result = await generateText({
|
|
3506
|
+
model: resolveModel(this.model),
|
|
3507
|
+
system: systemPrompt,
|
|
3508
|
+
messages: [
|
|
3509
|
+
{ role: "user", content: task }
|
|
3510
|
+
],
|
|
3511
|
+
tools,
|
|
3512
|
+
stopWhen: stepCountIs(this.maxSteps),
|
|
3513
|
+
abortSignal,
|
|
3514
|
+
onStepFinish: async (step) => {
|
|
3515
|
+
if (step.text) {
|
|
3516
|
+
await addStep({
|
|
3517
|
+
type: "text",
|
|
3518
|
+
content: step.text
|
|
3519
|
+
});
|
|
3520
|
+
await onProgress?.({
|
|
3521
|
+
type: "text",
|
|
3522
|
+
subagentId: execution.id,
|
|
3523
|
+
subagentType: this.type,
|
|
3524
|
+
text: step.text
|
|
3525
|
+
});
|
|
3526
|
+
}
|
|
3527
|
+
if (step.toolCalls) {
|
|
3528
|
+
for (const toolCall of step.toolCalls) {
|
|
3529
|
+
await addStep({
|
|
3530
|
+
type: "tool_call",
|
|
3531
|
+
content: `Calling ${toolCall.toolName}`,
|
|
3532
|
+
toolName: toolCall.toolName,
|
|
3533
|
+
toolInput: toolCall.input
|
|
3534
|
+
});
|
|
3535
|
+
await onProgress?.({
|
|
3536
|
+
type: "tool_call",
|
|
3537
|
+
subagentId: execution.id,
|
|
3538
|
+
subagentType: this.type,
|
|
3539
|
+
toolName: toolCall.toolName,
|
|
3540
|
+
toolInput: toolCall.input
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
if (step.toolResults) {
|
|
3545
|
+
for (const toolResult of step.toolResults) {
|
|
3546
|
+
await addStep({
|
|
3547
|
+
type: "tool_result",
|
|
3548
|
+
content: `Result from ${toolResult.toolName}`,
|
|
3549
|
+
toolName: toolResult.toolName,
|
|
3550
|
+
toolOutput: toolResult.output
|
|
3551
|
+
});
|
|
3552
|
+
await onProgress?.({
|
|
3553
|
+
type: "tool_result",
|
|
3554
|
+
subagentId: execution.id,
|
|
3555
|
+
subagentType: this.type,
|
|
3556
|
+
toolName: toolResult.toolName,
|
|
3557
|
+
toolOutput: toolResult.output
|
|
3558
|
+
});
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3562
|
+
});
|
|
3563
|
+
const parsedResult = this.parseResult(result.text, steps);
|
|
3564
|
+
subagentQueries.complete(execution.id, parsedResult);
|
|
3565
|
+
await onProgress?.({
|
|
3566
|
+
type: "complete",
|
|
3567
|
+
subagentId: execution.id,
|
|
3568
|
+
subagentType: this.type,
|
|
3569
|
+
result: parsedResult
|
|
3570
|
+
});
|
|
3571
|
+
return {
|
|
3572
|
+
success: true,
|
|
3573
|
+
result: parsedResult,
|
|
3574
|
+
steps,
|
|
3575
|
+
executionId: execution.id
|
|
3576
|
+
};
|
|
3577
|
+
} catch (error) {
|
|
3578
|
+
const errorMessage = error.message || "Unknown error";
|
|
3579
|
+
subagentQueries.markError(execution.id, errorMessage);
|
|
3580
|
+
await onProgress?.({
|
|
3581
|
+
type: "error",
|
|
3582
|
+
subagentId: execution.id,
|
|
3583
|
+
subagentType: this.type,
|
|
3584
|
+
error: errorMessage
|
|
3585
|
+
});
|
|
3586
|
+
return {
|
|
3587
|
+
success: false,
|
|
3588
|
+
error: errorMessage,
|
|
3589
|
+
steps,
|
|
3590
|
+
executionId: execution.id
|
|
3591
|
+
};
|
|
3592
|
+
}
|
|
3593
|
+
}
|
|
3594
|
+
/**
|
|
3595
|
+
* Run with streaming (for real-time progress in UI)
|
|
3596
|
+
*/
|
|
3597
|
+
async *stream(options) {
|
|
3598
|
+
const events = [];
|
|
3599
|
+
let resolveNext = null;
|
|
3600
|
+
let done = false;
|
|
3601
|
+
const eventQueue = [];
|
|
3602
|
+
const runPromise = this.run({
|
|
3603
|
+
...options,
|
|
3604
|
+
onProgress: async (event) => {
|
|
3605
|
+
eventQueue.push(event);
|
|
3606
|
+
if (resolveNext) {
|
|
3607
|
+
resolveNext(eventQueue.shift());
|
|
3608
|
+
resolveNext = null;
|
|
3609
|
+
}
|
|
3610
|
+
}
|
|
3611
|
+
}).then((result) => {
|
|
3612
|
+
done = true;
|
|
3613
|
+
if (resolveNext) {
|
|
3614
|
+
resolveNext(null);
|
|
3615
|
+
}
|
|
3616
|
+
return result;
|
|
3617
|
+
});
|
|
3618
|
+
while (!done || eventQueue.length > 0) {
|
|
3619
|
+
if (eventQueue.length > 0) {
|
|
3620
|
+
yield eventQueue.shift();
|
|
3621
|
+
} else if (!done) {
|
|
3622
|
+
const event = await new Promise((resolve11) => {
|
|
3623
|
+
resolveNext = resolve11;
|
|
3624
|
+
});
|
|
3625
|
+
if (event) {
|
|
3626
|
+
yield event;
|
|
3627
|
+
}
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
await runPromise;
|
|
3631
|
+
}
|
|
3632
|
+
};
|
|
3633
|
+
|
|
3634
|
+
// src/agent/subagents/search.ts
|
|
3635
|
+
import { tool as tool7 } from "ai";
|
|
3636
|
+
import { z as z8 } from "zod";
|
|
3637
|
+
import { exec as exec4 } from "child_process";
|
|
3638
|
+
import { promisify as promisify4 } from "util";
|
|
3639
|
+
import { readFile as readFile7, stat as stat3, readdir as readdir3 } from "fs/promises";
|
|
3640
|
+
import { resolve as resolve8, relative as relative6, isAbsolute as isAbsolute4 } from "path";
|
|
3641
|
+
import { existsSync as existsSync10 } from "fs";
|
|
3642
|
+
var execAsync4 = promisify4(exec4);
|
|
3643
|
+
var MAX_OUTPUT_CHARS4 = 2e4;
|
|
3644
|
+
var MAX_FILE_SIZE2 = 2 * 1024 * 1024;
|
|
3645
|
+
var SearchSubagent = class extends Subagent {
|
|
3646
|
+
type = "search";
|
|
3647
|
+
name = "Search Agent";
|
|
3648
|
+
constructor(model) {
|
|
3649
|
+
super(model || SUBAGENT_MODELS.search);
|
|
3650
|
+
this.maxSteps = 15;
|
|
3651
|
+
}
|
|
3652
|
+
getSystemPrompt(options) {
|
|
3653
|
+
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.
|
|
3654
|
+
|
|
3655
|
+
Working Directory: ${options.workingDirectory}
|
|
3656
|
+
|
|
3657
|
+
You have these tools available:
|
|
3658
|
+
- grep: Search for patterns in files using ripgrep (rg)
|
|
3659
|
+
- glob: Find files matching a pattern
|
|
3660
|
+
- read_file: Read contents of a specific file
|
|
3661
|
+
- list_dir: List directory contents
|
|
3662
|
+
|
|
3663
|
+
## Strategy - Search in Parallel
|
|
3664
|
+
|
|
3665
|
+
IMPORTANT: When searching, run MULTIPLE searches in PARALLEL to cover different variations and related terms. Don't search sequentially - batch your searches together!
|
|
3666
|
+
|
|
3667
|
+
For example, if asked "how does authentication work":
|
|
3668
|
+
- Search for "auth", "login", "session", "jwt", "token" all at once
|
|
3669
|
+
- Search in different likely directories: src/auth/, lib/auth/, services/auth/
|
|
3670
|
+
- Look for common patterns: AuthProvider, useAuth, authenticate, isAuthenticated
|
|
3671
|
+
|
|
3672
|
+
**Parallel Search Patterns:**
|
|
3673
|
+
1. Try multiple naming conventions at once:
|
|
3674
|
+
- camelCase: "getUserData", "handleAuth"
|
|
3675
|
+
- snake_case: "get_user_data", "handle_auth"
|
|
3676
|
+
- PascalCase: "UserService", "AuthProvider"
|
|
3677
|
+
|
|
3678
|
+
2. Search for related concepts together:
|
|
3679
|
+
- For "database": search "db", "database", "query", "model", "schema"
|
|
3680
|
+
- For "api": search "endpoint", "route", "handler", "controller", "api"
|
|
3681
|
+
|
|
3682
|
+
3. Use glob AND grep together:
|
|
3683
|
+
- Find files: \`*.auth.ts\`, \`*Auth*.tsx\`, \`auth/*.ts\`
|
|
3684
|
+
- Search content: patterns, function names, class names
|
|
3685
|
+
|
|
3686
|
+
## Execution Flow
|
|
3687
|
+
1. First, run 2-4 parallel searches covering different angles of the query
|
|
3688
|
+
2. Review results and identify the most relevant files
|
|
3689
|
+
3. Read the key files to understand the full context
|
|
3690
|
+
4. Provide a clear summary with exact file paths and line numbers
|
|
3691
|
+
|
|
3692
|
+
Be efficient - you have limited steps. Maximize coverage with parallel tool calls.
|
|
3693
|
+
|
|
3694
|
+
## Output Format
|
|
3695
|
+
When done, provide a summary with:
|
|
3696
|
+
- Key files/locations found (with full paths)
|
|
3697
|
+
- Relevant code snippets showing the important parts
|
|
3698
|
+
- How the pieces connect together
|
|
3699
|
+
|
|
3700
|
+
Keep your responses concise and focused on actionable information.`;
|
|
3701
|
+
}
|
|
3702
|
+
getTools(options) {
|
|
3703
|
+
const workingDirectory = options.workingDirectory;
|
|
3704
|
+
return {
|
|
3705
|
+
grep: tool7({
|
|
3706
|
+
description: "Search for patterns in files using ripgrep. Returns matching lines with file paths and line numbers.",
|
|
3707
|
+
inputSchema: z8.object({
|
|
3708
|
+
pattern: z8.string().describe("The regex pattern to search for"),
|
|
3709
|
+
path: z8.string().optional().describe("Subdirectory or file to search in (relative to working directory)"),
|
|
3710
|
+
fileType: z8.string().optional().describe('File type to filter (e.g., "ts", "js", "py")'),
|
|
3711
|
+
maxResults: z8.number().optional().default(50).describe("Maximum number of results to return")
|
|
3712
|
+
}),
|
|
3713
|
+
execute: async ({ pattern, path, fileType, maxResults }) => {
|
|
3714
|
+
try {
|
|
3715
|
+
const searchPath = path ? resolve8(workingDirectory, path) : workingDirectory;
|
|
3716
|
+
let args = ["rg", "--line-number", "--no-heading"];
|
|
3717
|
+
if (fileType) {
|
|
3718
|
+
args.push("--type", fileType);
|
|
3719
|
+
}
|
|
3720
|
+
args.push("--max-count", String(maxResults || 50));
|
|
3721
|
+
args.push("--", pattern, searchPath);
|
|
3722
|
+
const { stdout, stderr } = await execAsync4(args.join(" "), {
|
|
3723
|
+
cwd: workingDirectory,
|
|
3724
|
+
maxBuffer: 5 * 1024 * 1024,
|
|
3725
|
+
timeout: 3e4
|
|
3726
|
+
});
|
|
3727
|
+
const output = truncateOutput(stdout || "No matches found", MAX_OUTPUT_CHARS4);
|
|
3728
|
+
const matchCount = (stdout || "").split("\n").filter(Boolean).length;
|
|
3729
|
+
return {
|
|
3730
|
+
success: true,
|
|
3731
|
+
output,
|
|
3732
|
+
matchCount,
|
|
3733
|
+
pattern
|
|
3734
|
+
};
|
|
3735
|
+
} catch (error) {
|
|
3736
|
+
if (error.code === 1 && !error.stderr) {
|
|
3737
|
+
return {
|
|
3738
|
+
success: true,
|
|
3739
|
+
output: "No matches found",
|
|
3740
|
+
matchCount: 0,
|
|
3741
|
+
pattern
|
|
3742
|
+
};
|
|
3743
|
+
}
|
|
3744
|
+
return {
|
|
3745
|
+
success: false,
|
|
3746
|
+
error: error.message,
|
|
3747
|
+
pattern
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}),
|
|
3752
|
+
glob: tool7({
|
|
3753
|
+
description: "Find files matching a glob pattern. Returns list of matching file paths.",
|
|
3754
|
+
inputSchema: z8.object({
|
|
3755
|
+
pattern: z8.string().describe('Glob pattern (e.g., "**/*.ts", "src/**/*.tsx", "*.json")'),
|
|
3756
|
+
maxResults: z8.number().optional().default(100).describe("Maximum number of files to return")
|
|
3757
|
+
}),
|
|
3758
|
+
execute: async ({ pattern, maxResults }) => {
|
|
3759
|
+
try {
|
|
3760
|
+
const { stdout } = await execAsync4(
|
|
3761
|
+
`find . -type f -name "${pattern.replace("**/", "")}" 2>/dev/null | head -n ${maxResults || 100}`,
|
|
3762
|
+
{
|
|
3763
|
+
cwd: workingDirectory,
|
|
3764
|
+
timeout: 3e4
|
|
3765
|
+
}
|
|
3766
|
+
);
|
|
3767
|
+
const files = stdout.trim().split("\n").filter(Boolean);
|
|
3768
|
+
return {
|
|
3769
|
+
success: true,
|
|
3770
|
+
files,
|
|
3771
|
+
count: files.length,
|
|
3772
|
+
pattern
|
|
3773
|
+
};
|
|
3774
|
+
} catch (error) {
|
|
3775
|
+
return {
|
|
3776
|
+
success: false,
|
|
3777
|
+
error: error.message,
|
|
3778
|
+
pattern
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
}
|
|
3782
|
+
}),
|
|
3783
|
+
read_file: tool7({
|
|
3784
|
+
description: "Read the contents of a file. Use this to examine specific files found in search.",
|
|
3785
|
+
inputSchema: z8.object({
|
|
3786
|
+
path: z8.string().describe("Path to the file (relative to working directory or absolute)"),
|
|
3787
|
+
startLine: z8.number().optional().describe("Start reading from this line (1-indexed)"),
|
|
3788
|
+
endLine: z8.number().optional().describe("Stop reading at this line (1-indexed, inclusive)")
|
|
3789
|
+
}),
|
|
3790
|
+
execute: async ({ path, startLine, endLine }) => {
|
|
3791
|
+
try {
|
|
3792
|
+
const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
|
|
3793
|
+
if (!existsSync10(absolutePath)) {
|
|
3794
|
+
return {
|
|
3795
|
+
success: false,
|
|
3796
|
+
error: `File not found: ${path}`
|
|
3797
|
+
};
|
|
3798
|
+
}
|
|
3799
|
+
const stats = await stat3(absolutePath);
|
|
3800
|
+
if (stats.size > MAX_FILE_SIZE2) {
|
|
3801
|
+
return {
|
|
3802
|
+
success: false,
|
|
3803
|
+
error: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Use startLine/endLine to read portions.`
|
|
3804
|
+
};
|
|
3805
|
+
}
|
|
3806
|
+
let content = await readFile7(absolutePath, "utf-8");
|
|
3807
|
+
if (startLine !== void 0 || endLine !== void 0) {
|
|
3808
|
+
const lines = content.split("\n");
|
|
3809
|
+
const start = (startLine ?? 1) - 1;
|
|
3810
|
+
const end = endLine ?? lines.length;
|
|
3811
|
+
content = lines.slice(start, end).join("\n");
|
|
3812
|
+
}
|
|
3813
|
+
return {
|
|
3814
|
+
success: true,
|
|
3815
|
+
path: relative6(workingDirectory, absolutePath),
|
|
3816
|
+
content: truncateOutput(content, MAX_OUTPUT_CHARS4),
|
|
3817
|
+
lineCount: content.split("\n").length
|
|
3818
|
+
};
|
|
3819
|
+
} catch (error) {
|
|
3820
|
+
return {
|
|
3821
|
+
success: false,
|
|
3822
|
+
error: error.message
|
|
3823
|
+
};
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
}),
|
|
3827
|
+
list_dir: tool7({
|
|
3828
|
+
description: "List contents of a directory. Shows files and subdirectories.",
|
|
3829
|
+
inputSchema: z8.object({
|
|
3830
|
+
path: z8.string().optional().default(".").describe("Directory path (relative to working directory)"),
|
|
3831
|
+
recursive: z8.boolean().optional().default(false).describe("List recursively (be careful with large directories)"),
|
|
3832
|
+
maxDepth: z8.number().optional().default(2).describe("Maximum depth for recursive listing")
|
|
3833
|
+
}),
|
|
3834
|
+
execute: async ({ path, recursive, maxDepth }) => {
|
|
3835
|
+
try {
|
|
3836
|
+
const absolutePath = isAbsolute4(path) ? path : resolve8(workingDirectory, path);
|
|
3837
|
+
if (!existsSync10(absolutePath)) {
|
|
3838
|
+
return {
|
|
3839
|
+
success: false,
|
|
3840
|
+
error: `Directory not found: ${path}`
|
|
3841
|
+
};
|
|
3842
|
+
}
|
|
3843
|
+
const stats = await stat3(absolutePath);
|
|
3844
|
+
if (!stats.isDirectory()) {
|
|
3845
|
+
return {
|
|
3846
|
+
success: false,
|
|
3847
|
+
error: `Not a directory: ${path}`
|
|
3848
|
+
};
|
|
3849
|
+
}
|
|
3850
|
+
if (recursive) {
|
|
3851
|
+
const { stdout } = await execAsync4(
|
|
3852
|
+
`find . -maxdepth ${maxDepth} -type f 2>/dev/null | head -n 200`,
|
|
3853
|
+
{
|
|
3854
|
+
cwd: absolutePath,
|
|
3855
|
+
timeout: 1e4
|
|
3856
|
+
}
|
|
3857
|
+
);
|
|
3858
|
+
const files = stdout.trim().split("\n").filter(Boolean);
|
|
3859
|
+
return {
|
|
3860
|
+
success: true,
|
|
3861
|
+
path: relative6(workingDirectory, absolutePath) || ".",
|
|
3862
|
+
files,
|
|
3863
|
+
count: files.length,
|
|
3864
|
+
recursive: true
|
|
3865
|
+
};
|
|
3866
|
+
} else {
|
|
3867
|
+
const entries = await readdir3(absolutePath, { withFileTypes: true });
|
|
3868
|
+
const items = entries.slice(0, 200).map((e) => ({
|
|
3869
|
+
name: e.name,
|
|
3870
|
+
type: e.isDirectory() ? "directory" : "file"
|
|
3871
|
+
}));
|
|
3872
|
+
return {
|
|
3873
|
+
success: true,
|
|
3874
|
+
path: relative6(workingDirectory, absolutePath) || ".",
|
|
3875
|
+
items,
|
|
3876
|
+
count: items.length
|
|
3877
|
+
};
|
|
3878
|
+
}
|
|
3879
|
+
} catch (error) {
|
|
3880
|
+
return {
|
|
3881
|
+
success: false,
|
|
3882
|
+
error: error.message
|
|
3883
|
+
};
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
})
|
|
3887
|
+
};
|
|
3888
|
+
}
|
|
3889
|
+
parseResult(text2, steps) {
|
|
3890
|
+
const findings = [];
|
|
3891
|
+
let filesSearched = 0;
|
|
3892
|
+
let matchCount = 0;
|
|
3893
|
+
for (const step of steps) {
|
|
3894
|
+
if (step.type === "tool_result" && step.toolOutput) {
|
|
3895
|
+
const output = step.toolOutput;
|
|
3896
|
+
if (step.toolName === "grep" && output.success) {
|
|
3897
|
+
matchCount += output.matchCount || 0;
|
|
3898
|
+
const lines = (output.output || "").split("\n").filter(Boolean).slice(0, 10);
|
|
3899
|
+
for (const line of lines) {
|
|
3900
|
+
const match = line.match(/^([^:]+):(\d+):(.*)$/);
|
|
3901
|
+
if (match) {
|
|
3902
|
+
findings.push({
|
|
3903
|
+
type: "match",
|
|
3904
|
+
path: match[1],
|
|
3905
|
+
lineNumber: parseInt(match[2], 10),
|
|
3906
|
+
content: match[3].trim(),
|
|
3907
|
+
relevance: "high"
|
|
3908
|
+
});
|
|
2878
3909
|
}
|
|
2879
3910
|
}
|
|
3911
|
+
} else if (step.toolName === "glob" && output.success) {
|
|
3912
|
+
filesSearched += output.count || 0;
|
|
3913
|
+
for (const file of (output.files || []).slice(0, 5)) {
|
|
3914
|
+
findings.push({
|
|
3915
|
+
type: "file",
|
|
3916
|
+
path: file,
|
|
3917
|
+
relevance: "medium"
|
|
3918
|
+
});
|
|
3919
|
+
}
|
|
3920
|
+
} else if (step.toolName === "read_file" && output.success) {
|
|
3921
|
+
findings.push({
|
|
3922
|
+
type: "file",
|
|
3923
|
+
path: output.path,
|
|
3924
|
+
relevance: "high",
|
|
3925
|
+
context: `${output.lineCount} lines`
|
|
3926
|
+
});
|
|
2880
3927
|
}
|
|
2881
|
-
|
|
3928
|
+
}
|
|
3929
|
+
}
|
|
3930
|
+
const query = steps.length > 0 ? steps.find((s) => s.type === "text")?.content || "" : "";
|
|
3931
|
+
return {
|
|
3932
|
+
query,
|
|
3933
|
+
summary: text2,
|
|
3934
|
+
findings: findings.slice(0, 20),
|
|
3935
|
+
// Limit findings
|
|
3936
|
+
filesSearched,
|
|
3937
|
+
matchCount
|
|
3938
|
+
};
|
|
3939
|
+
}
|
|
3940
|
+
};
|
|
3941
|
+
function createSearchSubagent(model) {
|
|
3942
|
+
return new SearchSubagent(model);
|
|
3943
|
+
}
|
|
3944
|
+
|
|
3945
|
+
// src/tools/search.ts
|
|
3946
|
+
var MAX_RESULT_CHARS = 8e3;
|
|
3947
|
+
function createSearchTool(options) {
|
|
3948
|
+
return tool8({
|
|
3949
|
+
description: `Delegate a search task to a specialized search agent. Use this when you need to:
|
|
3950
|
+
- Find files or code matching a pattern
|
|
3951
|
+
- Explore the codebase structure
|
|
3952
|
+
- Search for specific functions, classes, or variables
|
|
3953
|
+
- Understand how a feature is implemented
|
|
3954
|
+
|
|
3955
|
+
The search agent will explore the codebase and return a summary of findings.
|
|
3956
|
+
This is more thorough than a simple grep because it can follow references and understand context.
|
|
3957
|
+
|
|
3958
|
+
Examples:
|
|
3959
|
+
- "Find all React components that use the useState hook"
|
|
3960
|
+
- "Where is the authentication logic implemented?"
|
|
3961
|
+
- "Find all API routes and their handlers"
|
|
3962
|
+
- "Search for usages of the UserService class"`,
|
|
3963
|
+
inputSchema: z9.object({
|
|
3964
|
+
query: z9.string().describe("What to search for. Be specific about what you're looking for."),
|
|
3965
|
+
context: z9.string().optional().describe("Optional additional context about why you need this information.")
|
|
3966
|
+
}),
|
|
3967
|
+
execute: async ({ query, context }, toolOptions) => {
|
|
3968
|
+
const toolCallId = toolOptions.toolCallId || `search_${Date.now()}`;
|
|
3969
|
+
await options.onProgress?.({
|
|
3970
|
+
status: "started",
|
|
3971
|
+
subagentId: toolCallId
|
|
3972
|
+
});
|
|
3973
|
+
try {
|
|
3974
|
+
const subagent = createSearchSubagent();
|
|
3975
|
+
const fullTask = context ? `${query}
|
|
3976
|
+
|
|
3977
|
+
Context: ${context}` : query;
|
|
3978
|
+
const result = await subagent.run({
|
|
3979
|
+
task: fullTask,
|
|
3980
|
+
sessionId: options.sessionId,
|
|
3981
|
+
toolCallId,
|
|
3982
|
+
workingDirectory: options.workingDirectory,
|
|
3983
|
+
onProgress: async (event) => {
|
|
3984
|
+
if (event.type === "step" && event.step) {
|
|
3985
|
+
await options.onProgress?.({
|
|
3986
|
+
status: "step",
|
|
3987
|
+
subagentId: event.subagentId,
|
|
3988
|
+
stepType: event.step.type,
|
|
3989
|
+
stepContent: event.step.content,
|
|
3990
|
+
toolName: event.step.toolName,
|
|
3991
|
+
toolInput: event.step.toolInput,
|
|
3992
|
+
toolOutput: event.step.toolOutput
|
|
3993
|
+
});
|
|
3994
|
+
} else if (event.type === "complete") {
|
|
3995
|
+
await options.onProgress?.({
|
|
3996
|
+
status: "complete",
|
|
3997
|
+
subagentId: event.subagentId,
|
|
3998
|
+
result: event.result
|
|
3999
|
+
});
|
|
4000
|
+
} else if (event.type === "error") {
|
|
4001
|
+
await options.onProgress?.({
|
|
4002
|
+
status: "error",
|
|
4003
|
+
subagentId: event.subagentId,
|
|
4004
|
+
error: event.error
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
});
|
|
4009
|
+
if (!result.success) {
|
|
2882
4010
|
return {
|
|
2883
|
-
success:
|
|
2884
|
-
|
|
2885
|
-
|
|
2886
|
-
totalErrors: 0,
|
|
2887
|
-
totalWarnings: 0
|
|
4011
|
+
success: false,
|
|
4012
|
+
error: result.error || "Search failed",
|
|
4013
|
+
executionId: result.executionId
|
|
2888
4014
|
};
|
|
2889
4015
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
4016
|
+
const searchResult = result.result;
|
|
4017
|
+
let formattedResult = `## Search Results
|
|
4018
|
+
|
|
4019
|
+
`;
|
|
4020
|
+
formattedResult += `**Summary:** ${searchResult.summary}
|
|
4021
|
+
|
|
4022
|
+
`;
|
|
4023
|
+
if (searchResult.findings.length > 0) {
|
|
4024
|
+
formattedResult += `### Key Findings (${searchResult.findings.length} items)
|
|
4025
|
+
|
|
4026
|
+
`;
|
|
4027
|
+
for (const finding of searchResult.findings) {
|
|
4028
|
+
if (finding.type === "match") {
|
|
4029
|
+
formattedResult += `- **${finding.path}:${finding.lineNumber}** - ${truncateOutput(finding.content || "", 200)}
|
|
4030
|
+
`;
|
|
4031
|
+
} else if (finding.type === "file") {
|
|
4032
|
+
formattedResult += `- **${finding.path}** ${finding.context ? `(${finding.context})` : ""}
|
|
4033
|
+
`;
|
|
4034
|
+
}
|
|
2898
4035
|
}
|
|
2899
4036
|
}
|
|
2900
|
-
|
|
4037
|
+
formattedResult += `
|
|
4038
|
+
**Stats:** ${searchResult.matchCount} matches across ${searchResult.filesSearched} files searched`;
|
|
4039
|
+
return {
|
|
4040
|
+
success: true,
|
|
4041
|
+
query: searchResult.query,
|
|
4042
|
+
summary: searchResult.summary,
|
|
4043
|
+
findings: searchResult.findings,
|
|
4044
|
+
matchCount: searchResult.matchCount,
|
|
4045
|
+
filesSearched: searchResult.filesSearched,
|
|
4046
|
+
formattedResult: truncateOutput(formattedResult, MAX_RESULT_CHARS),
|
|
4047
|
+
executionId: result.executionId,
|
|
4048
|
+
stepsCount: result.steps.length
|
|
4049
|
+
};
|
|
2901
4050
|
} catch (error) {
|
|
4051
|
+
await options.onProgress?.({
|
|
4052
|
+
status: "error",
|
|
4053
|
+
error: error.message
|
|
4054
|
+
});
|
|
2902
4055
|
return {
|
|
2903
4056
|
success: false,
|
|
2904
4057
|
error: error.message
|
|
@@ -2907,84 +4060,6 @@ Working directory: ${options.workingDirectory}`,
|
|
|
2907
4060
|
}
|
|
2908
4061
|
});
|
|
2909
4062
|
}
|
|
2910
|
-
function formatDiagnosticsResult(diagnosticsMap, workingDirectory) {
|
|
2911
|
-
let totalErrors = 0;
|
|
2912
|
-
let totalWarnings = 0;
|
|
2913
|
-
let totalInfo = 0;
|
|
2914
|
-
const files = [];
|
|
2915
|
-
for (const [filePath, diagnostics] of Object.entries(diagnosticsMap)) {
|
|
2916
|
-
const relativePath = relative4(workingDirectory, filePath);
|
|
2917
|
-
let fileErrors = 0;
|
|
2918
|
-
let fileWarnings = 0;
|
|
2919
|
-
const formattedDiagnostics = diagnostics.map((d) => {
|
|
2920
|
-
const severity = getSeverityString(d.severity);
|
|
2921
|
-
if (d.severity === 1 /* Error */) {
|
|
2922
|
-
fileErrors++;
|
|
2923
|
-
totalErrors++;
|
|
2924
|
-
} else if (d.severity === 2 /* Warning */) {
|
|
2925
|
-
fileWarnings++;
|
|
2926
|
-
totalWarnings++;
|
|
2927
|
-
} else {
|
|
2928
|
-
totalInfo++;
|
|
2929
|
-
}
|
|
2930
|
-
return {
|
|
2931
|
-
severity,
|
|
2932
|
-
line: d.range.start.line + 1,
|
|
2933
|
-
column: d.range.start.character + 1,
|
|
2934
|
-
message: d.message,
|
|
2935
|
-
source: d.source,
|
|
2936
|
-
code: d.code
|
|
2937
|
-
};
|
|
2938
|
-
});
|
|
2939
|
-
files.push({
|
|
2940
|
-
path: filePath,
|
|
2941
|
-
relativePath,
|
|
2942
|
-
errors: fileErrors,
|
|
2943
|
-
warnings: fileWarnings,
|
|
2944
|
-
diagnostics: formattedDiagnostics
|
|
2945
|
-
});
|
|
2946
|
-
}
|
|
2947
|
-
files.sort((a, b) => b.errors - a.errors);
|
|
2948
|
-
const hasIssues = totalErrors > 0 || totalWarnings > 0;
|
|
2949
|
-
return {
|
|
2950
|
-
success: true,
|
|
2951
|
-
message: hasIssues ? `Found ${totalErrors} error(s) and ${totalWarnings} warning(s) in ${files.length} file(s).` : `No lint errors found in ${Object.keys(diagnosticsMap).length || "any"} file(s).`,
|
|
2952
|
-
files,
|
|
2953
|
-
totalErrors,
|
|
2954
|
-
totalWarnings,
|
|
2955
|
-
totalInfo,
|
|
2956
|
-
summary: hasIssues ? formatSummary(files) : void 0
|
|
2957
|
-
};
|
|
2958
|
-
}
|
|
2959
|
-
function getSeverityString(severity) {
|
|
2960
|
-
switch (severity) {
|
|
2961
|
-
case 1 /* Error */:
|
|
2962
|
-
return "error";
|
|
2963
|
-
case 2 /* Warning */:
|
|
2964
|
-
return "warning";
|
|
2965
|
-
case 3 /* Information */:
|
|
2966
|
-
return "info";
|
|
2967
|
-
case 4 /* Hint */:
|
|
2968
|
-
return "hint";
|
|
2969
|
-
default:
|
|
2970
|
-
return "error";
|
|
2971
|
-
}
|
|
2972
|
-
}
|
|
2973
|
-
function formatSummary(files) {
|
|
2974
|
-
const lines = [];
|
|
2975
|
-
for (const file of files) {
|
|
2976
|
-
lines.push(`
|
|
2977
|
-
${file.relativePath}:`);
|
|
2978
|
-
for (const d of file.diagnostics.slice(0, 10)) {
|
|
2979
|
-
const prefix = d.severity === "error" ? "\u274C" : d.severity === "warning" ? "\u26A0\uFE0F" : "\u2139\uFE0F";
|
|
2980
|
-
lines.push(` ${prefix} [${d.line}:${d.column}] ${d.message}`);
|
|
2981
|
-
}
|
|
2982
|
-
if (file.diagnostics.length > 10) {
|
|
2983
|
-
lines.push(` ... and ${file.diagnostics.length - 10} more`);
|
|
2984
|
-
}
|
|
2985
|
-
}
|
|
2986
|
-
return lines.join("\n");
|
|
2987
|
-
}
|
|
2988
4063
|
|
|
2989
4064
|
// src/tools/index.ts
|
|
2990
4065
|
function createTools(options) {
|
|
@@ -3001,7 +4076,8 @@ function createTools(options) {
|
|
|
3001
4076
|
write_file: createWriteFileTool({
|
|
3002
4077
|
workingDirectory: options.workingDirectory,
|
|
3003
4078
|
sessionId: options.sessionId,
|
|
3004
|
-
enableLSP: options.enableLSP ?? true
|
|
4079
|
+
enableLSP: options.enableLSP ?? true,
|
|
4080
|
+
onProgress: options.onWriteFileProgress
|
|
3005
4081
|
}),
|
|
3006
4082
|
todo: createTodoTool({
|
|
3007
4083
|
sessionId: options.sessionId
|
|
@@ -3012,15 +4088,20 @@ function createTools(options) {
|
|
|
3012
4088
|
}),
|
|
3013
4089
|
linter: createLinterTool({
|
|
3014
4090
|
workingDirectory: options.workingDirectory
|
|
4091
|
+
}),
|
|
4092
|
+
search: createSearchTool({
|
|
4093
|
+
sessionId: options.sessionId,
|
|
4094
|
+
workingDirectory: options.workingDirectory,
|
|
4095
|
+
onProgress: options.onSearchProgress
|
|
3015
4096
|
})
|
|
3016
4097
|
};
|
|
3017
4098
|
}
|
|
3018
4099
|
|
|
3019
4100
|
// src/agent/context.ts
|
|
3020
|
-
import { generateText } from "ai";
|
|
3021
|
-
import { gateway } from "@ai-sdk/gateway";
|
|
4101
|
+
import { generateText as generateText2 } from "ai";
|
|
3022
4102
|
|
|
3023
4103
|
// src/agent/prompts.ts
|
|
4104
|
+
init_skills();
|
|
3024
4105
|
import os from "os";
|
|
3025
4106
|
function getSearchInstructions() {
|
|
3026
4107
|
const platform3 = process.platform;
|
|
@@ -3039,9 +4120,33 @@ function getSearchInstructions() {
|
|
|
3039
4120
|
- **If ripgrep (\`rg\`) is installed**: \`rg "pattern" -t ts src/\` - faster and respects .gitignore`;
|
|
3040
4121
|
}
|
|
3041
4122
|
async function buildSystemPrompt(options) {
|
|
3042
|
-
const {
|
|
3043
|
-
|
|
3044
|
-
|
|
4123
|
+
const {
|
|
4124
|
+
workingDirectory,
|
|
4125
|
+
skillsDirectories,
|
|
4126
|
+
sessionId,
|
|
4127
|
+
discoveredSkills,
|
|
4128
|
+
activeFiles = [],
|
|
4129
|
+
customInstructions
|
|
4130
|
+
} = options;
|
|
4131
|
+
let alwaysLoadedContent = "";
|
|
4132
|
+
let globMatchedContent = "";
|
|
4133
|
+
let agentsMdContent = "";
|
|
4134
|
+
let onDemandSkillsContext = "";
|
|
4135
|
+
if (discoveredSkills) {
|
|
4136
|
+
const { always, onDemand, all } = await loadAllSkillsFromDiscovered(discoveredSkills);
|
|
4137
|
+
alwaysLoadedContent = formatAlwaysLoadedSkills(always);
|
|
4138
|
+
onDemandSkillsContext = formatSkillsForContext(onDemand);
|
|
4139
|
+
const agentsMd = await loadAgentsMd(discoveredSkills.agentsMdPath);
|
|
4140
|
+
agentsMdContent = formatAgentsMdContent(agentsMd);
|
|
4141
|
+
if (activeFiles.length > 0) {
|
|
4142
|
+
const globMatched = await getGlobMatchedSkills(all, activeFiles, workingDirectory);
|
|
4143
|
+
globMatchedContent = formatGlobMatchedSkills(globMatched);
|
|
4144
|
+
}
|
|
4145
|
+
} else {
|
|
4146
|
+
const { loadAllSkills: loadAllSkills2 } = await Promise.resolve().then(() => (init_skills(), skills_exports));
|
|
4147
|
+
const skills = await loadAllSkills2(skillsDirectories);
|
|
4148
|
+
onDemandSkillsContext = formatSkillsForContext(skills);
|
|
4149
|
+
}
|
|
3045
4150
|
const todos = todoQueries.getBySession(sessionId);
|
|
3046
4151
|
const todosContext = formatTodosForContext(todos);
|
|
3047
4152
|
const platform3 = process.platform === "win32" ? "Windows" : process.platform === "darwin" ? "macOS" : "Linux";
|
|
@@ -3062,6 +4167,7 @@ You have access to powerful tools for:
|
|
|
3062
4167
|
- **linter**: Check files for type errors and lint issues (TypeScript, JavaScript, TSX, JSX)
|
|
3063
4168
|
- **todo**: Manage your task list to track progress on complex operations
|
|
3064
4169
|
- **load_skill**: Load specialized knowledge documents for specific tasks
|
|
4170
|
+
- **search**: Semantic search using a subagent - for exploratory questions and finding code by meaning
|
|
3065
4171
|
|
|
3066
4172
|
|
|
3067
4173
|
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.
|
|
@@ -3138,6 +4244,9 @@ Terminal output is stored in the global SparkECoder data directory. Use the \`ta
|
|
|
3138
4244
|
- Use \`write_file\` with mode "full" only for new files or complete rewrites
|
|
3139
4245
|
- After making changes, use the \`linter\` tool to check for type errors and lint issues
|
|
3140
4246
|
- The \`write_file\` tool automatically shows lint errors in its output for TypeScript/JavaScript files
|
|
4247
|
+
- If the user asks to write/create a file, always use \`write_file\` rather than printing the full contents
|
|
4248
|
+
- If the user requests a file but does not provide a path, choose a sensible default (e.g. \`index.html\`) and proceed
|
|
4249
|
+
- For large content (hundreds of lines), avoid placing it in chat output; write to a file instead
|
|
3141
4250
|
|
|
3142
4251
|
### Linter Tool
|
|
3143
4252
|
The linter tool uses Language Server Protocol (LSP) to detect type errors and lint issues:
|
|
@@ -3149,6 +4258,30 @@ linter({ paths: ["src/"] }) // Check all files in a directory
|
|
|
3149
4258
|
Use this proactively after making code changes to catch errors early.
|
|
3150
4259
|
|
|
3151
4260
|
### Searching and Exploration
|
|
4261
|
+
|
|
4262
|
+
**Choose the right search approach:**
|
|
4263
|
+
|
|
4264
|
+
1. **Use the \`search\` tool (subagent)** for:
|
|
4265
|
+
- Semantic/exploratory questions: "How does authentication work?", "Where is user data processed?"
|
|
4266
|
+
- Finding code by meaning or concept, not exact text
|
|
4267
|
+
- Understanding how features are implemented across multiple files
|
|
4268
|
+
- Exploring unfamiliar parts of the codebase
|
|
4269
|
+
- Questions like "where", "how", "what does X do"
|
|
4270
|
+
|
|
4271
|
+
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.
|
|
4272
|
+
|
|
4273
|
+
2. **Use direct commands (grep/rg, find)** for:
|
|
4274
|
+
- Exact string matches: \`rg "functionName"\`, \`rg "class MyClass"\`
|
|
4275
|
+
- Finding files by name: \`find . -name "*.config.ts"\`
|
|
4276
|
+
- Simple pattern matching when you know exactly what you're looking for
|
|
4277
|
+
- Counting occurrences or listing all matches
|
|
4278
|
+
|
|
4279
|
+
**Examples:**
|
|
4280
|
+
- "Where is the API authentication handled?" \u2192 Use \`search\` tool
|
|
4281
|
+
- "Find all usages of getUserById" \u2192 Use \`rg "getUserById"\`
|
|
4282
|
+
- "How does the payment flow work?" \u2192 Use \`search\` tool
|
|
4283
|
+
- "Find files named config" \u2192 Use \`find . -name "*config*"\`
|
|
4284
|
+
|
|
3152
4285
|
${searchInstructions}
|
|
3153
4286
|
|
|
3154
4287
|
###Follow these principles when designing and implementing software:
|
|
@@ -3171,11 +4304,11 @@ ${searchInstructions}
|
|
|
3171
4304
|
16. **Diversity** \u2014 Distrust all claims for "one true way"
|
|
3172
4305
|
17. **Extensibility** \u2014 Design for the future, because it will be here sooner than you think
|
|
3173
4306
|
|
|
3174
|
-
###Follow these
|
|
4307
|
+
### Follow these rules to be a good agent for the user:
|
|
3175
4308
|
|
|
3176
|
-
1. Understand first - Read relevant files before making any changes. Use search
|
|
4309
|
+
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.
|
|
3177
4310
|
2. Plan for complexity - If the task involves 3+ steps or has meaningful trade-offs, create a todo list to track progress before implementing.
|
|
3178
|
-
3. Use the right tools - Have specialized tools for reading files, editing code,
|
|
4311
|
+
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.
|
|
3179
4312
|
4. Work efficiently - When need to do multiple independent things (like reading several files), do them in parallel rather than one at a time.
|
|
3180
4313
|
5. Be direct - Focus on technical accuracy rather than validation. If see issues with an approach or need clarification, say so.
|
|
3181
4314
|
6. Verify my work - After making changes, check for linter errors and fix any introduced.
|
|
@@ -3188,8 +4321,14 @@ ${searchInstructions}
|
|
|
3188
4321
|
- Ask clarifying questions when requirements are ambiguous
|
|
3189
4322
|
- Report progress on multi-step tasks
|
|
3190
4323
|
|
|
3191
|
-
|
|
3192
|
-
|
|
4324
|
+
${agentsMdContent}
|
|
4325
|
+
|
|
4326
|
+
${alwaysLoadedContent}
|
|
4327
|
+
|
|
4328
|
+
${globMatchedContent}
|
|
4329
|
+
|
|
4330
|
+
## On-Demand Skills
|
|
4331
|
+
${onDemandSkillsContext}
|
|
3193
4332
|
|
|
3194
4333
|
## Current Task List
|
|
3195
4334
|
${todosContext}
|
|
@@ -3284,8 +4423,8 @@ ${this.summary}`
|
|
|
3284
4423
|
try {
|
|
3285
4424
|
const config = getConfig();
|
|
3286
4425
|
const summaryPrompt = createSummaryPrompt(historyText);
|
|
3287
|
-
const result = await
|
|
3288
|
-
model:
|
|
4426
|
+
const result = await generateText2({
|
|
4427
|
+
model: resolveModel(config.defaultModel),
|
|
3289
4428
|
prompt: summaryPrompt
|
|
3290
4429
|
});
|
|
3291
4430
|
this.summary = result.text;
|
|
@@ -3355,7 +4494,9 @@ var Agent = class _Agent {
|
|
|
3355
4494
|
sessionId: this.session.id,
|
|
3356
4495
|
workingDirectory: this.session.workingDirectory,
|
|
3357
4496
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
3358
|
-
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0
|
|
4497
|
+
onBashProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "bash", data: progress }) : void 0,
|
|
4498
|
+
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
4499
|
+
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "search", data: progress }) : void 0
|
|
3359
4500
|
});
|
|
3360
4501
|
}
|
|
3361
4502
|
/**
|
|
@@ -3464,28 +4605,33 @@ ${prompt}` });
|
|
|
3464
4605
|
const systemPrompt = await buildSystemPrompt({
|
|
3465
4606
|
workingDirectory: this.session.workingDirectory,
|
|
3466
4607
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
3467
|
-
sessionId: this.session.id
|
|
4608
|
+
sessionId: this.session.id,
|
|
4609
|
+
discoveredSkills: config.discoveredSkills,
|
|
4610
|
+
// TODO: Pass activeFiles from client for glob matching
|
|
4611
|
+
activeFiles: []
|
|
3468
4612
|
});
|
|
3469
4613
|
const messages2 = await this.context.getMessages();
|
|
3470
4614
|
const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
3471
4615
|
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
3472
|
-
const
|
|
3473
|
-
|
|
4616
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
4617
|
+
const stream = streamText2({
|
|
4618
|
+
model: resolveModel(this.session.model),
|
|
3474
4619
|
system: systemPrompt,
|
|
3475
4620
|
messages: messages2,
|
|
3476
4621
|
tools: wrappedTools,
|
|
3477
|
-
stopWhen:
|
|
4622
|
+
stopWhen: stepCountIs2(500),
|
|
3478
4623
|
// Forward abort signal if provided
|
|
3479
4624
|
abortSignal: options.abortSignal,
|
|
3480
4625
|
// Enable extended thinking/reasoning for models that support it
|
|
3481
|
-
providerOptions: {
|
|
4626
|
+
providerOptions: useAnthropic ? {
|
|
3482
4627
|
anthropic: {
|
|
4628
|
+
toolStreaming: true,
|
|
3483
4629
|
thinking: {
|
|
3484
4630
|
type: "enabled",
|
|
3485
4631
|
budgetTokens: 1e4
|
|
3486
4632
|
}
|
|
3487
4633
|
}
|
|
3488
|
-
},
|
|
4634
|
+
} : void 0,
|
|
3489
4635
|
onStepFinish: async (step) => {
|
|
3490
4636
|
options.onStepFinish?.(step);
|
|
3491
4637
|
},
|
|
@@ -3515,26 +4661,29 @@ ${prompt}` });
|
|
|
3515
4661
|
const systemPrompt = await buildSystemPrompt({
|
|
3516
4662
|
workingDirectory: this.session.workingDirectory,
|
|
3517
4663
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
3518
|
-
sessionId: this.session.id
|
|
4664
|
+
sessionId: this.session.id,
|
|
4665
|
+
discoveredSkills: config.discoveredSkills,
|
|
4666
|
+
activeFiles: []
|
|
3519
4667
|
});
|
|
3520
4668
|
const messages2 = await this.context.getMessages();
|
|
3521
4669
|
const tools = options.onToolProgress ? this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
3522
4670
|
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
3523
|
-
const
|
|
3524
|
-
|
|
4671
|
+
const useAnthropic = isAnthropicModel(this.session.model);
|
|
4672
|
+
const result = await generateText3({
|
|
4673
|
+
model: resolveModel(this.session.model),
|
|
3525
4674
|
system: systemPrompt,
|
|
3526
4675
|
messages: messages2,
|
|
3527
4676
|
tools: wrappedTools,
|
|
3528
|
-
stopWhen:
|
|
4677
|
+
stopWhen: stepCountIs2(500),
|
|
3529
4678
|
// Enable extended thinking/reasoning for models that support it
|
|
3530
|
-
providerOptions: {
|
|
4679
|
+
providerOptions: useAnthropic ? {
|
|
3531
4680
|
anthropic: {
|
|
3532
4681
|
thinking: {
|
|
3533
4682
|
type: "enabled",
|
|
3534
4683
|
budgetTokens: 1e4
|
|
3535
4684
|
}
|
|
3536
4685
|
}
|
|
3537
|
-
}
|
|
4686
|
+
} : void 0
|
|
3538
4687
|
});
|
|
3539
4688
|
const responseMessages = result.response.messages;
|
|
3540
4689
|
this.context.addResponseMessages(responseMessages);
|
|
@@ -3556,11 +4705,11 @@ ${prompt}` });
|
|
|
3556
4705
|
wrappedTools[name] = originalTool;
|
|
3557
4706
|
continue;
|
|
3558
4707
|
}
|
|
3559
|
-
wrappedTools[name] =
|
|
4708
|
+
wrappedTools[name] = tool9({
|
|
3560
4709
|
description: originalTool.description || "",
|
|
3561
|
-
inputSchema: originalTool.inputSchema ||
|
|
4710
|
+
inputSchema: originalTool.inputSchema || z10.object({}),
|
|
3562
4711
|
execute: async (input, toolOptions) => {
|
|
3563
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
4712
|
+
const toolCallId = toolOptions.toolCallId || nanoid4();
|
|
3564
4713
|
const execution = toolExecutionQueries.create({
|
|
3565
4714
|
sessionId: this.session.id,
|
|
3566
4715
|
toolName: name,
|
|
@@ -3572,8 +4721,8 @@ ${prompt}` });
|
|
|
3572
4721
|
this.pendingApprovals.set(toolCallId, execution);
|
|
3573
4722
|
options.onApprovalRequired?.(execution);
|
|
3574
4723
|
sessionQueries.updateStatus(this.session.id, "waiting");
|
|
3575
|
-
const approved = await new Promise((
|
|
3576
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
4724
|
+
const approved = await new Promise((resolve11) => {
|
|
4725
|
+
approvalResolvers.set(toolCallId, { resolve: resolve11, sessionId: this.session.id });
|
|
3577
4726
|
});
|
|
3578
4727
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
3579
4728
|
approvalResolvers.delete(toolCallId);
|
|
@@ -3668,18 +4817,18 @@ ${prompt}` });
|
|
|
3668
4817
|
|
|
3669
4818
|
// src/server/routes/sessions.ts
|
|
3670
4819
|
var sessions2 = new Hono();
|
|
3671
|
-
var createSessionSchema =
|
|
3672
|
-
name:
|
|
3673
|
-
workingDirectory:
|
|
3674
|
-
model:
|
|
3675
|
-
toolApprovals:
|
|
4820
|
+
var createSessionSchema = z11.object({
|
|
4821
|
+
name: z11.string().optional(),
|
|
4822
|
+
workingDirectory: z11.string().optional(),
|
|
4823
|
+
model: z11.string().optional(),
|
|
4824
|
+
toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
|
|
3676
4825
|
});
|
|
3677
|
-
var paginationQuerySchema =
|
|
3678
|
-
limit:
|
|
3679
|
-
offset:
|
|
4826
|
+
var paginationQuerySchema = z11.object({
|
|
4827
|
+
limit: z11.string().optional(),
|
|
4828
|
+
offset: z11.string().optional()
|
|
3680
4829
|
});
|
|
3681
|
-
var messagesQuerySchema =
|
|
3682
|
-
limit:
|
|
4830
|
+
var messagesQuerySchema = z11.object({
|
|
4831
|
+
limit: z11.string().optional()
|
|
3683
4832
|
});
|
|
3684
4833
|
sessions2.get(
|
|
3685
4834
|
"/",
|
|
@@ -3818,10 +4967,10 @@ sessions2.get("/:id/tools", async (c) => {
|
|
|
3818
4967
|
count: executions.length
|
|
3819
4968
|
});
|
|
3820
4969
|
});
|
|
3821
|
-
var updateSessionSchema =
|
|
3822
|
-
model:
|
|
3823
|
-
name:
|
|
3824
|
-
toolApprovals:
|
|
4970
|
+
var updateSessionSchema = z11.object({
|
|
4971
|
+
model: z11.string().optional(),
|
|
4972
|
+
name: z11.string().optional(),
|
|
4973
|
+
toolApprovals: z11.record(z11.string(), z11.boolean()).optional()
|
|
3825
4974
|
});
|
|
3826
4975
|
sessions2.patch(
|
|
3827
4976
|
"/:id",
|
|
@@ -4020,11 +5169,11 @@ sessions2.get("/:id/diff/:filePath", async (c) => {
|
|
|
4020
5169
|
});
|
|
4021
5170
|
function getAttachmentsDir(sessionId) {
|
|
4022
5171
|
const appDataDir = getAppDataDirectory();
|
|
4023
|
-
return
|
|
5172
|
+
return join4(appDataDir, "attachments", sessionId);
|
|
4024
5173
|
}
|
|
4025
5174
|
function ensureAttachmentsDir(sessionId) {
|
|
4026
5175
|
const dir = getAttachmentsDir(sessionId);
|
|
4027
|
-
if (!
|
|
5176
|
+
if (!existsSync11(dir)) {
|
|
4028
5177
|
mkdirSync3(dir, { recursive: true });
|
|
4029
5178
|
}
|
|
4030
5179
|
return dir;
|
|
@@ -4036,12 +5185,12 @@ sessions2.get("/:id/attachments", async (c) => {
|
|
|
4036
5185
|
return c.json({ error: "Session not found" }, 404);
|
|
4037
5186
|
}
|
|
4038
5187
|
const dir = getAttachmentsDir(sessionId);
|
|
4039
|
-
if (!
|
|
5188
|
+
if (!existsSync11(dir)) {
|
|
4040
5189
|
return c.json({ sessionId, attachments: [], count: 0 });
|
|
4041
5190
|
}
|
|
4042
5191
|
const files = readdirSync(dir);
|
|
4043
5192
|
const attachments = files.map((filename) => {
|
|
4044
|
-
const filePath =
|
|
5193
|
+
const filePath = join4(dir, filename);
|
|
4045
5194
|
const stats = statSync(filePath);
|
|
4046
5195
|
return {
|
|
4047
5196
|
id: filename.split("_")[0],
|
|
@@ -4073,10 +5222,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
4073
5222
|
return c.json({ error: "No file provided" }, 400);
|
|
4074
5223
|
}
|
|
4075
5224
|
const dir = ensureAttachmentsDir(sessionId);
|
|
4076
|
-
const id =
|
|
4077
|
-
const ext =
|
|
5225
|
+
const id = nanoid5(10);
|
|
5226
|
+
const ext = extname6(file.name) || "";
|
|
4078
5227
|
const safeFilename = `${id}_${basename2(file.name).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
4079
|
-
const filePath =
|
|
5228
|
+
const filePath = join4(dir, safeFilename);
|
|
4080
5229
|
const arrayBuffer = await file.arrayBuffer();
|
|
4081
5230
|
writeFileSync2(filePath, Buffer.from(arrayBuffer));
|
|
4082
5231
|
return c.json({
|
|
@@ -4099,10 +5248,10 @@ sessions2.post("/:id/attachments", async (c) => {
|
|
|
4099
5248
|
return c.json({ error: "Missing filename or data" }, 400);
|
|
4100
5249
|
}
|
|
4101
5250
|
const dir = ensureAttachmentsDir(sessionId);
|
|
4102
|
-
const id =
|
|
4103
|
-
const ext =
|
|
5251
|
+
const id = nanoid5(10);
|
|
5252
|
+
const ext = extname6(body.filename) || "";
|
|
4104
5253
|
const safeFilename = `${id}_${basename2(body.filename).replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
4105
|
-
const filePath =
|
|
5254
|
+
const filePath = join4(dir, safeFilename);
|
|
4106
5255
|
let base64Data = body.data;
|
|
4107
5256
|
if (base64Data.includes(",")) {
|
|
4108
5257
|
base64Data = base64Data.split(",")[1];
|
|
@@ -4131,7 +5280,7 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
4131
5280
|
return c.json({ error: "Session not found" }, 404);
|
|
4132
5281
|
}
|
|
4133
5282
|
const dir = getAttachmentsDir(sessionId);
|
|
4134
|
-
if (!
|
|
5283
|
+
if (!existsSync11(dir)) {
|
|
4135
5284
|
return c.json({ error: "Attachment not found" }, 404);
|
|
4136
5285
|
}
|
|
4137
5286
|
const files = readdirSync(dir);
|
|
@@ -4139,17 +5288,154 @@ sessions2.delete("/:id/attachments/:attachmentId", async (c) => {
|
|
|
4139
5288
|
if (!file) {
|
|
4140
5289
|
return c.json({ error: "Attachment not found" }, 404);
|
|
4141
5290
|
}
|
|
4142
|
-
const filePath =
|
|
5291
|
+
const filePath = join4(dir, file);
|
|
4143
5292
|
unlinkSync(filePath);
|
|
4144
5293
|
return c.json({ success: true, id: attachmentId });
|
|
4145
5294
|
});
|
|
5295
|
+
var filesQuerySchema = z11.object({
|
|
5296
|
+
query: z11.string().optional(),
|
|
5297
|
+
// Filter query (e.g., "src/com" to match "src/components")
|
|
5298
|
+
limit: z11.string().optional()
|
|
5299
|
+
// Max results (default 50)
|
|
5300
|
+
});
|
|
5301
|
+
var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([
|
|
5302
|
+
"node_modules",
|
|
5303
|
+
".git",
|
|
5304
|
+
".next",
|
|
5305
|
+
"dist",
|
|
5306
|
+
"build",
|
|
5307
|
+
".turbo",
|
|
5308
|
+
".cache",
|
|
5309
|
+
"coverage",
|
|
5310
|
+
"__pycache__",
|
|
5311
|
+
".pytest_cache",
|
|
5312
|
+
"venv",
|
|
5313
|
+
".venv",
|
|
5314
|
+
"target",
|
|
5315
|
+
// Rust
|
|
5316
|
+
".idea",
|
|
5317
|
+
".vscode"
|
|
5318
|
+
]);
|
|
5319
|
+
var IGNORED_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
5320
|
+
".pyc",
|
|
5321
|
+
".pyo",
|
|
5322
|
+
".class",
|
|
5323
|
+
".o",
|
|
5324
|
+
".obj",
|
|
5325
|
+
".exe",
|
|
5326
|
+
".dll",
|
|
5327
|
+
".so",
|
|
5328
|
+
".dylib"
|
|
5329
|
+
]);
|
|
5330
|
+
async function listWorkspaceFiles(baseDir, currentDir, query, limit, results = []) {
|
|
5331
|
+
if (results.length >= limit) {
|
|
5332
|
+
return results;
|
|
5333
|
+
}
|
|
5334
|
+
try {
|
|
5335
|
+
const entries = await readdir4(currentDir, { withFileTypes: true });
|
|
5336
|
+
const queryLower = query.toLowerCase();
|
|
5337
|
+
for (const entry of entries) {
|
|
5338
|
+
if (results.length >= limit) break;
|
|
5339
|
+
const fullPath = join4(currentDir, entry.name);
|
|
5340
|
+
const relativePath = relative7(baseDir, fullPath);
|
|
5341
|
+
if (entry.isDirectory() && IGNORED_DIRECTORIES.has(entry.name)) {
|
|
5342
|
+
continue;
|
|
5343
|
+
}
|
|
5344
|
+
if (entry.name.startsWith(".")) {
|
|
5345
|
+
continue;
|
|
5346
|
+
}
|
|
5347
|
+
const ext = extname6(entry.name).toLowerCase();
|
|
5348
|
+
if (IGNORED_EXTENSIONS.has(ext)) {
|
|
5349
|
+
continue;
|
|
5350
|
+
}
|
|
5351
|
+
const matchesQuery = !query || relativePath.toLowerCase().includes(queryLower) || entry.name.toLowerCase().includes(queryLower);
|
|
5352
|
+
if (entry.isDirectory()) {
|
|
5353
|
+
if (matchesQuery) {
|
|
5354
|
+
results.push({
|
|
5355
|
+
path: relativePath,
|
|
5356
|
+
name: entry.name,
|
|
5357
|
+
type: "folder"
|
|
5358
|
+
});
|
|
5359
|
+
}
|
|
5360
|
+
const shouldRecurse = !query || relativePath.toLowerCase().startsWith(queryLower) || queryLower.startsWith(relativePath.toLowerCase());
|
|
5361
|
+
if (shouldRecurse && results.length < limit) {
|
|
5362
|
+
await listWorkspaceFiles(baseDir, fullPath, query, limit, results);
|
|
5363
|
+
}
|
|
5364
|
+
} else if (entry.isFile()) {
|
|
5365
|
+
if (matchesQuery) {
|
|
5366
|
+
results.push({
|
|
5367
|
+
path: relativePath,
|
|
5368
|
+
name: entry.name,
|
|
5369
|
+
type: "file",
|
|
5370
|
+
extension: ext || void 0
|
|
5371
|
+
});
|
|
5372
|
+
}
|
|
5373
|
+
}
|
|
5374
|
+
}
|
|
5375
|
+
} catch {
|
|
5376
|
+
}
|
|
5377
|
+
return results;
|
|
5378
|
+
}
|
|
5379
|
+
sessions2.get(
|
|
5380
|
+
"/:id/files",
|
|
5381
|
+
zValidator("query", filesQuerySchema),
|
|
5382
|
+
async (c) => {
|
|
5383
|
+
const sessionId = c.req.param("id");
|
|
5384
|
+
const { query = "", limit: limitStr = "50" } = c.req.valid("query");
|
|
5385
|
+
const limit = Math.min(parseInt(limitStr) || 50, 100);
|
|
5386
|
+
const session = sessionQueries.getById(sessionId);
|
|
5387
|
+
if (!session) {
|
|
5388
|
+
return c.json({ error: "Session not found" }, 404);
|
|
5389
|
+
}
|
|
5390
|
+
const workingDirectory = session.workingDirectory;
|
|
5391
|
+
if (!existsSync11(workingDirectory)) {
|
|
5392
|
+
return c.json({
|
|
5393
|
+
sessionId,
|
|
5394
|
+
workingDirectory,
|
|
5395
|
+
files: [],
|
|
5396
|
+
count: 0,
|
|
5397
|
+
error: "Working directory does not exist"
|
|
5398
|
+
});
|
|
5399
|
+
}
|
|
5400
|
+
try {
|
|
5401
|
+
const files = await listWorkspaceFiles(
|
|
5402
|
+
workingDirectory,
|
|
5403
|
+
workingDirectory,
|
|
5404
|
+
query,
|
|
5405
|
+
limit
|
|
5406
|
+
);
|
|
5407
|
+
files.sort((a, b) => {
|
|
5408
|
+
if (a.type !== b.type) {
|
|
5409
|
+
return a.type === "folder" ? -1 : 1;
|
|
5410
|
+
}
|
|
5411
|
+
return a.path.localeCompare(b.path);
|
|
5412
|
+
});
|
|
5413
|
+
return c.json({
|
|
5414
|
+
sessionId,
|
|
5415
|
+
workingDirectory,
|
|
5416
|
+
files,
|
|
5417
|
+
count: files.length,
|
|
5418
|
+
query
|
|
5419
|
+
});
|
|
5420
|
+
} catch (err) {
|
|
5421
|
+
console.error("Failed to list workspace files:", err);
|
|
5422
|
+
return c.json({
|
|
5423
|
+
error: "Failed to list files",
|
|
5424
|
+
sessionId,
|
|
5425
|
+
workingDirectory,
|
|
5426
|
+
files: [],
|
|
5427
|
+
count: 0
|
|
5428
|
+
}, 500);
|
|
5429
|
+
}
|
|
5430
|
+
}
|
|
5431
|
+
);
|
|
4146
5432
|
|
|
4147
5433
|
// src/server/routes/agents.ts
|
|
4148
5434
|
import { Hono as Hono2 } from "hono";
|
|
4149
5435
|
import { zValidator as zValidator2 } from "@hono/zod-validator";
|
|
4150
|
-
import { z as
|
|
4151
|
-
import { existsSync as
|
|
4152
|
-
import { join as
|
|
5436
|
+
import { z as z12 } from "zod";
|
|
5437
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
5438
|
+
import { join as join5 } from "path";
|
|
4153
5439
|
|
|
4154
5440
|
// src/server/resumable-stream.ts
|
|
4155
5441
|
import { createResumableStreamContext } from "resumable-stream/generic";
|
|
@@ -4224,41 +5510,107 @@ var streamContext = createResumableStreamContext({
|
|
|
4224
5510
|
});
|
|
4225
5511
|
|
|
4226
5512
|
// src/server/routes/agents.ts
|
|
4227
|
-
import { nanoid as
|
|
5513
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
5514
|
+
var MAX_TOOL_INPUT_LENGTH = 8 * 1024;
|
|
5515
|
+
var MAX_TOOL_INPUT_PREVIEW = 2 * 1024;
|
|
5516
|
+
var MAX_TOOL_ARGS_CHUNK = 2 * 1024;
|
|
5517
|
+
function sanitizeToolInput(toolName, input) {
|
|
5518
|
+
if (toolName !== "write_file" || !input || typeof input !== "object") {
|
|
5519
|
+
return input;
|
|
5520
|
+
}
|
|
5521
|
+
const data = input;
|
|
5522
|
+
let changed = false;
|
|
5523
|
+
const next = { ...data };
|
|
5524
|
+
const content = typeof data.content === "string" ? data.content : void 0;
|
|
5525
|
+
if (content && content.length > MAX_TOOL_INPUT_LENGTH) {
|
|
5526
|
+
next.content = `${content.slice(0, MAX_TOOL_INPUT_PREVIEW)}
|
|
5527
|
+
... (truncated)`;
|
|
5528
|
+
next.contentLength = content.length;
|
|
5529
|
+
next.contentTruncated = true;
|
|
5530
|
+
changed = true;
|
|
5531
|
+
}
|
|
5532
|
+
const oldString = typeof data.old_string === "string" ? data.old_string : void 0;
|
|
5533
|
+
if (oldString && oldString.length > MAX_TOOL_INPUT_LENGTH) {
|
|
5534
|
+
next.old_string = `${oldString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
|
|
5535
|
+
... (truncated)`;
|
|
5536
|
+
next.oldStringLength = oldString.length;
|
|
5537
|
+
next.oldStringTruncated = true;
|
|
5538
|
+
changed = true;
|
|
5539
|
+
}
|
|
5540
|
+
const newString = typeof data.new_string === "string" ? data.new_string : void 0;
|
|
5541
|
+
if (newString && newString.length > MAX_TOOL_INPUT_LENGTH) {
|
|
5542
|
+
next.new_string = `${newString.slice(0, MAX_TOOL_INPUT_PREVIEW)}
|
|
5543
|
+
... (truncated)`;
|
|
5544
|
+
next.newStringLength = newString.length;
|
|
5545
|
+
next.newStringTruncated = true;
|
|
5546
|
+
changed = true;
|
|
5547
|
+
}
|
|
5548
|
+
if (changed) {
|
|
5549
|
+
console.log("[TOOL-INPUT] Truncated write_file input for streaming payload size");
|
|
5550
|
+
}
|
|
5551
|
+
return changed ? next : input;
|
|
5552
|
+
}
|
|
5553
|
+
function buildToolArgsText(input) {
|
|
5554
|
+
try {
|
|
5555
|
+
return JSON.stringify(input ?? {});
|
|
5556
|
+
} catch {
|
|
5557
|
+
return "{}";
|
|
5558
|
+
}
|
|
5559
|
+
}
|
|
5560
|
+
async function emitSyntheticToolStreaming(writeSSE, toolCallStarts, toolCallId, toolName, input) {
|
|
5561
|
+
if (toolCallStarts.has(toolCallId)) return;
|
|
5562
|
+
toolCallStarts.add(toolCallId);
|
|
5563
|
+
await writeSSE(JSON.stringify({
|
|
5564
|
+
type: "tool-input-start",
|
|
5565
|
+
toolCallId,
|
|
5566
|
+
toolName
|
|
5567
|
+
}));
|
|
5568
|
+
if (toolName !== "write_file") return;
|
|
5569
|
+
const argsText = buildToolArgsText(input);
|
|
5570
|
+
for (let i = 0; i < argsText.length; i += MAX_TOOL_ARGS_CHUNK) {
|
|
5571
|
+
const chunk = argsText.slice(i, i + MAX_TOOL_ARGS_CHUNK);
|
|
5572
|
+
await writeSSE(JSON.stringify({
|
|
5573
|
+
type: "tool-input-delta",
|
|
5574
|
+
toolCallId,
|
|
5575
|
+
argsTextDelta: chunk
|
|
5576
|
+
}));
|
|
5577
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
5578
|
+
}
|
|
5579
|
+
}
|
|
4228
5580
|
var agents = new Hono2();
|
|
4229
|
-
var attachmentSchema =
|
|
4230
|
-
type:
|
|
4231
|
-
data:
|
|
5581
|
+
var attachmentSchema = z12.object({
|
|
5582
|
+
type: z12.enum(["image", "file"]),
|
|
5583
|
+
data: z12.string(),
|
|
4232
5584
|
// base64 data URL or raw base64
|
|
4233
|
-
mediaType:
|
|
4234
|
-
filename:
|
|
5585
|
+
mediaType: z12.string().optional(),
|
|
5586
|
+
filename: z12.string().optional()
|
|
4235
5587
|
});
|
|
4236
|
-
var runPromptSchema =
|
|
4237
|
-
prompt:
|
|
5588
|
+
var runPromptSchema = z12.object({
|
|
5589
|
+
prompt: z12.string(),
|
|
4238
5590
|
// Can be empty if attachments are provided
|
|
4239
|
-
attachments:
|
|
5591
|
+
attachments: z12.array(attachmentSchema).optional()
|
|
4240
5592
|
}).refine(
|
|
4241
5593
|
(data) => data.prompt.trim().length > 0 || data.attachments && data.attachments.length > 0,
|
|
4242
5594
|
{ message: "Either prompt or attachments must be provided" }
|
|
4243
5595
|
);
|
|
4244
|
-
var quickStartSchema =
|
|
4245
|
-
prompt:
|
|
4246
|
-
name:
|
|
4247
|
-
workingDirectory:
|
|
4248
|
-
model:
|
|
4249
|
-
toolApprovals:
|
|
5596
|
+
var quickStartSchema = z12.object({
|
|
5597
|
+
prompt: z12.string().min(1),
|
|
5598
|
+
name: z12.string().optional(),
|
|
5599
|
+
workingDirectory: z12.string().optional(),
|
|
5600
|
+
model: z12.string().optional(),
|
|
5601
|
+
toolApprovals: z12.record(z12.string(), z12.boolean()).optional()
|
|
4250
5602
|
});
|
|
4251
|
-
var rejectSchema =
|
|
4252
|
-
reason:
|
|
5603
|
+
var rejectSchema = z12.object({
|
|
5604
|
+
reason: z12.string().optional()
|
|
4253
5605
|
}).optional();
|
|
4254
5606
|
var streamAbortControllers = /* @__PURE__ */ new Map();
|
|
4255
5607
|
function getAttachmentsDirectory(sessionId) {
|
|
4256
5608
|
const appDataDir = getAppDataDirectory();
|
|
4257
|
-
return
|
|
5609
|
+
return join5(appDataDir, "attachments", sessionId);
|
|
4258
5610
|
}
|
|
4259
5611
|
function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
4260
5612
|
const attachmentsDir = getAttachmentsDirectory(sessionId);
|
|
4261
|
-
if (!
|
|
5613
|
+
if (!existsSync12(attachmentsDir)) {
|
|
4262
5614
|
mkdirSync4(attachmentsDir, { recursive: true });
|
|
4263
5615
|
}
|
|
4264
5616
|
let filename = attachment.filename;
|
|
@@ -4270,7 +5622,7 @@ function saveAttachmentToDisk(sessionId, attachment, index) {
|
|
|
4270
5622
|
if (base64Data.includes(",")) {
|
|
4271
5623
|
base64Data = base64Data.split(",")[1];
|
|
4272
5624
|
}
|
|
4273
|
-
const filePath =
|
|
5625
|
+
const filePath = join5(attachmentsDir, filename);
|
|
4274
5626
|
const buffer = Buffer.from(base64Data, "base64");
|
|
4275
5627
|
writeFileSync3(filePath, buffer);
|
|
4276
5628
|
return filePath;
|
|
@@ -4303,6 +5655,7 @@ function createAgentStreamProducer(sessionId, prompt, streamId, attachments) {
|
|
|
4303
5655
|
const { readable, writable } = new TransformStream();
|
|
4304
5656
|
const writer = writable.getWriter();
|
|
4305
5657
|
let writerClosed = false;
|
|
5658
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
4306
5659
|
const abortController = new AbortController();
|
|
4307
5660
|
streamAbortControllers.set(streamId, abortController);
|
|
4308
5661
|
const writeSSE = async (data) => {
|
|
@@ -4411,11 +5764,32 @@ ${prompt}` });
|
|
|
4411
5764
|
}));
|
|
4412
5765
|
},
|
|
4413
5766
|
onToolProgress: async (progress) => {
|
|
5767
|
+
const status = progress.data?.status || "no-status";
|
|
5768
|
+
const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
|
|
5769
|
+
const chunkIndex = progress.data?.chunkIndex;
|
|
5770
|
+
const chunkCount = progress.data?.chunkCount;
|
|
5771
|
+
console.log(
|
|
5772
|
+
"[TOOL-PROGRESS] Sending:",
|
|
5773
|
+
progress.toolName,
|
|
5774
|
+
status,
|
|
5775
|
+
contentLength !== void 0 ? `contentLength=${contentLength}` : "",
|
|
5776
|
+
chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
|
|
5777
|
+
);
|
|
4414
5778
|
await writeSSE(JSON.stringify({
|
|
4415
5779
|
type: "tool-progress",
|
|
4416
5780
|
toolName: progress.toolName,
|
|
4417
5781
|
data: progress.data
|
|
4418
5782
|
}));
|
|
5783
|
+
if (progress.toolName === "write_file" && status === "content") {
|
|
5784
|
+
await writeSSE(JSON.stringify({
|
|
5785
|
+
type: "debug",
|
|
5786
|
+
label: "write-file-progress",
|
|
5787
|
+
contentLength,
|
|
5788
|
+
chunkIndex,
|
|
5789
|
+
chunkCount
|
|
5790
|
+
}));
|
|
5791
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
5792
|
+
}
|
|
4419
5793
|
},
|
|
4420
5794
|
onStepFinish: async () => {
|
|
4421
5795
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -4457,6 +5831,7 @@ ${prompt}` });
|
|
|
4457
5831
|
toolCallId: p.toolCallId,
|
|
4458
5832
|
toolName: p.toolName
|
|
4459
5833
|
}));
|
|
5834
|
+
toolCallStarts.add(p.toolCallId);
|
|
4460
5835
|
} else if (part.type === "tool-call-delta") {
|
|
4461
5836
|
const p = part;
|
|
4462
5837
|
await writeSSE(JSON.stringify({
|
|
@@ -4465,11 +5840,23 @@ ${prompt}` });
|
|
|
4465
5840
|
argsTextDelta: p.argsTextDelta
|
|
4466
5841
|
}));
|
|
4467
5842
|
} else if (part.type === "tool-call") {
|
|
5843
|
+
await emitSyntheticToolStreaming(
|
|
5844
|
+
writeSSE,
|
|
5845
|
+
toolCallStarts,
|
|
5846
|
+
part.toolCallId,
|
|
5847
|
+
part.toolName,
|
|
5848
|
+
part.input
|
|
5849
|
+
);
|
|
4468
5850
|
await writeSSE(JSON.stringify({
|
|
4469
5851
|
type: "tool-input-available",
|
|
4470
5852
|
toolCallId: part.toolCallId,
|
|
4471
5853
|
toolName: part.toolName,
|
|
4472
|
-
input: part.input
|
|
5854
|
+
input: sanitizeToolInput(part.toolName, part.input)
|
|
5855
|
+
}));
|
|
5856
|
+
await writeSSE(JSON.stringify({
|
|
5857
|
+
type: "debug",
|
|
5858
|
+
label: "tool-input-available",
|
|
5859
|
+
toolName: part.toolName
|
|
4473
5860
|
}));
|
|
4474
5861
|
} else if (part.type === "tool-result") {
|
|
4475
5862
|
await writeSSE(JSON.stringify({
|
|
@@ -4588,7 +5975,7 @@ ${prompt}` });
|
|
|
4588
5975
|
userMessageContent = prompt;
|
|
4589
5976
|
}
|
|
4590
5977
|
messageQueries.create(id, { role: "user", content: userMessageContent });
|
|
4591
|
-
const streamId = `stream_${id}_${
|
|
5978
|
+
const streamId = `stream_${id}_${nanoid6(10)}`;
|
|
4592
5979
|
activeStreamQueries.create(id, streamId);
|
|
4593
5980
|
const stream = await streamContext.resumableStream(
|
|
4594
5981
|
streamId,
|
|
@@ -4785,13 +6172,14 @@ agents.post(
|
|
|
4785
6172
|
sessionConfig: body.toolApprovals ? { toolApprovals: body.toolApprovals } : void 0
|
|
4786
6173
|
});
|
|
4787
6174
|
const session = agent.getSession();
|
|
4788
|
-
const streamId = `stream_${session.id}_${
|
|
6175
|
+
const streamId = `stream_${session.id}_${nanoid6(10)}`;
|
|
4789
6176
|
await createCheckpoint(session.id, session.workingDirectory, 0);
|
|
4790
6177
|
activeStreamQueries.create(session.id, streamId);
|
|
4791
6178
|
const createQuickStreamProducer = () => {
|
|
4792
6179
|
const { readable, writable } = new TransformStream();
|
|
4793
6180
|
const writer = writable.getWriter();
|
|
4794
6181
|
let writerClosed = false;
|
|
6182
|
+
const toolCallStarts = /* @__PURE__ */ new Set();
|
|
4795
6183
|
const abortController = new AbortController();
|
|
4796
6184
|
streamAbortControllers.set(streamId, abortController);
|
|
4797
6185
|
const writeSSE = async (data) => {
|
|
@@ -4837,11 +6225,32 @@ agents.post(
|
|
|
4837
6225
|
abortSignal: abortController.signal,
|
|
4838
6226
|
// Use our managed abort controller, NOT client signal
|
|
4839
6227
|
onToolProgress: async (progress) => {
|
|
6228
|
+
const status = progress.data?.status || "no-status";
|
|
6229
|
+
const contentLength = typeof progress.data?.content === "string" ? progress.data.content.length : void 0;
|
|
6230
|
+
const chunkIndex = progress.data?.chunkIndex;
|
|
6231
|
+
const chunkCount = progress.data?.chunkCount;
|
|
6232
|
+
console.log(
|
|
6233
|
+
"[TOOL-PROGRESS] Sending:",
|
|
6234
|
+
progress.toolName,
|
|
6235
|
+
status,
|
|
6236
|
+
contentLength !== void 0 ? `contentLength=${contentLength}` : "",
|
|
6237
|
+
chunkIndex !== void 0 || chunkCount !== void 0 ? `chunk=${chunkIndex}/${chunkCount}` : ""
|
|
6238
|
+
);
|
|
4840
6239
|
await writeSSE(JSON.stringify({
|
|
4841
6240
|
type: "tool-progress",
|
|
4842
6241
|
toolName: progress.toolName,
|
|
4843
6242
|
data: progress.data
|
|
4844
6243
|
}));
|
|
6244
|
+
if (progress.toolName === "write_file" && status === "content") {
|
|
6245
|
+
await writeSSE(JSON.stringify({
|
|
6246
|
+
type: "debug",
|
|
6247
|
+
label: "write-file-progress",
|
|
6248
|
+
contentLength,
|
|
6249
|
+
chunkIndex,
|
|
6250
|
+
chunkCount
|
|
6251
|
+
}));
|
|
6252
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
6253
|
+
}
|
|
4845
6254
|
},
|
|
4846
6255
|
onStepFinish: async () => {
|
|
4847
6256
|
await writeSSE(JSON.stringify({ type: "finish-step" }));
|
|
@@ -4883,6 +6292,7 @@ agents.post(
|
|
|
4883
6292
|
toolCallId: p.toolCallId,
|
|
4884
6293
|
toolName: p.toolName
|
|
4885
6294
|
}));
|
|
6295
|
+
toolCallStarts.add(p.toolCallId);
|
|
4886
6296
|
} else if (part.type === "tool-call-delta") {
|
|
4887
6297
|
const p = part;
|
|
4888
6298
|
await writeSSE(JSON.stringify({
|
|
@@ -4891,11 +6301,23 @@ agents.post(
|
|
|
4891
6301
|
argsTextDelta: p.argsTextDelta
|
|
4892
6302
|
}));
|
|
4893
6303
|
} else if (part.type === "tool-call") {
|
|
6304
|
+
await emitSyntheticToolStreaming(
|
|
6305
|
+
writeSSE,
|
|
6306
|
+
toolCallStarts,
|
|
6307
|
+
part.toolCallId,
|
|
6308
|
+
part.toolName,
|
|
6309
|
+
part.input
|
|
6310
|
+
);
|
|
4894
6311
|
await writeSSE(JSON.stringify({
|
|
4895
6312
|
type: "tool-input-available",
|
|
4896
6313
|
toolCallId: part.toolCallId,
|
|
4897
6314
|
toolName: part.toolName,
|
|
4898
|
-
input: part.input
|
|
6315
|
+
input: sanitizeToolInput(part.toolName, part.input)
|
|
6316
|
+
}));
|
|
6317
|
+
await writeSSE(JSON.stringify({
|
|
6318
|
+
type: "debug",
|
|
6319
|
+
label: "tool-input-available",
|
|
6320
|
+
toolName: part.toolName
|
|
4899
6321
|
}));
|
|
4900
6322
|
} else if (part.type === "tool-result") {
|
|
4901
6323
|
await writeSSE(JSON.stringify({
|
|
@@ -4963,7 +6385,28 @@ agents.post(
|
|
|
4963
6385
|
// src/server/routes/health.ts
|
|
4964
6386
|
import { Hono as Hono3 } from "hono";
|
|
4965
6387
|
import { zValidator as zValidator3 } from "@hono/zod-validator";
|
|
4966
|
-
import { z as
|
|
6388
|
+
import { z as z13 } from "zod";
|
|
6389
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6390
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6391
|
+
import { dirname as dirname6, join as join6 } from "path";
|
|
6392
|
+
var __filename = fileURLToPath2(import.meta.url);
|
|
6393
|
+
var __dirname = dirname6(__filename);
|
|
6394
|
+
var packageJsonPath = join6(__dirname, "../../../package.json");
|
|
6395
|
+
var currentVersion = "0.0.0";
|
|
6396
|
+
var packageName = "sparkecoder";
|
|
6397
|
+
try {
|
|
6398
|
+
const packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
6399
|
+
currentVersion = packageJson.version || "0.0.0";
|
|
6400
|
+
packageName = packageJson.name || "sparkecoder";
|
|
6401
|
+
} catch {
|
|
6402
|
+
try {
|
|
6403
|
+
const devPackageJsonPath = join6(__dirname, "../../package.json");
|
|
6404
|
+
const packageJson = JSON.parse(readFileSync3(devPackageJsonPath, "utf-8"));
|
|
6405
|
+
currentVersion = packageJson.version || "0.0.0";
|
|
6406
|
+
packageName = packageJson.name || "sparkecoder";
|
|
6407
|
+
} catch {
|
|
6408
|
+
}
|
|
6409
|
+
}
|
|
4967
6410
|
var health = new Hono3();
|
|
4968
6411
|
health.get("/", async (c) => {
|
|
4969
6412
|
const config = getConfig();
|
|
@@ -4972,7 +6415,7 @@ health.get("/", async (c) => {
|
|
|
4972
6415
|
const hasApiKey = gatewayKey?.configured ?? false;
|
|
4973
6416
|
return c.json({
|
|
4974
6417
|
status: "ok",
|
|
4975
|
-
version:
|
|
6418
|
+
version: currentVersion,
|
|
4976
6419
|
uptime: process.uptime(),
|
|
4977
6420
|
apiKeyConfigured: hasApiKey,
|
|
4978
6421
|
config: {
|
|
@@ -4984,6 +6427,42 @@ health.get("/", async (c) => {
|
|
|
4984
6427
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
4985
6428
|
});
|
|
4986
6429
|
});
|
|
6430
|
+
health.get("/version", async (c) => {
|
|
6431
|
+
let latestVersion = currentVersion;
|
|
6432
|
+
let updateAvailable = false;
|
|
6433
|
+
let error;
|
|
6434
|
+
try {
|
|
6435
|
+
const npmResponse = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
6436
|
+
headers: { "Accept": "application/json" },
|
|
6437
|
+
signal: AbortSignal.timeout(5e3)
|
|
6438
|
+
// 5 second timeout
|
|
6439
|
+
});
|
|
6440
|
+
if (npmResponse.ok) {
|
|
6441
|
+
const npmData = await npmResponse.json();
|
|
6442
|
+
latestVersion = npmData.version || currentVersion;
|
|
6443
|
+
const parseVersion = (v) => {
|
|
6444
|
+
const parts = v.replace(/^v/, "").split(".").map(Number);
|
|
6445
|
+
return { major: parts[0] || 0, minor: parts[1] || 0, patch: parts[2] || 0 };
|
|
6446
|
+
};
|
|
6447
|
+
const current = parseVersion(currentVersion);
|
|
6448
|
+
const latest = parseVersion(latestVersion);
|
|
6449
|
+
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;
|
|
6450
|
+
} else {
|
|
6451
|
+
error = `npm registry returned ${npmResponse.status}`;
|
|
6452
|
+
}
|
|
6453
|
+
} catch (err) {
|
|
6454
|
+
error = err instanceof Error ? err.message : "Failed to check for updates";
|
|
6455
|
+
}
|
|
6456
|
+
return c.json({
|
|
6457
|
+
packageName,
|
|
6458
|
+
currentVersion,
|
|
6459
|
+
latestVersion,
|
|
6460
|
+
updateAvailable,
|
|
6461
|
+
updateCommand: updateAvailable ? `npm install -g ${packageName}@latest` : null,
|
|
6462
|
+
error,
|
|
6463
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
6464
|
+
});
|
|
6465
|
+
});
|
|
4987
6466
|
health.get("/ready", async (c) => {
|
|
4988
6467
|
try {
|
|
4989
6468
|
getConfig();
|
|
@@ -5009,9 +6488,9 @@ health.get("/api-keys", async (c) => {
|
|
|
5009
6488
|
supportedProviders: SUPPORTED_PROVIDERS
|
|
5010
6489
|
});
|
|
5011
6490
|
});
|
|
5012
|
-
var setApiKeySchema =
|
|
5013
|
-
provider:
|
|
5014
|
-
apiKey:
|
|
6491
|
+
var setApiKeySchema = z13.object({
|
|
6492
|
+
provider: z13.string(),
|
|
6493
|
+
apiKey: z13.string().min(1)
|
|
5015
6494
|
});
|
|
5016
6495
|
health.post(
|
|
5017
6496
|
"/api-keys",
|
|
@@ -5050,12 +6529,12 @@ health.delete("/api-keys/:provider", async (c) => {
|
|
|
5050
6529
|
// src/server/routes/terminals.ts
|
|
5051
6530
|
import { Hono as Hono4 } from "hono";
|
|
5052
6531
|
import { zValidator as zValidator4 } from "@hono/zod-validator";
|
|
5053
|
-
import { z as
|
|
6532
|
+
import { z as z14 } from "zod";
|
|
5054
6533
|
var terminals2 = new Hono4();
|
|
5055
|
-
var spawnSchema =
|
|
5056
|
-
command:
|
|
5057
|
-
cwd:
|
|
5058
|
-
name:
|
|
6534
|
+
var spawnSchema = z14.object({
|
|
6535
|
+
command: z14.string(),
|
|
6536
|
+
cwd: z14.string().optional(),
|
|
6537
|
+
name: z14.string().optional()
|
|
5059
6538
|
});
|
|
5060
6539
|
terminals2.post(
|
|
5061
6540
|
"/:sessionId/terminals",
|
|
@@ -5136,8 +6615,8 @@ terminals2.get("/:sessionId/terminals/:terminalId", async (c) => {
|
|
|
5136
6615
|
// We don't track exit codes in tmux mode
|
|
5137
6616
|
});
|
|
5138
6617
|
});
|
|
5139
|
-
var logsQuerySchema =
|
|
5140
|
-
tail:
|
|
6618
|
+
var logsQuerySchema = z14.object({
|
|
6619
|
+
tail: z14.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
5141
6620
|
});
|
|
5142
6621
|
terminals2.get(
|
|
5143
6622
|
"/:sessionId/terminals/:terminalId/logs",
|
|
@@ -5161,8 +6640,8 @@ terminals2.get(
|
|
|
5161
6640
|
});
|
|
5162
6641
|
}
|
|
5163
6642
|
);
|
|
5164
|
-
var killSchema =
|
|
5165
|
-
signal:
|
|
6643
|
+
var killSchema = z14.object({
|
|
6644
|
+
signal: z14.enum(["SIGTERM", "SIGKILL"]).optional()
|
|
5166
6645
|
});
|
|
5167
6646
|
terminals2.post(
|
|
5168
6647
|
"/:sessionId/terminals/:terminalId/kill",
|
|
@@ -5176,8 +6655,8 @@ terminals2.post(
|
|
|
5176
6655
|
return c.json({ success: true, message: "Terminal killed" });
|
|
5177
6656
|
}
|
|
5178
6657
|
);
|
|
5179
|
-
var writeSchema =
|
|
5180
|
-
input:
|
|
6658
|
+
var writeSchema = z14.object({
|
|
6659
|
+
input: z14.string()
|
|
5181
6660
|
});
|
|
5182
6661
|
terminals2.post(
|
|
5183
6662
|
"/:sessionId/terminals/:terminalId/write",
|
|
@@ -5359,10 +6838,10 @@ data: ${JSON.stringify({ status: "stopped" })}
|
|
|
5359
6838
|
});
|
|
5360
6839
|
|
|
5361
6840
|
// src/utils/dependencies.ts
|
|
5362
|
-
import { exec as
|
|
5363
|
-
import { promisify as
|
|
6841
|
+
import { exec as exec5 } from "child_process";
|
|
6842
|
+
import { promisify as promisify5 } from "util";
|
|
5364
6843
|
import { platform as platform2 } from "os";
|
|
5365
|
-
var
|
|
6844
|
+
var execAsync5 = promisify5(exec5);
|
|
5366
6845
|
function getInstallInstructions() {
|
|
5367
6846
|
const os2 = platform2();
|
|
5368
6847
|
if (os2 === "darwin") {
|
|
@@ -5395,7 +6874,7 @@ Install tmux:
|
|
|
5395
6874
|
}
|
|
5396
6875
|
async function checkTmux() {
|
|
5397
6876
|
try {
|
|
5398
|
-
const { stdout } = await
|
|
6877
|
+
const { stdout } = await execAsync5("tmux -V", { timeout: 5e3 });
|
|
5399
6878
|
const version = stdout.trim();
|
|
5400
6879
|
return {
|
|
5401
6880
|
available: true,
|
|
@@ -5439,21 +6918,21 @@ async function tryAutoInstallTmux() {
|
|
|
5439
6918
|
try {
|
|
5440
6919
|
if (os2 === "darwin") {
|
|
5441
6920
|
try {
|
|
5442
|
-
await
|
|
6921
|
+
await execAsync5("which brew", { timeout: 5e3 });
|
|
5443
6922
|
} catch {
|
|
5444
6923
|
return false;
|
|
5445
6924
|
}
|
|
5446
6925
|
console.log("\u{1F4E6} Installing tmux via Homebrew...");
|
|
5447
|
-
await
|
|
6926
|
+
await execAsync5("brew install tmux", { timeout: 3e5 });
|
|
5448
6927
|
console.log("\u2705 tmux installed successfully");
|
|
5449
6928
|
return true;
|
|
5450
6929
|
}
|
|
5451
6930
|
if (os2 === "linux") {
|
|
5452
6931
|
try {
|
|
5453
|
-
await
|
|
6932
|
+
await execAsync5("which apt-get", { timeout: 5e3 });
|
|
5454
6933
|
console.log("\u{1F4E6} Installing tmux via apt-get...");
|
|
5455
6934
|
console.log(" (This may require sudo password)");
|
|
5456
|
-
await
|
|
6935
|
+
await execAsync5("sudo apt-get update && sudo apt-get install -y tmux", {
|
|
5457
6936
|
timeout: 3e5
|
|
5458
6937
|
});
|
|
5459
6938
|
console.log("\u2705 tmux installed successfully");
|
|
@@ -5461,9 +6940,9 @@ async function tryAutoInstallTmux() {
|
|
|
5461
6940
|
} catch {
|
|
5462
6941
|
}
|
|
5463
6942
|
try {
|
|
5464
|
-
await
|
|
6943
|
+
await execAsync5("which dnf", { timeout: 5e3 });
|
|
5465
6944
|
console.log("\u{1F4E6} Installing tmux via dnf...");
|
|
5466
|
-
await
|
|
6945
|
+
await execAsync5("sudo dnf install -y tmux", { timeout: 3e5 });
|
|
5467
6946
|
console.log("\u2705 tmux installed successfully");
|
|
5468
6947
|
return true;
|
|
5469
6948
|
} catch {
|
|
@@ -5497,13 +6976,13 @@ var DEFAULT_WEB_PORT = 6969;
|
|
|
5497
6976
|
var WEB_PORT_SEQUENCE = [6969, 6970, 6971, 6972, 6973, 6974, 6975, 6976, 6977, 6978];
|
|
5498
6977
|
function getWebDirectory() {
|
|
5499
6978
|
try {
|
|
5500
|
-
const currentDir =
|
|
5501
|
-
const webDir =
|
|
5502
|
-
if (
|
|
6979
|
+
const currentDir = dirname7(fileURLToPath3(import.meta.url));
|
|
6980
|
+
const webDir = resolve9(currentDir, "..", "web");
|
|
6981
|
+
if (existsSync13(webDir) && existsSync13(join7(webDir, "package.json"))) {
|
|
5503
6982
|
return webDir;
|
|
5504
6983
|
}
|
|
5505
|
-
const altWebDir =
|
|
5506
|
-
if (
|
|
6984
|
+
const altWebDir = resolve9(currentDir, "..", "..", "web");
|
|
6985
|
+
if (existsSync13(altWebDir) && existsSync13(join7(altWebDir, "package.json"))) {
|
|
5507
6986
|
return altWebDir;
|
|
5508
6987
|
}
|
|
5509
6988
|
return null;
|
|
@@ -5526,18 +7005,18 @@ async function isSparkcoderWebRunning(port) {
|
|
|
5526
7005
|
}
|
|
5527
7006
|
}
|
|
5528
7007
|
function isPortInUse(port) {
|
|
5529
|
-
return new Promise((
|
|
7008
|
+
return new Promise((resolve11) => {
|
|
5530
7009
|
const server = createNetServer();
|
|
5531
7010
|
server.once("error", (err) => {
|
|
5532
7011
|
if (err.code === "EADDRINUSE") {
|
|
5533
|
-
|
|
7012
|
+
resolve11(true);
|
|
5534
7013
|
} else {
|
|
5535
|
-
|
|
7014
|
+
resolve11(false);
|
|
5536
7015
|
}
|
|
5537
7016
|
});
|
|
5538
7017
|
server.once("listening", () => {
|
|
5539
7018
|
server.close();
|
|
5540
|
-
|
|
7019
|
+
resolve11(false);
|
|
5541
7020
|
});
|
|
5542
7021
|
server.listen(port, "0.0.0.0");
|
|
5543
7022
|
});
|
|
@@ -5561,30 +7040,30 @@ async function findWebPort(preferredPort) {
|
|
|
5561
7040
|
return { port: preferredPort, alreadyRunning: false };
|
|
5562
7041
|
}
|
|
5563
7042
|
function hasProductionBuild(webDir) {
|
|
5564
|
-
const buildIdPath =
|
|
5565
|
-
return
|
|
7043
|
+
const buildIdPath = join7(webDir, ".next", "BUILD_ID");
|
|
7044
|
+
return existsSync13(buildIdPath);
|
|
5566
7045
|
}
|
|
5567
7046
|
function hasSourceFiles(webDir) {
|
|
5568
|
-
const appDir =
|
|
5569
|
-
const pagesDir =
|
|
5570
|
-
const rootAppDir =
|
|
5571
|
-
const rootPagesDir =
|
|
5572
|
-
return
|
|
7047
|
+
const appDir = join7(webDir, "src", "app");
|
|
7048
|
+
const pagesDir = join7(webDir, "src", "pages");
|
|
7049
|
+
const rootAppDir = join7(webDir, "app");
|
|
7050
|
+
const rootPagesDir = join7(webDir, "pages");
|
|
7051
|
+
return existsSync13(appDir) || existsSync13(pagesDir) || existsSync13(rootAppDir) || existsSync13(rootPagesDir);
|
|
5573
7052
|
}
|
|
5574
7053
|
function getStandaloneServerPath(webDir) {
|
|
5575
7054
|
const possiblePaths = [
|
|
5576
|
-
|
|
5577
|
-
|
|
7055
|
+
join7(webDir, ".next", "standalone", "server.js"),
|
|
7056
|
+
join7(webDir, ".next", "standalone", "web", "server.js")
|
|
5578
7057
|
];
|
|
5579
7058
|
for (const serverPath of possiblePaths) {
|
|
5580
|
-
if (
|
|
7059
|
+
if (existsSync13(serverPath)) {
|
|
5581
7060
|
return serverPath;
|
|
5582
7061
|
}
|
|
5583
7062
|
}
|
|
5584
7063
|
return null;
|
|
5585
7064
|
}
|
|
5586
7065
|
function runCommand(command, args, cwd, env) {
|
|
5587
|
-
return new Promise((
|
|
7066
|
+
return new Promise((resolve11) => {
|
|
5588
7067
|
const child = spawn2(command, args, {
|
|
5589
7068
|
cwd,
|
|
5590
7069
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -5599,10 +7078,10 @@ function runCommand(command, args, cwd, env) {
|
|
|
5599
7078
|
output += data.toString();
|
|
5600
7079
|
});
|
|
5601
7080
|
child.on("close", (code) => {
|
|
5602
|
-
|
|
7081
|
+
resolve11({ success: code === 0, output });
|
|
5603
7082
|
});
|
|
5604
7083
|
child.on("error", (err) => {
|
|
5605
|
-
|
|
7084
|
+
resolve11({ success: false, output: err.message });
|
|
5606
7085
|
});
|
|
5607
7086
|
});
|
|
5608
7087
|
}
|
|
@@ -5617,13 +7096,13 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5617
7096
|
if (!quiet) console.log(` \u2713 Web UI already running at http://localhost:${actualPort}`);
|
|
5618
7097
|
return { process: null, port: actualPort };
|
|
5619
7098
|
}
|
|
5620
|
-
const usePnpm =
|
|
5621
|
-
const useNpm = !usePnpm &&
|
|
7099
|
+
const usePnpm = existsSync13(join7(webDir, "pnpm-lock.yaml"));
|
|
7100
|
+
const useNpm = !usePnpm && existsSync13(join7(webDir, "package-lock.json"));
|
|
5622
7101
|
const pkgManager = usePnpm ? "pnpm" : useNpm ? "npm" : "npx";
|
|
5623
7102
|
const { NODE_OPTIONS, TSX_TSCONFIG_PATH, ...cleanEnv } = process.env;
|
|
5624
7103
|
const apiUrl = publicUrl || `http://127.0.0.1:${apiPort}`;
|
|
5625
7104
|
const runtimeConfig = { apiBaseUrl: apiUrl };
|
|
5626
|
-
const runtimeConfigPath =
|
|
7105
|
+
const runtimeConfigPath = join7(webDir, "runtime-config.json");
|
|
5627
7106
|
try {
|
|
5628
7107
|
writeFileSync4(runtimeConfigPath, JSON.stringify(runtimeConfig, null, 2));
|
|
5629
7108
|
if (!quiet) console.log(` \u{1F4DD} Runtime config written to ${runtimeConfigPath}`);
|
|
@@ -5645,7 +7124,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5645
7124
|
if (standaloneServerPath) {
|
|
5646
7125
|
command = "node";
|
|
5647
7126
|
args = ["server.js"];
|
|
5648
|
-
cwd =
|
|
7127
|
+
cwd = dirname7(standaloneServerPath);
|
|
5649
7128
|
webEnv.PORT = String(actualPort);
|
|
5650
7129
|
webEnv.HOSTNAME = "0.0.0.0";
|
|
5651
7130
|
if (!quiet) console.log(" \u{1F4E6} Starting Web UI from standalone build...");
|
|
@@ -5686,10 +7165,10 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5686
7165
|
let started = false;
|
|
5687
7166
|
let exited = false;
|
|
5688
7167
|
let exitCode = null;
|
|
5689
|
-
const startedPromise = new Promise((
|
|
7168
|
+
const startedPromise = new Promise((resolve11) => {
|
|
5690
7169
|
const timeout = setTimeout(() => {
|
|
5691
7170
|
if (!started && !exited) {
|
|
5692
|
-
|
|
7171
|
+
resolve11(false);
|
|
5693
7172
|
}
|
|
5694
7173
|
}, startupTimeout);
|
|
5695
7174
|
child.stdout?.on("data", (data) => {
|
|
@@ -5703,7 +7182,7 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5703
7182
|
if (!started && (output.includes("Ready") || output.includes("started") || output.includes("localhost"))) {
|
|
5704
7183
|
started = true;
|
|
5705
7184
|
clearTimeout(timeout);
|
|
5706
|
-
|
|
7185
|
+
resolve11(true);
|
|
5707
7186
|
}
|
|
5708
7187
|
});
|
|
5709
7188
|
child.stderr?.on("data", (data) => {
|
|
@@ -5715,14 +7194,14 @@ async function startWebUI(apiPort, webPort = DEFAULT_WEB_PORT, quiet = false, pu
|
|
|
5715
7194
|
child.on("error", (err) => {
|
|
5716
7195
|
if (!quiet) console.error(` \u274C Web UI spawn error: ${err.message}`);
|
|
5717
7196
|
clearTimeout(timeout);
|
|
5718
|
-
|
|
7197
|
+
resolve11(false);
|
|
5719
7198
|
});
|
|
5720
7199
|
child.on("exit", (code) => {
|
|
5721
7200
|
exited = true;
|
|
5722
7201
|
exitCode = code;
|
|
5723
7202
|
if (!started) {
|
|
5724
7203
|
clearTimeout(timeout);
|
|
5725
|
-
|
|
7204
|
+
resolve11(false);
|
|
5726
7205
|
}
|
|
5727
7206
|
webUIProcess = null;
|
|
5728
7207
|
});
|
|
@@ -5815,7 +7294,7 @@ async function startServer(options = {}) {
|
|
|
5815
7294
|
if (options.workingDirectory) {
|
|
5816
7295
|
config.resolvedWorkingDirectory = options.workingDirectory;
|
|
5817
7296
|
}
|
|
5818
|
-
if (!
|
|
7297
|
+
if (!existsSync13(config.resolvedWorkingDirectory)) {
|
|
5819
7298
|
mkdirSync5(config.resolvedWorkingDirectory, { recursive: true });
|
|
5820
7299
|
if (!options.quiet) console.log(`\u{1F4C1} Created agent workspace: ${config.resolvedWorkingDirectory}`);
|
|
5821
7300
|
}
|
|
@@ -6319,8 +7798,8 @@ function generateOpenAPISpec() {
|
|
|
6319
7798
|
}
|
|
6320
7799
|
|
|
6321
7800
|
// src/cli.ts
|
|
6322
|
-
import { writeFileSync as writeFileSync5, existsSync as
|
|
6323
|
-
import { resolve as
|
|
7801
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync14 } from "fs";
|
|
7802
|
+
import { resolve as resolve10, join as join8 } from "path";
|
|
6324
7803
|
async function apiRequest(baseUrl, path, options = {}) {
|
|
6325
7804
|
const url = `${baseUrl}${path}`;
|
|
6326
7805
|
const init = {
|
|
@@ -6355,13 +7834,13 @@ async function getActiveStream(baseUrl, sessionId) {
|
|
|
6355
7834
|
return { hasActiveStream: false };
|
|
6356
7835
|
}
|
|
6357
7836
|
function promptApproval(rl, toolName, input) {
|
|
6358
|
-
return new Promise((
|
|
7837
|
+
return new Promise((resolve11) => {
|
|
6359
7838
|
const inputStr = JSON.stringify(input);
|
|
6360
7839
|
const truncatedInput = inputStr.length > 100 ? inputStr.slice(0, 100) + "..." : inputStr;
|
|
6361
7840
|
console.log(chalk.dim(` Command: ${truncatedInput}`));
|
|
6362
7841
|
rl.question(chalk.yellow(` Approve? [y/n]: `), (answer) => {
|
|
6363
7842
|
const approved = answer.toLowerCase().startsWith("y");
|
|
6364
|
-
|
|
7843
|
+
resolve11(approved);
|
|
6365
7844
|
});
|
|
6366
7845
|
});
|
|
6367
7846
|
}
|
|
@@ -6544,9 +8023,9 @@ async function runChat(options) {
|
|
|
6544
8023
|
input: process.stdin,
|
|
6545
8024
|
output: process.stdout
|
|
6546
8025
|
});
|
|
6547
|
-
const apiKey = await new Promise((
|
|
8026
|
+
const apiKey = await new Promise((resolve11) => {
|
|
6548
8027
|
keyRl.question(chalk.cyan("Enter your AI Gateway API key: "), (answer) => {
|
|
6549
|
-
|
|
8028
|
+
resolve11(answer.trim());
|
|
6550
8029
|
});
|
|
6551
8030
|
});
|
|
6552
8031
|
keyRl.close();
|
|
@@ -6877,13 +8356,13 @@ program.command("init").description("Create a sparkecoder.config.json file").opt
|
|
|
6877
8356
|
let configLocation;
|
|
6878
8357
|
if (options.global) {
|
|
6879
8358
|
const appDataDir = ensureAppDataDirectory();
|
|
6880
|
-
configPath =
|
|
8359
|
+
configPath = join8(appDataDir, "sparkecoder.config.json");
|
|
6881
8360
|
configLocation = "global";
|
|
6882
8361
|
} else {
|
|
6883
|
-
configPath =
|
|
8362
|
+
configPath = resolve10(process.cwd(), "sparkecoder.config.json");
|
|
6884
8363
|
configLocation = "local";
|
|
6885
8364
|
}
|
|
6886
|
-
if (
|
|
8365
|
+
if (existsSync14(configPath) && !options.force) {
|
|
6887
8366
|
console.log(chalk.yellow("Config file already exists. Use --force to overwrite."));
|
|
6888
8367
|
console.log(chalk.dim(` ${configPath}`));
|
|
6889
8368
|
return;
|