@agent-native/core 0.127.3 → 0.128.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 (410) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +80 -0
  3. package/corpus/core/README.md +5 -26
  4. package/corpus/core/docs/content/actions.mdx +47 -4
  5. package/corpus/core/docs/content/native-chat-ui.mdx +7 -0
  6. package/corpus/core/docs/content/organizations-teams-permissions.mdx +109 -3
  7. package/corpus/core/docs/content/template-design-collaboration-and-full-apps.mdx +15 -0
  8. package/corpus/core/docs/content/toolkit-org-team.mdx +72 -12
  9. package/corpus/core/package.json +1 -1
  10. package/corpus/core/src/a2a/artifact-response.ts +36 -29
  11. package/corpus/core/src/action.ts +83 -2
  12. package/corpus/core/src/chat-threads/store.ts +48 -43
  13. package/corpus/core/src/cli/skills-content/design-visual-edit-skill.ts +46 -3
  14. package/corpus/core/src/client/AgentPanel.tsx +26 -6
  15. package/corpus/core/src/client/AssistantChat.tsx +63 -7
  16. package/corpus/core/src/client/DefaultSpinner.tsx +29 -0
  17. package/corpus/core/src/client/org/TeamPage.tsx +161 -6
  18. package/corpus/core/src/client/org/hooks.ts +67 -0
  19. package/corpus/core/src/client/org/index.ts +9 -0
  20. package/corpus/core/src/client/transcription/use-live-transcription.ts +62 -10
  21. package/corpus/core/src/db/client.ts +42 -3
  22. package/corpus/core/src/db/ensure-additive-columns.ts +60 -33
  23. package/corpus/core/src/deploy/build.ts +31 -2
  24. package/corpus/core/src/mcp-client/routes.ts +10 -1
  25. package/corpus/core/src/org/app-roles-handlers.ts +152 -0
  26. package/corpus/core/src/org/app-roles.ts +355 -0
  27. package/corpus/core/src/org/index.ts +27 -1
  28. package/corpus/core/src/org/migrations.ts +22 -0
  29. package/corpus/core/src/org/plugin.ts +27 -0
  30. package/corpus/core/src/org/schema.ts +15 -0
  31. package/corpus/core/src/scripts/docs/search.ts +17 -0
  32. package/corpus/core/src/scripts/docs/source-search.ts +10 -0
  33. package/corpus/core/src/server/agent-chat/script-entries.ts +3 -0
  34. package/corpus/core/src/server/agent-chat-plugin.ts +53 -52
  35. package/corpus/core/src/server/core-routes-plugin.ts +6 -1
  36. package/corpus/core/src/server/framework-request-handler.ts +44 -6
  37. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +32 -0
  38. package/corpus/core/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -0
  39. package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +31 -0
  40. package/corpus/core/src/templates/headless/.agents/skills/actions/SKILL.md +32 -0
  41. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -0
  42. package/corpus/core/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -0
  43. package/corpus/core/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +40 -0
  44. package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +31 -0
  45. package/corpus/core/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +4 -0
  46. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +32 -0
  47. package/corpus/templates/analytics/.agents/skills/security/SKILL.md +31 -0
  48. package/corpus/templates/analytics/.agents/skills/sharing/SKILL.md +4 -0
  49. package/corpus/templates/analytics/actions/data-source-status.ts +21 -12
  50. package/corpus/templates/analytics/app/global.css +7 -7
  51. package/corpus/templates/analytics/app/i18n-data.ts +30 -0
  52. package/corpus/templates/analytics/app/lib/data-source-status.ts +2 -1
  53. package/corpus/templates/analytics/app/pages/DataSources.tsx +54 -10
  54. package/corpus/templates/analytics/changelog/2026-07-28-analytics-pages-load-reliably-with-large-dashboard-workspaces.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-07-28-dashboard-loading-shimmer-now-flows-smoothly-in-one-directi.md +6 -0
  56. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +5 -4
  57. package/corpus/templates/analytics/server/plugins/agent-chat.ts +54 -42
  58. package/corpus/templates/analytics/server/plugins/db.ts +1 -18
  59. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +32 -0
  60. package/corpus/templates/assets/.agents/skills/security/SKILL.md +31 -0
  61. package/corpus/templates/assets/.agents/skills/sharing/SKILL.md +4 -0
  62. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +32 -0
  63. package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +7 -0
  64. package/corpus/templates/brain/.agents/skills/security/SKILL.md +31 -0
  65. package/corpus/templates/brain/.agents/skills/sharing/SKILL.md +4 -0
  66. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +32 -0
  67. package/corpus/templates/calendar/.agents/skills/security/SKILL.md +31 -0
  68. package/corpus/templates/calendar/.agents/skills/sharing/SKILL.md +4 -0
  69. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +32 -0
  70. package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +7 -0
  71. package/corpus/templates/chat/.agents/skills/security/SKILL.md +31 -0
  72. package/corpus/templates/chat/.agents/skills/sharing/SKILL.md +4 -0
  73. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +32 -0
  74. package/corpus/templates/clips/.agents/skills/security/SKILL.md +31 -0
  75. package/corpus/templates/clips/actions/request-transcript.ts +77 -11
  76. package/corpus/templates/clips/actions/save-browser-transcript.ts +28 -7
  77. package/corpus/templates/clips/app/routes/record.tsx +11 -3
  78. package/corpus/templates/clips/changelog/2026-07-28-browser-recordings-no-longer-save-a-truncated-transcript-as-.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-28-browser-recordings-no-longer-stop-transcribing-partway-throu.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-07-28-desktop-full-screen-clips-now-get-an-instant-local-transcrip.md +6 -0
  81. package/corpus/templates/clips/changelog/2026-07-28-long-transcriptions-no-longer-show-a-false-transcription-sto.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-07-28-restarting-for-an-update-now-installs-the-newest-release-ins.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-07-28-the-meeting-recording-pill-now-anchors-to-the-right-edge-of-.md +6 -0
  84. package/corpus/templates/clips/desktop/src/lib/audio-meter.ts +31 -2
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +114 -13
  86. package/corpus/templates/clips/desktop/src/lib/updater.ts +12 -4
  87. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +40 -15
  88. package/corpus/templates/clips/desktop/src/styles.css +10 -7
  89. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +21 -14
  90. package/corpus/templates/clips/shared/transcript-status.ts +10 -0
  91. package/corpus/templates/content/.agents/skills/actions/SKILL.md +32 -0
  92. package/corpus/templates/content/.agents/skills/security/SKILL.md +31 -0
  93. package/corpus/templates/content/.agents/skills/sharing/SKILL.md +4 -0
  94. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +4 -0
  95. package/corpus/templates/design/.agents/skills/actions/SKILL.md +32 -0
  96. package/corpus/templates/design/.agents/skills/security/SKILL.md +31 -0
  97. package/corpus/templates/design/.agents/skills/sharing/SKILL.md +4 -0
  98. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +46 -3
  99. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +7 -5
  100. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +2 -2
  101. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +7 -4
  102. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +1 -1
  103. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +10 -4
  104. package/corpus/templates/design/app/lib/design-ui-events.ts +5 -0
  105. package/corpus/templates/design/app/root.tsx +18 -1
  106. package/corpus/templates/design/changelog/2026-07-28-show-or-hide-design-editing-chrome-with-cmd-on-apple-devices.md +6 -0
  107. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +32 -0
  108. package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +31 -0
  109. package/corpus/templates/dispatch/.agents/skills/sharing/SKILL.md +4 -0
  110. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +32 -0
  111. package/corpus/templates/forms/.agents/skills/security/SKILL.md +31 -0
  112. package/corpus/templates/forms/.agents/skills/sharing/SKILL.md +4 -0
  113. package/corpus/templates/macros/.agents/skills/security/SKILL.md +31 -0
  114. package/corpus/templates/macros/.agents/skills/sharing/SKILL.md +4 -0
  115. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +32 -0
  116. package/corpus/templates/mail/.agents/skills/security/SKILL.md +31 -0
  117. package/corpus/templates/mail/.agents/skills/sharing/SKILL.md +4 -0
  118. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +32 -0
  119. package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +7 -0
  120. package/corpus/templates/plan/.agents/skills/security/SKILL.md +31 -0
  121. package/corpus/templates/plan/.agents/skills/sharing/SKILL.md +4 -0
  122. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +32 -0
  123. package/corpus/templates/slides/.agents/skills/security/SKILL.md +31 -0
  124. package/corpus/templates/slides/.agents/skills/sharing/SKILL.md +4 -0
  125. package/corpus/templates/tasks/.agents/skills/actions/SKILL.md +32 -0
  126. package/corpus/templates/tasks/.agents/skills/adding-a-feature/SKILL.md +7 -0
  127. package/corpus/templates/tasks/.agents/skills/security/SKILL.md +31 -0
  128. package/corpus/templates/tasks/.agents/skills/sharing/SKILL.md +4 -0
  129. package/dist/a2a/artifact-response.d.ts.map +1 -1
  130. package/dist/a2a/artifact-response.js +30 -29
  131. package/dist/a2a/artifact-response.js.map +1 -1
  132. package/dist/action.d.ts +36 -0
  133. package/dist/action.d.ts.map +1 -1
  134. package/dist/action.js +37 -2
  135. package/dist/action.js.map +1 -1
  136. package/dist/chat-threads/store.d.ts +12 -0
  137. package/dist/chat-threads/store.d.ts.map +1 -1
  138. package/dist/chat-threads/store.js +48 -42
  139. package/dist/chat-threads/store.js.map +1 -1
  140. package/dist/cli/skills-content/design-visual-edit-skill.d.ts +1 -1
  141. package/dist/cli/skills-content/design-visual-edit-skill.d.ts.map +1 -1
  142. package/dist/cli/skills-content/design-visual-edit-skill.js +46 -3
  143. package/dist/cli/skills-content/design-visual-edit-skill.js.map +1 -1
  144. package/dist/client/AgentPanel.d.ts +3 -1
  145. package/dist/client/AgentPanel.d.ts.map +1 -1
  146. package/dist/client/AgentPanel.js +11 -9
  147. package/dist/client/AgentPanel.js.map +1 -1
  148. package/dist/client/AssistantChat.d.ts.map +1 -1
  149. package/dist/client/AssistantChat.js +57 -6
  150. package/dist/client/AssistantChat.js.map +1 -1
  151. package/dist/client/DefaultSpinner.d.ts +7 -0
  152. package/dist/client/DefaultSpinner.d.ts.map +1 -1
  153. package/dist/client/DefaultSpinner.js +25 -1
  154. package/dist/client/DefaultSpinner.js.map +1 -1
  155. package/dist/client/org/TeamPage.d.ts +13 -1
  156. package/dist/client/org/TeamPage.d.ts.map +1 -1
  157. package/dist/client/org/TeamPage.js +41 -11
  158. package/dist/client/org/TeamPage.js.map +1 -1
  159. package/dist/client/org/hooks.d.ts +37 -0
  160. package/dist/client/org/hooks.d.ts.map +1 -1
  161. package/dist/client/org/hooks.js +37 -0
  162. package/dist/client/org/hooks.js.map +1 -1
  163. package/dist/client/org/index.d.ts +3 -2
  164. package/dist/client/org/index.d.ts.map +1 -1
  165. package/dist/client/org/index.js +1 -1
  166. package/dist/client/org/index.js.map +1 -1
  167. package/dist/client/transcription/use-live-transcription.d.ts +8 -0
  168. package/dist/client/transcription/use-live-transcription.d.ts.map +1 -1
  169. package/dist/client/transcription/use-live-transcription.js +38 -11
  170. package/dist/client/transcription/use-live-transcription.js.map +1 -1
  171. package/dist/collab/routes.d.ts +2 -2
  172. package/dist/db/client.d.ts +12 -0
  173. package/dist/db/client.d.ts.map +1 -1
  174. package/dist/db/client.js +40 -3
  175. package/dist/db/client.js.map +1 -1
  176. package/dist/db/ensure-additive-columns.d.ts.map +1 -1
  177. package/dist/db/ensure-additive-columns.js +54 -32
  178. package/dist/db/ensure-additive-columns.js.map +1 -1
  179. package/dist/deploy/build.d.ts +1 -0
  180. package/dist/deploy/build.d.ts.map +1 -1
  181. package/dist/deploy/build.js +23 -2
  182. package/dist/deploy/build.js.map +1 -1
  183. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  184. package/dist/mcp-client/routes.d.ts +3 -1
  185. package/dist/mcp-client/routes.d.ts.map +1 -1
  186. package/dist/mcp-client/routes.js +8 -2
  187. package/dist/mcp-client/routes.js.map +1 -1
  188. package/dist/notifications/routes.d.ts +1 -1
  189. package/dist/org/app-roles-handlers.d.ts +27 -0
  190. package/dist/org/app-roles-handlers.d.ts.map +1 -0
  191. package/dist/org/app-roles-handlers.js +125 -0
  192. package/dist/org/app-roles-handlers.js.map +1 -0
  193. package/dist/org/app-roles.d.ts +136 -0
  194. package/dist/org/app-roles.d.ts.map +1 -0
  195. package/dist/org/app-roles.js +216 -0
  196. package/dist/org/app-roles.js.map +1 -0
  197. package/dist/org/index.d.ts +4 -1
  198. package/dist/org/index.d.ts.map +1 -1
  199. package/dist/org/index.js +3 -1
  200. package/dist/org/index.js.map +1 -1
  201. package/dist/org/migrations.d.ts.map +1 -1
  202. package/dist/org/migrations.js +22 -0
  203. package/dist/org/migrations.js.map +1 -1
  204. package/dist/org/plugin.d.ts +2 -0
  205. package/dist/org/plugin.d.ts.map +1 -1
  206. package/dist/org/plugin.js +20 -0
  207. package/dist/org/plugin.js.map +1 -1
  208. package/dist/org/schema.d.ts +143 -0
  209. package/dist/org/schema.d.ts.map +1 -1
  210. package/dist/org/schema.js +14 -0
  211. package/dist/org/schema.js.map +1 -1
  212. package/dist/progress/routes.d.ts +1 -1
  213. package/dist/provider-api/actions/custom-provider-registration.d.ts +4 -4
  214. package/dist/provider-api/actions/provider-api.d.ts +12 -12
  215. package/dist/scripts/docs/search.d.ts.map +1 -1
  216. package/dist/scripts/docs/search.js +15 -0
  217. package/dist/scripts/docs/search.js.map +1 -1
  218. package/dist/scripts/docs/source-search.d.ts +7 -0
  219. package/dist/scripts/docs/source-search.d.ts.map +1 -1
  220. package/dist/scripts/docs/source-search.js +9 -0
  221. package/dist/scripts/docs/source-search.js.map +1 -1
  222. package/dist/secrets/routes.d.ts +9 -9
  223. package/dist/server/agent-chat/script-entries.d.ts.map +1 -1
  224. package/dist/server/agent-chat/script-entries.js +3 -0
  225. package/dist/server/agent-chat/script-entries.js.map +1 -1
  226. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  227. package/dist/server/agent-chat-plugin.js +46 -48
  228. package/dist/server/agent-chat-plugin.js.map +1 -1
  229. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  230. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  231. package/dist/server/core-routes-plugin.js +6 -1
  232. package/dist/server/core-routes-plugin.js.map +1 -1
  233. package/dist/server/framework-request-handler.d.ts.map +1 -1
  234. package/dist/server/framework-request-handler.js +42 -6
  235. package/dist/server/framework-request-handler.js.map +1 -1
  236. package/dist/server/realtime-token.d.ts +1 -1
  237. package/dist/templates/chat/.agents/skills/actions/SKILL.md +32 -0
  238. package/dist/templates/chat/.agents/skills/adding-a-feature/SKILL.md +7 -0
  239. package/dist/templates/chat/.agents/skills/security/SKILL.md +31 -0
  240. package/dist/templates/chat/.agents/skills/sharing/SKILL.md +4 -0
  241. package/dist/templates/default/.agents/skills/actions/SKILL.md +32 -0
  242. package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -0
  243. package/dist/templates/default/.agents/skills/security/SKILL.md +31 -0
  244. package/dist/templates/headless/.agents/skills/actions/SKILL.md +32 -0
  245. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -0
  246. package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -0
  247. package/dist/templates/workspace-core/.agents/skills/authentication/SKILL.md +40 -0
  248. package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +31 -0
  249. package/dist/templates/workspace-core/.agents/skills/sharing/SKILL.md +4 -0
  250. package/docs/content/actions.mdx +47 -4
  251. package/docs/content/native-chat-ui.mdx +7 -0
  252. package/docs/content/organizations-teams-permissions.mdx +109 -3
  253. package/docs/content/template-design-collaboration-and-full-apps.mdx +15 -0
  254. package/docs/content/toolkit-org-team.mdx +72 -12
  255. package/package.json +1 -1
  256. package/src/a2a/artifact-response.ts +36 -29
  257. package/src/action.ts +83 -2
  258. package/src/chat-threads/store.ts +48 -43
  259. package/src/cli/skills-content/design-visual-edit-skill.ts +46 -3
  260. package/src/client/AgentPanel.tsx +26 -6
  261. package/src/client/AssistantChat.tsx +63 -7
  262. package/src/client/DefaultSpinner.tsx +29 -0
  263. package/src/client/org/TeamPage.tsx +161 -6
  264. package/src/client/org/hooks.ts +67 -0
  265. package/src/client/org/index.ts +9 -0
  266. package/src/client/transcription/use-live-transcription.ts +62 -10
  267. package/src/db/client.ts +42 -3
  268. package/src/db/ensure-additive-columns.ts +60 -33
  269. package/src/deploy/build.ts +31 -2
  270. package/src/mcp-client/routes.ts +10 -1
  271. package/src/org/app-roles-handlers.ts +152 -0
  272. package/src/org/app-roles.ts +355 -0
  273. package/src/org/index.ts +27 -1
  274. package/src/org/migrations.ts +22 -0
  275. package/src/org/plugin.ts +27 -0
  276. package/src/org/schema.ts +15 -0
  277. package/src/scripts/docs/search.ts +17 -0
  278. package/src/scripts/docs/source-search.ts +10 -0
  279. package/src/server/agent-chat/script-entries.ts +3 -0
  280. package/src/server/agent-chat-plugin.ts +53 -52
  281. package/src/server/core-routes-plugin.ts +6 -1
  282. package/src/server/framework-request-handler.ts +44 -6
  283. package/src/templates/chat/.agents/skills/actions/SKILL.md +32 -0
  284. package/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +7 -0
  285. package/src/templates/chat/.agents/skills/security/SKILL.md +31 -0
  286. package/src/templates/chat/.agents/skills/sharing/SKILL.md +4 -0
  287. package/src/templates/default/.agents/skills/actions/SKILL.md +32 -0
  288. package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +7 -0
  289. package/src/templates/default/.agents/skills/security/SKILL.md +31 -0
  290. package/src/templates/headless/.agents/skills/actions/SKILL.md +32 -0
  291. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +32 -0
  292. package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +7 -0
  293. package/src/templates/workspace-core/.agents/skills/authentication/SKILL.md +40 -0
  294. package/src/templates/workspace-core/.agents/skills/security/SKILL.md +31 -0
  295. package/src/templates/workspace-core/.agents/skills/sharing/SKILL.md +4 -0
  296. package/corpus/core/src/templates/chat/.agents/skills/actions/SKILL.md +0 -530
  297. package/corpus/core/src/templates/chat/.agents/skills/adding-a-feature/SKILL.md +0 -191
  298. package/corpus/core/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +0 -115
  299. package/corpus/core/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +0 -177
  300. package/corpus/core/src/templates/chat/.agents/skills/capture-learnings/SKILL.md +0 -89
  301. package/corpus/core/src/templates/chat/.agents/skills/create-skill/SKILL.md +0 -221
  302. package/corpus/core/src/templates/chat/.agents/skills/customizing-agent-native/SKILL.md +0 -220
  303. package/corpus/core/src/templates/chat/.agents/skills/delegate-to-agent/SKILL.md +0 -263
  304. package/corpus/core/src/templates/chat/.agents/skills/feature-flags/SKILL.md +0 -169
  305. package/corpus/core/src/templates/chat/.agents/skills/frontend-design/SKILL.md +0 -174
  306. package/corpus/core/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +0 -232
  307. package/corpus/core/src/templates/chat/.agents/skills/security/SKILL.md +0 -282
  308. package/corpus/core/src/templates/chat/.agents/skills/self-modifying-code/SKILL.md +0 -119
  309. package/corpus/core/src/templates/chat/.agents/skills/shadcn-ui/SKILL.md +0 -123
  310. package/corpus/core/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +0 -122
  311. package/corpus/core/src/templates/chat/.env.example +0 -5
  312. package/corpus/core/src/templates/chat/.ignore +0 -0
  313. package/corpus/core/src/templates/chat/.oxfmtrc.json +0 -8
  314. package/corpus/core/src/templates/chat/AGENTS.md +0 -77
  315. package/corpus/core/src/templates/chat/CHANGELOG.md +0 -10
  316. package/corpus/core/src/templates/chat/DEVELOPING.md +0 -185
  317. package/corpus/core/src/templates/chat/README.md +0 -32
  318. package/corpus/core/src/templates/chat/_gitignore +0 -46
  319. package/corpus/core/src/templates/chat/actions/hello.ts +0 -13
  320. package/corpus/core/src/templates/chat/actions/navigate.ts +0 -41
  321. package/corpus/core/src/templates/chat/actions/run.ts +0 -2
  322. package/corpus/core/src/templates/chat/actions/view-screen.ts +0 -31
  323. package/corpus/core/src/templates/chat/app/components/layout/Header.tsx +0 -60
  324. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +0 -188
  325. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +0 -505
  326. package/corpus/core/src/templates/chat/app/components/ui/button.tsx +0 -1
  327. package/corpus/core/src/templates/chat/app/components/ui/card.tsx +0 -1
  328. package/corpus/core/src/templates/chat/app/components/ui/dropdown-menu.tsx +0 -1
  329. package/corpus/core/src/templates/chat/app/components/ui/input.tsx +0 -1
  330. package/corpus/core/src/templates/chat/app/components/ui/label.tsx +0 -1
  331. package/corpus/core/src/templates/chat/app/components/ui/sheet.tsx +0 -1
  332. package/corpus/core/src/templates/chat/app/components/ui/toolkit-provider.tsx +0 -17
  333. package/corpus/core/src/templates/chat/app/components/ui/tooltip.tsx +0 -1
  334. package/corpus/core/src/templates/chat/app/design-system.ts +0 -3
  335. package/corpus/core/src/templates/chat/app/entry.client.tsx +0 -22
  336. package/corpus/core/src/templates/chat/app/entry.server.tsx +0 -10
  337. package/corpus/core/src/templates/chat/app/global.css +0 -93
  338. package/corpus/core/src/templates/chat/app/hooks/use-navigation-state.ts +0 -93
  339. package/corpus/core/src/templates/chat/app/i18n/ar-SA.ts +0 -68
  340. package/corpus/core/src/templates/chat/app/i18n/de-DE.ts +0 -71
  341. package/corpus/core/src/templates/chat/app/i18n/en-US.ts +0 -70
  342. package/corpus/core/src/templates/chat/app/i18n/es-ES.ts +0 -71
  343. package/corpus/core/src/templates/chat/app/i18n/fr-FR.ts +0 -71
  344. package/corpus/core/src/templates/chat/app/i18n/hi-IN.ts +0 -69
  345. package/corpus/core/src/templates/chat/app/i18n/index.ts +0 -34
  346. package/corpus/core/src/templates/chat/app/i18n/ja-JP.ts +0 -70
  347. package/corpus/core/src/templates/chat/app/i18n/ko-KR.ts +0 -70
  348. package/corpus/core/src/templates/chat/app/i18n/pt-BR.ts +0 -71
  349. package/corpus/core/src/templates/chat/app/i18n/zh-CN.ts +0 -66
  350. package/corpus/core/src/templates/chat/app/i18n/zh-TW.ts +0 -66
  351. package/corpus/core/src/templates/chat/app/i18n-data.ts +0 -556
  352. package/corpus/core/src/templates/chat/app/lib/agent-page.tsx +0 -49
  353. package/corpus/core/src/templates/chat/app/lib/app-config.ts +0 -11
  354. package/corpus/core/src/templates/chat/app/lib/tab-id.ts +0 -1
  355. package/corpus/core/src/templates/chat/app/lib/utils.ts +0 -1
  356. package/corpus/core/src/templates/chat/app/root.tsx +0 -173
  357. package/corpus/core/src/templates/chat/app/routes/_index.tsx +0 -94
  358. package/corpus/core/src/templates/chat/app/routes/agent.tsx +0 -24
  359. package/corpus/core/src/templates/chat/app/routes/chat.$threadId.tsx +0 -1
  360. package/corpus/core/src/templates/chat/app/routes/database.tsx +0 -17
  361. package/corpus/core/src/templates/chat/app/routes/extensions.$id.$slug.tsx +0 -2
  362. package/corpus/core/src/templates/chat/app/routes/extensions.$id.tsx +0 -11
  363. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +0 -11
  364. package/corpus/core/src/templates/chat/app/routes/extensions.tsx +0 -5
  365. package/corpus/core/src/templates/chat/app/routes/observability.tsx +0 -19
  366. package/corpus/core/src/templates/chat/app/routes/settings.tsx +0 -89
  367. package/corpus/core/src/templates/chat/app/routes/team.tsx +0 -11
  368. package/corpus/core/src/templates/chat/app/routes.ts +0 -4
  369. package/corpus/core/src/templates/chat/app/vite-env.d.ts +0 -6
  370. package/corpus/core/src/templates/chat/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +0 -6
  371. package/corpus/core/src/templates/chat/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +0 -6
  372. package/corpus/core/src/templates/chat/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +0 -6
  373. package/corpus/core/src/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +0 -6
  374. package/corpus/core/src/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +0 -6
  375. package/corpus/core/src/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +0 -5
  376. package/corpus/core/src/templates/chat/changelog/2026-07-10-chat-now-makes-ai-connection-setup-clear-without-shifting-th.md +0 -6
  377. package/corpus/core/src/templates/chat/changelog/2026-07-13-a-full-agent-page-now-brings-context-files-connections-jobs-.md +0 -6
  378. package/corpus/core/src/templates/chat/changelog/2026-07-14-chat-opens-reliably-on-hosted-deployments-instead-of-failing.md +0 -6
  379. package/corpus/core/src/templates/chat/changelog/2026-07-14-fixed-chat-template-startup-with-older-core-versions.md +0 -6
  380. package/corpus/core/src/templates/chat/changelog/2026-07-15-chat-navigation-focuses-on-chat-and-agent.md +0 -6
  381. package/corpus/core/src/templates/chat/changelog/2026-07-17-the-agent-chat-sidebar-stays-closed-until-you-open-it-or-sta.md +0 -6
  382. package/corpus/core/src/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +0 -6
  383. package/corpus/core/src/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +0 -6
  384. package/corpus/core/src/templates/chat/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +0 -6
  385. package/corpus/core/src/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +0 -6
  386. package/corpus/core/src/templates/chat/changelog/2026-07-23-full-page-chat-is-better-centered-with-quieter-chat-history-.md +0 -6
  387. package/corpus/core/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +0 -6
  388. package/corpus/core/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +0 -6
  389. package/corpus/core/src/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +0 -6
  390. package/corpus/core/src/templates/chat/components.json +0 -20
  391. package/corpus/core/src/templates/chat/learnings.defaults.md +0 -5
  392. package/corpus/core/src/templates/chat/netlify.toml +0 -11
  393. package/corpus/core/src/templates/chat/package.json +0 -92
  394. package/corpus/core/src/templates/chat/public/agent-native-icon-dark.svg +0 -10
  395. package/corpus/core/src/templates/chat/public/agent-native-icon-light.svg +0 -10
  396. package/corpus/core/src/templates/chat/public/agent-native-logo-dark.svg +0 -21
  397. package/corpus/core/src/templates/chat/public/agent-native-logo-light.svg +0 -21
  398. package/corpus/core/src/templates/chat/public/favicon.svg +0 -1
  399. package/corpus/core/src/templates/chat/public/icon-180.svg +0 -1
  400. package/corpus/core/src/templates/chat/public/icon-192.svg +0 -1
  401. package/corpus/core/src/templates/chat/public/icon-512.svg +0 -1
  402. package/corpus/core/src/templates/chat/public/manifest.json +0 -21
  403. package/corpus/core/src/templates/chat/react-router.config.ts +0 -7
  404. package/corpus/core/src/templates/chat/server/middleware/auth.ts +0 -15
  405. package/corpus/core/src/templates/chat/server/plugins/agent-chat.ts +0 -21
  406. package/corpus/core/src/templates/chat/server/plugins/auth.ts +0 -17
  407. package/corpus/core/src/templates/chat/server/routes/[...page].get.ts +0 -5
  408. package/corpus/core/src/templates/chat/ssr-entry.ts +0 -15
  409. package/corpus/core/src/templates/chat/tsconfig.json +0 -21
  410. package/corpus/core/src/templates/chat/vite.config.ts +0 -19
