@agent-native/core 0.90.11 → 0.91.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 (315) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +22 -0
  3. package/corpus/core/docs/content/toolkit-settings.mdx +56 -3
  4. package/corpus/core/package.json +2 -1
  5. package/corpus/core/src/agent/run-ownership.ts +29 -1
  6. package/corpus/core/src/changelog/parse.ts +78 -0
  7. package/corpus/core/src/chat-threads/schema.ts +43 -0
  8. package/corpus/core/src/chat-threads/store.ts +144 -12
  9. package/corpus/core/src/cli/workspace-dev.ts +25 -5
  10. package/corpus/core/src/client/AgentPanel.tsx +79 -14
  11. package/corpus/core/src/client/AssistantChat.tsx +359 -425
  12. package/corpus/core/src/client/analytics-session.ts +70 -0
  13. package/corpus/core/src/client/analytics.ts +175 -49
  14. package/corpus/core/src/client/chat/message-components.tsx +22 -2
  15. package/corpus/core/src/client/components/ui/message-scroller.tsx +131 -0
  16. package/corpus/core/src/client/conversation/AgentConversation.tsx +63 -41
  17. package/corpus/core/src/client/error-capture.ts +616 -0
  18. package/corpus/core/src/client/index.ts +12 -0
  19. package/corpus/core/src/client/session-replay.ts +57 -7
  20. package/corpus/core/src/client/settings/SettingsPanel.tsx +123 -1
  21. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +292 -45
  22. package/corpus/core/src/client/settings/index.ts +1 -0
  23. package/corpus/core/src/client/sharing/ShareButton.tsx +14 -11
  24. package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -10
  25. package/corpus/core/src/mcp/builtin-tools.ts +29 -2
  26. package/corpus/core/src/server/agent-chat-plugin.ts +174 -79
  27. package/corpus/core/src/server/onboarding-html.ts +28 -16
  28. package/corpus/core/src/sharing/actions/list-resource-shares.ts +45 -1
  29. package/corpus/core/src/sharing/actions/share-resource.ts +9 -0
  30. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  31. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  32. package/corpus/core/src/vite/client.ts +217 -35
  33. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +8 -7
  34. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +14 -10
  35. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +15 -15
  36. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +1 -0
  37. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -22
  38. package/corpus/templates/analytics/.agents/skills/data-programs/SKILL.md +114 -61
  39. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -0
  40. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +7 -7
  41. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +1 -0
  42. package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +1 -1
  43. package/corpus/templates/analytics/.builder/skills/bigquery/SKILL.md +17 -17
  44. package/corpus/templates/analytics/.builder/skills/gcloud/SKILL.md +5 -5
  45. package/corpus/templates/analytics/.builder/skills/github/SKILL.md +7 -7
  46. package/corpus/templates/analytics/.builder/skills/gong/SKILL.md +12 -12
  47. package/corpus/templates/analytics/.builder/skills/grafana/SKILL.md +7 -7
  48. package/corpus/templates/analytics/.builder/skills/sentry/SKILL.md +7 -7
  49. package/corpus/templates/analytics/.builder/skills/slack/SKILL.md +6 -6
  50. package/corpus/templates/analytics/.builder/skills/stripe/SKILL.md +7 -7
  51. package/corpus/templates/analytics/AGENTS.md +47 -2
  52. package/corpus/templates/analytics/actions/add-status-page-monitor.ts +36 -0
  53. package/corpus/templates/analytics/actions/capture-test-error.ts +33 -0
  54. package/corpus/templates/analytics/actions/delete-monitor.ts +24 -0
  55. package/corpus/templates/analytics/actions/delete-status-page.ts +23 -0
  56. package/corpus/templates/analytics/actions/get-error-issue.ts +36 -0
  57. package/corpus/templates/analytics/actions/get-monitor-stats.ts +55 -0
  58. package/corpus/templates/analytics/actions/get-monitor.ts +27 -0
  59. package/corpus/templates/analytics/actions/get-public-status-page.ts +41 -0
  60. package/corpus/templates/analytics/actions/get-status-page.ts +29 -0
  61. package/corpus/templates/analytics/actions/list-error-issues.ts +40 -0
  62. package/corpus/templates/analytics/actions/list-monitors.ts +21 -0
  63. package/corpus/templates/analytics/actions/list-status-pages.ts +21 -0
  64. package/corpus/templates/analytics/actions/match-error-issues.ts +51 -0
  65. package/corpus/templates/analytics/actions/navigate.ts +58 -4
  66. package/corpus/templates/analytics/actions/remove-status-page-monitor.ts +22 -0
  67. package/corpus/templates/analytics/actions/reorder-status-page-monitors.ts +25 -0
  68. package/corpus/templates/analytics/actions/resolve-error-issue.ts +38 -0
  69. package/corpus/templates/analytics/actions/run-monitor-check.ts +23 -0
  70. package/corpus/templates/analytics/actions/save-analysis.ts +1 -0
  71. package/corpus/templates/analytics/actions/save-monitor.ts +145 -0
  72. package/corpus/templates/analytics/actions/save-status-page.ts +47 -0
  73. package/corpus/templates/analytics/actions/view-screen.ts +212 -0
  74. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  75. package/corpus/templates/analytics/app/components/layout/Layout.tsx +10 -1
  76. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +134 -39
  77. package/corpus/templates/analytics/app/components/monitoring/PublicStatusView.tsx +351 -0
  78. package/corpus/templates/analytics/app/components/monitoring/ResponseTimeChart.tsx +174 -0
  79. package/corpus/templates/analytics/app/components/monitoring/UptimeStatCards.tsx +85 -0
  80. package/corpus/templates/analytics/app/components/monitoring/UptimeTimelineBars.tsx +106 -0
  81. package/corpus/templates/analytics/app/components/monitoring/chart-utils.ts +101 -0
  82. package/corpus/templates/analytics/app/components/monitoring/index.ts +28 -0
  83. package/corpus/templates/analytics/app/components/monitoring/types.ts +42 -0
  84. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +34 -0
  85. package/corpus/templates/analytics/app/i18n/zh-TW.ts +6 -0
  86. package/corpus/templates/analytics/app/i18n-data.ts +33 -0
  87. package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -0
  88. package/corpus/templates/analytics/app/pages/Settings.tsx +77 -9
  89. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -3
  90. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +7 -0
  91. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +205 -0
  92. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +100 -0
  93. package/corpus/templates/analytics/app/pages/monitoring/UptimePanel.tsx +475 -0
  94. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +372 -0
  95. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +360 -0
  96. package/corpus/templates/analytics/app/pages/monitoring/errors/Sparkline.tsx +55 -0
  97. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +106 -0
  98. package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +75 -0
  99. package/corpus/templates/analytics/app/pages/monitoring/errors/utils.ts +132 -0
  100. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +535 -0
  101. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +1026 -0
  102. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorList.tsx +311 -0
  103. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +235 -0
  104. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPageEditor.tsx +661 -0
  105. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPagesView.tsx +355 -0
  106. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/i18n.ts +106 -0
  107. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/slug.ts +24 -0
  108. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/types.ts +20 -0
  109. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-summary.ts +87 -0
  110. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +161 -0
  111. package/corpus/templates/analytics/app/pages/monitoring/uptime/utils.ts +210 -0
  112. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +76 -1
  113. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +74 -9
  114. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +197 -20
  115. package/corpus/templates/analytics/app/root.tsx +33 -1
  116. package/corpus/templates/analytics/app/routes/monitoring._index.tsx +10 -0
  117. package/corpus/templates/analytics/app/routes/status.$slug.tsx +118 -0
  118. package/corpus/templates/analytics/changelog/2026-07-08-add-uptime-monitors-that-ping-your-urls-and-alert-you-when-t.md +6 -0
  119. package/corpus/templates/analytics/changelog/2026-07-08-analytics-chats-keep-large-data-dictionaries-compact-so-prov.md +6 -0
  120. package/corpus/templates/analytics/changelog/2026-07-08-create-public-status-pages-that-share-the-live-health-of-cho.md +6 -0
  121. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-extension-panels-use-clearer-open-copy.md +6 -0
  122. package/corpus/templates/analytics/changelog/2026-07-08-error-capture.md +6 -0
  123. package/corpus/templates/analytics/changelog/2026-07-08-fixed-single-panel-dashboard-drags-so-they-no-longer-rewrite.md +6 -0
  124. package/corpus/templates/analytics/changelog/2026-07-08-jump-straight-from-an-error-in-a-session-recording-to-its-fu.md +6 -0
  125. package/corpus/templates/analytics/changelog/2026-07-08-monitor-detail-and-list-now-show-colorful-uptime-timelines-r.md +6 -0
  126. package/corpus/templates/analytics/changelog/2026-07-08-pinned-ask-chats-now-show-a-pin-icon-in-the-sidebar.md +6 -0
  127. package/corpus/templates/analytics/changelog/2026-07-08-publish-a-public-uptime-status-page-with-colorful-uptime-tim.md +6 -0
  128. package/corpus/templates/analytics/changelog/2026-07-08-sessions-now-hide-visitor-emails-in-demo-mode-and-show-clean.md +6 -0
  129. package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +5 -0
  130. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +5 -0
  131. package/corpus/templates/analytics/changelog/2026-07-08-the-uptime-monitor-list-now-has-a-current-status-overview-wi.md +6 -0
  132. package/corpus/templates/analytics/docs/error-capture.md +172 -0
  133. package/corpus/templates/analytics/docs/uptime-monitoring.md +150 -0
  134. package/corpus/templates/analytics/server/db/schema-errors.ts +150 -0
  135. package/corpus/templates/analytics/server/db/schema-monitoring.ts +177 -0
  136. package/corpus/templates/analytics/server/db/schema.ts +6 -0
  137. package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +123 -0
  138. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +41 -0
  139. package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +55 -12
  140. package/corpus/templates/analytics/server/lib/error-capture.ts +1331 -0
  141. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +47 -1
  142. package/corpus/templates/analytics/server/lib/monitor-stats.ts +494 -0
  143. package/corpus/templates/analytics/server/lib/status-pages.ts +798 -0
  144. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +1665 -0
  145. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -37
  146. package/corpus/templates/analytics/server/plugins/auth.ts +11 -1
  147. package/corpus/templates/analytics/server/plugins/db.ts +408 -0
  148. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +63 -0
  149. package/corpus/templates/assets/app/routes/_index.tsx +1 -1
  150. package/corpus/templates/assets/app/routes/settings.tsx +22 -1
  151. package/corpus/templates/assets/changelog/2026-07-08-recent-drafts-keep-comfortable-side-padding.md +6 -0
  152. package/corpus/templates/assets/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  153. package/corpus/templates/brain/app/routes/settings.tsx +43 -5
  154. package/corpus/templates/brain/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  155. package/corpus/templates/calendar/app/pages/Settings.tsx +44 -6
  156. package/corpus/templates/calendar/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  157. package/corpus/templates/chat/app/routes/settings.tsx +16 -1
  158. package/corpus/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  159. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +79 -37
  160. package/corpus/templates/clips/app/components/player/player-controls.tsx +35 -0
  161. package/corpus/templates/clips/app/components/player/video-player.tsx +162 -18
  162. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +63 -6
  163. package/corpus/templates/clips/app/routes/record.tsx +18 -1
  164. package/corpus/templates/clips/changelog/2026-07-07-shared-clips-start-playback-reliably-from-the-first-play-click.md +6 -0
  165. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-camera-and-microphone-menus-now-show-which-.md +6 -0
  166. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-copies-new-clip-links-after-saving.md +6 -0
  167. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-recordings-now-continue-after-granting-came.md +6 -0
  168. package/corpus/templates/clips/changelog/2026-07-08-mobile-shared-clips-now-have-loom-style-tap-and-skip-controls.md +6 -0
  169. package/corpus/templates/clips/changelog/2026-07-08-recordings-and-uploads-copy-links-after-saving.md +6 -0
  170. package/corpus/templates/clips/changelog/2026-07-08-settings-are-cleaner-searchable-and-collapse-s3-with-builder.md +5 -0
  171. package/corpus/templates/clips/changelog/2026-07-08-transcripts-are-more-reliable-for-longer-desktop-recordings-.md +6 -0
  172. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -0
  173. package/corpus/templates/clips/chrome-extension/src/background.ts +162 -13
  174. package/corpus/templates/clips/chrome-extension/src/content-script.ts +14 -0
  175. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +36 -0
  176. package/corpus/templates/clips/chrome-extension/src/overlay.ts +99 -25
  177. package/corpus/templates/clips/chrome-extension/src/permission.ts +63 -14
  178. package/corpus/templates/clips/chrome-extension/src/popup.ts +123 -23
  179. package/corpus/templates/content/actions/_builder-cms-read-client.ts +47 -0
  180. package/corpus/templates/content/actions/_database-source-utils.ts +33 -4
  181. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +214 -29
  182. package/corpus/templates/content/actions/attach-content-database-source.ts +3 -0
  183. package/corpus/templates/content/actions/change-content-database-source-role.ts +1 -0
  184. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +42 -32
  185. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +352 -81
  186. package/corpus/templates/content/app/components/editor/database/settings.tsx +49 -24
  187. package/corpus/templates/content/app/routes/_app.settings.tsx +19 -1
  188. package/corpus/templates/content/changelog/2026-07-07-builder-source-tag-fields-now-import-as-multi-select.md +6 -0
  189. package/corpus/templates/content/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  190. package/corpus/templates/content/shared/api.ts +3 -0
  191. package/corpus/templates/content/shared/properties.ts +42 -0
  192. package/corpus/templates/design/actions/generate-design.ts +3 -0
  193. package/corpus/templates/design/actions/get-design-system.ts +147 -1
  194. package/corpus/templates/design/app/routes/settings.tsx +16 -1
  195. package/corpus/templates/design/changelog/2026-07-08-design-generation-now-follows-hydrated-builder-design-system.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  197. package/corpus/templates/dispatch/app/routes/settings.tsx +23 -2
  198. package/corpus/templates/dispatch/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  199. package/corpus/templates/forms/app/routes/_app.settings.tsx +16 -1
  200. package/corpus/templates/forms/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  201. package/corpus/templates/macros/app/routes/settings.tsx +16 -1
  202. package/corpus/templates/macros/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  203. package/corpus/templates/mail/app/components/settings/GmailFiltersSection.tsx +1 -1
  204. package/corpus/templates/mail/app/components/settings/SnippetsSection.tsx +1 -1
  205. package/corpus/templates/mail/app/pages/SettingsPage.tsx +123 -105
  206. package/corpus/templates/mail/changelog/2026-07-08-settings-redesigned-with-a-consistent-searchable-layout.md +5 -0
  207. package/corpus/templates/plan/app/routes/settings.tsx +23 -2
  208. package/corpus/templates/plan/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  209. package/corpus/templates/slides/actions/add-slide.ts +1 -0
  210. package/corpus/templates/slides/actions/get-design-system.ts +147 -1
  211. package/corpus/templates/slides/app/pages/Index.tsx +46 -1
  212. package/corpus/templates/slides/app/routes/settings.tsx +16 -1
  213. package/corpus/templates/slides/changelog/2026-07-08-deck-generation-now-follows-hydrated-builder-design-system-g.md +6 -0
  214. package/corpus/templates/slides/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  215. package/dist/agent/run-ownership.d.ts +6 -0
  216. package/dist/agent/run-ownership.d.ts.map +1 -1
  217. package/dist/agent/run-ownership.js +11 -1
  218. package/dist/agent/run-ownership.js.map +1 -1
  219. package/dist/changelog/parse.d.ts +7 -0
  220. package/dist/changelog/parse.d.ts.map +1 -1
  221. package/dist/changelog/parse.js +64 -0
  222. package/dist/changelog/parse.js.map +1 -1
  223. package/dist/chat-threads/schema.d.ts +445 -0
  224. package/dist/chat-threads/schema.d.ts.map +1 -0
  225. package/dist/chat-threads/schema.js +39 -0
  226. package/dist/chat-threads/schema.js.map +1 -0
  227. package/dist/chat-threads/store.d.ts +12 -0
  228. package/dist/chat-threads/store.d.ts.map +1 -1
  229. package/dist/chat-threads/store.js +99 -10
  230. package/dist/chat-threads/store.js.map +1 -1
  231. package/dist/cli/workspace-dev.d.ts.map +1 -1
  232. package/dist/cli/workspace-dev.js +20 -5
  233. package/dist/cli/workspace-dev.js.map +1 -1
  234. package/dist/client/AgentPanel.d.ts.map +1 -1
  235. package/dist/client/AgentPanel.js +35 -7
  236. package/dist/client/AgentPanel.js.map +1 -1
  237. package/dist/client/AssistantChat.d.ts.map +1 -1
  238. package/dist/client/AssistantChat.js +105 -183
  239. package/dist/client/AssistantChat.js.map +1 -1
  240. package/dist/client/analytics-session.d.ts +3 -0
  241. package/dist/client/analytics-session.d.ts.map +1 -0
  242. package/dist/client/analytics-session.js +66 -0
  243. package/dist/client/analytics-session.js.map +1 -0
  244. package/dist/client/analytics.d.ts +32 -0
  245. package/dist/client/analytics.d.ts.map +1 -1
  246. package/dist/client/analytics.js +116 -46
  247. package/dist/client/analytics.js.map +1 -1
  248. package/dist/client/chat/message-components.d.ts +2 -1
  249. package/dist/client/chat/message-components.d.ts.map +1 -1
  250. package/dist/client/chat/message-components.js +16 -6
  251. package/dist/client/chat/message-components.js.map +1 -1
  252. package/dist/client/components/ui/message-scroller.d.ts +16 -0
  253. package/dist/client/components/ui/message-scroller.d.ts.map +1 -0
  254. package/dist/client/components/ui/message-scroller.js +25 -0
  255. package/dist/client/components/ui/message-scroller.js.map +1 -0
  256. package/dist/client/conversation/AgentConversation.d.ts +1 -1
  257. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  258. package/dist/client/conversation/AgentConversation.js +3 -8
  259. package/dist/client/conversation/AgentConversation.js.map +1 -1
  260. package/dist/client/error-capture.d.ts +99 -0
  261. package/dist/client/error-capture.d.ts.map +1 -0
  262. package/dist/client/error-capture.js +437 -0
  263. package/dist/client/error-capture.js.map +1 -0
  264. package/dist/client/index.d.ts +2 -2
  265. package/dist/client/index.d.ts.map +1 -1
  266. package/dist/client/index.js +3 -1
  267. package/dist/client/index.js.map +1 -1
  268. package/dist/client/session-replay.d.ts +22 -0
  269. package/dist/client/session-replay.d.ts.map +1 -1
  270. package/dist/client/session-replay.js +45 -4
  271. package/dist/client/session-replay.js.map +1 -1
  272. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  273. package/dist/client/settings/SettingsPanel.js +100 -5
  274. package/dist/client/settings/SettingsPanel.js.map +1 -1
  275. package/dist/client/settings/SettingsTabsPage.d.ts +49 -1
  276. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  277. package/dist/client/settings/SettingsTabsPage.js +131 -19
  278. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  279. package/dist/client/settings/index.d.ts +1 -1
  280. package/dist/client/settings/index.d.ts.map +1 -1
  281. package/dist/client/settings/index.js.map +1 -1
  282. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  283. package/dist/client/sharing/ShareButton.js +13 -8
  284. package/dist/client/sharing/ShareButton.js.map +1 -1
  285. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  286. package/dist/client/sharing/ShareDialog.js +12 -7
  287. package/dist/client/sharing/ShareDialog.js.map +1 -1
  288. package/dist/collab/struct-routes.d.ts +1 -1
  289. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  290. package/dist/mcp/builtin-tools.js +21 -2
  291. package/dist/mcp/builtin-tools.js.map +1 -1
  292. package/dist/notifications/routes.d.ts +1 -1
  293. package/dist/org/schema.d.ts +15 -15
  294. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  295. package/dist/resources/handlers.d.ts +3 -3
  296. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  297. package/dist/server/agent-chat-plugin.js +109 -73
  298. package/dist/server/agent-chat-plugin.js.map +1 -1
  299. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  300. package/dist/server/onboarding-html.d.ts.map +1 -1
  301. package/dist/server/onboarding-html.js +28 -16
  302. package/dist/server/onboarding-html.js.map +1 -1
  303. package/dist/sharing/actions/list-resource-shares.js +30 -1
  304. package/dist/sharing/actions/list-resource-shares.js.map +1 -1
  305. package/dist/sharing/actions/share-resource.js +6 -0
  306. package/dist/sharing/actions/share-resource.js.map +1 -1
  307. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  308. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  309. package/dist/vite/client.d.ts.map +1 -1
  310. package/dist/vite/client.js +192 -30
  311. package/dist/vite/client.js.map +1 -1
  312. package/docs/content/toolkit-settings.mdx +56 -3
  313. package/package.json +2 -1
  314. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  315. package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
