@lobehub/lobehub 2.0.0-next.94 → 2.0.0-next.96
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/.github/workflows/issue-auto-comments.yml +0 -19
- package/CHANGELOG.md +50 -0
- package/changelog/v1.json +18 -0
- package/locales/ar/common.json +21 -0
- package/locales/ar/hotkey.json +4 -0
- package/locales/bg-BG/common.json +21 -0
- package/locales/bg-BG/hotkey.json +4 -0
- package/locales/de-DE/common.json +21 -0
- package/locales/de-DE/hotkey.json +4 -0
- package/locales/en-US/common.json +21 -0
- package/locales/en-US/hotkey.json +4 -0
- package/locales/es-ES/common.json +21 -0
- package/locales/es-ES/hotkey.json +4 -0
- package/locales/fa-IR/common.json +21 -0
- package/locales/fa-IR/hotkey.json +4 -0
- package/locales/fr-FR/common.json +21 -0
- package/locales/fr-FR/hotkey.json +4 -0
- package/locales/it-IT/common.json +21 -0
- package/locales/it-IT/hotkey.json +4 -0
- package/locales/ja-JP/common.json +21 -0
- package/locales/ja-JP/hotkey.json +4 -0
- package/locales/ko-KR/common.json +21 -0
- package/locales/ko-KR/hotkey.json +4 -0
- package/locales/nl-NL/common.json +21 -0
- package/locales/nl-NL/hotkey.json +4 -0
- package/locales/pl-PL/common.json +21 -0
- package/locales/pl-PL/hotkey.json +4 -0
- package/locales/pt-BR/common.json +21 -0
- package/locales/pt-BR/hotkey.json +4 -0
- package/locales/ru-RU/common.json +21 -0
- package/locales/ru-RU/hotkey.json +4 -0
- package/locales/tr-TR/common.json +21 -0
- package/locales/tr-TR/hotkey.json +4 -0
- package/locales/vi-VN/common.json +21 -0
- package/locales/vi-VN/hotkey.json +4 -0
- package/locales/zh-CN/common.json +21 -0
- package/locales/zh-CN/hotkey.json +4 -0
- package/locales/zh-TW/common.json +21 -0
- package/locales/zh-TW/hotkey.json +4 -0
- package/package.json +3 -1
- package/packages/agent-runtime/src/core/InterventionChecker.ts +85 -0
- package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +492 -22
- package/packages/agent-runtime/src/core/defaultSecurityBlacklist.ts +335 -0
- package/packages/agent-runtime/src/core/index.ts +1 -0
- package/packages/agent-runtime/src/types/state.ts +10 -1
- package/packages/const/src/hotkeys.ts +6 -0
- package/packages/conversation-flow/src/__tests__/indexing.test.ts +513 -0
- package/packages/conversation-flow/src/__tests__/structuring.test.ts +600 -0
- package/packages/types/src/hotkey.ts +1 -0
- package/packages/types/src/tool/intervention.ts +38 -0
- package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +41 -8
- package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +6 -4
- package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/index.tsx +16 -4
- package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +15 -3
- package/src/app/[variants]/(main)/settings/provider/detail/index.tsx +23 -15
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +25 -0
- package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +28 -0
- package/src/layout/GlobalProvider/Cmdk.tsx +470 -0
- package/src/layout/GlobalProvider/CmdkLazy.tsx +17 -0
- package/src/layout/GlobalProvider/index.tsx +2 -0
- package/src/locales/default/common.ts +21 -0
- package/src/locales/default/hotkey.ts +4 -0
- package/src/store/chat/agents/GeneralChatAgent.ts +22 -8
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { buildHelperMaps } from '../indexing';
|
|
4
|
+
import { buildIdTree } from '../structuring';
|
|
5
|
+
import type { Message } from '../types';
|
|
6
|
+
|
|
7
|
+
describe('buildIdTree', () => {
|
|
8
|
+
describe('basic tree building', () => {
|
|
9
|
+
it('should build tree from single root message', () => {
|
|
10
|
+
const messages: Message[] = [
|
|
11
|
+
{
|
|
12
|
+
content: 'Root',
|
|
13
|
+
createdAt: 1000,
|
|
14
|
+
id: 'msg-1',
|
|
15
|
+
meta: {},
|
|
16
|
+
role: 'user',
|
|
17
|
+
updatedAt: 1000,
|
|
18
|
+
},
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const helperMaps = buildHelperMaps(messages);
|
|
22
|
+
const result = buildIdTree(helperMaps);
|
|
23
|
+
|
|
24
|
+
expect(result).toHaveLength(1);
|
|
25
|
+
expect(result[0]).toEqual({
|
|
26
|
+
children: [],
|
|
27
|
+
id: 'msg-1',
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should build tree with linear conversation', () => {
|
|
32
|
+
const messages: Message[] = [
|
|
33
|
+
{
|
|
34
|
+
content: 'Message 1',
|
|
35
|
+
createdAt: 1000,
|
|
36
|
+
id: 'msg-1',
|
|
37
|
+
meta: {},
|
|
38
|
+
role: 'user',
|
|
39
|
+
updatedAt: 1000,
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
content: 'Message 2',
|
|
43
|
+
createdAt: 2000,
|
|
44
|
+
id: 'msg-2',
|
|
45
|
+
meta: {},
|
|
46
|
+
parentId: 'msg-1',
|
|
47
|
+
role: 'assistant',
|
|
48
|
+
updatedAt: 2000,
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
content: 'Message 3',
|
|
52
|
+
createdAt: 3000,
|
|
53
|
+
id: 'msg-3',
|
|
54
|
+
meta: {},
|
|
55
|
+
parentId: 'msg-2',
|
|
56
|
+
role: 'user',
|
|
57
|
+
updatedAt: 3000,
|
|
58
|
+
},
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
const helperMaps = buildHelperMaps(messages);
|
|
62
|
+
const result = buildIdTree(helperMaps);
|
|
63
|
+
|
|
64
|
+
expect(result).toHaveLength(1);
|
|
65
|
+
expect(result[0].id).toBe('msg-1');
|
|
66
|
+
expect(result[0].children).toHaveLength(1);
|
|
67
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
68
|
+
expect(result[0].children[0].children).toHaveLength(1);
|
|
69
|
+
expect(result[0].children[0].children[0].id).toBe('msg-3');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should handle multiple root messages', () => {
|
|
73
|
+
const messages: Message[] = [
|
|
74
|
+
{
|
|
75
|
+
content: 'Root 1',
|
|
76
|
+
createdAt: 1000,
|
|
77
|
+
id: 'msg-1',
|
|
78
|
+
meta: {},
|
|
79
|
+
role: 'user',
|
|
80
|
+
updatedAt: 1000,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
content: 'Root 2',
|
|
84
|
+
createdAt: 2000,
|
|
85
|
+
id: 'msg-2',
|
|
86
|
+
meta: {},
|
|
87
|
+
role: 'user',
|
|
88
|
+
updatedAt: 2000,
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
const helperMaps = buildHelperMaps(messages);
|
|
93
|
+
const result = buildIdTree(helperMaps);
|
|
94
|
+
|
|
95
|
+
expect(result).toHaveLength(2);
|
|
96
|
+
expect(result[0].id).toBe('msg-1');
|
|
97
|
+
expect(result[1].id).toBe('msg-2');
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('branching conversations', () => {
|
|
102
|
+
it('should build tree with branches', () => {
|
|
103
|
+
const messages: Message[] = [
|
|
104
|
+
{
|
|
105
|
+
content: 'Root',
|
|
106
|
+
createdAt: 1000,
|
|
107
|
+
id: 'msg-1',
|
|
108
|
+
meta: {},
|
|
109
|
+
role: 'user',
|
|
110
|
+
updatedAt: 1000,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
content: 'Branch 1',
|
|
114
|
+
createdAt: 2000,
|
|
115
|
+
id: 'msg-2',
|
|
116
|
+
meta: {},
|
|
117
|
+
parentId: 'msg-1',
|
|
118
|
+
role: 'assistant',
|
|
119
|
+
updatedAt: 2000,
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
content: 'Branch 2',
|
|
123
|
+
createdAt: 3000,
|
|
124
|
+
id: 'msg-3',
|
|
125
|
+
meta: {},
|
|
126
|
+
parentId: 'msg-1',
|
|
127
|
+
role: 'assistant',
|
|
128
|
+
updatedAt: 3000,
|
|
129
|
+
},
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
const helperMaps = buildHelperMaps(messages);
|
|
133
|
+
const result = buildIdTree(helperMaps);
|
|
134
|
+
|
|
135
|
+
expect(result).toHaveLength(1);
|
|
136
|
+
expect(result[0].id).toBe('msg-1');
|
|
137
|
+
expect(result[0].children).toHaveLength(2);
|
|
138
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
139
|
+
expect(result[0].children[1].id).toBe('msg-3');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should handle nested branches', () => {
|
|
143
|
+
const messages: Message[] = [
|
|
144
|
+
{
|
|
145
|
+
content: 'Root',
|
|
146
|
+
createdAt: 1000,
|
|
147
|
+
id: 'msg-1',
|
|
148
|
+
meta: {},
|
|
149
|
+
role: 'user',
|
|
150
|
+
updatedAt: 1000,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
content: 'Branch 1',
|
|
154
|
+
createdAt: 2000,
|
|
155
|
+
id: 'msg-2',
|
|
156
|
+
meta: {},
|
|
157
|
+
parentId: 'msg-1',
|
|
158
|
+
role: 'assistant',
|
|
159
|
+
updatedAt: 2000,
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
content: 'Branch 1.1',
|
|
163
|
+
createdAt: 3000,
|
|
164
|
+
id: 'msg-3',
|
|
165
|
+
meta: {},
|
|
166
|
+
parentId: 'msg-2',
|
|
167
|
+
role: 'user',
|
|
168
|
+
updatedAt: 3000,
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
content: 'Branch 1.2',
|
|
172
|
+
createdAt: 4000,
|
|
173
|
+
id: 'msg-4',
|
|
174
|
+
meta: {},
|
|
175
|
+
parentId: 'msg-2',
|
|
176
|
+
role: 'user',
|
|
177
|
+
updatedAt: 4000,
|
|
178
|
+
},
|
|
179
|
+
];
|
|
180
|
+
|
|
181
|
+
const helperMaps = buildHelperMaps(messages);
|
|
182
|
+
const result = buildIdTree(helperMaps);
|
|
183
|
+
|
|
184
|
+
expect(result).toHaveLength(1);
|
|
185
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
186
|
+
expect(result[0].children[0].children).toHaveLength(2);
|
|
187
|
+
expect(result[0].children[0].children[0].id).toBe('msg-3');
|
|
188
|
+
expect(result[0].children[0].children[1].id).toBe('msg-4');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should handle deeply nested tree (4 levels)', () => {
|
|
192
|
+
const messages: Message[] = [
|
|
193
|
+
{
|
|
194
|
+
content: 'Level 0',
|
|
195
|
+
createdAt: 1000,
|
|
196
|
+
id: 'msg-1',
|
|
197
|
+
meta: {},
|
|
198
|
+
role: 'user',
|
|
199
|
+
updatedAt: 1000,
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
content: 'Level 1',
|
|
203
|
+
createdAt: 2000,
|
|
204
|
+
id: 'msg-2',
|
|
205
|
+
meta: {},
|
|
206
|
+
parentId: 'msg-1',
|
|
207
|
+
role: 'assistant',
|
|
208
|
+
updatedAt: 2000,
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
content: 'Level 2',
|
|
212
|
+
createdAt: 3000,
|
|
213
|
+
id: 'msg-3',
|
|
214
|
+
meta: {},
|
|
215
|
+
parentId: 'msg-2',
|
|
216
|
+
role: 'user',
|
|
217
|
+
updatedAt: 3000,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
content: 'Level 3',
|
|
221
|
+
createdAt: 4000,
|
|
222
|
+
id: 'msg-4',
|
|
223
|
+
meta: {},
|
|
224
|
+
parentId: 'msg-3',
|
|
225
|
+
role: 'assistant',
|
|
226
|
+
updatedAt: 4000,
|
|
227
|
+
},
|
|
228
|
+
];
|
|
229
|
+
|
|
230
|
+
const helperMaps = buildHelperMaps(messages);
|
|
231
|
+
const result = buildIdTree(helperMaps);
|
|
232
|
+
|
|
233
|
+
expect(result).toHaveLength(1);
|
|
234
|
+
|
|
235
|
+
let current = result[0];
|
|
236
|
+
expect(current.id).toBe('msg-1');
|
|
237
|
+
|
|
238
|
+
current = current.children[0];
|
|
239
|
+
expect(current.id).toBe('msg-2');
|
|
240
|
+
|
|
241
|
+
current = current.children[0];
|
|
242
|
+
expect(current.id).toBe('msg-3');
|
|
243
|
+
|
|
244
|
+
current = current.children[0];
|
|
245
|
+
expect(current.id).toBe('msg-4');
|
|
246
|
+
expect(current.children).toHaveLength(0);
|
|
247
|
+
});
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
describe('thread handling', () => {
|
|
251
|
+
it('should exclude messages with threadId from main tree', () => {
|
|
252
|
+
const messages: Message[] = [
|
|
253
|
+
{
|
|
254
|
+
content: 'Root',
|
|
255
|
+
createdAt: 1000,
|
|
256
|
+
id: 'msg-1',
|
|
257
|
+
meta: {},
|
|
258
|
+
role: 'user',
|
|
259
|
+
updatedAt: 1000,
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
content: 'Main flow',
|
|
263
|
+
createdAt: 2000,
|
|
264
|
+
id: 'msg-2',
|
|
265
|
+
meta: {},
|
|
266
|
+
parentId: 'msg-1',
|
|
267
|
+
role: 'assistant',
|
|
268
|
+
updatedAt: 2000,
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
content: 'Thread message',
|
|
272
|
+
createdAt: 3000,
|
|
273
|
+
id: 'msg-3',
|
|
274
|
+
meta: {},
|
|
275
|
+
parentId: 'msg-1',
|
|
276
|
+
role: 'assistant',
|
|
277
|
+
threadId: 'thread-1',
|
|
278
|
+
updatedAt: 3000,
|
|
279
|
+
},
|
|
280
|
+
];
|
|
281
|
+
|
|
282
|
+
const helperMaps = buildHelperMaps(messages);
|
|
283
|
+
const result = buildIdTree(helperMaps);
|
|
284
|
+
|
|
285
|
+
expect(result).toHaveLength(1);
|
|
286
|
+
expect(result[0].id).toBe('msg-1');
|
|
287
|
+
expect(result[0].children).toHaveLength(1);
|
|
288
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should exclude root messages with threadId', () => {
|
|
292
|
+
const messages: Message[] = [
|
|
293
|
+
{
|
|
294
|
+
content: 'Main root',
|
|
295
|
+
createdAt: 1000,
|
|
296
|
+
id: 'msg-1',
|
|
297
|
+
meta: {},
|
|
298
|
+
role: 'user',
|
|
299
|
+
updatedAt: 1000,
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
content: 'Thread root',
|
|
303
|
+
createdAt: 2000,
|
|
304
|
+
id: 'msg-2',
|
|
305
|
+
meta: {},
|
|
306
|
+
role: 'assistant',
|
|
307
|
+
threadId: 'thread-1',
|
|
308
|
+
updatedAt: 2000,
|
|
309
|
+
},
|
|
310
|
+
];
|
|
311
|
+
|
|
312
|
+
const helperMaps = buildHelperMaps(messages);
|
|
313
|
+
const result = buildIdTree(helperMaps);
|
|
314
|
+
|
|
315
|
+
expect(result).toHaveLength(1);
|
|
316
|
+
expect(result[0].id).toBe('msg-1');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should filter out thread children at any level', () => {
|
|
320
|
+
const messages: Message[] = [
|
|
321
|
+
{
|
|
322
|
+
content: 'Root',
|
|
323
|
+
createdAt: 1000,
|
|
324
|
+
id: 'msg-1',
|
|
325
|
+
meta: {},
|
|
326
|
+
role: 'user',
|
|
327
|
+
updatedAt: 1000,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
content: 'Main child',
|
|
331
|
+
createdAt: 2000,
|
|
332
|
+
id: 'msg-2',
|
|
333
|
+
meta: {},
|
|
334
|
+
parentId: 'msg-1',
|
|
335
|
+
role: 'assistant',
|
|
336
|
+
updatedAt: 2000,
|
|
337
|
+
},
|
|
338
|
+
{
|
|
339
|
+
content: 'Main grandchild',
|
|
340
|
+
createdAt: 3000,
|
|
341
|
+
id: 'msg-3',
|
|
342
|
+
meta: {},
|
|
343
|
+
parentId: 'msg-2',
|
|
344
|
+
role: 'user',
|
|
345
|
+
updatedAt: 3000,
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
content: 'Thread grandchild',
|
|
349
|
+
createdAt: 4000,
|
|
350
|
+
id: 'msg-4',
|
|
351
|
+
meta: {},
|
|
352
|
+
parentId: 'msg-2',
|
|
353
|
+
role: 'user',
|
|
354
|
+
threadId: 'thread-1',
|
|
355
|
+
updatedAt: 4000,
|
|
356
|
+
},
|
|
357
|
+
];
|
|
358
|
+
|
|
359
|
+
const helperMaps = buildHelperMaps(messages);
|
|
360
|
+
const result = buildIdTree(helperMaps);
|
|
361
|
+
|
|
362
|
+
expect(result).toHaveLength(1);
|
|
363
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
364
|
+
expect(result[0].children[0].children).toHaveLength(1);
|
|
365
|
+
expect(result[0].children[0].children[0].id).toBe('msg-3');
|
|
366
|
+
});
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
describe('edge cases', () => {
|
|
370
|
+
it('should handle empty messages', () => {
|
|
371
|
+
const helperMaps = buildHelperMaps([]);
|
|
372
|
+
const result = buildIdTree(helperMaps);
|
|
373
|
+
|
|
374
|
+
expect(result).toEqual([]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it('should handle messages with no children', () => {
|
|
378
|
+
const messages: Message[] = [
|
|
379
|
+
{
|
|
380
|
+
content: 'Lone message',
|
|
381
|
+
createdAt: 1000,
|
|
382
|
+
id: 'msg-1',
|
|
383
|
+
meta: {},
|
|
384
|
+
role: 'user',
|
|
385
|
+
updatedAt: 1000,
|
|
386
|
+
},
|
|
387
|
+
];
|
|
388
|
+
|
|
389
|
+
const helperMaps = buildHelperMaps(messages);
|
|
390
|
+
const result = buildIdTree(helperMaps);
|
|
391
|
+
|
|
392
|
+
expect(result).toHaveLength(1);
|
|
393
|
+
expect(result[0]).toEqual({
|
|
394
|
+
children: [],
|
|
395
|
+
id: 'msg-1',
|
|
396
|
+
});
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('should handle messages with missing parent references', () => {
|
|
400
|
+
const messages: Message[] = [
|
|
401
|
+
{
|
|
402
|
+
content: 'Orphan',
|
|
403
|
+
createdAt: 1000,
|
|
404
|
+
id: 'msg-1',
|
|
405
|
+
meta: {},
|
|
406
|
+
parentId: 'non-existent',
|
|
407
|
+
role: 'user',
|
|
408
|
+
updatedAt: 1000,
|
|
409
|
+
},
|
|
410
|
+
];
|
|
411
|
+
|
|
412
|
+
const helperMaps = buildHelperMaps(messages);
|
|
413
|
+
const result = buildIdTree(helperMaps);
|
|
414
|
+
|
|
415
|
+
// Orphan messages without valid parents are not in main flow
|
|
416
|
+
expect(result).toEqual([]);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('should build correct tree when all messages are threaded', () => {
|
|
420
|
+
const messages: Message[] = [
|
|
421
|
+
{
|
|
422
|
+
content: 'Thread 1',
|
|
423
|
+
createdAt: 1000,
|
|
424
|
+
id: 'msg-1',
|
|
425
|
+
meta: {},
|
|
426
|
+
role: 'user',
|
|
427
|
+
threadId: 'thread-1',
|
|
428
|
+
updatedAt: 1000,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
content: 'Thread 2',
|
|
432
|
+
createdAt: 2000,
|
|
433
|
+
id: 'msg-2',
|
|
434
|
+
meta: {},
|
|
435
|
+
role: 'assistant',
|
|
436
|
+
threadId: 'thread-1',
|
|
437
|
+
updatedAt: 2000,
|
|
438
|
+
},
|
|
439
|
+
];
|
|
440
|
+
|
|
441
|
+
const helperMaps = buildHelperMaps(messages);
|
|
442
|
+
const result = buildIdTree(helperMaps);
|
|
443
|
+
|
|
444
|
+
expect(result).toEqual([]);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe('complex scenarios', () => {
|
|
449
|
+
it('should build tree with mixed branches and linear paths', () => {
|
|
450
|
+
const messages: Message[] = [
|
|
451
|
+
{
|
|
452
|
+
content: 'Root',
|
|
453
|
+
createdAt: 1000,
|
|
454
|
+
id: 'msg-1',
|
|
455
|
+
meta: {},
|
|
456
|
+
role: 'user',
|
|
457
|
+
updatedAt: 1000,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
content: 'Branch A',
|
|
461
|
+
createdAt: 2000,
|
|
462
|
+
id: 'msg-2',
|
|
463
|
+
meta: {},
|
|
464
|
+
parentId: 'msg-1',
|
|
465
|
+
role: 'assistant',
|
|
466
|
+
updatedAt: 2000,
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
content: 'Branch A -> Child',
|
|
470
|
+
createdAt: 3000,
|
|
471
|
+
id: 'msg-3',
|
|
472
|
+
meta: {},
|
|
473
|
+
parentId: 'msg-2',
|
|
474
|
+
role: 'user',
|
|
475
|
+
updatedAt: 3000,
|
|
476
|
+
},
|
|
477
|
+
{
|
|
478
|
+
content: 'Branch B',
|
|
479
|
+
createdAt: 4000,
|
|
480
|
+
id: 'msg-4',
|
|
481
|
+
meta: {},
|
|
482
|
+
parentId: 'msg-1',
|
|
483
|
+
role: 'assistant',
|
|
484
|
+
updatedAt: 4000,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
content: 'Branch B -> Child 1',
|
|
488
|
+
createdAt: 5000,
|
|
489
|
+
id: 'msg-5',
|
|
490
|
+
meta: {},
|
|
491
|
+
parentId: 'msg-4',
|
|
492
|
+
role: 'user',
|
|
493
|
+
updatedAt: 5000,
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
content: 'Branch B -> Child 2',
|
|
497
|
+
createdAt: 6000,
|
|
498
|
+
id: 'msg-6',
|
|
499
|
+
meta: {},
|
|
500
|
+
parentId: 'msg-4',
|
|
501
|
+
role: 'user',
|
|
502
|
+
updatedAt: 6000,
|
|
503
|
+
},
|
|
504
|
+
];
|
|
505
|
+
|
|
506
|
+
const helperMaps = buildHelperMaps(messages);
|
|
507
|
+
const result = buildIdTree(helperMaps);
|
|
508
|
+
|
|
509
|
+
expect(result).toHaveLength(1);
|
|
510
|
+
expect(result[0].id).toBe('msg-1');
|
|
511
|
+
expect(result[0].children).toHaveLength(2);
|
|
512
|
+
|
|
513
|
+
// Branch A
|
|
514
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
515
|
+
expect(result[0].children[0].children).toHaveLength(1);
|
|
516
|
+
expect(result[0].children[0].children[0].id).toBe('msg-3');
|
|
517
|
+
|
|
518
|
+
// Branch B
|
|
519
|
+
expect(result[0].children[1].id).toBe('msg-4');
|
|
520
|
+
expect(result[0].children[1].children).toHaveLength(2);
|
|
521
|
+
expect(result[0].children[1].children[0].id).toBe('msg-5');
|
|
522
|
+
expect(result[0].children[1].children[1].id).toBe('msg-6');
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should handle multiple root trees', () => {
|
|
526
|
+
const messages: Message[] = [
|
|
527
|
+
// Tree 1
|
|
528
|
+
{
|
|
529
|
+
content: 'Tree 1 Root',
|
|
530
|
+
createdAt: 1000,
|
|
531
|
+
id: 'msg-1',
|
|
532
|
+
meta: {},
|
|
533
|
+
role: 'user',
|
|
534
|
+
updatedAt: 1000,
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
content: 'Tree 1 Child',
|
|
538
|
+
createdAt: 2000,
|
|
539
|
+
id: 'msg-2',
|
|
540
|
+
meta: {},
|
|
541
|
+
parentId: 'msg-1',
|
|
542
|
+
role: 'assistant',
|
|
543
|
+
updatedAt: 2000,
|
|
544
|
+
},
|
|
545
|
+
// Tree 2
|
|
546
|
+
{
|
|
547
|
+
content: 'Tree 2 Root',
|
|
548
|
+
createdAt: 3000,
|
|
549
|
+
id: 'msg-3',
|
|
550
|
+
meta: {},
|
|
551
|
+
role: 'user',
|
|
552
|
+
updatedAt: 3000,
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
content: 'Tree 2 Child',
|
|
556
|
+
createdAt: 4000,
|
|
557
|
+
id: 'msg-4',
|
|
558
|
+
meta: {},
|
|
559
|
+
parentId: 'msg-3',
|
|
560
|
+
role: 'assistant',
|
|
561
|
+
updatedAt: 4000,
|
|
562
|
+
},
|
|
563
|
+
];
|
|
564
|
+
|
|
565
|
+
const helperMaps = buildHelperMaps(messages);
|
|
566
|
+
const result = buildIdTree(helperMaps);
|
|
567
|
+
|
|
568
|
+
expect(result).toHaveLength(2);
|
|
569
|
+
expect(result[0].id).toBe('msg-1');
|
|
570
|
+
expect(result[0].children[0].id).toBe('msg-2');
|
|
571
|
+
expect(result[1].id).toBe('msg-3');
|
|
572
|
+
expect(result[1].children[0].id).toBe('msg-4');
|
|
573
|
+
});
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
describe('performance', () => {
|
|
577
|
+
it('should build tree for large dataset efficiently', () => {
|
|
578
|
+
const messages: Message[] = Array.from({ length: 1000 }, (_, i) => ({
|
|
579
|
+
content: `Message ${i}`,
|
|
580
|
+
createdAt: i,
|
|
581
|
+
id: `msg-${i}`,
|
|
582
|
+
meta: {},
|
|
583
|
+
parentId: i > 0 ? `msg-${i - 1}` : undefined,
|
|
584
|
+
role: i % 2 === 0 ? ('user' as const) : ('assistant' as const),
|
|
585
|
+
updatedAt: i,
|
|
586
|
+
}));
|
|
587
|
+
|
|
588
|
+
const helperMaps = buildHelperMaps(messages);
|
|
589
|
+
|
|
590
|
+
const startTime = performance.now();
|
|
591
|
+
const result = buildIdTree(helperMaps);
|
|
592
|
+
const endTime = performance.now();
|
|
593
|
+
|
|
594
|
+
const executionTime = endTime - startTime;
|
|
595
|
+
|
|
596
|
+
expect(result).toHaveLength(1);
|
|
597
|
+
expect(executionTime).toBeLessThan(50); // Should be fast
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
});
|
|
@@ -60,6 +60,7 @@ export const KeyEnum = {
|
|
|
60
60
|
export const HotkeyEnum = {
|
|
61
61
|
AddUserMessage: 'addUserMessage',
|
|
62
62
|
ClearCurrentMessages: 'clearCurrentMessages',
|
|
63
|
+
CommandPalette: 'commandPalette',
|
|
63
64
|
DeleteAndRegenerateMessage: 'deleteAndRegenerateMessage',
|
|
64
65
|
DeleteLastMessage: 'deleteLastMessage',
|
|
65
66
|
EditMessage: 'editMessage',
|
|
@@ -146,6 +146,36 @@ export const UserInterventionConfigSchema = z.object({
|
|
|
146
146
|
approvalMode: z.enum(['auto-run', 'allow-list', 'manual']),
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* Security Blacklist Rule
|
|
151
|
+
* Used to forcefully block dangerous operations regardless of user settings
|
|
152
|
+
*/
|
|
153
|
+
export interface SecurityBlacklistRule {
|
|
154
|
+
/**
|
|
155
|
+
* Description of why this rule exists (for error messages)
|
|
156
|
+
*/
|
|
157
|
+
description: string;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Parameter filter - matches against tool call arguments
|
|
161
|
+
* Same format as HumanInterventionRule.match
|
|
162
|
+
*/
|
|
163
|
+
match: Record<string, ArgumentMatcher>;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export const SecurityBlacklistRuleSchema = z.object({
|
|
167
|
+
description: z.string(),
|
|
168
|
+
match: z.record(z.string(), ArgumentMatcherSchema),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Security Blacklist Configuration
|
|
173
|
+
* A list of rules that will always block execution and require intervention
|
|
174
|
+
*/
|
|
175
|
+
export type SecurityBlacklistConfig = SecurityBlacklistRule[];
|
|
176
|
+
|
|
177
|
+
export const SecurityBlacklistConfigSchema = z.array(SecurityBlacklistRuleSchema);
|
|
178
|
+
|
|
149
179
|
/**
|
|
150
180
|
* Parameters for shouldIntervene method
|
|
151
181
|
*/
|
|
@@ -161,6 +191,13 @@ export interface ShouldInterveneParams {
|
|
|
161
191
|
*/
|
|
162
192
|
confirmedHistory?: string[];
|
|
163
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Security blacklist rules that will be checked first
|
|
196
|
+
* These rules override all other settings including auto-run mode
|
|
197
|
+
* @default []
|
|
198
|
+
*/
|
|
199
|
+
securityBlacklist?: SecurityBlacklistConfig;
|
|
200
|
+
|
|
164
201
|
/**
|
|
165
202
|
* Tool call arguments to check against rules
|
|
166
203
|
* @default {}
|
|
@@ -177,6 +214,7 @@ export interface ShouldInterveneParams {
|
|
|
177
214
|
export const ShouldInterveneParamsSchema = z.object({
|
|
178
215
|
config: HumanInterventionConfigSchema.optional(),
|
|
179
216
|
confirmedHistory: z.array(z.string()).optional(),
|
|
217
|
+
securityBlacklist: SecurityBlacklistConfigSchema.optional(),
|
|
180
218
|
toolArgs: z.record(z.string(), z.any()).optional(),
|
|
181
219
|
toolKey: z.string().optional(),
|
|
182
220
|
});
|