@lobehub/lobehub 2.0.0-next.48 → 2.0.0-next.49

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 (90) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/changelog/v1.json +12 -0
  5. package/locales/ar/chat.json +1 -0
  6. package/locales/ar/topic.json +1 -0
  7. package/locales/bg-BG/chat.json +1 -0
  8. package/locales/bg-BG/topic.json +1 -0
  9. package/locales/de-DE/chat.json +1 -0
  10. package/locales/de-DE/topic.json +1 -0
  11. package/locales/en-US/chat.json +1 -0
  12. package/locales/en-US/topic.json +1 -0
  13. package/locales/es-ES/chat.json +1 -0
  14. package/locales/es-ES/topic.json +1 -0
  15. package/locales/fa-IR/chat.json +1 -0
  16. package/locales/fa-IR/topic.json +1 -0
  17. package/locales/fr-FR/chat.json +1 -0
  18. package/locales/fr-FR/topic.json +1 -0
  19. package/locales/it-IT/chat.json +1 -0
  20. package/locales/it-IT/topic.json +1 -0
  21. package/locales/ja-JP/chat.json +1 -0
  22. package/locales/ja-JP/topic.json +1 -0
  23. package/locales/ko-KR/chat.json +1 -0
  24. package/locales/ko-KR/topic.json +1 -0
  25. package/locales/nl-NL/chat.json +1 -0
  26. package/locales/nl-NL/topic.json +1 -0
  27. package/locales/pl-PL/chat.json +1 -0
  28. package/locales/pl-PL/topic.json +1 -0
  29. package/locales/pt-BR/chat.json +1 -0
  30. package/locales/pt-BR/topic.json +1 -0
  31. package/locales/ru-RU/chat.json +1 -0
  32. package/locales/ru-RU/topic.json +1 -0
  33. package/locales/tr-TR/chat.json +1 -0
  34. package/locales/tr-TR/topic.json +1 -0
  35. package/locales/vi-VN/chat.json +1 -0
  36. package/locales/vi-VN/topic.json +1 -0
  37. package/locales/zh-CN/chat.json +1 -0
  38. package/locales/zh-CN/discover.json +1 -1
  39. package/locales/zh-CN/topic.json +1 -0
  40. package/locales/zh-TW/chat.json +1 -0
  41. package/locales/zh-TW/topic.json +1 -0
  42. package/package.json +9 -3
  43. package/packages/agent-runtime/src/core/InterventionChecker.ts +5 -16
  44. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +27 -80
  45. package/packages/agent-runtime/src/core/__tests__/runtime.test.ts +32 -13
  46. package/packages/agent-runtime/src/core/runtime.ts +7 -3
  47. package/packages/agent-runtime/src/types/event.ts +2 -1
  48. package/packages/agent-runtime/src/types/generalAgent.ts +1 -0
  49. package/packages/agent-runtime/src/types/instruction.ts +3 -2
  50. package/packages/agent-runtime/src/types/state.ts +3 -1
  51. package/packages/conversation-flow/src/transformation/FlatListBuilder.ts +4 -1
  52. package/packages/database/src/models/message.ts +3 -0
  53. package/packages/obervability-otel/src/node.ts +15 -1
  54. package/packages/types/src/message/common/base.ts +2 -2
  55. package/packages/types/src/message/common/tools.ts +16 -10
  56. package/packages/types/src/message/ui/chat.ts +7 -1
  57. package/packages/types/src/tool/intervention.ts +2 -3
  58. package/packages/types/src/user/settings/tool.ts +15 -28
  59. package/renovate.json +28 -11
  60. package/src/app/[variants]/(main)/chat/components/topic/features/Topic/TopicListContent/TopicItem/TopicContent.tsx +1 -1
  61. package/src/app/[variants]/(main)/chat/session/features/SessionListContent/List/Item/Actions.tsx +1 -1
  62. package/src/features/Conversation/Messages/Group/GroupChildren.tsx +20 -15
  63. package/src/features/Conversation/Messages/Group/GroupContext.tsx +15 -0
  64. package/src/features/Conversation/Messages/Group/Tool/Inspector/BuiltinPluginTitle.tsx +2 -4
  65. package/src/features/Conversation/Messages/Group/Tool/Inspector/ToolTitle.tsx +3 -5
  66. package/src/features/Conversation/Messages/Group/Tool/Inspector/index.tsx +19 -7
  67. package/src/features/Conversation/Messages/Group/Tool/Render/Arguments/index.tsx +14 -12
  68. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ApprovalActions.tsx +143 -0
  69. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/KeyValueEditor.tsx +213 -0
  70. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +134 -0
  71. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +99 -0
  72. package/src/features/Conversation/Messages/Group/Tool/Render/RejectedResponse.tsx +45 -0
  73. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +23 -1
  74. package/src/features/Conversation/Messages/Group/Tool/index.tsx +42 -18
  75. package/src/features/Conversation/Messages/Group/Tools.tsx +3 -1
  76. package/src/locales/default/chat.ts +22 -0
  77. package/src/locales/default/common.ts +1 -0
  78. package/src/locales/default/topic.ts +1 -0
  79. package/src/server/routers/lambda/message.ts +4 -1
  80. package/src/server/services/message/index.ts +13 -0
  81. package/src/services/message/index.ts +17 -2
  82. package/src/store/chat/agents/GeneralChatAgent.ts +141 -24
  83. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +605 -0
  84. package/src/store/chat/agents/createAgentExecutors.ts +144 -26
  85. package/src/store/chat/agents/createToolEngine.ts +22 -0
  86. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +106 -0
  87. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +54 -26
  88. package/src/store/chat/slices/message/reducer.ts +2 -1
  89. package/src/store/chat/slices/plugin/actions/optimisticUpdate.ts +26 -1
  90. package/src/store/user/slices/settings/action.ts +15 -0