@@ -6,13 +6,25 @@ import {
6
6
  normalizeThreadTitle,
7
7
  } from "../agent/thread-data-builder.js";
8
8
  import { getDbExec, intType, isPostgres } from "../db/client.js";
9
+ import { createGetDb } from "../db/create-get-db.js";
9
10
  import {
10
11
  ensureColumnExists,
11
12
  ensureIndexExists,
12
13
  ensureTableExists,
13
14
  } from "../db/ddl-guard.js";
14
15
  import { widenIntColumnsToBigInt } from "../db/widen-columns.js";
16
+ import { getRequestOrgId } from "../server/request-context.js";
17
+ import { resolveAccess, type AccessContext } from "../sharing/access.js";
18
+ import { registerShareableResource } from "../sharing/registry.js";
19
+ import { roleSatisfies, type ShareRole } from "../sharing/schema.js";
15
20
  import { emitChatThreadChange } from "./emitter.js";
21
+ import {
22
+ chatThreads,
23
+ chatThreadShares,
24
+ CHAT_THREAD_SHARES_CREATE_SQL,
25
+ CHAT_THREAD_SHARES_CREATE_SQL_PG,
26
+ CHAT_THREAD_SHARES_RESOURCE_INDEX_SQL,
27
+ } from "./schema.js";
16
28
 
