@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.
- package/package.json +4 -14
- package/src/__tests__/integration/login-email-acceptance.test.ts +16 -0
- package/src/__tests__/story-view.test.ts +33 -1
- package/src/components/agents/__tests__/agent-bindings-tab.test.tsx +221 -0
- package/src/components/agents/__tests__/agents-page.test.tsx +284 -0
- package/src/components/agents/agent-bindings-tab.tsx +59 -45
- package/src/components/agents/agent-form-dialog.tsx +3 -3
- package/src/components/agents/agent-selector.tsx +2 -3
- package/src/components/agents/agents-page.tsx +66 -31
- package/src/components/agents/slash-skill-menu.tsx +2 -3
- package/src/components/auth/__tests__/session-guard.test.tsx +74 -0
- package/src/components/auth/session-guard.tsx +8 -0
- package/src/components/billing/login-prompt-dialog.tsx +1 -1
- package/src/components/billing/subscription-provider.tsx +2 -16
- package/src/components/bug/__tests__/peek-modes.test.tsx +8 -0
- package/src/components/bug/__tests__/subgroup-header.test.tsx +202 -40
- package/src/components/bug/peek-panel.tsx +5 -3
- package/src/components/bug/views/bug-list/group.tsx +12 -2
- package/src/components/bug/views/bug-list/index.tsx +94 -67
- package/src/components/command/command-palette.tsx +10 -9
- package/src/components/design/canvas/document/frame-custom-presets.ts +9 -1
- package/src/components/design/canvas/panels/toolbar.tsx +21 -22
- package/src/components/design/canvas/react/__tests__/canvas-context-load-echo.test.tsx +199 -0
- package/src/components/design/canvas/react/__tests__/hooks.test.tsx +17 -3
- package/src/components/design/canvas/react/canvas-context.tsx +47 -26
- package/src/components/design/canvas/react/hooks.ts +39 -34
- package/src/components/design/editor/layout/__tests__/collab-param-wiring.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/context-menu.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/no-page-save-toast.test.tsx +17 -0
- package/src/components/design/editor/layout/__tests__/persistence-restore.test.tsx +17 -1
- package/src/components/design/editor/layout/__tests__/ssr-localstorage-smoke.test.tsx +17 -1
- package/src/components/design/editor/layout/design-editor-layout.tsx +13 -2
- package/src/components/design/editor/layout/hooks/use-design-auto-save.ts +3 -19
- package/src/components/devtools/devtools-panel.tsx +56 -0
- package/src/components/forms/__tests__/create-app-form.test.tsx +131 -0
- package/src/components/forms/app-field-error.tsx +21 -1
- package/src/components/forms/app-field.tsx +8 -8
- package/src/components/forms/app-submit.tsx +12 -9
- package/src/components/forms/create-app-form.tsx +124 -0
- package/src/components/forms/index.ts +2 -3
- package/src/components/harness/__tests__/gallery-behavior.test.tsx +37 -1
- package/src/components/harness/__tests__/harness-workbench-first-send.test.tsx +4 -1
- package/src/components/harness/__tests__/message-actions.test.tsx +7 -6
- package/src/components/harness/__tests__/use-video-generation.test.tsx +165 -0
- package/src/components/harness/ai-chat-history/index.tsx +5 -3
- package/src/components/harness/gallery/gallery-card.tsx +57 -35
- package/src/components/harness/gallery/gallery-grid.tsx +32 -15
- package/src/components/harness/gallery/gallery-types.ts +19 -0
- package/src/components/harness/gallery/gallery-video-card.tsx +54 -0
- package/src/components/harness/gallery/types.ts +2 -0
- package/src/components/harness/gallery/use-video-generation.ts +95 -0
- package/src/components/harness/harness-workbench-page.tsx +2 -3
- package/src/components/harness/message-actions.tsx +5 -3
- package/src/components/hotkeys/shortcuts-panel.tsx +28 -13
- package/src/components/iteration/__tests__/burndown-chart.test.tsx +118 -0
- package/src/components/iteration/burndown-chart.tsx +54 -23
- package/src/components/knowledge/editor/__tests__/knowledge-doc-editor-collab.test.tsx +96 -0
- package/src/components/knowledge/editor/knowledge-doc-editor.tsx +56 -1
- package/src/components/knowledge/editor/mindmap-doc-editor.tsx +12 -0
- package/src/components/knowledge/editor/use-auto-save-channels.ts +3 -1
- package/src/components/layout/sidebar-03/__tests__/nav-workspace.test.tsx +0 -4
- package/src/components/layout/sidebar-03/nav-knowledge.tsx +11 -11
- package/src/components/layout/sidebar-03/nav-main.tsx +12 -15
- package/src/components/layout/sidebar-03/nav-notifications.tsx +5 -8
- package/src/components/layout/sidebar-03/nav-workspace.tsx +11 -9
- package/src/components/notifications/__tests__/settings-notifications.test.tsx +158 -0
- package/src/components/notifications/settings-notifications.tsx +37 -13
- package/src/components/notifications/team-notifications.tsx +10 -14
- package/src/components/settings/personal-team/account/__tests__/account-client.test.tsx +170 -0
- package/src/components/settings/personal-team/account/account-client.tsx +44 -7
- package/src/components/settings/personal-team/team/team-client.tsx +17 -19
- package/src/components/shared-kit/list-kit/__tests__/frozen-header.test.ts +210 -41
- package/src/components/shared-kit/list-kit/frozen-header.ts +134 -21
- package/src/components/shared-kit/list-kit/kanban/frozen-header.ts +20 -27
- package/src/components/story/__tests__/peek-panel-patch-sync.test.tsx +8 -0
- package/src/components/story/__tests__/story-list-table.test.tsx +214 -0
- package/src/components/story/columns.tsx +11 -2
- package/src/components/story/peek-panel.tsx +4 -2
- package/src/components/story/story-list-table.tsx +16 -1
- package/src/components/story/views/story-list/__tests__/frozen-headers.test.tsx +186 -0
- package/src/components/story/views/story-list/group.tsx +12 -1
- package/src/components/story/views/story-list/index.tsx +101 -68
- package/src/components/testcase/__tests__/case-row.test.tsx +134 -2
- package/src/components/testcase/__tests__/peek-panel.test.tsx +8 -0
- package/src/components/testcase/__tests__/step-editor.test.tsx +106 -0
- package/src/components/testcase/list/grouped-list.tsx +50 -22
- package/src/components/testcase/peek-panel.tsx +7 -4
- package/src/components/testcase/step-editor.tsx +196 -150
- package/src/components/tiptap/notion-like/editor-extensions.ts +0 -3
- package/src/components/tiptap/notion-like/notion-like-editor-header.tsx +1 -31
- package/src/components/tiptap/notion-like/notion-like-editor.tsx +32 -1
- package/src/components/workspace/__tests__/dialogs/create-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/__tests__/dialogs/edit-workspace-dialog.test.tsx +2 -28
- package/src/components/workspace/dialogs/create-workspace-dialog.tsx +45 -43
- package/src/components/workspace/dialogs/edit-workspace-dialog.tsx +38 -36
- package/src/design-engine/engine/engine-decor.ts +2 -0
- package/src/design-engine/engine/engine-path.ts +2 -0
- package/src/hooks/__tests__/use-unsaved-guard.test.tsx +180 -0
- package/src/hooks/use-notifications.ts +9 -16
- package/src/hooks/use-unsaved-guard.tsx +87 -0
- package/src/lib/__tests__/pacer.test.ts +33 -96
- package/src/lib/__tests__/query-defaults.test.ts +169 -0
- package/src/lib/__tests__/query-global-error.test.ts +256 -0
- package/src/lib/anyhub/anyhub-store.ts +2 -2
- package/src/lib/changelog/sdk-versions.ts +2 -10
- package/src/lib/collab/presence-store.ts +5 -5
- package/src/lib/collab/use-presence-list-refresh.ts +2 -2
- package/src/lib/collab/use-share-presence.ts +2 -2
- package/src/lib/collections/query-keys.ts +7 -2
- package/src/lib/commands/__tests__/command-palette-state.test.ts +12 -14
- package/src/lib/commands/__tests__/registry.test.ts +6 -3
- package/src/lib/commands/command-palette-state.ts +8 -31
- package/src/lib/hotkeys/__tests__/registry.test.ts +71 -18
- package/src/lib/hotkeys/registry.ts +188 -85
- package/src/lib/hotkeys/types.ts +1 -13
- package/src/lib/pacer.ts +1 -79
- package/src/lib/queries/README.md +67 -0
- package/src/lib/queries/__tests__/agents-queries.test.ts +101 -0
- package/src/lib/queries/__tests__/compliance-queries.test.ts +136 -0
- package/src/lib/queries/__tests__/notifications-queries.test.ts +175 -0
- package/src/lib/queries/__tests__/team-queries.test.ts +87 -0
- package/src/lib/queries/agents.ts +40 -0
- package/src/lib/queries/compliance.ts +69 -0
- package/src/lib/queries/notifications.ts +63 -0
- package/src/lib/queries/team.ts +39 -0
- package/src/lib/query-defaults.ts +8 -0
- package/src/lib/query-global-error.ts +128 -0
- package/src/lib/remote-auth-client.ts +16 -0
- package/src/paraglide/README.md +1 -1
- package/src/paraglide/messages/_index.d.ts +11 -0
- package/src/paraglide/messages/_index.js +5659 -88285
- package/src/paraglide/messages/common_error_generic.d.ts +16 -0
- package/src/paraglide/messages/common_error_generic.js +29 -0
- package/src/paraglide/messages/common_unsaved_body.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_body.js +29 -0
- package/src/paraglide/messages/common_unsaved_leave.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_leave.js +29 -0
- package/src/paraglide/messages/common_unsaved_stay.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_stay.js +29 -0
- package/src/paraglide/messages/common_unsaved_title.d.ts +16 -0
- package/src/paraglide/messages/common_unsaved_title.js +29 -0
- package/src/paraglide/messages/gallery_videofailed1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videofailed1.js +30 -0
- package/src/paraglide/messages/gallery_videogenerating1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videogenerating1.js +30 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.d.ts +17 -0
- package/src/paraglide/messages/gallery_videomissingprompt2.js +30 -0
- package/src/paraglide/messages/gallery_videoprogress1.d.ts +19 -0
- package/src/paraglide/messages/gallery_videoprogress1.js +30 -0
- package/src/paraglide/messages/gallery_videostop1.d.ts +17 -0
- package/src/paraglide/messages/gallery_videostop1.js +30 -0
- package/src/paraglide/messages/story_table_resizecolumn1.d.ts +17 -0
- package/src/paraglide/messages/story_table_resizecolumn1.js +30 -0
- package/src/providers/index.tsx +5 -50
- package/src/routeTree.gen.ts +21 -0
- package/src/router.tsx +21 -8
- package/src/routes/__root.tsx +2 -1
- package/src/routes/__tests__/-edition-routing.test.tsx +6 -1
- package/src/routes/_login/forgot-password/route.lazy.tsx +7 -6
- package/src/routes/_login/login/route.lazy.tsx +10 -9
- package/src/routes/_login/register/route.lazy.tsx +19 -18
- package/src/routes/_login/reset-password/route.lazy.tsx +10 -9
- package/src/routes/_nav/knowledge/compliance/$id.lazy.tsx +9 -12
- package/src/routes/_nav/knowledge/compliance/index.lazy.tsx +16 -13
- package/src/routes/_nav/knowledge/laws/$id.lazy.tsx +1 -1
- package/src/routes/_nav/notifications/route.tsx +6 -16
- package/src/routes/_nav/settings/-settings-tabs.tsx +9 -12
- package/src/routes/api/generate/__tests__/media.test.ts +378 -0
- package/src/routes/api/generate/media.ts +179 -0
- package/src/server/__tests__/agent-client-graph.test.ts +0 -1
- package/src/server/__tests__/agent-crud.test.ts +37 -4
- package/src/server/__tests__/bug-groups-two-level.test.ts +34 -1
- package/src/server/__tests__/chat-conversations.test.ts +37 -4
- package/src/server/__tests__/chat-list-messages.test.ts +40 -7
- package/src/server/__tests__/csv-export.test.ts +36 -3
- package/src/server/__tests__/desktop-bootstrap.test.ts +189 -0
- package/src/server/__tests__/remote-auth.test.ts +72 -0
- package/src/server/__tests__/skill-crud.test.ts +37 -4
- package/src/server/__tests__/sso-domain.test.ts +1 -1
- package/src/server/__tests__/sso-rate-limit-singleton.test.ts +123 -0
- package/src/server/__tests__/task-serverfn-crud.test.ts +35 -2
- package/src/server/__tests__/task-serverfn-queries.test.ts +35 -2
- package/src/server/__tests__/testcase-board.test.ts +38 -5
- package/src/server/__tests__/testcase-bulk.test.ts +38 -5
- package/src/server/__tests__/testcase-list-scope.test.ts +38 -5
- package/src/server/ai-gateway/__tests__/ai-output-guard.test.ts +126 -2
- package/src/server/ai-gateway/__tests__/chat-stream-builder-code-mode.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-memory.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-sandbox.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/chat-stream-builder-skills.test.ts +4 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-adapter.test.ts +30 -0
- package/src/server/ai-gateway/__tests__/pgvector-memory-conformance.test.ts +92 -0
- package/src/server/ai-gateway/__tests__/source-mode.test.ts +49 -8
- package/src/server/ai-gateway/ai-output-guard.ts +50 -2
- package/src/server/ai-gateway/chat-stream-builder.ts +3 -0
- package/src/server/ai-gateway/media/index.ts +36 -29
- package/src/server/ai-gateway/media/trim-input.ts +52 -0
- package/src/server/ai-gateway/memory/pgvector-adapter.ts +27 -13
- package/src/server/ai-gateway/sandbox/__tests__/harness-sandbox.test.ts +252 -41
- package/src/server/ai-gateway/sandbox/harness-sandbox.ts +6 -67
- package/src/server/ai-gateway/source-mode.ts +24 -8
- package/src/server/desktop-bootstrap.ts +74 -0
- package/src/server/lib/__tests__/rate-limit.test.ts +0 -1
- package/src/server/lib/rate-limit.ts +0 -2
- package/src/server/middlewares/__tests__/auth-context.test.ts +156 -0
- package/src/server/middlewares/auth-context.ts +62 -0
- package/src/server/remote-auth.ts +58 -31
- package/src/server/serverFns/__tests__/ai-hosted-models.test.ts +47 -15
- package/src/server/serverFns/__tests__/ai-source-mode.test.ts +45 -15
- package/src/server/serverFns/__tests__/billing.test.ts +37 -2
- package/src/server/serverFns/__tests__/collab-snapshot.test.ts +41 -14
- package/src/server/serverFns/__tests__/collab.test.ts +41 -14
- package/src/server/serverFns/__tests__/compliance.test.ts +33 -1
- package/src/server/serverFns/__tests__/email-verification.test.ts +35 -1
- package/src/server/serverFns/__tests__/knowledge-breadcrumb.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-delete.test.ts +42 -14
- package/src/server/serverFns/__tests__/knowledge-kb-projects.test.ts +46 -6
- package/src/server/serverFns/__tests__/knowledge-public-compliance.test.ts +36 -2
- package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +42 -10
- package/src/server/serverFns/__tests__/knowledge-search.test.ts +45 -9
- package/src/server/serverFns/__tests__/legal-code-canonical.test.ts +36 -2
- package/src/server/serverFns/__tests__/link-preview.test.ts +38 -4
- package/src/server/serverFns/__tests__/notifications-list.test.ts +36 -3
- package/src/server/serverFns/__tests__/ownership-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/project-delete-transaction.test.ts +39 -2
- package/src/server/serverFns/__tests__/project-kb.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-member-idor.test.ts +34 -1
- package/src/server/serverFns/__tests__/project-team-assign.test.ts +34 -1
- package/src/server/serverFns/__tests__/read-write-merge.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-comments.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-archive-detail.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-crud-labels-users.test.ts +37 -3
- package/src/server/serverFns/__tests__/story-crud.test.ts +36 -3
- package/src/server/serverFns/__tests__/story-links.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-reactions.test.ts +38 -4
- package/src/server/serverFns/__tests__/story-tree.test.ts +38 -4
- package/src/server/serverFns/__tests__/team-accept.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-invitation.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-join-request.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-members.test.ts +34 -1
- package/src/server/serverFns/__tests__/team-ownership-merge.test.ts +34 -1
- package/src/server/serverFns/__tests__/update-bug-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/update-document-guard.test.ts +38 -4
- package/src/server/serverFns/__tests__/update-page-guard.test.ts +40 -4
- package/src/server/serverFns/__tests__/update-seats.test.ts +33 -1
- package/src/server/serverFns/__tests__/update-story-guard.test.ts +36 -3
- package/src/server/serverFns/__tests__/update-testcase-guard.test.ts +37 -3
- package/src/server/serverFns/__tests__/workspace-kb-idor.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace-kb.test.ts +36 -2
- package/src/server/serverFns/__tests__/workspace.test.ts +34 -1
- package/src/server/serverFns/activity.ts +10 -14
- package/src/server/serverFns/agents/agent-crud.ts +16 -15
- package/src/server/serverFns/agents/bindings.ts +12 -13
- package/src/server/serverFns/agents/skill-crud.ts +16 -15
- package/src/server/serverFns/ai/code-mode-readiness.ts +3 -4
- package/src/server/serverFns/ai/search-capability.ts +3 -4
- package/src/server/serverFns/anyhub/app.ts +10 -14
- package/src/server/serverFns/anyhub/index.ts +7 -10
- package/src/server/serverFns/anyhub/install.ts +4 -4
- package/src/server/serverFns/anyhub/permissions.ts +10 -14
- package/src/server/serverFns/anyhub/proxy.ts +4 -6
- package/src/server/serverFns/auth/email-verification.ts +4 -5
- package/src/server/serverFns/auth/session.ts +9 -0
- package/src/server/serverFns/billing.ts +18 -28
- package/src/server/serverFns/blob-upload.ts +6 -9
- package/src/server/serverFns/bug/__tests__/bug-access-tokens.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-activities.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-comments.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-crud.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-fix.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-groups.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reactions.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-reorder.test.ts +39 -5
- package/src/server/serverFns/bug/__tests__/bug-repro.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-story.test.ts +34 -1
- package/src/server/serverFns/bug/__tests__/bug-webhooks.test.ts +34 -1
- package/src/server/serverFns/bug/bug-access-tokens.ts +16 -22
- package/src/server/serverFns/bug/bug-activities.ts +4 -6
- package/src/server/serverFns/bug/bug-collect-tokens.ts +13 -18
- package/src/server/serverFns/bug/bug-comments.ts +13 -18
- package/src/server/serverFns/bug/bug-crud/detail.ts +10 -14
- package/src/server/serverFns/bug/bug-crud.ts +28 -38
- package/src/server/serverFns/bug/bug-display-options.ts +8 -11
- package/src/server/serverFns/bug/bug-export.ts +5 -7
- package/src/server/serverFns/bug/bug-fix.ts +10 -14
- package/src/server/serverFns/bug/bug-groups.ts +10 -14
- package/src/server/serverFns/bug/bug-reactions.ts +10 -14
- package/src/server/serverFns/bug/bug-reorder.ts +4 -6
- package/src/server/serverFns/bug/bug-repro.ts +8 -11
- package/src/server/serverFns/bug/bug-story-bulk.ts +13 -18
- package/src/server/serverFns/bug/bug-story.ts +10 -14
- package/src/server/serverFns/bug/bug-views.ts +12 -18
- package/src/server/serverFns/bug/bug-webhooks.ts +13 -18
- package/src/server/serverFns/bug/test-bug-confirm.ts +7 -10
- package/src/server/serverFns/changelog/__tests__/fetch.test.ts +40 -1
- package/src/server/serverFns/changelog/fetch.ts +4 -6
- package/src/server/serverFns/chat/conversations.ts +17 -28
- package/src/server/serverFns/chat/list-messages.ts +4 -7
- package/src/server/serverFns/collab-presence.ts +11 -13
- package/src/server/serverFns/collab-snapshot.ts +6 -7
- package/src/server/serverFns/collab.ts +7 -7
- package/src/server/serverFns/compliance.ts +8 -8
- package/src/server/serverFns/design/__tests__/comments.test.ts +54 -10
- package/src/server/serverFns/design/__tests__/designs.test.ts +39 -16
- package/src/server/serverFns/design/__tests__/page-versions.test.ts +52 -14
- package/src/server/serverFns/design/__tests__/shares-password.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/shares.test.ts +44 -14
- package/src/server/serverFns/design/__tests__/versions.test.ts +44 -14
- package/src/server/serverFns/design/ai/__tests__/agent-conversations.test.ts +50 -21
- package/src/server/serverFns/design/ai/__tests__/providers.test.ts +42 -8
- package/src/server/serverFns/design/ai/agent-conversations.ts +19 -26
- package/src/server/serverFns/design/ai/fill-annotation.ts +5 -7
- package/src/server/serverFns/design/ai/generate-prototype.ts +5 -7
- package/src/server/serverFns/design/ai/hosted-models.ts +4 -5
- package/src/server/serverFns/design/ai/providers.ts +30 -39
- package/src/server/serverFns/design/ai/source-mode.ts +10 -12
- package/src/server/serverFns/design/comments.ts +14 -13
- package/src/server/serverFns/design/designs.ts +13 -19
- package/src/server/serverFns/design/page-versions.ts +17 -18
- package/src/server/serverFns/design/pages.ts +17 -18
- package/src/server/serverFns/design/reviews.ts +8 -9
- package/src/server/serverFns/design/shares-password.ts +13 -19
- package/src/server/serverFns/design/shares.ts +22 -31
- package/src/server/serverFns/design/versions.ts +8 -9
- package/src/server/serverFns/harness/__tests__/attach-prototype.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-kb.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story-ssrf.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/attach-to-story.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/cancel-run.test.ts +40 -15
- package/src/server/serverFns/harness/__tests__/extract-attachment.spec.ts +52 -2
- package/src/server/serverFns/harness/__tests__/gallery.spec.ts +76 -12
- package/src/server/serverFns/harness/__tests__/github-workspace.spec.ts +38 -6
- package/src/server/serverFns/harness/__tests__/media-prototype.spec.ts +50 -6
- package/src/server/serverFns/harness/__tests__/media.spec.ts +55 -11
- package/src/server/serverFns/harness/__tests__/sandbox-policy.test.ts +41 -16
- package/src/server/serverFns/harness/__tests__/workspace-files.test.ts +41 -16
- package/src/server/serverFns/harness/attach-prototype.ts +4 -4
- package/src/server/serverFns/harness/attach-to-kb.ts +4 -4
- package/src/server/serverFns/harness/attach-to-story.ts +4 -4
- package/src/server/serverFns/harness/cancel-run.ts +4 -6
- package/src/server/serverFns/harness/extract-attachment.ts +3 -4
- package/src/server/serverFns/harness/gallery.ts +19 -23
- package/src/server/serverFns/harness/github-workspace.ts +14 -21
- package/src/server/serverFns/harness/media.ts +18 -117
- package/src/server/serverFns/harness/sandbox-policy.ts +11 -10
- package/src/server/serverFns/harness/search-mention-candidates.ts +4 -6
- package/src/server/serverFns/harness/workspace-files.ts +19 -26
- package/src/server/serverFns/iteration/__tests__/iteration-auto-schedule.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-crud.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-stories.test.ts +35 -3
- package/src/server/serverFns/iteration/__tests__/iteration-transfer.test.ts +35 -3
- package/src/server/serverFns/iteration/iteration-auto-schedule.ts +7 -10
- package/src/server/serverFns/iteration/iteration-burndown.ts +4 -6
- package/src/server/serverFns/iteration/iteration-crud.ts +26 -35
- package/src/server/serverFns/iteration/iteration-stories.ts +10 -14
- package/src/server/serverFns/iteration/iteration-transfer.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/categories.ts +11 -15
- package/src/server/serverFns/knowledge/public/bookmarks/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/favorite.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/refresh-favicon.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/sync-github.ts +5 -7
- package/src/server/serverFns/knowledge/public/bookmarks/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/bookmarks/update.ts +6 -8
- package/src/server/serverFns/knowledge/public/compliance/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/compliance/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/compliance/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/dictionary/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/dictionary/update.ts +5 -7
- package/src/server/serverFns/knowledge/public/entity-tags.ts +4 -6
- package/src/server/serverFns/knowledge/public/favorite.ts +7 -10
- package/src/server/serverFns/knowledge/public/legal/create.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/delete.ts +5 -7
- package/src/server/serverFns/knowledge/public/legal/detail.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/list.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/share.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/unshare.ts +4 -6
- package/src/server/serverFns/knowledge/public/legal/update.ts +5 -7
- package/src/server/serverFns/knowledge-index.ts +12 -16
- package/src/server/serverFns/knowledge.ts +50 -67
- package/src/server/serverFns/notifications.ts +25 -34
- package/src/server/serverFns/plugins/harness-plugins.ts +16 -23
- package/src/server/serverFns/project/module-overview.ts +7 -10
- package/src/server/serverFns/project.ts +48 -68
- package/src/server/serverFns/references/create.ts +4 -6
- package/src/server/serverFns/references/resolve.ts +4 -6
- package/src/server/serverFns/references/search.ts +4 -6
- package/src/server/serverFns/share/design/[designId]/events.ts +4 -6
- package/src/server/serverFns/sso.ts +1 -1
- package/src/server/serverFns/story/__tests__/bulk.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-assoc.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-attachments.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-crud-date-key.test.ts +42 -17
- package/src/server/serverFns/story/__tests__/story-dependencies.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-gantt-deps.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-groups.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-read-path-authz.test.ts +40 -4
- package/src/server/serverFns/story/__tests__/story-search.test.ts +34 -1
- package/src/server/serverFns/story/__tests__/story-transfer.test.ts +34 -1
- package/src/server/serverFns/story/link-preview.ts +5 -7
- package/src/server/serverFns/story/story-assoc.ts +28 -38
- package/src/server/serverFns/story/story-attachments.ts +7 -10
- package/src/server/serverFns/story/story-children.ts +4 -6
- package/src/server/serverFns/story/story-comments.ts +13 -18
- package/src/server/serverFns/story/story-crud/activities.ts +4 -6
- package/src/server/serverFns/story/story-crud/archive.ts +7 -10
- package/src/server/serverFns/story/story-crud/bulk.ts +10 -14
- package/src/server/serverFns/story/story-crud/detail.ts +7 -10
- package/src/server/serverFns/story/story-crud/label-suggest.ts +4 -6
- package/src/server/serverFns/story/story-crud/labels.ts +6 -9
- package/src/server/serverFns/story/story-crud/user-brief.ts +4 -6
- package/src/server/serverFns/story/story-crud.ts +13 -18
- package/src/server/serverFns/story/story-dependencies.ts +16 -22
- package/src/server/serverFns/story/story-groups.ts +10 -14
- package/src/server/serverFns/story/story-links.ts +13 -18
- package/src/server/serverFns/story/story-reactions.ts +13 -18
- package/src/server/serverFns/story/story-search.ts +7 -10
- package/src/server/serverFns/story/story-transfer.ts +10 -14
- package/src/server/serverFns/story/story-tree.ts +7 -10
- package/src/server/serverFns/story-display-options.ts +7 -8
- package/src/server/serverFns/story-view.ts +12 -18
- package/src/server/serverFns/tasks/task-crud.ts +13 -17
- package/src/server/serverFns/tasks/task-queries.ts +12 -18
- package/src/server/serverFns/team.ts +46 -66
- package/src/server/serverFns/testcase/__tests__/automation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/evidence-upload.test.ts +37 -3
- package/src/server/serverFns/testcase/__tests__/generation.test.ts +38 -5
- package/src/server/serverFns/testcase/__tests__/plans.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/regression.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/run-cases.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/runs.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/stats.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-board.test.ts +39 -5
- package/src/server/serverFns/testcase/__tests__/testcase-crud.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-folders.test.ts +36 -3
- package/src/server/serverFns/testcase/__tests__/testcase-groups.test.ts +38 -5
- package/src/server/serverFns/testcase/automation.ts +13 -17
- package/src/server/serverFns/testcase/bulk.ts +10 -14
- package/src/server/serverFns/testcase/evidence-upload.ts +5 -7
- package/src/server/serverFns/testcase/generation.ts +10 -13
- package/src/server/serverFns/testcase/plans.ts +23 -31
- package/src/server/serverFns/testcase/regression.ts +7 -10
- package/src/server/serverFns/testcase/run-cases.ts +13 -18
- package/src/server/serverFns/testcase/runs.ts +13 -18
- package/src/server/serverFns/testcase/stats.ts +13 -18
- package/src/server/serverFns/testcase/testcase-activities.ts +4 -6
- package/src/server/serverFns/testcase/testcase-board.ts +4 -6
- package/src/server/serverFns/testcase/testcase-crud.ts +20 -27
- package/src/server/serverFns/testcase/testcase-display-options.ts +8 -11
- package/src/server/serverFns/testcase/testcase-export.ts +5 -7
- package/src/server/serverFns/testcase/testcase-filter-views.ts +12 -18
- package/src/server/serverFns/testcase/testcase-folders.ts +16 -22
- package/src/server/serverFns/testcase/testcase-groups.ts +4 -6
- package/src/server/serverFns/testcase/testcase-reorder.ts +4 -6
- package/src/server/serverFns/translation.ts +4 -6
- package/src/server/serverFns/user/avatar.ts +4 -6
- package/src/server/serverFns/user/settings.ts +4 -6
- package/src/server/serverFns/user/update-locale.ts +4 -6
- package/src/server/serverFns/workspace-kb.ts +26 -35
- package/src/server/serverFns/workspace.ts +43 -59
- package/src/server/serverFns/ws.ts +4 -6
- package/src/server/serverfn-public-allowlist.ts +117 -0
- package/src/server/story/types.ts +0 -1
- package/src/server/sync/__tests__/scopes.test.ts +64 -0
- package/src/server/sync/scopes.ts +7 -0
- package/src/server/testcase/__tests__/auto-bug.test.ts +44 -12
- package/src/__tests__/bench/baselines/format-timestamp.json +0 -44
- package/src/__typeprobe__.d.ts +0 -4
- package/src/components/forms/app-form-dialog.tsx +0 -126
- package/src/components/forms/hooks/use-form-submit.ts +0 -57
- package/src/components/forms/types.ts +0 -34
- package/src/components/harness/code/workspace-picker.tsx +0 -239
- package/src/components/tiptap/extensions/local-file/insert-local-file.ts +0 -35
- package/src/components/tiptap/extensions/local-file/local-file-node.ts +0 -71
- package/src/devframe-docks/ai-chat-iframe.tsx +0 -29
- package/src/devframe-docks/session-iframe.tsx +0 -90
- package/src/lib/__tests__/desktop-detect.test.ts +0 -19
- package/src/lib/collections/index.ts +0 -22
- package/src/lib/collections/knowledge/index.ts +0 -18
- package/src/lib/collections/knowledge/knowledge-bases-collection.ts +0 -34
- package/src/lib/collections/reference/bookmark-categories-collection.ts +0 -31
- package/src/lib/collections/reference/labels-collection.ts +0 -31
- package/src/lib/collections/reference/projects-collection.ts +0 -31
- package/src/lib/collections/reference/users-brief-collection.ts +0 -35
- package/src/lib/collections/reference/workspaces-collection.ts +0 -34
- package/src/lib/desktop-detect.ts +0 -29
- package/src/paraglide/messages/en.js +0 -28260
- 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
|
|
41
|
+
it('desktop 游客读隔离键 aiSourceMode:local-guest', async () => {
|
|
36
42
|
prismaMock.desktopSetting.findUnique.mockResolvedValue({ value: 'local-agent' })
|
|
37
|
-
expect(await resolveSourceMode(
|
|
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('
|
|
69
|
-
await setSourceMode(
|
|
107
|
+
it('游客写隔离键 aiSourceMode:local-guest(迁移读后写入落新键,旧键不删除)', async () => {
|
|
108
|
+
await setSourceMode(GUEST, 'local-agent')
|
|
70
109
|
expect(prismaMock.desktopSetting.upsert).toHaveBeenCalledWith({
|
|
71
|
-
where: { key:
|
|
72
|
-
create: { key:
|
|
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
|
|
23
|
-
*
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
:
|
|
173
|
-
const
|
|
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
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
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" = ${
|
|
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
|
-
|
|
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" = ${
|
|
127
|
+
FROM "memory_facts" WHERE "userId" = ${ownerKey} ORDER BY embedding <=> ${embeddingStr}::vector LIMIT ${candidateLimit}`
|
|
116
128
|
)
|
|
117
129
|
}
|
|
118
130
|
|
|
119
|
-
async function countFacts(
|
|
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" = ${
|
|
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(
|
|
150
|
+
if (Number((await countFacts(ownerKey)).c) === 0) {
|
|
138
151
|
return { systemPrompt: "" }
|
|
139
152
|
}
|
|
140
|
-
hits = (await searchFacts(
|
|
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}, ${
|
|
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" = ${
|
|
204
|
+
WHERE "userId" = ${ownerKey}
|
|
191
205
|
AND "id" NOT IN (
|
|
192
206
|
SELECT "id" FROM "memory_facts"
|
|
193
|
-
WHERE "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
|
|
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
|
|
247
|
+
const rows = await listFactsByUser(scopeOwnerKey(scope), listFactsLimit)
|
|
234
248
|
return rows.map((row) => ({
|
|
235
249
|
id: row.id,
|
|
236
250
|
text: row.content,
|