sparkecoder 0.1.98 → 0.1.99
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 +1258 -59
- package/dist/agent/index.js.map +1 -1
- package/dist/cli.js +10659 -8977
- package/dist/cli.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/{index-BvIissiB.d.ts → index-D5l-DMGC.d.ts} +390 -6
- package/dist/index.d.ts +5 -5
- package/dist/index.js +9985 -8095
- package/dist/index.js.map +1 -1
- package/dist/{schema-CohdIL13.d.ts → schema-ecQSnCMz.d.ts} +41 -0
- package/dist/server/index.js +8852 -6970
- package/dist/server/index.js.map +1 -1
- package/dist/skills/default/manage-mcp.md +94 -0
- package/dist/skills/default/search-conversations.md +100 -0
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +111 -2
- package/dist/tools/index.js.map +1 -1
- package/package.json +5 -1
- package/src/skills/default/manage-mcp.md +94 -0
- package/src/skills/default/search-conversations.md +100 -0
- 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 +2 -1
- package/web/.next/standalone/web/.next/build-manifest.json +5 -5
- package/web/.next/standalone/web/.next/prerender-manifest.json +51 -3
- package/web/.next/standalone/web/.next/routes-manifest.json +13 -24
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/build-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/next-font-manifest.json +1 -1
- package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js +7 -6
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +3 -3
- 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 +3 -3
- 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/(main)/settings/page/app-paths-manifest.json +3 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/build-manifest.json +18 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/next-font-manifest.json +11 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/react-loadable-manifest.json +1 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page/server-reference-manifest.json +4 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js +21 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.map +5 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -0
- package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +2 -0
- package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
- 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 +3 -3
- 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/agents.html +1 -0
- package/web/.next/standalone/web/.next/server/app/agents.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/agents.rsc +25 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +9 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +25 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +5 -0
- 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/build-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +3 -3
- package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
- package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
- 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/settings.html +1 -0
- package/web/.next/standalone/web/.next/server/app/settings.meta +16 -0
- package/web/.next/standalone/web/.next/server/app/settings.rsc +25 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +9 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +4 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +25 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +6 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +7 -0
- package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +5 -0
- package/web/.next/standalone/web/.next/server/app-paths-manifest.json +2 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_60d8842c._.js → 2374f_3b04c7b5._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2801b766._.js → 2374f_5ee1ee50._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c13c8f4f._.js → 2374f_7b7dd4c7._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b7f45fdf._.js → 2374f_9e444fb0._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a383a4d9._.js → 2374f_b57914d2._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_c2c47039._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_806bd012._.js → 2374f_db1d6704._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f363c084._.js → 2374f_e6dbbf5d._.js} +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_50c2f239._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2ea52390._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__6097da17._.js +15 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9f149e88._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__b050bb8f._.js +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c94db61a._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_4fe3c244._.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/{web_3c2b112b._.js → web_7ca56356._.js} +3 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_8e76ee8b._.js +8 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_90d4125e._.js +7 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_agents_page_actions_30f6e448.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_settings_page_actions_7285839d.js +3 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_b38a47ee._.js +4 -0
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_f7cf6b63._.js +3 -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 +1 -1
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +33 -0
- package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +3 -3
- package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
- package/web/.next/standalone/web/.next/server/next-font-manifest.json +8 -4
- 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/03b5edce6d5b809e.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/344be859c2c8600b.css +1 -0
- package/web/.next/standalone/web/.next/static/chunks/545725e4c1237026.js +7 -0
- package/web/.next/standalone/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
- package/web/.next/standalone/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
- package/web/.next/standalone/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
- package/web/.next/standalone/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
- package/web/.next/standalone/web/.next/static/chunks/f50a66c24c564585.js +13 -0
- package/web/.next/standalone/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
- package/web/.next/{static/chunks/turbopack-597558bb7b6982f6.js → standalone/web/.next/static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
- package/web/.next/standalone/web/.next/static/static/chunks/03b5edce6d5b809e.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/29dcecc3c2ca92b0.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/344be859c2c8600b.css +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/545725e4c1237026.js +7 -0
- package/web/.next/standalone/web/.next/static/static/chunks/60359bdd369c0c72.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/699803c4fb2dd3fc.js +5 -0
- package/web/.next/standalone/web/.next/static/static/chunks/735a2408c315b2f0.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/d54077a2bb8314ed.js +31 -0
- package/web/.next/standalone/web/.next/static/static/chunks/dc34aa94e57fa28e.js +3 -0
- package/web/.next/standalone/web/.next/static/static/chunks/ea89ca7892d8c557.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/f0f19357f3fb7cf8.js +1 -0
- package/web/.next/standalone/web/.next/static/static/chunks/f50a66c24c564585.js +13 -0
- package/web/.next/standalone/web/.next/static/static/chunks/f5fe518b79d1bf41.js +1 -0
- package/web/.next/standalone/web/.next/static/{chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
- package/web/.next/standalone/web/next.config.ts +1 -55
- package/web/.next/standalone/web/package-lock.json +3 -3
- package/web/.next/standalone/web/src/app/(main)/agents/page.tsx +222 -0
- package/web/.next/standalone/web/src/app/(main)/page.tsx +40 -1
- package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +9 -1
- package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +1116 -0
- package/web/.next/standalone/web/src/components/chat-interface.tsx +205 -4
- package/web/.next/standalone/web/src/components/pending-question-banner.tsx +106 -0
- package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +120 -50
- package/web/.next/standalone/web/src/lib/api.ts +43 -2
- package/web/.next/static/chunks/03b5edce6d5b809e.js +1 -0
- package/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
- package/web/.next/static/chunks/344be859c2c8600b.css +1 -0
- package/web/.next/static/chunks/545725e4c1237026.js +7 -0
- package/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
- package/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
- package/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
- package/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
- package/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
- package/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
- package/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
- package/web/.next/static/chunks/f50a66c24c564585.js +13 -0
- package/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
- package/web/.next/{standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page/app-paths-manifest.json +0 -3
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +0 -1
- package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +0 -2
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_317b1fef._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_37dd9702._.js +0 -45
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_4d44e4ed._.js +0 -26
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_54ac917f._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_86585101._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_c59a35bb._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_fdfc7f3d._.js +0 -31
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__234f92d8._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9a826344._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9d3a7cbf._.js +0 -15
- package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de76483d._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_08242997._.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_123ffe97._.js +0 -8
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +0 -7
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_99b01335._.js +0 -8
- package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_embed_[id]_page_actions_dd0b7fea.js +0 -3
- package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
- package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
- package/web/.next/standalone/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
- package/web/.next/standalone/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
- package/web/.next/standalone/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
- package/web/.next/standalone/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
- package/web/.next/standalone/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/1ebba7ac024244f9.js +0 -5
- package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/2bf377e04592f3c8.js +0 -13
- package/web/.next/standalone/web/.next/static/static/chunks/376a123113ccf5eb.js +0 -3
- package/web/.next/standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/61c9922b38a9569d.js +0 -3
- package/web/.next/standalone/web/.next/static/static/chunks/74b64476a24dd71e.css +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/767bcdfbabf0703e.js +0 -7
- package/web/.next/standalone/web/.next/static/static/chunks/a888d448ceab1abe.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/c6f40df16a9396b9.js +0 -1
- package/web/.next/standalone/web/.next/static/static/chunks/ea29be392100ab0f.js +0 -5
- package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +0 -77
- package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +0 -108
- package/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
- package/web/.next/static/chunks/275e8268daf318b2.js +0 -7
- package/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
- package/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
- package/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
- package/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
- package/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
- package/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
- package/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
- package/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
- package/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
- package/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
- package/dist/{search-CCffrVJE.d.ts → search-DOzC4ojH.d.ts} +1 -1
- /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/react-loadable-manifest.json +0 -0
- /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/server-reference-manifest.json +0 -0
- /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js.map +0 -0
- /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
- /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
- /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
- /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
- /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
package/dist/agent/index.js
CHANGED
|
@@ -56,7 +56,49 @@ var init_types = __esm({
|
|
|
56
56
|
computerUseEnabled: z.boolean().optional(),
|
|
57
57
|
// Display dimensions for the computer use tool (defaults: 1280x800).
|
|
58
58
|
computerUseDisplayWidth: z.number().int().positive().optional(),
|
|
59
|
-
computerUseDisplayHeight: z.number().int().positive().optional()
|
|
59
|
+
computerUseDisplayHeight: z.number().int().positive().optional(),
|
|
60
|
+
// 'orchestrator' = supervisor session; 'worker' = task spawned by an orchestrator.
|
|
61
|
+
role: z.enum(["orchestrator", "worker", "chat"]).optional(),
|
|
62
|
+
// Optional persona / extra system-prompt text appended to the orchestrator's
|
|
63
|
+
// system prompt (e.g. "speak in haiku", "answer in Korean", "be terse").
|
|
64
|
+
personality: z.string().optional(),
|
|
65
|
+
// For workers: the orchestrator session that spawned them. Used so
|
|
66
|
+
// terminal events (complete_task / task_failed / ask_question_to_user)
|
|
67
|
+
// can push system events back to the orchestrator's inbox.
|
|
68
|
+
orchestratorSessionId: z.string().optional(),
|
|
69
|
+
// Persisted Slack thread context the orchestrator can reply to.
|
|
70
|
+
slack: z.object({
|
|
71
|
+
channel: z.string().optional(),
|
|
72
|
+
threadTs: z.string().optional(),
|
|
73
|
+
teamId: z.string().optional()
|
|
74
|
+
}).optional(),
|
|
75
|
+
// Recurring prompts owned by this orchestrator. Stored inline on the
|
|
76
|
+
// session row so we don't need a separate DB collection.
|
|
77
|
+
schedules: z.array(z.object({
|
|
78
|
+
id: z.string(),
|
|
79
|
+
name: z.string(),
|
|
80
|
+
cron: z.string(),
|
|
81
|
+
prompt: z.string(),
|
|
82
|
+
replyChannel: z.string().optional(),
|
|
83
|
+
enabled: z.boolean().default(true),
|
|
84
|
+
lastFiredAt: z.string().optional(),
|
|
85
|
+
// ISO date
|
|
86
|
+
createdAt: z.string()
|
|
87
|
+
// ISO date
|
|
88
|
+
})).optional(),
|
|
89
|
+
// Custom token-protected inbound webhook URLs.
|
|
90
|
+
webhooks: z.array(z.object({
|
|
91
|
+
id: z.string(),
|
|
92
|
+
name: z.string(),
|
|
93
|
+
token: z.string(),
|
|
94
|
+
wake: z.enum(["now", "next"]).default("now"),
|
|
95
|
+
template: z.string().optional(),
|
|
96
|
+
hitCount: z.number().int().default(0),
|
|
97
|
+
lastHitAt: z.string().optional(),
|
|
98
|
+
// ISO date
|
|
99
|
+
createdAt: z.string()
|
|
100
|
+
// ISO date
|
|
101
|
+
})).optional()
|
|
60
102
|
});
|
|
61
103
|
VectorGatewayConfigSchema = z.object({
|
|
62
104
|
// Redis cluster nodes URL for Vector Gateway (or use REDIS_CLUSTER_NODES env var)
|
|
@@ -144,7 +186,74 @@ var init_types = __esm({
|
|
|
144
186
|
// If configured, uses remote MongoDB instead of local SQLite
|
|
145
187
|
remoteServer: RemoteServerConfigSchema,
|
|
146
188
|
// Vector Gateway configuration for semantic search
|
|
147
|
-
vectorGateway: VectorGatewayConfigSchema
|
|
189
|
+
vectorGateway: VectorGatewayConfigSchema,
|
|
190
|
+
// Slack integration. When configured, the orchestrator can post to Slack
|
|
191
|
+
// and Slack `app_mention` events are routed into the orchestrator session.
|
|
192
|
+
slack: z.object({
|
|
193
|
+
botToken: z.string().optional(),
|
|
194
|
+
signingSecret: z.string().optional(),
|
|
195
|
+
defaultOrchestratorName: z.string().optional().default("orchestrator"),
|
|
196
|
+
// Allowlist controls — when set, only matching events reach the orchestrator.
|
|
197
|
+
// Empty arrays = "no allowlist" => allow all (subject to allowDmsFromAnyone below).
|
|
198
|
+
allowedUsers: z.array(z.string()).optional().default([]),
|
|
199
|
+
// Slack user IDs (U0123...)
|
|
200
|
+
allowedChannels: z.array(z.string()).optional().default([]),
|
|
201
|
+
// Slack channel IDs (C0123...)
|
|
202
|
+
// If true (default), the bot replies to DMs from anyone. If false, the user
|
|
203
|
+
// must also appear in `allowedUsers`.
|
|
204
|
+
allowDmsFromAnyone: z.boolean().optional().default(true),
|
|
205
|
+
// When true, denied @mentions / DMs get a friendly canned reply
|
|
206
|
+
// (no AI / no orchestrator). Use {user} and {channel} placeholders.
|
|
207
|
+
deniedReplyEnabled: z.boolean().optional().default(true),
|
|
208
|
+
deniedReplyTemplate: z.string().optional().default(
|
|
209
|
+
"Sorry, you don't have permission to use this bot. (Contact the bot owner if you think this is a mistake.)"
|
|
210
|
+
)
|
|
211
|
+
}).partial().optional(),
|
|
212
|
+
// MCP integrations. Each server is connected on demand and its tools are
|
|
213
|
+
// merged into the agent's toolset under `mcp_<server-name>_<tool>` keys.
|
|
214
|
+
mcp: z.object({
|
|
215
|
+
servers: z.array(z.object({
|
|
216
|
+
id: z.string(),
|
|
217
|
+
name: z.string(),
|
|
218
|
+
// human-friendly; also used as tool prefix
|
|
219
|
+
transport: z.enum(["http", "sse", "stdio"]),
|
|
220
|
+
url: z.string().optional(),
|
|
221
|
+
// http/sse
|
|
222
|
+
headers: z.record(z.string(), z.string()).optional(),
|
|
223
|
+
// http/sse (e.g. Authorization: Bearer ...)
|
|
224
|
+
command: z.string().optional(),
|
|
225
|
+
// stdio
|
|
226
|
+
args: z.array(z.string()).optional(),
|
|
227
|
+
// stdio
|
|
228
|
+
enabled: z.boolean().default(true),
|
|
229
|
+
createdAt: z.string(),
|
|
230
|
+
// ISO date
|
|
231
|
+
// OAuth scaffolding — present so tokens can be persisted by the
|
|
232
|
+
// settings UI once the auth flow completes. Bearer tokens can also
|
|
233
|
+
// be stored directly via `headers.Authorization` for now.
|
|
234
|
+
oauth: z.object({
|
|
235
|
+
authorizationUrl: z.string().optional(),
|
|
236
|
+
tokenUrl: z.string().optional(),
|
|
237
|
+
clientId: z.string().optional(),
|
|
238
|
+
clientSecret: z.string().optional(),
|
|
239
|
+
accessToken: z.string().optional(),
|
|
240
|
+
refreshToken: z.string().optional(),
|
|
241
|
+
expiresAt: z.string().optional()
|
|
242
|
+
}).partial().optional()
|
|
243
|
+
})).optional().default([])
|
|
244
|
+
}).partial().optional(),
|
|
245
|
+
// Authentication for the public (cloudflared) surface. When
|
|
246
|
+
// auth.cfAccess.enabled is true, every non-loopback request must carry a
|
|
247
|
+
// valid Cf-Access-Jwt-Assertion header whose `email` claim is in
|
|
248
|
+
// `auth.allowedEmails`. Slack events and /health bypass the check.
|
|
249
|
+
auth: z.object({
|
|
250
|
+
cfAccess: z.object({
|
|
251
|
+
enabled: z.boolean().optional().default(false),
|
|
252
|
+
teamDomain: z.string().optional(),
|
|
253
|
+
audTag: z.string().optional()
|
|
254
|
+
}).partial().optional(),
|
|
255
|
+
allowedEmails: z.array(z.string()).optional().default([])
|
|
256
|
+
}).partial().optional()
|
|
148
257
|
});
|
|
149
258
|
}
|
|
150
259
|
});
|
|
@@ -164,12 +273,43 @@ function getAppDataDirectory() {
|
|
|
164
273
|
return join(process.env.XDG_DATA_HOME || join(homedir(), ".local", "share"), appName);
|
|
165
274
|
}
|
|
166
275
|
}
|
|
276
|
+
function ensureAppDataDirectory() {
|
|
277
|
+
const dir = getAppDataDirectory();
|
|
278
|
+
if (!existsSync(dir)) {
|
|
279
|
+
mkdirSync(dir, { recursive: true });
|
|
280
|
+
}
|
|
281
|
+
return dir;
|
|
282
|
+
}
|
|
167
283
|
function getConfig() {
|
|
168
284
|
if (!cachedConfig) {
|
|
169
285
|
throw new Error("Config not loaded. Call loadConfig first.");
|
|
170
286
|
}
|
|
171
287
|
return cachedConfig;
|
|
172
288
|
}
|
|
289
|
+
function setMcpServers(servers2) {
|
|
290
|
+
if (cachedConfig) {
|
|
291
|
+
const cur = cachedConfig.mcp || {};
|
|
292
|
+
cachedConfig.mcp = { ...cur, servers: servers2 };
|
|
293
|
+
}
|
|
294
|
+
try {
|
|
295
|
+
const cwdPath = resolve(process.cwd(), "sparkecoder.config.json");
|
|
296
|
+
const target = existsSync(cwdPath) ? cwdPath : join(ensureAppDataDirectory(), "sparkecoder.config.json");
|
|
297
|
+
let raw = {};
|
|
298
|
+
if (existsSync(target)) {
|
|
299
|
+
try {
|
|
300
|
+
raw = JSON.parse(readFileSync(target, "utf-8"));
|
|
301
|
+
} catch {
|
|
302
|
+
raw = {};
|
|
303
|
+
}
|
|
304
|
+
} else {
|
|
305
|
+
raw = createDefaultConfig();
|
|
306
|
+
}
|
|
307
|
+
raw.mcp = { ...raw.mcp || {}, servers: servers2 };
|
|
308
|
+
writeFileSync(target, JSON.stringify(raw, null, 2));
|
|
309
|
+
} catch (err) {
|
|
310
|
+
console.warn("[config] failed to persist mcp config:", err?.message || err);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
173
313
|
function requiresApproval(toolName, sessionConfig) {
|
|
174
314
|
const config = getConfig();
|
|
175
315
|
if (sessionConfig?.toolApprovals?.["*"] !== void 0) {
|
|
@@ -187,6 +327,33 @@ function requiresApproval(toolName, sessionConfig) {
|
|
|
187
327
|
}
|
|
188
328
|
return false;
|
|
189
329
|
}
|
|
330
|
+
function createDefaultConfig() {
|
|
331
|
+
return {
|
|
332
|
+
defaultModel: "anthropic/claude-opus-4.7",
|
|
333
|
+
// workingDirectory is intentionally not set - defaults to where CLI is run
|
|
334
|
+
toolApprovals: {
|
|
335
|
+
bash: true,
|
|
336
|
+
write_file: false,
|
|
337
|
+
read_file: false,
|
|
338
|
+
load_skill: false,
|
|
339
|
+
todo: false
|
|
340
|
+
},
|
|
341
|
+
skills: {
|
|
342
|
+
directory: "./skills",
|
|
343
|
+
additionalDirectories: []
|
|
344
|
+
},
|
|
345
|
+
context: {
|
|
346
|
+
maxChars: 2e5,
|
|
347
|
+
autoSummarize: true,
|
|
348
|
+
keepRecentMessages: 10
|
|
349
|
+
},
|
|
350
|
+
server: {
|
|
351
|
+
port: 3141,
|
|
352
|
+
host: "127.0.0.1"
|
|
353
|
+
},
|
|
354
|
+
databasePath: "./sparkecoder.db"
|
|
355
|
+
};
|
|
356
|
+
}
|
|
190
357
|
var cachedConfig, PROVIDER_ENV_MAP, SUPPORTED_PROVIDERS;
|
|
191
358
|
var init_config = __esm({
|
|
192
359
|
"src/config/index.ts"() {
|
|
@@ -661,13 +828,43 @@ var init_remote = __esm({
|
|
|
661
828
|
});
|
|
662
829
|
|
|
663
830
|
// src/db/index.ts
|
|
831
|
+
var db_exports = {};
|
|
832
|
+
__export(db_exports, {
|
|
833
|
+
activeStreamQueries: () => activeStreamQueries,
|
|
834
|
+
checkpointQueries: () => checkpointQueries,
|
|
835
|
+
closeDatabase: () => closeDatabase,
|
|
836
|
+
fileBackupQueries: () => fileBackupQueries,
|
|
837
|
+
getDb: () => getDb,
|
|
838
|
+
indexStatusQueries: () => indexStatusQueries,
|
|
839
|
+
indexedChunkQueries: () => indexedChunkQueries,
|
|
840
|
+
initDatabase: () => initDatabase,
|
|
841
|
+
isUsingRemote: () => isUsingRemote,
|
|
842
|
+
messageQueries: () => messageQueries,
|
|
843
|
+
sessionQueries: () => sessionQueries,
|
|
844
|
+
skillQueries: () => skillQueries,
|
|
845
|
+
subagentQueries: () => subagentQueries,
|
|
846
|
+
terminalQueries: () => terminalQueries,
|
|
847
|
+
todoQueries: () => todoQueries,
|
|
848
|
+
toolExecutionQueries: () => toolExecutionQueries
|
|
849
|
+
});
|
|
850
|
+
function initDatabase(config) {
|
|
851
|
+
initRemoteDatabase(config.url, config.authKey);
|
|
852
|
+
initialized = true;
|
|
853
|
+
}
|
|
664
854
|
function getDb() {
|
|
665
855
|
if (!initialized) {
|
|
666
856
|
throw new Error("Database not initialized. Call initDatabase first.");
|
|
667
857
|
}
|
|
668
858
|
return {};
|
|
669
859
|
}
|
|
670
|
-
|
|
860
|
+
function isUsingRemote() {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
function closeDatabase() {
|
|
864
|
+
closeRemoteDatabase();
|
|
865
|
+
initialized = false;
|
|
866
|
+
}
|
|
867
|
+
var initialized, sessionQueries, messageQueries, toolExecutionQueries, todoQueries, skillQueries, terminalQueries, activeStreamQueries, checkpointQueries, fileBackupQueries, subagentQueries, indexedChunkQueries, indexStatusQueries;
|
|
671
868
|
var init_db = __esm({
|
|
672
869
|
"src/db/index.ts"() {
|
|
673
870
|
"use strict";
|
|
@@ -678,8 +875,12 @@ var init_db = __esm({
|
|
|
678
875
|
toolExecutionQueries = remoteToolExecutionQueries;
|
|
679
876
|
todoQueries = remoteTodoQueries;
|
|
680
877
|
skillQueries = remoteSkillQueries;
|
|
878
|
+
terminalQueries = remoteTerminalQueries;
|
|
879
|
+
activeStreamQueries = remoteActiveStreamQueries;
|
|
880
|
+
checkpointQueries = remoteCheckpointQueries;
|
|
681
881
|
fileBackupQueries = remoteFileBackupQueries;
|
|
682
882
|
subagentQueries = remoteSubagentQueries;
|
|
883
|
+
indexedChunkQueries = remoteIndexedChunkQueries;
|
|
683
884
|
indexStatusQueries = remoteIndexStatusQueries;
|
|
684
885
|
}
|
|
685
886
|
});
|
|
@@ -783,20 +984,20 @@ async function loadSkillsFromDirectory(directory, options = {}) {
|
|
|
783
984
|
}
|
|
784
985
|
const skills = [];
|
|
785
986
|
const entries = await readdir(directory, { withFileTypes: true });
|
|
786
|
-
for (const
|
|
987
|
+
for (const entry2 of entries) {
|
|
787
988
|
let filePath;
|
|
788
989
|
let fileName;
|
|
789
|
-
if (
|
|
790
|
-
const skillMdPath = resolve6(directory,
|
|
990
|
+
if (entry2.isDirectory()) {
|
|
991
|
+
const skillMdPath = resolve6(directory, entry2.name, "SKILL.md");
|
|
791
992
|
if (existsSync10(skillMdPath)) {
|
|
792
993
|
filePath = skillMdPath;
|
|
793
|
-
fileName =
|
|
994
|
+
fileName = entry2.name;
|
|
794
995
|
} else {
|
|
795
996
|
continue;
|
|
796
997
|
}
|
|
797
|
-
} else if (
|
|
798
|
-
filePath = resolve6(directory,
|
|
799
|
-
fileName =
|
|
998
|
+
} else if (entry2.name.endsWith(".md") || entry2.name.endsWith(".mdc")) {
|
|
999
|
+
filePath = resolve6(directory, entry2.name);
|
|
1000
|
+
fileName = entry2.name;
|
|
800
1001
|
} else {
|
|
801
1002
|
continue;
|
|
802
1003
|
}
|
|
@@ -1425,6 +1626,63 @@ var init_semantic_search = __esm({
|
|
|
1425
1626
|
}
|
|
1426
1627
|
});
|
|
1427
1628
|
|
|
1629
|
+
// src/orchestrator/conversation-archive.ts
|
|
1630
|
+
var conversation_archive_exports = {};
|
|
1631
|
+
__export(conversation_archive_exports, {
|
|
1632
|
+
appendTurn: () => appendTurn,
|
|
1633
|
+
flattenContent: () => flattenContent,
|
|
1634
|
+
getHistoryDir: () => getHistoryDir,
|
|
1635
|
+
listSessionArchives: () => listSessionArchives
|
|
1636
|
+
});
|
|
1637
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync6, appendFileSync as appendFileSync2, readdirSync as readdirSync2 } from "fs";
|
|
1638
|
+
import { join as join9 } from "path";
|
|
1639
|
+
function getHistoryDir() {
|
|
1640
|
+
const dir = join9(ensureAppDataDirectory(), "history");
|
|
1641
|
+
if (!existsSync16(dir)) mkdirSync6(dir, { recursive: true });
|
|
1642
|
+
return dir;
|
|
1643
|
+
}
|
|
1644
|
+
function appendTurn(turn) {
|
|
1645
|
+
try {
|
|
1646
|
+
const dir = getHistoryDir();
|
|
1647
|
+
const path = join9(dir, `${turn.sessionId}.jsonl`);
|
|
1648
|
+
const line = JSON.stringify({
|
|
1649
|
+
ts: turn.ts ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1650
|
+
sessionId: turn.sessionId,
|
|
1651
|
+
sessionName: turn.sessionName,
|
|
1652
|
+
role: turn.role,
|
|
1653
|
+
content: turn.content,
|
|
1654
|
+
channel: turn.channel
|
|
1655
|
+
});
|
|
1656
|
+
appendFileSync2(path, line + "\n", "utf-8");
|
|
1657
|
+
} catch (err) {
|
|
1658
|
+
console.warn(`[conversation-archive] failed to persist turn for ${turn.sessionId}: ${err?.message || err}`);
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
function flattenContent(content) {
|
|
1662
|
+
if (typeof content === "string") return content;
|
|
1663
|
+
if (Array.isArray(content)) {
|
|
1664
|
+
return content.map((p) => {
|
|
1665
|
+
if (!p || typeof p !== "object") return "";
|
|
1666
|
+
if (p.type === "text" && typeof p.text === "string") return p.text;
|
|
1667
|
+
if (p.type === "image") return `[image${p.filename ? ` ${p.filename}` : ""}]`;
|
|
1668
|
+
if (p.type === "file") return `[file${p.filename ? ` ${p.filename}` : ""}]`;
|
|
1669
|
+
return "";
|
|
1670
|
+
}).filter(Boolean).join(" ");
|
|
1671
|
+
}
|
|
1672
|
+
return "";
|
|
1673
|
+
}
|
|
1674
|
+
function listSessionArchives() {
|
|
1675
|
+
const dir = getHistoryDir();
|
|
1676
|
+
if (!existsSync16(dir)) return [];
|
|
1677
|
+
return readdirSync2(dir).filter((f) => f.endsWith(".jsonl"));
|
|
1678
|
+
}
|
|
1679
|
+
var init_conversation_archive = __esm({
|
|
1680
|
+
"src/orchestrator/conversation-archive.ts"() {
|
|
1681
|
+
"use strict";
|
|
1682
|
+
init_config();
|
|
1683
|
+
}
|
|
1684
|
+
});
|
|
1685
|
+
|
|
1428
1686
|
// src/browser/stream-proxy.ts
|
|
1429
1687
|
var stream_proxy_exports = {};
|
|
1430
1688
|
__export(stream_proxy_exports, {
|
|
@@ -1634,9 +1892,9 @@ __export(recorder_exports, {
|
|
|
1634
1892
|
import { exec as exec6 } from "child_process";
|
|
1635
1893
|
import { promisify as promisify6 } from "util";
|
|
1636
1894
|
import { writeFile as writeFile5, mkdir as mkdir4, readFile as readFile11, unlink as unlink2, readdir as readdir5, rm } from "fs/promises";
|
|
1637
|
-
import { join as
|
|
1895
|
+
import { join as join11 } from "path";
|
|
1638
1896
|
import { tmpdir as tmpdir2 } from "os";
|
|
1639
|
-
import { nanoid as
|
|
1897
|
+
import { nanoid as nanoid8 } from "nanoid";
|
|
1640
1898
|
async function checkFfmpeg() {
|
|
1641
1899
|
try {
|
|
1642
1900
|
await execAsync6("ffmpeg -version", { timeout: 5e3 });
|
|
@@ -1691,21 +1949,21 @@ var init_recorder = __esm({
|
|
|
1691
1949
|
*/
|
|
1692
1950
|
async encode() {
|
|
1693
1951
|
if (this.frames.length === 0) return null;
|
|
1694
|
-
const workDir =
|
|
1952
|
+
const workDir = join11(tmpdir2(), `sparkecoder-recording-${nanoid8(8)}`);
|
|
1695
1953
|
await mkdir4(workDir, { recursive: true });
|
|
1696
1954
|
try {
|
|
1697
1955
|
for (let i = 0; i < this.frames.length; i++) {
|
|
1698
|
-
const framePath =
|
|
1956
|
+
const framePath = join11(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
|
|
1699
1957
|
await writeFile5(framePath, this.frames[i].data);
|
|
1700
1958
|
}
|
|
1701
1959
|
const duration = (this.frames[this.frames.length - 1].timestamp - this.frames[0].timestamp) / 1e3;
|
|
1702
1960
|
const fps = duration > 0 ? Math.round(this.frames.length / duration) : 10;
|
|
1703
1961
|
const clampedFps = Math.max(1, Math.min(fps, 30));
|
|
1704
|
-
const outputPath =
|
|
1962
|
+
const outputPath = join11(workDir, `recording_${this.sessionId}.mp4`);
|
|
1705
1963
|
const hasFfmpeg = await checkFfmpeg();
|
|
1706
1964
|
if (hasFfmpeg) {
|
|
1707
1965
|
await execAsync6(
|
|
1708
|
-
`ffmpeg -y -framerate ${clampedFps} -i "${
|
|
1966
|
+
`ffmpeg -y -framerate ${clampedFps} -i "${join11(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
|
|
1709
1967
|
{ timeout: 12e4 }
|
|
1710
1968
|
);
|
|
1711
1969
|
} else {
|
|
@@ -1717,7 +1975,7 @@ var init_recorder = __esm({
|
|
|
1717
1975
|
const files = await readdir5(workDir);
|
|
1718
1976
|
for (const f of files) {
|
|
1719
1977
|
if (f.startsWith("frame_")) {
|
|
1720
|
-
await unlink2(
|
|
1978
|
+
await unlink2(join11(workDir, f)).catch(() => {
|
|
1721
1979
|
});
|
|
1722
1980
|
}
|
|
1723
1981
|
}
|
|
@@ -1742,7 +2000,7 @@ var init_recorder = __esm({
|
|
|
1742
2000
|
import {
|
|
1743
2001
|
streamText as streamText2,
|
|
1744
2002
|
generateText as generateText3,
|
|
1745
|
-
tool as
|
|
2003
|
+
tool as tool15,
|
|
1746
2004
|
stepCountIs as stepCountIs2
|
|
1747
2005
|
} from "ai";
|
|
1748
2006
|
|
|
@@ -1950,8 +2208,8 @@ var SUBAGENT_MODELS = {
|
|
|
1950
2208
|
// src/agent/index.ts
|
|
1951
2209
|
init_db();
|
|
1952
2210
|
init_config();
|
|
1953
|
-
import { z as
|
|
1954
|
-
import { nanoid as
|
|
2211
|
+
import { z as z16 } from "zod";
|
|
2212
|
+
import { nanoid as nanoid9 } from "nanoid";
|
|
1955
2213
|
|
|
1956
2214
|
// src/tools/bash.ts
|
|
1957
2215
|
import { tool } from "ai";
|
|
@@ -3133,7 +3391,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3133
3391
|
},
|
|
3134
3392
|
async waitForDiagnostics(filePath, timeoutMs = 5e3) {
|
|
3135
3393
|
const normalized = normalizePath(filePath);
|
|
3136
|
-
return new Promise((
|
|
3394
|
+
return new Promise((resolve11) => {
|
|
3137
3395
|
const startTime = Date.now();
|
|
3138
3396
|
let debounceTimer;
|
|
3139
3397
|
let resolved = false;
|
|
@@ -3152,7 +3410,7 @@ async function createClient(serverId, handle, root) {
|
|
|
3152
3410
|
if (resolved) return;
|
|
3153
3411
|
resolved = true;
|
|
3154
3412
|
cleanup2();
|
|
3155
|
-
|
|
3413
|
+
resolve11(diagnostics.get(normalized) || []);
|
|
3156
3414
|
};
|
|
3157
3415
|
const onDiagnostic = () => {
|
|
3158
3416
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
@@ -3363,20 +3621,20 @@ async function getClientsForFile(filePath) {
|
|
|
3363
3621
|
return client ? [client] : [];
|
|
3364
3622
|
}
|
|
3365
3623
|
async function touchFile(filePath, waitForDiagnostics = false) {
|
|
3366
|
-
const
|
|
3367
|
-
if (
|
|
3624
|
+
const clients2 = await getClientsForFile(filePath);
|
|
3625
|
+
if (clients2.length === 0) {
|
|
3368
3626
|
return;
|
|
3369
3627
|
}
|
|
3370
|
-
await Promise.all(
|
|
3628
|
+
await Promise.all(clients2.map((client) => client.notifyOpen(filePath)));
|
|
3371
3629
|
if (waitForDiagnostics) {
|
|
3372
|
-
await Promise.all(
|
|
3630
|
+
await Promise.all(clients2.map((client) => client.waitForDiagnostics(filePath)));
|
|
3373
3631
|
}
|
|
3374
3632
|
}
|
|
3375
3633
|
async function getDiagnostics(filePath) {
|
|
3376
3634
|
const normalized = normalizePath(filePath);
|
|
3377
|
-
const
|
|
3635
|
+
const clients2 = await getClientsForFile(normalized);
|
|
3378
3636
|
const allDiagnostics = [];
|
|
3379
|
-
for (const client of
|
|
3637
|
+
for (const client of clients2) {
|
|
3380
3638
|
const diags = client.getDiagnostics(normalized);
|
|
3381
3639
|
allDiagnostics.push(...diags);
|
|
3382
3640
|
}
|
|
@@ -3507,7 +3765,7 @@ Working directory: ${options.workingDirectory}`,
|
|
|
3507
3765
|
isChunked: true
|
|
3508
3766
|
});
|
|
3509
3767
|
if (chunkCount > 1) {
|
|
3510
|
-
await new Promise((
|
|
3768
|
+
await new Promise((resolve11) => setTimeout(resolve11, 0));
|
|
3511
3769
|
}
|
|
3512
3770
|
}
|
|
3513
3771
|
}
|
|
@@ -4103,16 +4361,16 @@ async function findSupportedFiles(dir, workingDirectory, maxFiles = 50) {
|
|
|
4103
4361
|
if (files.length >= maxFiles) return;
|
|
4104
4362
|
try {
|
|
4105
4363
|
const entries = await readdir2(currentDir, { withFileTypes: true });
|
|
4106
|
-
for (const
|
|
4364
|
+
for (const entry2 of entries) {
|
|
4107
4365
|
if (files.length >= maxFiles) break;
|
|
4108
|
-
const fullPath = resolve7(currentDir,
|
|
4109
|
-
if (
|
|
4110
|
-
if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(
|
|
4366
|
+
const fullPath = resolve7(currentDir, entry2.name);
|
|
4367
|
+
if (entry2.isDirectory()) {
|
|
4368
|
+
if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry2.name)) {
|
|
4111
4369
|
continue;
|
|
4112
4370
|
}
|
|
4113
4371
|
await walk(fullPath);
|
|
4114
|
-
} else if (
|
|
4115
|
-
const ext = extname5(
|
|
4372
|
+
} else if (entry2.isFile()) {
|
|
4373
|
+
const ext = extname5(entry2.name);
|
|
4116
4374
|
if (supportedExtensions.includes(ext)) {
|
|
4117
4375
|
files.push(fullPath);
|
|
4118
4376
|
}
|
|
@@ -4453,8 +4711,8 @@ var Subagent = class {
|
|
|
4453
4711
|
if (eventQueue.length > 0) {
|
|
4454
4712
|
yield eventQueue.shift();
|
|
4455
4713
|
} else if (!done) {
|
|
4456
|
-
const event = await new Promise((
|
|
4457
|
-
resolveNext =
|
|
4714
|
+
const event = await new Promise((resolve11) => {
|
|
4715
|
+
resolveNext = resolve11;
|
|
4458
4716
|
});
|
|
4459
4717
|
if (event) {
|
|
4460
4718
|
yield event;
|
|
@@ -4605,16 +4863,16 @@ async function grepForSymbol(symbol, workingDirectory) {
|
|
|
4605
4863
|
let remaining = maxFiles;
|
|
4606
4864
|
try {
|
|
4607
4865
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
4608
|
-
for (const
|
|
4866
|
+
for (const entry2 of entries) {
|
|
4609
4867
|
if (remaining <= 0) return null;
|
|
4610
|
-
const fullPath = resolve8(dir,
|
|
4611
|
-
if (
|
|
4612
|
-
if (IGNORED_DIRS.has(
|
|
4868
|
+
const fullPath = resolve8(dir, entry2.name);
|
|
4869
|
+
if (entry2.isDirectory()) {
|
|
4870
|
+
if (IGNORED_DIRS.has(entry2.name) || entry2.name.startsWith(".")) continue;
|
|
4613
4871
|
const found = await search(fullPath, remaining);
|
|
4614
4872
|
if (found) return found;
|
|
4615
4873
|
remaining -= 10;
|
|
4616
|
-
} else if (
|
|
4617
|
-
const ext =
|
|
4874
|
+
} else if (entry2.isFile()) {
|
|
4875
|
+
const ext = entry2.name.substring(entry2.name.lastIndexOf("."));
|
|
4618
4876
|
if (!SUPPORTED_EXTS.has(ext)) continue;
|
|
4619
4877
|
remaining--;
|
|
4620
4878
|
const content = await readFile8(fullPath, "utf-8");
|
|
@@ -6753,6 +7011,84 @@ ${JSON.stringify(outputSchema, null, 2)}
|
|
|
6753
7011
|
- **\`ask_question_to_user({ question, context?, choices? })\`** \u2014 Call only when you need information that is not available in the repo, task prompt, files, logs, or tools. Ask one clear question; after the answer is returned, continue working.
|
|
6754
7012
|
`;
|
|
6755
7013
|
}
|
|
7014
|
+
function buildOrchestratorPromptAddendum() {
|
|
7015
|
+
return `
|
|
7016
|
+
## Orchestrator Mode
|
|
7017
|
+
|
|
7018
|
+
You are the **orchestrator agent**. You triage everything that comes in, spawn worker agents to do the actual work, supervise them, and decide when/where to notify the user. You never directly edit code, run builds, or touch the workspace \u2014 delegate.
|
|
7019
|
+
|
|
7020
|
+
### Channels (where messages come from, and how to reply)
|
|
7021
|
+
|
|
7022
|
+
Every user-message you see is tagged at the front with a channel pill describing where it came from. **You are responsible for routing replies to the correct channel.** Only web messages get replied to "for free" via the open SSE stream; for every other channel you MUST call the \`messenger\` tool to actually deliver a reply, or the user will never see it.
|
|
7023
|
+
|
|
7024
|
+
Pill formats:
|
|
7025
|
+
|
|
7026
|
+
- \`[WEB] ...\` \u2014 typed in the web dashboard. Your assistant text streams back automatically. No tool call needed.
|
|
7027
|
+
- \`[SLACK channel=C0123 thread=1700000000.001 user=U0123] ...\` \u2014 a Slack mention or DM. To reply: \`messenger({action:'post', channel:'slack', to:'C0123', threadTs:'1700000000.001', text:'...'})\`. **Always set \`threadTs\`** so the reply lands in the same thread.
|
|
7028
|
+
- \`[SYSTEM worker.completed worker-name] ...\` \u2014 a worker you spawned finished. Look back at the conversation: where did the request originate?
|
|
7029
|
+
- If the original request was \`[WEB]\`, a normal text reply is enough (the user is watching).
|
|
7030
|
+
- If it was \`[SLACK ...]\`, post the result to that Slack thread via \`messenger\`.
|
|
7031
|
+
- If it was a schedule with \`replyChannel\`, post there.
|
|
7032
|
+
- \`[SYSTEM worker.failed worker-name] ...\` \u2014 same routing logic as completed; tell the user.
|
|
7033
|
+
- \`[SYSTEM worker.question worker-name] ...\` \u2014 a worker is blocked on \`ask_question_to_user\`. Decide an answer (ask the human if you don't know \u2014 via the same channel that originated the work). Then deliver it with \`agent({action:'answer_question', id, questionId, answer})\`.
|
|
7034
|
+
- \`[SCHEDULE name] ...\` \u2014 a scheduled prompt fired. Treat as a user request from that schedule. Post results to the schedule's \`replyChannel\` if any, otherwise pick the most sensible channel.
|
|
7035
|
+
- \`[WEBHOOK name] ...\` \u2014 an external service hit one of your webhook URLs. Body is the request body (verbatim or per the webhook's template).
|
|
7036
|
+
|
|
7037
|
+
### Handling delivery failures
|
|
7038
|
+
|
|
7039
|
+
If \`messenger({action:'post', ...})\` returns \`{ok:false, error:'...'}\` (e.g. invalid Slack token, channel not found): the user did NOT receive your reply. Try:
|
|
7040
|
+
1. Re-checking the destination (channel id, thread ts).
|
|
7041
|
+
2. Falling back to another channel the user is reachable on (e.g. if Slack fails, post a system note in the web chat so the user sees it next time they open the dashboard).
|
|
7042
|
+
3. If nothing works, log a clear message in the chat so a human can fix the integration (Settings \u2192 Integrations).
|
|
7043
|
+
**Never silently swallow a delivery failure.**
|
|
7044
|
+
|
|
7045
|
+
### Hard rules
|
|
7046
|
+
|
|
7047
|
+
- Never edit code, run builds, or modify files yourself. \`bash\`, \`write_file\` etc. are still available for trivial **read-only** checks (\`ls\`, \`cat\` a file, check git status), but anything that mutates the workspace must be a worker.
|
|
7048
|
+
- Give workers **clear, self-contained goals**. Include any context they'd otherwise have to ask you about.
|
|
7049
|
+
- Prefer \`agent({action:'message'})\` (queued) over \`agent({action:'stop'})\` for course corrections.
|
|
7050
|
+
- Don't poll. Worker completions wake you automatically via SYSTEM events.
|
|
7051
|
+
|
|
7052
|
+
### Tools (4 total \u2014 each takes an \`action\` field)
|
|
7053
|
+
|
|
7054
|
+
\`\`\`
|
|
7055
|
+
agent({action: 'list' | 'get' | 'spawn' | 'message' | 'answer_question' | 'stop', ...})
|
|
7056
|
+
messenger({action: 'list_channels' | 'post', ...})
|
|
7057
|
+
schedule({action: 'create' | 'list' | 'update' | 'delete' | 'pause' | 'resume', ...})
|
|
7058
|
+
webhook({action: 'create' | 'list' | 'update' | 'delete', ...})
|
|
7059
|
+
\`\`\`
|
|
7060
|
+
|
|
7061
|
+
You ALSO have the regular agent toolset (\`bash\`, \`read_file\`, \`write_file\`, \`load_skill\`, \`linter\`, \`explore_agent\`, \`code_graph\`, etc.) for low-level work.
|
|
7062
|
+
|
|
7063
|
+
### Self-extension via skills + filesystem
|
|
7064
|
+
|
|
7065
|
+
You manage your own configuration by editing files. Load the relevant skill first to get the file path and schema:
|
|
7066
|
+
|
|
7067
|
+
- **MCP integrations** \u2014 load the \`manage-mcp\` skill. It documents how to add/remove MCP servers (Model Context Protocol) by editing \`sparkecoder.config.json\`. Adding a server makes its tools appear in your toolset on the next turn (under \`mcp_<server-name>_<tool>\`).
|
|
7068
|
+
- **Conversation history / long-term memory** \u2014 load the \`search-conversations\` skill. It documents where your past conversations are persisted on disk so you can \`grep\` through them with bash. Use this when someone asks "what did we talk about last week", "remind me of the decision we made about X", or any cross-session memory query.
|
|
7069
|
+
|
|
7070
|
+
If the user asks "add the GitHub MCP" or "remember that I prefer Python", load the right skill first, then act on the documented file paths with bash/read_file/write_file.
|
|
7071
|
+
|
|
7072
|
+
#### Common shapes
|
|
7073
|
+
- Spawn a worker:
|
|
7074
|
+
\`agent({action:'spawn', name:'count-tests', goal:'Run X and report Y as summary', outputSchema?: { type:'object', properties:{...}, required:[...] }})\`
|
|
7075
|
+
- Answer a worker's question:
|
|
7076
|
+
\`agent({action:'answer_question', id, questionId, answer})\`
|
|
7077
|
+
- Post to Slack thread:
|
|
7078
|
+
\`messenger({action:'post', channel:'slack', to:'C0123', threadTs:'1700000000.001', text:'...'})\`
|
|
7079
|
+
- Schedule a recurring prompt:
|
|
7080
|
+
\`schedule({action:'create', name:'standup-9am', cron:'0 9 * * 1-5', prompt:'Summarize yesterday\\'s git activity in this repo'})\`
|
|
7081
|
+
- Create a webhook:
|
|
7082
|
+
\`webhook({action:'create', name:'github-prs', wake:'now'})\` \u2014 returns the URL.
|
|
7083
|
+
|
|
7084
|
+
### Typical flow
|
|
7085
|
+
|
|
7086
|
+
1. Inbound event arrives (any channel).
|
|
7087
|
+
2. You decompose, \`spawn\` one or more workers with explicit goals.
|
|
7088
|
+
3. Workers run autonomously. They wake you via SYSTEM events when done / failed / blocked.
|
|
7089
|
+
4. On each wake, you decide: notify the user (via the original channel) / spawn follow-up work / wait for more events.
|
|
7090
|
+
`;
|
|
7091
|
+
}
|
|
6756
7092
|
function createSummaryPrompt(conversationHistory) {
|
|
6757
7093
|
return `Please provide a concise summary of the following conversation history. Focus on:
|
|
6758
7094
|
1. The main task or goal being worked on
|
|
@@ -7164,6 +7500,23 @@ ${summaryContent}`
|
|
|
7164
7500
|
}
|
|
7165
7501
|
async addResponseMessages(messages) {
|
|
7166
7502
|
await messageQueries.addMany(this.sessionId, messages);
|
|
7503
|
+
try {
|
|
7504
|
+
const { appendTurn: appendTurn2, flattenContent: flattenContent2 } = await Promise.resolve().then(() => (init_conversation_archive(), conversation_archive_exports));
|
|
7505
|
+
const { sessionQueries: sessionQueries2 } = await Promise.resolve().then(() => (init_db(), db_exports));
|
|
7506
|
+
const session = await sessionQueries2.getById(this.sessionId).catch(() => void 0);
|
|
7507
|
+
for (const m of messages) {
|
|
7508
|
+
if (m.role !== "assistant") continue;
|
|
7509
|
+
const text = flattenContent2(m.content).trim();
|
|
7510
|
+
if (!text) continue;
|
|
7511
|
+
appendTurn2({
|
|
7512
|
+
sessionId: this.sessionId,
|
|
7513
|
+
sessionName: session?.name ?? void 0,
|
|
7514
|
+
role: "assistant",
|
|
7515
|
+
content: text
|
|
7516
|
+
});
|
|
7517
|
+
}
|
|
7518
|
+
} catch {
|
|
7519
|
+
}
|
|
7167
7520
|
}
|
|
7168
7521
|
async getStats() {
|
|
7169
7522
|
const messages = await messageQueries.getModelMessages(this.sessionId);
|
|
@@ -7241,6 +7594,685 @@ function repairToolPairing(messages) {
|
|
|
7241
7594
|
return repaired;
|
|
7242
7595
|
}
|
|
7243
7596
|
|
|
7597
|
+
// src/tools/orchestrator-actions.ts
|
|
7598
|
+
import { tool as tool14 } from "ai";
|
|
7599
|
+
import { z as z15 } from "zod";
|
|
7600
|
+
|
|
7601
|
+
// src/integrations/channels/web.ts
|
|
7602
|
+
var webChannel = {
|
|
7603
|
+
id: "web",
|
|
7604
|
+
canSend: () => true,
|
|
7605
|
+
// outbound goes through the live SSE writer, not via channel.send
|
|
7606
|
+
displayLabel: () => "WEB"
|
|
7607
|
+
};
|
|
7608
|
+
|
|
7609
|
+
// src/integrations/slack/client.ts
|
|
7610
|
+
init_config();
|
|
7611
|
+
function readSlackConfig() {
|
|
7612
|
+
try {
|
|
7613
|
+
const cfg = getConfig();
|
|
7614
|
+
const slack = cfg?.slack;
|
|
7615
|
+
if (!slack?.botToken) return void 0;
|
|
7616
|
+
return {
|
|
7617
|
+
botToken: String(slack.botToken),
|
|
7618
|
+
signingSecret: slack.signingSecret ? String(slack.signingSecret) : void 0,
|
|
7619
|
+
defaultOrchestratorName: slack.defaultOrchestratorName ? String(slack.defaultOrchestratorName) : void 0
|
|
7620
|
+
};
|
|
7621
|
+
} catch {
|
|
7622
|
+
return void 0;
|
|
7623
|
+
}
|
|
7624
|
+
}
|
|
7625
|
+
function getSlackAdapter() {
|
|
7626
|
+
const cfg = readSlackConfig();
|
|
7627
|
+
if (!cfg) return void 0;
|
|
7628
|
+
return {
|
|
7629
|
+
async postMessage({ channel, text, threadTs }) {
|
|
7630
|
+
const res = await fetch("https://slack.com/api/chat.postMessage", {
|
|
7631
|
+
method: "POST",
|
|
7632
|
+
headers: {
|
|
7633
|
+
"Content-Type": "application/json; charset=utf-8",
|
|
7634
|
+
"Authorization": `Bearer ${cfg.botToken}`
|
|
7635
|
+
},
|
|
7636
|
+
body: JSON.stringify({ channel, text, thread_ts: threadTs })
|
|
7637
|
+
});
|
|
7638
|
+
const data = await res.json().catch(() => ({}));
|
|
7639
|
+
if (!res.ok || data?.ok === false) {
|
|
7640
|
+
return { ok: false, error: data?.error || `HTTP ${res.status}` };
|
|
7641
|
+
}
|
|
7642
|
+
return { ok: true, ts: data?.ts };
|
|
7643
|
+
}
|
|
7644
|
+
};
|
|
7645
|
+
}
|
|
7646
|
+
function isSlackConfigured() {
|
|
7647
|
+
return readSlackConfig() !== void 0;
|
|
7648
|
+
}
|
|
7649
|
+
|
|
7650
|
+
// src/integrations/channels/slack.ts
|
|
7651
|
+
var slackChannel = {
|
|
7652
|
+
id: "slack",
|
|
7653
|
+
canSend: () => isSlackConfigured(),
|
|
7654
|
+
async send(ref, text) {
|
|
7655
|
+
const r = ref;
|
|
7656
|
+
const adapter = getSlackAdapter();
|
|
7657
|
+
if (!adapter) throw new Error("slack not configured");
|
|
7658
|
+
const result = await adapter.postMessage({
|
|
7659
|
+
channel: r.slackChannel,
|
|
7660
|
+
text,
|
|
7661
|
+
threadTs: r.threadTs
|
|
7662
|
+
});
|
|
7663
|
+
if (!result.ok) throw new Error(`slack post failed: ${result.error}`);
|
|
7664
|
+
},
|
|
7665
|
+
displayLabel(ref) {
|
|
7666
|
+
const r = ref;
|
|
7667
|
+
const parts = ["SLACK"];
|
|
7668
|
+
if (r.slackChannel) parts.push(`channel=${r.slackChannel}`);
|
|
7669
|
+
if (r.threadTs) parts.push(`thread=${r.threadTs}`);
|
|
7670
|
+
if (r.user) parts.push(`user=${r.user}`);
|
|
7671
|
+
return parts.join(" ");
|
|
7672
|
+
}
|
|
7673
|
+
};
|
|
7674
|
+
|
|
7675
|
+
// src/integrations/channels/system.ts
|
|
7676
|
+
var systemChannel = {
|
|
7677
|
+
id: "system",
|
|
7678
|
+
canSend: () => false,
|
|
7679
|
+
// no outbound; orchestrator decides how to relay
|
|
7680
|
+
displayLabel(ref) {
|
|
7681
|
+
const r = ref;
|
|
7682
|
+
return `SYSTEM ${r.kind} ${r.workerName}`;
|
|
7683
|
+
}
|
|
7684
|
+
};
|
|
7685
|
+
function workerCompletedEvent(workerId, workerName, summary) {
|
|
7686
|
+
const ref = { channel: "system", kind: "worker.completed", workerId, workerName };
|
|
7687
|
+
return {
|
|
7688
|
+
ref,
|
|
7689
|
+
content: `[${systemChannel.displayLabel(ref)}] Worker "${workerName}" (${workerId}) completed. Summary: ${summary}`,
|
|
7690
|
+
wake: "now",
|
|
7691
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
7692
|
+
};
|
|
7693
|
+
}
|
|
7694
|
+
function workerFailedEvent(workerId, workerName, error) {
|
|
7695
|
+
const ref = { channel: "system", kind: "worker.failed", workerId, workerName };
|
|
7696
|
+
return {
|
|
7697
|
+
ref,
|
|
7698
|
+
content: `[${systemChannel.displayLabel(ref)}] Worker "${workerName}" (${workerId}) FAILED. Error: ${error}`,
|
|
7699
|
+
wake: "now",
|
|
7700
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
7701
|
+
};
|
|
7702
|
+
}
|
|
7703
|
+
function workerQuestionEvent(workerId, workerName, question, questionId) {
|
|
7704
|
+
const ref = { channel: "system", kind: "worker.question", workerId, workerName };
|
|
7705
|
+
return {
|
|
7706
|
+
ref,
|
|
7707
|
+
content: `[${systemChannel.displayLabel(ref)}] Worker "${workerName}" (${workerId}) is BLOCKED on a question. Use the agent tool to answer it:
|
|
7708
|
+
Question: ${question}
|
|
7709
|
+
questionId: ${questionId}`,
|
|
7710
|
+
wake: "now",
|
|
7711
|
+
enqueuedAt: /* @__PURE__ */ new Date()
|
|
7712
|
+
};
|
|
7713
|
+
}
|
|
7714
|
+
|
|
7715
|
+
// src/integrations/channels/schedule.ts
|
|
7716
|
+
var scheduleChannel = {
|
|
7717
|
+
id: "schedule",
|
|
7718
|
+
canSend: () => false,
|
|
7719
|
+
displayLabel(ref) {
|
|
7720
|
+
const r = ref;
|
|
7721
|
+
return `SCHEDULE ${r.scheduleName}`;
|
|
7722
|
+
}
|
|
7723
|
+
};
|
|
7724
|
+
|
|
7725
|
+
// src/integrations/channels/webhook.ts
|
|
7726
|
+
var webhookChannel = {
|
|
7727
|
+
id: "webhook",
|
|
7728
|
+
canSend: () => false,
|
|
7729
|
+
displayLabel(ref) {
|
|
7730
|
+
const r = ref;
|
|
7731
|
+
return `WEBHOOK ${r.webhookName}`;
|
|
7732
|
+
}
|
|
7733
|
+
};
|
|
7734
|
+
|
|
7735
|
+
// src/integrations/channels/registry.ts
|
|
7736
|
+
var channels = /* @__PURE__ */ new Map();
|
|
7737
|
+
for (const c of [webChannel, slackChannel, systemChannel, scheduleChannel, webhookChannel]) {
|
|
7738
|
+
channels.set(c.id, c);
|
|
7739
|
+
}
|
|
7740
|
+
function getChannel(id) {
|
|
7741
|
+
return channels.get(id);
|
|
7742
|
+
}
|
|
7743
|
+
function listChannels() {
|
|
7744
|
+
return Array.from(channels.values());
|
|
7745
|
+
}
|
|
7746
|
+
function listOutboundChannels() {
|
|
7747
|
+
return listChannels().filter((c) => c.canSend());
|
|
7748
|
+
}
|
|
7749
|
+
|
|
7750
|
+
// src/integrations/channels/messenger.ts
|
|
7751
|
+
async function postMessage(opts) {
|
|
7752
|
+
const adapter = getChannel(opts.channel);
|
|
7753
|
+
if (!adapter) return { ok: false, error: `unknown channel: ${opts.channel}` };
|
|
7754
|
+
if (!adapter.canSend() || !adapter.send) {
|
|
7755
|
+
return { ok: false, error: `channel ${opts.channel} not configured or does not support sending` };
|
|
7756
|
+
}
|
|
7757
|
+
let ref;
|
|
7758
|
+
switch (opts.channel) {
|
|
7759
|
+
case "slack": {
|
|
7760
|
+
const slackRef = { channel: "slack", slackChannel: opts.to, threadTs: opts.threadTs };
|
|
7761
|
+
ref = slackRef;
|
|
7762
|
+
break;
|
|
7763
|
+
}
|
|
7764
|
+
default:
|
|
7765
|
+
ref = { channel: opts.channel, to: opts.to };
|
|
7766
|
+
}
|
|
7767
|
+
try {
|
|
7768
|
+
await adapter.send(ref, opts.text);
|
|
7769
|
+
return { ok: true };
|
|
7770
|
+
} catch (err) {
|
|
7771
|
+
return { ok: false, error: err?.message || String(err) };
|
|
7772
|
+
}
|
|
7773
|
+
}
|
|
7774
|
+
function describeConfiguredChannels() {
|
|
7775
|
+
return listOutboundChannels().map((c) => ({
|
|
7776
|
+
id: String(c.id),
|
|
7777
|
+
canSend: true,
|
|
7778
|
+
label: c.id === "slack" ? "Slack" : String(c.id)
|
|
7779
|
+
}));
|
|
7780
|
+
}
|
|
7781
|
+
|
|
7782
|
+
// src/orchestrator/schedules-store.ts
|
|
7783
|
+
init_db();
|
|
7784
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
7785
|
+
async function readOrch(orchestratorSessionId) {
|
|
7786
|
+
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
7787
|
+
if (!s) return null;
|
|
7788
|
+
const schedules = s.config?.schedules ?? [];
|
|
7789
|
+
return { schedules, config: s.config ?? {} };
|
|
7790
|
+
}
|
|
7791
|
+
async function listSchedules(orchestratorSessionId) {
|
|
7792
|
+
const data = await readOrch(orchestratorSessionId);
|
|
7793
|
+
return data?.schedules ?? [];
|
|
7794
|
+
}
|
|
7795
|
+
async function createSchedule(orchestratorSessionId, input) {
|
|
7796
|
+
const data = await readOrch(orchestratorSessionId);
|
|
7797
|
+
if (!data) throw new Error("orchestrator session not found");
|
|
7798
|
+
const row = {
|
|
7799
|
+
id: `sch_${nanoid5(10)}`,
|
|
7800
|
+
name: input.name,
|
|
7801
|
+
cron: input.cron,
|
|
7802
|
+
prompt: input.prompt,
|
|
7803
|
+
replyChannel: input.replyChannel,
|
|
7804
|
+
enabled: true,
|
|
7805
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7806
|
+
};
|
|
7807
|
+
const schedules = [...data.schedules, row];
|
|
7808
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, schedules } });
|
|
7809
|
+
return row;
|
|
7810
|
+
}
|
|
7811
|
+
async function updateSchedule(orchestratorSessionId, id, patch) {
|
|
7812
|
+
const data = await readOrch(orchestratorSessionId);
|
|
7813
|
+
if (!data) return null;
|
|
7814
|
+
const schedules = data.schedules.map((s) => s.id === id ? { ...s, ...patch } : s);
|
|
7815
|
+
const updated = schedules.find((s) => s.id === id) || null;
|
|
7816
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, schedules } });
|
|
7817
|
+
return updated;
|
|
7818
|
+
}
|
|
7819
|
+
async function deleteSchedule(orchestratorSessionId, id) {
|
|
7820
|
+
const data = await readOrch(orchestratorSessionId);
|
|
7821
|
+
if (!data) return false;
|
|
7822
|
+
const next = data.schedules.filter((s) => s.id !== id);
|
|
7823
|
+
if (next.length === data.schedules.length) return false;
|
|
7824
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, schedules: next } });
|
|
7825
|
+
return true;
|
|
7826
|
+
}
|
|
7827
|
+
|
|
7828
|
+
// src/orchestrator/webhooks-store.ts
|
|
7829
|
+
init_db();
|
|
7830
|
+
import { randomBytes } from "crypto";
|
|
7831
|
+
import { nanoid as nanoid6 } from "nanoid";
|
|
7832
|
+
function newToken() {
|
|
7833
|
+
return randomBytes(24).toString("base64url");
|
|
7834
|
+
}
|
|
7835
|
+
async function readOrch2(orchestratorSessionId) {
|
|
7836
|
+
const s = await sessionQueries.getById(orchestratorSessionId);
|
|
7837
|
+
if (!s) return null;
|
|
7838
|
+
const webhooks = s.config?.webhooks ?? [];
|
|
7839
|
+
return { webhooks, config: s.config ?? {} };
|
|
7840
|
+
}
|
|
7841
|
+
async function listWebhooks(orchestratorSessionId) {
|
|
7842
|
+
return (await readOrch2(orchestratorSessionId))?.webhooks ?? [];
|
|
7843
|
+
}
|
|
7844
|
+
async function createWebhook(orchestratorSessionId, input) {
|
|
7845
|
+
const data = await readOrch2(orchestratorSessionId);
|
|
7846
|
+
if (!data) throw new Error("orchestrator session not found");
|
|
7847
|
+
const row = {
|
|
7848
|
+
id: `whk_${nanoid6(10)}`,
|
|
7849
|
+
name: input.name,
|
|
7850
|
+
token: newToken(),
|
|
7851
|
+
wake: input.wake ?? "now",
|
|
7852
|
+
template: input.template,
|
|
7853
|
+
hitCount: 0,
|
|
7854
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
7855
|
+
};
|
|
7856
|
+
const webhooks = [...data.webhooks, row];
|
|
7857
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, webhooks } });
|
|
7858
|
+
return row;
|
|
7859
|
+
}
|
|
7860
|
+
async function updateWebhook(orchestratorSessionId, id, patch) {
|
|
7861
|
+
const data = await readOrch2(orchestratorSessionId);
|
|
7862
|
+
if (!data) return null;
|
|
7863
|
+
const webhooks = data.webhooks.map((w) => {
|
|
7864
|
+
if (w.id !== id) return w;
|
|
7865
|
+
const next = { ...w, ...patch };
|
|
7866
|
+
if (patch.rotateToken) next.token = newToken();
|
|
7867
|
+
return next;
|
|
7868
|
+
});
|
|
7869
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, webhooks } });
|
|
7870
|
+
return webhooks.find((w) => w.id === id) ?? null;
|
|
7871
|
+
}
|
|
7872
|
+
async function deleteWebhook(orchestratorSessionId, id) {
|
|
7873
|
+
const data = await readOrch2(orchestratorSessionId);
|
|
7874
|
+
if (!data) return false;
|
|
7875
|
+
const next = data.webhooks.filter((w) => w.id !== id);
|
|
7876
|
+
if (next.length === data.webhooks.length) return false;
|
|
7877
|
+
await sessionQueries.update(orchestratorSessionId, { config: { ...data.config, webhooks: next } });
|
|
7878
|
+
return true;
|
|
7879
|
+
}
|
|
7880
|
+
|
|
7881
|
+
// src/tools/orchestrator-actions.ts
|
|
7882
|
+
async function api2(baseUrl, path, init = {}) {
|
|
7883
|
+
const res = await fetch(`${baseUrl}${path}`, {
|
|
7884
|
+
method: init.method || "GET",
|
|
7885
|
+
headers: { "Content-Type": "application/json" },
|
|
7886
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0
|
|
7887
|
+
});
|
|
7888
|
+
const text = await res.text();
|
|
7889
|
+
let parsed;
|
|
7890
|
+
try {
|
|
7891
|
+
parsed = text ? JSON.parse(text) : {};
|
|
7892
|
+
} catch {
|
|
7893
|
+
parsed = { raw: text };
|
|
7894
|
+
}
|
|
7895
|
+
if (!res.ok) throw new Error(parsed?.error || `HTTP ${res.status}`);
|
|
7896
|
+
return parsed;
|
|
7897
|
+
}
|
|
7898
|
+
function previewMessageContent(content) {
|
|
7899
|
+
if (typeof content === "string") return content.slice(0, 500);
|
|
7900
|
+
if (Array.isArray(content)) {
|
|
7901
|
+
const text = content.find((p) => p?.type === "text");
|
|
7902
|
+
if (text?.text) return String(text.text).slice(0, 500);
|
|
7903
|
+
}
|
|
7904
|
+
return "";
|
|
7905
|
+
}
|
|
7906
|
+
var AGENT_STATUS_ENUM = z15.enum(["running", "needs_attention", "completed", "failed", "idle"]);
|
|
7907
|
+
var agentInputSchema = z15.object({
|
|
7908
|
+
action: z15.enum(["list", "get", "spawn", "message", "answer_question", "stop"]).describe("Which agent operation to perform."),
|
|
7909
|
+
// list
|
|
7910
|
+
status: AGENT_STATUS_ENUM.optional().describe("list only: filter to one status."),
|
|
7911
|
+
limit: z15.number().int().min(1).max(100).optional().describe("list only: max rows."),
|
|
7912
|
+
// get / message / answer_question / stop
|
|
7913
|
+
id: z15.string().optional().describe("get | message | answer_question | stop: the agent (session) id."),
|
|
7914
|
+
recentMessages: z15.number().int().min(0).max(50).optional().describe("get only: how many recent messages to include."),
|
|
7915
|
+
// spawn
|
|
7916
|
+
name: z15.string().optional().describe("spawn only: short human-readable label."),
|
|
7917
|
+
goal: z15.string().optional().describe("spawn only: the worker's self-contained instruction."),
|
|
7918
|
+
outputSchema: z15.record(z15.string(), z15.unknown()).optional().describe(
|
|
7919
|
+
'spawn only: JSON Schema for the worker result. Defaults to {type:"object", properties:{summary:{type:"string"}}, required:["summary"]}.'
|
|
7920
|
+
),
|
|
7921
|
+
model: z15.string().optional().describe("spawn only: model override."),
|
|
7922
|
+
workingDirectory: z15.string().optional().describe("spawn only: working directory override."),
|
|
7923
|
+
maxIterations: z15.number().int().min(1).max(500).optional().describe("spawn only."),
|
|
7924
|
+
// message
|
|
7925
|
+
text: z15.string().optional().describe("message only: the text to deliver to the worker."),
|
|
7926
|
+
force: z15.boolean().optional().describe("message only: soft-interrupt the current step."),
|
|
7927
|
+
// answer_question
|
|
7928
|
+
questionId: z15.string().optional().describe("answer_question only: pending question id (e.g. q_abc123)."),
|
|
7929
|
+
answer: z15.string().optional().describe("answer_question only: your answer.")
|
|
7930
|
+
});
|
|
7931
|
+
function buildAgentTool(opts) {
|
|
7932
|
+
return tool14({
|
|
7933
|
+
description: "Manage worker agents. Actions: list (browse with optional status filter), get (deep dive: status, todos, pending question, recent messages, final result; required: id), spawn (start a new worker; required: name, goal), message (post a message to a running worker; force=true to soft-interrupt; required: id, text), answer_question (resolve a worker's ask_question_to_user prompt; required: id, questionId, answer), stop (hard-cancel a running worker; required: id).",
|
|
7934
|
+
inputSchema: agentInputSchema,
|
|
7935
|
+
execute: async (input) => {
|
|
7936
|
+
switch (input.action) {
|
|
7937
|
+
case "list": {
|
|
7938
|
+
const limit = input.limit ?? 50;
|
|
7939
|
+
const data = await api2(opts.baseUrl, `/sessions?role=worker&limit=${limit}`);
|
|
7940
|
+
const filtered = input.status ? data.sessions.filter((s) => s.agentStatus === input.status) : data.sessions;
|
|
7941
|
+
return {
|
|
7942
|
+
count: filtered.length,
|
|
7943
|
+
agents: filtered.map((s) => ({
|
|
7944
|
+
id: s.id,
|
|
7945
|
+
name: s.name,
|
|
7946
|
+
status: s.agentStatus,
|
|
7947
|
+
isStreaming: s.isStreaming,
|
|
7948
|
+
pendingQuestion: s.pendingQuestion,
|
|
7949
|
+
lastMessagePreview: s.lastMessagePreview,
|
|
7950
|
+
updatedAt: s.updatedAt
|
|
7951
|
+
}))
|
|
7952
|
+
};
|
|
7953
|
+
}
|
|
7954
|
+
case "get": {
|
|
7955
|
+
if (!input.id) return { error: "id required for action=get" };
|
|
7956
|
+
const limit = input.recentMessages ?? 5;
|
|
7957
|
+
const [session, messages] = await Promise.all([
|
|
7958
|
+
api2(opts.baseUrl, `/sessions/${input.id}`),
|
|
7959
|
+
api2(opts.baseUrl, `/sessions/${input.id}/messages?limit=${limit}`)
|
|
7960
|
+
]);
|
|
7961
|
+
const task = session.config?.task ?? null;
|
|
7962
|
+
return {
|
|
7963
|
+
id: session.id,
|
|
7964
|
+
name: session.name,
|
|
7965
|
+
status: session.agentStatus,
|
|
7966
|
+
role: session.role,
|
|
7967
|
+
isStreaming: session.isStreaming,
|
|
7968
|
+
model: session.model,
|
|
7969
|
+
workingDirectory: session.workingDirectory,
|
|
7970
|
+
pendingQuestion: session.pendingQuestion,
|
|
7971
|
+
todos: session.todos,
|
|
7972
|
+
task: task ? { status: task.status, result: task.result, error: task.error, iterations: task.iterations } : null,
|
|
7973
|
+
recentMessages: messages.messages.map((m) => ({ role: m.role, content: previewMessageContent(m.content) })),
|
|
7974
|
+
createdAt: session.createdAt,
|
|
7975
|
+
updatedAt: session.updatedAt
|
|
7976
|
+
};
|
|
7977
|
+
}
|
|
7978
|
+
case "spawn": {
|
|
7979
|
+
if (!input.name || !input.goal) return { error: "name and goal required for action=spawn" };
|
|
7980
|
+
const outputSchema = input.outputSchema ?? {
|
|
7981
|
+
type: "object",
|
|
7982
|
+
properties: { summary: { type: "string", description: "Human-readable summary of what was done." } },
|
|
7983
|
+
required: ["summary"]
|
|
7984
|
+
};
|
|
7985
|
+
const created = await api2(opts.baseUrl, "/tasks", {
|
|
7986
|
+
method: "POST",
|
|
7987
|
+
body: {
|
|
7988
|
+
prompt: input.goal,
|
|
7989
|
+
outputSchema,
|
|
7990
|
+
model: input.model ?? opts.defaultModel,
|
|
7991
|
+
workingDirectory: input.workingDirectory ?? opts.defaultWorkingDirectory,
|
|
7992
|
+
name: input.name,
|
|
7993
|
+
maxIterations: input.maxIterations ?? 100,
|
|
7994
|
+
orchestratorSessionId: opts.orchestratorSessionId
|
|
7995
|
+
}
|
|
7996
|
+
});
|
|
7997
|
+
return {
|
|
7998
|
+
id: created.taskId,
|
|
7999
|
+
name: input.name,
|
|
8000
|
+
status: "running",
|
|
8001
|
+
message: `Spawned "${input.name}" (${created.taskId}). It will wake you with a system event when it completes, fails, or asks a question. No polling needed.`
|
|
8002
|
+
};
|
|
8003
|
+
}
|
|
8004
|
+
case "message": {
|
|
8005
|
+
if (!input.id || !input.text) return { error: "id and text required for action=message" };
|
|
8006
|
+
await api2(opts.baseUrl, `/sessions/${input.id}/messages`, {
|
|
8007
|
+
method: "POST",
|
|
8008
|
+
body: { text: input.text, source: "orchestrator", force: !!input.force }
|
|
8009
|
+
});
|
|
8010
|
+
return {
|
|
8011
|
+
delivered: true,
|
|
8012
|
+
id: input.id,
|
|
8013
|
+
force: !!input.force,
|
|
8014
|
+
note: input.force ? "Will be delivered after the current step aborts." : "Queued; delivered before the worker's next iteration."
|
|
8015
|
+
};
|
|
8016
|
+
}
|
|
8017
|
+
case "answer_question": {
|
|
8018
|
+
if (!input.id || !input.questionId || !input.answer) return { error: "id, questionId, answer required for action=answer_question" };
|
|
8019
|
+
const res = await api2(opts.baseUrl, `/tasks/${input.id}/questions/${input.questionId}/answer`, {
|
|
8020
|
+
method: "POST",
|
|
8021
|
+
body: { answer: input.answer, answeredBy: "orchestrator" }
|
|
8022
|
+
});
|
|
8023
|
+
return { delivered: true, ...res };
|
|
8024
|
+
}
|
|
8025
|
+
case "stop": {
|
|
8026
|
+
if (!input.id) return { error: "id required for action=stop" };
|
|
8027
|
+
const res = await api2(opts.baseUrl, `/tasks/${input.id}/cancel`, { method: "POST" });
|
|
8028
|
+
return { stopped: true, ...res };
|
|
8029
|
+
}
|
|
8030
|
+
}
|
|
8031
|
+
}
|
|
8032
|
+
});
|
|
8033
|
+
}
|
|
8034
|
+
var messengerInputSchema = z15.object({
|
|
8035
|
+
action: z15.enum(["list_channels", "post"]),
|
|
8036
|
+
// post
|
|
8037
|
+
channel: z15.string().optional().describe('post only: channel id (e.g. "slack").'),
|
|
8038
|
+
to: z15.string().optional().describe('post only: destination. Slack: channel id (C0123), user id (U0123), or "#channel-name".'),
|
|
8039
|
+
text: z15.string().optional().describe("post only: message body."),
|
|
8040
|
+
threadTs: z15.string().optional().describe("post + slack: reply in this thread."),
|
|
8041
|
+
subject: z15.string().optional().describe("post + email: subject (future).")
|
|
8042
|
+
});
|
|
8043
|
+
function buildMessengerTool() {
|
|
8044
|
+
return tool14({
|
|
8045
|
+
description: "Send messages on configured external channels. Actions: list_channels (no args; returns which integrations are configured), post (required: channel, to, text). Use this to ping the user on Slack when a worker finishes, when a schedule fires, etc.",
|
|
8046
|
+
inputSchema: messengerInputSchema,
|
|
8047
|
+
execute: async (input) => {
|
|
8048
|
+
if (input.action === "list_channels") {
|
|
8049
|
+
return { channels: describeConfiguredChannels() };
|
|
8050
|
+
}
|
|
8051
|
+
if (!input.channel || !input.to || !input.text) {
|
|
8052
|
+
return { ok: false, error: "channel, to, text required for action=post" };
|
|
8053
|
+
}
|
|
8054
|
+
const result = await postMessage({
|
|
8055
|
+
channel: input.channel,
|
|
8056
|
+
to: input.to,
|
|
8057
|
+
text: input.text,
|
|
8058
|
+
threadTs: input.threadTs,
|
|
8059
|
+
subject: input.subject
|
|
8060
|
+
});
|
|
8061
|
+
return result;
|
|
8062
|
+
}
|
|
8063
|
+
});
|
|
8064
|
+
}
|
|
8065
|
+
var scheduleInputSchema = z15.object({
|
|
8066
|
+
action: z15.enum(["create", "list", "update", "delete", "pause", "resume"]),
|
|
8067
|
+
// create / update
|
|
8068
|
+
name: z15.string().optional().describe("create | update"),
|
|
8069
|
+
cron: z15.string().optional().describe('create | update: 5-field cron (e.g. "0 9 * * 1-5" = weekdays at 9am).'),
|
|
8070
|
+
prompt: z15.string().optional().describe("create | update: the prompt injected when the schedule fires."),
|
|
8071
|
+
replyChannel: z15.string().optional().describe("create | update: default channel id for orchestrator replies."),
|
|
8072
|
+
// update / delete / pause / resume
|
|
8073
|
+
id: z15.string().optional().describe("update | delete | pause | resume: schedule id."),
|
|
8074
|
+
enabled: z15.boolean().optional().describe("update only.")
|
|
8075
|
+
});
|
|
8076
|
+
function buildScheduleTool(opts) {
|
|
8077
|
+
return tool14({
|
|
8078
|
+
description: "Recurring prompts. Actions: create (required: name, cron, prompt), list, update (required: id; any of name/cron/prompt/enabled/replyChannel), delete (required: id), pause (required: id), resume (required: id). Cron is standard 5-field syntax.",
|
|
8079
|
+
inputSchema: scheduleInputSchema,
|
|
8080
|
+
execute: async (input) => {
|
|
8081
|
+
const orcId = opts.orchestratorSessionId;
|
|
8082
|
+
switch (input.action) {
|
|
8083
|
+
case "create": {
|
|
8084
|
+
if (!input.name || !input.cron || !input.prompt) return { error: "name, cron, prompt required" };
|
|
8085
|
+
return await createSchedule(orcId, {
|
|
8086
|
+
name: input.name,
|
|
8087
|
+
cron: input.cron,
|
|
8088
|
+
prompt: input.prompt,
|
|
8089
|
+
replyChannel: input.replyChannel
|
|
8090
|
+
});
|
|
8091
|
+
}
|
|
8092
|
+
case "list":
|
|
8093
|
+
return { schedules: await listSchedules(orcId) };
|
|
8094
|
+
case "update": {
|
|
8095
|
+
if (!input.id) return { error: "id required" };
|
|
8096
|
+
const { action: _a, id, ...patch } = input;
|
|
8097
|
+
const row = await updateSchedule(orcId, id, patch);
|
|
8098
|
+
return row ? row : { error: "not found" };
|
|
8099
|
+
}
|
|
8100
|
+
case "delete":
|
|
8101
|
+
if (!input.id) return { error: "id required" };
|
|
8102
|
+
return { deleted: await deleteSchedule(orcId, input.id) };
|
|
8103
|
+
case "pause":
|
|
8104
|
+
if (!input.id) return { error: "id required" };
|
|
8105
|
+
return await updateSchedule(orcId, input.id, { enabled: false });
|
|
8106
|
+
case "resume":
|
|
8107
|
+
if (!input.id) return { error: "id required" };
|
|
8108
|
+
return await updateSchedule(orcId, input.id, { enabled: true });
|
|
8109
|
+
}
|
|
8110
|
+
}
|
|
8111
|
+
});
|
|
8112
|
+
}
|
|
8113
|
+
var webhookInputSchema = z15.object({
|
|
8114
|
+
action: z15.enum(["create", "list", "update", "delete"]),
|
|
8115
|
+
name: z15.string().optional().describe("create | update."),
|
|
8116
|
+
wake: z15.enum(["now", "next"]).optional().describe("create | update: now = wake orchestrator immediately; next = add as context."),
|
|
8117
|
+
template: z15.string().optional().describe("create | update: mustache-style template ({{path.to.field}}). Defaults to pretty-printed JSON."),
|
|
8118
|
+
id: z15.string().optional().describe("update | delete: webhook id."),
|
|
8119
|
+
rotateToken: z15.boolean().optional().describe("update only: regenerate the URL token.")
|
|
8120
|
+
});
|
|
8121
|
+
function buildWebhookUrl(opts, token) {
|
|
8122
|
+
const base = opts.publicBaseUrl?.replace(/\/$/, "") || opts.baseUrl.replace(/\/$/, "");
|
|
8123
|
+
return `${base}/api/inbox/${token}`;
|
|
8124
|
+
}
|
|
8125
|
+
function buildWebhookTool(opts) {
|
|
8126
|
+
return tool14({
|
|
8127
|
+
description: "Custom token-protected inbound URLs. Anyone POSTing JSON to the URL pings the orchestrator. Actions: create (required: name), list, update (required: id; optional: name, wake, template, rotateToken), delete (required: id). Use these to wire up GitHub, IFTTT, n8n, etc.",
|
|
8128
|
+
inputSchema: webhookInputSchema,
|
|
8129
|
+
execute: async (input) => {
|
|
8130
|
+
const orcId = opts.orchestratorSessionId;
|
|
8131
|
+
switch (input.action) {
|
|
8132
|
+
case "create": {
|
|
8133
|
+
if (!input.name) return { error: "name required" };
|
|
8134
|
+
const row = await createWebhook(orcId, {
|
|
8135
|
+
name: input.name,
|
|
8136
|
+
wake: input.wake,
|
|
8137
|
+
template: input.template
|
|
8138
|
+
});
|
|
8139
|
+
return { ...row, url: buildWebhookUrl(opts, row.token) };
|
|
8140
|
+
}
|
|
8141
|
+
case "list": {
|
|
8142
|
+
const rows = await listWebhooks(orcId);
|
|
8143
|
+
return { webhooks: rows.map((r) => ({ ...r, url: buildWebhookUrl(opts, r.token) })) };
|
|
8144
|
+
}
|
|
8145
|
+
case "update": {
|
|
8146
|
+
if (!input.id) return { error: "id required" };
|
|
8147
|
+
const { action: _a, id, ...patch } = input;
|
|
8148
|
+
const row = await updateWebhook(orcId, id, patch);
|
|
8149
|
+
if (!row) return { error: "not found" };
|
|
8150
|
+
return { ...row, url: buildWebhookUrl(opts, row.token) };
|
|
8151
|
+
}
|
|
8152
|
+
case "delete":
|
|
8153
|
+
if (!input.id) return { error: "id required" };
|
|
8154
|
+
return { deleted: await deleteWebhook(orcId, input.id) };
|
|
8155
|
+
}
|
|
8156
|
+
}
|
|
8157
|
+
});
|
|
8158
|
+
}
|
|
8159
|
+
function createOrchestratorActionTools(opts) {
|
|
8160
|
+
return {
|
|
8161
|
+
agent: buildAgentTool(opts),
|
|
8162
|
+
messenger: buildMessengerTool(),
|
|
8163
|
+
schedule: buildScheduleTool(opts),
|
|
8164
|
+
webhook: buildWebhookTool(opts)
|
|
8165
|
+
};
|
|
8166
|
+
}
|
|
8167
|
+
|
|
8168
|
+
// src/integrations/mcp/pool.ts
|
|
8169
|
+
import { createMCPClient } from "@ai-sdk/mcp";
|
|
8170
|
+
|
|
8171
|
+
// src/integrations/mcp/store.ts
|
|
8172
|
+
init_config();
|
|
8173
|
+
import { nanoid as nanoid7 } from "nanoid";
|
|
8174
|
+
import { existsSync as existsSync17, readFileSync as readFileSync8 } from "fs";
|
|
8175
|
+
import { resolve as resolve10, join as join10 } from "path";
|
|
8176
|
+
function readServers() {
|
|
8177
|
+
try {
|
|
8178
|
+
const cfg = getConfig();
|
|
8179
|
+
return Array.isArray(cfg?.mcp?.servers) ? cfg.mcp.servers : [];
|
|
8180
|
+
} catch {
|
|
8181
|
+
return [];
|
|
8182
|
+
}
|
|
8183
|
+
}
|
|
8184
|
+
function refreshMcpServersFromDisk() {
|
|
8185
|
+
const candidates = [
|
|
8186
|
+
resolve10(process.cwd(), "sparkecoder.config.json"),
|
|
8187
|
+
join10(ensureAppDataDirectory(), "sparkecoder.config.json")
|
|
8188
|
+
];
|
|
8189
|
+
for (const path of candidates) {
|
|
8190
|
+
if (!existsSync17(path)) continue;
|
|
8191
|
+
try {
|
|
8192
|
+
const raw = JSON.parse(readFileSync8(path, "utf-8"));
|
|
8193
|
+
const servers2 = Array.isArray(raw?.mcp?.servers) ? raw.mcp.servers : [];
|
|
8194
|
+
setMcpServers(servers2);
|
|
8195
|
+
return servers2;
|
|
8196
|
+
} catch {
|
|
8197
|
+
}
|
|
8198
|
+
}
|
|
8199
|
+
return readServers();
|
|
8200
|
+
}
|
|
8201
|
+
function listMcpServers() {
|
|
8202
|
+
return readServers();
|
|
8203
|
+
}
|
|
8204
|
+
|
|
8205
|
+
// src/integrations/mcp/pool.ts
|
|
8206
|
+
var clients = /* @__PURE__ */ new Map();
|
|
8207
|
+
var failureCount = /* @__PURE__ */ new Map();
|
|
8208
|
+
function buildTransportConfig(server) {
|
|
8209
|
+
if (server.transport === "http") {
|
|
8210
|
+
return {
|
|
8211
|
+
type: "http",
|
|
8212
|
+
url: server.url,
|
|
8213
|
+
headers: server.headers
|
|
8214
|
+
};
|
|
8215
|
+
}
|
|
8216
|
+
if (server.transport === "sse") {
|
|
8217
|
+
return {
|
|
8218
|
+
type: "sse",
|
|
8219
|
+
url: server.url,
|
|
8220
|
+
headers: server.headers
|
|
8221
|
+
};
|
|
8222
|
+
}
|
|
8223
|
+
throw new Error("stdio transport is wired separately (see getOrCreate)");
|
|
8224
|
+
}
|
|
8225
|
+
async function buildStdioTransport(server) {
|
|
8226
|
+
const mod = await import("@ai-sdk/mcp/mcp-stdio");
|
|
8227
|
+
const Cls = mod.Experimental_StdioMCPTransport || mod.StdioClientTransport;
|
|
8228
|
+
if (!Cls) throw new Error("@ai-sdk/mcp/mcp-stdio is missing the stdio transport class");
|
|
8229
|
+
return new Cls({ command: server.command, args: server.args ?? [] });
|
|
8230
|
+
}
|
|
8231
|
+
async function getOrCreate(server) {
|
|
8232
|
+
const existing = clients.get(server.id);
|
|
8233
|
+
if (existing) return existing;
|
|
8234
|
+
const transport = server.transport === "stdio" ? await buildStdioTransport(server) : buildTransportConfig(server);
|
|
8235
|
+
const client = await createMCPClient({ transport });
|
|
8236
|
+
clients.set(server.id, client);
|
|
8237
|
+
failureCount.delete(server.id);
|
|
8238
|
+
return client;
|
|
8239
|
+
}
|
|
8240
|
+
async function closeClient(serverId) {
|
|
8241
|
+
const c = clients.get(serverId);
|
|
8242
|
+
if (!c) return;
|
|
8243
|
+
clients.delete(serverId);
|
|
8244
|
+
try {
|
|
8245
|
+
await c.close();
|
|
8246
|
+
} catch {
|
|
8247
|
+
}
|
|
8248
|
+
}
|
|
8249
|
+
async function loadAllMcpTools(opts = {}) {
|
|
8250
|
+
const tools = {};
|
|
8251
|
+
const previous = new Set(listMcpServers().map((s) => s.id));
|
|
8252
|
+
const current = refreshMcpServersFromDisk();
|
|
8253
|
+
const currentIds = new Set(current.map((s) => s.id));
|
|
8254
|
+
for (const oldId of previous) {
|
|
8255
|
+
if (!currentIds.has(oldId)) await closeClient(oldId);
|
|
8256
|
+
}
|
|
8257
|
+
for (const s of current.filter((x) => x.enabled)) {
|
|
8258
|
+
try {
|
|
8259
|
+
const client = await getOrCreate(s);
|
|
8260
|
+
const serverTools = await client.tools();
|
|
8261
|
+
for (const [name, t] of Object.entries(serverTools)) {
|
|
8262
|
+
const key2 = `mcp_${s.name}_${name}`;
|
|
8263
|
+
tools[key2] = t;
|
|
8264
|
+
}
|
|
8265
|
+
} catch (err) {
|
|
8266
|
+
failureCount.set(s.id, (failureCount.get(s.id) ?? 0) + 1);
|
|
8267
|
+
if (!opts.quiet) {
|
|
8268
|
+
console.warn(`[mcp] loading tools from "${s.name}" (${s.id}) failed: ${err?.message || err}`);
|
|
8269
|
+
}
|
|
8270
|
+
await closeClient(s.id);
|
|
8271
|
+
}
|
|
8272
|
+
}
|
|
8273
|
+
return tools;
|
|
8274
|
+
}
|
|
8275
|
+
|
|
7244
8276
|
// src/utils/webhook.ts
|
|
7245
8277
|
var TERMINAL_EVENTS = /* @__PURE__ */ new Set([
|
|
7246
8278
|
"task.started",
|
|
@@ -7294,19 +8326,102 @@ function waitForTaskQuestionAnswer(question) {
|
|
|
7294
8326
|
if (pendingQuestions.has(k)) {
|
|
7295
8327
|
return Promise.reject(new Error(`Question already pending: ${question.questionId}`));
|
|
7296
8328
|
}
|
|
7297
|
-
return new Promise((
|
|
8329
|
+
return new Promise((resolve11, reject) => {
|
|
7298
8330
|
pendingQuestions.set(k, {
|
|
7299
8331
|
...question,
|
|
7300
8332
|
createdAt: /* @__PURE__ */ new Date(),
|
|
7301
|
-
resolve:
|
|
8333
|
+
resolve: resolve11,
|
|
7302
8334
|
reject
|
|
7303
8335
|
});
|
|
7304
8336
|
});
|
|
7305
8337
|
}
|
|
7306
8338
|
|
|
8339
|
+
// src/tasks/pending-input.ts
|
|
8340
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
8341
|
+
function entry(sessionId) {
|
|
8342
|
+
let e = sessions.get(sessionId);
|
|
8343
|
+
if (!e) {
|
|
8344
|
+
e = { queue: [] };
|
|
8345
|
+
sessions.set(sessionId, e);
|
|
8346
|
+
}
|
|
8347
|
+
return e;
|
|
8348
|
+
}
|
|
8349
|
+
function drainInputs(sessionId) {
|
|
8350
|
+
const e = sessions.get(sessionId);
|
|
8351
|
+
if (!e || e.queue.length === 0) return [];
|
|
8352
|
+
const out = e.queue.slice();
|
|
8353
|
+
e.queue.length = 0;
|
|
8354
|
+
return out;
|
|
8355
|
+
}
|
|
8356
|
+
function registerInterruptController(sessionId, controller) {
|
|
8357
|
+
entry(sessionId).interruptController = controller;
|
|
8358
|
+
}
|
|
8359
|
+
function clearInterruptController(sessionId) {
|
|
8360
|
+
const e = sessions.get(sessionId);
|
|
8361
|
+
if (e) e.interruptController = void 0;
|
|
8362
|
+
}
|
|
8363
|
+
|
|
8364
|
+
// src/orchestrator/inbox.ts
|
|
8365
|
+
var inboxes = /* @__PURE__ */ new Map();
|
|
8366
|
+
var FLUSH_DEBOUNCE_MS = 200;
|
|
8367
|
+
var flushHandler = null;
|
|
8368
|
+
function entryFor(sessionId) {
|
|
8369
|
+
let e = inboxes.get(sessionId);
|
|
8370
|
+
if (!e) {
|
|
8371
|
+
e = { pending: [] };
|
|
8372
|
+
inboxes.set(sessionId, e);
|
|
8373
|
+
}
|
|
8374
|
+
return e;
|
|
8375
|
+
}
|
|
8376
|
+
function pushToInbox(orchestratorSessionId, event) {
|
|
8377
|
+
const e = entryFor(orchestratorSessionId);
|
|
8378
|
+
e.pending.push(event);
|
|
8379
|
+
if (event.wake === "now") {
|
|
8380
|
+
scheduleFlush(orchestratorSessionId);
|
|
8381
|
+
}
|
|
8382
|
+
}
|
|
8383
|
+
function scheduleFlush(sessionId) {
|
|
8384
|
+
const e = inboxes.get(sessionId);
|
|
8385
|
+
if (!e) return;
|
|
8386
|
+
if (e.timer) clearTimeout(e.timer);
|
|
8387
|
+
e.timer = setTimeout(() => {
|
|
8388
|
+
void flush(sessionId);
|
|
8389
|
+
}, FLUSH_DEBOUNCE_MS);
|
|
8390
|
+
}
|
|
8391
|
+
async function flush(sessionId) {
|
|
8392
|
+
const e = inboxes.get(sessionId);
|
|
8393
|
+
if (!e) return;
|
|
8394
|
+
if (e.timer) {
|
|
8395
|
+
clearTimeout(e.timer);
|
|
8396
|
+
e.timer = void 0;
|
|
8397
|
+
}
|
|
8398
|
+
const events = e.pending.splice(0);
|
|
8399
|
+
if (events.length === 0) return;
|
|
8400
|
+
if (!flushHandler) {
|
|
8401
|
+
console.warn("[orchestrator-inbox] flush called with no handler installed; dropping events");
|
|
8402
|
+
return;
|
|
8403
|
+
}
|
|
8404
|
+
try {
|
|
8405
|
+
await flushHandler(sessionId, events);
|
|
8406
|
+
} catch (err) {
|
|
8407
|
+
console.error("[orchestrator-inbox] flush handler threw:", err?.message || err);
|
|
8408
|
+
}
|
|
8409
|
+
}
|
|
8410
|
+
|
|
7307
8411
|
// src/agent/index.ts
|
|
7308
8412
|
var MAX_SSE_FIELD_LENGTH = 8 * 1024;
|
|
7309
8413
|
var SSE_PREVIEW_LENGTH = 2 * 1024;
|
|
8414
|
+
function anySignal(signals) {
|
|
8415
|
+
const ctrl = new AbortController();
|
|
8416
|
+
for (const s of signals) {
|
|
8417
|
+
if (s.aborted) {
|
|
8418
|
+
ctrl.abort();
|
|
8419
|
+
return ctrl.signal;
|
|
8420
|
+
}
|
|
8421
|
+
s.addEventListener("abort", () => ctrl.abort(), { once: true });
|
|
8422
|
+
}
|
|
8423
|
+
return ctrl.signal;
|
|
8424
|
+
}
|
|
7310
8425
|
function truncateWriteFileInput(input) {
|
|
7311
8426
|
const out = { ...input };
|
|
7312
8427
|
for (const key2 of ["content", "old_string", "new_string"]) {
|
|
@@ -7344,7 +8459,7 @@ var Agent = class _Agent {
|
|
|
7344
8459
|
async createToolsWithCallbacks(options) {
|
|
7345
8460
|
const config = getConfig();
|
|
7346
8461
|
const sessionConfig = this.session.config || {};
|
|
7347
|
-
|
|
8462
|
+
const tools = await createTools({
|
|
7348
8463
|
sessionId: this.session.id,
|
|
7349
8464
|
workingDirectory: this.session.workingDirectory,
|
|
7350
8465
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
@@ -7355,6 +8470,23 @@ var Agent = class _Agent {
|
|
|
7355
8470
|
onWriteFileProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "write_file", data: progress }) : void 0,
|
|
7356
8471
|
onSearchProgress: options.onToolProgress ? (progress) => options.onToolProgress({ toolName: "explore_agent", data: progress }) : void 0
|
|
7357
8472
|
});
|
|
8473
|
+
if (this.session.config?.role === "orchestrator") {
|
|
8474
|
+
const orchTools = createOrchestratorActionTools({
|
|
8475
|
+
baseUrl: `http://127.0.0.1:${config.server?.port ?? 3141}`,
|
|
8476
|
+
orchestratorSessionId: this.session.id,
|
|
8477
|
+
defaultWorkingDirectory: this.session.workingDirectory,
|
|
8478
|
+
defaultModel: this.session.model || config.defaultModel,
|
|
8479
|
+
publicBaseUrl: config.server?.publicUrl
|
|
8480
|
+
});
|
|
8481
|
+
for (const [name, t] of Object.entries(orchTools)) {
|
|
8482
|
+
tools[name] = t;
|
|
8483
|
+
}
|
|
8484
|
+
}
|
|
8485
|
+
const mcpTools = await loadAllMcpTools({ quiet: true });
|
|
8486
|
+
for (const [name, t] of Object.entries(mcpTools)) {
|
|
8487
|
+
tools[name] = t;
|
|
8488
|
+
}
|
|
8489
|
+
return tools;
|
|
7358
8490
|
}
|
|
7359
8491
|
/**
|
|
7360
8492
|
* Create or resume an agent session
|
|
@@ -7392,6 +8524,23 @@ var Agent = class _Agent {
|
|
|
7392
8524
|
computerUseDisplayWidth: sessionConfig.computerUseDisplayWidth,
|
|
7393
8525
|
computerUseDisplayHeight: sessionConfig.computerUseDisplayHeight
|
|
7394
8526
|
});
|
|
8527
|
+
if (session.config?.role === "orchestrator") {
|
|
8528
|
+
const baseUrl = `http://127.0.0.1:${config.server?.port ?? 3141}`;
|
|
8529
|
+
const orchTools = createOrchestratorActionTools({
|
|
8530
|
+
baseUrl,
|
|
8531
|
+
orchestratorSessionId: session.id,
|
|
8532
|
+
defaultWorkingDirectory: session.workingDirectory,
|
|
8533
|
+
defaultModel: session.model || config.defaultModel,
|
|
8534
|
+
publicBaseUrl: config.server?.publicUrl
|
|
8535
|
+
});
|
|
8536
|
+
for (const [name, t] of Object.entries(orchTools)) {
|
|
8537
|
+
tools[name] = t;
|
|
8538
|
+
}
|
|
8539
|
+
}
|
|
8540
|
+
const mcpTools = await loadAllMcpTools({ quiet: true });
|
|
8541
|
+
for (const [name, t] of Object.entries(mcpTools)) {
|
|
8542
|
+
tools[name] = t;
|
|
8543
|
+
}
|
|
7395
8544
|
return new _Agent(session, context, tools);
|
|
7396
8545
|
}
|
|
7397
8546
|
/**
|
|
@@ -7463,7 +8612,7 @@ ${prompt}` });
|
|
|
7463
8612
|
this.context.addUserMessage(userContent);
|
|
7464
8613
|
}
|
|
7465
8614
|
await sessionQueries.updateStatus(this.session.id, "active");
|
|
7466
|
-
|
|
8615
|
+
let systemPrompt = await buildSystemPrompt({
|
|
7467
8616
|
workingDirectory: this.session.workingDirectory,
|
|
7468
8617
|
skillsDirectories: config.resolvedSkillsDirectories,
|
|
7469
8618
|
sessionId: this.session.id,
|
|
@@ -7471,6 +8620,19 @@ ${prompt}` });
|
|
|
7471
8620
|
// TODO: Pass activeFiles from client for glob matching
|
|
7472
8621
|
activeFiles: []
|
|
7473
8622
|
});
|
|
8623
|
+
if (this.session.config?.role === "orchestrator") {
|
|
8624
|
+
systemPrompt = `${systemPrompt}
|
|
8625
|
+
|
|
8626
|
+
${buildOrchestratorPromptAddendum()}`;
|
|
8627
|
+
const personality = this.session.config.personality;
|
|
8628
|
+
if (personality && personality.trim()) {
|
|
8629
|
+
systemPrompt = `${systemPrompt}
|
|
8630
|
+
|
|
8631
|
+
## Your personality / persona
|
|
8632
|
+
|
|
8633
|
+
${personality.trim()}`;
|
|
8634
|
+
}
|
|
8635
|
+
}
|
|
7474
8636
|
const messages = await this.context.getMessages();
|
|
7475
8637
|
const tools = options.onToolProgress ? await this.createToolsWithCallbacks({ onToolProgress: options.onToolProgress }) : this.baseTools;
|
|
7476
8638
|
const wrappedTools = this.wrapToolsWithApproval(options, tools);
|
|
@@ -7639,6 +8801,15 @@ ${prompt}` });
|
|
|
7639
8801
|
if (emit) {
|
|
7640
8802
|
await emit(JSON.stringify({ type: "task-question", data: payload }));
|
|
7641
8803
|
}
|
|
8804
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
8805
|
+
if (orchId) {
|
|
8806
|
+
pushToInbox(orchId, workerQuestionEvent(
|
|
8807
|
+
this.session.id,
|
|
8808
|
+
this.session.name || "worker",
|
|
8809
|
+
question.question,
|
|
8810
|
+
question.questionId
|
|
8811
|
+
));
|
|
8812
|
+
}
|
|
7642
8813
|
const answer = await answerPromise;
|
|
7643
8814
|
const answeredPayload = {
|
|
7644
8815
|
questionId: question.questionId,
|
|
@@ -7675,8 +8846,22 @@ ${taskAddendum}`;
|
|
|
7675
8846
|
if (options.abortSignal?.aborted) {
|
|
7676
8847
|
const cancelError = "Task was cancelled";
|
|
7677
8848
|
fireWebhook("task.failed", { status: "failed", error: cancelError, iterations: iteration });
|
|
8849
|
+
clearInterruptController(this.session.id);
|
|
7678
8850
|
return { status: "failed", error: cancelError, iterations: iteration };
|
|
7679
8851
|
}
|
|
8852
|
+
const pending = drainInputs(this.session.id);
|
|
8853
|
+
for (const p of pending) {
|
|
8854
|
+
const labelled = p.source === "orchestrator" ? `[message from orchestrator]
|
|
8855
|
+
${p.text}` : p.source === "system" ? `[system note]
|
|
8856
|
+
${p.text}` : p.text;
|
|
8857
|
+
if (emit) {
|
|
8858
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: labelled } }));
|
|
8859
|
+
}
|
|
8860
|
+
await this.context.addUserMessage(labelled);
|
|
8861
|
+
}
|
|
8862
|
+
const interruptController = new AbortController();
|
|
8863
|
+
registerInterruptController(this.session.id, interruptController);
|
|
8864
|
+
const combinedAbort = options.abortSignal ? anySignal([options.abortSignal, interruptController.signal]) : interruptController.signal;
|
|
7680
8865
|
const messages = await this.context.getMessages();
|
|
7681
8866
|
const useAnthropic = isAnthropicModel(this.session.model);
|
|
7682
8867
|
if (emit) {
|
|
@@ -7693,7 +8878,7 @@ ${taskAddendum}`;
|
|
|
7693
8878
|
messages,
|
|
7694
8879
|
tools: taskTools,
|
|
7695
8880
|
stopWhen: stepCountIs2(500),
|
|
7696
|
-
abortSignal:
|
|
8881
|
+
abortSignal: combinedAbort,
|
|
7697
8882
|
providerOptions: useAnthropic ? {
|
|
7698
8883
|
anthropic: getAnthropicProviderOptions(this.session.model, { toolStreaming: true })
|
|
7699
8884
|
} : void 0,
|
|
@@ -7771,6 +8956,8 @@ ${taskAddendum}`;
|
|
|
7771
8956
|
if (emit && reasoningStarted) {
|
|
7772
8957
|
await emit(JSON.stringify({ type: "reasoning-end", id: reasoningId }));
|
|
7773
8958
|
}
|
|
8959
|
+
const interrupted = interruptController.signal.aborted;
|
|
8960
|
+
clearInterruptController(this.session.id);
|
|
7774
8961
|
const iterResponse = await iterStream.response;
|
|
7775
8962
|
const responseMessages = iterResponse.messages;
|
|
7776
8963
|
await this.context.addResponseMessages(responseMessages);
|
|
@@ -7826,6 +9013,11 @@ ${taskAddendum}`;
|
|
|
7826
9013
|
await sessionQueries.update(this.session.id, {
|
|
7827
9014
|
config: { ...this.session.config, task: updatedTask2 }
|
|
7828
9015
|
});
|
|
9016
|
+
const orchId = this.session.config?.orchestratorSessionId;
|
|
9017
|
+
if (orchId) {
|
|
9018
|
+
const summary = finalStatus === "completed" ? typeof sig.result?.summary === "string" ? sig.result.summary : JSON.stringify(sig.result) : sig.error || "unknown error";
|
|
9019
|
+
pushToInbox(orchId, finalStatus === "completed" ? workerCompletedEvent(this.session.id, this.session.name || "worker", summary) : workerFailedEvent(this.session.id, this.session.name || "worker", summary));
|
|
9020
|
+
}
|
|
7829
9021
|
return {
|
|
7830
9022
|
status: finalStatus,
|
|
7831
9023
|
result: sig.result,
|
|
@@ -7833,12 +9025,15 @@ ${taskAddendum}`;
|
|
|
7833
9025
|
iterations: iteration
|
|
7834
9026
|
};
|
|
7835
9027
|
}
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
|
|
9028
|
+
if (!interrupted) {
|
|
9029
|
+
const continuationPrompt = "Continue working on the task. Before calling `complete_task`, VERIFY your work is correct \u2014 re-read edited files, run the linter, run tests if applicable, and check the browser/server if you made UI or API changes. Make sure you searched the right directories and found everything relevant. When fully verified, call `complete_task` with the result. If you cannot complete it, call `task_failed` with a reason.";
|
|
9030
|
+
if (emit) {
|
|
9031
|
+
await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
|
|
9032
|
+
}
|
|
9033
|
+
await this.context.addUserMessage(continuationPrompt);
|
|
7839
9034
|
}
|
|
7840
|
-
await this.context.addUserMessage(continuationPrompt);
|
|
7841
9035
|
}
|
|
9036
|
+
clearInterruptController(this.session.id);
|
|
7842
9037
|
const timeoutError = `Task did not complete within ${maxIterations} iterations`;
|
|
7843
9038
|
const timeoutRecordingUrls = await this.finishTaskRecording(taskRecorder);
|
|
7844
9039
|
fireWebhook("task.failed", {
|
|
@@ -7856,6 +9051,10 @@ ${taskAddendum}`;
|
|
|
7856
9051
|
await sessionQueries.update(this.session.id, {
|
|
7857
9052
|
config: { ...this.session.config, task: updatedTask }
|
|
7858
9053
|
});
|
|
9054
|
+
const orchIdTimeout = this.session.config?.orchestratorSessionId;
|
|
9055
|
+
if (orchIdTimeout) {
|
|
9056
|
+
pushToInbox(orchIdTimeout, workerFailedEvent(this.session.id, this.session.name || "worker", timeoutError));
|
|
9057
|
+
}
|
|
7859
9058
|
return { status: "failed", error: timeoutError, iterations: iteration };
|
|
7860
9059
|
}
|
|
7861
9060
|
/**
|
|
@@ -7916,11 +9115,11 @@ ${taskAddendum}`;
|
|
|
7916
9115
|
const { isRemoteConfigured: isRemoteConfigured2, storageQueries: storageQueries2 } = await Promise.resolve().then(() => (init_remote(), remote_exports));
|
|
7917
9116
|
if (!isRemoteConfigured2()) return [];
|
|
7918
9117
|
const { readFile: readFile12 } = await import("fs/promises");
|
|
7919
|
-
const { join:
|
|
9118
|
+
const { join: join12, basename: basename5 } = await import("path");
|
|
7920
9119
|
const urls = [];
|
|
7921
9120
|
for (const filePath of filePaths) {
|
|
7922
9121
|
try {
|
|
7923
|
-
const fullPath = filePath.startsWith("/") ? filePath :
|
|
9122
|
+
const fullPath = filePath.startsWith("/") ? filePath : join12(this.session.workingDirectory, filePath);
|
|
7924
9123
|
const fileName = basename5(fullPath);
|
|
7925
9124
|
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
7926
9125
|
const mimeMap = {
|
|
@@ -7978,11 +9177,11 @@ ${taskAddendum}`;
|
|
|
7978
9177
|
wrappedTools[name] = originalTool;
|
|
7979
9178
|
continue;
|
|
7980
9179
|
}
|
|
7981
|
-
wrappedTools[name] =
|
|
9180
|
+
wrappedTools[name] = tool15({
|
|
7982
9181
|
description: originalTool.description || "",
|
|
7983
|
-
inputSchema: originalTool.inputSchema ||
|
|
9182
|
+
inputSchema: originalTool.inputSchema || z16.object({}),
|
|
7984
9183
|
execute: async (input, toolOptions) => {
|
|
7985
|
-
const toolCallId = toolOptions.toolCallId ||
|
|
9184
|
+
const toolCallId = toolOptions.toolCallId || nanoid9();
|
|
7986
9185
|
const execution = toolExecutionQueries.create({
|
|
7987
9186
|
sessionId: this.session.id,
|
|
7988
9187
|
toolName: name,
|
|
@@ -7994,8 +9193,8 @@ ${taskAddendum}`;
|
|
|
7994
9193
|
this.pendingApprovals.set(toolCallId, await execution);
|
|
7995
9194
|
options.onApprovalRequired?.(await execution);
|
|
7996
9195
|
await sessionQueries.updateStatus(this.session.id, "waiting");
|
|
7997
|
-
const approved = await new Promise((
|
|
7998
|
-
approvalResolvers.set(toolCallId, { resolve:
|
|
9196
|
+
const approved = await new Promise((resolve11) => {
|
|
9197
|
+
approvalResolvers.set(toolCallId, { resolve: resolve11, sessionId: this.session.id });
|
|
7999
9198
|
});
|
|
8000
9199
|
const resolverData = approvalResolvers.get(toolCallId);
|
|
8001
9200
|
approvalResolvers.delete(toolCallId);
|