17
29
  let _initPromise: Promise<void> | undefined;
18
30
 
@@ -33,6 +45,7 @@ let _initPromise: Promise<void> | undefined;
33
45
  const _threadDataLocks = new Map<string, Promise<unknown>>();
34
46
  const DEFAULT_THREAD_DATA_UPDATE_ATTEMPTS = 12;
35
47
  const THREAD_DATA_CONFLICT_BACKOFF_MS = 25;
48
+ const getChatThreadsDb = createGetDb({ chatThreads, chatThreadShares });
36
49
 
37
50
  export function withThreadDataLock<T>(
38
51
  threadId: string,
@@ -73,7 +86,9 @@ async function ensureTable(): Promise<void> {
73
86
  scope_label TEXT,
74
87
  pinned_at ${intType()},
75
88
  archived_at ${intType()},
76
- share_token_hash TEXT
89
+ share_token_hash TEXT,
90
+ org_id TEXT,
91
+ visibility TEXT NOT NULL DEFAULT 'private'
77
92
  )
78
93
  `;
79
94
 
@@ -100,6 +115,8 @@ async function ensureTable(): Promise<void> {
100
115
  ["pinned_at", intType()],
101
116
  ["archived_at", intType()],
102
117
  ["share_token_hash", "TEXT"],
118
+ ["org_id", "TEXT"],
119
+ ["visibility", "TEXT NOT NULL DEFAULT 'private'"],
103
120
  ] as const) {
104
121
  await ensureColumnExists(
105
122
  "chat_threads",
@@ -107,6 +124,10 @@ async function ensureTable(): Promise<void> {
107
124
  `ALTER TABLE chat_threads ADD COLUMN IF NOT EXISTS ${col} ${type}`,
108
125
  );
109
126
  }
127
+ await ensureTableExists(
128
+ "chat_thread_shares",
129
+ CHAT_THREAD_SHARES_CREATE_SQL_PG,
130
+ );
110
131
  // Widen millisecond-timestamp columns that older deployments created as
111
132
  // 32-bit `INTEGER`; on Postgres the `Date.now()` written on every turn
112
133
  // overflows int4. No-op once widened / on fresh BIGINT databases.
@@ -134,6 +155,10 @@ async function ensureTable(): Promise<void> {
134
155
  "chat_threads_share_token_idx",
135
156
  `CREATE INDEX IF NOT EXISTS chat_threads_share_token_idx ON chat_threads (share_token_hash)`,
136
157
  );
158
+ await ensureIndexExists(
159
+ "chat_thread_shares_resource_idx",
160
+ CHAT_THREAD_SHARES_RESOURCE_INDEX_SQL,
161
+ );
137
162
  // One-time backfill of message_count for legacy rows written before
138
163
  // the column was maintained.
139
164
  await backfillLegacyMessageCounts(client);
@@ -153,6 +178,8 @@ async function ensureTable(): Promise<void> {
153
178
  ["pinned_at", intType()],
154
179
  ["archived_at", intType()],
155
180
  ["share_token_hash", "TEXT"],
181
+ ["org_id", "TEXT"],
182
+ ["visibility", "TEXT NOT NULL DEFAULT 'private'"],
156
183
  ] as const) {
157
184
  try {
158
185
  await client.execute(
@@ -162,6 +189,11 @@ async function ensureTable(): Promise<void> {
162
189
  // Column already exists.
163
190
  }
164
191
  }
192
+ try {
193
+ await client.execute(CHAT_THREAD_SHARES_CREATE_SQL);
194
+ } catch {
195
+ // Table already exists.
196
+ }
165
197
  // Widen millisecond-timestamp columns that older deployments created as
166
198
  // 32-bit `INTEGER`; on Postgres the `Date.now()` written on every turn
167
199
  // overflows int4. No-op once widened / on fresh BIGINT databases.
@@ -181,6 +213,7 @@ async function ensureTable(): Promise<void> {
181
213
  `CREATE INDEX IF NOT EXISTS chat_threads_owner_updated_idx ON chat_threads (owner_email, updated_at)`,
182
214
  `CREATE INDEX IF NOT EXISTS chat_threads_scope_updated_idx ON chat_threads (scope_type, scope_id, updated_at)`,
183
215
  `CREATE INDEX IF NOT EXISTS chat_threads_share_token_idx ON chat_threads (share_token_hash)`,
216
+ CHAT_THREAD_SHARES_RESOURCE_INDEX_SQL,
184
217
  ]) {
185
218
  try {
186
219
  await client.execute(ddl);
@@ -261,6 +294,8 @@ export interface ChatThread {
261
294
  scope: ChatThreadScope | null;
262
295
  pinnedAt: number | null;
263
296
  archivedAt: number | null;
297
+ orgId: string | null;
298
+ visibility: "private" | "org" | "public";
264
299
  }
265
300
 
266
301
  export interface ChatThreadSummary {
@@ -273,6 +308,8 @@ export interface ChatThreadSummary {
273
308
  scope: ChatThreadScope | null;
274
309
  pinnedAt: number | null;
275
310
  archivedAt: number | null;
311
+ orgId: string | null;
312
+ visibility: "private" | "org" | "public";
276
313
  }
277
314
 
278
315
  export interface ForkThreadSourceSnapshot {
@@ -297,6 +334,10 @@ function readNullableNumber(value: unknown): number | null {
297
334
  return Number.isFinite(n) && n > 0 ? n : null;
298
335
  }
299
336
 
337
+ function readVisibility(value: unknown): "private" | "org" | "public" {
338
+ return value === "org" || value === "public" ? value : "private";
339
+ }
340
+
300
341
  function normalizeForkSourceSnapshot(
301
342
  source: ForkThreadSourceSnapshot | null | undefined,
302
343
  ): {
@@ -359,6 +400,8 @@ function rowToThread(r: Record<string, unknown>): ChatThread {
359
400
  scope: readScope(r),
360
401
  pinnedAt: readNullableNumber(r.pinned_at),
361
402
  archivedAt: readNullableNumber(r.archived_at),
403
+ orgId: (r.org_id as string | null | undefined) ?? null,
404
+ visibility: readVisibility(r.visibility),
362
405
  };
363
406
  }
364
407
 
@@ -378,6 +421,8 @@ function rowToSummary(r: Record<string, unknown>): ChatThreadSummary | null {
378
421
  scope: readScope(r),
379
422
  pinnedAt: readNullableNumber(r.pinned_at),
380
423
  archivedAt: readNullableNumber(r.archived_at),
424
+ orgId: (r.org_id as string | null | undefined) ?? null,
425
+ visibility: readVisibility(r.visibility),
381
426
  };
382
427
  }
383
428
 
@@ -391,9 +436,10 @@ export async function createThread(
391
436
  const now = Date.now();
392
437
  const title = opts?.title ?? "";
393
438
  const scope = opts?.scope ?? null;
439
+ const orgId = getRequestOrgId() ?? null;
394
440
 
395
441
  await client.execute({
396
- sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label) VALUES (?, ?, ?, '', '{}', 0, ?, ?, ?, ?, ?)`,
442
+ sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label, org_id, visibility) VALUES (?, ?, ?, '', '{}', 0, ?, ?, ?, ?, ?, ?, 'private')`,
397
443
  args: [
398
444
  id,
399
445
  ownerEmail,
@@ -403,6 +449,7 @@ export async function createThread(
403
449
  scope?.type ?? null,
404
450
  scope?.id ?? null,
405
451
  scope?.label ?? null,
452
+ orgId,
406
453
  ],
407
454
  });
408
455
 
@@ -418,10 +465,12 @@ export async function createThread(
418
465
  scope,
419
466
  pinnedAt: null,
420
467
  archivedAt: null,
468
+ orgId,
469
+ visibility: "private",
421
470
  };
422
471
  }
423
472
 
424
- const THREAD_COLUMNS = `id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label, pinned_at, archived_at`;
473
+ const THREAD_COLUMNS = `id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label, pinned_at, archived_at, org_id, visibility`;
425
474
  // The list/summary path deliberately omits `thread_data`: it is the full
426
475
  // message-history JSON blob and selecting it for every row turns "open the
427
476
  // sidebar" into "download every conversation". The summary derives nothing
@@ -429,7 +478,41 @@ const THREAD_COLUMNS = `id, owner_email, title, preview, thread_data, message_co
429
478
  // (message_count is maintained on write and backfilled for legacy rows at
