sparkecoder 0.1.98 → 0.1.100

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 (305) hide show
  1. package/README.md +41 -41
  2. package/dist/agent/index.d.ts +3 -3
  3. package/dist/agent/index.js +1274 -59
  4. package/dist/agent/index.js.map +1 -1
  5. package/dist/cli.js +10671 -8973
  6. package/dist/cli.js.map +1 -1
  7. package/dist/db/index.d.ts +2 -2
  8. package/dist/{index-BvIissiB.d.ts → index-D5l-DMGC.d.ts} +390 -6
  9. package/dist/index.d.ts +5 -5
  10. package/dist/index.js +10002 -8096
  11. package/dist/index.js.map +1 -1
  12. package/dist/{schema-CohdIL13.d.ts → schema-ecQSnCMz.d.ts} +41 -0
  13. package/dist/server/index.js +8867 -6969
  14. package/dist/server/index.js.map +1 -1
  15. package/dist/skills/default/manage-mcp.md +94 -0
  16. package/dist/skills/default/search-conversations.md +100 -0
  17. package/dist/tools/index.d.ts +2 -2
  18. package/dist/tools/index.js +111 -2
  19. package/dist/tools/index.js.map +1 -1
  20. package/package.json +5 -1
  21. package/src/skills/default/manage-mcp.md +94 -0
  22. package/src/skills/default/search-conversations.md +100 -0
  23. package/web/.next/BUILD_ID +1 -1
  24. package/web/.next/standalone/web/.next/BUILD_ID +1 -1
  25. package/web/.next/standalone/web/.next/app-path-routes-manifest.json +2 -1
  26. package/web/.next/standalone/web/.next/build-manifest.json +5 -5
  27. package/web/.next/standalone/web/.next/prerender-manifest.json +51 -3
  28. package/web/.next/standalone/web/.next/routes-manifest.json +13 -24
  29. package/web/.next/standalone/web/.next/server/app/(main)/agents/page/app-paths-manifest.json +3 -0
  30. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/build-manifest.json +3 -3
  31. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/next-font-manifest.json +1 -1
  32. package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js +7 -6
  33. package/web/.next/standalone/web/.next/server/app/(main)/agents/page.js.nft.json +1 -0
  34. package/web/.next/standalone/web/.next/server/app/(main)/agents/page_client-reference-manifest.js +2 -0
  35. package/web/.next/standalone/web/.next/server/app/(main)/page/build-manifest.json +3 -3
  36. package/web/.next/standalone/web/.next/server/app/(main)/page.js.nft.json +1 -1
  37. package/web/.next/standalone/web/.next/server/app/(main)/page_client-reference-manifest.js +1 -1
  38. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page/build-manifest.json +3 -3
  39. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page.js.nft.json +1 -1
  40. package/web/.next/standalone/web/.next/server/app/(main)/session/[id]/page_client-reference-manifest.js +1 -1
  41. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/app-paths-manifest.json +3 -0
  42. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/build-manifest.json +18 -0
  43. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/next-font-manifest.json +11 -0
  44. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/react-loadable-manifest.json +1 -0
  45. package/web/.next/standalone/web/.next/server/app/(main)/settings/page/server-reference-manifest.json +4 -0
  46. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js +21 -0
  47. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.map +5 -0
  48. package/web/.next/standalone/web/.next/server/app/(main)/settings/page.js.nft.json +1 -0
  49. package/web/.next/standalone/web/.next/server/app/(main)/settings/page_client-reference-manifest.js +2 -0
  50. package/web/.next/standalone/web/.next/server/app/_global-error/page/build-manifest.json +3 -3
  51. package/web/.next/standalone/web/.next/server/app/_global-error.html +2 -2
  52. package/web/.next/standalone/web/.next/server/app/_global-error.rsc +1 -1
  53. package/web/.next/standalone/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  54. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  55. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  56. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  57. package/web/.next/standalone/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  58. package/web/.next/standalone/web/.next/server/app/_not-found/page/build-manifest.json +3 -3
  59. package/web/.next/standalone/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  60. package/web/.next/standalone/web/.next/server/app/_not-found.html +1 -1
  61. package/web/.next/standalone/web/.next/server/app/_not-found.rsc +3 -3
  62. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  63. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  64. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  65. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  66. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  67. package/web/.next/standalone/web/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  68. package/web/.next/standalone/web/.next/server/app/agents.html +1 -0
  69. package/web/.next/standalone/web/.next/server/app/agents.meta +16 -0
  70. package/web/.next/standalone/web/.next/server/app/agents.rsc +25 -0
  71. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents/__PAGE__.segment.rsc +9 -0
  72. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p/agents.segment.rsc +4 -0
  73. package/web/.next/standalone/web/.next/server/app/agents.segments/!KG1haW4p.segment.rsc +7 -0
  74. package/web/.next/standalone/web/.next/server/app/agents.segments/_full.segment.rsc +25 -0
  75. package/web/.next/standalone/web/.next/server/app/agents.segments/_head.segment.rsc +6 -0
  76. package/web/.next/standalone/web/.next/server/app/agents.segments/_index.segment.rsc +7 -0
  77. package/web/.next/standalone/web/.next/server/app/agents.segments/_tree.segment.rsc +5 -0
  78. package/web/.next/standalone/web/.next/server/app/api/config/route.js.nft.json +1 -1
  79. package/web/.next/standalone/web/.next/server/app/api/health/route.js.nft.json +1 -1
  80. package/web/.next/standalone/web/.next/server/app/docs/installation/page/build-manifest.json +3 -3
  81. package/web/.next/standalone/web/.next/server/app/docs/installation/page_client-reference-manifest.js +1 -1
  82. package/web/.next/standalone/web/.next/server/app/docs/installation.html +2 -2
  83. package/web/.next/standalone/web/.next/server/app/docs/installation.rsc +4 -4
  84. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_full.segment.rsc +4 -4
  85. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_head.segment.rsc +1 -1
  86. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_index.segment.rsc +3 -3
  87. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/_tree.segment.rsc +2 -2
  88. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation/__PAGE__.segment.rsc +2 -2
  89. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs/installation.segment.rsc +1 -1
  90. package/web/.next/standalone/web/.next/server/app/docs/installation.segments/docs.segment.rsc +2 -2
  91. package/web/.next/standalone/web/.next/server/app/docs/page/build-manifest.json +3 -3
  92. package/web/.next/standalone/web/.next/server/app/docs/page_client-reference-manifest.js +1 -1
  93. package/web/.next/standalone/web/.next/server/app/docs/skills/page/build-manifest.json +3 -3
  94. package/web/.next/standalone/web/.next/server/app/docs/skills/page_client-reference-manifest.js +1 -1
  95. package/web/.next/standalone/web/.next/server/app/docs/skills.html +2 -2
  96. package/web/.next/standalone/web/.next/server/app/docs/skills.rsc +4 -4
  97. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_full.segment.rsc +4 -4
  98. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_head.segment.rsc +1 -1
  99. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_index.segment.rsc +3 -3
  100. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/_tree.segment.rsc +2 -2
  101. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills/__PAGE__.segment.rsc +1 -1
  102. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs/skills.segment.rsc +1 -1
  103. package/web/.next/standalone/web/.next/server/app/docs/skills.segments/docs.segment.rsc +2 -2
  104. package/web/.next/standalone/web/.next/server/app/docs/tools/page/build-manifest.json +3 -3
  105. package/web/.next/standalone/web/.next/server/app/docs/tools/page_client-reference-manifest.js +1 -1
  106. package/web/.next/standalone/web/.next/server/app/docs/tools.html +2 -2
  107. package/web/.next/standalone/web/.next/server/app/docs/tools.rsc +4 -4
  108. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_full.segment.rsc +4 -4
  109. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_head.segment.rsc +1 -1
  110. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_index.segment.rsc +3 -3
  111. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/_tree.segment.rsc +2 -2
  112. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools/__PAGE__.segment.rsc +2 -2
  113. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs/tools.segment.rsc +1 -1
  114. package/web/.next/standalone/web/.next/server/app/docs/tools.segments/docs.segment.rsc +2 -2
  115. package/web/.next/standalone/web/.next/server/app/docs.html +2 -2
  116. package/web/.next/standalone/web/.next/server/app/docs.rsc +4 -4
  117. package/web/.next/standalone/web/.next/server/app/docs.segments/_full.segment.rsc +4 -4
  118. package/web/.next/standalone/web/.next/server/app/docs.segments/_head.segment.rsc +1 -1
  119. package/web/.next/standalone/web/.next/server/app/docs.segments/_index.segment.rsc +3 -3
  120. package/web/.next/standalone/web/.next/server/app/docs.segments/_tree.segment.rsc +2 -2
  121. package/web/.next/standalone/web/.next/server/app/docs.segments/docs/__PAGE__.segment.rsc +2 -2
  122. package/web/.next/standalone/web/.next/server/app/docs.segments/docs.segment.rsc +2 -2
  123. package/web/.next/standalone/web/.next/server/app/index.html +1 -1
  124. package/web/.next/standalone/web/.next/server/app/index.rsc +5 -5
  125. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p/__PAGE__.segment.rsc +2 -2
  126. package/web/.next/standalone/web/.next/server/app/index.segments/!KG1haW4p.segment.rsc +2 -2
  127. package/web/.next/standalone/web/.next/server/app/index.segments/_full.segment.rsc +5 -5
  128. package/web/.next/standalone/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  129. package/web/.next/standalone/web/.next/server/app/index.segments/_index.segment.rsc +3 -3
  130. package/web/.next/standalone/web/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  131. package/web/.next/standalone/web/.next/server/app/settings.html +1 -0
  132. package/web/.next/standalone/web/.next/server/app/settings.meta +16 -0
  133. package/web/.next/standalone/web/.next/server/app/settings.rsc +25 -0
  134. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings/__PAGE__.segment.rsc +9 -0
  135. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p/settings.segment.rsc +4 -0
  136. package/web/.next/standalone/web/.next/server/app/settings.segments/!KG1haW4p.segment.rsc +7 -0
  137. package/web/.next/standalone/web/.next/server/app/settings.segments/_full.segment.rsc +25 -0
  138. package/web/.next/standalone/web/.next/server/app/settings.segments/_head.segment.rsc +6 -0
  139. package/web/.next/standalone/web/.next/server/app/settings.segments/_index.segment.rsc +7 -0
  140. package/web/.next/standalone/web/.next/server/app/settings.segments/_tree.segment.rsc +5 -0
  141. package/web/.next/standalone/web/.next/server/app-paths-manifest.json +2 -1
  142. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_a383a4d9._.js → 2374f_1f3f2d00._.js} +1 -1
  143. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_60d8842c._.js → 2374f_38945fd9._.js} +1 -1
  144. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_f363c084._.js → 2374f_570c34dc._.js} +1 -1
  145. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_806bd012._.js → 2374f_9c560f3a._.js} +1 -1
  146. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_b7f45fdf._.js → 2374f_a0d5caeb._.js} +1 -1
  147. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_c13c8f4f._.js → 2374f_c87abaf4._.js} +1 -1
  148. package/web/.next/standalone/web/.next/server/chunks/ssr/{2374f_2801b766._.js → 2374f_d8122230._.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]__be5e2967._.js +1 -1
  155. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__c94db61a._.js +3 -0
  156. package/web/.next/standalone/web/.next/server/chunks/ssr/web_3b9a2423._.js +3 -0
  157. package/web/.next/standalone/web/.next/server/chunks/ssr/web_4fe3c244._.js +3 -0
  158. package/web/.next/standalone/web/.next/server/chunks/ssr/{web_3c2b112b._.js → web_7ca56356._.js} +3 -3
  159. package/web/.next/standalone/web/.next/server/chunks/ssr/web_8e76ee8b._.js +8 -0
  160. package/web/.next/standalone/web/.next/server/chunks/ssr/web_90d4125e._.js +7 -0
  161. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_agents_page_actions_30f6e448.js +3 -0
  162. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_(main)_settings_page_actions_7285839d.js +3 -0
  163. package/web/.next/standalone/web/.next/server/chunks/ssr/web_b38a47ee._.js +4 -0
  164. package/web/.next/standalone/web/.next/server/chunks/ssr/web_f7cf6b63._.js +3 -0
  165. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_layout_tsx_453f6492._.js +3 -0
  166. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_page_tsx_5ac4794b._.js +1 -1
  167. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_app_(main)_settings_page_tsx_eb320e07._.js +33 -0
  168. package/web/.next/standalone/web/.next/server/middleware-build-manifest.js +3 -3
  169. package/web/.next/standalone/web/.next/server/next-font-manifest.js +1 -1
  170. package/web/.next/standalone/web/.next/server/next-font-manifest.json +8 -4
  171. package/web/.next/standalone/web/.next/server/pages/404.html +1 -1
  172. package/web/.next/standalone/web/.next/server/pages/500.html +2 -2
  173. package/web/.next/standalone/web/.next/server/server-reference-manifest.js +1 -1
  174. package/web/.next/standalone/web/.next/server/server-reference-manifest.json +1 -1
  175. package/web/.next/standalone/web/.next/static/chunks/03b5edce6d5b809e.js +1 -0
  176. package/web/.next/standalone/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
  177. package/web/.next/standalone/web/.next/static/chunks/344be859c2c8600b.css +1 -0
  178. package/web/.next/standalone/web/.next/static/chunks/4239395558fab3ef.js +1 -0
  179. package/web/.next/standalone/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
  180. package/web/.next/standalone/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
  181. package/web/.next/standalone/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
  182. package/web/.next/standalone/web/.next/static/chunks/ae4bb24474ff1ed0.js +13 -0
  183. package/web/.next/standalone/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
  184. package/web/.next/standalone/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
  185. package/web/.next/standalone/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
  186. package/web/.next/standalone/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
  187. package/web/.next/standalone/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
  188. package/web/.next/standalone/web/.next/static/chunks/fbdcbd65f9a38f4b.js +7 -0
  189. package/web/.next/{static/chunks/turbopack-597558bb7b6982f6.js → standalone/web/.next/static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  190. package/web/.next/standalone/web/.next/static/static/chunks/03b5edce6d5b809e.js +1 -0
  191. package/web/.next/standalone/web/.next/static/static/chunks/29dcecc3c2ca92b0.js +1 -0
  192. package/web/.next/standalone/web/.next/static/static/chunks/344be859c2c8600b.css +1 -0
  193. package/web/.next/standalone/web/.next/static/static/chunks/4239395558fab3ef.js +1 -0
  194. package/web/.next/standalone/web/.next/static/static/chunks/60359bdd369c0c72.js +1 -0
  195. package/web/.next/standalone/web/.next/static/static/chunks/699803c4fb2dd3fc.js +5 -0
  196. package/web/.next/standalone/web/.next/static/static/chunks/735a2408c315b2f0.js +1 -0
  197. package/web/.next/standalone/web/.next/static/static/chunks/ae4bb24474ff1ed0.js +13 -0
  198. package/web/.next/standalone/web/.next/static/static/chunks/d54077a2bb8314ed.js +31 -0
  199. package/web/.next/standalone/web/.next/static/static/chunks/dc34aa94e57fa28e.js +3 -0
  200. package/web/.next/standalone/web/.next/static/static/chunks/ea89ca7892d8c557.js +1 -0
  201. package/web/.next/standalone/web/.next/static/static/chunks/f0f19357f3fb7cf8.js +1 -0
  202. package/web/.next/standalone/web/.next/static/static/chunks/f5fe518b79d1bf41.js +1 -0
  203. package/web/.next/standalone/web/.next/static/static/chunks/fbdcbd65f9a38f4b.js +7 -0
  204. package/web/.next/standalone/web/.next/static/{chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  205. package/web/.next/standalone/web/next.config.ts +1 -55
  206. package/web/.next/standalone/web/package-lock.json +7 -7
  207. package/web/.next/standalone/web/src/app/(main)/agents/page.tsx +222 -0
  208. package/web/.next/standalone/web/src/app/(main)/page.tsx +40 -1
  209. package/web/.next/standalone/web/src/app/(main)/session/[id]/page.tsx +9 -1
  210. package/web/.next/standalone/web/src/app/(main)/settings/page.tsx +1116 -0
  211. package/web/.next/standalone/web/src/components/ai-elements/mention-input.tsx +125 -17
  212. package/web/.next/standalone/web/src/components/chat-interface.tsx +205 -4
  213. package/web/.next/standalone/web/src/components/pending-question-banner.tsx +106 -0
  214. package/web/.next/standalone/web/src/components/sessions-sidebar.tsx +120 -50
  215. package/web/.next/standalone/web/src/lib/api.ts +43 -2
  216. package/web/.next/static/chunks/03b5edce6d5b809e.js +1 -0
  217. package/web/.next/static/chunks/29dcecc3c2ca92b0.js +1 -0
  218. package/web/.next/static/chunks/344be859c2c8600b.css +1 -0
  219. package/web/.next/static/chunks/4239395558fab3ef.js +1 -0
  220. package/web/.next/static/chunks/60359bdd369c0c72.js +1 -0
  221. package/web/.next/static/chunks/699803c4fb2dd3fc.js +5 -0
  222. package/web/.next/static/chunks/735a2408c315b2f0.js +1 -0
  223. package/web/.next/static/chunks/ae4bb24474ff1ed0.js +13 -0
  224. package/web/.next/static/chunks/d54077a2bb8314ed.js +31 -0
  225. package/web/.next/static/chunks/dc34aa94e57fa28e.js +3 -0
  226. package/web/.next/static/chunks/ea89ca7892d8c557.js +1 -0
  227. package/web/.next/static/chunks/f0f19357f3fb7cf8.js +1 -0
  228. package/web/.next/static/chunks/f5fe518b79d1bf41.js +1 -0
  229. package/web/.next/static/chunks/fbdcbd65f9a38f4b.js +7 -0
  230. package/web/.next/{standalone/web/.next/static/static/chunks/turbopack-597558bb7b6982f6.js → static/chunks/turbopack-2c0905c7bbebae3f.js} +1 -1
  231. package/web/.next/standalone/web/.next/server/app/embed/[id]/page/app-paths-manifest.json +0 -3
  232. package/web/.next/standalone/web/.next/server/app/embed/[id]/page.js.nft.json +0 -1
  233. package/web/.next/standalone/web/.next/server/app/embed/[id]/page_client-reference-manifest.js +0 -2
  234. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_317b1fef._.js +0 -3
  235. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_37dd9702._.js +0 -45
  236. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_4d44e4ed._.js +0 -26
  237. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_54ac917f._.js +0 -3
  238. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_86585101._.js +0 -3
  239. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_c59a35bb._.js +0 -3
  240. package/web/.next/standalone/web/.next/server/chunks/ssr/2374f_fdfc7f3d._.js +0 -31
  241. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__234f92d8._.js +0 -3
  242. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9a826344._.js +0 -3
  243. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__9d3a7cbf._.js +0 -15
  244. package/web/.next/standalone/web/.next/server/chunks/ssr/[root-of-the-server]__de76483d._.js +0 -3
  245. package/web/.next/standalone/web/.next/server/chunks/ssr/web_08242997._.js +0 -3
  246. package/web/.next/standalone/web/.next/server/chunks/ssr/web_123ffe97._.js +0 -8
  247. package/web/.next/standalone/web/.next/server/chunks/ssr/web_5cca707f._.js +0 -7
  248. package/web/.next/standalone/web/.next/server/chunks/ssr/web_935e81f5._.js +0 -7
  249. package/web/.next/standalone/web/.next/server/chunks/ssr/web_99b01335._.js +0 -8
  250. package/web/.next/standalone/web/.next/server/chunks/ssr/web__next-internal_server_app_embed_[id]_page_actions_dd0b7fea.js +0 -3
  251. package/web/.next/standalone/web/.next/server/chunks/ssr/web_src_components_sessions-sidebar_tsx_92510070._.js +0 -3
  252. package/web/.next/standalone/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
  253. package/web/.next/standalone/web/.next/static/chunks/275e8268daf318b2.js +0 -7
  254. package/web/.next/standalone/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
  255. package/web/.next/standalone/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
  256. package/web/.next/standalone/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
  257. package/web/.next/standalone/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
  258. package/web/.next/standalone/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
  259. package/web/.next/standalone/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
  260. package/web/.next/standalone/web/.next/static/chunks/9fce2ce79c4c834e.js +0 -1
  261. package/web/.next/standalone/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
  262. package/web/.next/standalone/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
  263. package/web/.next/standalone/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
  264. package/web/.next/standalone/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
  265. package/web/.next/standalone/web/.next/static/static/chunks/1ebba7ac024244f9.js +0 -5
  266. package/web/.next/standalone/web/.next/static/static/chunks/275e8268daf318b2.js +0 -7
  267. package/web/.next/standalone/web/.next/static/static/chunks/2bf377e04592f3c8.js +0 -13
  268. package/web/.next/standalone/web/.next/static/static/chunks/376a123113ccf5eb.js +0 -3
  269. package/web/.next/standalone/web/.next/static/static/chunks/58fd0aaa2746b444.js +0 -1
  270. package/web/.next/standalone/web/.next/static/static/chunks/61c9922b38a9569d.js +0 -3
  271. package/web/.next/standalone/web/.next/static/static/chunks/74b64476a24dd71e.css +0 -1
  272. package/web/.next/standalone/web/.next/static/static/chunks/767bcdfbabf0703e.js +0 -7
  273. package/web/.next/standalone/web/.next/static/static/chunks/9fce2ce79c4c834e.js +0 -1
  274. package/web/.next/standalone/web/.next/static/static/chunks/a888d448ceab1abe.js +0 -1
  275. package/web/.next/standalone/web/.next/static/static/chunks/b9ad1584d4e11d12.js +0 -1
  276. package/web/.next/standalone/web/.next/static/static/chunks/c6f40df16a9396b9.js +0 -1
  277. package/web/.next/standalone/web/.next/static/static/chunks/ea29be392100ab0f.js +0 -5
  278. package/web/.next/standalone/web/src/app/embed/[id]/page.tsx +0 -77
  279. package/web/.next/standalone/web/src/lib/embed-bootstrap.ts +0 -108
  280. package/web/.next/static/chunks/1ebba7ac024244f9.js +0 -5
  281. package/web/.next/static/chunks/275e8268daf318b2.js +0 -7
  282. package/web/.next/static/chunks/2bf377e04592f3c8.js +0 -13
  283. package/web/.next/static/chunks/376a123113ccf5eb.js +0 -3
  284. package/web/.next/static/chunks/58fd0aaa2746b444.js +0 -1
  285. package/web/.next/static/chunks/61c9922b38a9569d.js +0 -3
  286. package/web/.next/static/chunks/74b64476a24dd71e.css +0 -1
  287. package/web/.next/static/chunks/767bcdfbabf0703e.js +0 -7
  288. package/web/.next/static/chunks/9fce2ce79c4c834e.js +0 -1
  289. package/web/.next/static/chunks/a888d448ceab1abe.js +0 -1
  290. package/web/.next/static/chunks/b9ad1584d4e11d12.js +0 -1
  291. package/web/.next/static/chunks/c6f40df16a9396b9.js +0 -1
  292. package/web/.next/static/chunks/ea29be392100ab0f.js +0 -5
  293. package/dist/{search-CCffrVJE.d.ts → search-DOzC4ojH.d.ts} +1 -1
  294. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/react-loadable-manifest.json +0 -0
  295. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page/server-reference-manifest.json +0 -0
  296. /package/web/.next/standalone/web/.next/server/app/{embed/[id] → (main)/agents}/page.js.map +0 -0
  297. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_buildManifest.js +0 -0
  298. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_clientMiddlewareManifest.json +0 -0
  299. /package/web/.next/standalone/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_ssgManifest.js +0 -0
  300. /package/web/.next/standalone/web/.next/static/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_buildManifest.js +0 -0
  301. /package/web/.next/standalone/web/.next/static/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_clientMiddlewareManifest.json +0 -0
  302. /package/web/.next/standalone/web/.next/static/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_ssgManifest.js +0 -0
  303. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_buildManifest.js +0 -0
  304. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_clientMiddlewareManifest.json +0 -0
  305. /package/web/.next/static/{WCqUmRTRCgZqwBVGKQESX → eyxBRyMA8Zq36Pfen3Ylj}/_ssgManifest.js +0 -0
@@ -59,8 +59,15 @@ export interface SlashCommand {
59
59
  action?: () => void;
60
60
  }
61
61
 
62
+ interface McpServer {
63
+ id: string;
64
+ name: string;
65
+ enabled: boolean;
66
+ transport: string;
67
+ }
68
+
62
69
  interface TriggerState {
63
- type: "file" | "command";
70
+ type: "file" | "command" | "mcp";
64
71
  query: string;
65
72
  startIndex: number; // Where the trigger character is
66
73
  endIndex: number; // Current cursor position
@@ -87,8 +94,9 @@ export function useMentionInput() {
87
94
  return ctx;
88
95
  }
89
96
 
90
- // Regex to match @mentions in text (matches @path/to/file or @folder/)
91
- const MENTION_REGEX = /@([\w./-]+\/?)/g;
97
+ // Regex to match @mentions in text. Captures both file-style (`@path/to/file`,
98
+ // `@folder/`) and MCP-style (`@mcp/<server-name>`).
99
+ const MENTION_REGEX = /@(mcp\/[\w-]+|[\w./-]+\/?)/g;
92
100
 
93
101
  // ============================================================================
94
102
  // Default Slash Commands - Each inserts a prompt template
@@ -311,6 +319,36 @@ export function MentionTextarea({
311
319
  limit: 15,
312
320
  });
313
321
 
322
+ // Fetch enabled MCP servers when either the dedicated @mcp trigger is
323
+ // active OR the user is typing a plain @<query> that might match a server
324
+ // name (e.g. `@gmail`, `@github`). Server names appear alongside files
325
+ // in the popover.
326
+ const [mcpServers, setMcpServers] = useState<McpServer[]>([]);
327
+ useEffect(() => {
328
+ if (trigger?.type !== "mcp" && trigger?.type !== "file") {
329
+ setMcpServers([]);
330
+ return;
331
+ }
332
+ let cancelled = false;
333
+ (async () => {
334
+ try {
335
+ const { getApiUrl } = await import("@/lib/config");
336
+ const res = await fetch(`${getApiUrl()}/api/mcp`);
337
+ if (!res.ok) return;
338
+ const data = await res.json();
339
+ if (cancelled) return;
340
+ const q = (trigger.query || "").toLowerCase();
341
+ const filtered: McpServer[] = (data.servers || [])
342
+ .filter((s: any) => s.enabled)
343
+ .filter((s: any) => !q || s.name.toLowerCase().includes(q));
344
+ setMcpServers(filtered);
345
+ } catch {
346
+ if (!cancelled) setMcpServers([]);
347
+ }
348
+ })();
349
+ return () => { cancelled = true; };
350
+ }, [trigger]);
351
+
314
352
  // Filter slash commands based on query
315
353
  const filteredCommands = useMemo(() => {
316
354
  if (trigger?.type !== "command") return [];
@@ -322,8 +360,14 @@ export function MentionTextarea({
322
360
  );
323
361
  }, [trigger, slashCommands]);
324
362
 
325
- // Current items to display
326
- const items = trigger?.type === "file" ? files : filteredCommands;
363
+ // Current items to display. For file mode we prepend any matching MCP
364
+ // servers so the same @-popover surfaces both. MCP names render in their
365
+ // own group above files (see CommandList below); we keep them sequenced
366
+ // first here so arrow-key navigation matches the visual order.
367
+ const items: Array<(typeof files)[0] | SlashCommand | McpServer> =
368
+ trigger?.type === "file" ? [...mcpServers, ...files]
369
+ : trigger?.type === "mcp" ? mcpServers
370
+ : filteredCommands;
327
371
  const showPopover = trigger !== null && (items.length > 0 || isLoading);
328
372
 
329
373
  // Calculate popover position based on textarea caret
@@ -408,6 +452,20 @@ export function MentionTextarea({
408
452
 
409
453
  const beforeCursor = text.slice(0, cursorPos);
410
454
 
455
+ // Check for @mcp trigger (MCP server mention). Supports `@mcp/<query>`,
456
+ // `@mcp:<query>`, and bare `@mcp` (no separator yet).
457
+ const mcpMatch = beforeCursor.match(/@mcp(?:[\/:]([^\s@]*))?$/i);
458
+ if (mcpMatch) {
459
+ setTrigger({
460
+ type: "mcp",
461
+ query: mcpMatch[1] ?? "",
462
+ startIndex: cursorPos - mcpMatch[0].length,
463
+ endIndex: cursorPos,
464
+ });
465
+ setSelectedIndex(0);
466
+ return;
467
+ }
468
+
411
469
  // Check for @ trigger (file mention)
412
470
  const atMatch = beforeCursor.match(/@([^\s@]*)$/);
413
471
  if (atMatch) {
@@ -453,9 +511,31 @@ export function MentionTextarea({
453
511
 
454
512
  // Handle selection from popover
455
513
  const handleSelect = useCallback(
456
- (item: (typeof files)[0] | SlashCommand) => {
514
+ (item: (typeof files)[0] | SlashCommand | McpServer) => {
457
515
  if (!trigger || !textareaRef.current) return;
458
516
 
517
+ // MCP server selection. Works under either the dedicated `@mcp/` trigger
518
+ // OR a plain `@<name>` file-trigger when the query happens to match a
519
+ // configured server (so `@gmail` works the same as `@mcp/gmail`).
520
+ if ("name" in item && "transport" in item) {
521
+ const mcp = item as McpServer;
522
+ const before = value.slice(0, trigger.startIndex);
523
+ const after = value.slice(trigger.endIndex);
524
+ const mentionText = `@mcp/${mcp.name} `;
525
+ const newValue = before + mentionText + after;
526
+ onChange(newValue);
527
+ setTimeout(() => {
528
+ if (textareaRef.current) {
529
+ const newPos = trigger.startIndex + mentionText.length;
530
+ textareaRef.current.selectionStart = newPos;
531
+ textareaRef.current.selectionEnd = newPos;
532
+ textareaRef.current.focus();
533
+ }
534
+ }, 0);
535
+ setTrigger(null);
536
+ return;
537
+ }
538
+
459
539
  if (trigger.type === "file" && "path" in item) {
460
540
  // Insert @path inline (replacing @query with @fullpath)
461
541
  const before = value.slice(0, trigger.startIndex);
@@ -711,30 +791,58 @@ export function MentionTextarea({
711
791
  <CommandEmpty>
712
792
  {trigger?.type === "file"
713
793
  ? "No files found"
794
+ : trigger?.type === "mcp"
795
+ ? "No MCP servers configured. Add one in Settings → MCP."
714
796
  : "No commands found"}
715
797
  </CommandEmpty>
716
798
  )}
717
- {trigger?.type === "file" && files.length > 0 && (
718
- <CommandGroup heading="Files & Folders">
719
- {files.map((file, index) => (
799
+ {(trigger?.type === "mcp" || trigger?.type === "file") && mcpServers.length > 0 && (
800
+ <CommandGroup heading="MCP servers">
801
+ {mcpServers.map((s, index) => (
720
802
  <CommandItem
721
- key={file.path}
722
- onSelect={() => handleSelect(file)}
803
+ key={s.id}
804
+ onSelect={() => handleSelect(s)}
723
805
  className={cn(
724
806
  "cursor-pointer",
725
807
  index === selectedIndex && "bg-accent"
726
808
  )}
727
809
  >
728
- {file.type === "folder" ? (
729
- <FolderIcon className="mr-2 size-4 text-blue-500" />
730
- ) : (
731
- <FileIcon className="mr-2 size-4 text-muted-foreground" />
732
- )}
733
- <span className="truncate">{file.path}</span>
810
+ <HashIcon className="mr-2 size-4 text-emerald-500" />
811
+ <div className="flex flex-col">
812
+ <span className="font-medium">{s.name}</span>
813
+ <span className="text-xs text-muted-foreground">{s.transport} · tools: mcp_{s.name}_*</span>
814
+ </div>
734
815
  </CommandItem>
735
816
  ))}
736
817
  </CommandGroup>
737
818
  )}
819
+ {trigger?.type === "file" && files.length > 0 && (
820
+ <CommandGroup heading="Files & Folders">
821
+ {files.map((file, fileIdx) => {
822
+ // In file mode the items array is [mcpServers, ...files],
823
+ // so the per-file selection index is offset by the number
824
+ // of MCP rows rendered above.
825
+ const idx = mcpServers.length + fileIdx;
826
+ return (
827
+ <CommandItem
828
+ key={file.path}
829
+ onSelect={() => handleSelect(file)}
830
+ className={cn(
831
+ "cursor-pointer",
832
+ idx === selectedIndex && "bg-accent"
833
+ )}
834
+ >
835
+ {file.type === "folder" ? (
836
+ <FolderIcon className="mr-2 size-4 text-blue-500" />
837
+ ) : (
838
+ <FileIcon className="mr-2 size-4 text-muted-foreground" />
839
+ )}
840
+ <span className="truncate">{file.path}</span>
841
+ </CommandItem>
842
+ );
843
+ })}
844
+ </CommandGroup>
845
+ )}
738
846
  {trigger?.type === "command" && filteredCommands.length > 0 && (
739
847
  <CommandGroup heading="Commands">
740
848
  {filteredCommands.map((cmd, index) => (
@@ -1,5 +1,6 @@
1
1
  'use client';
2
2
 
3
+ import * as React from 'react';
3
4
  import { useState, useRef, useEffect } from 'react';
4
5
  import Image from 'next/image';
5
6
  import { cn } from '@/lib/utils';
@@ -238,6 +239,167 @@ function stripDevtoolsContext(text: string): string {
238
239
  return text.replace(DEVTOOLS_CONTEXT_RE, '').trim();
239
240
  }
240
241
 
242
+ /** Channel pill at the start of an orchestrator user-message, e.g. "[SLACK #ops by @ryan]" or "[WEB]". */
243
+ interface ChannelPillInfo {
244
+ channel: string; // SLACK | WEB | SYSTEM | SCHEDULE | WEBHOOK
245
+ detail?: string; // remainder of the bracket, e.g. "#ops by @ryan"
246
+ }
247
+ function extractChannelPill(text: string): { pill: ChannelPillInfo | null; rest: string } {
248
+ const m = text.match(/^\[([A-Z]+)(?:\s+([^\]]*))?\]\s*([\s\S]*)$/);
249
+ if (!m) return { pill: null, rest: text };
250
+ const channel = m[1];
251
+ // Only treat as a channel pill if it's a known channel id.
252
+ if (!['WEB', 'SLACK', 'SYSTEM', 'SCHEDULE', 'WEBHOOK'].includes(channel)) {
253
+ return { pill: null, rest: text };
254
+ }
255
+ return { pill: { channel, detail: m[2]?.trim() || undefined }, rest: m[3] };
256
+ }
257
+
258
+ const CHANNEL_PALETTE: Record<string, string> = {
259
+ SLACK: 'bg-purple-500/10 text-purple-600 dark:text-purple-300 border-purple-500/30',
260
+ SYSTEM: 'bg-blue-500/10 text-blue-600 dark:text-blue-300 border-blue-500/30',
261
+ SCHEDULE: 'bg-amber-500/10 text-amber-600 dark:text-amber-300 border-amber-500/30',
262
+ WEBHOOK: 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-300 border-emerald-500/30',
263
+ };
264
+
265
+ function ChannelPill({ channel, detail }: ChannelPillInfo) {
266
+ // Hide [WEB] pills — web is the default surface, showing a pill on
267
+ // every typed message is noise.
268
+ if (channel === 'WEB') return null;
269
+ const tone = CHANNEL_PALETTE[channel] || 'bg-muted text-muted-foreground border-border';
270
+ return (
271
+ <span className={`inline-flex items-center gap-1 text-[10px] font-medium uppercase tracking-wide px-1.5 py-0.5 rounded border mr-2 mb-1 ${tone}`}>
272
+ <span>{channel}</span>
273
+ {detail && <span className="font-normal normal-case opacity-80">{detail}</span>}
274
+ </span>
275
+ );
276
+ }
277
+
278
+ /** Channels that aren't direct human chat — render compactly with click-to-expand. */
279
+ const SYNTHETIC_CHANNELS = new Set(['SYSTEM', 'SCHEDULE', 'WEBHOOK']);
280
+
281
+ /**
282
+ * Inbound human message from a non-web channel (Slack today, others later).
283
+ * Renders the tag ABOVE a normal user bubble so the convo doesn't read like
284
+ * the channel pill is part of the message body.
285
+ */
286
+ function TaggedInboundUserMessage({
287
+ pill, body, attachmentsBlock, devtoolsTrail, footer,
288
+ }: {
289
+ pill: ChannelPillInfo;
290
+ body: string;
291
+ attachmentsBlock?: React.ReactNode;
292
+ devtoolsTrail?: React.ReactNode;
293
+ footer?: React.ReactNode;
294
+ }) {
295
+ const tone = CHANNEL_PALETTE[pill.channel] || 'bg-muted text-muted-foreground border-border';
296
+ return (
297
+ <div className="flex flex-col items-end gap-1 my-2">
298
+ <div className={`inline-flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide px-2 py-0.5 rounded-md border self-end ${tone}`}>
299
+ <span>{pill.channel}</span>
300
+ {pill.detail && <span className="font-normal normal-case opacity-80">{pill.detail}</span>}
301
+ </div>
302
+ <Message from="user">
303
+ <MessageContent>
304
+ {attachmentsBlock}
305
+ {body && <p className="whitespace-pre-wrap">{body}</p>}
306
+ {devtoolsTrail}
307
+ </MessageContent>
308
+ {footer}
309
+ </Message>
310
+ </div>
311
+ );
312
+ }
313
+
314
+ /**
315
+ * Renders an outbound `messenger({action:'post', ...})` tool call as if it
316
+ * were a special assistant message: tag on top ("→ SENT VIA SLACK #ops"),
317
+ * the actual text bubble below, and a small status footer. So when a user
318
+ * comes back to the web later they can see exactly what was sent on other
319
+ * channels.
320
+ */
321
+ function SentMessengerCard({
322
+ input, output, status,
323
+ }: {
324
+ input: any;
325
+ output: any;
326
+ status: string;
327
+ }) {
328
+ if (!input || input.action !== 'post') return null;
329
+ const channel = String(input.channel || 'unknown').toUpperCase();
330
+ const tone = CHANNEL_PALETTE[channel] || 'bg-muted text-muted-foreground border-border';
331
+ const to = String(input.to || '');
332
+ const threadTs = input.threadTs ? String(input.threadTs) : null;
333
+ const text = String(input.text || '');
334
+ const ok = output && typeof output === 'object' && (output as any).ok !== false;
335
+ const errorText = output && typeof output === 'object' ? (output as any).error : undefined;
336
+ const pending = status === 'streaming' || status === 'running' || status === 'pending' || status === 'input-available';
337
+ return (
338
+ <div className="flex flex-col items-start gap-1 my-2 max-w-[80%]">
339
+ <div className={`inline-flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide px-2 py-0.5 rounded-md border ${tone}`}>
340
+ <span>→ Sent via {channel}</span>
341
+ {to && <span className="font-normal normal-case opacity-80">to {to}</span>}
342
+ {threadTs && <span className="font-normal normal-case opacity-60">thread {threadTs}</span>}
343
+ </div>
344
+ <div className={`rounded-2xl border bg-card/40 px-3 py-2 ${pending ? 'opacity-60' : ''}`}>
345
+ {text ? (
346
+ <p className="text-sm whitespace-pre-wrap">{text}</p>
347
+ ) : (
348
+ <p className="text-xs italic text-muted-foreground">(empty message)</p>
349
+ )}
350
+ </div>
351
+ <div className="text-[10px] text-muted-foreground flex items-center gap-1">
352
+ {pending ? (
353
+ <span className="opacity-70">sending…</span>
354
+ ) : ok ? (
355
+ <span className="text-emerald-500">✓ delivered</span>
356
+ ) : (
357
+ <span className="text-rose-500">✗ failed{errorText ? `: ${errorText}` : ''}</span>
358
+ )}
359
+ </div>
360
+ </div>
361
+ );
362
+ }
363
+
364
+ /**
365
+ * Synthetic event row — a compact one-liner for SYSTEM/SCHEDULE/WEBHOOK
366
+ * messages so the chat doesn't get cluttered with every worker completion,
367
+ * cron tick, or webhook hit. Click to expand the full payload.
368
+ */
369
+ function SyntheticEventMessage({ pill, body }: { pill: ChannelPillInfo; body: string }) {
370
+ const [expanded, setExpanded] = React.useState(false);
371
+ const tone = CHANNEL_PALETTE[pill.channel] || 'bg-muted text-muted-foreground border-border';
372
+ // First non-empty line for the collapsed summary.
373
+ const firstLine = body.split('\n').find((l) => l.trim().length > 0) || '';
374
+ const truncated = firstLine.length > 140 ? firstLine.slice(0, 140) + '…' : firstLine;
375
+ const isMultiline = body.includes('\n') || body.length > 140;
376
+
377
+ return (
378
+ <div className="flex justify-center my-1.5">
379
+ <button
380
+ type="button"
381
+ onClick={() => isMultiline && setExpanded((v) => !v)}
382
+ className={`group max-w-[80%] text-left rounded-md border px-2 py-1 transition-colors ${tone} ${isMultiline ? 'cursor-pointer hover:opacity-90' : 'cursor-default'}`}
383
+ >
384
+ <div className="flex items-center gap-2 text-[10px] uppercase tracking-wide">
385
+ <span className="font-medium">{pill.channel}</span>
386
+ {pill.detail && <span className="font-normal normal-case opacity-80 truncate">{pill.detail}</span>}
387
+ {isMultiline && (
388
+ <ChevronDown
389
+ className={`h-3 w-3 ml-auto shrink-0 transition-transform ${expanded ? 'rotate-180' : ''}`}
390
+ />
391
+ )}
392
+ </div>
393
+ {expanded ? (
394
+ <pre className="mt-1 text-[11px] whitespace-pre-wrap font-mono opacity-90 max-h-[400px] overflow-y-auto">{body}</pre>
395
+ ) : (
396
+ <p className="mt-0.5 text-[11px] opacity-80 truncate">{truncated}</p>
397
+ )}
398
+ </button>
399
+ </div>
400
+ );
401
+ }
402
+
241
403
  /** Parse devtools context metadata from a user message */
242
404
  function parseDevtoolsContext(text: string): DevtoolsContextMeta | null {
243
405
  const match = text.match(/<devtools-context>([\s\S]*?)<\/devtools-context>/);
@@ -2998,12 +3160,31 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
2998
3160
  })
2999
3161
  .map((item) => {
3000
3162
  if (item.type === 'user-message') {
3163
+ // Channel-aware rendering for orchestrator-bound user messages.
3164
+ // - SYSTEM/SCHEDULE/WEBHOOK → compact collapsible row
3165
+ // - SLACK and other non-web human channels → tag ABOVE the bubble,
3166
+ // clean body inside (so the chat doesn't read like the pill is
3167
+ // part of the message)
3168
+ // - WEB / no pill → normal user bubble (unchanged)
3169
+ const extracted = item.content ? extractChannelPill(item.content) : { pill: null, rest: item.content || '' };
3170
+ if (extracted.pill && SYNTHETIC_CHANNELS.has(extracted.pill.channel)) {
3171
+ return <SyntheticEventMessage key={item.id} pill={extracted.pill} body={extracted.rest} />;
3172
+ }
3173
+ const useTaggedAbove = !!extracted.pill && extracted.pill.channel !== 'WEB';
3174
+
3001
3175
  // Check if there's a checkpoint for this message (meaning we can revert to before it)
3002
3176
  const hasCheckpoint = item.messageSequence !== undefined &&
3003
3177
  checkpoints.some(cp => cp.messageSequence === item.messageSequence);
3004
3178
 
3005
3179
  return (
3006
- <Message key={item.id} from="user">
3180
+ <div key={item.id} className={useTaggedAbove ? 'flex flex-col items-end gap-1' : undefined}>
3181
+ {useTaggedAbove && extracted.pill && (
3182
+ <div className={`inline-flex items-center gap-1.5 text-[10px] font-medium uppercase tracking-wide px-2 py-0.5 rounded-md border self-end ${CHANNEL_PALETTE[extracted.pill.channel] || 'bg-muted text-muted-foreground border-border'}`}>
3183
+ <span>{extracted.pill.channel}</span>
3184
+ {extracted.pill.detail && <span className="font-normal normal-case opacity-80">{extracted.pill.detail}</span>}
3185
+ </div>
3186
+ )}
3187
+ <Message from="user">
3007
3188
  <MessageContent>
3008
3189
  {/* Display attachments if any */}
3009
3190
  {item.attachments && item.attachments.length > 0 && (
@@ -3032,10 +3213,15 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
3032
3213
  </div>
3033
3214
  )}
3034
3215
  {item.content && (() => {
3035
- const displayText = stripDevtoolsContext(item.content);
3036
- const devtoolsMeta = parseDevtoolsContext(item.content);
3216
+ // Use the already-extracted pill/rest from the parent
3217
+ // scope so we don't double-render the tag for tagged-above
3218
+ // messages.
3219
+ const rest = extracted.rest;
3220
+ const displayText = stripDevtoolsContext(rest);
3221
+ const devtoolsMeta = parseDevtoolsContext(rest);
3037
3222
  return (
3038
3223
  <>
3224
+ {!useTaggedAbove && extracted.pill && <ChannelPill {...extracted.pill} />}
3039
3225
  {displayText && (
3040
3226
  <p className="whitespace-pre-wrap">{displayText}</p>
3041
3227
  )}
@@ -3078,6 +3264,7 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
3078
3264
  </MessageActions>
3079
3265
  )}
3080
3266
  </Message>
3267
+ </div>
3081
3268
  );
3082
3269
  }
3083
3270
 
@@ -3126,7 +3313,21 @@ export function ChatInterface({ session, isEmbed = false }: ChatInterfaceProps)
3126
3313
  if (item.type === 'tool-call' && item.toolCall) {
3127
3314
  const tc = item.toolCall;
3128
3315
  const toolType = `tool-${tc.toolName}` as `tool-${string}`;
3129
-
3316
+
3317
+ // Render outbound `messenger({action:'post',...})` as a special
3318
+ // "Sent via SLACK #ops" card so anyone returning to the web can
3319
+ // see what the orchestrator actually pushed to external channels.
3320
+ if (tc.toolName === 'messenger' && (tc.input as any)?.action === 'post') {
3321
+ return (
3322
+ <SentMessengerCard
3323
+ key={item.id}
3324
+ input={tc.input}
3325
+ output={tc.output}
3326
+ status={tc.status}
3327
+ />
3328
+ );
3329
+ }
3330
+
3130
3331
  // Use dedicated WriteFileTool component for write_file
3131
3332
  if (tc.toolName === 'write_file') {
3132
3333
  return (
@@ -0,0 +1,106 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState, useCallback } from 'react';
4
+ import { MessageCircleQuestion, Loader2 } from 'lucide-react';
5
+ import { Button } from '@/components/ui/button';
6
+ import { getSession, answerAgentQuestion } from '@/lib/api';
7
+
8
+ /**
9
+ * Polls the session and shows a banner inside the chat surface when the
10
+ * worker agent has called `ask_question_to_user`. Lets the user answer
11
+ * inline so they don't have to leave the session.
12
+ */
13
+ export function PendingQuestionBanner({ sessionId }: { sessionId: string }) {
14
+ const [question, setQuestion] = useState<{
15
+ questionId: string;
16
+ question: string;
17
+ context?: string;
18
+ choices?: string[];
19
+ } | null>(null);
20
+ const [answer, setAnswer] = useState('');
21
+ const [sending, setSending] = useState(false);
22
+
23
+ const refresh = useCallback(async () => {
24
+ const s = await getSession(sessionId);
25
+ setQuestion(s?.pendingQuestion ?? null);
26
+ }, [sessionId]);
27
+
28
+ useEffect(() => {
29
+ let cancelled = false;
30
+ const tick = async () => {
31
+ if (cancelled) return;
32
+ await refresh();
33
+ };
34
+ tick();
35
+ const id = setInterval(tick, 3000);
36
+ return () => {
37
+ cancelled = true;
38
+ clearInterval(id);
39
+ };
40
+ }, [refresh]);
41
+
42
+ if (!question) return null;
43
+
44
+ const send = async () => {
45
+ if (!answer.trim() || sending) return;
46
+ setSending(true);
47
+ try {
48
+ await answerAgentQuestion(sessionId, question.questionId, answer.trim());
49
+ setAnswer('');
50
+ setQuestion(null);
51
+ // The worker will resume; the next poll will reflect that.
52
+ refresh();
53
+ } finally {
54
+ setSending(false);
55
+ }
56
+ };
57
+
58
+ return (
59
+ <div className="border-b border-amber-500/40 bg-amber-500/5 px-4 py-3">
60
+ <div className="flex items-start gap-3 max-w-3xl mx-auto">
61
+ <MessageCircleQuestion className="size-5 text-amber-500 shrink-0 mt-0.5" />
62
+ <div className="flex-1 min-w-0">
63
+ <p className="text-sm font-medium text-amber-800 dark:text-amber-200">
64
+ {question.question}
65
+ </p>
66
+ {question.context && (
67
+ <p className="text-xs text-muted-foreground mt-1">{question.context}</p>
68
+ )}
69
+ {question.choices && question.choices.length > 0 && (
70
+ <div className="flex flex-wrap gap-1.5 mt-2">
71
+ {question.choices.map((c) => (
72
+ <button
73
+ key={c}
74
+ type="button"
75
+ onClick={() => setAnswer(c)}
76
+ className="text-xs px-2 py-1 rounded bg-amber-500/10 hover:bg-amber-500/20 text-amber-800 dark:text-amber-200"
77
+ >
78
+ {c}
79
+ </button>
80
+ ))}
81
+ </div>
82
+ )}
83
+ <div className="flex gap-2 mt-2">
84
+ <input
85
+ type="text"
86
+ value={answer}
87
+ onChange={(e) => setAnswer(e.target.value)}
88
+ onKeyDown={(e) => {
89
+ if (e.key === 'Enter') {
90
+ e.preventDefault();
91
+ send();
92
+ }
93
+ }}
94
+ placeholder="Type your answer…"
95
+ className="flex-1 px-3 py-1.5 text-sm rounded border border-border bg-background focus:outline-none focus:ring-2 focus:ring-amber-500/40"
96
+ autoFocus
97
+ />
98
+ <Button size="sm" onClick={send} disabled={!answer.trim() || sending}>
99
+ {sending ? <Loader2 className="size-3.5 animate-spin" /> : 'Answer'}
100
+ </Button>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ );
106
+ }