@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.
Files changed (63) hide show
  1. package/.github/workflows/issue-auto-comments.yml +0 -19
  2. package/CHANGELOG.md +50 -0
  3. package/changelog/v1.json +18 -0
  4. package/locales/ar/common.json +21 -0
  5. package/locales/ar/hotkey.json +4 -0
  6. package/locales/bg-BG/common.json +21 -0
  7. package/locales/bg-BG/hotkey.json +4 -0
  8. package/locales/de-DE/common.json +21 -0
  9. package/locales/de-DE/hotkey.json +4 -0
  10. package/locales/en-US/common.json +21 -0
  11. package/locales/en-US/hotkey.json +4 -0
  12. package/locales/es-ES/common.json +21 -0
  13. package/locales/es-ES/hotkey.json +4 -0
  14. package/locales/fa-IR/common.json +21 -0
  15. package/locales/fa-IR/hotkey.json +4 -0
  16. package/locales/fr-FR/common.json +21 -0
  17. package/locales/fr-FR/hotkey.json +4 -0
  18. package/locales/it-IT/common.json +21 -0
  19. package/locales/it-IT/hotkey.json +4 -0
  20. package/locales/ja-JP/common.json +21 -0
  21. package/locales/ja-JP/hotkey.json +4 -0
  22. package/locales/ko-KR/common.json +21 -0
  23. package/locales/ko-KR/hotkey.json +4 -0
  24. package/locales/nl-NL/common.json +21 -0
  25. package/locales/nl-NL/hotkey.json +4 -0
  26. package/locales/pl-PL/common.json +21 -0
  27. package/locales/pl-PL/hotkey.json +4 -0
  28. package/locales/pt-BR/common.json +21 -0
  29. package/locales/pt-BR/hotkey.json +4 -0
  30. package/locales/ru-RU/common.json +21 -0
  31. package/locales/ru-RU/hotkey.json +4 -0
  32. package/locales/tr-TR/common.json +21 -0
  33. package/locales/tr-TR/hotkey.json +4 -0
  34. package/locales/vi-VN/common.json +21 -0
  35. package/locales/vi-VN/hotkey.json +4 -0
  36. package/locales/zh-CN/common.json +21 -0
  37. package/locales/zh-CN/hotkey.json +4 -0
  38. package/locales/zh-TW/common.json +21 -0
  39. package/locales/zh-TW/hotkey.json +4 -0
  40. package/package.json +3 -1
  41. package/packages/agent-runtime/src/core/InterventionChecker.ts +85 -0
  42. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +492 -22
  43. package/packages/agent-runtime/src/core/defaultSecurityBlacklist.ts +335 -0
  44. package/packages/agent-runtime/src/core/index.ts +1 -0
  45. package/packages/agent-runtime/src/types/state.ts +10 -1
  46. package/packages/const/src/hotkeys.ts +6 -0
  47. package/packages/conversation-flow/src/__tests__/indexing.test.ts +513 -0
  48. package/packages/conversation-flow/src/__tests__/structuring.test.ts +600 -0
  49. package/packages/types/src/hotkey.ts +1 -0
  50. package/packages/types/src/tool/intervention.ts +38 -0
  51. package/src/app/[variants]/(main)/settings/_layout/Desktop/index.tsx +41 -8
  52. package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/Card.tsx +6 -4
  53. package/src/app/[variants]/(main)/settings/provider/(list)/ProviderGrid/index.tsx +16 -4
  54. package/src/app/[variants]/(main)/settings/provider/(list)/index.tsx +15 -3
  55. package/src/app/[variants]/(main)/settings/provider/detail/index.tsx +23 -15
  56. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +25 -0
  57. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +28 -0
  58. package/src/layout/GlobalProvider/Cmdk.tsx +470 -0
  59. package/src/layout/GlobalProvider/CmdkLazy.tsx +17 -0
  60. package/src/layout/GlobalProvider/index.tsx +2 -0
  61. package/src/locales/default/common.ts +21 -0
  62. package/src/locales/default/hotkey.ts +4 -0
  63. package/src/store/chat/agents/GeneralChatAgent.ts +22 -8