@@ -2,14 +2,23 @@
2
2
  * Full-screen loading spinner rendered during SSR and initial hydration.
3
3
  * Uses inline SVG + styles because Tailwind may not be loaded yet on the server.
4
4
  * Respects the user's OS color scheme so dark-mode users don't get a white flash.
5
+ *
6
+ * The stall hint is revealed by a pure-CSS `animation-delay`, never a timer:
7
+ * the states that strand a user here (hydration never runs, the route module
8
+ * 404s, a cold dev-server compile) are exactly the states where no JS of ours
9
+ * executes, so a `setTimeout` fallback would never fire. A featureless spinner
10
+ * is indistinguishable from a blank screen, and reads as "the app is broken"
11
+ * rather than "look at the terminal" — that mis-read is what this text buys.
5
12
  */
6
13
  export function DefaultSpinner() {
7
14
  return (
8
15
  <div
9
16
  style={{
10
17
  display: "flex",
18
+ flexDirection: "column",
11
19
  alignItems: "center",
12
20
  justifyContent: "center",
21
+ gap: 16,
13
22
  height: "100vh",
14
23
  width: "100%",
15
24
  }}
@@ -29,8 +38,28 @@ export function DefaultSpinner() {
29
38
  >
30
39
  <path d="M21 12a9 9 0 1 1-6.219-8.56" />
31
40
  </svg>
41
+ <p className="an-stall-hint">
42
+ Still loading. A first run compiles dependencies and can take a minute —
43
+ if it does not finish, check the terminal running the dev server for
44
+ errors.
45
+ </p>
32
46
  <style>{`
33
47
  @keyframes an-spin { to { transform: rotate(360deg) } }
48
+ @keyframes an-stall-in { to { opacity: 0.6 } }
49
+ .an-stall-hint {
50
+ opacity: 0;
51
+ margin: 0;
52
+ max-width: 32rem;
53
+ padding: 0 1.5rem;
54
+ text-align: center;
55
+ font-size: 0.875rem;
56
+ line-height: 1.5;
57
+ font-family: ui-sans-serif, system-ui, sans-serif;
58
+ animation: an-stall-in 0.4s ease-out 10s forwards;
59
+ }
60
+ @media (prefers-reduced-motion: reduce) {
61
+ .an-stall-hint { animation-duration: 0s }
62
+ }
34
63
  html {
35
64
  background: hsl(var(--background, 0 0% 100%));
36
65
  color: hsl(var(--foreground, 240 10% 3.9%));
@@ -56,6 +56,9 @@ import {
56
56
  type ReactNode,
57
57
  } from "react";
58
58
 
59
+ // Type-only: erased at build time, so declaring app roles pulls no server or
60
+ // database code into the browser bundle.
61
+ import type { AppRolesDescriptor } from "../../org/app-roles.js";
59
62
  import type { DomainMatchOrg } from "../../org/types.js";
60
63
  import {
61
64
  Tooltip,
@@ -82,6 +85,8 @@ import {
82
85
  useSetA2ASecret,
83
86
  useSyncA2ASecret,
84
87
  useJoinByDomain,
88
+ useAppRoles,
89
+ useSetAppMemberRole,
85
90
  type InviteRole,
86
91
  type SyncA2ASecretResult,
87
92
  } from "./hooks.js";
@@ -127,6 +132,17 @@ export interface TeamPageProps {
127
132
  * tweak page width.
128
133
  */
129
134
  className?: string;
135
+ /**
136
+ * Opt in to an app-role column on the members table, using the same
137
+ * descriptor the app passes to `defineAppRoles`. Pass it explicitly rather
138
+ * than letting the page discover registered apps: a workspace can host
139
+ * several, and a members table that silently grows a column when some
140
+ * unrelated module registers itself is a surprise, not a feature.
141
+ *
142
+ * Only org owners/admins can change assignments; everyone else sees the
143
+ * column read-only.
144
+ */
145
+ appRoles?: AppRolesDescriptor;
130
146
  }
131
147
 
132
148
  function RoleIcon({ role }: { role: string }) {
@@ -383,7 +399,7 @@ interface PendingInviteListItem {
383
399
  role: string;
384
400
  }
385
401
 
386
- function MembersCard() {
402
+ function MembersCard({ appRoles }: { appRoles?: AppRolesDescriptor }) {
387
403
  const t = useT();
388
404
  const { data: org } = useOrg();
389
405
  const { data: membersData, isLoading: isLoadingMembers } = useOrgMembers();
@@ -457,6 +473,7 @@ function MembersCard() {
457
473
  isLoadingMembers={isLoadingMembers}
458
474
  currentUserEmail={org.email}
459
475
  currentUserRole={org.role ?? null}
476
+ appRoles={appRoles}
460
477
  />
461
478
 
462
479
  {isOwner && <DangerZoneCard orgName={org.orgName ?? ""} />}
@@ -470,16 +487,26 @@ function MembersTableCard({
470
487
  isLoadingMembers,
471
488
  currentUserEmail,
472
489
  currentUserRole,
490
+ appRoles,
473
491
  }: {
474
492
  members: MemberListItem[];
475
493
  pendingInvites: PendingInviteListItem[];
476
494
  isLoadingMembers: boolean;
477
495
  currentUserEmail: string;
478
496
  currentUserRole: string | null;
497
+ appRoles?: AppRolesDescriptor;
479
498
  }) {
480
499
  const t = useT();
481
500
  const [showInviteForm, setShowInviteForm] = useState(false);
482
501
  const canInvite = currentUserRole === "owner" || currentUserRole === "admin";
502
+ const { data: appRoleData } = useAppRoles(appRoles?.appId);
503
+ const appRoleByEmail = new Map(
504
+ (appRoleData?.assignments ?? []).map((a) => [
505
+ a.email.toLowerCase(),
506
+ a.role,
507
+ ]),
508
+ );
509
+ const columnCount = appRoles ? 5 : 4;
483
510
 
484
511
  return (
485
512
  <section className="rounded-lg border border-border bg-card">
@@ -516,6 +543,9 @@ function MembersTableCard({
516
543
  <TableRow>
517
544
  <TableHead>{t("org.member")}</TableHead>
518
545
  <TableHead>{t("org.role")}</TableHead>
546
+ {appRoles && (
547
+ <TableHead>{appRoles.label ?? appRoles.appId}</TableHead>
548
+ )}
519
549
  <TableHead>{t("org.status")}</TableHead>
520
550
  <TableHead className="text-end">{t("org.actions")}</TableHead>
521
551
  </TableRow>
@@ -524,7 +554,7 @@ function MembersTableCard({
524
554
  {isLoadingMembers && members.length === 0 ? (
525
555
  [0, 1, 2].map((i) => (
526
556
  <TableRow key={i}>
527
- <TableCell colSpan={4}>
557
+ <TableCell colSpan={columnCount}>
528
558
  <div
529
559
  className="h-3.5 rounded bg-muted animate-pulse"
530
560
  style={{ width: `${180 + i * 48}px` }}
@@ -535,7 +565,7 @@ function MembersTableCard({
535
565
  ) : members.length === 0 && pendingInvites.length === 0 ? (
536
566
  <TableRow>
537
567
  <TableCell
538
- colSpan={4}
568
+ colSpan={columnCount}
539
569
  className="py-8 text-center text-sm text-muted-foreground"
540
570
  >
541
571
  {t("org.noMembers")}
@@ -550,10 +580,17 @@ function MembersTableCard({
550
580
  role={m.role}
551
581
  isCurrentUser={m.email === currentUserEmail}
552
582
  currentUserRole={currentUserRole}
583
+ appRoles={appRoles}
584
+ appRole={appRoleByEmail.get(m.email.toLowerCase()) ?? null}
585
+ canManageAppRoles={Boolean(appRoleData?.canManage)}
553
586
  />
554
587
  ))}
555
588
  {pendingInvites.map((inv) => (
556
- <PendingInviteRow key={inv.id} invite={inv} />
589
+ <PendingInviteRow
590
+ key={inv.id}
591
+ invite={inv}
592
+ hasAppRoleColumn={Boolean(appRoles)}
593
+ />
557
594
  ))}
558
595
  </>
559
596
  )}
@@ -665,7 +702,13 @@ function RoleBadge({ role }: { role: string }) {
665
702
  );
666
703
  }
667
704
 
668
- function PendingInviteRow({ invite }: { invite: PendingInviteListItem }) {
705
+ function PendingInviteRow({
706
+ invite,
707
+ hasAppRoleColumn,
708
+ }: {
709
+ invite: PendingInviteListItem;
710
+ hasAppRoleColumn?: boolean;
711
+ }) {
669
712
  const t = useT();
670
713
  return (
671
714
  <TableRow className="opacity-70">
@@ -675,6 +718,13 @@ function PendingInviteRow({ invite }: { invite: PendingInviteListItem }) {
675
718
  <TableCell>
676
719
  <RoleBadge role={invite.role} />
677
720
  </TableCell>
721
+ {/* App roles hang off membership, so there is nothing to assign until the
722
+ invitation is accepted. */}
723
+ {hasAppRoleColumn && (
724
+ <TableCell>
725
+ <span className="text-muted-foreground">-</span>
726
+ </TableCell>
727
+ )}
678
728
  <TableCell>
679
729
  <span className="rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
680
730
  {t("org.invited")}
@@ -685,16 +735,112 @@ function PendingInviteRow({ invite }: { invite: PendingInviteListItem }) {
685
735
  );
686
736
  }
687
737
 
738
+ /** Sentinel for "clear the assignment" — Select cannot carry an empty value. */
739
+ const UNASSIGNED = "__unassigned__";
740
+
741
+ function AppRoleCell({
742
+ email,
743
+ appRoles,
744
+ appRole,
745
+ canManage,
746
+ }: {
747
+ email: string;
748
+ appRoles: AppRolesDescriptor;
749
+ appRole: string | null;
750
+ canManage: boolean;
751
+ }) {
752
+ const [editing, setEditing] = useState(false);
753
+ const setAppRole = useSetAppMemberRole(appRoles.appId);
754
+ const labelFor = (r: string) => appRoles.roleLabels?.[r] ?? r;
755
+
756
+ if (editing) {
757
+ return (
758
+ <TableCell>
759
+ <Select
760
+ defaultOpen
761
+ value={appRole ?? UNASSIGNED}
762
+ onOpenChange={(open) => {
763
+ if (!open) setEditing(false);
764
+ }}
765
+ onValueChange={(value) => {
766
+ const next = value === UNASSIGNED ? null : value;
767
+ if (next === appRole) {
768
+ setEditing(false);
769
+ return;
770
+ }
771
+ setAppRole.mutate(
772
+ { email, role: next },
773
+ { onSuccess: () => setEditing(false) },
774
+ );
775
+ }}
776
+ disabled={setAppRole.isPending}
777
+ >
778
+ <SelectTrigger
779
+ autoFocus
780
+ className="h-auto w-auto rounded-md border border-border bg-background px-1.5 py-0.5 text-[11px]"
781
+ >
782
+ <SelectValue />
783
+ </SelectTrigger>
784
+ <SelectContent>
785
+ <SelectItem value={UNASSIGNED}>
786
+ {appRoles.defaultRole ? labelFor(appRoles.defaultRole) : "—"}
787
+ </SelectItem>
788
+ {appRoles.roles.map((r) => (
789
+ <SelectItem key={r} value={r}>
790
+ {labelFor(r)}
791
+ </SelectItem>
792
+ ))}
793
+ </SelectContent>
794
+ </Select>
795
+ </TableCell>
796
+ );
797
+ }
798
+
799
+ // An unassigned member shows the app's default only as a hint. The default
800
+ // never satisfies a server guard, so it must not read as a granted role.
801
+ const display = appRole ? (
802
+ <span className="inline-flex items-center rounded border border-border px-2 py-1 text-xs text-muted-foreground">
803
+ {labelFor(appRole)}
804
+ </span>
805
+ ) : (
806
+ <span className="text-xs text-muted-foreground/70">
807
+ {appRoles.defaultRole ? labelFor(appRoles.defaultRole) : "—"}
808
+ </span>
809
+ );
810
+
811
+ return (
812
+ <TableCell>
813
+ {canManage ? (
814
+ <Button
815
+ type="button"
816
+ onClick={() => setEditing(true)}
817
+ className="cursor-pointer rounded hover:opacity-80"
818
+ >
819
+ {display}
820
+ </Button>
821
+ ) : (
822
+ display
823
+ )}
824
+ </TableCell>
825
+ );
826
+ }
827
+
688
828
  function MemberRow({
689
829
  email,
690
830
  role,
691
831
  isCurrentUser,
692
832
  currentUserRole,
833
+ appRoles,
834
+ appRole,
835
+ canManageAppRoles,
693
836
  }: {
694
837
  email: string;
695
838
  role: string;
696
839
  isCurrentUser: boolean;
697
840
  currentUserRole: string | null;
841
+ appRoles?: AppRolesDescriptor;
842
+ appRole?: string | null;
843
+ canManageAppRoles?: boolean;
698
844
  }) {
699
845
  const t = useT();
700
846
  const removeMember = useRemoveMember();
@@ -719,6 +865,14 @@ function MemberRow({
719
865
  <TableCell>
720
866
  <RoleBadge role={role} />
721
867
  </TableCell>
868
+ {appRoles && (
869
+ <AppRoleCell
870
+ email={email}
871
+ appRoles={appRoles}
872
+ appRole={appRole ?? null}
873
+ canManage={Boolean(canManageAppRoles)}
874
+ />
875
+ )}
722
876
  <TableCell>
723
877
  {isCurrentUser ? (
724
878
  <span className="rounded border border-border px-1.5 py-0.5 text-[10px] text-muted-foreground">
@@ -1620,6 +1774,7 @@ export function TeamPage({
1620
1774
  showTitle = true,
1621
1775
  createOrgDescription,
1622
1776
  className,
1777
+ appRoles,
1623
1778
  }: TeamPageProps) {
1624
1779
  const t = useT();
1625
1780
  const { data: org, isLoading } = useOrg();
@@ -1652,7 +1807,7 @@ export function TeamPage({
1652
1807
  {!org?.orgId ? (
1653
1808
  <CreateOrgCard description={createOrgDescription} />
1654
1809
  ) : (
1655
- <MembersCard />
1810
+ <MembersCard appRoles={appRoles} />
1656
1811
  )}
1657
1812
  </>
1658
1813
  )}
@@ -318,6 +318,73 @@ export function useSetOrgDomain() {
318
318
  });
319
319
  }
320
320
 
321
+ export interface AppRoleAssignment {
322
+ email: string;
323
+ role: string;
324
+ }
325
+
326
+ export interface AppRolesInfo {
327
+ appId: string;
328
+ roles: string[];
329
+ /** Shown for members with no assignment. Never satisfies a server guard. */
330
+ defaultRole: string | null;
331
+ roleLabels: Record<string, string>;
332
+ /** Whether the current user may change assignments (org owner/admin). */
333
+ canManage: boolean;
334
+ assignments: AppRoleAssignment[];
335
+ myRole: string | null;
336
+ }
337
+
338
+ /**
339
+ * App-role vocabulary and assignments for the active org.
340
+ *
341
+ * `myRole` and `canManage` are progressive disclosure only — every guarded
342
+ * operation re-resolves both server-side, so a client that shows the wrong
343
+ * affordance still cannot perform the operation.
344
+ */
345
+ export function useAppRoles(appId: string | undefined) {
346
+ const { data: org } = useOrg();
347
+ return useQuery<AppRolesInfo>({
348
+ queryKey: ["org-app-roles", appId ?? null, org?.orgId ?? null],
349
+ queryFn: () =>
350
+ apiFetch(`${ORG_BASE}/app-roles?appId=${encodeURIComponent(appId!)}`),
351
+ enabled: Boolean(appId),
352
+ staleTime: 30_000,
353
+ });
354
+ }
355
+
356
+ /** The current user's role in one app, or `null` when unassigned. */
357
+ export function useAppRole(appId: string | undefined): {
358
+ role: string | null;
359
+ isLoading: boolean;
360
+ error: Error | null;
361
+ } {
362
+ const query = useAppRoles(appId);
363
+ return {
364
+ role: query.data?.myRole ?? null,
365
+ isLoading: query.isLoading,
366
+ error: query.error,
367
+ };
368
+ }
369
+
370
+ export function useSetAppMemberRole(appId: string) {
371
+ const qc = useQueryClient();
372
+ return useMutation<
373
+ { appId: string; email: string; role: string | null },
374
+ Error,
375
+ { email: string; role: string | null }
376
+ >({
377
+ mutationFn: ({ email, role }) =>
378
+ apiFetch(`${ORG_BASE}/app-roles/${encodeURIComponent(email)}`, {
379
+ method: "PUT",
380
+ body: JSON.stringify({ appId, role }),
381
+ }),
382
+ onSuccess: async () => {
383
+ await qc.invalidateQueries({ queryKey: ["org-app-roles"] });
384
+ },
385
+ });
386
+ }
387
+
321
388
  /**
322
389
  * Fetch the org's A2A secret on demand (owner/admin). Deliberately a separate
323
390
  * request from `useOrg()` so the secret only reaches the browser when the
@@ -19,6 +19,9 @@ export {
19
19
  useSetA2ASecret,
20
20
  useSyncA2ASecret,
21
21
  useOrgRole,
22
+ useAppRoles,
23
+ useAppRole,
24
+ useSetAppMemberRole,
22
25
  } from "./hooks.js";
23
26
 
24
27
  export type {
@@ -27,8 +30,14 @@ export type {
27
30
  BulkInviteResult,
28
31
  SyncA2ASecretResult,
29
32
  UseOrgRoleResult,
33
+ AppRoleAssignment,
34
+ AppRolesInfo,
30
35
  } from "./hooks.js";
31
36
 
37
+ // Type-only re-export so templates can annotate the `appRoles` prop without
38
+ // importing the server module.
39
+ export type { AppRolesDescriptor } from "../../org/app-roles.js";
40
+
32
41
  export { OrgSwitcher, type OrgSwitcherProps } from "./OrgSwitcher.js";
33
42
  export {
34
43
  InvitationBanner,
@@ -15,6 +15,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
15
15
 
16
16
  const NETWORK_RESTART_BASE_MS = 1_000;
17
17
  const NETWORK_RESTART_MAX_MS = 30_000;
18
+ const RESTART_RETRY_BASE_MS = 400;
19
+ const MAX_RESTART_ATTEMPTS = 8;
18
20
 
19
21
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
22
  function getSpeechRecognitionCtor(): any {
@@ -42,6 +44,14 @@ export interface LiveTranscriptionApi {
42
44
  stopAndWait: (timeoutMs?: number) => Promise<string>;
43
45
  pause: () => void;
44
46
  resume: () => void;
47
+ /**
48
+ * Non-null when recognition died before the caller asked it to stop, so the
49
+ * returned transcript covers only part of the session. Callers must pass it
50
+ * along rather than storing a partial transcript as a finished one. Read
51
+ * through a getter (not state) so a stop handler can never observe a stale
52
+ * "everything was fine" value from an earlier render.
53
+ */
54
+ getIncompleteReason: () => string | null;
45
55
  }
46
56
 
47
57
  export function useLiveTranscription(
@@ -64,6 +74,20 @@ export function useLiveTranscription(
64
74
  const restartTimerRef = useRef<number | null>(null);
65
75
  const networkErrorCountRef = useRef(0);
66
76
  const restartDelayRef = useRef(0);
77
+ const restartFailureCountRef = useRef(0);
78
+ const incompleteReasonRef = useRef<string | null>(null);
79
+
80
+ // First reason wins: it names what actually broke the capture, and later
81
+ // teardown noise must not overwrite it.
82
+ const markIncomplete = useCallback((reason: string) => {
83
+ if (incompleteReasonRef.current) return;
84
+ incompleteReasonRef.current = reason;
85
+ }, []);
86
+
87
+ const getIncompleteReason = useCallback(
88
+ () => incompleteReasonRef.current,
89
+ [],
90
+ );
67
91
 
68
92
  const currentTranscript = useCallback(
69
93
  () =>
@@ -102,6 +126,8 @@ export function useLiveTranscription(
102
126
  stoppedManuallyRef.current = false;
103
127
  networkErrorCountRef.current = 0;
104
128
  restartDelayRef.current = 0;
129
+ restartFailureCountRef.current = 0;
130
+ incompleteReasonRef.current = null;
105
131
  setTranscript("");
106
132
  setInterimText("");
107
133
 
@@ -122,6 +148,7 @@ export function useLiveTranscription(
122
148
  if (event.results.length > 0) {
123
149
  networkErrorCountRef.current = 0;
124
150
  restartDelayRef.current = 0;
151
+ restartFailureCountRef.current = 0;
125
152
  }
126
153
  };
127
154
 
@@ -133,6 +160,9 @@ export function useLiveTranscription(
133
160
  event.error === "audio-capture"
134
161
  ) {
135
162
  stoppedManuallyRef.current = true;
163
+ markIncomplete(
164
+ `Browser speech recognition stopped mid-recording (${event.error}).`,
165
+ );
136
166
  setIsActive(false);
137
167
  return;
138
168
  }
@@ -163,17 +193,34 @@ export function useLiveTranscription(
163
193
  }
164
194
  try {
165
195
  recognition.start();
196
+ restartFailureCountRef.current = 0;
166
197
  } catch {
167
- setIsActive(false);
198
+ // Chrome throws InvalidStateError while the previous session is
199
+ // still releasing. `onend` has already fired and will not fire
200
+ // again, so this timer is the only thing left that can revive
201
+ // recognition — giving up here freezes the transcript mid-recording
202
+ // and the partial text then looks like the whole recording.
203
+ const attempt = ++restartFailureCountRef.current;
204
+ if (attempt >= MAX_RESTART_ATTEMPTS) {
205
+ markIncomplete(
206
+ "Browser speech recognition stopped mid-recording and could not be restarted.",
207
+ );
208
+ setIsActive(false);
209
+ return;
210
+ }
211
+ restartTimerRef.current = window.setTimeout(
212
+ restart,
213
+ RESTART_RETRY_BASE_MS * attempt,
214
+ );
168
215
  }
169
216
  };
170
- const delayMs = restartDelayRef.current;
217
+ // Never call start() synchronously from inside onend — Chrome still
218
+ // has the session marked as running during dispatch and throws.
219
+ restartTimerRef.current = window.setTimeout(
220
+ restart,
221
+ restartDelayRef.current,
222
+ );
171
223
  restartDelayRef.current = 0;
172
- if (delayMs > 0) {
173
- restartTimerRef.current = window.setTimeout(restart, delayMs);
174
- } else {
175
- restart();
176
- }
177
224
  return;
178
225
  }
179
226
  setIsActive(false);
@@ -185,10 +232,14 @@ export function useLiveTranscription(
185
232
  try {
186
233
  recognition.start();
187
234
  setIsActive(true);
188
- } catch {
189
- /* browser may block without user gesture */
235
+ } catch (err) {
236
+ // Browser may block without user gesture. Nothing was captured, and the
237
+ // caller must not treat the resulting empty text as "no speech".
238
+ markIncomplete(
239
+ `Browser speech recognition could not start (${(err as Error)?.message || "unknown error"}).`,
240
+ );
190
241
  }
191
- }, [currentTranscript, lang]);
242
+ }, [currentTranscript, lang, markIncomplete]);
192
243
 
193
244
  const stop = useCallback((): string => {
194
245
  const text = currentTranscript();
@@ -307,5 +358,6 @@ export function useLiveTranscription(
307
358
  stopAndWait,
308
359
  pause,
309
360
  resume,
361
+ getIncompleteReason,
310
362
  };
311
363
  }
package/src/db/client.ts CHANGED
@@ -1595,6 +1595,35 @@ async function initClient(): Promise<void> {
1595
1595
  * Get the singleton database client. Returns a `DbExec` whose first
1596
1596
  * `execute()` call lazily initializes the underlying driver.
1597
1597
  */
1598
+ /**
1599
+ * Point a missing-table failure at the cause instead of the symptom.
1600
+ *
1601
+ * The driver reports `no such table: x` / `relation "x" does not exist` from
1602
+ * whichever query happened to touch it first, so the stack lands in an action
1603
+ * and reads as a bug in that action. The actual cause is almost always that no
1604
+ * migration ever created the table — a template with no `server/plugins/db.ts`
1605
+ * creates none, and core's own tables self-heal, so app tables are the only
1606
+ * ones that fail this way. Appends rather than replaces: `isDuplicateColumnError`
1607
+ * and friends match substrings of the driver's original text.
1608
+ */
1609
+ export function annotateMissingTable(err: unknown, sql: unknown): unknown {
1610
+ if (!(err instanceof Error)) return err;
1611
+ const match =
1612
+ /no such table:?\s*["'`]?([\w.]+)/i.exec(err.message) ??
1613
+ /relation\s+["'`]?([\w.]+)["'`]?\s+does not exist/i.exec(err.message);
1614
+ if (!match) return err;
1615
+ if (err.message.includes("server/plugins/db.ts")) return err;
1616
+ const statement =
1617
+ typeof sql === "string" ? sql : (sql as { sql?: string })?.sql;
1618
+ err.message =
1619
+ `${err.message}\n` +
1620
+ ` [agent-native] Table "${match[1]}" does not exist. App tables are created by migrations in ` +
1621
+ `server/plugins/db.ts — check that the plugin exists and declares a migration for this table, ` +
1622
+ `then restart the dev server so it runs.` +
1623
+ (statement ? `\n Statement: ${statement.slice(0, 200)}` : "");
1624
+ return err;
1625
+ }
1626
+
1598
1627
  export function getDbExec(): DbExec {
1599
1628
  if (_exec) return _exec;
1600
1629
 
@@ -1608,6 +1637,16 @@ export function getDbExec(): DbExec {
1608
1637
  return sql;
1609
1638
  }
1610
1639
 
1640
+ async function execAnnotated(
1641
+ s: string | { sql: string; args?: unknown[] },
1642
+ ): ReturnType<DbExec["execute"]> {
1643
+ try {
1644
+ return await _exec!.execute(sanitize(s));
1645
+ } catch (err) {
1646
+ throw annotateMissingTable(err, s);
1647
+ }
1648
+ }
1649
+
1611
1650
  // Return a proxy that lazy-inits on first call
1612
1651
  const proxy: DbExec = {
1613
1652
  async execute(sql) {
@@ -1624,7 +1663,7 @@ export function getDbExec(): DbExec {
1624
1663
  }
1625
1664
  // After init, swap to a sanitizing wrapper around the real client
1626
1665
  const wrapper: DbExec = {
1627
- execute: (s) => _exec!.execute(sanitize(s)),
1666
+ execute: (s) => execAnnotated(s),
1628
1667
  atomicBatch: _exec!.atomicBatch
1629
1668
  ? (statements) =>
1630
1669
  _exec!.atomicBatch!(statements.map((s) => sanitize(s)))
@@ -1640,7 +1679,7 @@ export function getDbExec(): DbExec {
1640
1679
  : undefined,
1641
1680
  };
1642
1681
  Object.assign(proxy, wrapper);
1643
- return _exec!.execute(sanitize(sql));
1682
+ return execAnnotated(sql);
1644
1683
  },
1645
1684
  async transaction(fn) {
1646
1685
  if (!_initPromise) _initPromise = initClient();
@@ -1652,7 +1691,7 @@ export function getDbExec(): DbExec {
1652
1691
  throw err;
1653
1692
  }
1654
1693
  const wrapper: DbExec = {
1655
- execute: (s) => _exec!.execute(sanitize(s)),
1694
+ execute: (s) => execAnnotated(s),
1656
1695
  atomicBatch: _exec!.atomicBatch
1657
1696
  ? (statements) =>
1658
1697
  _exec!.atomicBatch!(statements.map((s) => sanitize(s)))