@lobehub/lobehub 2.0.0-next.353 → 2.0.0-next.355

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 (144) hide show
  1. package/.agents/skills/add-provider-doc/SKILL.md +90 -0
  2. package/.agents/skills/add-setting-env/SKILL.md +106 -0
  3. package/.agents/skills/debug/SKILL.md +66 -0
  4. package/.agents/skills/desktop/SKILL.md +78 -0
  5. package/.agents/skills/desktop/references/feature-implementation.md +99 -0
  6. package/.agents/skills/desktop/references/local-tools.md +133 -0
  7. package/.agents/skills/desktop/references/menu-config.md +103 -0
  8. package/.agents/skills/desktop/references/window-management.md +143 -0
  9. package/.agents/skills/drizzle/SKILL.md +129 -0
  10. package/.agents/skills/drizzle/references/db-migrations.md +50 -0
  11. package/.agents/skills/hotkey/SKILL.md +90 -0
  12. package/{.cursor/rules/i18n.mdc → .agents/skills/i18n/SKILL.md} +14 -23
  13. package/.agents/skills/linear/SKILL.md +51 -0
  14. package/.agents/skills/microcopy/SKILL.md +83 -0
  15. package/.agents/skills/modal/SKILL.md +102 -0
  16. package/{.cursor/rules/project-structure.mdc → .agents/skills/project-overview/SKILL.md} +65 -37
  17. package/.agents/skills/react/SKILL.md +73 -0
  18. package/.agents/skills/react/references/layout-kit.md +100 -0
  19. package/.agents/skills/recent-data/SKILL.md +108 -0
  20. package/.agents/skills/testing/SKILL.md +89 -0
  21. package/.agents/skills/testing/references/agent-runtime-e2e.md +126 -0
  22. package/.agents/skills/testing/references/db-model-test.md +124 -0
  23. package/.agents/skills/testing/references/desktop-controller-test.md +124 -0
  24. package/.agents/skills/testing/references/electron-ipc-test.md +63 -0
  25. package/.agents/skills/testing/references/zustand-store-action-test.md +150 -0
  26. package/.agents/skills/typescript/SKILL.md +52 -0
  27. package/.agents/skills/zustand/SKILL.md +78 -0
  28. package/.agents/skills/zustand/references/action-patterns.md +125 -0
  29. package/.agents/skills/zustand/references/slice-organization.md +125 -0
  30. package/AGENTS.md +42 -55
  31. package/CHANGELOG.md +60 -0
  32. package/CLAUDE.md +57 -46
  33. package/GEMINI.md +47 -39
  34. package/changelog/v1.json +18 -0
  35. package/locales/en-US/plugin.json +3 -0
  36. package/locales/zh-CN/plugin.json +3 -0
  37. package/package.json +1 -1
  38. package/packages/builtin-tool-memory/src/client/Render/SearchUserMemory/index.tsx +3 -11
  39. package/packages/context-engine/src/engine/messages/MessagesEngine.ts +0 -13
  40. package/packages/context-engine/src/engine/messages/__tests__/MessagesEngine.test.ts +0 -25
  41. package/packages/database/src/models/__tests__/topics/topic.create.test.ts +3 -3
  42. package/src/app/[variants]/(main)/agent/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
  43. package/src/app/[variants]/(main)/agent/features/Conversation/ConversationArea.tsx +4 -0
  44. package/src/app/[variants]/(main)/group/_layout/Sidebar/Topic/List/Item/index.tsx +1 -0
  45. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/AgentItem/index.tsx +1 -1
  46. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/InboxItem.tsx +19 -29
  47. package/src/app/[variants]/(main)/home/_layout/Body/Agent/List/List.tsx +1 -1
  48. package/src/app/[variants]/(main)/home/_layout/Body/Agent/ModalProvider.tsx +1 -1
  49. package/src/features/FileViewer/Renderer/PDF/index.tsx +2 -3
  50. package/src/features/ShareModal/SharePdf/PdfPreview.tsx +1 -2
  51. package/src/libs/pdfjs/index.tsx +25 -0
  52. package/src/locales/default/plugin.ts +3 -0
  53. package/src/server/modules/Mecha/ContextEngineering/__tests__/serverMessagesEngine.test.ts +0 -25
  54. package/src/services/chat/chat.test.ts +19 -19
  55. package/src/services/chat/index.ts +8 -3
  56. package/src/services/chat/mecha/agentConfigResolver.test.ts +72 -55
  57. package/src/services/chat/mecha/agentConfigResolver.ts +28 -4
  58. package/src/services/chat/mecha/contextEngineering.test.ts +21 -14
  59. package/src/services/chat/mecha/contextEngineering.ts +12 -0
  60. package/src/services/chat/types.ts +7 -1
  61. package/src/store/chat/agents/createAgentExecutors.ts +15 -4
  62. package/src/store/chat/slices/aiChat/actions/conversationLifecycle.ts +1 -0
  63. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +6 -2
  64. package/src/store/test-coverage.md +5 -5
  65. package/.cursor/rules/add-provider-doc.mdc +0 -183
  66. package/.cursor/rules/add-setting-env.mdc +0 -175
  67. package/.cursor/rules/cursor-rules.mdc +0 -28
  68. package/.cursor/rules/db-migrations.mdc +0 -46
  69. package/.cursor/rules/debug-usage.mdc +0 -86
  70. package/.cursor/rules/desktop-controller-tests.mdc +0 -189
  71. package/.cursor/rules/desktop-feature-implementation.mdc +0 -155
  72. package/.cursor/rules/desktop-local-tools-implement.mdc +0 -81
  73. package/.cursor/rules/desktop-menu-configuration.mdc +0 -209
  74. package/.cursor/rules/desktop-window-management.mdc +0 -301
  75. package/.cursor/rules/drizzle-schema-style-guide.mdc +0 -218
  76. package/.cursor/rules/hotkey.mdc +0 -162
  77. package/.cursor/rules/linear.mdc +0 -53
  78. package/.cursor/rules/microcopy-cn.mdc +0 -158
  79. package/.cursor/rules/microcopy-en.mdc +0 -148
  80. package/.cursor/rules/modal-imperative.mdc +0 -162
  81. package/.cursor/rules/packages/react-layout-kit.mdc +0 -122
  82. package/.cursor/rules/project-introduce.mdc +0 -36
  83. package/.cursor/rules/react.mdc +0 -169
  84. package/.cursor/rules/recent-data-usage.mdc +0 -139
  85. package/.cursor/rules/rules-index.mdc +0 -44
  86. package/.cursor/rules/testing-guide/agent-runtime-e2e.mdc +0 -285
  87. package/.cursor/rules/testing-guide/db-model-test.mdc +0 -455
  88. package/.cursor/rules/testing-guide/electron-ipc-test.mdc +0 -80
  89. package/.cursor/rules/testing-guide/testing-guide.mdc +0 -534
  90. package/.cursor/rules/testing-guide/zustand-store-action-test.mdc +0 -574
  91. package/.cursor/rules/typescript.mdc +0 -55
  92. package/.cursor/rules/zustand-action-patterns.mdc +0 -328
  93. package/.cursor/rules/zustand-slice-organization.mdc +0 -308
  94. package/src/libs/pdfjs/pdf.worker.ts +0 -1
  95. package/src/libs/pdfjs/worker.ts +0 -12
  96. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/AGENTS.md +0 -0
  97. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/SKILL.md +0 -0
  98. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-event-handler-refs.md +0 -0
  99. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/advanced-use-latest.md +0 -0
  100. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-api-routes.md +0 -0
  101. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-defer-await.md +0 -0
  102. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-dependencies.md +0 -0
  103. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-parallel.md +0 -0
  104. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/async-suspense-boundaries.md +0 -0
  105. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-barrel-imports.md +0 -0
  106. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-conditional.md +0 -0
  107. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-defer-third-party.md +0 -0
  108. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-dynamic-imports.md +0 -0
  109. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/bundle-preload.md +0 -0
  110. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-event-listeners.md +0 -0
  111. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-localstorage-schema.md +0 -0
  112. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-passive-event-listeners.md +0 -0
  113. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/client-swr-dedup.md +0 -0
  114. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-batch-dom-css.md +0 -0
  115. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-function-results.md +0 -0
  116. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-property-access.md +0 -0
  117. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-cache-storage.md +0 -0
  118. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-combine-iterations.md +0 -0
  119. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-early-exit.md +0 -0
  120. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-hoist-regexp.md +0 -0
  121. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-index-maps.md +0 -0
  122. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-length-check-first.md +0 -0
  123. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-min-max-loop.md +0 -0
  124. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-set-map-lookups.md +0 -0
  125. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/js-tosorted-immutable.md +0 -0
  126. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-activity.md +0 -0
  127. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-animate-svg-wrapper.md +0 -0
  128. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-conditional-render.md +0 -0
  129. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-content-visibility.md +0 -0
  130. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hoist-jsx.md +0 -0
  131. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-hydration-no-flicker.md +0 -0
  132. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rendering-svg-precision.md +0 -0
  133. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-defer-reads.md +0 -0
  134. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-dependencies.md +0 -0
  135. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-derived-state.md +0 -0
  136. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-functional-setstate.md +0 -0
  137. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-lazy-state-init.md +0 -0
  138. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-memo.md +0 -0
  139. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/rerender-transitions.md +0 -0
  140. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-after-nonblocking.md +0 -0
  141. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-lru.md +0 -0
  142. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-cache-react.md +0 -0
  143. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-parallel-fetching.md +0 -0
  144. /package/.agents/{vercel-react-best-practices → skills/vercel-react-best-practices}/rules/server-serialization.md +0 -0
