@lobehub/lobehub 2.0.0-next.254 → 2.0.0-next.256

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 (148) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/changelog/v1.json +18 -0
  3. package/locales/en-US/plugin.json +1 -0
  4. package/locales/zh-CN/plugin.json +1 -0
  5. package/package.json +1 -1
  6. package/packages/builtin-tool-notebook/src/client/Placeholder/CreateDocument.tsx +6 -6
  7. package/packages/builtin-tool-notebook/src/client/Render/CreateDocument/DocumentCard.tsx +23 -10
  8. package/packages/builtin-tool-notebook/src/client/Streaming/CreateDocument/index.tsx +1 -1
  9. package/packages/database/src/models/__tests__/agent.test.ts +91 -4
  10. package/packages/database/src/models/agent.ts +15 -7
  11. package/packages/editor-runtime/src/EditorRuntime.ts +1 -1
  12. package/packages/editor-runtime/src/__tests__/EditorRuntime.real.test.ts +65 -4
  13. package/packages/editor-runtime/src/__tests__/__snapshots__/EditorRuntime.real.test.ts.snap +108 -17
  14. package/packages/editor-runtime/src/__tests__/fixtures/remove-then-add.json +636 -0
  15. package/packages/editor-runtime/src/__tests__/fixtures/remove.json +1 -0
  16. package/packages/types/src/agent/agentConfig.ts +8 -8
  17. package/src/app/[variants]/(main)/chat/features/Portal/_layout/Mobile.tsx +2 -1
  18. package/src/app/[variants]/(main)/group/features/Portal/_layout/Mobile.tsx +2 -1
  19. package/src/app/[variants]/(main)/home/_layout/hooks/useCreateMenuItems.tsx +2 -2
  20. package/src/app/[variants]/(main)/page/{features/PageTitle → PageTitle}/index.tsx +0 -1
  21. package/src/app/[variants]/(main)/page/[id]/index.tsx +43 -1
  22. package/src/app/[variants]/(main)/page/_layout/Body/AllPagesDrawer/Content.tsx +15 -15
  23. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/Editing.tsx +3 -3
  24. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/index.tsx +7 -12
  25. package/src/app/[variants]/(main)/page/_layout/Body/List/Item/useDropdownMenu.tsx +5 -5
  26. package/src/app/[variants]/(main)/page/_layout/Body/List/index.tsx +7 -7
  27. package/src/app/[variants]/(main)/page/_layout/Body/index.tsx +15 -9
  28. package/src/app/[variants]/(main)/page/_layout/Body/useDropdownMenu.tsx +3 -3
  29. package/src/app/[variants]/(main)/page/_layout/DataSync.tsx +15 -0
  30. package/src/app/[variants]/(main)/page/_layout/Header/AddButton.tsx +2 -2
  31. package/src/app/[variants]/(main)/page/_layout/index.tsx +2 -0
  32. package/src/app/[variants]/(main)/page/index.tsx +3 -7
  33. package/src/components/Editor/AutoSaveHint.tsx +1 -1
  34. package/src/features/Conversation/Messages/AssistantGroup/Tool/Render/Intervention/ApprovalActions.tsx +2 -2
  35. package/src/features/Conversation/Messages/AssistantGroup/Tool/index.tsx +18 -15
  36. package/src/features/Conversation/Messages/AssistantGroup/components/Group.tsx +3 -3
  37. package/src/features/Conversation/Messages/Contexts/MessageAggregationContext.ts +15 -0
  38. package/src/features/Conversation/Messages/Supervisor/components/Group.tsx +3 -3
  39. package/src/features/Conversation/Messages/User/Actions/index.tsx +5 -1
  40. package/src/features/EditorCanvas/AutoSaveHint.tsx +37 -0
  41. package/src/features/{PageEditor → EditorCanvas}/DiffAllToolbar.tsx +57 -16
  42. package/src/features/EditorCanvas/DocumentIdMode.tsx +111 -0
  43. package/src/features/EditorCanvas/EditorCanvas.tsx +148 -0
  44. package/src/features/EditorCanvas/EditorDataMode.tsx +64 -0
  45. package/src/features/EditorCanvas/ErrorBoundary.tsx +66 -0
  46. package/src/features/EditorCanvas/InlineToolbar.tsx +245 -0
  47. package/src/features/EditorCanvas/InternalEditor.tsx +134 -0
  48. package/src/features/{PageEditor/EditorCanvas → EditorCanvas}/actions.ts +10 -8
  49. package/src/features/EditorCanvas/index.ts +9 -0
  50. package/src/features/PageEditor/EditorCanvas/index.tsx +14 -111
  51. package/src/features/PageEditor/EditorCanvas/useAskCopilotItem.tsx +95 -0
  52. package/src/features/PageEditor/EditorCanvas/useSlashItems.tsx +1 -1
  53. package/src/features/PageEditor/Header/Breadcrumb.tsx +2 -2
  54. package/src/features/PageEditor/Header/index.tsx +15 -18
  55. package/src/features/PageEditor/Header/useMenu.tsx +12 -9
  56. package/src/features/PageEditor/PageEditor.tsx +45 -21
  57. package/src/features/PageEditor/PageEditorProvider.tsx +13 -1
  58. package/src/features/PageEditor/PageTitle/index.tsx +2 -2
  59. package/src/features/PageEditor/StoreUpdater.tsx +35 -308
  60. package/src/features/PageEditor/{Body/Title.tsx → TitleSection.tsx} +16 -16
  61. package/src/features/PageEditor/store/action.ts +96 -188
  62. package/src/features/PageEditor/store/initialState.ts +16 -21
  63. package/src/features/PageEditor/store/selectors.ts +3 -4
  64. package/src/features/PageExplorer/PageExplorerPlaceholder.tsx +22 -14
  65. package/src/features/PageExplorer/index.tsx +34 -67
  66. package/src/features/Portal/Artifacts/index.ts +0 -2
  67. package/src/features/Portal/Document/AutoSaveHint.tsx +7 -6
  68. package/src/features/Portal/Document/Body.tsx +1 -3
  69. package/src/features/Portal/Document/EditorCanvas.tsx +7 -50
  70. package/src/features/Portal/Document/Header.tsx +13 -10
  71. package/src/features/Portal/Document/TodoList.tsx +6 -4
  72. package/src/features/Portal/Document/Wrapper.tsx +3 -11
  73. package/src/features/Portal/Document/index.ts +0 -2
  74. package/src/features/Portal/FilePreview/index.ts +0 -2
  75. package/src/features/Portal/GroupThread/index.ts +0 -3
  76. package/src/features/Portal/MessageDetail/index.ts +0 -2
  77. package/src/features/Portal/Notebook/index.ts +0 -2
  78. package/src/features/Portal/Plugins/index.ts +0 -2
  79. package/src/features/Portal/Thread/index.ts +0 -3
  80. package/src/features/Portal/components/Header.tsx +18 -6
  81. package/src/features/Portal/router.tsx +34 -97
  82. package/src/features/Portal/type.ts +0 -2
  83. package/src/libs/next/config/define-config.ts +4 -1
  84. package/src/locales/default/plugin.ts +1 -0
  85. package/src/store/chat/slices/portal/action.test.ts +218 -15
  86. package/src/store/chat/slices/portal/action.ts +194 -41
  87. package/src/store/chat/slices/portal/initialState.ts +40 -1
  88. package/src/store/chat/slices/portal/selectors/thread.ts +44 -3
  89. package/src/store/chat/slices/portal/selectors.test.ts +119 -17
  90. package/src/store/chat/slices/portal/selectors.ts +117 -36
  91. package/src/store/document/index.ts +17 -5
  92. package/src/store/document/slices/document/action.ts +209 -0
  93. package/src/store/document/slices/document/index.ts +6 -0
  94. package/src/store/document/slices/editor/action.test.ts +340 -0
  95. package/src/store/document/slices/editor/action.ts +133 -149
  96. package/src/store/document/slices/editor/index.ts +9 -2
  97. package/src/store/document/slices/editor/initialState.ts +66 -29
  98. package/src/store/document/slices/editor/reducer.test.ts +217 -0
  99. package/src/store/document/slices/editor/reducer.ts +67 -0
  100. package/src/store/document/slices/editor/selectors.test.ts +395 -0
  101. package/src/store/document/slices/editor/selectors.ts +107 -5
  102. package/src/store/document/store.ts +12 -13
  103. package/src/store/file/slices/document/action.ts +19 -188
  104. package/src/store/file/slices/document/initialState.ts +0 -30
  105. package/src/store/file/slices/document/selectors.ts +25 -59
  106. package/src/store/notebook/index.ts +5 -4
  107. package/src/store/page/index.ts +2 -0
  108. package/src/store/page/initialState.ts +92 -0
  109. package/src/store/page/selectors.ts +5 -0
  110. package/src/store/page/slices/crud/action.ts +477 -0
  111. package/src/store/page/slices/crud/index.ts +2 -0
  112. package/src/store/page/slices/crud/initialState.ts +7 -0
  113. package/src/store/page/slices/internal/action.ts +32 -0
  114. package/src/store/page/slices/internal/index.ts +2 -0
  115. package/src/store/page/slices/internal/reducer.ts +105 -0
  116. package/src/store/page/slices/list/action.ts +206 -0
  117. package/src/store/page/slices/list/index.ts +3 -0
  118. package/src/store/page/slices/list/initialState.ts +29 -0
  119. package/src/store/page/slices/list/selectors.ts +90 -0
  120. package/src/store/page/slices/selection/action.ts +67 -0
  121. package/src/store/page/slices/selection/index.ts +2 -0
  122. package/src/store/page/slices/selection/initialState.ts +11 -0
  123. package/src/store/page/store.ts +29 -0
  124. package/src/store/tool/slices/lobehubSkillStore/selectors.ts +10 -20
  125. package/src/utils/identifier.ts +8 -2
  126. package/src/features/Conversation/Messages/AssistantGroup/components/GroupContext.ts +0 -15
  127. package/src/features/Conversation/Messages/Supervisor/components/GroupContext.ts +0 -15
  128. package/src/features/PageEditor/Body/index.tsx +0 -68
  129. package/src/features/PageEditor/EditorCanvas/InlineToolbar.tsx +0 -316
  130. package/src/features/PageEditor/Header/AutoSaveHint.tsx +0 -27
  131. package/src/features/Portal/Artifacts/useEnable.ts +0 -4
  132. package/src/features/Portal/Document/DocumentEditorProvider.tsx +0 -34
  133. package/src/features/Portal/Document/StoreUpdater.tsx +0 -80
  134. package/src/features/Portal/Document/Title.tsx +0 -54
  135. package/src/features/Portal/Document/store/action.ts +0 -114
  136. package/src/features/Portal/Document/store/index.ts +0 -21
  137. package/src/features/Portal/Document/store/initialState.ts +0 -24
  138. package/src/features/Portal/Document/useEnable.ts +0 -8
  139. package/src/features/Portal/FilePreview/useEnable.ts +0 -6
  140. package/src/features/Portal/GroupThread/hook.ts +0 -9
  141. package/src/features/Portal/MessageDetail/useEnable.ts +0 -4
  142. package/src/features/Portal/Notebook/useEnable.ts +0 -6
  143. package/src/features/Portal/Plugins/useEnable.ts +0 -6
  144. package/src/features/Portal/Thread/hook.ts +0 -8
  145. package/src/store/document/slices/notebook/action.ts +0 -119
  146. package/src/store/document/slices/notebook/index.ts +0 -3
  147. package/src/store/document/slices/notebook/initialState.ts +0 -12
  148. package/src/store/document/slices/notebook/selectors.ts +0 -26