@@ -12,43 +12,16 @@ describe('InterventionChecker', () => {
12
12
 
13
13
  it('should return the policy when config is a simple string', () => {
14
14
  expect(InterventionChecker.shouldIntervene({ config: 'never', toolArgs: {} })).toBe('never');
15
- expect(InterventionChecker.shouldIntervene({ config: 'always', toolArgs: {} })).toBe(
16
- 'always',
15
+ expect(InterventionChecker.shouldIntervene({ config: 'require', toolArgs: {} })).toBe(
16
+ 'require',
17
17
  );
18
- expect(InterventionChecker.shouldIntervene({ config: 'first', toolArgs: {} })).toBe('first');
19
- });
20
-
21
- it('should handle "first" policy with confirmed history', () => {
22
- const toolKey = 'web-browsing/crawlSinglePage';
23
- const confirmedHistory = [toolKey];
24
-
25
- const result = InterventionChecker.shouldIntervene({
26
- config: 'first',
27
- toolArgs: {},
28
- confirmedHistory,
29
- toolKey,
30
- });
31
- expect(result).toBe('never');
32
- });
33
-
34
- it('should require intervention for "first" policy without confirmation', () => {
35
- const toolKey = 'web-browsing/crawlSinglePage';
36
- const confirmedHistory: string[] = [];
37
-
38
- const result = InterventionChecker.shouldIntervene({
39
- config: 'first',
40
- toolArgs: {},
41
- confirmedHistory,
42
- toolKey,
43
- });
44
- expect(result).toBe('first');
45
18
  });
46
19
 
47
20
  it('should match rules in order and return first match', () => {
48
21
  const config: HumanInterventionConfig = [
49
22
  { match: { command: 'ls:*' }, policy: 'never' },
50
- { match: { command: 'git commit:*' }, policy: 'first' },
51
- { policy: 'always' }, // Default rule
23
+ { match: { command: 'git commit:*' }, policy: 'require' },
24
+ { policy: 'require' }, // Default rule
52
25
  ];
53
26
 
54
27
  expect(InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'ls:' } })).toBe(
@@ -56,20 +29,20 @@ describe('InterventionChecker', () => {
56
29
  );
57
30
  expect(
58
31
  InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'git commit:' } }),
59
- ).toBe('first');
32
+ ).toBe('require');
60
33
  expect(
61
34
  InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'rm -rf /' } }),