@@ -0,0 +1,470 @@
1
+ 'use client';
2
+
3
+ import { Tag } from '@lobehub/ui';
4
+ import { createStyles } from 'antd-style';
5
+ import { Command } from 'cmdk';
6
+ import {
7
+ ArrowLeft,
8
+ ArrowUpDown,
9
+ BookOpen,
10
+ Bot,
11
+ Compass,
12
+ CornerDownLeft,
13
+ Github,
14
+ MessageCircle,
15
+ Monitor,
16
+ Moon,
17
+ Palette,
18
+ Settings,
19
+ Star,
20
+ Sun,
21
+ } from 'lucide-react';
22
+ import { usePathname, useRouter } from 'next/navigation';
23
+ import { memo, useEffect, useState } from 'react';
24
+ import { createPortal } from 'react-dom';
25
+ import { useTranslation } from 'react-i18next';
26
+
27
+ import { useHotkeyById } from '@/hooks/useHotkeys/useHotkeyById';
28
+ import { useGlobalStore } from '@/store/global';
29
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
30
+ import { useSessionStore } from '@/store/session';
31
+ import { HotkeyEnum } from '@/types/hotkey';
32
+
33
+ const useStyles = createStyles(({ css, token }) => ({
34
+ backTag: css`
35
+ cursor: pointer;
36
+
37
+ &:hover {
38
+ opacity: 0.8;
39
+ }
40
+ `,
41
+ commandFooter: css`
42
+ display: flex;
43
+ gap: 16px;
44
+ align-items: center;
45
+ justify-content: flex-end;
46
+
47
+ padding-block: 8px;
48
+ padding-inline: 16px;
49
+ border-block-start: 1px solid ${token.colorBorderSecondary};
50
+
51
+ background: ${token.colorBgContainer};
52
+ `,
53
+ commandRoot: css`
54
+ overflow: hidden;
55
+ display: flex;
56
+ flex-direction: column;
57
+
58
+ width: min(640px, 90vw);
59
+ max-height: min(500px, 70vh);
60
+ border-radius: ${token.borderRadiusLG}px;
61
+
62
+ background: ${token.colorBgElevated};
63
+ box-shadow: ${token.boxShadowSecondary};
64
+
65
+ animation: slide-down 0.12s ease-out;
66
+
67
+ @keyframes slide-down {
68
+ from {
69
+ transform: translateY(-20px) scale(0.96);
70
+ opacity: 0;
71
+ }
72
+
73
+ to {
74
+ transform: translateY(0) scale(1);
75
+ opacity: 1;
76
+ }
77
+ }
78
+
79
+ [cmdk-input] {
80
+ flex: 1;
81
+
82
+ min-width: 0;
83
+ padding: 0;
84
+ border: none;
85
+
86
+ font-family: inherit;
87
+ font-size: 16px;
88
+ color: ${token.colorText};
89
+
90
+ background: transparent;
91
+ outline: none;
92
+
93
+ &::placeholder {
94
+ color: ${token.colorTextPlaceholder};
95
+ }
96
+ }
97
+
98
+ [cmdk-list] {
99
+ overflow-y: auto;
100
+ max-height: 400px;
101
+ padding: 8px;
102
+ }
103
+
104
+ [cmdk-empty] {
105
+ padding-block: 32px;
106
+ padding-inline: 16px;
107
+
108
+ font-size: 14px;
109
+ color: ${token.colorTextTertiary};
110
+ text-align: center;
111
+ }
112
+
113
+ [cmdk-item] {
114
+ cursor: pointer;
115
+ user-select: none;
116
+
117
+ display: flex;
118
+ gap: 12px;
119
+ align-items: center;
120
+
121
+ padding-block: 12px;
122
+ padding-inline: 16px;
123
+ border-radius: ${token.borderRadius}px;
124
+
125
+ color: ${token.colorText};
126
+
127
+ transition: all 0.15s ease;
128
+
129
+ &[aria-selected='true'] {
130
+ background: ${token.colorBgTextHover};
131
+ }
132
+
133
+ &:hover {
134
+ background: ${token.colorBgTextHover};
135
+ }
136
+ }
137
+
138
+ [cmdk-group-heading] {
139
+ user-select: none;
140
+
141
+ padding-block: 8px;
142
+ padding-inline: 16px;
143
+
144
+ font-size: 12px;
145
+ font-weight: 500;
146
+ color: ${token.colorTextSecondary};
147
+ }
148
+
149
+ [cmdk-separator] {
150
+ height: 1px;
151
+ margin-block: 4px;
152
+ background: ${token.colorBorderSecondary};
153
+ }
154
+ `,
155
+ icon: css`
156
+ flex-shrink: 0;
157
+ width: 20px;
158
+ height: 20px;
159
+ color: ${token.colorTextSecondary};
160
+ `,
161
+ inputWrapper: css`
162
+ display: flex;
163
+ gap: 8px;
164
+ align-items: center;
165
+
166
+ padding: 16px;
167
+ border-block-end: 1px solid ${token.colorBorderSecondary};
168
+ `,
169
+ itemContent: css`
170
+ flex: 1;
171
+ min-width: 0;
172
+ `,
173
+ itemDescription: css`
174
+ margin-block-start: 2px;
175
+ font-size: 12px;
176
+ line-height: 1.4;
177
+ color: ${token.colorTextTertiary};
178
+ `,
179
+ itemLabel: css`
180
+ font-size: 14px;
181
+ font-weight: 500;
182
+ line-height: 1.4;
183
+ `,
184
+ kbd: css`
185
+ display: inline-flex;
186
+ gap: 4px;
187
+ align-items: center;
188
+
189
+ padding-block: 2px;
190
+ padding-inline: 6px;
191
+ border-radius: ${token.borderRadiusSM}px;
192
+
193
+ font-size: 11px;
194
+ font-weight: 500;
195
+ line-height: 1.2;
196
+ color: ${token.colorTextSecondary};
197
+
198
+ background: ${token.colorFillQuaternary};
199
+ `,
200
+ kbdIcon: css`
201
+ width: 12px;
202
+ height: 12px;
203
+ `,
204
+ overlay: css`
205
+ position: fixed;
206
+ z-index: 9999;
207
+ inset: 0;
208
+
209
+ display: flex;
210
+ justify-content: center;
211
+
212
+ padding-block-start: 15vh;
213
+
214
+ background: ${token.colorBgMask};
215
+
216
+ animation: fade-in 0.1s ease-in-out;
217
+
218
+ @keyframes fade-in {
219
+ from {
220
+ opacity: 0;
221
+ }
222
+
223
+ to {
224
+ opacity: 1;
225
+ }
226
+ }
227
+ `,
228
+ }));
229
+
230
+ const Cmdk = memo(() => {
231
+ const [open, setOpen] = useState(false);
232
+ const [mounted, setMounted] = useState(false);
233
+ const [search, setSearch] = useState('');
234
+ const [pages, setPages] = useState<string[]>([]);
235
+ const router = useRouter();
236
+ const pathname = usePathname();
237
+ const { t } = useTranslation('common');
238
+ const { styles } = useStyles();
239
+ const switchThemeMode = useGlobalStore((s) => s.switchThemeMode);
240
+ const createSession = useSessionStore((s) => s.createSession);
241
+ const { showCreateSession } = useServerConfigStore(featureFlagsSelectors);
242
+
243
+ const page = pages.at(-1);
244
+
245
+ // Ensure we're mounted on the client
246
+ useEffect(() => {
247
+ setMounted(true);
248
+ }, []);
249
+
250
+ // Register Cmd+K / Ctrl+K hotkey
251
+ useHotkeyById(HotkeyEnum.CommandPalette, () => {
252
+ setOpen((prev) => !prev);
253
+ });
254
+
255
+ // Close on Escape key and prevent body scroll
256
+ useEffect(() => {
257
+ if (open) {
258
+ const originalStyle = window.getComputedStyle(document.body).overflow;
259
+ document.body.style.overflow = 'hidden';
260
+
261
+ return () => {
262
+ document.body.style.overflow = originalStyle;
263
+ };
264
+ }
265
+ }, [open]);
266
+
267
+ // Reset pages and search when opening/closing
268
+ useEffect(() => {
269
+ if (open) {
270
+ setPages([]);
271
+ setSearch('');
272
+ }
273
+ }, [open]);
274
+
275
+ const handleNavigate = (path: string) => {
276
+ router.push(path);
277
+ setOpen(false);
278
+ };
279
+
280
+ const handleExternalLink = (url: string) => {
281
+ window.open(url, '_blank', 'noopener,noreferrer');
282
+ setOpen(false);
283
+ };
284
+
285
+ const handleThemeChange = (theme: 'light' | 'dark' | 'auto') => {
286
+ switchThemeMode(theme);
287
+ setOpen(false);
288
+ };
289
+
290
+ if (!mounted || !open) return null;
291
+
292
+ return createPortal(
293
+ <div className={styles.overlay} onClick={() => setOpen(false)}>
294
+ <div onClick={(e) => e.stopPropagation()}>
295
+ <Command
296
+ className={styles.commandRoot}
297
+ onKeyDown={(e) => {
298
+ // Escape goes to previous page or closes
299
+ if (e.key === 'Escape') {
300
+ e.preventDefault();
301
+ if (pages.length > 0) {
302
+ setPages((prev) => prev.slice(0, -1));
303
+ } else {
304
+ setOpen(false);
305
+ }
306
+ }
307
+ // Backspace goes to previous page when search is empty
308
+ if (e.key === 'Backspace' && !search && pages.length > 0) {
309
+ e.preventDefault();
310
+ setPages((prev) => prev.slice(0, -1));
311
+ }
312
+ }}
313
+ shouldFilter={true}
314
+ >
315
+ <div className={styles.inputWrapper}>
316
+ {pages.length > 0 && (
317
+ <Tag
318
+ className={styles.backTag}
319
+ icon={<ArrowLeft size={12} />}
320
+ onClick={() => setPages((prev) => prev.slice(0, -1))}
321
+ />
322
+ )}
323
+ <Command.Input
324
+ autoFocus
325
+ onValueChange={setSearch}
326
+ placeholder={t('cmdk.searchPlaceholder')}
327
+ value={search}
328
+ />
329
+ <Tag>ESC</Tag>
330
+ </div>
331
+ <Command.List>
332
+ <Command.Empty>{t('cmdk.noResults')}</Command.Empty>
333
+
334
+ {!page && (
335
+ <>
336
+ {showCreateSession && (
337
+ <Command.Item
338
+ onSelect={() => {
339
+ createSession();
340
+ setOpen(false);
341
+ }}
342
+ value="new-agent"
343
+ >
344
+ <Bot className={styles.icon} />
345
+ <div className={styles.itemContent}>
346
+ <div className={styles.itemLabel}>{t('cmdk.newAgent')}</div>
347
+ </div>
348
+ </Command.Item>
349
+ )}
350
+
351
+ {!pathname?.startsWith('/settings') && (
352
+ <Command.Item onSelect={() => handleNavigate('/settings')} value="settings">
353
+ <Settings className={styles.icon} />
354
+ <div className={styles.itemContent}>
355
+ <div className={styles.itemLabel}>{t('cmdk.settings')}</div>
356
+ </div>
357
+ </Command.Item>
358
+ )}
359
+
360
+ <Command.Item onSelect={() => setPages([...pages, 'theme'])} value="theme">
361
+ <Monitor className={styles.icon} />
362
+ <div className={styles.itemContent}>
363
+ <div className={styles.itemLabel}>{t('cmdk.theme')}</div>
364
+ </div>
365
+ </Command.Item>
366
+
367
+ <Command.Group heading={t('cmdk.navigate')}>
368
+ {!pathname?.startsWith('/discover') && (
369
+ <Command.Item onSelect={() => handleNavigate('/discover')} value="discover">
370
+ <Compass className={styles.icon} />
371
+ <div className={styles.itemContent}>
372
+ <div className={styles.itemLabel}>{t('cmdk.discover')}</div>
373
+ </div>
374
+ </Command.Item>
375
+ )}
376
+ {!pathname?.startsWith('/image') && (
377
+ <Command.Item onSelect={() => handleNavigate('/image')} value="painting">
378
+ <Palette className={styles.icon} />
379
+ <div className={styles.itemContent}>
380
+ <div className={styles.itemLabel}>{t('cmdk.painting')}</div>
381
+ </div>
382
+ </Command.Item>
383
+ )}
384
+ {!pathname?.startsWith('/knowledge') && (
385
+ <Command.Item onSelect={() => handleNavigate('/knowledge')} value="knowledge">
386
+ <BookOpen className={styles.icon} />
387
+ <div className={styles.itemContent}>
388
+ <div className={styles.itemLabel}>{t('cmdk.knowledgeBase')}</div>
389
+ </div>
390
+ </Command.Item>
391
+ )}
392
+ </Command.Group>
393
+
394
+ <Command.Group heading={t('cmdk.about')}>
395
+ <Command.Item
396
+ onSelect={() =>
397
+ handleExternalLink('https://github.com/lobehub/lobe-chat/issues/new/choose')
398
+ }
399
+ value="submit-issue"
400
+ >
401
+ <Github className={styles.icon} />
402
+ <div className={styles.itemContent}>
403
+ <div className={styles.itemLabel}>{t('cmdk.submitIssue')}</div>
404
+ </div>
405
+ </Command.Item>
406
+ <Command.Item
407
+ onSelect={() => handleExternalLink('https://github.com/lobehub/lobe-chat')}
408
+ value="star-github"
409
+ >
410
+ <Star className={styles.icon} />
411
+ <div className={styles.itemContent}>
412
+ <div className={styles.itemLabel}>{t('cmdk.starOnGitHub')}</div>
413
+ </div>
414
+ </Command.Item>
415
+ <Command.Item
416
+ onSelect={() => handleExternalLink('https://discord.gg/AYFPHvv2jT')}
417
+ value="discord"
418
+ >
419
+ <MessageCircle className={styles.icon} />
420
+ <div className={styles.itemContent}>
421
+ <div className={styles.itemLabel}>{t('cmdk.communitySupport')}</div>
422
+ </div>
423
+ </Command.Item>
424
+ </Command.Group>
425
+ </>
426
+ )}
427
+
428
+ {page === 'theme' && (
429
+ <>
430
+ <Command.Item onSelect={() => handleThemeChange('light')} value="theme-light">
431
+ <Sun className={styles.icon} />
432
+ <div className={styles.itemContent}>
433
+ <div className={styles.itemLabel}>{t('cmdk.themeLight')}</div>
434
+ </div>
435
+ </Command.Item>
436
+ <Command.Item onSelect={() => handleThemeChange('dark')} value="theme-dark">
437
+ <Moon className={styles.icon} />
438
+ <div className={styles.itemContent}>
439
+ <div className={styles.itemLabel}>{t('cmdk.themeDark')}</div>
440
+ </div>
441
+ </Command.Item>
442
+ <Command.Item onSelect={() => handleThemeChange('auto')} value="theme-auto">
443
+ <Monitor className={styles.icon} />
444
+ <div className={styles.itemContent}>
445
+ <div className={styles.itemLabel}>{t('cmdk.themeAuto')}</div>
446
+ </div>
447
+ </Command.Item>
448
+ </>
449
+ )}
450
+ </Command.List>
451
+ <div className={styles.commandFooter}>
452
+ <div className={styles.kbd}>
453
+ <CornerDownLeft className={styles.kbdIcon} />
454
+ <span>{t('cmdk.toOpen')}</span>
455
+ </div>
456
+ <div className={styles.kbd}>
457
+ <ArrowUpDown className={styles.kbdIcon} />
458
+ <span>{t('cmdk.toSelect')}</span>
459
+ </div>
460
+ </div>
461
+ </Command>
462
+ </div>
463
+ </div>,
464
+ document.body,
465
+ );
466
+ });
467
+
468
+ Cmdk.displayName = 'Cmdk';
469
+
470
+ export default Cmdk;
@@ -0,0 +1,17 @@
1
+ 'use client';
2
+
3
+ import dynamic from 'next/dynamic';
4
+ import { memo } from 'react';
5
+
6
+ // Lazy load the CMDK component with Next.js dynamic import
7
+ // This splits the CMDK code into a separate chunk that only loads when needed
8
+ // ssr: false ensures it only loads on the client side
9
+ const CmdkComponent = dynamic(() => import('./Cmdk'), {
10
+ ssr: false,
11
+ });
12
+
13
+ const CmdkLazy = memo(() => <CmdkComponent />);
14
+
15
+ CmdkLazy.displayName = 'CmdkLazy';
16
+
17
+ export default CmdkLazy;
@@ -10,6 +10,7 @@ import { getAntdLocale } from '@/utils/locale';
10
10
 
