@agent-native/core 0.99.3 → 0.100.0

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 (362) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/docs/content/agent-surfaces.mdx +43 -0
  4. package/corpus/core/docs/content/external-agents.mdx +11 -0
  5. package/corpus/core/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
  6. package/corpus/core/docs/content/locales/ar-SA/external-agents.mdx +9 -0
  7. package/corpus/core/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
  8. package/corpus/core/docs/content/locales/de-DE/external-agents.mdx +10 -0
  9. package/corpus/core/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
  10. package/corpus/core/docs/content/locales/es-ES/external-agents.mdx +10 -0
  11. package/corpus/core/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
  12. package/corpus/core/docs/content/locales/fr-FR/external-agents.mdx +11 -0
  13. package/corpus/core/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
  14. package/corpus/core/docs/content/locales/hi-IN/external-agents.mdx +9 -0
  15. package/corpus/core/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
  16. package/corpus/core/docs/content/locales/ja-JP/external-agents.mdx +10 -0
  17. package/corpus/core/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
  18. package/corpus/core/docs/content/locales/ko-KR/external-agents.mdx +9 -0
  19. package/corpus/core/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
  20. package/corpus/core/docs/content/locales/pt-BR/external-agents.mdx +10 -0
  21. package/corpus/core/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
  22. package/corpus/core/docs/content/locales/zh-CN/external-agents.mdx +8 -0
  23. package/corpus/core/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
  24. package/corpus/core/docs/content/locales/zh-TW/external-agents.mdx +8 -0
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/agent/context-xray/actions/context-evict.ts +11 -0
  27. package/corpus/core/src/agent/context-xray/actions/context-pin.ts +11 -0
  28. package/corpus/core/src/agent/context-xray/actions/context-preview-get.ts +134 -0
  29. package/corpus/core/src/agent/context-xray/actions/context-restore.ts +11 -0
  30. package/corpus/core/src/agent/context-xray/actions/errors.ts +20 -0
  31. package/corpus/core/src/agent/context-xray/manifest.ts +106 -7
  32. package/corpus/core/src/agent/engine/context-directives-transform.ts +5 -1
  33. package/corpus/core/src/agent/production-agent.ts +100 -38
  34. package/corpus/core/src/agent/run-loop-with-resume.ts +7 -2
  35. package/corpus/core/src/client/AgentPanel.tsx +35 -8
  36. package/corpus/core/src/client/agent-page/AgentContextTab.tsx +501 -0
  37. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +370 -0
  38. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +1084 -0
  39. package/corpus/core/src/client/agent-page/types.ts +18 -0
  40. package/corpus/core/src/client/agent-page/use-jobs.ts +150 -0
  41. package/corpus/core/src/client/context-xray/ContextMeter.tsx +22 -5
  42. package/corpus/core/src/client/context-xray/ContextTreemap.tsx +16 -1
  43. package/corpus/core/src/client/context-xray/ContextXRayPanel.tsx +168 -3
  44. package/corpus/core/src/client/context-xray/format.ts +6 -0
  45. package/corpus/core/src/client/index.ts +6 -0
  46. package/corpus/core/src/client/resources/ResourcesPanel.tsx +51 -23
  47. package/corpus/core/src/client/settings/AutomationsSection.tsx +407 -387
  48. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +105 -0
  49. package/corpus/core/src/jobs/actions/manage-recurring-job.ts +85 -0
  50. package/corpus/core/src/jobs/tools.ts +1 -1
  51. package/corpus/core/src/localization/default-messages.ts +110 -0
  52. package/corpus/core/src/mcp/build-server.ts +1 -0
  53. package/corpus/core/src/mcp/connect-route.ts +62 -65
  54. package/corpus/core/src/server/action-discovery.ts +23 -0
  55. package/corpus/core/src/server/agent-chat/prompt-resources.ts +174 -2
  56. package/corpus/core/src/server/agent-chat-plugin.ts +175 -2
  57. package/corpus/core/src/server/auth.ts +1 -7
  58. package/corpus/core/src/server/google-oauth.ts +1 -7
  59. package/corpus/core/src/server/workspace-oauth.ts +31 -0
  60. package/corpus/core/src/shared/context-xray.ts +84 -1
  61. package/corpus/core/src/shared/mcp-connect-content.ts +130 -0
  62. package/corpus/core/src/templates/default/app/routes/_index.tsx +11 -0
  63. package/corpus/core/src/templates/default/app/routes/agent.tsx +33 -0
  64. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  65. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  66. package/corpus/core/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  67. package/corpus/core/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  68. package/corpus/core/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
  69. package/corpus/core/src/triggers/actions/list-automations.ts +99 -0
  70. package/corpus/core/src/triggers/actions/manage-automation.ts +78 -0
  71. package/corpus/templates/analytics/actions/data-source-status.ts +33 -15
  72. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  73. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +149 -24
  74. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +17 -0
  75. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  76. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -0
  77. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +8 -0
  78. package/corpus/templates/analytics/app/i18n/zh-TW.ts +1 -0
  79. package/corpus/templates/analytics/app/i18n-data.ts +1 -0
  80. package/corpus/templates/analytics/app/routes/agent.tsx +5 -0
  81. package/corpus/templates/analytics/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  82. package/corpus/templates/analytics/changelog/2026-07-13-analytics-answers-can-use-built-in-first-party-event-data.md +6 -0
  83. package/corpus/templates/analytics/changelog/2026-07-13-chart-legends-can-filter-to-one-series-or-hide-a-series-on-h.md +6 -0
  84. package/corpus/templates/analytics/changelog/2026-07-13-gpt-5-6-luna-answers-now-recover-normally-instead-of-showing.md +6 -0
  85. package/corpus/templates/analytics/server/plugins/agent-chat.ts +11 -2
  86. package/corpus/templates/assets/app/components/layout/Header.tsx +1 -0
  87. package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -0
  88. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -0
  89. package/corpus/templates/assets/app/routes/agent.tsx +5 -0
  90. package/corpus/templates/assets/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  91. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -0
  92. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +4 -1
  93. package/corpus/templates/brain/app/lib/brain.ts +11 -0
  94. package/corpus/templates/brain/app/root.tsx +15 -1
  95. package/corpus/templates/brain/app/routes/agent.tsx +11 -0
  96. package/corpus/templates/brain/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  97. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +1 -0
  98. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +2 -0
  99. package/corpus/templates/calendar/app/root.tsx +17 -1
  100. package/corpus/templates/calendar/app/routes/_app.agent.tsx +25 -0
  101. package/corpus/templates/calendar/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  102. package/corpus/templates/chat/app/components/layout/Header.tsx +1 -0
  103. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -0
  104. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +7 -0
  105. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +3 -0
  106. package/corpus/templates/chat/app/root.tsx +24 -2
  107. package/corpus/templates/chat/app/routes/agent.tsx +15 -0
  108. package/corpus/templates/chat/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +6 -0
  109. package/corpus/templates/clips/app/components/library/library-layout.tsx +8 -0
  110. package/corpus/templates/clips/app/i18n/ar-SA.ts +2 -0
  111. package/corpus/templates/clips/app/i18n/de-DE.ts +2 -0
  112. package/corpus/templates/clips/app/i18n/en-US.ts +2 -0
  113. package/corpus/templates/clips/app/i18n/es-ES.ts +2 -0
  114. package/corpus/templates/clips/app/i18n/fr-FR.ts +2 -0
  115. package/corpus/templates/clips/app/i18n/hi-IN.ts +2 -0
  116. package/corpus/templates/clips/app/i18n/ja-JP.ts +2 -0
  117. package/corpus/templates/clips/app/i18n/ko-KR.ts +2 -0
  118. package/corpus/templates/clips/app/i18n/pt-BR.ts +2 -0
  119. package/corpus/templates/clips/app/i18n/zh-CN.ts +2 -0
  120. package/corpus/templates/clips/app/i18n/zh-TW.ts +2 -0
  121. package/corpus/templates/clips/app/root.tsx +7 -1
  122. package/corpus/templates/clips/app/routes/_app.agent.tsx +15 -0
  123. package/corpus/templates/clips/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  124. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -0
  125. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +36 -0
  126. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  127. package/corpus/templates/content/app/i18n-data.ts +44 -6
  128. package/corpus/templates/content/app/root.tsx +8 -0
  129. package/corpus/templates/content/app/routes/_app.agent.tsx +15 -0
  130. package/corpus/templates/content/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  131. package/corpus/templates/design/app/components/layout/Layout.tsx +1 -0
  132. package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -0
  133. package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
  134. package/corpus/templates/design/app/i18n-data.ts +20 -0
  135. package/corpus/templates/design/app/root.tsx +7 -1
  136. package/corpus/templates/design/app/routes/agent.tsx +15 -0
  137. package/corpus/templates/design/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  138. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +10 -1
  139. package/corpus/templates/dispatch/app/i18n/zh-TW.ts +2 -0
  140. package/corpus/templates/dispatch/app/i18n-data.ts +20 -0
  141. package/corpus/templates/dispatch/app/root.tsx +7 -2
  142. package/corpus/templates/dispatch/app/routes/agent.tsx +18 -0
  143. package/corpus/templates/dispatch/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  144. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -0
  145. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +35 -0
  146. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  147. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  148. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  149. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  150. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  151. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  152. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  153. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  154. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  155. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  156. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  157. package/corpus/templates/forms/app/root.tsx +6 -1
  158. package/corpus/templates/forms/app/routes/_app.agent.tsx +15 -0
  159. package/corpus/templates/forms/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  160. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +14 -2
  161. package/corpus/templates/macros/app/components/layout/Header.tsx +1 -0
  162. package/corpus/templates/macros/app/root.tsx +17 -2
  163. package/corpus/templates/macros/app/routes/agent.tsx +15 -0
  164. package/corpus/templates/macros/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  165. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +42 -0
  166. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +6 -0
  167. package/corpus/templates/mail/app/routes/agent.tsx +15 -0
  168. package/corpus/templates/mail/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  169. package/corpus/templates/plan/app/components/layout/Header.tsx +1 -0
  170. package/corpus/templates/plan/app/components/layout/Layout.tsx +1 -0
  171. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -0
  172. package/corpus/templates/plan/app/root.tsx +8 -0
  173. package/corpus/templates/plan/app/routes/agent.tsx +15 -0
  174. package/corpus/templates/plan/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  175. package/corpus/templates/slides/app/components/layout/Header.tsx +1 -0
  176. package/corpus/templates/slides/app/components/layout/Layout.tsx +1 -0
  177. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +2 -0
  178. package/corpus/templates/slides/app/root.tsx +10 -1
  179. package/corpus/templates/slides/app/routes/agent.tsx +15 -0
  180. package/corpus/templates/slides/changelog/2026-07-13-new-agent-page-see-and-manage-your-agent-s-context-files-con.md +6 -0
  181. package/dist/agent/context-xray/actions/context-evict.js +5 -2
  182. package/dist/agent/context-xray/actions/context-evict.js.map +1 -1
  183. package/dist/agent/context-xray/actions/context-pin.js +5 -2
  184. package/dist/agent/context-xray/actions/context-pin.js.map +1 -1
  185. package/dist/agent/context-xray/actions/context-preview-get.d.ts +17 -0
  186. package/dist/agent/context-xray/actions/context-preview-get.d.ts.map +1 -0
  187. package/dist/agent/context-xray/actions/context-preview-get.js +94 -0
  188. package/dist/agent/context-xray/actions/context-preview-get.js.map +1 -0
  189. package/dist/agent/context-xray/actions/context-restore.js +5 -2
  190. package/dist/agent/context-xray/actions/context-restore.js.map +1 -1
  191. package/dist/agent/context-xray/actions/errors.d.ts +3 -0
  192. package/dist/agent/context-xray/actions/errors.d.ts.map +1 -1
  193. package/dist/agent/context-xray/actions/errors.js +6 -0
  194. package/dist/agent/context-xray/actions/errors.js.map +1 -1
  195. package/dist/agent/context-xray/manifest.d.ts +18 -1
  196. package/dist/agent/context-xray/manifest.d.ts.map +1 -1
  197. package/dist/agent/context-xray/manifest.js +68 -6
  198. package/dist/agent/context-xray/manifest.js.map +1 -1
  199. package/dist/agent/engine/context-directives-transform.d.ts +2 -0
  200. package/dist/agent/engine/context-directives-transform.d.ts.map +1 -1
  201. package/dist/agent/engine/context-directives-transform.js +2 -1
  202. package/dist/agent/engine/context-directives-transform.js.map +1 -1
  203. package/dist/agent/production-agent.d.ts +15 -0
  204. package/dist/agent/production-agent.d.ts.map +1 -1
  205. package/dist/agent/production-agent.js +74 -36
  206. package/dist/agent/production-agent.js.map +1 -1
  207. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  208. package/dist/agent/run-loop-with-resume.js +6 -3
  209. package/dist/agent/run-loop-with-resume.js.map +1 -1
  210. package/dist/client/AgentPanel.d.ts +5 -1
  211. package/dist/client/AgentPanel.d.ts.map +1 -1
  212. package/dist/client/AgentPanel.js +9 -11
  213. package/dist/client/AgentPanel.js.map +1 -1
  214. package/dist/client/agent-page/AgentContextTab.d.ts +5 -0
  215. package/dist/client/agent-page/AgentContextTab.d.ts.map +1 -0
  216. package/dist/client/agent-page/AgentContextTab.js +202 -0
  217. package/dist/client/agent-page/AgentContextTab.js.map +1 -0
  218. package/dist/client/agent-page/AgentJobsTab.d.ts +3 -0
  219. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -0
  220. package/dist/client/agent-page/AgentJobsTab.js +98 -0
  221. package/dist/client/agent-page/AgentJobsTab.js.map +1 -0
  222. package/dist/client/agent-page/AgentTabsPage.d.ts +20 -0
  223. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -0
  224. package/dist/client/agent-page/AgentTabsPage.js +420 -0
  225. package/dist/client/agent-page/AgentTabsPage.js.map +1 -0
  226. package/dist/client/agent-page/types.d.ts +17 -0
  227. package/dist/client/agent-page/types.d.ts.map +1 -0
  228. package/dist/client/agent-page/types.js +9 -0
  229. package/dist/client/agent-page/types.js.map +1 -0
  230. package/dist/client/agent-page/use-jobs.d.ts +61 -0
  231. package/dist/client/agent-page/use-jobs.d.ts.map +1 -0
  232. package/dist/client/agent-page/use-jobs.js +71 -0
  233. package/dist/client/agent-page/use-jobs.js.map +1 -0
  234. package/dist/client/context-xray/ContextMeter.d.ts +3 -1
  235. package/dist/client/context-xray/ContextMeter.d.ts.map +1 -1
  236. package/dist/client/context-xray/ContextMeter.js +14 -5
  237. package/dist/client/context-xray/ContextMeter.js.map +1 -1
  238. package/dist/client/context-xray/ContextTreemap.d.ts +3 -2
  239. package/dist/client/context-xray/ContextTreemap.d.ts.map +1 -1
  240. package/dist/client/context-xray/ContextTreemap.js +9 -1
  241. package/dist/client/context-xray/ContextTreemap.js.map +1 -1
  242. package/dist/client/context-xray/ContextXRayPanel.d.ts.map +1 -1
  243. package/dist/client/context-xray/ContextXRayPanel.js +95 -20
  244. package/dist/client/context-xray/ContextXRayPanel.js.map +1 -1
  245. package/dist/client/context-xray/format.d.ts.map +1 -1
  246. package/dist/client/context-xray/format.js +12 -0
  247. package/dist/client/context-xray/format.js.map +1 -1
  248. package/dist/client/index.d.ts +3 -0
  249. package/dist/client/index.d.ts.map +1 -1
  250. package/dist/client/index.js +1 -0
  251. package/dist/client/index.js.map +1 -1
  252. package/dist/client/resources/ResourcesPanel.d.ts +9 -1
  253. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  254. package/dist/client/resources/ResourcesPanel.js +26 -10
  255. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  256. package/dist/client/settings/AutomationsSection.d.ts +9 -0
  257. package/dist/client/settings/AutomationsSection.d.ts.map +1 -1
  258. package/dist/client/settings/AutomationsSection.js +128 -147
  259. package/dist/client/settings/AutomationsSection.js.map +1 -1
  260. package/dist/collab/routes.d.ts +1 -1
  261. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  262. package/dist/jobs/actions/list-recurring-jobs.d.ts +21 -0
  263. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -0
  264. package/dist/jobs/actions/list-recurring-jobs.js +74 -0
  265. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -0
  266. package/dist/jobs/actions/manage-recurring-job.d.ts +18 -0
  267. package/dist/jobs/actions/manage-recurring-job.d.ts.map +1 -0
  268. package/dist/jobs/actions/manage-recurring-job.js +68 -0
  269. package/dist/jobs/actions/manage-recurring-job.js.map +1 -0
  270. package/dist/jobs/tools.d.ts +13 -0
  271. package/dist/jobs/tools.d.ts.map +1 -1
  272. package/dist/jobs/tools.js +1 -1
  273. package/dist/jobs/tools.js.map +1 -1
  274. package/dist/localization/default-messages.d.ts +96 -0
  275. package/dist/localization/default-messages.d.ts.map +1 -1
  276. package/dist/localization/default-messages.js +96 -0
  277. package/dist/localization/default-messages.js.map +1 -1
  278. package/dist/mcp/build-server.d.ts.map +1 -1
  279. package/dist/mcp/build-server.js +1 -0
  280. package/dist/mcp/build-server.js.map +1 -1
  281. package/dist/mcp/connect-route.d.ts.map +1 -1
  282. package/dist/mcp/connect-route.js +44 -59
  283. package/dist/mcp/connect-route.js.map +1 -1
  284. package/dist/notifications/routes.d.ts +2 -2
  285. package/dist/observability/routes.d.ts +5 -5
  286. package/dist/progress/routes.d.ts +1 -1
  287. package/dist/resources/handlers.d.ts +1 -1
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/action-discovery.d.ts.map +1 -1
  290. package/dist/server/action-discovery.js +23 -0
  291. package/dist/server/action-discovery.js.map +1 -1
  292. package/dist/server/agent-chat/prompt-resources.d.ts +14 -0
  293. package/dist/server/agent-chat/prompt-resources.d.ts.map +1 -1
  294. package/dist/server/agent-chat/prompt-resources.js +139 -2
  295. package/dist/server/agent-chat/prompt-resources.js.map +1 -1
  296. package/dist/server/agent-chat-plugin.d.ts +1 -0
  297. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  298. package/dist/server/agent-chat-plugin.js +136 -3
  299. package/dist/server/agent-chat-plugin.js.map +1 -1
  300. package/dist/server/auth.d.ts.map +1 -1
  301. package/dist/server/auth.js +1 -4
  302. package/dist/server/auth.js.map +1 -1
  303. package/dist/server/google-oauth.d.ts.map +1 -1
  304. package/dist/server/google-oauth.js +1 -4
  305. package/dist/server/google-oauth.js.map +1 -1
  306. package/dist/server/workspace-oauth.d.ts +11 -0
  307. package/dist/server/workspace-oauth.d.ts.map +1 -0
  308. package/dist/server/workspace-oauth.js +25 -0
  309. package/dist/server/workspace-oauth.js.map +1 -0
  310. package/dist/shared/context-xray.d.ts +45 -1
  311. package/dist/shared/context-xray.d.ts.map +1 -1
  312. package/dist/shared/context-xray.js +12 -0
  313. package/dist/shared/context-xray.js.map +1 -1
  314. package/dist/shared/mcp-connect-content.d.ts +37 -0
  315. package/dist/shared/mcp-connect-content.d.ts.map +1 -0
  316. package/dist/shared/mcp-connect-content.js +92 -0
  317. package/dist/shared/mcp-connect-content.js.map +1 -0
  318. package/dist/templates/default/app/routes/_index.tsx +11 -0
  319. package/dist/templates/default/app/routes/agent.tsx +33 -0
  320. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  321. package/dist/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  322. package/dist/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  323. package/dist/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  324. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
  325. package/dist/triggers/actions/list-automations.d.ts +24 -0
  326. package/dist/triggers/actions/list-automations.d.ts.map +1 -0
  327. package/dist/triggers/actions/list-automations.js +72 -0
  328. package/dist/triggers/actions/list-automations.js.map +1 -0
  329. package/dist/triggers/actions/manage-automation.d.ts +18 -0
  330. package/dist/triggers/actions/manage-automation.d.ts.map +1 -0
  331. package/dist/triggers/actions/manage-automation.js +60 -0
  332. package/dist/triggers/actions/manage-automation.js.map +1 -0
  333. package/docs/content/agent-surfaces.mdx +43 -0
  334. package/docs/content/external-agents.mdx +11 -0
  335. package/docs/content/locales/ar-SA/agent-surfaces.mdx +30 -0
  336. package/docs/content/locales/ar-SA/external-agents.mdx +9 -0
  337. package/docs/content/locales/de-DE/agent-surfaces.mdx +34 -0
  338. package/docs/content/locales/de-DE/external-agents.mdx +10 -0
  339. package/docs/content/locales/es-ES/agent-surfaces.mdx +34 -0
  340. package/docs/content/locales/es-ES/external-agents.mdx +10 -0
  341. package/docs/content/locales/fr-FR/agent-surfaces.mdx +34 -0
  342. package/docs/content/locales/fr-FR/external-agents.mdx +11 -0
  343. package/docs/content/locales/hi-IN/agent-surfaces.mdx +31 -0
  344. package/docs/content/locales/hi-IN/external-agents.mdx +9 -0
  345. package/docs/content/locales/ja-JP/agent-surfaces.mdx +32 -0
  346. package/docs/content/locales/ja-JP/external-agents.mdx +10 -0
  347. package/docs/content/locales/ko-KR/agent-surfaces.mdx +32 -0
  348. package/docs/content/locales/ko-KR/external-agents.mdx +9 -0
  349. package/docs/content/locales/pt-BR/agent-surfaces.mdx +33 -0
  350. package/docs/content/locales/pt-BR/external-agents.mdx +10 -0
  351. package/docs/content/locales/zh-CN/agent-surfaces.mdx +29 -0
  352. package/docs/content/locales/zh-CN/external-agents.mdx +8 -0
  353. package/docs/content/locales/zh-TW/agent-surfaces.mdx +29 -0
  354. package/docs/content/locales/zh-TW/external-agents.mdx +8 -0
  355. package/package.json +1 -1
  356. package/src/templates/default/app/routes/_index.tsx +11 -0
  357. package/src/templates/default/app/routes/agent.tsx +33 -0
  358. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  359. package/src/templates/workspace-core/.agents/skills/agent-page/SKILL.md +59 -0
  360. package/src/templates/workspace-core/.agents/skills/context-xray/SKILL.md +25 -0
  361. package/src/templates/workspace-core/.agents/skills/external-agents/SKILL.md +7 -0
  362. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +9 -0
