@allbluecn/web-app 0.22.4 → 0.23.1

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 (500) hide show
  1. package/package.json +4 -14
  2. package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
  3. package/src/__tests__/story-view.test.ts +33 -1
  4. package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
  5. package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
  6. package/src/components/agents/agent-bindings-tab.tsx +59 -45
  7. package/src/components/agents/agent-form-dialog.tsx +3 -3
  8. package/src/components/agents/agent-selector.tsx +2 -3
  9. package/src/components/agents/agents-page.tsx +66 -31
  10. package/src/components/agents/slash-skill-menu.tsx +2 -3
  11. package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
  12. package/src/components/auth/session-guard.tsx +8 -0
  13. package/src/components/billing/login-prompt-dialog.tsx +1 -1
  14. package/src/components/billing/subscription-provider.tsx +2 -16
  15. package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
  16. package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
  17. package/src/components/bug/peek-panel.tsx +5 -3
  18. package/src/components/bug/views/bug-list/group.tsx +12 -2
  19. package/src/components/bug/views/bug-list/index.tsx +94 -67
  20. package/src/components/command/command-palette.tsx +10 -9
  21. package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
  22. package/src/components/design/canvas/panels/toolbar.tsx +21 -22
  23. package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
  24. package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
  25. package/src/components/design/canvas/react/canvas-context.tsx +47 -26
  26. package/src/components/design/canvas/react/hooks.ts +39 -34
  27. package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
  28. package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
  29. package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
  30. package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
  31. package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
  32. package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
  33. package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
  34. package/src/components/devtools/devtools-panel.tsx +56 -0
  35. package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
  36. package/src/components/forms/app-field-error.tsx +21 -1
  37. package/src/components/forms/app-field.tsx +8 -8
  38. package/src/components/forms/app-submit.tsx +12 -9
  39. package/src/components/forms/create-app-form.tsx +124 -0
  40. package/src/components/forms/index.ts +2 -3
  41. package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
  42. package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
  43. package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
  44. package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
  45. package/src/components/harness/ai-chat-history/index.tsx +5 -3
  46. package/src/components/harness/gallery/gallery-card.tsx +57 -35
  47. package/src/components/harness/gallery/gallery-grid.tsx +32 -15
  48. package/src/components/harness/gallery/gallery-types.ts +19 -0
  49. package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
  50. package/src/components/harness/gallery/types.ts +2 -0
  51. package/src/components/harness/gallery/use-video-generation.ts +95 -0
  52. package/src/components/harness/harness-workbench-page.tsx +2 -3
  53. package/src/components/harness/message-actions.tsx +5 -3
  54. package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
  55. package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
  56. package/src/components/iteration/burndown-chart.tsx +54 -23
  57. package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
  58. package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
  59. package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
  60. package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
  61. package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
  62. package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
  63. package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
  64. package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
  65. package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
  66. package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
  67. package/src/components/notifications/settings-notifications.tsx +37 -13
  68. package/src/components/notifications/team-notifications.tsx +10 -14
  69. package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
  70. package/src/components/settings/personal-team/account/account-client.tsx +44 -7
  71. package/src/components/settings/personal-team/team/team-client.tsx +17 -19
  72. package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
  73. package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
  74. package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
  75. package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
  76. package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
  77. package/src/components/story/columns.tsx +11 -2
  78. package/src/components/story/peek-panel.tsx +4 -2
  79. package/src/components/story/story-list-table.tsx +16 -1
  80. package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
  81. package/src/components/story/views/story-list/group.tsx +12 -1
  82. package/src/components/story/views/story-list/index.tsx +101 -68
  83. package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
  84. package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
  85. package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
  86. package/src/components/testcase/list/grouped-list.tsx +50 -22
  87. package/src/components/testcase/peek-panel.tsx +7 -4
  88. package/src/components/testcase/step-editor.tsx +196 -150
  89. package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
  90. package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
  91. package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
  92. package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
  93. package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
  94. package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
  95. package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
  96. package/src/design-engine/engine/engine-decor.ts +2 -0
  97. package/src/design-engine/engine/engine-path.ts +2 -0
  98. package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
  99. package/src/hooks/use-notifications.ts +9 -16
  100. package/src/hooks/use-unsaved-guard.tsx +87 -0
  101. package/src/lib/__tests__/pacer.test.ts +33 -96
  102. package/src/lib/__tests__/query-defaults.test.ts +169 -0
  103. package/src/lib/__tests__/query-global-error.test.ts +256 -0
  104. package/src/lib/anyhub/anyhub-store.ts +2 -2
  105. package/src/lib/changelog/sdk-versions.ts +2 -10
  106. package/src/lib/collab/presence-store.ts +5 -5
  107. package/src/lib/collab/use-presence-list-refresh.ts +2 -2
  108. package/src/lib/collab/use-share-presence.ts +2 -2
  109. package/src/lib/collections/query-keys.ts +7 -2
  110. package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
  111. package/src/lib/commands/__tests__/registry.test.ts +6 -3
  112. package/src/lib/commands/command-palette-state.ts +8 -31
  113. package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
  114. package/src/lib/hotkeys/registry.ts +188 -85
  115. package/src/lib/hotkeys/types.ts +1 -13
  116. package/src/lib/pacer.ts +1 -79
  117. package/src/lib/queries/README.md +67 -0
  118. package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
  119. package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
  120. package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
  121. package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
  122. package/src/lib/queries/agents.ts +40 -0
  123. package/src/lib/queries/compliance.ts +69 -0
  124. package/src/lib/queries/notifications.ts +63 -0
  125. package/src/lib/queries/team.ts +39 -0
  126. package/src/lib/query-defaults.ts +8 -0
  127. package/src/lib/query-global-error.ts +128 -0
  128. package/src/lib/remote-auth-client.ts +16 -0
  129. package/src/paraglide/README.md +1 -1
  130. package/src/paraglide/messages/_index.d.ts +11 -0
  131. package/src/paraglide/messages/_index.js +5659 -88285
  132. package/src/paraglide/messages/common_error_generic.d.ts +16 -0
  133. package/src/paraglide/messages/common_error_generic.js +29 -0
  134. package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
  135. package/src/paraglide/messages/common_unsaved_body.js +29 -0
  136. package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
  137. package/src/paraglide/messages/common_unsaved_leave.js +29 -0
  138. package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
  139. package/src/paraglide/messages/common_unsaved_stay.js +29 -0
  140. package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
  141. package/src/paraglide/messages/common_unsaved_title.js +29 -0
  142. package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
  143. package/src/paraglide/messages/gallery_videofailed1.js +30 -0
  144. package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
  145. package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
  146. package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
  147. package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
  148. package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
  149. package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
  150. package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
  151. package/src/paraglide/messages/gallery_videostop1.js +30 -0
  152. package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
  153. package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
  154. package/src/providers/index.tsx +5 -50
  155. package/src/routeTree.gen.ts +21 -0
  156. package/src/router.tsx +21 -8
  157. package/src/routes/__root.tsx +2 -1
  158. package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
  159. package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
  160. package/src/routes/_login/login/route.lazy.tsx +10 -9
  161. package/src/routes/_login/register/route.lazy.tsx +19 -18
  162. package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
  163. package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
  164. package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
  165. package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
  166. package/src/routes/_nav/notifications/route.tsx +6 -16
  167. package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
  168. package/src/routes/api/generate/__tests__/media.test.ts +378 -0
  169. package/src/routes/api/generate/media.ts +179 -0
  170. package/src/server/__tests__/agent-client-graph.test.ts +0 -1
  171. package/src/server/__tests__/agent-crud.test.ts +37 -4
  172. package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
  173. package/src/server/__tests__/chat-conversations.test.ts +37 -4
  174. package/src/server/__tests__/chat-list-messages.test.ts +40 -7
  175. package/src/server/__tests__/csv-export.test.ts +36 -3
  176. package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
  177. package/src/server/__tests__/remote-auth.test.ts +72 -0
  178. package/src/server/__tests__/skill-crud.test.ts +37 -4
  179. package/src/server/__tests__/sso-domain.test.ts +1 -1
  180. package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
  181. package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
  182. package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
  183. package/src/server/__tests__/testcase-board.test.ts +38 -5
  184. package/src/server/__tests__/testcase-bulk.test.ts +38 -5
  185. package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
  186. package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
  187. package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
  188. package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
  189. package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
  190. package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
  191. package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
  192. package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
  193. package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
  194. package/src/server/ai-gateway/ai-output-guard.ts +50 -2
  195. package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
  196. package/src/server/ai-gateway/media/index.ts +36 -29
  197. package/src/server/ai-gateway/media/trim-input.ts +52 -0
  198. package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
  199. package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
  200. package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
  201. package/src/server/ai-gateway/source-mode.ts +24 -8
  202. package/src/server/desktop-bootstrap.ts +74 -0
  203. package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
  204. package/src/server/lib/rate-limit.ts +0 -2
  205. package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
  206. package/src/server/middlewares/auth-context.ts +62 -0
  207. package/src/server/remote-auth.ts +58 -31
  208. package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
  209. package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
  210. package/src/server/serverFns/__tests__/billing.test.ts +37 -2
  211. package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
  212. package/src/server/serverFns/__tests__/collab.test.ts +41 -14
  213. package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
  214. package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
  215. package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
  216. package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
  217. package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
  218. package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
  219. package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
  220. package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
  221. package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
  222. package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
  223. package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
  224. package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
  225. package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
  226. package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
  227. package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
  228. package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
  229. package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
  230. package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
  231. package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
  232. package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
  233. package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
  234. package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
  235. package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
  236. package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
  237. package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
  238. package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
  239. package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
  240. package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
  241. package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
  242. package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
  243. package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
  244. package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
  245. package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
  246. package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
  247. package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
  248. package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
  249. package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
  250. package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
  251. package/src/server/serverFns/activity.ts +10 -14
  252. package/src/server/serverFns/agents/agent-crud.ts +16 -15
  253. package/src/server/serverFns/agents/bindings.ts +12 -13
  254. package/src/server/serverFns/agents/skill-crud.ts +16 -15
  255. package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
  256. package/src/server/serverFns/ai/search-capability.ts +3 -4
  257. package/src/server/serverFns/anyhub/app.ts +10 -14
  258. package/src/server/serverFns/anyhub/index.ts +7 -10
  259. package/src/server/serverFns/anyhub/install.ts +4 -4
  260. package/src/server/serverFns/anyhub/permissions.ts +10 -14
  261. package/src/server/serverFns/anyhub/proxy.ts +4 -6
  262. package/src/server/serverFns/auth/email-verification.ts +4 -5
  263. package/src/server/serverFns/auth/session.ts +9 -0
  264. package/src/server/serverFns/billing.ts +18 -28
  265. package/src/server/serverFns/blob-upload.ts +6 -9
  266. package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
  267. package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
  268. package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
  269. package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
  270. package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
  271. package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
  272. package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
  273. package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
  274. package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
  275. package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
  276. package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
  277. package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
  278. package/src/server/serverFns/bug/bug-activities.ts +4 -6
  279. package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
  280. package/src/server/serverFns/bug/bug-comments.ts +13 -18
  281. package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
  282. package/src/server/serverFns/bug/bug-crud.ts +28 -38
  283. package/src/server/serverFns/bug/bug-display-options.ts +8 -11
  284. package/src/server/serverFns/bug/bug-export.ts +5 -7
  285. package/src/server/serverFns/bug/bug-fix.ts +10 -14
  286. package/src/server/serverFns/bug/bug-groups.ts +10 -14
  287. package/src/server/serverFns/bug/bug-reactions.ts +10 -14
  288. package/src/server/serverFns/bug/bug-reorder.ts +4 -6
  289. package/src/server/serverFns/bug/bug-repro.ts +8 -11
  290. package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
  291. package/src/server/serverFns/bug/bug-story.ts +10 -14
  292. package/src/server/serverFns/bug/bug-views.ts +12 -18
  293. package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
  294. package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
  295. package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
  296. package/src/server/serverFns/changelog/fetch.ts +4 -6
  297. package/src/server/serverFns/chat/conversations.ts +17 -28
  298. package/src/server/serverFns/chat/list-messages.ts +4 -7
  299. package/src/server/serverFns/collab-presence.ts +11 -13
  300. package/src/server/serverFns/collab-snapshot.ts +6 -7
  301. package/src/server/serverFns/collab.ts +7 -7
  302. package/src/server/serverFns/compliance.ts +8 -8
  303. package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
  304. package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
  305. package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
  306. package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
  307. package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
  308. package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
  309. package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
  310. package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
  311. package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
  312. package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
  313. package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
  314. package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
  315. package/src/server/serverFns/design/ai/providers.ts +30 -39
  316. package/src/server/serverFns/design/ai/source-mode.ts +10 -12
  317. package/src/server/serverFns/design/comments.ts +14 -13
  318. package/src/server/serverFns/design/designs.ts +13 -19
  319. package/src/server/serverFns/design/page-versions.ts +17 -18
  320. package/src/server/serverFns/design/pages.ts +17 -18
  321. package/src/server/serverFns/design/reviews.ts +8 -9
  322. package/src/server/serverFns/design/shares-password.ts +13 -19
  323. package/src/server/serverFns/design/shares.ts +22 -31
  324. package/src/server/serverFns/design/versions.ts +8 -9
  325. package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
  326. package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
  327. package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
  328. package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
  329. package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
  330. package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
  331. package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
  332. package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
  333. package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
  334. package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
  335. package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
  336. package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
  337. package/src/server/serverFns/harness/attach-prototype.ts +4 -4
  338. package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
  339. package/src/server/serverFns/harness/attach-to-story.ts +4 -4
  340. package/src/server/serverFns/harness/cancel-run.ts +4 -6
  341. package/src/server/serverFns/harness/extract-attachment.ts +3 -4
  342. package/src/server/serverFns/harness/gallery.ts +19 -23
  343. package/src/server/serverFns/harness/github-workspace.ts +14 -21
  344. package/src/server/serverFns/harness/media.ts +18 -117
  345. package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
  346. package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
  347. package/src/server/serverFns/harness/workspace-files.ts +19 -26
  348. package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
  349. package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
  350. package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
  351. package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
  352. package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
  353. package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
  354. package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
  355. package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
  356. package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
  357. package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
  358. package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
  359. package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
  360. package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
  361. package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
  362. package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
  363. package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
  364. package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
  365. package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
  366. package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
  367. package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
  368. package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
  369. package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
  370. package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
  371. package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
  372. package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
  373. package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
  374. package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
  375. package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
  376. package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
  377. package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
  378. package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
  379. package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
  380. package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
  381. package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
  382. package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
  383. package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
  384. package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
  385. package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
  386. package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
  387. package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
  388. package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
  389. package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
  390. package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
  391. package/src/server/serverFns/knowledge-index.ts +12 -16
  392. package/src/server/serverFns/knowledge.ts +50 -67
  393. package/src/server/serverFns/notifications.ts +25 -34
  394. package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
  395. package/src/server/serverFns/project/module-overview.ts +7 -10
  396. package/src/server/serverFns/project.ts +48 -68
  397. package/src/server/serverFns/references/create.ts +4 -6
  398. package/src/server/serverFns/references/resolve.ts +4 -6
  399. package/src/server/serverFns/references/search.ts +4 -6
  400. package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
  401. package/src/server/serverFns/sso.ts +1 -1
  402. package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
  403. package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
  404. package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
  405. package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
  406. package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
  407. package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
  408. package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
  409. package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
  410. package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
  411. package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
  412. package/src/server/serverFns/story/link-preview.ts +5 -7
  413. package/src/server/serverFns/story/story-assoc.ts +28 -38
  414. package/src/server/serverFns/story/story-attachments.ts +7 -10
  415. package/src/server/serverFns/story/story-children.ts +4 -6
  416. package/src/server/serverFns/story/story-comments.ts +13 -18
  417. package/src/server/serverFns/story/story-crud/activities.ts +4 -6
  418. package/src/server/serverFns/story/story-crud/archive.ts +7 -10
  419. package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
  420. package/src/server/serverFns/story/story-crud/detail.ts +7 -10
  421. package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
  422. package/src/server/serverFns/story/story-crud/labels.ts +6 -9
  423. package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
  424. package/src/server/serverFns/story/story-crud.ts +13 -18
  425. package/src/server/serverFns/story/story-dependencies.ts +16 -22
  426. package/src/server/serverFns/story/story-groups.ts +10 -14
  427. package/src/server/serverFns/story/story-links.ts +13 -18
  428. package/src/server/serverFns/story/story-reactions.ts +13 -18
  429. package/src/server/serverFns/story/story-search.ts +7 -10
  430. package/src/server/serverFns/story/story-transfer.ts +10 -14
  431. package/src/server/serverFns/story/story-tree.ts +7 -10
  432. package/src/server/serverFns/story-display-options.ts +7 -8
  433. package/src/server/serverFns/story-view.ts +12 -18
  434. package/src/server/serverFns/tasks/task-crud.ts +13 -17
  435. package/src/server/serverFns/tasks/task-queries.ts +12 -18
  436. package/src/server/serverFns/team.ts +46 -66
  437. package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
  438. package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
  439. package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
  440. package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
  441. package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
  442. package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
  443. package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
  444. package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
  445. package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
  446. package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
  447. package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
  448. package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
  449. package/src/server/serverFns/testcase/automation.ts +13 -17
  450. package/src/server/serverFns/testcase/bulk.ts +10 -14
  451. package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
  452. package/src/server/serverFns/testcase/generation.ts +10 -13
  453. package/src/server/serverFns/testcase/plans.ts +23 -31
  454. package/src/server/serverFns/testcase/regression.ts +7 -10
  455. package/src/server/serverFns/testcase/run-cases.ts +13 -18
  456. package/src/server/serverFns/testcase/runs.ts +13 -18
  457. package/src/server/serverFns/testcase/stats.ts +13 -18
  458. package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
  459. package/src/server/serverFns/testcase/testcase-board.ts +4 -6
  460. package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
  461. package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
  462. package/src/server/serverFns/testcase/testcase-export.ts +5 -7
  463. package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
  464. package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
  465. package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
  466. package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
  467. package/src/server/serverFns/translation.ts +4 -6
  468. package/src/server/serverFns/user/avatar.ts +4 -6
  469. package/src/server/serverFns/user/settings.ts +4 -6
  470. package/src/server/serverFns/user/update-locale.ts +4 -6
  471. package/src/server/serverFns/workspace-kb.ts +26 -35
  472. package/src/server/serverFns/workspace.ts +43 -59
  473. package/src/server/serverFns/ws.ts +4 -6
  474. package/src/server/serverfn-public-allowlist.ts +117 -0
  475. package/src/server/story/types.ts +0 -1
  476. package/src/server/sync/__tests__/scopes.test.ts +64 -0
  477. package/src/server/sync/scopes.ts +7 -0
  478. package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
  479. package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
  480. package/src/__typeprobe__.d.ts +0 -4
  481. package/src/components/forms/app-form-dialog.tsx +0 -126
  482. package/src/components/forms/hooks/use-form-submit.ts +0 -57
  483. package/src/components/forms/types.ts +0 -34
  484. package/src/components/harness/code/workspace-picker.tsx +0 -239
  485. package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
  486. package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
  487. package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
  488. package/src/devframe-docks/session-iframe.tsx +0 -90
  489. package/src/lib/__tests__/desktop-detect.test.ts +0 -19
  490. package/src/lib/collections/index.ts +0 -22
  491. package/src/lib/collections/knowledge/index.ts +0 -18
  492. package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
  493. package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
  494. package/src/lib/collections/reference/labels-collection.ts +0 -31
  495. package/src/lib/collections/reference/projects-collection.ts +0 -31
  496. package/src/lib/collections/reference/users-brief-collection.ts +0 -35
  497. package/src/lib/collections/reference/workspaces-collection.ts +0 -34
  498. package/src/lib/desktop-detect.ts +0 -29
  499. package/src/paraglide/messages/en.js +0 -28260
  500. package/src/paraglide/messages/zh.js +0 -28258