11
11
  import AntdV5MonkeyPatch from './AntdV5MonkeyPatch';
12
12
  import AppTheme from './AppTheme';
13
+ import CmdkLazy from './CmdkLazy';
13
14
  import ImportSettings from './ImportSettings';
14
15
  import Locale from './Locale';
15
16
  import QueryProvider from './Query';
@@ -65,6 +66,7 @@ const GlobalLayout = async ({
65
66
  <ImportSettings />
66
67
  {process.env.NODE_ENV === 'development' && <DevPanel />}
67
68
  </Suspense>
69
+ <CmdkLazy />
68
70
  </ServerConfigStoreProvider>
69
71
  </AppTheme>
70
72
  </Locale>
@@ -138,6 +138,27 @@ export default {
138
138
  },
139
139
  },
140
140
  close: '关闭',
141
+ cmdk: {
142
+ about: '关于',
143
+ communitySupport: '社区支持',
144
+ discover: '发现',
145
+ knowledgeBase: '知识库',
146
+ navigate: '导航',
147
+ newAgent: '新建助手',
148
+ noResults: '未找到相关结果',
149
+ openSettings: '打开设置',
150
+ painting: 'AI 绘画',
151
+ searchPlaceholder: '输入命令或搜索...',
152
+ settings: '设置',
153
+ starOnGitHub: '在 GitHub 上给我们 Star',
154
+ submitIssue: '提交问题',
155
+ theme: '主题',
156
+ themeAuto: '跟随系统',
157
+ themeDark: '深色模式',
158
+ themeLight: '浅色模式',
159
+ toOpen: '打开',
160
+ toSelect: '选择',
161
+ },
141
162
  confirm: '确认',
142
163
  contact: '联系我们',
143
164
  copy: '复制',
@@ -11,6 +11,10 @@ const hotkey: HotkeyI18nTranslations & {
11
11
  desc: '清空当前会话的消息和上传的文件',
12
12
  title: '清空会话消息',
13
13
  },
14
+ commandPalette: {
15
+ desc: '打开全局命令面板快速访问功能',
16
+ title: '命令面板',
17
+ },
14
18
  deleteAndRegenerateMessage: {
15
19
  desc: '删除最后一条消息并重新生成',
16
20
  title: '删除并重新生成',
@@ -3,6 +3,7 @@ import {
3
3
  AgentInstruction,
4
4
  AgentRuntimeContext,
5
5
  AgentState,
6
+ DEFAULT_SECURITY_BLACKLIST,
6
7
  GeneralAgentCallLLMInstructionPayload,
7
8
  GeneralAgentCallLLMResultPayload,
8
9
  GeneralAgentCallToolResultPayload,
@@ -65,6 +66,9 @@ export class GeneralChatAgent implements Agent {
65
66
  const toolsNeedingIntervention: ChatToolPayload[] = [];
66
67
  const toolsToExecute: ChatToolPayload[] = [];
67
68
 
69
+ // Get security blacklist (use default if not provided)
70
+ const securityBlacklist = state.securityBlacklist ?? DEFAULT_SECURITY_BLACKLIST;
71
+
68
72
  // Get user config (default to 'manual' mode)
69
73
  const userConfig = state.userInterventionConfig || { approvalMode: 'manual' };
70
74
  const { approvalMode, allowList = [] } = userConfig;
@@ -73,6 +77,23 @@ export class GeneralChatAgent implements Agent {
73
77
  const { identifier, apiName } = toolCalling;
74
78
  const toolKey = `${identifier}/${apiName}`;
75
79
 
80
+ // Parse arguments for intervention checking
81
+ let toolArgs: Record<string, any> = {};
82
+ try {
83
+ toolArgs = JSON.parse(toolCalling.arguments || '{}');
84
+ } catch {
85
+ // Invalid JSON, treat as empty args
86
+ }
87
+
88
+ // Priority 0: CRITICAL - Check security blacklist FIRST
89
+ // This overrides ALL other settings, including auto-run mode
90
+ const securityCheck = InterventionChecker.checkSecurityBlacklist(securityBlacklist, toolArgs);
91
+ if (securityCheck.blocked) {
92
+ // Security blacklist always requires intervention
93
+ toolsNeedingIntervention.push(toolCalling);
94
+ continue;
95
+ }
96
+
76
97
  // Priority 1: User config is 'auto-run', all tools execute directly
77
98
  if (approvalMode === 'auto-run') {
78
99
  toolsToExecute.push(toolCalling);
@@ -92,16 +113,9 @@ export class GeneralChatAgent implements Agent {
92
113
  // Priority 3: User config is 'manual' (default), use tool's own config
93
114
  const config = this.getToolInterventionConfig(toolCalling, state);
94
115
 
95
- // Parse arguments for intervention checking
96
- let toolArgs: Record<string, any> = {};
97
- try {
98
- toolArgs = JSON.parse(toolCalling.arguments || '{}');
99
- } catch {
100
- // Invalid JSON, treat as empty args
101
- }
102
-
103
116
  const policy = InterventionChecker.shouldIntervene({
104
117
  config,
118
+ securityBlacklist,
105
119
  toolArgs,
106
120
  });
107
121