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