@@ -0,0 +1,1084 @@
1
+ import {
2
+ IconBrain,
3
+ IconClock,
4
+ IconExternalLink,
5
+ IconFolder,
6
+ IconPlugConnected,
7
+ IconSearch,
8
+ IconShieldLock,
9
+ IconX,
10
+ } from "@tabler/icons-react";
11
+ import {
12
+ lazy,
13
+ Suspense,
14
+ useCallback,
15
+ useEffect,
16
+ useMemo,
17
+ useRef,
18
+ useState,
19
+ type ComponentType,
20
+ } from "react";
21
+
22
+ import {
23
+ MCP_CONNECT_GUIDES,
24
+ MCP_CONNECT_MCP_URL_TEMPLATE,
25
+ MCP_STATIC_TOKEN_FALLBACK,
26
+ interpolateMcpConnectTemplate,
27
+ type McpConnectTemplateValues,
28
+ } from "../../shared/mcp-connect-content.js";
29
+ import { appPath } from "../api-path.js";
30
+ import {
31
+ Tooltip,
32
+ TooltipContent,
33
+ TooltipTrigger,
34
+ } from "../components/ui/tooltip.js";
35
+ import { useT } from "../i18n.js";
36
+ import { useOrg } from "../org/hooks.js";
37
+ import {
38
+ McpIntegrationDialog,
39
+ // The dialog is intentionally reused here so the Agent page remains a thin
40
+ // host for the existing MCP management flow.
41
+ } from "../resources/McpIntegrationDialog.js";
42
+ import { McpServerDetail } from "../resources/McpServerDetail.js";
43
+ import {
44
+ useCreateMcpServer,
45
+ useDeleteMcpServer,
46
+ useMcpServers,
47
+ type McpServer,
48
+ type McpServerScope,
49
+ } from "../resources/use-mcp-servers.js";
50
+ import { AgentsSection } from "../settings/AgentsSection.js";
51
+ import type {
52
+ SettingsSearchEntry,
53
+ SettingsTabItem,
54
+ } from "../settings/SettingsTabsPage.js";
55
+ import { cn } from "../utils.js";
56
+ import type { AgentPageScope, AgentPageTabProps } from "./types.js";
57
+
58
+ const AgentContextTab = lazy(() =>
59
+ import("./AgentContextTab.js").then((module) => ({
60
+ default: module.AgentContextTab,
61
+ })),
62
+ );
63
+ const AgentJobsTab = lazy(() =>
64
+ import("./AgentJobsTab.js").then((module) => ({
65
+ default: module.AgentJobsTab,
66
+ })),
67
+ );
68
+ const ResourcesPanel = lazy(() =>
69
+ import("../resources/ResourcesPanel.js").then((module) => ({
70
+ default: module.ResourcesPanel,
71
+ })),
72
+ );
73
+
74
+ type SettingsTabIcon = ComponentType<{ className?: string }>;
75
+
76
+ function normalizeTabId(value?: string | null): string | null {
77
+ const normalized = value
78
+ ?.replace(/^#/, "")
79
+ .trim()
80
+ .toLowerCase()
81
+ .replace(/["']/g, "")
82
+ .replace(/[\s_]+/g, "-");
83
+ return normalized || null;
84
+ }
85
+
86
+ function resolveTabId(
87
+ tabs: SettingsTabItem[],
88
+ value?: string | null,
89
+ ): string | null {
90
+ const normalized = normalizeTabId(value);
91
+ if (!normalized) return null;
92
+ if (tabs.some((tab) => tab.id === normalized)) return normalized;
93
+ const section = normalized.split(":", 1)[0];
94
+ const owner = tabs.find((tab) =>
95
+ tab.searchEntries?.some(
96
+ (entry) => normalizeTabId(entry.hash ?? entry.id) === section,
97
+ ),
98
+ );
99
+ return owner?.id ?? null;
100
+ }
101
+
102
+ function updateTabHash(tabId: string) {
103
+ if (typeof window === "undefined") return;
104
+ const hash = tabId === "context" ? "" : `#${encodeURIComponent(tabId)}`;
105
+ window.history.pushState(
106
+ null,
107
+ "",
108
+ `${window.location.pathname}${window.location.search}${hash}`,
109
+ );
110
+ }
111
+
112
+ function initialScope(): AgentPageScope {
113
+ if (typeof window === "undefined") return "user";
114
+ try {
115
+ const queryScope = new URLSearchParams(window.location.search).get("scope");
116
+ if (queryScope === "org" || queryScope === "user") return queryScope;
117
+ return localStorage.getItem("agent-page-scope") === "org" ? "org" : "user";
118
+ } catch {
119
+ return "user";
120
+ }
121
+ }
122
+
123
+ function updateScopeUrl(scope: AgentPageScope) {
124
+ if (typeof window === "undefined") return;
125
+ const search = new URLSearchParams(window.location.search);
126
+ search.set("scope", scope);
127
+ const query = search.toString();
128
+ window.history.replaceState(
129
+ null,
130
+ "",
131
+ `${window.location.pathname}${query ? `?${query}` : ""}${window.location.hash}`,
132
+ );
133
+ }
134
+
135
+ function TabLoading() {
136
+ return (
137
+ <div className="space-y-3" aria-busy="true">
138
+ <div className="h-5 w-36 animate-pulse rounded bg-muted" />
139
+ <div className="h-20 animate-pulse rounded-lg border border-border bg-muted/30" />
140
+ <div className="h-20 animate-pulse rounded-lg border border-border bg-muted/30" />
141
+ </div>
142
+ );
143
+ }
144
+
145
+ function EmptySlot({ label }: { label: string }) {
146
+ return (
147
+ <div className="flex min-h-[220px] items-center justify-center rounded-lg border border-dashed border-border/70 bg-muted/10">
148
+ <span className="sr-only">{label}</span>
149
+ </div>
150
+ );
151
+ }
152
+
153
+ function FilesTab({ scope }: AgentPageTabProps) {
154
+ return (
155
+ <div className="h-full min-h-[360px]">
156
+ <ResourcesPanel
157
+ key={scope}
158
+ showMcpServers={false}
159
+ scope={scope === "org" ? "shared" : "personal"}
160
+ />
161
+ </div>
162
+ );
163
+ }
164
+
165
+ function ScopeBadge({ scope }: { scope: McpServerScope }) {
166
+ return (
167
+ <span className="rounded-full border border-border bg-muted/40 px-2 py-0.5 text-[10px] font-medium text-muted-foreground">
168
+ {scope === "user" ? "Personal" : "Organization"}
169
+ </span>
170
+ );
171
+ }
172
+
173
+ function ServerStatus({ server }: { server: McpServer }) {
174
+ if (server.status.state === "connected") {
175
+ return (
176
+ <span className="text-[11px] text-emerald-600 dark:text-emerald-400">
177
+ Connected · {server.status.toolCount} tools
178
+ </span>
179
+ );
180
+ }
181
+ if (server.status.state === "error") {
182
+ return (
183
+ <span className="truncate text-[11px] text-destructive">
184
+ Connection error
185
+ </span>
186
+ );
187
+ }
188
+ return (
189
+ <span className="text-[11px] text-muted-foreground">Status unknown</span>
190
+ );
191
+ }
192
+
193
+ function ConnectionsTab({ scope, canManageOrg = false }: AgentPageTabProps) {
194
+ const t = useT();
195
+ const serversQuery = useMcpServers();
196
+ const createServer = useCreateMcpServer();
197
+ const deleteServer = useDeleteMcpServer();
198
+ const [dialogOpen, setDialogOpen] = useState(false);
199
+ const [selectedServer, setSelectedServer] = useState<McpServer | null>(null);
200
+ const [deleteTarget, setDeleteTarget] = useState<string | null>(null);
201
+ const [error, setError] = useState<string | null>(null);
202
+ const data = serversQuery.data;
203
+ const hasOrg = Boolean(data?.orgId);
204
+ const canCreateOrgMcp = hasOrg && canManageOrg;
205
+ const activeScope: McpServerScope = scope === "org" ? "org" : "user";
206
+
207
+ const onCreateMcpServer = useCallback(
208
+ async (args: {
209
+ scope: McpServerScope;
210
+ name: string;
211
+ url: string;
212
+ headers?: Record<string, string>;
213
+ description?: string;
214
+ }) => {
215
+ if (args.scope === "org" && !canCreateOrgMcp) {
216
+ throw new Error(
217
+ "Only organization admins can add organization MCP servers.",
218
+ );
219
+ }
220
+ return createServer.mutateAsync(args);
221
+ },
222
+ [canCreateOrgMcp, createServer],
223
+ );
224
+
225
+ const removeServer = async (server: McpServer) => {
226
+ const key = `${server.scope}:${server.id}`;
227
+ if (deleteTarget !== key) {
228
+ setDeleteTarget(key);
229
+ return;
230
+ }
231
+ setError(null);
232
+ try {
233
+ await deleteServer.mutateAsync({ id: server.id, scope: server.scope });
234
+ if (
235
+ selectedServer?.id === server.id &&
236
+ selectedServer.scope === server.scope
237
+ ) {
238
+ setSelectedServer(null);
239
+ }
240
+ setDeleteTarget(null);
241
+ } catch (cause) {
242
+ setError(cause instanceof Error ? cause.message : String(cause));
243
+ }
244
+ };
245
+
246
+ const renderServer = (server: McpServer) => {
247
+ const key = `${server.scope}:${server.id}`;
248
+ const canDelete = server.scope === "user" || canManageOrg;
249
+ const selected =
250
+ selectedServer?.id === server.id && selectedServer.scope === server.scope;
251
+ return (
252
+ <div
253
+ key={key}
254
+ className={cn(
255
+ "rounded-lg border border-border bg-card p-3 transition-colors",
256
+ selected && "border-foreground/30 bg-accent/20",
257
+ )}
258
+ >
259
+ <div className="flex items-start gap-3">
260
+ <button
261
+ type="button"
262
+ onClick={() => setSelectedServer(selected ? null : server)}
263
+ className="min-w-0 flex-1 cursor-pointer text-start"
264
+ >
265
+ <div className="flex flex-wrap items-center gap-2">
266
+ <span className="truncate text-sm font-medium text-foreground">
267
+ {server.name}
268
+ </span>
269
+ <ScopeBadge scope={server.scope} />
270
+ {server.scope === activeScope && (
271
+ <span className="text-[10px] text-muted-foreground">
272
+ Selected scope
273
+ </span>
274
+ )}
275
+ </div>
276
+ <code className="mt-1 block truncate text-[11px] text-muted-foreground">
277
+ {server.url}
278
+ </code>
279
+ <div className="mt-2 flex items-center gap-2">
280
+ <ServerStatus server={server} />
281
+ {server.description && (
282
+ <span className="truncate text-[11px] text-muted-foreground/70">
283
+ {server.description}
284
+ </span>
285
+ )}
286
+ </div>
287
+ </button>
288
+ {canDelete && (
289
+ <button
290
+ type="button"
291
+ onClick={() => void removeServer(server)}
292
+ disabled={deleteServer.isPending}
293
+ className={cn(
294
+ "cursor-pointer rounded-md px-2 py-1 text-[11px] font-medium text-muted-foreground hover:bg-destructive/10 hover:text-destructive",
295
+ deleteTarget === key && "bg-destructive/10 text-destructive",
296
+ )}
297
+ >
298
+ {deleteTarget === key ? "Confirm" : "Delete"}
299
+ </button>
300
+ )}
301
+ </div>
302
+ {selected && (
303
+ <div className="mt-3 border-t border-border/70 pt-3">
304
+ <McpServerDetail server={server} />
305
+ </div>
306
+ )}
307
+ </div>
308
+ );
309
+ };
310
+
311
+ return (
312
+ <div className="space-y-8">
313
+ <section className="space-y-3">
314
+ <div className="flex flex-wrap items-start justify-between gap-3">
315
+ <div>
316
+ <h2 className="text-base font-semibold text-foreground">
317
+ MCP servers
318
+ </h2>
319
+ <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground">
320
+ Tools and services this agent can reach. The page scope controls
321
+ where new servers are saved.
322
+ </p>
323
+ </div>
324
+ <button
325
+ type="button"
326
+ onClick={() => {
327
+ setError(null);
328
+ setDialogOpen(true);
329
+ }}
330
+ className="cursor-pointer rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground hover:bg-primary/90"
331
+ >
332
+ {t("mcpIntegrations.connect")}
333
+ </button>
334
+ </div>
335
+ {error && (
336
+ <p className="rounded-md border border-destructive/20 bg-destructive/5 px-3 py-2 text-xs text-destructive">
337
+ {error}
338
+ </p>
339
+ )}
340
+ {serversQuery.isLoading ? (
341
+ <TabLoading />
342
+ ) : serversQuery.isError ? (
343
+ <p className="rounded-md border border-destructive/20 bg-destructive/5 px-3 py-2 text-xs text-destructive">
344
+ Could not load MCP servers.
345
+ </p>
346
+ ) : data && data.user.length + data.org.length > 0 ? (
347
+ <div className="space-y-2">
348
+ {data.user.map(renderServer)}
349
+ {data.org.map(renderServer)}
350
+ </div>
351
+ ) : (
352
+ <div className="rounded-lg border border-dashed border-border p-5 text-sm text-muted-foreground">
353
+ No MCP servers are connected yet.
354
+ </div>
355
+ )}
356
+ <McpIntegrationDialog
357
+ open={dialogOpen}
358
+ onOpenChange={setDialogOpen}
359
+ defaultScope={activeScope}
360
+ canCreateOrgMcp={canCreateOrgMcp}
361
+ hasOrg={hasOrg}
362
+ onCreateMcpServer={onCreateMcpServer}
363
+ />
364
+ </section>
365
+
366
+ <section className="space-y-3 border-t border-border/70 pt-6">
367
+ <div>
368
+ <h2 className="text-base font-semibold text-foreground">
369
+ Remote agents
370
+ </h2>
371
+ <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground">
372
+ Other agents this app can call through A2A.
373
+ </p>
374
+ </div>
375
+ <div className="rounded-lg border border-border bg-card p-4">
376
+ <AgentsSection />
377
+ </div>
378
+ </section>
379
+ </div>
380
+ );
381
+ }
382
+
383
+ interface AccessUrls {
384
+ appName: string;
385
+ appUrl: string;
386
+ mcpUrl: string;
387
+ connectUrl: string;
388
+ agentCardUrl: string;
389
+ }
390
+
391
+ function CopyField({ label, value }: { label: string; value: string }) {
392
+ const [copied, setCopied] = useState(false);
393
+ const copy = async () => {
394
+ try {
395
+ await navigator.clipboard.writeText(value);
396
+ setCopied(true);
397
+ window.setTimeout(() => setCopied(false), 1600);
398
+ } catch {
399
+ setCopied(false);
400
+ }
401
+ };
402
+ return (
403
+ <div className="flex min-w-0 items-center gap-2 rounded-md border border-border bg-muted/20 p-2">
404
+ <div className="min-w-0 flex-1">
405
+ <div className="text-[10px] font-semibold uppercase tracking-wide text-muted-foreground/70">
406
+ {label}
407
+ </div>
408
+ <code className="mt-1 block truncate text-xs text-foreground">
409
+ {value}
410
+ </code>
411
+ </div>
412
+ <button
413
+ type="button"
414
+ onClick={() => void copy()}
415
+ className="shrink-0 cursor-pointer rounded-md border border-border bg-background px-2.5 py-1.5 text-[11px] font-medium text-foreground hover:bg-accent"
416
+ >
417
+ {copied ? "Copied" : "Copy"}
418
+ </button>
419
+ </div>
420
+ );
421
+ }
422
+
423
+ function AccessTab({
424
+ appName: appNameProp,
425
+ }: AgentPageTabProps & { appName?: string }) {
426
+ const [urls, setUrls] = useState<AccessUrls | null>(null);
427
+ const [agentCardAvailable, setAgentCardAvailable] = useState(false);
428
+ const [activeGuide, setActiveGuide] = useState(MCP_CONNECT_GUIDES[0]?.id);
429
+
430
+ useEffect(() => {
431
+ const origin = window.location.origin;
432
+ const baseUrl = new URL(appPath("/"), origin).toString().replace(/\/$/, "");
433
+ const hostname = window.location.hostname || "app";
434
+ const metaSiteName = document
435
+ .querySelector('meta[property="og:site_name"]')
436
+ ?.getAttribute("content")
437
+ ?.trim();
438
+ const hostnameGuess =
439
+ hostname !== "localhost" && hostname !== "127.0.0.1"
440
+ ? hostname.split(".")[0]
441
+ : "";
442
+ const appName =
443
+ appNameProp?.trim() || metaSiteName || hostnameGuess || "this app";
444
+ const templateValues = {
445
+ appName,
446
+ appUrl: baseUrl,
447
+ mcpUrl: "",
448
+ serverId: `agent-native-${hostname}`,
449
+ } satisfies McpConnectTemplateValues;
450
+ setUrls({
451
+ appName,
452
+ appUrl: baseUrl,
453
+ mcpUrl: interpolateMcpConnectTemplate(
454
+ MCP_CONNECT_MCP_URL_TEMPLATE,
455
+ templateValues,
456
+ ),
457
+ connectUrl: new URL(appPath("/mcp/connect"), origin).toString(),
458
+ agentCardUrl: new URL(
459
+ appPath("/.well-known/agent-card.json"),
460
+ origin,
461
+ ).toString(),
462
+ });
463
+ }, [appNameProp]);
464
+
465
+ useEffect(() => {
466
+ if (!urls) return;
467
+ let cancelled = false;
468
+ fetch(urls.agentCardUrl)
469
+ .then((response) => {
470
+ if (!cancelled) setAgentCardAvailable(response.ok);
471
+ })
472
+ .catch(() => {
473
+ if (!cancelled) setAgentCardAvailable(false);
474
+ });
475
+ return () => {
476
+ cancelled = true;
477
+ };
478
+ }, [urls]);
479
+
480
+ const templateValues: McpConnectTemplateValues | null = urls
481
+ ? {
482
+ appName: urls.appName,
483
+ appUrl: urls.appUrl,
484
+ mcpUrl: urls.mcpUrl,
485
+ serverId: `agent-native-${window.location.hostname || "app"}`,
486
+ }
487
+ : null;
488
+ const guide =
489
+ MCP_CONNECT_GUIDES.find((item) => item.id === activeGuide) ??
490
+ MCP_CONNECT_GUIDES[0];
491
+
492
+ return (
493
+ <div className="space-y-6">
494
+ <div>
495
+ <h2 className="text-base font-semibold text-foreground">Access</h2>
496
+ <p className="mt-1 max-w-2xl text-sm leading-relaxed text-muted-foreground">
497
+ Choose which external clients can talk to this app&apos;s agent and
498
+ follow the setup for each one. Grants, scopes, and revocation will
499
+ live here in a future pass.
500
+ </p>
501
+ </div>
502
+ {urls ? (
503
+ <>
504
+ <CopyField label="MCP URL" value={urls.mcpUrl} />
505
+ {agentCardAvailable && (
506
+ <CopyField label="A2A agent card" value={urls.agentCardUrl} />
507
+ )}
508
+ <section className="space-y-3 rounded-lg border border-border bg-card p-4">
509
+ <div>
510
+ <h3 className="text-sm font-semibold text-foreground">
511
+ Client setup
512
+ </h3>
513
+ <p className="mt-1 text-xs text-muted-foreground">
514
+ These instructions are also available on the full connect page.
515
+ </p>
516
+ </div>
517
+ <div
518
+ className="flex gap-1 overflow-x-auto border-b border-border pb-2"
519
+ role="tablist"
520
+ aria-label="Choose your AI assistant"
521
+ >
522
+ {MCP_CONNECT_GUIDES.map((item) => (
523
+ <button
524
+ key={item.id}
525
+ type="button"
526
+ role="tab"
527
+ aria-selected={item.id === guide?.id}
528
+ onClick={() => setActiveGuide(item.id)}
529
+ className={cn(
530
+ "shrink-0 cursor-pointer rounded-md px-2.5 py-1.5 text-xs font-medium",
531
+ item.id === guide?.id
532
+ ? "bg-accent text-foreground"
533
+ : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
534
+ )}
535
+ >
536
+ {item.label}
537
+ </button>
538
+ ))}
539
+ </div>
540
+ {guide && templateValues && (
541
+ <div className="space-y-3 pt-1" role="tabpanel">
542
+ {guide.steps?.length ? (
543
+ <ol className="list-decimal space-y-2 ps-5 text-xs leading-relaxed text-muted-foreground">
544
+ {guide.steps.map((step) => (
545
+ <li key={step}>
546
+ {interpolateMcpConnectTemplate(step, templateValues)}
547
+ </li>
548
+ ))}
549
+ </ol>
550
+ ) : null}
551
+ {guide.intro && (
552
+ <p className="text-xs text-muted-foreground">
553
+ {interpolateMcpConnectTemplate(guide.intro, templateValues)}
554
+ </p>
555
+ )}
556
+ {guide.commandTemplate && (
557
+ <CopyField
558
+ label="Command"
559
+ value={interpolateMcpConnectTemplate(
560
+ guide.commandTemplate,
561
+ templateValues,
562
+ )}
563
+ />
564
+ )}
565
+ {guide.configTemplate && (
566
+ <CopyField
567
+ label="MCP config"
568
+ value={interpolateMcpConnectTemplate(
569
+ guide.configTemplate,
570
+ templateValues,
571
+ )}
572
+ />
573
+ )}
574
+ {guide.action?.kind === "link" && guide.action.href && (
575
+ <a
576
+ href={guide.action.href}
577
+ target="_blank"
578
+ rel="noopener noreferrer"
579
+ className="inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent"
580
+ >
581
+ {guide.action.label}
582
+ <IconExternalLink className="size-3.5" />
583
+ </a>
584
+ )}
585
+ {guide.note && (
586
+ <p className="text-xs leading-relaxed text-muted-foreground">
587
+ {interpolateMcpConnectTemplate(guide.note, templateValues)}
588
+ </p>
589
+ )}
590
+ </div>
591
+ )}
592
+ </section>
593
+ <section className="rounded-lg border border-border/70 bg-muted/10 p-4">
594
+ <h3 className="text-sm font-semibold text-foreground">
595
+ {MCP_STATIC_TOKEN_FALLBACK.title}
596
+ </h3>
597
+ <p className="mt-1 text-xs leading-relaxed text-muted-foreground">
598
+ {MCP_STATIC_TOKEN_FALLBACK.state}. Open the connect page to create
599
+ a token for clients that cannot complete OAuth.
600
+ </p>
601
+ <a
602
+ href={urls.connectUrl}
603
+ className="mt-3 inline-flex cursor-pointer items-center gap-1.5 rounded-md border border-border bg-background px-3 py-1.5 text-xs font-medium text-foreground hover:bg-accent"
604
+ >
605
+ Open full connect page
606
+ <IconExternalLink className="size-3.5" />
607
+ </a>
608
+ </section>
609
+ </>
610
+ ) : (
611
+ <TabLoading />
612
+ )}
613
+ </div>
614
+ );
615
+ }
616
+
617
+ export interface AgentTabsPageProps {
618
+ /**
619
+ * Human-readable app name used in the Access tab's connect instructions
620
+ * (e.g. "name it Mail"). Falls back to the `og:site_name` meta tag, then a
621
+ * hostname-derived guess — never `document.title`, which this page owns.
622
+ */
623
+ appName?: string;
624
+ extraTabs?: SettingsTabItem[];
625
+ defaultTab?: string;
626
+ className?: string;
627
+ /** Whether to render the Agent page search box. Defaults to true. */
628
+ enableSearch?: boolean;
629
+ searchPlaceholder?: string;
630
+ hiddenTabs?: string[];
631
+ value?: string;
632
+ onValueChange?: (tabId: string) => void;
633
+ }
634
+
635
+ export function AgentTabsPage({
636
+ appName,
637
+ extraTabs = [],
638
+ defaultTab = "context",
639
+ className,
640
+ enableSearch = true,
641
+ searchPlaceholder = "Search agent settings",
642
+ hiddenTabs = [],
643
+ value,
644
+ onValueChange,
645
+ }: AgentTabsPageProps) {
646
+ const { data: org, isLoading: orgLoading } = useOrg();
647
+ const hasOrg = Boolean(org?.orgId);
648
+ const canManageOrg =
649
+ !org?.orgId || org.role === "owner" || org.role === "admin";
650
+ const [scope, setScope] = useState<AgentPageScope>(initialScope);
651
+ const scopeInitialized = useRef(false);
652
+ const rootRef = useRef<HTMLDivElement | null>(null);
653
+ const searchRef = useRef<HTMLInputElement | null>(null);
654
+ const [query, setQuery] = useState("");
655
+ const normalizedHiddenTabs = useMemo(
656
+ () => new Set(hiddenTabs.map((tab) => normalizeTabId(tab)).filter(Boolean)),
657
+ [hiddenTabs],
658
+ );
659
+
660
+ const tabs = useMemo<SettingsTabItem[]>(
661
+ () => [
662
+ {
663
+ id: "context",
664
+ label: "Context",
665
+ icon: IconBrain,
666
+ keywords: "influence provenance prompt",
667
+ content: (
668
+ <div className="space-y-4">
669
+ <div>
670
+ <h1 className="text-lg font-semibold text-foreground">Context</h1>
671
+ <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
672
+ See what can influence this agent and why.
673
+ </p>
674
+ </div>
675
+ <div className="min-h-[220px] rounded-lg border border-dashed border-border/70 bg-muted/10 p-4">
676
+ <Suspense fallback={<TabLoading />}>
677
+ <AgentContextTab scope={scope} canManageOrg={canManageOrg} />
678
+ </Suspense>
679
+ </div>
680
+ </div>
681
+ ),
682
+ },
683
+ {
684
+ id: "files",
685
+ label: "Files",
686
+ icon: IconFolder,
687
+ keywords: "workspace resources instructions skills",
688
+ content: (
689
+ <div className="space-y-4">
690
+ <div>
691
+ <h1 className="text-lg font-semibold text-foreground">Files</h1>
692
+ <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
693
+ Workspace files that shape the agent&apos;s context.
694
+ </p>
695
+ </div>
696
+ <Suspense fallback={<TabLoading />}>
697
+ <FilesTab scope={scope} canManageOrg={canManageOrg} />
698
+ </Suspense>
699
+ </div>
700
+ ),
701
+ },
702
+ {
703
+ id: "connections",
704
+ label: "Connections",
705
+ icon: IconPlugConnected,
706
+ keywords: "mcp servers tools remote agents a2a",
707
+ searchEntries: [
708
+ { id: "mcp-servers", label: "MCP servers", keywords: "tools" },
709
+ { id: "remote-agents", label: "Remote agents", keywords: "a2a" },
710
+ ],
711
+ content: <ConnectionsTab scope={scope} canManageOrg={canManageOrg} />,
712
+ },
713
+ {
714
+ id: "jobs",
715
+ label: "Jobs",
716
+ icon: IconClock,
717
+ keywords: "scheduled automations recurring",
718
+ content: (
719
+ <div className="space-y-4">
720
+ <div>
721
+ <h1 className="text-lg font-semibold text-foreground">Jobs</h1>
722
+ <p className="mt-1 text-sm leading-relaxed text-muted-foreground">
723
+ Scheduled work that can run with this agent.
724
+ </p>
725
+ </div>
726
+ <div className="min-h-[220px] rounded-lg border border-dashed border-border/70 bg-muted/10 p-4">
727
+ <Suspense fallback={<TabLoading />}>
728
+ <AgentJobsTab scope={scope} canManageOrg={canManageOrg} />
729
+ </Suspense>
730
+ </div>
731
+ </div>
732
+ ),
733
+ },
734
+ {
735
+ id: "access",
736
+ label: "Access",
737
+ icon: IconShieldLock,
738
+ keywords: "external clients oauth a2a exposure",
739
+ searchEntries: [
740
+ {
741
+ id: "mcp-connect",
742
+ label: "External client setup",
743
+ keywords: "oauth connect",
744
+ },
745
+ {
746
+ id: "a2a-agent-card",
747
+ label: "A2A agent card",
748
+ keywords: "agent card",
749
+ },
750
+ ],
751
+ content: (
752
+ <AccessTab
753
+ scope={scope}
754
+ canManageOrg={canManageOrg}
755
+ appName={appName}
756
+ />
757
+ ),
758
+ },
759
+ ...extraTabs,
760
+ ],
761
+ [canManageOrg, extraTabs, scope],
762
+ );
763
+ const visibleTabs = useMemo(
764
+ () => tabs.filter((tab) => !normalizedHiddenTabs.has(tab.id)),
765
+ [normalizedHiddenTabs, tabs],
766
+ );
767
+ const fallbackTab = visibleTabs.some((tab) => tab.id === defaultTab)
768
+ ? defaultTab
769
+ : (visibleTabs[0]?.id ?? "context");
770
+ const [internalTab, setInternalTab] = useState(() => {
771
+ if (typeof window === "undefined") return fallbackTab;
772
+ return resolveTabId(visibleTabs, window.location.hash) ?? fallbackTab;
773
+ });
774
+ const isControlled = value !== undefined;
775
+ const activeTab = isControlled ? value : internalTab;
776
+ const selectedTab =
777
+ visibleTabs.find((tab) => tab.id === activeTab) ?? visibleTabs[0];
778
+ const tabGroups = useMemo(() => {
779
+ const groups: Array<{ id: string; tabs: SettingsTabItem[] }> = [];
780
+ for (const tab of visibleTabs) {
781
+ const groupId = tab.group ?? "agent";
782
+ const previous = groups.at(-1);
783
+ if (previous?.id === groupId) previous.tabs.push(tab);
784
+ else groups.push({ id: groupId, tabs: [tab] });
785
+ }
786
+ return groups;
787
+ }, [visibleTabs]);
788
+
789
+ useEffect(() => {
790
+ if (scopeInitialized.current || orgLoading) return;
791
+ scopeInitialized.current = true;
792
+ if (!hasOrg && scope === "org") setScope("user");
793
+ }, [hasOrg, orgLoading, scope]);
794
+
795
+ useEffect(() => {
796
+ if (orgLoading) return;
797
+ const resolvedScope = hasOrg ? scope : "user";
798
+ if (resolvedScope !== scope) {
799
+ setScope(resolvedScope);
800
+ return;
801
+ }
802
+ updateScopeUrl(resolvedScope);
803
+ }, [hasOrg, orgLoading, scope]);
804
+
805
+ useEffect(() => {
806
+ if (!isControlled && !visibleTabs.some((tab) => tab.id === internalTab)) {
807
+ setInternalTab(fallbackTab);
808
+ }
809
+ }, [fallbackTab, internalTab, isControlled, visibleTabs]);
810
+
811
+ useEffect(() => {
812
+ if (isControlled) return;
813
+ const onHashChange = () => {
814
+ const next = resolveTabId(visibleTabs, window.location.hash);
815
+ if (next) setInternalTab(next);
816
+ };
817
+ window.addEventListener("hashchange", onHashChange);
818
+ return () => window.removeEventListener("hashchange", onHashChange);
819
+ }, [isControlled, visibleTabs]);
820
+
821
+ const changeTab = useCallback(
822
+ (tabId: string) => {
823
+ if (!isControlled) setInternalTab(tabId);
824
+ onValueChange?.(tabId);
825
+ },
826
+ [isControlled, onValueChange],
827
+ );
828
+
829
+ const changeScope = (next: AgentPageScope) => {
830
+ if (next === "org" && !hasOrg) return;
831
+ setScope(next);
832
+ try {
833
+ localStorage.setItem("agent-page-scope", next);
834
+ } catch {}
835
+ updateScopeUrl(next);
836
+ };
837
+
838
+ const searchIndex = useMemo(() => {
839
+ const entries: Array<
840
+ SettingsSearchEntry & { tabId: string; haystack: string }
841
+ > = [];
842
+ for (const tab of visibleTabs) {
843
+ entries.push({
844
+ id: `tab:${tab.id}`,
845
+ label: tab.label,
846
+ keywords: tab.keywords,
847
+ tabId: tab.id,
848
+ haystack: `${tab.label} ${tab.keywords ?? ""}`.toLowerCase(),
849
+ });
850
+ for (const entry of tab.searchEntries ?? []) {
851
+ entries.push({
852
+ ...entry,
853
+ tabId: entry.tabId ?? tab.id,
854
+ haystack:
855
+ `${entry.label} ${entry.keywords ?? ""} ${entry.description ?? ""} ${tab.label}`.toLowerCase(),
856
+ });
857
+ }
858
+ }
859
+ return entries;
860
+ }, [visibleTabs]);
861
+ const results = useMemo(() => {
862
+ const terms = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
863
+ if (!terms.length) return [];
864
+ return searchIndex
865
+ .filter((entry) => terms.every((term) => entry.haystack.includes(term)))
866
+ .sort((a, b) => a.label.localeCompare(b.label));
867
+ }, [query, searchIndex]);
868
+
869
+ const selectSearchResult = useCallback(
870
+ (entry: (typeof searchIndex)[number]) => {
871
+ changeTab(entry.tabId);
872
+ setQuery("");
873
+ if (isControlled || typeof window === "undefined") return;
874
+ if (entry.hash) {
875
+ window.history.pushState(
876
+ null,
877
+ "",
878
+ `${window.location.pathname}${window.location.search}#${entry.hash.replace(/^#/, "")}`,
879
+ );
880
+ window.dispatchEvent(new Event("hashchange"));
881
+ } else {
882
+ updateTabHash(entry.tabId);
883
+ }
884
+ },
885
+ [changeTab, isControlled],
886
+ );
887
+
888
+ return (
889
+ <div
890
+ ref={rootRef}
891
+ className={cn(
892
+ "flex h-full min-h-0 w-full flex-col overflow-hidden bg-background",
893
+ className,
894
+ )}
895
+ >
896
+ <header className="flex shrink-0 flex-wrap items-center justify-between gap-3 border-b border-border px-4 py-3 sm:px-6">
897
+ <div>
898
+ <h1 className="text-base font-semibold text-foreground">Agent</h1>
899
+ <p className="mt-0.5 text-xs text-muted-foreground">
900
+ What can influence this agent, and why.
901
+ </p>
902
+ </div>
903
+ <div className="flex items-center gap-2">
904
+ <span className="text-xs text-muted-foreground">Scope</span>
905
+ <div
906
+ className="flex rounded-md border border-border bg-muted/30 p-0.5"
907
+ role="radiogroup"
908
+ aria-label="Agent scope"
909
+ >
910
+ <button
911
+ type="button"
912
+ role="radio"
913
+ aria-checked={scope !== "org" || !hasOrg}
914
+ onClick={() => changeScope("user")}
915
+ className={cn(
916
+ "cursor-pointer rounded px-2.5 py-1.5 text-xs font-medium",
917
+ scope !== "org" || !hasOrg
918
+ ? "bg-background text-foreground shadow-sm"
919
+ : "text-muted-foreground hover:text-foreground",
920
+ )}
921
+ >
922
+ Personal
923
+ </button>
924
+ {hasOrg && (
925
+ <Tooltip>
926
+ <TooltipTrigger asChild>
927
+ <button
928
+ type="button"
929
+ role="radio"
930
+ aria-checked={scope === "org"}
931
+ onClick={() => changeScope("org")}
932
+ className={cn(
933
+ "cursor-pointer rounded px-2.5 py-1.5 text-xs font-medium",
934
+ scope === "org"
935
+ ? "bg-background text-foreground shadow-sm"
936
+ : "text-muted-foreground hover:text-foreground",
937
+ )}
938
+ >
939
+ Organization
940
+ </button>
941
+ </TooltipTrigger>
942
+ {!canManageOrg && (
943
+ <TooltipContent>
944
+ Organization settings are read-only for members.
945
+ </TooltipContent>
946
+ )}
947
+ </Tooltip>
948
+ )}
949
+ </div>
950
+ </div>
951
+ </header>
952
+ <div className="flex min-h-0 flex-1 flex-col sm:flex-row">
953
+ <div className="flex shrink-0 flex-col gap-2 bg-background p-2 sm:min-h-0 sm:w-56 sm:overflow-y-auto sm:p-3">
954
+ {enableSearch ? (
955
+ <div className="relative sm:mb-1">
956
+ <IconSearch className="pointer-events-none absolute start-2.5 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
957
+ <input
958
+ ref={searchRef}
959
+ type="search"
960
+ value={query}
961
+ onChange={(event) => setQuery(event.target.value)}
962
+ onKeyDown={(event) => {
963
+ if (event.key === "Escape") setQuery("");
964
+ if (event.key === "Enter" && results[0]) {
965
+ event.preventDefault();
966
+ selectSearchResult(results[0]);
967
+ }
968
+ }}
969
+ placeholder={searchPlaceholder}
970
+ aria-label={searchPlaceholder}
971
+ className="h-8 w-full rounded-md border border-border bg-background ps-8 pe-7 text-[13px] text-foreground outline-none transition-colors placeholder:text-muted-foreground focus:border-foreground/30 focus:ring-2 focus:ring-accent/40"
972
+ />
973
+ {query && (
974
+ <button
975
+ type="button"
976
+ onClick={() => setQuery("")}
977
+ aria-label="Clear search"
978
+ className="absolute end-1.5 top-1/2 flex size-5 -translate-y-1/2 cursor-pointer items-center justify-center rounded text-muted-foreground hover:bg-accent/60 hover:text-foreground"
979
+ >
980
+ <IconX className="size-3.5" />
981
+ </button>
982
+ )}
983
+ </div>
984
+ ) : null}
985
+ {query.trim() ? (
986
+ <div
987
+ role="listbox"
988
+ aria-label="Agent search results"
989
+ className="flex flex-col gap-0.5"
990
+ >
991
+ {results.length === 0 ? (
992
+ <p className="px-2 py-6 text-center text-xs text-muted-foreground">
993
+ No matching agent settings
994
+ </p>
995
+ ) : (
996
+ results.map((entry) => (
997
+ <button
998
+ key={entry.id}
999
+ type="button"
1000
+ role="option"
1001
+ onClick={() => selectSearchResult(entry)}
1002
+ className="flex cursor-pointer items-start gap-2 rounded-md px-2.5 py-2 text-start text-sm text-foreground hover:bg-accent/60"
1003
+ >
1004
+ <span className="flex min-w-0 flex-col">
1005
+ <span className="truncate font-medium">
1006
+ {entry.label}
1007
+ </span>
1008
+ <span className="truncate text-[11px] text-muted-foreground">
1009
+ {entry.description ?? entry.tabId}
1010
+ </span>
1011
+ </span>
1012
+ </button>
1013
+ ))
1014
+ )}
1015
+ </div>
1016
+ ) : (
1017
+ <nav
1018
+ aria-label="Agent sections"
1019
+ role="tablist"
1020
+ className="flex gap-1 overflow-x-auto sm:flex-col sm:overflow-x-visible"
1021
+ >
1022
+ {tabGroups.map((group, groupIndex) => (
1023
+ <div
1024
+ key={group.id}
1025
+ className={cn(
1026
+ "contents sm:block",
1027
+ groupIndex > 0 &&
1028
+ "sm:mt-2 sm:border-t sm:border-border/60 sm:pt-2",
1029
+ )}
1030
+ >
1031
+ <div className="contents sm:flex sm:flex-col sm:gap-1">
1032
+ {group.tabs.map((tab) => {
1033
+ const Icon = tab.icon as SettingsTabIcon | undefined;
1034
+ const selected = tab.id === selectedTab?.id;
1035
+ return (
1036
+ <button
1037
+ key={tab.id}
1038
+ type="button"
1039
+ role="tab"
1040
+ aria-selected={selected}
1041
+ aria-controls={`agent-tabpanel-${tab.id}`}
1042
+ onClick={() => {
1043
+ changeTab(tab.id);
1044
+ if (!isControlled) updateTabHash(tab.id);
1045
+ }}
1046
+ className={cn(
1047
+ "flex min-h-9 shrink-0 cursor-pointer items-center gap-2 rounded-md px-3 py-2 text-start text-sm font-medium transition-colors sm:w-full",
1048
+ selected
1049
+ ? "bg-accent text-foreground"
1050
+ : "text-muted-foreground hover:bg-accent/50 hover:text-foreground",
1051
+ )}
1052
+ >
1053
+ {Icon ? (
1054
+ <Icon
1055
+ className={cn(
1056
+ "size-4 shrink-0",
1057
+ selected
1058
+ ? "text-foreground"
1059
+ : "text-muted-foreground",
1060
+ )}
1061
+ />
1062
+ ) : null}
1063
+ <span className="truncate">{tab.label}</span>
1064
+ </button>
1065
+ );
1066
+ })}
1067
+ </div>
1068
+ </div>
1069
+ ))}
1070
+ </nav>
1071
+ )}
1072
+ </div>
1073
+ <div
1074
+ id={`agent-tabpanel-${selectedTab?.id ?? "context"}`}
1075
+ role="tabpanel"
1076
+ aria-labelledby={`agent-tab-${selectedTab?.id ?? "context"}`}
1077
+ className="min-h-0 min-w-0 flex-1 overflow-y-auto p-4 sm:p-6"
1078
+ >
1079
+ {selectedTab?.content ?? <EmptySlot label="Agent section" />}
1080
+ </div>
1081
+ </div>
1082
+ </div>
1083
+ );
1084
+ }