430
479
  // bootstrap). The detail path (`THREAD_COLUMNS` / `getThread`) still returns
431
480
  // the full blob.
432
- const SUMMARY_COLUMNS = `id, title, preview, message_count, created_at, updated_at, scope_type, scope_id, scope_label, pinned_at, archived_at`;
481
+ const SUMMARY_COLUMNS = `id, title, preview, message_count, created_at, updated_at, scope_type, scope_id, scope_label, pinned_at, archived_at, org_id, visibility`;
482
+
483
+ export function registerChatThreadsShareable(): void {
484
+ registerShareableResource({
485
+ type: "chat_thread",
486
+ resourceTable: chatThreads,
487
+ sharesTable: chatThreadShares,
488
+ displayName: "Chat",
489
+ titleColumn: "title",
490
+ getResourcePath: (thread) =>
491
+ `/?thread=${encodeURIComponent(String(thread.id ?? ""))}`,
492
+ getDb: () => getChatThreadsDb(),
493
+ allowPublic: false,
494
+ ownerAccessIgnoresOrg: true,
495
+ });
496
+ }
497
+
498
+ export async function ensureChatThreadTables(): Promise<void> {
499
+ await ensureTable();
500
+ }
501
+
502
+ export async function resolveThreadAccess(
503
+ userEmail: string | null | undefined,
504
+ threadId: string | null | undefined,
505
+ minRole: ShareRole | "owner" = "viewer",
506
+ ctx: Omit<AccessContext, "userEmail"> = {},
507
+ ): Promise<ChatThread | null> {
508
+ if (!userEmail || !threadId) return null;
509
+ const access = await resolveAccess("chat_thread", threadId, {
510
+ userEmail,
511
+ orgId: ctx.orgId,
512
+ });
513
+ if (!access || !roleSatisfies(access.role, minRole)) return null;
514
+ return await getThread(threadId);
515
+ }
433
516
 