package/CHANGELOG.md CHANGED
@@ -2,6 +2,56 @@
2
2
 
3
3
  # Changelog
4
4
 
5
+ ## [Version 2.0.0-next.256](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.255...v2.0.0-next.256)
6
+
7
+ <sup>Released on **2026-01-10**</sup>
8
+
9
+ #### ♻ Code Refactoring
10
+
11
+ - **misc**: Refactor page and notebook document usage.
12
+
13
+ <br/>
14
+
15
+ <details>
16
+ <summary><kbd>Improvements and Fixes</kbd></summary>
17
+
18
+ #### Code refactoring
19
+
20
+ - **misc**: Refactor page and notebook document usage, closes [#11345](https://github.com/lobehub/lobe-chat/issues/11345) ([88721eb](https://github.com/lobehub/lobe-chat/commit/88721eb))
21
+
22
+ </details>
23
+
24
+ <div align="right">
25
+
26
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
27
+
28
+ </div>
29
+
30
+ ## [Version 2.0.0-next.255](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.254...v2.0.0-next.255)
31
+
32
+ <sup>Released on **2026-01-10**</sup>
33
+
34
+ #### 🐛 Bug Fixes
35
+
36
+ - **misc**: Fix auto add group member crash.
37
+
38
+ <br/>
39
+
40
+ <details>
41
+ <summary><kbd>Improvements and Fixes</kbd></summary>
42
+
43
+ #### What's fixed
44
+
45
+ - **misc**: Fix auto add group member crash, closes [#11387](https://github.com/lobehub/lobe-chat/issues/11387) ([fe4ff91](https://github.com/lobehub/lobe-chat/commit/fe4ff91))
46
+
47
+ </details>
48
+
49
+ <div align="right">
50
+
51
+ [![](https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square)](#readme-top)
52
+
53
+ </div>
54
+
5
55
  ## [Version 2.0.0-next.254](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.253...v2.0.0-next.254)
6
56
 
7
57
  <sup>Released on **2026-01-10**</sup>
package/changelog/v1.json CHANGED
@@ -1,4 +1,22 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Refactor page and notebook document usage."
6
+ ]
7
+ },
8
+ "date": "2026-01-10",
9
+ "version": "2.0.0-next.256"
10
+ },
11
+ {
12
+ "children": {
13
+ "fixes": [
14
+ "Fix auto add group member crash."
15
+ ]
16
+ },
17
+ "date": "2026-01-10",
18
+ "version": "2.0.0-next.255"
19
+ },
2
20
  {
3
21
  "children": {
4
22
  "improvements": [
@@ -92,6 +92,7 @@
92
92
  "builtins.lobe-local-system.inspector.noResults": "No results",
93
93
  "builtins.lobe-local-system.inspector.rename.result": "<old>{{oldName}}</old> → <new>{{newName}}</new>",
94
94
  "builtins.lobe-local-system.title": "Local System",
95
+ "builtins.lobe-notebook.actions.collapse": "Collapse",
95
96
  "builtins.lobe-notebook.actions.copy": "Copy",
96
97
  "builtins.lobe-notebook.actions.creating": "Creating document...",
97
98
  "builtins.lobe-notebook.actions.edit": "Edit",
@@ -92,6 +92,7 @@
92
92
  "builtins.lobe-local-system.inspector.noResults": "无结果",
93
93
  "builtins.lobe-local-system.inspector.rename.result": "<old>{{oldName}}</old> → <new>{{newName}}</new>",
94
94
  "builtins.lobe-local-system.title": "本地系统",
95
+ "builtins.lobe-notebook.actions.collapse": "收起",
95
96
  "builtins.lobe-notebook.actions.copy": "复制",
96
97
  "builtins.lobe-notebook.actions.creating": "文档创建中...",
97
98
  "builtins.lobe-notebook.actions.edit": "编辑",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/lobehub",
3
- "version": "2.0.0-next.254",
3
+ "version": "2.0.0-next.256",
4
4
  "description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -21,7 +21,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
21
21
  border: 1px solid ${cssVar.colorBorderSecondary};
22
22
  border-radius: 16px;
23
23
 
24
- background: ${cssVar.colorBgElevated};
24
+ background: ${cssVar.colorBgContainer};
25
25
  `,
26
26
  content: css`
27
27
  padding-block: 16px;
@@ -42,11 +42,11 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
42
42
  transform: translateX(-50%);
43
43
 
44
44
  display: inline-flex;
45
- gap: 6px;
45
+ gap: 8px;
46
46
  align-items: center;
47
47
 
48
- padding-block: 4px;
49
- padding-inline: 12px;
48
+ height: 32px;
49
+ padding-inline: 16px;
50
50
  border: 1px solid ${cssVar.colorBorderSecondary};
51
51
  border-radius: 16px;
52
52
 
@@ -90,8 +90,8 @@ export const CreateDocumentPlaceholder = memo<BuiltinPlaceholderProps<CreateDocu
90
90
  )}
91
91
  </ScrollShadow>
92
92
  <div className={styles.statusTag}>
93
- <NeuralNetworkLoading size={14} />
94
- <span style={{ fontSize: 12 }}>{t('builtins.lobe-notebook.actions.creating')}</span>
93
+ <NeuralNetworkLoading size={16} />
94
+ <span>{t('builtins.lobe-notebook.actions.creating')}</span>
95
95
  </div>
96
96
  </Flexbox>
97
97
  );