@@ -1,169 +0,0 @@
1
- ---
2
- description:
3
- globs: *.tsx
4
- alwaysApply: false
5
- ---
6
-
7
- # React Component Writing Guide
8
-
9
- - Use antd-style for complex styles; for simple cases, use the `style` attribute for inline styles
10
- - Use `Flexbox` and `Center` components from `@lobehub/ui` for flex and centered layouts
11
- - Component selection priority: src/components > installed component packages > lobe-ui > antd
12
- - Use selectors to access zustand store data instead of accessing the store directly
13
-
14
- ## Lobe UI Components
15
-
16
- - If unsure how to use `@lobehub/ui` components or what props they accept, search for existing usage in this project instead of guessing. Most components extend antd components with additional props
17
- - For specific usage, search online. For example, for ActionIcon visit <https://ui.lobehub.com/components/action-icon>
18
- - Read `node_modules/@lobehub/ui/es/index.mjs` to see all available components and their props
19
-
20
- - General
21
- - ActionIcon
22
- - ActionIconGroup
23
- - Block
24
- - Button
25
- - Icon
26
- - Data Display
27
- - Accordion
28
- - Avatar
29
- - Collapse
30
- - Empty
31
- - FileTypeIcon
32
- - FluentEmoji
33
- - GroupAvatar
34
- - GuideCard
35
- - Highlighter
36
- - Hotkey
37
- - Image
38
- - List
39
- - Markdown
40
- - MaterialFileTypeIcon
41
- - Mermaid
42
- - Segmented
43
- - Skeleton
44
- - Snippet
45
- - SortableList
46
- - Tag
47
- - Tooltip
48
- - Video
49
- - Data Entry
50
- - AutoComplete
51
- - CodeEditor
52
- - ColorSwatches
53
- - CopyButton
54
- - DatePicker
55
- - DownloadButton
56
- - EditableText
57
- - EmojiPicker
58
- - Form
59
- - FormModal
60
- - HotkeyInput
61
- - ImageSelect
62
- - Input
63
- - SearchBar
64
- - Select
65
- - SliderWithInput
66
- - ThemeSwitch
67
- - Feedback
68
- - Alert
69
- - Drawer
70
- - Modal
71
- - Layout
72
- - Center
73
- - DraggablePanel
74
- - Flexbox
75
- - Footer
76
- - Grid
77
- - Header
78
- - Layout
79
- - MaskShadow
80
- - ScrollShadow
81
- - Navigation
82
- - Burger
83
- - DraggableSideNav
84
- - Dropdown
85
- - Menu
86
- - SideNav
87
- - Tabs
88
- - Toc
89
- - Theme
90
- - ConfigProvider
91
- - FontLoader
92
- - ThemeProvider
93
- - Typography
94
- - Text
95
-
96
- ## Routing Architecture
97
-
98
- This project uses a **hybrid routing architecture**: Next.js App Router for static pages + React Router DOM for the main SPA.
99
-
100
- ### Route Types
101
-
102
- ```plaintext
103
- +------------------+--------------------------------+--------------------------------+
104
- | Route Type | Use Case | Implementation |
105
- +------------------+--------------------------------+--------------------------------+
106
- | Next.js App | Auth pages (login, signup, | page.tsx file convention |
107
- | Router | oauth, reset-password, etc.) | src/app/[variants]/(auth)/ |
108
- +------------------+--------------------------------+--------------------------------+
109
- | React Router | Main SPA features | BrowserRouter + Routes |
110
- | DOM | (chat, community, settings) | desktopRouter.config.tsx |
111
- | | | mobileRouter.config.tsx |
112
- +------------------+--------------------------------+--------------------------------+
113
- ```
114
-
115
- ### Key Files
116
-
117
- - Entry point: `src/app/[variants]/page.tsx` - Routes to Desktop or Mobile based on device
118
- - Desktop router: `src/app/[variants]/router/desktopRouter.config.tsx`
119
- - Mobile router: `src/app/[variants]/(mobile)/router/mobileRouter.config.tsx`
120
- - Router utilities: `src/utils/router.tsx`
121
-
122
- ### Router Utilities
123
-
124
- ```tsx
125
- import { ErrorBoundary, RouteConfig, dynamicElement, redirectElement } from '@/utils/router';
126
-
127
- // Lazy load a page component
128
- element: dynamicElement(() => import('./chat'), 'Desktop > Chat');
129
-
130
- // Create a redirect
131
- element: redirectElement('/settings/profile');
132
-
133
- // Error boundary for route
134
- errorElement: <ErrorBoundary resetPath="/chat" />;
135
- ```
136
-
137
- ### Adding New Routes
138
-
139
- 1. Add route config to `desktopRouter.config.tsx` or `mobileRouter.config.tsx`
140
- 2. Create page component in the corresponding directory under `(main)/`
141
- 3. Use `dynamicElement()` for lazy loading
142
-
143
- ### Navigation
144
-
145
- **Important**: For SPA pages (React Router DOM routes), use `Link` from `react-router-dom`, NOT from `next/link`.
146
-
147
- ```tsx
148
- // ❌ Wrong - next/link in SPA pages
149
- import Link from 'next/link';
150
- <Link href="/">Home</Link>
151
-
152
- // ✅ Correct - react-router-dom Link in SPA pages
153
- import { Link } from 'react-router-dom';
154
- <Link to="/">Home</Link>
155
- ```
156
-
157
- ```tsx
158
- // In components - use react-router-dom hooks
159
- import { useNavigate, useParams } from 'react-router-dom';
160
-
161
- const navigate = useNavigate();
162
- navigate('/chat');
163
-
164
- // From stores - use global navigate
165
- import { useGlobalStore } from '@/store/global';
166
-
167
- const navigate = useGlobalStore.getState().navigate;
168
- navigate?.('/settings');
169
- ```
@@ -1,139 +0,0 @@
1
- # Recent Data 使用指南
2
-
3
- ## 概述
4
-
5
- Recent 数据(recentTopics, recentResources, recentPages)存储在 session store 中,可以在应用的任何地方访问。
6
-
7
- ## 数据初始化
8
-
9
- 在应用顶层(如 `RecentHydration.tsx`)中初始化所有 recent 数据:
10
-
11
- ```tsx
12
- import { useInitRecentPage } from '@/hooks/useInitRecentPage';
13
- import { useInitRecentResource } from '@/hooks/useInitRecentResource';
14
- import { useInitRecentTopic } from '@/hooks/useInitRecentTopic';
15
-
16
- const App = () => {
17
- // 初始化所有 recent 数据
18
- useInitRecentTopic();
19
- useInitRecentResource();
20
- useInitRecentPage();
21
-
22
- return <YourComponents />;
23
- };
24
- ```
25
-
26
- ## 使用方式
27
-
28
- ### 方式一:直接从 Store 读取(推荐用于多处使用)
29
-
30
- 在任何组件中直接访问 store 中的数据:
31
-
32
- ```tsx
33
- import { useSessionStore } from '@/store/session';
34
- import { recentSelectors } from '@/store/session/selectors';
35
-
36
- const Component = () => {
37
- // 读取数据
38
- const recentTopics = useSessionStore(recentSelectors.recentTopics);
39
- const isInit = useSessionStore(recentSelectors.isRecentTopicsInit);
40
-
41
- if (!isInit) return <div>Loading...</div>;
42
-
43
- return (
44
- <div>
45
- {recentTopics.map((topic) => (
46
- <div key={topic.id}>{topic.title}</div>
47
- ))}
48
- </div>
49
- );
50
- };
51
- ```
52
-
53
- ### 方式二:使用 Hook 返回的数据(用于单一组件)
54
-
55
- ```tsx
56
- import { useInitRecentTopic } from '@/hooks/useInitRecentTopic';
57
-
58
- const Component = () => {
59
- const { data: recentTopics, isLoading } = useInitRecentTopic();
60
-
61
- if (isLoading) return <div>Loading...</div>;
62
-
63
- return <div>{/* 使用 recentTopics */}</div>;
64
- };
65
- ```
66
-
67
- ## 可用的 Selectors
68
-
69
- ### Recent Topics (最近话题)
70
-
71
- ```tsx
72
- import { recentSelectors } from '@/store/session/selectors';
73
-
74
- // 数据
75
- const recentTopics = useSessionStore(recentSelectors.recentTopics);
76
- // 类型: RecentTopic[]
77
-
78
- // 初始化状态
79
- const isInit = useSessionStore(recentSelectors.isRecentTopicsInit);
80
- // 类型: boolean
81
- ```
82
-
83
- **RecentTopic 类型:**
84
-
85
- ```typescript
86
- interface RecentTopic {
87
- agent: {
88
- avatar: string | null;
89
- backgroundColor: string | null;
90
- id: string;
91
- title: string | null;
92
- } | null;
93
- id: string;
94
- title: string | null;
95
- updatedAt: Date;
96
- }
97
- ```
98
-
99
- ### Recent Resources (最近文件)
100
-
101
- ```tsx
102
- import { recentSelectors } from '@/store/session/selectors';
103
-
104
- // 数据
105
- const recentResources = useSessionStore(recentSelectors.recentResources);
106
- // 类型: FileListItem[]
107
-
108
- // 初始化状态
109
- const isInit = useSessionStore(recentSelectors.isRecentResourcesInit);
110
- // 类型: boolean
111
- ```
112
-
113
- ### Recent Pages (最近页面)
114
-
115
- ```tsx
116
- import { recentSelectors } from '@/store/session/selectors';
117
-
118
- // 数据
119
- const recentPages = useSessionStore(recentSelectors.recentPages);
120
- // 类型: any[]
121
-
122
- // 初始化状态
123
- const isInit = useSessionStore(recentSelectors.isRecentPagesInit);
124
- // 类型: boolean
125
- ```
126
-
127
- ## 特性
128
-
129
- 1. **自动登录检测**:只有在用户登录时才会加载数据
130
- 2. **数据缓存**:数据存储在 store 中,多处使用无需重复加载
131
- 3. **自动刷新**:使用 SWR,在用户重新聚焦时自动刷新(5分钟间隔)
132
- 4. **类型安全**:完整的 TypeScript 类型定义
133
-
134
- ## 最佳实践
135
-
136
- 1. **初始化位置**:在应用顶层统一初始化所有 recent 数据
137
- 2. **数据访问**:使用 selectors 从 store 读取数据
138
- 3. **多处使用**:同一数据在多个组件中使用时,推荐使用方式一(直接从 store 读取)
139
- 4. **性能优化**:使用 selector 确保只有相关数据变化时才重新渲染
@@ -1,44 +0,0 @@
1
- ---
2
- description:
3
- globs:
4
- alwaysApply: true
5
- ---
6
-
7
- # Available project rules index
8
-
9
- All following rules are saved under `.cursor/rules/` directory:
10
-
11
- ## Backend
12
-
13
- - `drizzle-schema-style-guide.mdc` – Style guide for defining Drizzle ORM schemas
14
-
15
- ## Frontend
16
-
17
- - `react.mdc` – React component style guide and conventions
18
- - `i18n.mdc` – Internationalization guide using react-i18next
19
- - `typescript.mdc` – TypeScript code style guide
20
- - `packages/react-layout-kit.mdc` – Usage guide for react-layout-kit
21
- - `modal-imperative.mdc` – Modal imperative API usage guide (createRawModal/createModal)
22
-
23
- ## State Management
24
-
25
- - `zustand-action-patterns.mdc` – Recommended patterns for organizing Zustand actions
26
- - `zustand-slice-organization.mdc` – Best practices for structuring Zustand slices
27
-
28
- ## Desktop (Electron)
29
-
30
- - `desktop-feature-implementation.mdc` – Implementing new Electron desktop features
31
- - `desktop-controller-tests.mdc` – Desktop controller unit testing guide
32
- - `desktop-local-tools-implement.mdc` – Workflow to add new desktop local tools
33
- - `desktop-menu-configuration.mdc` – Desktop menu configuration guide
34
- - `desktop-window-management.mdc` – Desktop window management guide
35
-
36
- ## Debugging
37
-
38
- - `debug-usage.mdc` – Using the debug package and namespace conventions
39
-
40
- ## Testing
41
-
42
- - `testing-guide/testing-guide.mdc` – Comprehensive testing guide for Vitest
43
- - `testing-guide/electron-ipc-test.mdc` – Electron IPC interface testing strategy
44
- - `testing-guide/db-model-test.mdc` – Database Model testing guide
@@ -1,285 +0,0 @@
1
- # Agent Runtime E2E 测试指南
2
-
3
- 本文档描述 Agent Runtime 端到端测试的核心原则和实施方法。
4
-
5
- ## 核心原则
6
-
7
- ### 1. 最小化 Mock 原则
8
-
9
- E2E 测试的目标是尽可能接近真实运行环境。因此,我们只 Mock **三个外部依赖**:
10
-
11
- | 依赖 | Mock 方式 | 说明 |
12
- | --- | --- | --- |
13
- | **Database** | PGLite | 使用 `@lobechat/database/test-utils` 提供的内存数据库 |
14
- | **Redis** | InMemoryAgentStateManager | Mock `AgentStateManager` 使用内存实现 |
15
- | **Redis** | InMemoryStreamEventManager | Mock `StreamEventManager` 使用内存实现 |
16
-
17
- **不 Mock 的部分:**
18
-
19
- - `model-bank` - 使用真实的模型配置数据
20
- - `Mecha` (AgentToolsEngine, ContextEngineering) - 使用真实逻辑
21
- - `AgentRuntimeService` - 使用真实逻辑
22
- - `AgentRuntimeCoordinator` - 使用真实逻辑
23
-
24
- ### 2. 使用 vi.spyOn 而非 vi.mock
25
-
26
- 不同测试场景需要不同的 LLM 响应。使用 `vi.spyOn` 可以:
27
-
28
- - 在每个测试中灵活控制返回值
29
- - 便于测试不同场景(纯文本、tool calls、错误等)
30
- - 避免全局 mock 导致的测试隔离问题
31
-
32
- ### 3. 默认模型使用 gpt-5
33
-
34
- - `model-bank` 中肯定有该模型的数据
35
- - 避免短期内因模型更新需要修改测试
36
-
37
- ## 技术实现
38
-
39
- ### 数据库设置
40
-
41
- ```typescript
42
- import { LobeChatDatabase } from '@lobechat/database';
43
- import { getTestDB } from '@lobechat/database/test-utils';
44
-
45
- let testDB: LobeChatDatabase;
46
-
47
- beforeEach(async () => {
48
- testDB = await getTestDB();
49
- });
50
- ```
51
-
52
- ### OpenAI Response Mock Helper
53
-
54
- 创建一个 helper 函数来生成 OpenAI 格式的流式响应:
55
-
56
- ```typescript
57
- /**
58
- * 创建 OpenAI 格式的流式响应
59
- */
60
- export const createOpenAIStreamResponse = (options: {
61
- content?: string;
62
- toolCalls?: Array<{
63
- id: string;
64
- name: string;
65
- arguments: string;
66
- }>;
67
- finishReason?: 'stop' | 'tool_calls';
68
- }) => {
69
- const { content, toolCalls, finishReason = 'stop' } = options;
70
-
71
- return new Response(
72
- new ReadableStream({
73
- start(controller) {
74
- const encoder = new TextEncoder();
75
-
76
- // 发送内容 chunk
77
- if (content) {
78
- const chunk = {
79
- id: 'chatcmpl-mock',
80
- object: 'chat.completion.chunk',
81
- model: 'gpt-5',
82
- choices: [
83
- {
84
- index: 0,
85
- delta: { content },
86
- finish_reason: null,
87
- },
88
- ],
89
- };
90
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
91
- }
92
-
93
- // 发送 tool_calls chunk
94
- if (toolCalls) {
95
- for (const tool of toolCalls) {
96
- const chunk = {
97
- id: 'chatcmpl-mock',
98
- object: 'chat.completion.chunk',
99
- model: 'gpt-5',
100
- choices: [
101
- {
102
- index: 0,
103
- delta: {
104
- tool_calls: [
105
- {
106
- index: 0,
107
- id: tool.id,
108
- type: 'function',
109
- function: {
110
- name: tool.name,
111
- arguments: tool.arguments,
112
- },
113
- },
114
- ],
115
- },
116
- finish_reason: null,
117
- },
118
- ],
119
- };
120
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
121
- }
122
- }
123
-
124
- // 发送完成 chunk
125
- const finishChunk = {
126
- id: 'chatcmpl-mock',
127
- object: 'chat.completion.chunk',
128
- model: 'gpt-5',
129
- choices: [
130
- {
131
- index: 0,
132
- delta: {},
133
- finish_reason: finishReason,
134
- },
135
- ],
136
- };
137
- controller.enqueue(encoder.encode(`data: ${JSON.stringify(finishChunk)}\n\n`));
138
- controller.enqueue(encoder.encode('data: [DONE]\n\n'));
139
- controller.close();
140
- },
141
- }),
142
- { headers: { 'content-type': 'text/event-stream' } },
143
- );
144
- };
145
- ```
146
-
147
- ### 内存状态管理
148
-
149
- 使用依赖注入替代 Redis:
150
-
151
- ```typescript
152
- import {
153
- InMemoryAgentStateManager,
154
- InMemoryStreamEventManager,
155
- } from '@/server/modules/AgentRuntime';
156
- import { AgentRuntimeService } from '@/server/services/agentRuntime';
157
-
158
- const stateManager = new InMemoryAgentStateManager();
159
- const streamEventManager = new InMemoryStreamEventManager();
160
-
161
- const service = new AgentRuntimeService(serverDB, userId, {
162
- coordinatorOptions: {
163
- stateManager,
164
- streamEventManager,
165
- },
166
- queueService: null, // 禁用 QStash 队列,使用 executeSync
167
- streamEventManager,
168
- });
169
- ```
170
-
171
- ### Mock OpenAI API
172
-
173
- 在测试中使用 `vi.spyOn` mock fetch:
174
-
175
- ```typescript
176
- import { vi } from 'vitest';
177
-
178
- // 在测试文件顶部或 beforeEach 中
179
- const fetchSpy = vi.spyOn(globalThis, 'fetch');
180
-
181
- // 在具体测试中设置返回值
182
- it('should handle text response', async () => {
183
- fetchSpy.mockResolvedValueOnce(createOpenAIStreamResponse({ content: '杭州今天天气晴朗' }));
184
-
185
- // ... 执行测试
186
- });
187
-
188
- it('should handle tool calls', async () => {
189
- fetchSpy.mockResolvedValueOnce(
190
- createOpenAIStreamResponse({
191
- toolCalls: [
192
- {
193
- id: 'call_123',
194
- name: 'lobe-web-browsing____search____builtin',
195
- arguments: JSON.stringify({ query: '杭州天气' }),
196
- },
197
- ],
198
- finishReason: 'tool_calls',
199
- }),
200
- );
201
-
202
- // ... 执行测试
203
- });
204
- ```
205
-
206
- ## 测试场景
207
-
208
- ### 1. 基本对话测试
209
-
210
- ```typescript
211
- describe('Basic Chat', () => {
212
- it('should complete a simple conversation', async () => {
213
- fetchSpy.mockResolvedValueOnce(
214
- createOpenAIStreamResponse({ content: 'Hello! How can I help you?' }),
215
- );
216
-
217
- const result = await service.createOperation({
218
- agentConfig: { model: 'gpt-5', provider: 'openai' },
219
- initialMessages: [{ role: 'user', content: 'Hi' }],
220
- // ...
221
- });
222
-
223
- const finalState = await service.executeSync(result.operationId);
224
- expect(finalState.status).toBe('done');
225
- });
226
- });
227
- ```
228
-
229
- ### 2. Tool 调用测试
230
-
231
- ```typescript
232
- describe('Tool Calls', () => {
233
- it('should execute web-browsing tool', async () => {
234
- // 第一次调用:LLM 返回 tool_calls
235
- fetchSpy.mockResolvedValueOnce(
236
- createOpenAIStreamResponse({
237
- toolCalls: [
238
- {
239
- id: 'call_123',
240
- name: 'lobe-web-browsing____search____builtin',
241
- arguments: JSON.stringify({ query: '杭州天气' }),
242
- },
243
- ],
244
- finishReason: 'tool_calls',
245
- }),
246
- );
247
-
248
- // 第二次调用:处理 tool 结果后的响应
249
- fetchSpy.mockResolvedValueOnce(
250
- createOpenAIStreamResponse({ content: '根据搜索结果,杭州今天...' }),
251
- );
252
-
253
- // ... 执行测试
254
- });
255
- });
256
- ```
257
-
258
- ### 3. 错误处理测试
259
-
260
- ```typescript
261
- describe('Error Handling', () => {
262
- it('should handle API errors gracefully', async () => {
263
- fetchSpy.mockRejectedValueOnce(new Error('API rate limit exceeded'));
264
-
265
- // ... 执行测试并验证错误处理
266
- });
267
- });
268
- ```
269
-
270
- ## 文件组织
271
-
272
- ```
273
- src/server/routers/lambda/__tests__/integration/
274
- ├── setup.ts # 测试设置工具
275
- ├── aiAgent.integration.test.ts # 现有集成测试
276
- ├── aiAgent.e2e.test.ts # E2E 测试
277
- └── helpers/
278
- └── openaiMock.ts # OpenAI mock helper
279
- ```
280
-
281
- ## 注意事项
282
-
283
- 1. **测试隔离**:每个测试后清理 `InMemoryAgentStateManager` 和 `InMemoryStreamEventManager`
284
- 2. **超时设置**:E2E 测试可能需要更长的超时时间
285
- 3. **调试**:使用 `DEBUG=lobe-server:*` 环境变量查看详细日志