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.
Files changed (296) hide show
  1. package/dist/agent/index.d.ts +3 -3
  2. package/dist/agent/index.js +1258 -59
  3. package/dist/agent/index.js.map +1 -1
  4. package/dist/cli.js +10659 -8977
  5. package/dist/cli.js.map +1 -1
  6. package/dist/db/index.d.ts +2 -2
  7. package/dist/{index-BvIissiB.d.ts → index-D5l-DMGC.d.ts} +390 -6
  8. package/dist/index.d.ts +5 -5
  9. package/dist/index.js +9985 -8095
  10. package/dist/index.js.map +1 -1
  11. package/dist/{schema-CohdIL13.d.ts → schema-ecQSnCMz.d.ts} +41 -0
  12. package/dist/server/index.js +8852 -6970
  13. package/dist/server/index.js.map +1 -1
  14. package/dist/skills/default/manage-mcp.md +94 -0
  15. package/dist/skills/default/search-conversations.md +100 -0
  16. package/dist/tools/index.d.ts +2 -2
  17. package/dist/tools/index.js +111 -2
  18. package/dist/tools/index.js.map +1 -1
  19. package/package.json +5 -1
  20. package/src/skills/default/manage-mcp.md +94 -0
  21. package/src/skills/default/search-conversations.md +100 -0
  22. package/web/.next/BUILD_ID +1 -1
  23. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  24. package/web/.next/standalone/web/.next/app-path-routes-manifest.json +2 -1
  25. package/web/.next/standalone/web/.next/build-manifest.json +5 -5
  26. package/web/.next/standalone/web/.next/prerender-manifest.json +51 -3
  27. package/web/.next/standalone/web/.next/routes-manifest.json +13 -24
  28. package/web/.next/standalone/web/.next/server/app/(main)/agents/page/app-paths-manifest.json +3 -0
  29. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/build-manifest.json +3 -3
  30. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/next-font-manifest.json +1 -1
  31. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js +7 -6
  32. package/web/.next/standalone/web/.next/server/app/(main)/agents/page.js.nft.json +1 -0
  33. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +2 -0
  34. package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +3 -3
  35. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  36. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  37. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/build-manifest.json +3 -3
  38. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  39. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  40. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/app-paths-manifest.json +3 -0
  41. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/build-manifest.json +18 -0
  42. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/next-font-manifest.json +11 -0
  43. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/react-loadable-manifest.json +1 -0
  44. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/server-reference-manifest.json +4 -0
  45. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js +21 -0
  46. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.map +5 -0
  47. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -0
  48. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +2 -0
  49. package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
  50. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  51. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  52. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
  58. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  59. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  60. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +2 -2
  61. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  62. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  63. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
  64. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  65. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  67. package/web/.next/standalone/web/.next/server/app/agents.html +1 -0
  68. package/web/.next/standalone/web/.next/server/app/agents.meta +16 -0
  69. package/web/.next/standalone/web/.next/server/app/agents.rsc +25 -0
  70. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +9 -0
  71. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +4 -0
  72. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +7 -0
  73. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +25 -0
  74. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +6 -0
  75. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +7 -0
  76. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +5 -0
  77. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  78. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  79. package/web/.next/standalone/web/.next/server/app/docs/installation/page/build-manifest.json +3 -3
  80. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  81. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  82. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +2 -2
  83. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +2 -2
  84. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  85. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +2 -2
  86. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  87. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +1 -1
  88. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  89. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +3 -3
  91. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  92. package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +3 -3
  93. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  94. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  95. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +2 -2
  96. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +2 -2
  97. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  98. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +2 -2
  99. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  100. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  101. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  102. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +1 -1
  103. package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +3 -3
  104. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  105. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  106. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +2 -2
  107. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +2 -2
  108. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  109. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +2 -2
  110. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  111. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +1 -1
  112. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  113. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +1 -1
  114. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  115. package/web/.next/standalone/web/.next/server/app/docs.rsc +2 -2
  116. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +2 -2
  117. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  118. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +2 -2
  119. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  120. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +1 -1
  121. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +1 -1
  122. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  123. package/web/.next/standalone/web/.next/server/app/index.rsc +4 -4
  124. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  125. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  126. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +4 -4
  127. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  128. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  129. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  130. package/web/.next/standalone/web/.next/server/app/settings.html +1 -0
  131. package/web/.next/standalone/web/.next/server/app/settings.meta +16 -0
  132. package/web/.next/standalone/web/.next/server/app/settings.rsc +25 -0
  133. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +9 -0
  134. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +4 -0
  135. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +7 -0
  136. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +25 -0
  137. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +6 -0
  138. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +7 -0
  139. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +5 -0
  140. package/web/.next/standalone/web/.next/server/app-paths-manifest.json +2 -1
  141. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_60d8842c._.js → 2374f_3b04c7b5._.js} +1 -1
  142. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2801b766._.js → 2374f_5ee1ee50._.js} +1 -1
  143. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c13c8f4f._.js → 2374f_7b7dd4c7._.js} +1 -1
  144. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b7f45fdf._.js → 2374f_9e444fb0._.js} +1 -1
  145. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a383a4d9._.js → 2374f_b57914d2._.js} +1 -1
  146. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_c2c47039._.js +3 -0
  147. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_806bd012._.js → 2374f_db1d6704._.js} +1 -1
  148. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f363c084._.js → 2374f_e6dbbf5d._.js} +1 -1
  149. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_lucide-react_dist_esm_icons_50c2f239._.js +3 -0
  150. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__2ea52390._.js +3 -0
  151. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__6097da17._.js +15 -0
  152. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9f149e88._.js +3 -0
  153. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__b050bb8f._.js +1 -1
  154. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c94db61a._.js +3 -0
  155. package/web/.next/standalone/web/.next/server/chunks/ssr/web_4fe3c244._.js +3 -0
  156. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_3c2b112b._.js → web_7ca56356._.js} +3 -3
  157. package/web/.next/standalone/web/.next/server/chunks/ssr/web_8e76ee8b._.js +8 -0
  158. package/web/.next/standalone/web/.next/server/chunks/ssr/web_90d4125e._.js +7 -0
  159. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_agents_page_actions_30f6e448.js +3 -0
  160. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_settings_page_actions_7285839d.js +3 -0
  161. package/web/.next/standalone/web/.next/server/chunks/ssr/web_b38a47ee._.js +4 -0
  162. package/web/.next/standalone/web/.next/server/chunks/ssr/web_f7cf6b63._.js +3 -0
  163. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +3 -0
  164. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  165. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +33 -0
  166. package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +3 -3
  167. package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
  168. package/web/.next/standalone/web/.next/server/next-font-manifest.json +8 -4
  169. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  170. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  171. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  172. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  173. package/web/.next/standalone/web/.next/static/chunks/03b5edce6d5b809e.js +1 -0
  174. package/web/.next/standalone/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
  175. package/web/.next/standalone/web/.next/static/chunks/344be859c2c8600b.css +1 -0
  176. package/web/.next/standalone/web/.next/static/chunks/545725e4c1237026.js +7 -0
  177. package/web/.next/standalone/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
  178. package/web/.next/standalone/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
  179. package/web/.next/standalone/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
  180. package/web/.next/standalone/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
  181. package/web/.next/standalone/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
  182. package/web/.next/standalone/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
  183. package/web/.next/standalone/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
  184. package/web/.next/standalone/web/.next/static/chunks/f50a66c24c564585.js +13 -0
  185. package/web/.next/standalone/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
  186. package/web/.next/{static/chunks/turbopack-597558bb7b6982f6.js → standalone/web/.next/static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  187. package/web/.next/standalone/web/.next/static/static/chunks/03b5edce6d5b809e.js +1 -0
  188. package/web/.next/standalone/web/.next/static/static/chunks/29dcecc3c2ca92b0.js +1 -0
  189. package/web/.next/standalone/web/.next/static/static/chunks/344be859c2c8600b.css +1 -0
  190. package/web/.next/standalone/web/.next/static/static/chunks/545725e4c1237026.js +7 -0
  191. package/web/.next/standalone/web/.next/static/static/chunks/60359bdd369c0c72.js +1 -0
  192. package/web/.next/standalone/web/.next/static/static/chunks/699803c4fb2dd3fc.js +5 -0
  193. package/web/.next/standalone/web/.next/static/static/chunks/735a2408c315b2f0.js +1 -0
  194. package/web/.next/standalone/web/.next/static/static/chunks/d54077a2bb8314ed.js +31 -0
  195. package/web/.next/standalone/web/.next/static/static/chunks/dc34aa94e57fa28e.js +3 -0
  196. package/web/.next/standalone/web/.next/static/static/chunks/ea89ca7892d8c557.js +1 -0
  197. package/web/.next/standalone/web/.next/static/static/chunks/f0f19357f3fb7cf8.js +1 -0
  198. package/web/.next/standalone/web/.next/static/static/chunks/f50a66c24c564585.js +13 -0
  199. package/web/.next/standalone/web/.next/static/static/chunks/f5fe518b79d1bf41.js +1 -0
  200. package/web/.next/standalone/web/.next/static/{chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  201. package/web/.next/standalone/web/next.config.ts +1 -55
  202. package/web/.next/standalone/web/package-lock.json +3 -3
  203. package/web/.next/standalone/web/src/app/(main)/agents/page.tsx +222 -0
  204. package/web/.next/standalone/web/src/app/(main)/page.tsx +40 -1
  205. package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +9 -1
  206. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +1116 -0
  207. package/web/.next/standalone/web/src/components/chat-interface.tsx +205 -4
  208. package/web/.next/standalone/web/src/components/pending-question-banner.tsx +106 -0
  209. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +120 -50
  210. package/web/.next/standalone/web/src/lib/api.ts +43 -2
  211. package/web/.next/static/chunks/03b5edce6d5b809e.js +1 -0
  212. package/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
  213. package/web/.next/static/chunks/344be859c2c8600b.css +1 -0
  214. package/web/.next/static/chunks/545725e4c1237026.js +7 -0
  215. package/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
  216. package/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
  217. package/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
  218. package/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
  219. package/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
  220. package/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
  221. package/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
  222. package/web/.next/static/chunks/f50a66c24c564585.js +13 -0
  223. package/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
  224. package/web/.next/{standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  225. package/web/.next/standalone/web/.next/server/app/embed/[id]/page/app-paths-manifest.json +0 -3
  226. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +0 -1
  227. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +0 -2
  228. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_317b1fef._.js +0 -3
  229. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_37dd9702._.js +0 -45
  230. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_4d44e4ed._.js +0 -26
  231. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_54ac917f._.js +0 -3
  232. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_86585101._.js +0 -3
  233. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_c59a35bb._.js +0 -3
  234. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_fdfc7f3d._.js +0 -31
  235. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__234f92d8._.js +0 -3
  236. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9a826344._.js +0 -3
  237. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9d3a7cbf._.js +0 -15
  238. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de76483d._.js +0 -3
  239. package/web/.next/standalone/web/.next/server/chunks/ssr/web_08242997._.js +0 -3
  240. package/web/.next/standalone/web/.next/server/chunks/ssr/web_123ffe97._.js +0 -8
  241. package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +0 -7
  242. package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +0 -7
  243. package/web/.next/standalone/web/.next/server/chunks/ssr/web_99b01335._.js +0 -8
  244. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_embed_[id]_page_actions_dd0b7fea.js +0 -3
  245. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +0 -3
  246. package/web/.next/standalone/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
  247. package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +0 -7
  248. package/web/.next/standalone/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
  249. package/web/.next/standalone/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
  250. package/web/.next/standalone/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
  251. package/web/.next/standalone/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
  252. package/web/.next/standalone/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
  253. package/web/.next/standalone/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
  254. package/web/.next/standalone/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
  255. package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
  256. package/web/.next/standalone/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
  257. package/web/.next/standalone/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
  258. package/web/.next/standalone/web/.next/static/static/chunks/1ebba7ac024244f9.js +0 -5
  259. package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +0 -7
  260. package/web/.next/standalone/web/.next/static/static/chunks/2bf377e04592f3c8.js +0 -13
  261. package/web/.next/standalone/web/.next/static/static/chunks/376a123113ccf5eb.js +0 -3
  262. package/web/.next/standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js +0 -1
  263. package/web/.next/standalone/web/.next/static/static/chunks/61c9922b38a9569d.js +0 -3
  264. package/web/.next/standalone/web/.next/static/static/chunks/74b64476a24dd71e.css +0 -1
  265. package/web/.next/standalone/web/.next/static/static/chunks/767bcdfbabf0703e.js +0 -7
  266. package/web/.next/standalone/web/.next/static/static/chunks/a888d448ceab1abe.js +0 -1
  267. package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +0 -1
  268. package/web/.next/standalone/web/.next/static/static/chunks/c6f40df16a9396b9.js +0 -1
  269. package/web/.next/standalone/web/.next/static/static/chunks/ea29be392100ab0f.js +0 -5
  270. package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +0 -77
  271. package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +0 -108
  272. package/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
  273. package/web/.next/static/chunks/275e8268daf318b2.js +0 -7
  274. package/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
  275. package/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
  276. package/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
  277. package/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
  278. package/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
  279. package/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
  280. package/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
  281. package/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
  282. package/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
  283. package/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
  284. package/dist/{search-CCffrVJE.d.ts → search-DOzC4ojH.d.ts} +1 -1
  285. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/react-loadable-manifest.json +0 -0
  286. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/server-reference-manifest.json +0 -0
  287. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js.map +0 -0
  288. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
  289. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
  290. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → static/yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
  291. /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
  292. /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
  293. /package/web/.next/standalone/web/.next/static/{static/WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
  294. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_buildManifest.js +0 -0
  295. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_clientMiddlewareManifest.json +0 -0
  296. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → yyzMTo2RYusiXE0e1_kdc}/_ssgManifest.js +0 -0
@@ -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
- var initialized, sessionQueries, messageQueries, toolExecutionQueries, todoQueries, skillQueries, fileBackupQueries, subagentQueries, indexStatusQueries;
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 entry of entries) {
987
+ for (const entry2 of entries) {
787
988
  let filePath;
788
989
  let fileName;
789
- if (entry.isDirectory()) {
790
- const skillMdPath = resolve6(directory, entry.name, "SKILL.md");
990
+ if (entry2.isDirectory()) {
991
+ const skillMdPath = resolve6(directory, entry2.name, "SKILL.md");
791
992
  if (existsSync10(skillMdPath)) {
792
993
  filePath = skillMdPath;
793
- fileName = entry.name;
994
+ fileName = entry2.name;
794
995
  } else {
795
996
  continue;
796
997
  }
797
- } else if (entry.name.endsWith(".md") || entry.name.endsWith(".mdc")) {
798
- filePath = resolve6(directory, entry.name);
799
- fileName = entry.name;
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 join9 } from "path";
1895
+ import { join as join11 } from "path";
1638
1896
  import { tmpdir as tmpdir2 } from "os";
1639
- import { nanoid as nanoid5 } from "nanoid";
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 = join9(tmpdir2(), `sparkecoder-recording-${nanoid5(8)}`);
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 = join9(workDir, `frame_${String(i).padStart(6, "0")}.jpg`);
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 = join9(workDir, `recording_${this.sessionId}.mp4`);
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 "${join9(workDir, "frame_%06d.jpg")}" -c:v libx264 -pix_fmt yuv420p -preset fast -crf 23 "${outputPath}"`,
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(join9(workDir, f)).catch(() => {
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 tool14,
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 z15 } from "zod";
1954
- import { nanoid as nanoid6 } from "nanoid";
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((resolve10) => {
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
- resolve10(diagnostics.get(normalized) || []);
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 clients = await getClientsForFile(filePath);
3367
- if (clients.length === 0) {
3624
+ const clients2 = await getClientsForFile(filePath);
3625
+ if (clients2.length === 0) {
3368
3626
  return;
3369
3627
  }
3370
- await Promise.all(clients.map((client) => client.notifyOpen(filePath)));
3628
+ await Promise.all(clients2.map((client) => client.notifyOpen(filePath)));
3371
3629
  if (waitForDiagnostics) {
3372
- await Promise.all(clients.map((client) => client.waitForDiagnostics(filePath)));
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 clients = await getClientsForFile(normalized);
3635
+ const clients2 = await getClientsForFile(normalized);
3378
3636
  const allDiagnostics = [];
3379
- for (const client of clients) {
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((resolve10) => setTimeout(resolve10, 0));
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 entry of entries) {
4364
+ for (const entry2 of entries) {
4107
4365
  if (files.length >= maxFiles) break;
4108
- const fullPath = resolve7(currentDir, entry.name);
4109
- if (entry.isDirectory()) {
4110
- if (["node_modules", ".git", "dist", "build", ".next", "coverage"].includes(entry.name)) {
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 (entry.isFile()) {
4115
- const ext = extname5(entry.name);
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((resolve10) => {
4457
- resolveNext = resolve10;
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 entry of entries) {
4866
+ for (const entry2 of entries) {
4609
4867
  if (remaining <= 0) return null;
4610
- const fullPath = resolve8(dir, entry.name);
4611
- if (entry.isDirectory()) {
4612
- if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith(".")) continue;
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 (entry.isFile()) {
4617
- const ext = entry.name.substring(entry.name.lastIndexOf("."));
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((resolve10, reject) => {
8329
+ return new Promise((resolve11, reject) => {
7298
8330
  pendingQuestions.set(k, {
7299
8331
  ...question,
7300
8332
  createdAt: /* @__PURE__ */ new Date(),
7301
- resolve: resolve10,
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
- return createTools({
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
- const systemPrompt = await buildSystemPrompt({
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: options.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
- 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.";
7837
- if (emit) {
7838
- await emit(JSON.stringify({ type: "data-user-message", data: { id: `user_${Date.now()}`, content: continuationPrompt } }));
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: join10, basename: basename5 } = await import("path");
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 : join10(this.session.workingDirectory, 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] = tool14({
9180
+ wrappedTools[name] = tool15({
7982
9181
  description: originalTool.description || "",
7983
- inputSchema: originalTool.inputSchema || z15.object({}),
9182
+ inputSchema: originalTool.inputSchema || z16.object({}),
7984
9183
  execute: async (input, toolOptions) => {
7985
- const toolCallId = toolOptions.toolCallId || nanoid6();
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((resolve10) => {
7998
- approvalResolvers.set(toolCallId, { resolve: resolve10, sessionId: this.session.id });
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);