@@ -3,11 +3,12 @@
3
3
  import { ActionIcon, CopyButton, Flexbox, Markdown, ScrollShadow } from '@lobehub/ui';
4
4
  import { Button } from 'antd';
5
5
  import { createStaticStyles } from 'antd-style';
6
- import { Maximize2, NotebookText, PencilLine } from 'lucide-react';
6
+ import { Maximize2, Minimize2, NotebookText, PencilLine } from 'lucide-react';
7
7
  import { memo } from 'react';
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
10
  import { useChatStore } from '@/store/chat';
11
+ import { chatPortalSelectors } from '@/store/chat/slices/portal/selectors';
11
12
 
12
13
  import { NotebookDocument } from '../../../types';
13
14
 
@@ -21,7 +22,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
21
22
  border: 1px solid ${cssVar.colorBorderSecondary};
22
23
  border-radius: 16px;
23
24
 
24
- background: ${cssVar.colorBgElevated};
25
+ background: ${cssVar.colorBgContainer};
25
26
  `,
26
27
  content: css`
27
28
  padding-inline: 16px;
@@ -60,10 +61,20 @@ interface DocumentCardProps {
60
61
 
61
62
  const DocumentCard = memo<DocumentCardProps>(({ document }) => {
62
63
  const { t } = useTranslation('plugin');
63
- const openDocument = useChatStore((s) => s.openDocument);
64
+ const [portalDocumentId, openDocument, closeDocument] = useChatStore((s) => [
65
+ chatPortalSelectors.portalDocumentId(s),
66
+ s.openDocument,
67
+ s.closeDocument,
68
+ ]);
64
69
 
65
- const handleExpand = () => {
66
- openDocument(document.id);
70
+ const isExpanded = portalDocumentId === document.id;
71
+
72
+ const handleToggle = () => {
73
+ if (isExpanded) {
74
+ closeDocument();
75
+ } else {
76
+ openDocument(document.id);
77
+ }
67
78
  };
68
79
 
69
80
  return (
@@ -82,7 +93,7 @@ const DocumentCard = memo<DocumentCardProps>(({ document }) => {
82
93
  />
83
94
  <ActionIcon
84
95
  icon={PencilLine}
85
- onClick={handleExpand}
96
+ onClick={handleToggle}
86
97
  size={'small'}
87
98
  title={t('builtins.lobe-notebook.actions.edit')}
88
99
  />
@@ -95,16 +106,18 @@ const DocumentCard = memo<DocumentCardProps>(({ document }) => {
95
106
  </Markdown>
96
107
  </ScrollShadow>
97
108
 
98
- {/* Floating expand button */}
109
+ {/* Floating expand/collapse button */}
99
110
  <Button
100
111
  className={styles.expandButton}
101
112
  color={'default'}
102
- icon={<Maximize2 size={14} />}
103
- onClick={handleExpand}
113
+ icon={isExpanded ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
114
+ onClick={handleToggle}
104
115
  shape={'round'}
105
116
  variant={'outlined'}
106
117
  >
107
- {t('builtins.lobe-notebook.actions.expand')}
118
+ {isExpanded
119
+ ? t('builtins.lobe-notebook.actions.collapse')
120
+ : t('builtins.lobe-notebook.actions.expand')}
108
121
  </Button>
109
122
  </Flexbox>
110
123
  );
@@ -20,7 +20,7 @@ const styles = createStaticStyles(({ css, cssVar }) => ({
20
20
  border: 1px solid ${cssVar.colorBorderSecondary};
21
21
  border-radius: 16px;
22
22
 
23
- background: ${cssVar.colorBgElevated};
23
+ background: ${cssVar.colorBgContainer};
24
24
  `,
25
25
  header: css`
26
26
  padding-block: 10px;
@@ -155,6 +155,33 @@ describe('AgentModel', () => {
155
155
  expect(result).not.toBeNull();
156
156
  expect(result!.files).toHaveLength(0);
157
157
  });
158
+
159
+ it('should not return agent belonging to another user', async () => {
160
+ const agentId = 'test-agent-other-user';
161
+ // Create agent for user2
162
+ await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
163
+
164
+ // Try to access with user1's model
165
+ const result = await agentModel.getAgentConfigById(agentId);
166
+
167
+ expect(result).toBeNull();
168
+ });
169
+
170
+ it('should not return knowledge from another user agent', async () => {
171
+ const agentId = 'test-agent-cross-user-knowledge';
172
+ // Create agent for user2 with knowledge
173
+ await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
174
+ await serverDB
175
+ .insert(agentsKnowledgeBases)
176
+ .values({ agentId, knowledgeBaseId: 'kb2', userId: userId2 });
177
+ await serverDB.insert(agentsFiles).values({ agentId, fileId: '3', userId: userId2 });
178
+
179
+ // Try to access with user1's model
180
+ const result = await agentModel.getAgentConfigById(agentId);
181
+
182
+ // Should return null since user1 cannot access user2's agent
183
+ expect(result).toBeNull();
184
+ });
158
185
  });
159
186
 
160
187
  describe('getAgentConfig', () => {
@@ -197,15 +224,14 @@ describe('AgentModel', () => {
197
224
  expect(result).toBeNull();
198
225
  });
199
226
 
200
- it('should find agent by ID even if it belongs to another user', async () => {
227
+ it('should not find agent by ID if it belongs to another user', async () => {
201
228
  const agentId = 'test-agent-cross-user';
202
229
  await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
203
230
 
204
- // ID lookup should work across users (ID is globally unique)
231
+ // ID lookup should not work across users for security
205
232
  const result = await agentModel.getAgentConfig(agentId);
206
233
 
207
- expect(result).toBeDefined();
208
- expect(result?.id).toBe(agentId);
234
+ expect(result).toBeNull();
209
235
  });
210
236
 
211
237
  it('should prefer ID match over slug match', async () => {
@@ -257,6 +283,67 @@ describe('AgentModel', () => {
257
283
 
258
284
  expect(result).toBeUndefined();
259
285
  });
286
+
287
+ it('should not return agent from another user session', async () => {
288
+ const agentId = 'test-agent-other-user-session';
289
+ const sessionId = 'test-session-other-user';
290
+ // Create agent and session for user2
291
+ await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
292
+ await serverDB.insert(sessions).values({ id: sessionId, userId: userId2 });
293
+ await serverDB
294
+ .insert(agentsToSessions)
295
+ .values({ agentId, sessionId, userId: userId2 });
296
+
297
+ // Try to access with user1's model
298
+ const result = await agentModel.findBySessionId(sessionId);
299
+
300
+ expect(result).toBeUndefined();
301
+ });
302
+ });
303
+
304
+ describe('getAgentAssignedKnowledge', () => {
305
+ it('should return knowledge bases and files for the agent', async () => {
306
+ const agentId = 'test-agent-knowledge';
307
+ await serverDB.insert(agents).values({ id: agentId, userId });
308
+ await serverDB
309
+ .insert(agentsKnowledgeBases)
310
+ .values({ agentId, knowledgeBaseId: 'kb1', userId, enabled: true });
311
+ await serverDB.insert(agentsFiles).values({ agentId, fileId: '1', userId, enabled: true });
312
+
313
+ const result = await agentModel.getAgentAssignedKnowledge(agentId);
314
+
315
+ expect(result.knowledgeBases).toHaveLength(1);
316
+ expect(result.files).toHaveLength(1);
317
+ });
318
+
319
+ it('should not return knowledge from another user', async () => {
320
+ const agentId = 'test-agent-knowledge-other-user';
321
+ // Create agent with knowledge for user2
322
+ await serverDB.insert(agents).values({ id: agentId, userId: userId2 });
323
+ await serverDB
324
+ .insert(agentsKnowledgeBases)
325
+ .values({ agentId, knowledgeBaseId: 'kb2', userId: userId2, enabled: true });
326
+ await serverDB
327
+ .insert(agentsFiles)
328
+ .values({ agentId, fileId: '3', userId: userId2, enabled: true });
329
+
330
+ // Try to access with user1's model
331
+ const result = await agentModel.getAgentAssignedKnowledge(agentId);
332
+
333
+ // Should return empty arrays since user1 cannot access user2's knowledge
334
+ expect(result.knowledgeBases).toHaveLength(0);
335
+ expect(result.files).toHaveLength(0);
336
+ });
337
+
338
+ it('should handle empty knowledge bases and files', async () => {
339
+ const agentId = 'test-agent-no-knowledge';
340
+ await serverDB.insert(agents).values({ id: agentId, userId });
341
+
342
+ const result = await agentModel.getAgentAssignedKnowledge(agentId);
343
+
344
+ expect(result.knowledgeBases).toHaveLength(0);
345
+ expect(result.files).toHaveLength(0);
346
+ });
260
347
  });
261
348
 
262
349
  describe('createAgentKnowledgeBase', () => {
@@ -28,7 +28,9 @@ export class AgentModel {
28
28
  }
29
29
 
30
30
  getAgentConfigById = async (id: string) => {
31
- const agent = await this.db.query.agents.findFirst({ where: eq(agents.id, id) });
31
+ const agent = await this.db.query.agents.findFirst({
32
+ where: and(eq(agents.id, id), eq(agents.userId, this.userId)),
33
+ });
32
34
 
33
35
  if (!agent) return null;
34
36
 
@@ -76,9 +78,9 @@ export class AgentModel {
76
78
  */
77
79
  getAgentConfig = async (idOrSlug: string) => {
78
80
  const agent = await this.db.query.agents.findFirst({
79
- where: or(
80
- eq(agents.id, idOrSlug),
81
- and(eq(agents.slug, idOrSlug), eq(agents.userId, this.userId)),
81
+ where: and(
82
+ eq(agents.userId, this.userId),
83
+ or(eq(agents.id, idOrSlug), eq(agents.slug, idOrSlug)),
82
84
  ),
83
85
  });
84
86
 
@@ -118,17 +120,20 @@ export class AgentModel {
118
120
 
119
121
  getAgentAssignedKnowledge = async (id: string) => {
120
122
  // Run both queries in parallel for better performance
123
+ // Include userId check to ensure user can only access their own agent's knowledge
121
124
  const [knowledgeBaseResult, fileResult] = await Promise.all([
122
125
  this.db
123
126
  .select({ enabled: agentsKnowledgeBases.enabled, knowledgeBases })
124
127
  .from(agentsKnowledgeBases)
125
- .where(eq(agentsKnowledgeBases.agentId, id))
128
+ .where(
129
+ and(eq(agentsKnowledgeBases.agentId, id), eq(agentsKnowledgeBases.userId, this.userId)),
130
+ )
126
131
  .orderBy(desc(agentsKnowledgeBases.createdAt))
127
132
  .leftJoin(knowledgeBases, eq(knowledgeBases.id, agentsKnowledgeBases.knowledgeBaseId)),
128
133
  this.db
129
134
  .select({ enabled: agentsFiles.enabled, files })
130
135
  .from(agentsFiles)
131
- .where(eq(agentsFiles.agentId, id))
136
+ .where(and(eq(agentsFiles.agentId, id), eq(agentsFiles.userId, this.userId)))
132
137
  .orderBy(desc(agentsFiles.createdAt))
133
138
  .leftJoin(files, eq(files.id, agentsFiles.fileId)),
134
139
  ]);
@@ -150,7 +155,10 @@ export class AgentModel {
150
155
  */
151
156
  findBySessionId = async (sessionId: string) => {
152
157
  const item = await this.db.query.agentsToSessions.findFirst({
153
- where: eq(agentsToSessions.sessionId, sessionId),
158
+ where: and(
159
+ eq(agentsToSessions.sessionId, sessionId),
160
+ eq(agentsToSessions.userId, this.userId),
161
+ ),
154
162
  });
155
163
 
156
164
  if (!item) return;
@@ -109,7 +109,7 @@ export class EditorRuntime {
109
109
  }
110
110
 
111
111
  // Set markdown content directly - the editor will convert it internally
112
- editor.setDocument('markdown', markdown);
112
+ editor.setDocument('markdown', markdown, { keepId: true });
113
113
 
114
114
  // Get the resulting document to count nodes
115
115
  const jsonState = editor.getDocument('json') as any;
@@ -2,6 +2,7 @@ import {
2
2
  CommonPlugin,
3
3
  type IEditor,
4
4
  Kernel,
5
+ ListPlugin,
5
6
  LitexmlPlugin,
6
7
  MarkdownPlugin,
7
8
  moment,
@@ -10,6 +11,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
10
11
 
11
12
  import { EditorRuntime } from '../EditorRuntime';
12
13
  import editAllFixture from './fixtures/edit-all.json';
14
+ import removeThenAddFixture from './fixtures/remove-then-add.json';
13
15
  import removeFixture from './fixtures/remove.json';
14
16
 
15
17
  describe('EditorRuntime - Real Cases', () => {
@@ -20,7 +22,7 @@ describe('EditorRuntime - Real Cases', () => {
20
22
 
21
23
  beforeEach(() => {
22
24
  editor = new Kernel();
23
- editor.registerPlugins([CommonPlugin, MarkdownPlugin, LitexmlPlugin]);
25
+ editor.registerPlugins([CommonPlugin, MarkdownPlugin, ListPlugin, LitexmlPlugin]);
24
26
  editor.initNodeEditor();
25
27
 
26
28
  runtime = new EditorRuntime();
@@ -163,9 +165,6 @@ describe('EditorRuntime - Real Cases', () => {
163
165
 
164
166
  // Verify paragraphs were removed
165
167
  const xmlAfter = editor.getDocument('litexml') as unknown as string;
166
- const paragraphsAfter = [...xmlAfter.matchAll(/<p id="([^"]+)"/g)];
167
-
168
- expect(paragraphsAfter.length).toBe(initialCount - 7);
169
168
 
170
169
  // Verify the removed IDs are no longer present
171
170
  expect(xmlAfter).not.toContain('id="wps3"');
@@ -179,4 +178,66 @@ describe('EditorRuntime - Real Cases', () => {
179
178
  expect(xmlAfter).toMatchSnapshot();
180
179
  });
181
180
  });
181
+
182
+ describe('modifyNodes - remove then add', () => {
183
+ it('should remove 13 paragraphs then insert a list', async () => {
184
+ // Initialize editor with the JSON fixture
185
+ editor.setDocument('json', removeThenAddFixture);
186
+ await moment();
187
+
188
+ // First operation: remove 13 paragraphs
189
+ const removeResult = await runtime.modifyNodes({
190
+ operations: [
191
+ { action: 'remove', id: 'x4qr' },
192
+ { action: 'remove', id: 'xfvd' },
193
+ { action: 'remove', id: 'xqzz' },
194
+ { action: 'remove', id: 'zrby' },
195
+ { action: 'remove', id: '02gk' },
196
+ { action: 'remove', id: '0dl6' },
197
+ { action: 'remove', id: '0ops' },
198
+ { action: 'remove', id: '1rnx' },
199
+ { action: 'remove', id: '22sj' },
200
+ { action: 'remove', id: '2dx5' },
201
+ { action: 'remove', id: '3gva' },
202
+ { action: 'remove', id: '3rzw' },
203
+ { action: 'remove', id: '434i' },
204
+ ],
205
+ });
206
+
207
+ await moment();
208
+
209
+ // Verify all remove operations succeeded
210
+ expect(removeResult.successCount).toBe(13);
211
+ expect(removeResult.totalCount).toBe(13);
212
+ expect(removeResult.results.every((r) => r.success)).toBe(true);
213
+ expect(removeResult.results.every((r) => r.action === 'remove')).toBe(true);
214
+
215
+ // Verify the content was removed
216
+ const removed = editor.getDocument('litexml') as unknown as string;
217
+ expect(removed).toMatchSnapshot('remove 13 paragraphs');
218
+
219
+ // Second operation: insert a list after wtm5
220
+ const insertResult = await runtime.modifyNodes({
221
+ operations: [
222
+ {
223
+ action: 'insert',
224
+ afterId: 'wtm5',
225
+ litexml:
226
+ '<ul><li>西湖风景区:杭州的灵魂,世界文化遗产</li><li>灵隐寺:杭州最著名的佛教寺庙</li><li>西溪国家湿地公园:中国第一个国家湿地公园</li><li>宋城:以宋代文化为主题的大型主题公园</li></ul>',
227
+ },
228
+ ],
229
+ });
230
+ await moment();
231
+
232
+ // Verify insert operation succeeded
233
+ expect(insertResult.successCount).toBe(1);
234
+ expect(insertResult.totalCount).toBe(1);
235
+ expect(insertResult.results.every((r) => r.success)).toBe(true);
236
+ expect(insertResult.results.every((r) => r.action === 'insert')).toBe(true);
237
+
238
+ // Verify full output
239
+ const xmlAfter = editor.getDocument('litexml') as unknown as string;
240
+ expect(xmlAfter).toMatchSnapshot('insert new');
241
+ });
242
+ });
182
243
  });