434
517
  export async function getThread(id: string): Promise<ChatThread | null> {
435
518
  await ensureTable();
@@ -445,7 +528,11 @@ export async function getThread(id: string): Promise<ChatThread | null> {
445
528
  export async function forkThread(
446
529
  sourceId: string,
447
530
  ownerEmail: string,
448
- opts?: { id?: string; source?: ForkThreadSourceSnapshot | null },
531
+ opts?: {
532
+ id?: string;
533
+ source?: ForkThreadSourceSnapshot | null;
534
+ sourceAccessGranted?: boolean;
535
+ },
449
536
  ): Promise<ChatThread | null> {
450
537
  const snapshot = normalizeForkSourceSnapshot(opts?.source);
451
538
  let source = await getThread(sourceId);
@@ -494,13 +581,19 @@ export async function forkThread(
494
581
  messageCount: snapshot.messageCount,
495
582
  };
496
583
  }
497
- if (!source || source.ownerEmail !== ownerEmail) return null;
584
+ if (
585
+ !source ||
586
+ (!opts?.sourceAccessGranted && source.ownerEmail !== ownerEmail)
587
+ ) {
588
+ return null;
589
+ }
498
590
  const id = opts?.id ?? generateId();
499
591
  const now = Date.now();
500
592
  const title = source.title ? `${source.title} (fork)` : "";
501
593
  const client = getDbExec();
594
+ const orgId = getRequestOrgId() ?? null;
502
595
  await client.execute({
503
- sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
596
+ sql: `INSERT INTO chat_threads (id, owner_email, title, preview, thread_data, message_count, created_at, updated_at, scope_type, scope_id, scope_label, org_id, visibility) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'private')`,
504
597
  args: [
505
598
  id,
506
599
  ownerEmail,
@@ -513,6 +606,7 @@ export async function forkThread(
513
606
  source.scope?.type ?? null,
514
607
  source.scope?.id ?? null,
515
608
  source.scope?.label ?? null,
609
+ orgId,
516
610
  ],
517
611
  });
518
612
  return {
@@ -527,6 +621,8 @@ export async function forkThread(
527
621
  scope: source.scope,
528
622
  pinnedAt: null,
529
623
  archivedAt: null,
624
+ orgId,
625
+ visibility: "private",
530
626
  };
531
627
  }
532
628
 
@@ -542,6 +638,28 @@ export interface ListThreadsOptions {
542
638
  scope?: { type: string; id: string };
543
639
  /** When true, returns only threads with no scope (general chats). */
544
640
  unscopedOnly?: boolean;
641
+ orgId?: string | null;
642
+ }
643
+
644
+ function chatThreadAccessSql(
645
+ userEmail: string,
646
+ orgId: string | null | undefined,
647
+ ): { sql: string; args: (string | number)[] } {
648
+ const normalizedEmail = userEmail.trim().toLowerCase();
649
+ const clauses = [
650
+ `LOWER(owner_email) = ?`,
651
+ `EXISTS (SELECT 1 FROM chat_thread_shares WHERE chat_thread_shares.resource_id = chat_threads.id AND chat_thread_shares.principal_type = 'user' AND LOWER(chat_thread_shares.principal_id) = ?)`,
652
+ ];
653
+ const args: (string | number)[] = [normalizedEmail, normalizedEmail];
654
+ if (orgId) {
655
+ clauses.push(`(visibility = 'org' AND org_id = ?)`);
656
+ args.push(orgId);
657
+ clauses.push(
658
+ `EXISTS (SELECT 1 FROM chat_thread_shares WHERE chat_thread_shares.resource_id = chat_threads.id AND chat_thread_shares.principal_type = 'org' AND chat_thread_shares.principal_id = ?)`,
659
+ );
660
+ args.push(orgId);
661
+ }
662
+ return { sql: `(${clauses.join(" OR ")})`, args };
545
663
  }
546
664
 
547
665
  export async function listThreads(
@@ -562,8 +680,12 @@ export async function listThreads(
562
680
  // maintained on every write and backfilled for legacy rows at bootstrap,
563
681
  // so the old `OR thread_data LIKE '%"messages"%'` substring scan over the
564
682
  // full blob is no longer needed here.
565
- const filters: string[] = [`owner_email = ?`, `message_count > 0`];
566
- const args: (string | number)[] = [ownerEmail];
683
+ const access = chatThreadAccessSql(
684
+ ownerEmail,
685
+ opts.orgId ?? getRequestOrgId(),
686
+ );
687
+ const filters: string[] = [access.sql, `message_count > 0`];
688
+ const args: (string | number)[] = [...access.args];
567
689
  if (opts.scope) {
568
690
  filters.push(`scope_type = ? AND scope_id = ?`);
569
691
  args.push(opts.scope.type, opts.scope.id);
@@ -588,7 +710,7 @@ export async function searchThreads(
588
710
  ownerEmail: string,
589
711
  query: string,
590
712
  limit = 50,
591
- options: { scope?: { type: string; id: string } } = {},
713
+ options: { scope?: { type: string; id: string }; orgId?: string | null } = {},
592
714
  ): Promise<ChatThreadSummary[]> {
593
715
  await ensureTable();
594
716
  const client = getDbExec();
@@ -596,12 +718,16 @@ export async function searchThreads(
596
718
  // The count-guard uses the maintained/backfilled `message_count` column
597
719
  // (same as listThreads). The content match still scans `thread_data` —
598
720
  // search legitimately needs to look inside message history.
721
+ const access = chatThreadAccessSql(
722
+ ownerEmail,
723
+ options.orgId ?? getRequestOrgId(),
724
+ );
599
725
  const filters: string[] = [
600
- `owner_email = ?`,
726
+ access.sql,
601
727
  `message_count > 0`,
602
728
  `(title LIKE ? ESCAPE '!' OR preview LIKE ? ESCAPE '!' OR thread_data LIKE ? ESCAPE '!')`,
603
729
  ];
604
- const args: (string | number)[] = [ownerEmail, pattern, pattern, pattern];
730
+ const args: (string | number)[] = [...access.args, pattern, pattern, pattern];
605
731
  if (options.scope) {
606
732
  filters.push(`scope_type = ? AND scope_id = ?`);
607
733
  args.push(options.scope.type, options.scope.id);
@@ -1137,6 +1263,12 @@ export async function deleteThread(id: string): Promise<boolean> {
1137
1263
  args: [id],
1138
1264
  });
1139
1265
  if (result.rowsAffected > 0) {
1266
+ await client
1267
+ .execute({
1268
+ sql: `DELETE FROM chat_thread_shares WHERE resource_id = ?`,
1269
+ args: [id],
1270
+ })
1271
+ .catch(() => {});
1140
1272
  emitChatThreadChange(id);
1141
1273
  return true;
1142
1274
  }
@@ -83,6 +83,7 @@ const DEFAULT_PROXY_RESPONSE_TIMEOUT_MS = 5_000;
83
83
  const DEFAULT_PROXY_NON_HTML_RESPONSE_TIMEOUT_MS = 120_000;
84
84
  const APP_OUTPUT_TAIL_BYTES = 8_000;
85
85
  const POLLING_WATCH_INTERVAL_MS = "1000";
86
+ const reportedDiscoverAppsReadFailures = new Set<string>();
86
87
  const STARTING_APP_RESPONSE_HEADERS: http.OutgoingHttpHeaders = {
87
88
  "content-type": "text/html; charset=utf-8",
88
89
  "cache-control": "no-store, no-cache, max-age=0, must-revalidate",
@@ -287,6 +288,23 @@ function readJson(file: string): any {
287
288
  }
288
289
  }
289
290
 
291
+ function shouldReportDiscoverAppsReadFailure(
292
+ appsDir: string,
293
+ code: string | undefined,
294
+ ): boolean {
295
+ if (code === "ENOENT") return false;
296
+ const key = `${appsDir}:${code ?? "unknown"}`;
297
+ if (reportedDiscoverAppsReadFailures.has(key)) return false;
298
+ reportedDiscoverAppsReadFailures.add(key);
299
+ return true;
300
+ }
301
+
302
+ function shouldCaptureDiscoverAppsReadFailure(
303
+ code: string | undefined,
304
+ ): boolean {
305
+ return code !== "EACCES" && code !== "EPERM";
306
+ }
307
+
290
308
  function discoverApps(appsDir: string, appPortStart: number): WorkspaceApp[] {
291
309
  if (!fs.existsSync(appsDir)) return [];
292
310
  // existsSync -> readdirSync is a TOCTOU race. Treat ENOENT as "no apps
@@ -296,15 +314,17 @@ function discoverApps(appsDir: string, appPortStart: number): WorkspaceApp[] {
296
314
  entries = fs.readdirSync(appsDir, { withFileTypes: true });
297
315
  } catch (err) {
298
316
  const code = (err as NodeJS.ErrnoException).code;
299
- if (code !== "ENOENT") {
317
+ if (shouldReportDiscoverAppsReadFailure(appsDir, code)) {
300
318
  console.warn(
301
319
  `[workspace] Could not read ${appsDir} (${code ?? "unknown"}): ` +
302
320
  `${(err as Error).message}`,
303
321
  );
304
- Sentry.captureException(err, {
305
- tags: { handled: "dev-discover-readdir" },
306
- level: "warning",
307
- });
322
+ if (shouldCaptureDiscoverAppsReadFailure(code)) {
323
+ Sentry.captureException(err, {
324
+ tags: { handled: "dev-discover-readdir" },
325
+ level: "warning",
326
+ });
327
+ }
308
328
  }
309
329
  return [];
310
330
  }
@@ -68,6 +68,7 @@ import {
68
68
  import { ErrorReportActions } from "./ErrorReportActions.js";
69
69
  import { FeedbackButton } from "./FeedbackButton.js";
70
70
  import { RunsTrayMenuItem } from "./progress/RunsTray.js";
71
+ import { ShareButton } from "./sharing/ShareButton.js";
71
72
  // Lazy-load the full assistant-ui chat stack (tiptap composer + react-markdown +
72
73
  // assistant-ui + zod block schemas) so it is NOT in the static import closure of
73
74
  // every page. The header/tab chrome renders immediately; chat streams in once the
@@ -90,7 +91,7 @@ import {
90
91
  type AgentSidebarStateChangeDetail,
91
92
  } from "./agent-sidebar-state.js";
92
93
  import { trackEvent } from "./analytics.js";
93
- import { agentNativePath } from "./api-path.js";
94
+ import { agentNativePath, appPath } from "./api-path.js";
94
95
  import { assistantUiRecoverableRenderErrorKind } from "./assistant-ui-recovery.js";
95
96
  import type { AssistantChatProps } from "./AssistantChat.js";
96
97
  import { shouldParentFrameOwnAgentPanel } from "./builder-frame.js";
@@ -1080,10 +1081,31 @@ function AgentPanelInner({
1080
1081
  const [headerMenuOpen, setHeaderMenuOpen] = useState(false);
1081
1082
  const [feedbackOpen, setFeedbackOpen] = useState(false);
1082
1083
 
1084
+ const getChatThreadShareUrl = useCallback(
1085
+ (threadId: string) => {
1086
+ if (typeof window === "undefined") return undefined;
1087
+ if (
1088
+ threadUrlSync &&
1089
+ typeof threadUrlSync === "object" &&
1090
+ typeof threadUrlSync.getPath === "function"
1091
+ ) {
1092
+ return new URL(
1093
+ appPath(threadUrlSync.getPath(threadId)),
1094
+ window.location.origin,
1095
+ ).toString();
1096
+ }
1097
+ const url = new URL(window.location.href);
1098
+ url.searchParams.set("thread", threadId);
1099
+ return url.toString();
1100
+ },
1101
+ [threadUrlSync],
1102
+ );
1103
+
1083
1104
  const renderHeaderActions = useCallback(
1084
1105
  ({
1085
1106
  activeChatSessionId,
1086
1107
  activeTabId,
1108
+ activeTabMessageCount,
1087
1109
  addTab,
1088
1110
  clearActiveTab,
1089
1111
  closeAllTabs,
@@ -1095,6 +1117,7 @@ function AgentPanelInner({
1095
1117
  }: Pick<
1096
1118
  MultiTabAssistantChatHeaderProps,
1097
1119
  | "activeTabId"
1120
+ | "activeTabMessageCount"
1098
1121
  | "addTab"
1099
1122
  | "clearActiveTab"
1100
1123
  | "closeAllTabs"
@@ -1110,6 +1133,28 @@ function AgentPanelInner({
1110
1133
  <SetupButton />
1111
1134
  </Suspense>
1112
1135
  )}
1136
+ {(() => {
1137
+ const activeTab =
1138
+ mode === "chat" && activeChatSessionId
1139
+ ? tabs.find((tab) => tab.id === activeChatSessionId)
1140
+ : undefined;
1141
+ if (
1142
+ !activeTab ||
1143
+ (activeTabMessageCount <= 0 && activeTab.status === "idle")
1144
+ ) {
1145
+ return null;
1146
+ }
1147
+ return (
1148
+ <ShareButton
1149
+ resourceType="chat_thread"
1150
+ resourceId={activeTab.id}
1151
+ resourceTitle={activeTab.label || "Chat"}
1152
+ shareUrl={getChatThreadShareUrl(activeTab.id)}
1153
+ trigger="icon"
1154
+ triggerClassName="h-7 w-7"
1155
+ />
1156
+ );
1157
+ })()}
1113
1158
  <FeedbackButton
1114
1159
  variant="icon"
1115
1160
  side="bottom"
@@ -1331,6 +1376,7 @@ function AgentPanelInner({
1331
1376
  closeOtherCliTabs,
1332
1377
  closeTabHint,
1333
1378
  feedbackOpen,
1379
+ getChatThreadShareUrl,
1334
1380
  headerMenuOpen,
1335
1381
  isFullscreen,
1336
1382
  mode,
@@ -1361,25 +1407,42 @@ function AgentPanelInner({
1361
1407
  ) {
1362
1408
  return null;
1363
1409
  }
1410
+ const activeTab = activeTabId
1411
+ ? tabs.find((tab) => tab.id === activeTabId)
1412
+ : undefined;
1413
+ const canShareActiveTab =
1414
+ activeTab && (activeTabMessageCount > 0 || activeTab.status !== "idle");
1364
1415
 
1365
1416
  return (
1366
1417
  <div className="pointer-events-none absolute inset-x-0 top-3 z-[60] flex justify-end px-3 sm:top-4 sm:px-4">
1367
- <button
1368
- type="button"
1369
- data-agent-page-new-chat=""
1370
- aria-label={t("agentPanel.newChat")}
1371
- onClick={() => {
1372
- addTab();
1373
- }}
1374
- className="pointer-events-auto inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1375
- >
1376
- <IconPlus size={14} />
1377
- <span>{t("agentPanel.newChat")}</span>
1378
- </button>
1418
+ <div className="pointer-events-auto flex items-center gap-1">
1419
+ {canShareActiveTab ? (
1420
+ <ShareButton
1421
+ resourceType="chat_thread"
1422
+ resourceId={activeTab.id}
1423
+ resourceTitle={activeTab.label || "Chat"}
1424
+ shareUrl={getChatThreadShareUrl(activeTab.id)}
1425
+ trigger="icon"
1426
+ triggerClassName="h-8 w-8 border border-border bg-background/95 shadow-sm backdrop-blur hover:bg-accent"
1427
+ />
1428
+ ) : null}
1429
+ <button
1430
+ type="button"
1431
+ data-agent-page-new-chat=""
1432
+ aria-label={t("agentPanel.newChat")}
1433
+ onClick={() => {
1434
+ addTab();
1435
+ }}
1436
+ className="inline-flex h-8 items-center gap-1.5 rounded-md border border-border bg-background/95 px-2.5 text-xs font-medium text-foreground shadow-sm backdrop-blur transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
1437
+ >
1438
+ <IconPlus size={14} />
1439
+ <span>{t("agentPanel.newChat")}</span>
1440
+ </button>
1441
+ </div>
1379
1442
  </div>
1380
1443
  );
1381
1444
  },
1382
- [t],
1445
+ [getChatThreadShareUrl, t],
1383
1446
  );
1384
1447
 
1385
1448
  // Ref callback: scroll the active tab into view in the overflow container.
@@ -1404,6 +1467,7 @@ function AgentPanelInner({
1404
1467
  ({
1405
1468
  tabs,
1406
1469
  activeTabId,
1470
+ activeTabMessageCount,
1407
1471
  setActiveTabId,
1408
1472
  addTab,
1409
1473
  clearActiveTab,
@@ -1432,6 +1496,7 @@ function AgentPanelInner({
1432
1496
  {renderHeaderActions({
1433
1497
  activeChatSessionId: activeTabId,
1434
1498
  activeTabId,
1499
+ activeTabMessageCount,
1435
1500
  addTab,
1436
1501
  clearActiveTab,
1437
1502
  closeAllTabs,