@@ -397,6 +397,36 @@ describe("pgvector MemoryAdapter.inspect / listFacts", () => {
397
397
  })
398
398
  })
399
399
 
400
+ describe("pgvector MemoryAdapter tenantId 隔离键折叠", () => {
401
+ it("save 以 <tenantId>:<userId> 落库(官方 tenantId 隔离契约的存储侧)", async () => {
402
+ const adapter = makeAdapter()
403
+
404
+ await adapter.save(
405
+ { userId: "u1", threadId: "t1", tenantId: "acme" },
406
+ { user: "足够长的用户输入内容", assistant: "回复" },
407
+ )
408
+
409
+ const [, , ownerKey] = mocks.executeRaw.mock.calls[0]!
410
+ expect(String(ownerKey)).toBe("acme:u1")
411
+ })
412
+
413
+ it("recall 按 <tenantId>:<userId> 过滤——同 userId 跨 tenantId 互不可见", async () => {
414
+ const adapter = makeAdapter()
415
+
416
+ await adapter.recall({ userId: "u1", threadId: "t1", tenantId: "other" }, "查询")
417
+
418
+ expect(String(mocks.queryRaw.mock.calls[0]![1])).toBe("other:u1")
419
+ })
420
+
421
+ it("无 tenantId 的 scope 保持裸 userId(既有路径零影响)", async () => {
422
+ const adapter = makeAdapter()
423
+
424
+ await adapter.recall(scope, "查询")
425
+
426
+ expect(String(mocks.queryRaw.mock.calls[0]![1])).toBe("user-1")
427
+ })
428
+ })
429
+
400
430
  describe("memory scope 归属校验", () => {
401
431
  const fakeCtx = { threadId: "thread-a" } as never
402
432
 
@@ -0,0 +1,92 @@
1
+ // @vitest-environment node
2
+ // SPDX-License-Identifier: AGPL-3.0-or-later
3
+ // AllBlue - 理想之海
4
+ // Copyright (C) 2026 AllBlue Contributors
5
+ //
6
+ // This program is free software: you can redistribute it and/or modify
7
+ // it under the terms of the GNU Affero General Public License as published by
8
+ // the Free Software Foundation, either version 3 of the License, or
9
+ // (at your option) any later version.
10
+ //
11
+ // This program is distributed in the hope that it will be useful,
12
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ // GNU Affero General Public License for more details.
15
+ //
16
+ // You should have received a copy of the GNU Affero General Public License
17
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
18
+
19
+ import { afterAll, beforeAll, describe, vi } from "vitest"
20
+ import { runMemoryAdapterContract } from "@tanstack/ai-memory/testkit"
21
+ import { prisma } from "@allbluecn/database"
22
+
23
+ const mocks = vi.hoisted(() => ({
24
+ generateEmbedding: vi.fn(),
25
+ }))
26
+
27
+ // 真实 embedding 依赖外部 API key + 网络,与契约无关(契约定的是「同一文本
28
+ // 换来同一向量、相似文本向量相近」的隔离/往返语义)。用确定性 token 哈希
29
+ // 词袋向量替身:1536 维对齐 memory_facts.embedding 的 vector(1536) 列类型,
30
+ // 相似文本(共享 token)余弦相似度可预期地高于相似度阈值,round-trip 用例
31
+ // 才能走真实 pgvector SQL(<=> 距离排序)而非被 mock 掉。
32
+ vi.mock("../embedding", () => ({ generateEmbedding: mocks.generateEmbedding }))
33
+
34
+ import { createPgVectorMemoryAdapter } from "../memory/pgvector-adapter"
35
+
36
+ const EMBEDDING_DIMENSIONS = 1536
37
+
38
+ function hashToken(token: string): number {
39
+ // FNV-1a 32-bit:确定性哈希,跨进程稳定(不能用 Math.random / crypto)
40
+ let hash = 0x811c9dc5
41
+ for (let i = 0; i < token.length; i++) {
42
+ hash ^= token.charCodeAt(i)
43
+ hash = Math.imul(hash, 0x01000193)
44
+ }
45
+ return hash >>> 0
46
+ }
47
+
48
+ function testEmbedding(text: string): number[] {
49
+ const vector = new Array<number>(EMBEDDING_DIMENSIONS).fill(0)
50
+ const tokens = text.toLowerCase().split(/[^a-z0-9]+/).filter(Boolean)
51
+ for (const token of tokens) vector[hashToken(token) % EMBEDDING_DIMENSIONS] += 1
52
+ const norm = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0)) || 1
53
+ return vector.map((v) => v / norm)
54
+ }
55
+
56
+ mocks.generateEmbedding.mockImplementation(async (text: string) => testEmbedding(text))
57
+
58
+ /**
59
+ * 官方 memory adapter 契约(@tanstack/ai-memory testkit)验证 pgvector
60
+ * MemoryAdapter 满足 save/recall 往返、scope 隔离(含 tenantId)、receipt
61
+ * 形状与可选 introspection 不变量。通过即 middleware 可直接挂载。
62
+ *
63
+ * `runMemoryAdapterContract` 同步返回 void——内部自注册 Vitest describe/it,
64
+ * 调用方**不能**再用 `it` 包裹或 `await`(与 persistence conformance 同规)。
65
+ *
66
+ * ⚠ 隔离键:契约用固定 scope(u1 / u2 / alice / bob / u×tenant),adapter 把
67
+ * tenantId 折叠进 userId 隔离键(`<tenantId>:<userId>`),beforeAll/afterAll
68
+ * 按这套固定键清理(幂等,中断轮次可自愈),不触碰业务数据。
69
+ *
70
+ * CI/本地手动执行(需 DATABASE_URL 指向带 pgvector 的库):
71
+ * DATABASE_URL=postgresql://allblue:allblue@localhost:5432/allblue \
72
+ * npx vitest run --project unit src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts
73
+ */
74
+ const CONTRACT_OWNER_KEYS = ["u1", "u2", "alice", "bob", "tenant-a:u", "tenant-b:u"]
75
+
76
+ describe.skipIf(!process.env.DATABASE_URL)("pgvector memory adapter conformance", () => {
77
+ beforeAll(async () => {
78
+ for (const key of CONTRACT_OWNER_KEYS) {
79
+ await prisma.$executeRaw`DELETE FROM "memory_facts" WHERE "userId" = ${key}`
80
+ }
81
+ })
82
+
83
+ afterAll(async () => {
84
+ for (const key of CONTRACT_OWNER_KEYS) {
85
+ await prisma
86
+ .$executeRaw`DELETE FROM "memory_facts" WHERE "userId" = ${key}`.catch(() => {})
87
+ }
88
+ await prisma.$disconnect().catch(() => {})
89
+ })
90
+
91
+ runMemoryAdapterContract("AllBlue pgvector adapter", () => createPgVectorMemoryAdapter())
92
+ })
@@ -4,7 +4,7 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'
4
4
  const { prismaMock } = vi.hoisted(() => ({
5
5
  prismaMock: {
6
6
  user: { findUnique: vi.fn(), update: vi.fn() },
7
- desktopSetting: { findUnique: vi.fn(), upsert: vi.fn() },
7
+ desktopSetting: { findUnique: vi.fn(), upsert: vi.fn(), delete: vi.fn() },
8
8
  },
9
9
  }))