62
- ).toBe('always');
35
+ ).toBe('require');
63
36
  });
64
37
 
65
- it('should return always as default when no rule matches', () => {
38
+ it('should return require as default when no rule matches', () => {
66
39
  const config: HumanInterventionConfig = [{ match: { command: 'ls:*' }, policy: 'never' }];
67
40
 
68
41
  const result = InterventionChecker.shouldIntervene({
69
42
  config,
70
43
  toolArgs: { command: 'rm -rf /' },
71
44
  });
72
- expect(result).toBe('always');
45
+ expect(result).toBe('require');
73
46
  });
74
47
 
75
48
  it('should handle multiple parameter matching', () => {
@@ -81,7 +54,7 @@ describe('InterventionChecker', () => {
81
54
  },
82
55
  policy: 'never',
83
56
  },
84
- { policy: 'always' },
57
+ { policy: 'require' },
85
58
  ];
86
59
 
87
60
  // Both match
@@ -104,20 +77,20 @@ describe('InterventionChecker', () => {
104
77
  path: '/tmp/file.ts',
105
78
  },
106
79
  }),
107
- ).toBe('always');
80
+ ).toBe('require');
108
81
  });
109
82
 
110
83
  it('should handle default rule without match', () => {
111
84
  const config: HumanInterventionConfig = [
112
85
  { match: { command: 'ls:*' }, policy: 'never' },
113
- { policy: 'first' }, // Default rule
86
+ { policy: 'require' }, // Default rule
114
87
  ];
115
88
 
116
89
  const result = InterventionChecker.shouldIntervene({
117
90
  config,
118
91
  toolArgs: { command: 'anything' },
119
92
  });
120
- expect(result).toBe('first');
93
+ expect(result).toBe('require');
121
94
  });
122
95
  });
123
96
 
@@ -249,10 +222,10 @@ describe('InterventionChecker', () => {
249
222
  it('should handle Bash tool scenario', () => {
250
223
  const config: HumanInterventionConfig = [
251
224
  { match: { command: 'ls:*' }, policy: 'never' },
252
- { match: { command: 'git add:*' }, policy: 'first' },
253
- { match: { command: 'git commit:*' }, policy: 'first' },
254
- { match: { command: 'rm:*' }, policy: 'always' },
255
- { policy: 'always' },
225
+ { match: { command: 'git add:*' }, policy: 'require' },
226
+ { match: { command: 'git commit:*' }, policy: 'require' },
227
+ { match: { command: 'rm:*' }, policy: 'require' },
228
+ { policy: 'require' },
256
229
  ];
257
230
 
258
231
  // Safe commands - never
@@ -260,27 +233,27 @@ describe('InterventionChecker', () => {
260
233
  'never',
261
234
  );
262
235
 
263
- // Git commands - first
236
+ // Git commands - require
264
237
  expect(
265
238
  InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'git add:.' } }),
266
- ).toBe('first');
239
+ ).toBe('require');
267
240
  expect(
268
241
  InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'git commit:-m' } }),
269
- ).toBe('first');
242
+ ).toBe('require');
270
243
 
271
- // Dangerous commands - always
244
+ // Dangerous commands - require
272
245
  expect(InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'rm:-rf' } })).toBe(
273
- 'always',
246
+ 'require',
274
247
  );
275
248
  expect(
276
249
  InterventionChecker.shouldIntervene({ config, toolArgs: { command: 'npm install' } }),
277
- ).toBe('always');
250
+ ).toBe('require');
278
251
  });
279
252
 
280
253
  it('should handle LocalSystem tool scenario', () => {
281
254
  const config: HumanInterventionConfig = [
282
255
  { match: { path: '/Users/project/*' }, policy: 'never' },
283
- { policy: 'first' },
256
+ { policy: 'require' },
284
257
  ];
285
258
 
286
259
  // Project directory - never
@@ -291,44 +264,18 @@ describe('InterventionChecker', () => {
291
264
  }),
292
265
  ).toBe('never');
293
266
 
294
- // Outside project - first
267
+ // Outside project - require
295
268
  expect(
296
269
  InterventionChecker.shouldIntervene({ config, toolArgs: { path: '/tmp/file.ts' } }),
297
- ).toBe('first');
270
+ ).toBe('require');
298
271
  });
