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