@lobehub/lobehub 2.0.0-next.137 → 2.0.0-next.138
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/CHANGELOG.md +25 -0
- package/CLAUDE.md +38 -0
- package/changelog/v1.json +5 -0
- package/package.json +1 -1
- package/packages/conversation-flow/src/transformation/BranchResolver.ts +24 -14
- package/packages/conversation-flow/src/transformation/ContextTreeBuilder.ts +6 -1
- package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +15 -0
- package/packages/conversation-flow/src/transformation/__tests__/BranchResolver.test.ts +66 -3
- package/packages/conversation-flow/src/transformation/__tests__/ContextTreeBuilder.test.ts +64 -0
- package/packages/conversation-flow/src/transformation/__tests__/FlatListBuilder.test.ts +47 -0
- package/packages/database/src/models/__tests__/agent.test.ts +102 -3
- package/packages/database/src/models/__tests__/document.test.ts +163 -0
- package/packages/database/src/models/__tests__/embedding.test.ts +294 -0
- package/packages/database/src/models/__tests__/oauthHandoff.test.ts +261 -0
- package/packages/database/src/models/__tests__/thread.test.ts +327 -0
- package/packages/database/src/models/__tests__/user.test.ts +372 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.138](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.137...v2.0.0-next.138)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2025-11-30**</sup>
|
|
8
|
+
|
|
9
|
+
#### 🐛 Bug Fixes
|
|
10
|
+
|
|
11
|
+
- **conversation-flow**: Support optimistic update for activeBranchIndex.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### What's fixed
|
|
19
|
+
|
|
20
|
+
- **conversation-flow**: Support optimistic update for activeBranchIndex, closes [#10517](https://github.com/lobehub/lobe-chat/issues/10517) ([9b5b234](https://github.com/lobehub/lobe-chat/commit/9b5b234))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.137](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.136...v2.0.0-next.137)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2025-11-30**</sup>
|
package/CLAUDE.md
CHANGED
|
@@ -55,6 +55,44 @@ see @.cursor/rules/typescript.mdc
|
|
|
55
55
|
- **Dev**: Translate `locales/zh-CN/namespace.json` and `locales/en-US/namespace.json` locales file only for dev preview
|
|
56
56
|
- DON'T run `pnpm i18n`, let CI auto handle it
|
|
57
57
|
|
|
58
|
+
## Linear Issue Management
|
|
59
|
+
|
|
60
|
+
When working with Linear issues:
|
|
61
|
+
|
|
62
|
+
1. **Retrieve issue details** before starting work using `mcp__linear-server__get_issue`
|
|
63
|
+
2. **Check for sub-issues**: If the issue has sub-issues, retrieve and review ALL sub-issues using `mcp__linear-server__list_issues` with `parentId` filter before starting work
|
|
64
|
+
3. **Update issue status** when completing tasks using `mcp__linear-server__update_issue`
|
|
65
|
+
4. **MUST add completion comment** using `mcp__linear-server__create_comment`
|
|
66
|
+
|
|
67
|
+
### Completion Comment (REQUIRED)
|
|
68
|
+
|
|
69
|
+
**Every time you complete an issue, you MUST add a comment summarizing the work done.** This is critical for:
|
|
70
|
+
|
|
71
|
+
- Team visibility and knowledge sharing
|
|
72
|
+
- Code review context
|
|
73
|
+
- Future reference and debugging
|
|
74
|
+
|
|
75
|
+
### IMPORTANT: Per-Issue Completion Rule
|
|
76
|
+
|
|
77
|
+
**When working on multiple issues (e.g., parent issue with sub-issues), you MUST update status and add comment for EACH issue IMMEDIATELY after completing it.** Do NOT wait until all issues are done to update them in batch.
|
|
78
|
+
|
|
79
|
+
**Workflow for EACH individual issue:**
|
|
80
|
+
|
|
81
|
+
1. Complete the implementation for this specific issue
|
|
82
|
+
2. Run type check: `bun run type-check`
|
|
83
|
+
3. Run related tests if applicable
|
|
84
|
+
4. **IMMEDIATELY** update issue status to "Done": `mcp__linear-server__update_issue`
|
|
85
|
+
5. **IMMEDIATELY** add completion comment: `mcp__linear-server__create_comment`
|
|
86
|
+
6. Only then move on to the next issue
|
|
87
|
+
|
|
88
|
+
**❌ Wrong approach:**
|
|
89
|
+
|
|
90
|
+
- Complete Issue A → Complete Issue B → Complete Issue C → Update all statuses → Add all comments
|
|
91
|
+
|
|
92
|
+
**✅ Correct approach:**
|
|
93
|
+
|
|
94
|
+
- Complete Issue A → Update A status → Add A comment → Complete Issue B → Update B status → Add B comment → ...
|
|
95
|
+
|
|
58
96
|
## Rules Index
|
|
59
97
|
|
|
60
98
|
Some useful project rules are listed in @.cursor/rules/rules-index.mdc
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.138",
|
|
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",
|
|
@@ -11,16 +11,21 @@ import type { IdNode, Message } from '../types';
|
|
|
11
11
|
export class BranchResolver {
|
|
12
12
|
/**
|
|
13
13
|
* Get active branch ID from IdNode structure (used in contextTree building)
|
|
14
|
+
* Returns undefined for optimistic updates when the branch hasn't been created yet
|
|
14
15
|
*/
|
|
15
|
-
getActiveBranchId(message: Message, idNode: IdNode): string {
|
|
16
|
+
getActiveBranchId(message: Message, idNode: IdNode): string | undefined {
|
|
16
17
|
// Priority 1: Try to get from metadata.activeBranchIndex (index-based)
|
|
17
18
|
const activeBranchIndex = (message.metadata as any)?.activeBranchIndex;
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
activeBranchIndex
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
if (typeof activeBranchIndex === 'number' && activeBranchIndex >= 0) {
|
|
20
|
+
// If index is within bounds, return the branch at that index
|
|
21
|
+
if (activeBranchIndex < idNode.children.length) {
|
|
22
|
+
return idNode.children[activeBranchIndex].id;
|
|
23
|
+
}
|
|
24
|
+
// Optimistic update: index === children.length means branch is being created
|
|
25
|
+
if (activeBranchIndex === idNode.children.length) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
// Invalid index (> children.length), ignore and continue to other strategies
|
|
24
29
|
}
|
|
25
30
|
|
|
26
31
|
// Priority 2: Infer from which branch has children
|
|
@@ -36,20 +41,25 @@ export class BranchResolver {
|
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* Get active branch ID from flat list (used in flatList building)
|
|
44
|
+
* Returns undefined for optimistic updates when the branch hasn't been created yet
|
|
39
45
|
*/
|
|
40
46
|
getActiveBranchIdFromMetadata(
|
|
41
47
|
message: Message,
|
|
42
48
|
childIds: string[],
|
|
43
49
|
childrenMap: Map<string | null, string[]>,
|
|
44
|
-
): string {
|
|
50
|
+
): string | undefined {
|
|
45
51
|
// Priority 1: Try to get from metadata.activeBranchIndex (index-based)
|
|
46
52
|
const activeBranchIndex = (message.metadata as any)?.activeBranchIndex;
|
|
47
|
-
if (
|
|
48
|
-
|
|
49
|
-
activeBranchIndex
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
if (typeof activeBranchIndex === 'number' && activeBranchIndex >= 0) {
|
|
54
|
+
// If index is within bounds, return the branch at that index
|
|
55
|
+
if (activeBranchIndex < childIds.length) {
|
|
56
|
+
return childIds[activeBranchIndex];
|
|
57
|
+
}
|
|
58
|
+
// Optimistic update: index === childIds.length means branch is being created
|
|
59
|
+
if (activeBranchIndex === childIds.length) {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
// Invalid index (> childIds.length), ignore and continue to other strategies
|
|
53
63
|
}
|
|
54
64
|
|
|
55
65
|
// Priority 2: Infer from which child has descendants
|
|
@@ -185,7 +185,12 @@ export class ContextTreeBuilder {
|
|
|
185
185
|
*/
|
|
186
186
|
private createBranchNode(message: Message, idNode: IdNode): BranchNode {
|
|
187
187
|
const activeBranchId = this.branchResolver.getActiveBranchId(message, idNode);
|
|
188
|
-
|
|
188
|
+
|
|
189
|
+
// For optimistic update (activeBranchId is undefined), use children.length as the index
|
|
190
|
+
// This indicates the branch is being created but doesn't exist yet
|
|
191
|
+
const activeBranchIndex = activeBranchId
|
|
192
|
+
? idNode.children.findIndex((child) => child.id === activeBranchId)
|
|
193
|
+
: idNode.children.length;
|
|
189
194
|
|
|
190
195
|
// Each branch is a tree starting from that child
|
|
191
196
|
const branches = idNode.children.map((child) => {
|
|
@@ -165,6 +165,15 @@ export class FlatListBuilder {
|
|
|
165
165
|
childMessages,
|
|
166
166
|
this.childrenMap,
|
|
167
167
|
);
|
|
168
|
+
|
|
169
|
+
// Optimistic update: activeBranchId is undefined when branch is being created
|
|
170
|
+
// In this case, just add user message without branch info and continue
|
|
171
|
+
if (!activeBranchId) {
|
|
172
|
+
flatList.push(message);
|
|
173
|
+
processedIds.add(message.id);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
168
177
|
const activeBranchIndex = childMessages.indexOf(activeBranchId);
|
|
169
178
|
const userWithBranches = this.createUserMessageWithBranches(
|
|
170
179
|
message,
|
|
@@ -248,6 +257,12 @@ export class FlatListBuilder {
|
|
|
248
257
|
flatList.push(message);
|
|
249
258
|
processedIds.add(message.id);
|
|
250
259
|
|
|
260
|
+
// Optimistic update: activeBranchId is undefined when branch is being created
|
|
261
|
+
// In this case, just add assistant message and continue (no active branch yet)
|
|
262
|
+
if (!activeBranchId) {
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
251
266
|
// Continue with active branch and process its message
|
|
252
267
|
const activeBranchMsg = this.messageMap.get(activeBranchId);
|
|
253
268
|
if (activeBranchMsg) {
|
|
@@ -71,13 +71,13 @@ describe('BranchResolver', () => {
|
|
|
71
71
|
expect(resolver.getActiveBranchId(message, idNode)).toBe('msg-2');
|
|
72
72
|
});
|
|
73
73
|
|
|
74
|
-
it('should
|
|
74
|
+
it('should return undefined for optimistic update (activeBranchIndex === children.length)', () => {
|
|
75
75
|
const message: Message = {
|
|
76
76
|
content: 'test',
|
|
77
77
|
createdAt: 0,
|
|
78
78
|
id: 'msg-1',
|
|
79
79
|
meta: {},
|
|
80
|
-
metadata: { activeBranchIndex:
|
|
80
|
+
metadata: { activeBranchIndex: 2 }, // index = children.length (optimistic update)
|
|
81
81
|
role: 'user',
|
|
82
82
|
updatedAt: 0,
|
|
83
83
|
};
|
|
@@ -90,7 +90,31 @@ describe('BranchResolver', () => {
|
|
|
90
90
|
id: 'msg-1',
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
//
|
|
93
|
+
// When activeBranchIndex === children.length, it's an optimistic update
|
|
94
|
+
// The branch hasn't been created yet, so return undefined
|
|
95
|
+
expect(resolver.getActiveBranchId(message, idNode)).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should ignore activeBranchIndex when it exceeds optimistic update range', () => {
|
|
99
|
+
const message: Message = {
|
|
100
|
+
content: 'test',
|
|
101
|
+
createdAt: 0,
|
|
102
|
+
id: 'msg-1',
|
|
103
|
+
meta: {},
|
|
104
|
+
metadata: { activeBranchIndex: 5 }, // > children.length (invalid)
|
|
105
|
+
role: 'user',
|
|
106
|
+
updatedAt: 0,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const idNode: IdNode = {
|
|
110
|
+
children: [
|
|
111
|
+
{ children: [], id: 'msg-2' },
|
|
112
|
+
{ children: [], id: 'msg-3' },
|
|
113
|
+
],
|
|
114
|
+
id: 'msg-1',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// activeBranchIndex > children.length should be ignored, fallback to default
|
|
94
118
|
expect(resolver.getActiveBranchId(message, idNode)).toBe('msg-2');
|
|
95
119
|
});
|
|
96
120
|
});
|
|
@@ -147,5 +171,44 @@ describe('BranchResolver', () => {
|
|
|
147
171
|
|
|
148
172
|
expect(resolver.getActiveBranchIdFromMetadata(message, childIds, childrenMap)).toBe('msg-2');
|
|
149
173
|
});
|
|
174
|
+
|
|
175
|
+
it('should return undefined for optimistic update (activeBranchIndex === childIds.length)', () => {
|
|
176
|
+
const message: Message = {
|
|
177
|
+
content: 'test',
|
|
178
|
+
createdAt: 0,
|
|
179
|
+
id: 'msg-1',
|
|
180
|
+
meta: {},
|
|
181
|
+
metadata: { activeBranchIndex: 2 }, // index = childIds.length (optimistic update)
|
|
182
|
+
role: 'user',
|
|
183
|
+
updatedAt: 0,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const childIds = ['msg-2', 'msg-3'];
|
|
187
|
+
const childrenMap = new Map<string | null, string[]>();
|
|
188
|
+
|
|
189
|
+
// When activeBranchIndex === childIds.length, it's an optimistic update
|
|
190
|
+
// The branch hasn't been created yet, so return undefined
|
|
191
|
+
expect(
|
|
192
|
+
resolver.getActiveBranchIdFromMetadata(message, childIds, childrenMap),
|
|
193
|
+
).toBeUndefined();
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should ignore activeBranchIndex when it exceeds optimistic update range', () => {
|
|
197
|
+
const message: Message = {
|
|
198
|
+
content: 'test',
|
|
199
|
+
createdAt: 0,
|
|
200
|
+
id: 'msg-1',
|
|
201
|
+
meta: {},
|
|
202
|
+
metadata: { activeBranchIndex: 5 }, // > childIds.length (invalid)
|
|
203
|
+
role: 'user',
|
|
204
|
+
updatedAt: 0,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const childIds = ['msg-2', 'msg-3'];
|
|
208
|
+
const childrenMap = new Map<string | null, string[]>();
|
|
209
|
+
|
|
210
|
+
// activeBranchIndex > childIds.length should be ignored, fallback to default
|
|
211
|
+
expect(resolver.getActiveBranchIdFromMetadata(message, childIds, childrenMap)).toBe('msg-2');
|
|
212
|
+
});
|
|
150
213
|
});
|
|
151
214
|
});
|
|
@@ -311,6 +311,70 @@ describe('ContextTreeBuilder', () => {
|
|
|
311
311
|
expect(result).toHaveLength(0);
|
|
312
312
|
});
|
|
313
313
|
|
|
314
|
+
it('should set activeBranchIndex to children.length for optimistic update', () => {
|
|
315
|
+
const messageMap = new Map<string, Message>([
|
|
316
|
+
[
|
|
317
|
+
'msg-1',
|
|
318
|
+
{
|
|
319
|
+
content: 'Hello',
|
|
320
|
+
createdAt: 0,
|
|
321
|
+
id: 'msg-1',
|
|
322
|
+
meta: {},
|
|
323
|
+
// activeBranchIndex = 2 means optimistic update (pointing to not-yet-created branch)
|
|
324
|
+
metadata: { activeBranchIndex: 2 },
|
|
325
|
+
role: 'user',
|
|
326
|
+
updatedAt: 0,
|
|
327
|
+
},
|
|
328
|
+
],
|
|
329
|
+
[
|
|
330
|
+
'msg-2',
|
|
331
|
+
{
|
|
332
|
+
content: 'Response 1',
|
|
333
|
+
createdAt: 0,
|
|
334
|
+
id: 'msg-2',
|
|
335
|
+
meta: {},
|
|
336
|
+
role: 'assistant',
|
|
337
|
+
updatedAt: 0,
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
[
|
|
341
|
+
'msg-3',
|
|
342
|
+
{
|
|
343
|
+
content: 'Response 2',
|
|
344
|
+
createdAt: 0,
|
|
345
|
+
id: 'msg-3',
|
|
346
|
+
meta: {},
|
|
347
|
+
role: 'assistant',
|
|
348
|
+
updatedAt: 0,
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
]);
|
|
352
|
+
|
|
353
|
+
const builder = createBuilder(messageMap);
|
|
354
|
+
const idNodes: IdNode[] = [
|
|
355
|
+
{
|
|
356
|
+
children: [
|
|
357
|
+
{ children: [], id: 'msg-2' },
|
|
358
|
+
{ children: [], id: 'msg-3' },
|
|
359
|
+
],
|
|
360
|
+
id: 'msg-1',
|
|
361
|
+
},
|
|
362
|
+
];
|
|
363
|
+
|
|
364
|
+
const result = builder.transformAll(idNodes);
|
|
365
|
+
|
|
366
|
+
expect(result).toHaveLength(2);
|
|
367
|
+
expect(result[0]).toEqual({ id: 'msg-1', type: 'message' });
|
|
368
|
+
// When activeBranchIndex === children.length (optimistic update),
|
|
369
|
+
// BranchResolver returns undefined, and ContextTreeBuilder uses children.length as index
|
|
370
|
+
expect(result[1]).toMatchObject({
|
|
371
|
+
activeBranchIndex: 2, // children.length = 2
|
|
372
|
+
branches: [[{ id: 'msg-2', type: 'message' }], [{ id: 'msg-3', type: 'message' }]],
|
|
373
|
+
parentMessageId: 'msg-1',
|
|
374
|
+
type: 'branch',
|
|
375
|
+
});
|
|
376
|
+
});
|
|
377
|
+
|
|
314
378
|
it('should continue with active column children in compare mode', () => {
|
|
315
379
|
const messageMap = new Map<string, Message>([
|
|
316
380
|
[
|
|
@@ -507,5 +507,52 @@ describe('FlatListBuilder', () => {
|
|
|
507
507
|
expect(result[1].role).toBe('assistantGroup');
|
|
508
508
|
expect(result[2].id).toBe('msg-3');
|
|
509
509
|
});
|
|
510
|
+
|
|
511
|
+
it('should handle optimistic update for user message with branches', () => {
|
|
512
|
+
// Scenario: User has sent a new message, activeBranchIndex points to a branch
|
|
513
|
+
// that is being created but doesn't exist yet (optimistic update)
|
|
514
|
+
const messages: Message[] = [
|
|
515
|
+
{
|
|
516
|
+
content: 'User',
|
|
517
|
+
createdAt: 0,
|
|
518
|
+
id: 'msg-1',
|
|
519
|
+
meta: {},
|
|
520
|
+
// activeBranchIndex = 2 means pointing to a not-yet-created branch (optimistic update)
|
|
521
|
+
// when there are only 2 existing children (msg-2, msg-3)
|
|
522
|
+
metadata: { activeBranchIndex: 2 },
|
|
523
|
+
role: 'user',
|
|
524
|
+
updatedAt: 0,
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
content: 'Branch 1',
|
|
528
|
+
createdAt: 0,
|
|
529
|
+
id: 'msg-2',
|
|
530
|
+
meta: {},
|
|
531
|
+
parentId: 'msg-1',
|
|
532
|
+
role: 'assistant',
|
|
533
|
+
updatedAt: 0,
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
content: 'Branch 2',
|
|
537
|
+
createdAt: 0,
|
|
538
|
+
id: 'msg-3',
|
|
539
|
+
meta: {},
|
|
540
|
+
parentId: 'msg-1',
|
|
541
|
+
role: 'assistant',
|
|
542
|
+
updatedAt: 0,
|
|
543
|
+
},
|
|
544
|
+
];
|
|
545
|
+
|
|
546
|
+
const builder = createBuilder(messages);
|
|
547
|
+
const result = builder.flatten(messages);
|
|
548
|
+
|
|
549
|
+
// When activeBranchIndex === children.length (optimistic update),
|
|
550
|
+
// BranchResolver returns undefined, and FlatListBuilder just adds the user message
|
|
551
|
+
// without branch info and doesn't continue to any branch
|
|
552
|
+
expect(result).toHaveLength(1);
|
|
553
|
+
expect(result[0].id).toBe('msg-1');
|
|
554
|
+
// User message should not have branch info since we're in optimistic update mode
|
|
555
|
+
expect((result[0] as any).siblingCount).toBeUndefined();
|
|
556
|
+
});
|
|
510
557
|
});
|
|
511
558
|
});
|
|
@@ -20,9 +20,12 @@ import { getTestDB } from './_util';
|
|
|
20
20
|
const serverDB: LobeChatDatabase = await getTestDB();
|
|
21
21
|
|
|
22
22
|
const userId = 'agent-model-test-user-id';
|
|
23
|
+
const userId2 = 'agent-model-test-user-id-2';
|
|
23
24
|
const agentModel = new AgentModel(serverDB, userId);
|
|
25
|
+
const agentModel2 = new AgentModel(serverDB, userId2);
|
|
24
26
|
|
|
25
27
|
const knowledgeBase = { id: 'kb1', userId, name: 'knowledgeBase' };
|
|
28
|
+
const knowledgeBase2 = { id: 'kb2', userId: userId2, name: 'knowledgeBase2' };
|
|
26
29
|
const fileList = [
|
|
27
30
|
{
|
|
28
31
|
id: '1',
|
|
@@ -42,11 +45,22 @@ const fileList = [
|
|
|
42
45
|
},
|
|
43
46
|
];
|
|
44
47
|
|
|
48
|
+
const fileList2 = [
|
|
49
|
+
{
|
|
50
|
+
id: '3',
|
|
51
|
+
name: 'other.pdf',
|
|
52
|
+
url: 'https://a.com/other.pdf',
|
|
53
|
+
size: 1000,
|
|
54
|
+
fileType: 'application/pdf',
|
|
55
|
+
userId: userId2,
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
45
59
|
beforeEach(async () => {
|
|
46
60
|
await serverDB.delete(users);
|
|
47
|
-
await serverDB.insert(users).values([{ id: userId }]);
|
|
48
|
-
await serverDB.insert(knowledgeBases).values(knowledgeBase);
|
|
49
|
-
await serverDB.insert(files).values(fileList);
|
|
61
|
+
await serverDB.insert(users).values([{ id: userId }, { id: userId2 }]);
|
|
62
|
+
await serverDB.insert(knowledgeBases).values([knowledgeBase, knowledgeBase2]);
|
|
63
|
+
await serverDB.insert(files).values([...fileList, ...fileList2]);
|
|
50
64
|
});
|
|
51
65
|
|
|
52
66
|
afterEach(async () => {
|
|
@@ -226,6 +240,27 @@ describe('AgentModel', () => {
|
|
|
226
240
|
|
|
227
241
|
expect(result).toBeUndefined();
|
|
228
242
|
});
|
|
243
|
+
|
|
244
|
+
it('should not delete another user agent knowledge base association', async () => {
|
|
245
|
+
const agent = await serverDB
|
|
246
|
+
.insert(agents)
|
|
247
|
+
.values({ userId })
|
|
248
|
+
.returning()
|
|
249
|
+
.then((res) => res[0]);
|
|
250
|
+
await serverDB
|
|
251
|
+
.insert(agentsKnowledgeBases)
|
|
252
|
+
.values({ agentId: agent.id, knowledgeBaseId: knowledgeBase.id, userId });
|
|
253
|
+
|
|
254
|
+
// Try to delete with another user's model
|
|
255
|
+
await agentModel2.deleteAgentKnowledgeBase(agent.id, knowledgeBase.id);
|
|
256
|
+
|
|
257
|
+
const result = await serverDB.query.agentsKnowledgeBases.findFirst({
|
|
258
|
+
where: eq(agentsKnowledgeBases.agentId, agent.id),
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Should still exist
|
|
262
|
+
expect(result).toBeDefined();
|
|
263
|
+
});
|
|
229
264
|
});
|
|
230
265
|
|
|
231
266
|
describe('toggleKnowledgeBase', () => {
|
|
@@ -248,6 +283,28 @@ describe('AgentModel', () => {
|
|
|
248
283
|
|
|
249
284
|
expect(result?.enabled).toBe(false);
|
|
250
285
|
});
|
|
286
|
+
|
|
287
|
+
it('should not toggle another user agent knowledge base association', async () => {
|
|
288
|
+
const agent = await serverDB
|
|
289
|
+
.insert(agents)
|
|
290
|
+
.values({ userId })
|
|
291
|
+
.returning()
|
|
292
|
+
.then((res) => res[0]);
|
|
293
|
+
|
|
294
|
+
await serverDB
|
|
295
|
+
.insert(agentsKnowledgeBases)
|
|
296
|
+
.values({ agentId: agent.id, knowledgeBaseId: knowledgeBase.id, userId, enabled: true });
|
|
297
|
+
|
|
298
|
+
// Try to toggle with another user's model
|
|
299
|
+
await agentModel2.toggleKnowledgeBase(agent.id, knowledgeBase.id, false);
|
|
300
|
+
|
|
301
|
+
const result = await serverDB.query.agentsKnowledgeBases.findFirst({
|
|
302
|
+
where: eq(agentsKnowledgeBases.agentId, agent.id),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
// Should still be enabled
|
|
306
|
+
expect(result?.enabled).toBe(true);
|
|
307
|
+
});
|
|
251
308
|
});
|
|
252
309
|
|
|
253
310
|
describe('createAgentFiles', () => {
|
|
@@ -363,6 +420,26 @@ describe('AgentModel', () => {
|
|
|
363
420
|
|
|
364
421
|
expect(result).toBeUndefined();
|
|
365
422
|
});
|
|
423
|
+
|
|
424
|
+
it('should not delete another user agent file association', async () => {
|
|
425
|
+
const agent = await serverDB
|
|
426
|
+
.insert(agents)
|
|
427
|
+
.values({ userId })
|
|
428
|
+
.returning()
|
|
429
|
+
.then((res) => res[0]);
|
|
430
|
+
|
|
431
|
+
await serverDB.insert(agentsFiles).values({ agentId: agent.id, fileId: '1', userId });
|
|
432
|
+
|
|
433
|
+
// Try to delete with another user's model
|
|
434
|
+
await agentModel2.deleteAgentFile(agent.id, '1');
|
|
435
|
+
|
|
436
|
+
const result = await serverDB.query.agentsFiles.findFirst({
|
|
437
|
+
where: eq(agentsFiles.agentId, agent.id),
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
// Should still exist
|
|
441
|
+
expect(result).toBeDefined();
|
|
442
|
+
});
|
|
366
443
|
});
|
|
367
444
|
|
|
368
445
|
describe('toggleFile', () => {
|
|
@@ -385,5 +462,27 @@ describe('AgentModel', () => {
|
|
|
385
462
|
|
|
386
463
|
expect(result?.enabled).toBe(false);
|
|
387
464
|
});
|
|
465
|
+
|
|
466
|
+
it('should not toggle another user agent file association', async () => {
|
|
467
|
+
const agent = await serverDB
|
|
468
|
+
.insert(agents)
|
|
469
|
+
.values({ userId })
|
|
470
|
+
.returning()
|
|
471
|
+
.then((res) => res[0]);
|
|
472
|
+
|
|
473
|
+
await serverDB
|
|
474
|
+
.insert(agentsFiles)
|
|
475
|
+
.values({ agentId: agent.id, fileId: '1', userId, enabled: true });
|
|
476
|
+
|
|
477
|
+
// Try to toggle with another user's model
|
|
478
|
+
await agentModel2.toggleFile(agent.id, '1', false);
|
|
479
|
+
|
|
480
|
+
const result = await serverDB.query.agentsFiles.findFirst({
|
|
481
|
+
where: eq(agentsFiles.agentId, agent.id),
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// Should still be enabled
|
|
485
|
+
expect(result?.enabled).toBe(true);
|
|
486
|
+
});
|
|
388
487
|
});
|
|
389
488
|
});
|