299
272
 
300
273
  it('should handle Web Browsing tool with simple policy', () => {
301
- const config: HumanInterventionConfig = 'always';
274
+ const config: HumanInterventionConfig = 'require';
302
275
 
303
276
  expect(
304
277
  InterventionChecker.shouldIntervene({ config, toolArgs: { url: 'https://example.com' } }),
305
- ).toBe('always');
306
- });
307
-
308
- it('should handle first policy with confirmation history', () => {
309
- const config: HumanInterventionConfig = [
310
- { match: { command: 'git add:*' }, policy: 'first' },
311
- { policy: 'always' },
312
- ];
313
-
314
- const toolKey = 'bash/bash#abc123';
315
- const args = { command: 'git add:.' };
316
-
317
- // First time - requires intervention
318
- expect(
319
- InterventionChecker.shouldIntervene({
320
- config,
321
- toolArgs: args,
322
- confirmedHistory: [],
323
- toolKey,
324
- }),
325
- ).toBe('first');
326
-
327
- // After confirmation - never
328
- const confirmedHistory = [toolKey];
329
- expect(
330
- InterventionChecker.shouldIntervene({ config, toolArgs: args, confirmedHistory, toolKey }),
331
- ).toBe('never');
278
+ ).toBe('require');
332
279
  });
333
280
  });
334
281
  });
@@ -1,3 +1,4 @@
1
+ import { ChatToolPayload } from '@lobechat/types';
1
2
  import { describe, expect, it, vi } from 'vitest';
2
3
 