10
10
 
@@ -21,23 +21,62 @@ vi.mock('../runtime', () => ({
21
21
 
22
22
  import { resolveSourceMode, setSourceMode } from '../source-mode'
23
23
 
24
+ // 产物键值逐字断言:guest 键隔离后的新键与被迁移的旧全局键
25
+ const NEW_KEY = 'aiSourceMode:local-guest'
26
+ const LEGACY_KEY = 'aiSourceMode'
27
+ const GUEST = { userId: 'local-guest', isGuest: true } as const
28
+
24
29
  describe('resolveSourceMode', () => {
25
30
  beforeEach(() => vi.clearAllMocks())
26
31
 
27
- it('登录用户读 User.aiSourceMode', async () => {
32
+ it('登录用户读 User.aiSourceMode(非 guest 路径不受键隔离影响)', async () => {
28
33
  prismaMock.user.findUnique.mockResolvedValue({ aiSourceMode: 'hosted' })
29
34
  expect(await resolveSourceMode({ userId: 'u1', isGuest: false })).toBe('hosted')
30
35
  expect(prismaMock.user.findUnique).toHaveBeenCalledWith({
31
36
  where: { id: 'u1' }, select: { aiSourceMode: true },
32
37
  })
38
+ expect(prismaMock.desktopSetting.findUnique).not.toHaveBeenCalled()
33
39
  })
34
40
 
35
- it('desktop 游客读 DesktopSetting KV', async () => {
41
+ it('desktop 游客读隔离键 aiSourceMode:local-guest', async () => {
36
42
  prismaMock.desktopSetting.findUnique.mockResolvedValue({ value: 'local-agent' })
37
- expect(await resolveSourceMode({ userId: 'local-guest', isGuest: true })).toBe('local-agent')
43
+ expect(await resolveSourceMode(GUEST)).toBe('local-agent')
44
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenCalledWith({ where: { key: NEW_KEY } })
45
+ expect(prismaMock.desktopSetting.findUnique).not.toHaveBeenCalledWith({ where: { key: LEGACY_KEY } })
38
46
  expect(prismaMock.user.findUnique).not.toHaveBeenCalled()
39
47
  })
40
48
 
49
+ it('迁移读:新键缺失 + 旧键存在 → 返回旧键值(读路径不写不删)', async () => {
50
+ prismaMock.desktopSetting.findUnique
51
+ .mockResolvedValueOnce(null) // 新键查询:无行
52
+ .mockResolvedValueOnce({ value: 'byok' }) // 旧键查询:有行
53
+ expect(await resolveSourceMode(GUEST)).toBe('byok')
54
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenNthCalledWith(1, { where: { key: NEW_KEY } })
55
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenNthCalledWith(2, { where: { key: LEGACY_KEY } })
56
+ expect(prismaMock.desktopSetting.upsert).not.toHaveBeenCalled()
57
+ expect(prismaMock.desktopSetting.delete).not.toHaveBeenCalled()
58
+ })
59
+
60
+ it('迁移读:新键已存在时不查旧键', async () => {
61
+ prismaMock.desktopSetting.findUnique.mockResolvedValue({ value: 'hosted' })
62
+ expect(await resolveSourceMode(GUEST)).toBe('hosted')
63
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenCalledTimes(1)
64
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenCalledWith({ where: { key: NEW_KEY } })
65
+ })
66
+
67
+ it('迁移读:新键与旧键均缺失 → null(默认 byok 行为)', async () => {
68
+ prismaMock.desktopSetting.findUnique.mockResolvedValue(null)
69
+ expect(await resolveSourceMode(GUEST)).toBeNull()
70
+ expect(prismaMock.desktopSetting.findUnique).toHaveBeenCalledTimes(2)
71
+ })
72
+
73
+ it('迁移读:旧键脏值 → null(对称收窄)', async () => {
74
+ prismaMock.desktopSetting.findUnique
75
+ .mockResolvedValueOnce(null)
76
+ .mockResolvedValueOnce({ value: 'invalid-mode' })
77
+ expect(await resolveSourceMode(GUEST)).toBeNull()
78
+ })
79
+
41
80
  it('无记录返回 null(默认 byok 行为)', async () => {
42
81
  prismaMock.user.findUnique.mockResolvedValue({ aiSourceMode: null })
43
82
  expect(await resolveSourceMode({ userId: 'u1', isGuest: false })).toBeNull()
@@ -65,13 +104,15 @@ describe('setSourceMode', () => {
65
104
  expect(invalidateMock).toHaveBeenCalledWith('u1')
66
105
  })
67
106
 
68
- it('游客写 DesktopSetting KV(不碰 User 表,避免 P2025)', async () => {
69
- await setSourceMode({ userId: 'local-guest', isGuest: true }, 'local-agent')
107
+ it('游客写隔离键 aiSourceMode:local-guest(迁移读后写入落新键,旧键不删除)', async () => {
108
+ await setSourceMode(GUEST, 'local-agent')
70
109
  expect(prismaMock.desktopSetting.upsert).toHaveBeenCalledWith({
71
- where: { key: 'aiSourceMode' },
72
- create: { key: 'aiSourceMode', value: 'local-agent' },
110
+ where: { key: NEW_KEY },
111
+ create: { key: NEW_KEY, value: 'local-agent' },
73
112
  update: { value: 'local-agent' },
74
113
  })
114
+ expect(prismaMock.desktopSetting.delete).not.toHaveBeenCalled()
75
115
  expect(prismaMock.user.update).not.toHaveBeenCalled()
116
+ expect(invalidateMock).not.toHaveBeenCalled()
76
117
  })
77
118
  })
@@ -15,12 +15,16 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
+ import { contentGuardMiddleware } from "@tanstack/ai/middlewares"
19
+
18
20
  /**
19
21
  * AI 输出泄露扫描(ai-output-guard)
20
22
  *
21
23
  * 注入攻击可能在 LLM 输出中回显 per-user API key、系统提示词或内网地址。
22
- * 本模块提供纯函数 `scanAiOutputForSecrets`,后续可挂到 TanStack AI 的
23
- * middleware `onFinish` 钩子对完整输出做一次输出侧拦截。
24
+ * 本模块提供纯函数 `scanAiOutputForSecrets`(检出)与 `redactAiOutputSecrets`
25
+ * (脱敏替换),并由 `createAiOutputGuardMiddleware` 包进官方
26
+ * `contentGuardMiddleware`,挂入 chat-stream-builder 的中间件链对流式输出
27
+ * 做输出侧拦截。
24
28
  *
25
29
  * 设计权衡(有意为之,勿当 bug 修掉):
26
30
  * 1. 只回传命中的规则 type,不回传匹配原文——扫描结果本身若被日志/告警
@@ -75,3 +79,47 @@ export function scanAiOutputForSecrets(text: string): AiOutputScanResult {
75
79
  }
76
80
  return { leaked: hits.length > 0, hits }
77
81
  }
82
+
83
+ /**
84
+ * 对一段 AI 输出文本做密钥脱敏:命中规则的部分替换为 `[REDACTED:<type>]`
85
+ * (占位符约定与 log-redact.ts 一致)。与 `scanAiOutputForSecrets` 共用同一
86
+ * RULES 单一来源——新增规则时检出与脱敏同步生效,不会出现「扫得出但抹不掉」。
87
+ */
88
+ export function redactAiOutputSecrets(text: string): string {
89
+ let out = text
90
+ for (const rule of RULES) {
91
+ rule.pattern.lastIndex = 0
92
+ out = out.replace(rule.pattern, `[REDACTED:${rule.type}]`)
93
+ }
94
+ return out
95
+ }
96
+
97
+ /**
98
+ * 把密钥脱敏包进官方 contentGuardMiddleware(chat-stream-builder 中间件链挂载)。
99
+ *
100
+ * - 拦截策略保守:默认替换脱敏(blockOnMatch 缺省 false),不终止整条消息。
101
+ * - strategy 用 buffered:规则可命中跨 chunk 分片的密钥(delta 策略会漏)。
102
+ * bufferSize 取 200:buffered 只保证「已流入安全窗口」的文本被检查,密钥形态
103
+ * 实际长度普遍在 200 字符内(规则本身无上界,残余风险仅限超长 secret 的
104
+ * 流中前缀,流结束 flush 仍会全量脱敏)。
105
+ * - onFiltered 只记录命中类型,不落原文(泄露面约定同上)。
106
+ *
107
+ * 必须每次 chat() 装配时新建实例(buffered 策略的缓冲状态存于闭包,单例会
108
+ * 在并发 run 间串写)。
109
+ */
110
+ export function createAiOutputGuardMiddleware() {
111
+ const loggedMessageIds = new Set<string>()
112
+ return contentGuardMiddleware({
113
+ rules: [{ fn: redactAiOutputSecrets }],
114
+ strategy: "buffered",
115
+ bufferSize: 200,
116
+ onFiltered: (info) => {
117
+ if (loggedMessageIds.has(info.messageId)) return
118
+ loggedMessageIds.add(info.messageId)
119
+ const { hits } = scanAiOutputForSecrets(info.original)
120
+ console.warn(
121
+ `[AI] 输出含疑似密钥已脱敏 messageId=${info.messageId} hits=${hits.join(",")}`,
122
+ )
123
+ },
124
+ })
125
+ }
@@ -35,6 +35,7 @@ import {
35
35
  createUsageLogMiddleware,
36
36
  resolveUsageWorkspaceId,
37
37
  } from './middlewares'
38
+ import { createAiOutputGuardMiddleware } from './ai-output-guard'
38
39
  import { planApprovalInterrupt } from './tools/plan-submit'
39
40
  import { withPersistence } from '@tanstack/ai-persistence'
40
41
  import type { AppContext } from './types'
@@ -274,6 +275,8 @@ export async function buildChatStreamWithTools(
274
275
  toolCacheMiddleware({
275
276
  toolNames: ["web_search", "knowledge_search"],
276
277
  }),
278
+ // 输出侧密钥拦截在持久化/计量之前:DB 快照与下游拿到的都是脱敏后文本
279
+ createAiOutputGuardMiddleware(),
277
280
  createToolBudgetMiddleware(),
278
281
  createPlanApprovalMiddleware(),
279
282
  compactionMiddleware(resolveCompactionOptions()),
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto"
2
2
  import { Buffer } from "node:buffer"
3
- import { generateImage, generateVideo, getVideoJobStatus, generateSpeech, generateTranscription } from "@tanstack/ai"
4
- import type { PersistedArtifactRef, ImageGenerationResult, TranscriptionResult } from "@tanstack/ai"
3
+ import { generateImage, generateVideo, generateSpeech, generateTranscription } from "@tanstack/ai"
4
+ import type { PersistedArtifactRef, ImageGenerationResult, MediaPrompt, StreamChunk, TranscriptionResult } from "@tanstack/ai"
5
5
  import { withGenerationPersistence, defineGenerationRunStore } from "@tanstack/ai-persistence"
6
6
  import type { GenerationRunRecord } from "@tanstack/ai-persistence"
7
7
  import { prisma, type Prisma } from "@allbluecn/database"
@@ -38,13 +38,6 @@ function json(value: unknown): Prisma.InputJsonValue {
38
38
  return JSON.parse(JSON.stringify(value))
39
39
  }
40
40
 
41
- /** 与 SDK 非流式 generateVideo 派生 run id 对齐(videoRunIdForJob,SDK 未导出):
42
- * `video:<encodeURIComponent(provider)>:<encodeURIComponent(jobId)>`。
43
- * 本地拼法与 pollVideoForUser 的查询 key 共用同一来源,避免两份拼法漂移。 */
44
- function videoRunStoreKey(adapterName: string, jobId: string) {
45
- return `video:${encodeURIComponent(adapterName)}:${encodeURIComponent(jobId)}`
46
- }
47
-
48
41
  export function createGenerationRunStore(opts: Pick<MediaGenOptions, "userId" | "threadId">, input?: unknown) {
49
42
  const scope = { userId: opts.userId, threadId: opts.threadId }
50
43
  const mapRow = (row: Prisma.GenerationRunGetPayload<{ select: typeof runSelect }>): GenerationRunRecord => ({
@@ -159,33 +152,47 @@ export async function generateImageForUser(opts: MediaGenOptions & {
159
152
  return { ...result, runId }
160
153
  }
161
154
 
162
- export async function generateVideoForUser(opts: MediaGenOptions & { prompt: string; duration?: number; size?: string; referenceImage?: string }) {
155
+ /**
156
+ * 流式视频生成(官方 generateVideo stream 模式):一次调用覆盖
157
+ * create → poll → complete 全生命周期,AsyncIterable<StreamChunk> 事件序列为
158
+ * RUN_STARTED → CUSTOM(video:job:created) → CUSTOM(video:status)* →
159
+ * CUSTOM(generation:result) → RUN_FINISHED(失败 RUN_ERROR),与
160
+ * useGenerateVideo 客户端逐事件对应。run 记录 id = 调用方 runId(流式模式由
161
+ * RUN_STARTED 段落库),非流式 submit 的 videoRunStoreKey 派生口径不适用。
162
+ *
163
+ * prompt 为 MediaPrompt 时,url source 经 imageDataUrl 归一(SSRF 守卫 + data:
164
+ * 转换,与 referenceImage 同口径;Sora/Veo 等仅收字节输入的 provider 因此兼容)。
165
+ */
166
+ export async function streamVideoForUser(opts: MediaGenOptions & { prompt: MediaPrompt; duration?: number; size?: string; referenceImage?: string; runId?: string }): Promise<AsyncIterable<StreamChunk>> {
163
167
  await requireThread(opts)
164
168
  const adapter = resolveVideoAdapter(opts)
165
169
  if (adapter.kind === "unavailable") throw new Error(`Video generation is not available for adapter kind: ${opts.adapterKind}`)
166
- const reference = opts.referenceImage ? await imageDataUrl(opts.referenceImage) : undefined
167
- const prompt = reference
168
- ? [
169
- { type: "text" as const, content: opts.prompt },
170
- { type: "image" as const, source: { type: "url" as const, value: reference }, metadata: { role: "start_frame" as const } },
171
- ]
172
- : opts.prompt
173
- const result = await generateVideo({ adapter, prompt, duration: opts.duration, size: opts.size, threadId: opts.threadId,
170
+ const [reference, prompt] = await Promise.all([
171
+ opts.referenceImage ? imageDataUrl(opts.referenceImage) : undefined,
172
+ normalizeMediaPrompt(opts.prompt),
173
+ ])
174
+ const startFrame = reference
175
+ ? { type: "image" as const, source: { type: "url" as const, value: reference }, metadata: { role: "start_frame" as const } }
176
+ : undefined
177
+ const mediaPrompt: MediaPrompt = !startFrame
178
+ ? prompt
179
+ : typeof prompt === "string"
180
+ ? [{ type: "text", content: prompt }, startFrame]
181
+ : [startFrame, ...prompt]
182
+ return generateVideo({ adapter, prompt: mediaPrompt, duration: opts.duration, size: opts.size, threadId: opts.threadId,
183
+ ...(opts.runId ? { runId: opts.runId } : {}),
184
+ stream: true,
174
185
  middleware: [withGenerationPersistence({ stores: { generationRuns: createGenerationRunStore(opts, { prompt: opts.prompt, referenceImage: opts.referenceImage }) } })],
175
186
  })
176
- return { ...result, runId: videoRunStoreKey(adapter.name, result.jobId) }
177
187
  }
178
188
 
179
- export async function pollVideoForUser(opts: MediaGenOptions & { jobId: string }) {
180
- await requireThread(opts)
181
- const adapter = resolveVideoAdapter(opts)
182
- if (adapter.kind === "unavailable") throw new Error(`Video generation is not available for adapter kind: ${opts.adapterKind}`)
183
- const store = createGenerationRunStore(opts)
184
- const run = await store.get(videoRunStoreKey(adapter.name, opts.jobId))
185
- if (!run || run.activity !== "video" || run.model !== opts.model) throw new Error("Video run not found")
186
- return getVideoJobStatus({ adapter, jobId: opts.jobId, threadId: opts.threadId,
187
- middleware: [withGenerationPersistence({ stores: { generationRuns: store } })],
188
- })
189
+ /** url source 一律经 imageDataUrl(SSRF 守卫 + data: 归一 + 大小上限),字符串原样 */
190
+ async function normalizeMediaPrompt(prompt: MediaPrompt): Promise<MediaPrompt> {
191
+ if (typeof prompt === "string") return prompt
192
+ return Promise.all(prompt.map(async (part) => {
193
+ if (part.type !== "image" || part.source.type !== "url") return part
194
+ return { ...part, source: { type: "url" as const, value: await imageDataUrl(part.source.value) } }
195
+ }))
189
196
  }
190
197
 
191
198
  export async function generateSpeechForUser(opts: MediaGenOptions & { text: string; voice?: string; format?: "mp3" | "opus" | "aac" | "flac" | "wav" | "pcm" }) {
@@ -0,0 +1,52 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+ //
5
+ // This program is free software: you can redistribute it and/or modify
6
+ // it under the terms of the GNU Affero General Public License as published by
7
+ // the Free Software Foundation, either version 3 of the License, or
8
+ // (at your option) any later version.
9
+ //
10
+ // This program is distributed in the hope that it will be useful,
11
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ // GNU Affero General Public License for more details.
14
+ //
15
+ // You should have received a copy of the GNU Affero General Public License
16
+ // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+
18
+ /**
19
+ * 画廊列表 input 快照裁剪(任务 15 审查 ①):流式视频 run 的持久化 input 含
20
+ * `prompt: MediaPrompt`(参考帧路径会把 ≤10MB data URL 塞进图片分片)与可选
21
+ * `referenceImage`(路由 schema 上限 16MB),listGenerationRunsFn 若全量透传,
22
+ * 24 条/页的响应会膨胀到数百 MB,而列表消费面(getSourcePrompt)只读字符串与
23
+ * text 分片。故:媒体分片(image/video/audio part)整片剔除、referenceImage 键
24
+ * 剔除、任意位置出现的 data: 字符串剔除;文本与轻量标量(size/text/
25
+ * numberOfImages 等)保留。独立纯模块:无服务端重依赖,serverFn 与测试可直接
26
+ * 消费,不经 media/index.ts 的 prisma 依赖链。
27
+ *
28
+ * 只作用于列表出口——持久化快照(createGenerationRunStore 落库)不受影响。
29
+ *
30
+ * 泛型签名:输出是输入的深子集(只删不增不变形,JSON 域内形状保持),
31
+ * 调用方(Prisma JsonValue)经 TanStack Start 可序列化校验不受影响。
32
+ */
33
+ export function trimGenerationRunInput<T>(input: T): T | undefined {
34
+ if (typeof input === "string") return (input.startsWith("data:") ? undefined : input) as T | undefined
35
+ if (Array.isArray(input)) {
36
+ const mapped = (input as unknown[]).map((entry) => trimGenerationRunInput(entry)).filter((entry) => entry !== undefined)
37
+ return (mapped.length ? mapped : undefined) as T | undefined
38
+ }
39
+ if (input && typeof input === "object") {
40
+ const record = input as Record<string, unknown>
41
+ // 媒体分片按类型整片剔除(其 source.value 为 data: URL,列表无消费面)
42
+ if (record.type === "image" || record.type === "video" || record.type === "audio") return undefined
43
+ const out: Record<string, unknown> = {}
44
+ for (const [key, value] of Object.entries(record)) {
45
+ if (key === "referenceImage") continue
46
+ const trimmed = trimGenerationRunInput(value)
47
+ if (trimmed !== undefined) out[key] = trimmed
48
+ }
49
+ return (Object.keys(out).length ? out : undefined) as T | undefined
50
+ }
51
+ return input
52
+ }
@@ -80,6 +80,18 @@ function buildFactContent(turn: MemoryTurn): string {
80
80
  return [turn.user.trim(), turn.assistant.trim()].filter(Boolean).join("\n")
81
81
  }
82
82
 
83
+ /**
84
+ * 记忆隔离键:tenantId 在场时折叠进 userId(`<tenantId>:<userId>`)。
85
+ *
86
+ * memory_facts 表只有 userId 隔离列(无 tenantId 列);官方 MemoryScope 把
87
+ * tenantId 定义为租户边界,testkit 契约(runMemoryAdapterContract)明确要求
88
+ * 同 threadId+userId 跨 tenantId 互不可见。AllBlue 自身不传 tenantId(单租户
89
+ * 部署),该折叠对既有路径零影响;未来多租户接入时无需再动表结构。
90
+ */
91
+ function scopeOwnerKey(scope: { userId?: string; tenantId?: string }): string {
92
+ return scope.tenantId ? `${scope.tenantId}:${scope.userId}` : (scope.userId ?? "")
93
+ }
94
+
83
95
  // pgvector 原生表由 raw SQL 读写(Prisma 无法表达 vector 类型)
84
96
  export function createPgVectorMemoryAdapter(
85
97
  options: PgVectorMemoryAdapterOptions = {},
@@ -95,16 +107,16 @@ const MEMORY_FACTS_USER_CAP = 200
95
107
  const takenAt = () => (options.now ?? (() => new Date().toISOString()))()
96
108
 
97
109
  async function listFactsByUser(
98
- userId: string,
110
+ ownerKey: string,
99
111
  limit: number,
100
112
  ): Promise<Array<StoredFactRow>> {
101
113
  return (await prisma.$queryRaw<Array<StoredFactRow>>`
102
- SELECT "id", "content", "threadId", "createdAt" FROM "memory_facts" WHERE "userId" = ${userId} ORDER BY "createdAt" DESC LIMIT ${limit}`
114
+ SELECT "id", "content", "threadId", "createdAt" FROM "memory_facts" WHERE "userId" = ${ownerKey} ORDER BY "createdAt" DESC LIMIT ${limit}`
103
115
  )
104
116
  }
105
117
 
106
118
  async function searchFacts(
107
- userId: string,
119
+ ownerKey: string,
108
120
  query: string,
109
121
  ): Promise<Array<StoredFactRow>> {
110
122
  const embedding = await generateEmbedding(query)
@@ -112,13 +124,13 @@ const MEMORY_FACTS_USER_CAP = 200
112
124
  const embeddingStr = formatVector(embedding)
113
125
  return (await prisma.$queryRaw<Array<StoredFactRow>>`
114
126
  SELECT "id", "content", "threadId", "createdAt", 1 - (embedding <=> ${embeddingStr}::vector) AS similarity
115
- FROM "memory_facts" WHERE "userId" = ${userId} ORDER BY embedding <=> ${embeddingStr}::vector LIMIT ${candidateLimit}`
127
+ FROM "memory_facts" WHERE "userId" = ${ownerKey} ORDER BY embedding <=> ${embeddingStr}::vector LIMIT ${candidateLimit}`
116
128
  )
117
129
  }
118
130
 
119
- async function countFacts(userId: string): Promise<MemoryStatsRow> {
131
+ async function countFacts(ownerKey: string): Promise<MemoryStatsRow> {
120
132
  const rows = await prisma.$queryRaw<Array<MemoryStatsRow>>`
121
- SELECT COUNT(*)::int AS "c", COALESCE(MAX("createdAt"), NOW()) AS "lastWrite" FROM "memory_facts" WHERE "userId" = ${userId}`
133
+ SELECT COUNT(*)::int AS "c", COALESCE(MAX("createdAt"), NOW()) AS "lastWrite" FROM "memory_facts" WHERE "userId" = ${ownerKey}`
122
134
  return rows[0] ?? { c: 0, lastWrite: null }
123
135
  }
124
136
 
@@ -132,12 +144,13 @@ const MEMORY_FACTS_USER_CAP = 200
132
144
 
133
145
  let hits: Array<StoredFactRow & { score: number }>
134
146
  try {
147
+ const ownerKey = scopeOwnerKey(scope)
135
148
  // 零记忆快路径:召回在 chat 链路的每个 turn 都会执行,先跑一次
136
149
  // 计数,尚无沉淀的用户直接返回空注入,省掉一次 embedding 调用。
137
- if (Number((await countFacts(scope.userId)).c) === 0) {
150
+ if (Number((await countFacts(ownerKey)).c) === 0) {
138
151
  return { systemPrompt: "" }
139
152
  }
140
- hits = (await searchFacts(scope.userId, query))
153
+ hits = (await searchFacts(ownerKey, query))
141
154
  .map((row) => ({ ...row, score: Number(row.similarity ?? 0) }))
142
155
  .filter((row) => row.score >= similarityThreshold)
143
156
  .slice(0, topK)
@@ -174,6 +187,7 @@ const MEMORY_FACTS_USER_CAP = 200
174
187
 
175
188
  const content = buildFactContent(turn)
176
189
  try {
190
+ const ownerKey = scopeOwnerKey(scope)
177
191
  const embedding = await generateEmbedding(content)
178
192
  if (!Array.isArray(embedding) || embedding.length === 0) {
179
193
  return [{ ok: false, error: "embedding returned an empty vector" }]
@@ -181,16 +195,16 @@ const MEMORY_FACTS_USER_CAP = 200
181
195
  const id = randomUUID()
182
196
  await prisma.$executeRaw`
183
197
  INSERT INTO "memory_facts" ("id", "userId", "threadId", "content", "embedding")
184
- VALUES (${id}, ${scope.userId}, ${scope.threadId}, ${content}, ${formatVector(embedding)}::vector)`
198
+ VALUES (${id}, ${ownerKey}, ${scope.threadId}, ${content}, ${formatVector(embedding)}::vector)`
185
199
  // S25(裁决:按高收益方向):per-user 上限窗口——超限淘汰最旧,防无限
186
200
  // 增长;相似度去重复杂度不成比例,不做。上限写死 200(长期记忆召回
187
201
  // 取 TOP-K 远小于此,裁剪只影响被淘汰的最旧事实)。
188
202
  await prisma.$executeRaw`
189
203
  DELETE FROM "memory_facts"
190
- WHERE "userId" = ${scope.userId}
204
+ WHERE "userId" = ${ownerKey}
191
205
  AND "id" NOT IN (
192
206
  SELECT "id" FROM "memory_facts"
193
- WHERE "userId" = ${scope.userId}
207
+ WHERE "userId" = ${ownerKey}
194
208
  ORDER BY "createdAt" DESC
195
209
  LIMIT ${MEMORY_FACTS_USER_CAP}
196
210
  )`
@@ -216,7 +230,7 @@ const MEMORY_FACTS_USER_CAP = 200
216
230
  if (!scope.userId) {
217
231
  return { takenAt: takenAt(), data: { userId: null, threadId: scope.threadId, count: 0 } }
218
232
  }
219
- const stats = await countFacts(scope.userId)
233
+ const stats = await countFacts(scopeOwnerKey(scope))
220
234
  return {
221
235
  takenAt: takenAt(),
222
236
  data: {
@@ -230,7 +244,7 @@ const MEMORY_FACTS_USER_CAP = 200
230
244
 
231
245
  async listFacts(scope): Promise<Array<MemoryFact>> {
232
246
  if (!scope.userId) return []
233
- const rows = await listFactsByUser(scope.userId, listFactsLimit)
247
+ const rows = await listFactsByUser(scopeOwnerKey(scope), listFactsLimit)
234
248
  return rows.map((row) => ({
235
249
  id: row.id,
236
250
  text: row.content,