3
4
  import {
@@ -344,9 +345,11 @@ describe('AgentRuntime', () => {
344
345
  type: 'request_human_approve',
345
346
  pendingToolsCalling: [
346
347
  {
348
+ apiName: 'test_tool',
349
+ arguments: '{}',
347
350
  id: 'call_123',
348
- type: 'function',
349
- function: { name: 'test_tool', arguments: '{}' },
351
+ identifier: 'test_tool',
352
+ type: 'default',
350
353
  },
351
354
  ],
352
355
  }),
@@ -357,14 +360,11 @@ describe('AgentRuntime', () => {
357
360
 
358
361
  const result = await runtime.step(state);
359
362
 
360
- expect(result.events).toHaveLength(2);
363
+ expect(result.events).toHaveLength(1);
361
364
  expect(result.events[0]).toMatchObject({
362
365
  type: 'human_approve_required',
363
366
  sessionId: 'test-session',
364
367
  });
365
- expect(result.events[1]).toMatchObject({
366
- type: 'tool_pending',
367
- });
368
368
 
369
369
  expect(result.newState.status).toBe('waiting_for_human');
370
370
  expect(result.newState.pendingToolsCalling).toBeDefined();
@@ -947,12 +947,29 @@ describe('AgentRuntime', () => {
947
947
  case 'llm_result':
948
948
  const llmPayload = context.payload as { result: any; hasToolCalls: boolean };
949
949
  if (llmPayload.hasToolCalls) {
950
+ // Convert OpenAI format tool_calls to ChatToolPayload format
951
+ const pendingToolsCalling = llmPayload.result.tool_calls.map((tc: any) => ({
952
+ apiName: tc.function.name,
953
+ arguments: tc.function.arguments,
954
+ id: tc.id,
955
+ identifier: tc.function.name,
956
+ type: 'default' as const,
957
+ }));
950
958
  return Promise.resolve({
959
+ pendingToolsCalling,
951
960
  type: 'request_human_approve',
952
- pendingToolsCalling: llmPayload.result.tool_calls,
953
961
  });
954
962
  }
955
963
  return Promise.resolve({ type: 'finish', reason: 'completed', reasonDetail: 'Done' });
964
+ case 'human_approved_tool':
965
+ const approvedPayload = context.payload as { approvedToolCall: ChatToolPayload };
966
+ return Promise.resolve({
967
+ payload: {
968
+ parentMessageId: 'user-msg-id',
969
+ toolCalling: approvedPayload.approvedToolCall,
970
+ },
971
+ type: 'call_tool',
972
+ });
956
973
  case 'tool_result':
957
974
  return Promise.resolve({ type: 'call_llm', payload: { messages: state.messages } });
958
975
  default:
@@ -1021,10 +1038,10 @@ describe('AgentRuntime', () => {
1021
1038
  // Step 2: Approve and execute tool call
1022
1039
  const pendingToolCall = result.newState.pendingToolsCalling![0];
1023
1040
  const toolCall = {
1041
+ apiName: pendingToolCall.apiName,
1042
+ arguments: pendingToolCall.arguments,
1024
1043
  id: pendingToolCall.id,
1025
- apiName: pendingToolCall.function.name,
1026
- identifier: pendingToolCall.function.name,
1027
- arguments: pendingToolCall.function.arguments,
1044
+ identifier: pendingToolCall.identifier,
1028
1045
  type: 'default' as const,
1029
1046
  };
1030
1047
  result = await runtime.approveToolCall(result.newState, toolCall);
@@ -1203,9 +1220,11 @@ describe('AgentRuntime', () => {
1203
1220
  {
1204
1221
  pendingToolsCalling: [
1205
1222
  {
1223
+ apiName: 'danger_tool',
1224
+ arguments: '{}',
1206
1225
  id: 'call_danger',
1207
- type: 'function' as const,
1208
- function: { name: 'danger_tool', arguments: '{}' },
1226
+ identifier: 'danger_tool',
1227
+ type: 'default' as const,
1209
1228
  },
1210
1229
  ],
1211
1230
  type: 'request_human_approve' as const,
@@ -1233,7 +1252,7 @@ describe('AgentRuntime', () => {
1233
1252
 
1234
1253
  // Should have pending tool calls
1235
1254
  expect(result.newState.pendingToolsCalling).toHaveLength(1);
1236
- expect(result.newState.pendingToolsCalling![0].function.name).toBe('danger_tool');
1255
+ expect(result.newState.pendingToolsCalling![0].apiName).toBe('danger_tool');
1237
1256
 
1238
1257
  // Should have both tool_result and human_approve_required events
1239
1258
  expect(result.events).toContainEqual(expect.objectContaining({ type: 'tool_result' }));
@@ -85,12 +85,17 @@ export class AgentRuntime {
85
85
 
86
86
  // Handle human approved tool calls
87
87
  if (runtimeContext.phase === 'human_approved_tool') {
88
- const approvedPayload = runtimeContext.payload as { approvedToolCall: ChatToolPayload };
88
+ const approvedPayload = runtimeContext.payload as {
89
+ approvedToolCall: ChatToolPayload;
90
+ parentMessageId: string;
91
+ skipCreateToolMessage: boolean;
92
+ };
89
93
  const toolCalling = approvedPayload.approvedToolCall;
90
94
 
91
95
  rawInstructions = {
92
96
  payload: {
93
- parentMessageId: '', // Not required for approval flow
97
+ parentMessageId: approvedPayload.parentMessageId,
98
+ skipCreateToolMessage: approvedPayload.skipCreateToolMessage,
94
99
  toolCalling,
95
100
  },
96
101
  type: 'call_tool',
@@ -538,7 +543,6 @@ export class AgentRuntime {
538
543
  sessionId: newState.sessionId,
539
544
  type: 'human_approve_required',
540
545
  },
541
- { toolCalls: pendingToolsCalling, type: 'tool_pending' },
542
546
  ];
543
547
 
544
548
  return { events, newState };
@@ -1,5 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
2
  import type { AgentState, ToolsCalling } from './state';
3
+ import { ChatToolPayload } from '@/types/message';
3
4
 
4
5
  export interface AgentEventInit {
5
6
  type: 'init';
@@ -33,7 +34,7 @@ export interface AgentEventToolResult {
33
34
 
34
35
  export interface AgentEventHumanApproveRequired {
35
36
  type: 'human_approve_required';
36
- pendingToolsCalling: ToolsCalling[];
37
+ pendingToolsCalling: ChatToolPayload[];
37
38
  sessionId: string;
38
39
  }
39
40
 
@@ -18,6 +18,7 @@ export interface GeneralAgentCallLLMResultPayload {
18
18
 
19
19
  export interface GeneralAgentCallingToolInstructionPayload {
20
20
  parentMessageId: string;
21
+ skipCreateToolMessage?: boolean;
21
22
  toolCalling: ChatToolPayload;
22
23
  }
23
24
 
@@ -1,7 +1,7 @@
1
1
  import { ChatToolPayload, ModelUsage } from '@lobechat/types';
2
2
 
3
3
  import type { FinishReason } from './event';
4
- import { AgentState, ToolRegistry, ToolsCalling } from './state';
4
+ import { AgentState, ToolRegistry } from './state';
5
5
  import type { Cost, CostCalculationContext, Usage } from './usage';
6
6
 
7
7
  /**
@@ -137,8 +137,9 @@ export interface AgentInstructionRequestHumanSelect {
137
137
  }
138
138
 
139
139
  export interface AgentInstructionRequestHumanApprove {
140
- pendingToolsCalling: ToolsCalling[];
140
+ pendingToolsCalling: ChatToolPayload[];
141
141
  reason?: string;
142
+ skipCreateToolMessage?: boolean;
142
143
  type: 'request_human_approve';
143
144
  }
144
145
 
@@ -1,4 +1,6 @@
1
1
  /* eslint-disable sort-keys-fix/sort-keys-fix, typescript-sort-keys/interface */
2
+ import { ChatToolPayload } from '@/types/message';
3
+
2
4
  import type { Cost, CostLimit, Usage } from './usage';
3
5
 
4
6
  /**
@@ -49,7 +51,7 @@ export interface AgentState {
49
51
  * When status is 'waiting_for_human', this stores pending requests
50
52
  * for human-in-the-loop operations.
51
53
  */
52
- pendingToolsCalling?: ToolsCalling[];
54
+ pendingToolsCalling?: ChatToolPayload[];
53
55
  pendingHumanPrompt?: { metadata?: Record<string, unknown>; prompt: string };
54
56
  pendingHumanSelect?: {
55
57
  metadata?: Record<string, unknown>;
@@ -427,11 +427,14 @@ export class FlatListBuilder {
427
427
  if (toolMsg.error) result.error = toolMsg.error;
428
428
  if (toolMsg.pluginState) result.state = toolMsg.pluginState;
429
429
 
430
- return {
430
+ const toolWithResult: ChatToolPayloadWithResult = {
431
431
  ...tool,
432
+ intervention: toolMsg.pluginIntervention,
432
433
  result,
433
434
  result_msg_id: toolMsg.id,
434
435
  };
436
+
437
+ return toolWithResult;
435
438
  }
436
439
  return tool;
437
440
  }) || [];
@@ -97,6 +97,7 @@ export class MessageModel {
97
97
  type: messagePlugins.type,
98
98
  },
99
99
  pluginError: messagePlugins.error,
100
+ pluginIntervention: messagePlugins.intervention,
100
101
  pluginState: messagePlugins.state,
101
102
 
102
103
  translate: {
@@ -462,6 +463,7 @@ export class MessageModel {
462
463
  provider: fromProvider,
463
464
  files,
464
465
  plugin,
466
+ pluginIntervention,
465
467
  pluginState,
466
468
  fileChunks,
467
469
  ragQueryId,
@@ -496,6 +498,7 @@ export class MessageModel {
496
498
  arguments: plugin?.arguments,
497
499
  id,
498
500
  identifier: plugin?.identifier,
501
+ intervention: pluginIntervention,
499
502
  state: pluginState,
500
503
  toolCallId: message.tool_call_id,
501
504
  type: plugin?.type,
@@ -8,6 +8,7 @@ import { resourceFromAttributes } from '@opentelemetry/resources';
8
8
  import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
9
9
  import { NodeSDK } from '@opentelemetry/sdk-node';
10
10
  import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
11
+ import { env } from 'node:process';
11
12
 
12
13
  export function register(options?: { debug?: true | DiagLogLevel; version?: string }) {
13
14
  const attributes: Record<string, string> = {
@@ -23,13 +24,26 @@ export function register(options?: { debug?: true | DiagLogLevel; version?: stri
23
24
  );
24
25
  }
25
26
 
27
+ let metricsExporterInterval = 1000;
28
+ if (env.OTEL_METRICS_EXPORTER_INTERVAL) {
29
+ const parsed = parseInt(env.OTEL_METRICS_EXPORTER_INTERVAL, 10);
30
+ if (!isNaN(parsed)) {
31
+ metricsExporterInterval = parsed;
32
+ }
33
+ }
34
+
26
35
  const sdk = new NodeSDK({
27
36
  instrumentations: [
28
37
  new PgInstrumentation(),
29
38
  new HttpInstrumentation(),
30
39
  getNodeAutoInstrumentations(),
31
40
  ],
32
- metricReaders: [new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter() })],
41
+ metricReaders: [
42
+ new PeriodicExportingMetricReader({
43
+ exportIntervalMillis: metricsExporterInterval,
44
+ exporter: new OTLPMetricExporter(),
45
+ }),
46
+ ],
33
47
  resource: resourceFromAttributes(attributes),
34
48
  traceExporter: new OTLPTraceExporter(),
35
49
  });
@@ -9,13 +9,13 @@ import { ErrorType } from '../../fetch';
9
9
  */
10
10
  export interface ChatMessageError {
11
11
  body?: any;
12
- message: string;
12
+ message?: string;
13
13
  type: ErrorType | IPluginErrorType | ILobeAgentRuntimeErrorType;
14
14
  }
15
15
 
16
16
  export const ChatMessageErrorSchema = z.object({
17
17
  body: z.any().optional(),
18
- message: z.string(),
18
+ message: z.string().optional(),
19
19
  type: z.union([z.string(), z.number()]),
20
20
  });
21
21
 
@@ -4,10 +4,22 @@ import { z } from 'zod';
4
4
 
5
5
  import { LobeToolRenderType } from '../../tool';
6
6
 
7
+ // ToolIntervention must be defined first to avoid circular dependency
8
+ export interface ToolIntervention {
9
+ rejectedReason?: string;
10
+ status?: 'pending' | 'approved' | 'rejected' | 'none';
11
+ }
12
+
13
+ export const ToolInterventionSchema = z.object({
14
+ rejectedReason: z.string().optional(),
15
+ status: z.enum(['pending', 'approved', 'rejected', 'none']).optional(),
16
+ });
17
+
7
18
  export interface ChatPluginPayload {
8
19
  apiName: string;
9
20
  arguments: string;
10
21
  identifier: string;
22
+ intervention?: ToolIntervention;
11
23
  type: LobeToolRenderType;
12
24
  }
13
25
 
@@ -16,6 +28,7 @@ export interface ChatToolPayload {
16
28
  arguments: string;
17
29
  id: string;
18
30
  identifier: string;
31
+ intervention?: ToolIntervention;
19
32
  result_msg_id?: string;
20
33
  type: LobeToolRenderType;
21
34
  }
@@ -34,7 +47,9 @@ export interface ChatToolResult {
34
47
  * Chat tool payload with merged execution result
35
48
  */
36
49
  export interface ChatToolPayloadWithResult extends ChatToolPayload {
50
+ intervention?: ToolIntervention;
37
51
  result?: ChatToolResult;
52
+ result_msg_id?: string;
38
53
  }
39
54
 
40
55
  export interface ToolsCallingContext {
@@ -91,6 +106,7 @@ export const ChatToolPayloadSchema = z.object({
91
106
  arguments: z.string(),
92
107
  id: z.string(),
93
108
  identifier: z.string(),
109
+ intervention: ToolInterventionSchema.optional(),
94
110
  result_msg_id: z.string().optional(),
95
111
  type: z.string(),
96
112
  });
@@ -103,13 +119,3 @@ export interface ChatMessagePluginError {
103
119
  message: string;
104
120
  type: IPluginErrorType;
105
121
  }
106
-
107
- export interface ToolIntervention {
108
- rejectedReason?: string;
109
- status?: 'pending' | 'approved' | 'rejected' | 'none';
110
- }
111
-
112
- export const ToolInterventionSchema = z.object({
113
- rejectedReason: z.string().optional(),
114
- status: z.enum(['pending', 'approved', 'rejected', 'none']).optional(),
115
- });
@@ -8,7 +8,12 @@ import {
8
8
  ModelReasoning,
9
9
  ModelUsage,
10
10
  } from '../common';
11
- import { ChatPluginPayload, ChatToolPayload, ChatToolPayloadWithResult } from '../common/tools';
11
+ import {
12
+ ChatPluginPayload,
13
+ ChatToolPayload,
14
+ ChatToolPayloadWithResult,
15
+ ToolIntervention,
16
+ } from '../common/tools';
12
17
  import { ChatMessageExtra } from './extra';
13
18
  import { ChatFileChunk } from './rag';
14
19
  import { ChatVideoItem } from './video';
@@ -95,6 +100,7 @@ export interface UIChatMessage {
95
100
  performance?: ModelPerformance;
96
101
  plugin?: ChatPluginPayload;
97
102
  pluginError?: any;
103
+ pluginIntervention?: ToolIntervention;
98
104
  pluginState?: any;
99
105
  provider?: string | null;
100
106
  /**
@@ -5,10 +5,9 @@ import { z } from 'zod';
5
5
  */
6
6
  export type HumanInterventionPolicy =
7
7
  | 'never' // Never intervene, auto-execute
8
- | 'always' // Always require intervention
9
- | 'first'; // Require intervention on first call only
8
+ | 'require'; // Always require intervention
10
9
 
11
- export const HumanInterventionPolicySchema = z.enum(['never', 'always', 'first']);
10
+ export const HumanInterventionPolicySchema = z.enum(['never', 'require']);
12
11
 
13
12
  /**
14
13
  * Argument Matcher for parameter-level filtering
@@ -1,42 +1,29 @@
1
- import type { HumanInterventionConfig } from '../../tool';
2
-
3
1
  export interface UserToolConfig {
2
+ /**
3
+ * Tool approval mode
4
+ * - auto-run: Automatically approve all tools without user consent
5
+ * - allow-list: Only approve tools in the allow list
6
+ * - manual: Require manual approval for each tool execution
7
+ */
8
+ approvalMode?: 'auto-run' | 'allow-list' | 'manual';
9
+
4
10
  dalle: {
5
11
  autoGenerate: boolean;
6
12
  };
13
+
7
14
  /**
8
- * Human intervention configuration
15
+ * Tool intervention configuration
9
16
  */
10
17
  humanIntervention?: {
11
18
  /**
12
- * List of confirmed tool calls (for 'once' policy)
13
- * Format: "identifier/apiName" or "identifier/apiName#argsHash"
19
+ * Allow list of approved tools (used in 'allow-list' mode)
20
+ * Format: "identifier/apiName"
14
21
  *
15
22
  * Examples:
16
23
  * - "web-browsing/crawlSinglePage"
17
- * - "bash/bash#a1b2c3d4"
18
- */
19
- confirmed?: string[];
20
-
21
- /**
22
- * Whether human intervention is enabled globally
23
- * @default true
24
- */
25
- enabled: boolean;
26
-
27
- /**
28
- * Per-tool intervention policy overrides
29
- * Key format: "identifier/apiName"
30
- *
31
- * Example:
32
- * {
33
- * "web-browsing/crawlSinglePage": "confirm",
34
- * "bash/bash": [
35
- * { match: { command: "git add:*" }, policy: "auto" },
36
- * { policy: "confirm" }
37
- * ]
38
- * }
24
+ * - "bash/bash"
25
+ * - "search/search"
39
26
  */
40
- overrides?: Record<string, HumanInterventionConfig>;
27
+ allowList?: string[];
41
28
  };
42
29
  }