@lobehub/lobehub 2.0.0-next.50 → 2.0.0-next.51

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 (118) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/apps/desktop/src/main/controllers/ShellCommandCtr.ts +242 -0
  3. package/apps/desktop/src/main/controllers/__tests__/ShellCommandCtr.test.ts +499 -0
  4. package/changelog/v1.json +9 -0
  5. package/locales/ar/chat.json +20 -0
  6. package/locales/ar/common.json +1 -0
  7. package/locales/ar/components.json +6 -0
  8. package/locales/ar/plugin.json +1 -0
  9. package/locales/bg-BG/chat.json +20 -0
  10. package/locales/bg-BG/common.json +1 -0
  11. package/locales/bg-BG/components.json +6 -0
  12. package/locales/bg-BG/plugin.json +1 -0
  13. package/locales/de-DE/chat.json +20 -0
  14. package/locales/de-DE/common.json +1 -0
  15. package/locales/de-DE/components.json +6 -0
  16. package/locales/de-DE/plugin.json +1 -0
  17. package/locales/en-US/chat.json +20 -0
  18. package/locales/en-US/common.json +1 -0
  19. package/locales/en-US/components.json +6 -0
  20. package/locales/en-US/plugin.json +1 -0
  21. package/locales/es-ES/chat.json +20 -0
  22. package/locales/es-ES/common.json +1 -0
  23. package/locales/es-ES/components.json +6 -0
  24. package/locales/es-ES/plugin.json +1 -0
  25. package/locales/fa-IR/chat.json +20 -0
  26. package/locales/fa-IR/common.json +1 -0
  27. package/locales/fa-IR/components.json +6 -0
  28. package/locales/fa-IR/plugin.json +1 -0
  29. package/locales/fr-FR/chat.json +20 -0
  30. package/locales/fr-FR/common.json +1 -0
  31. package/locales/fr-FR/components.json +6 -0
  32. package/locales/fr-FR/plugin.json +1 -0
  33. package/locales/it-IT/chat.json +20 -0
  34. package/locales/it-IT/common.json +1 -0
  35. package/locales/it-IT/components.json +6 -0
  36. package/locales/it-IT/plugin.json +1 -0
  37. package/locales/ja-JP/chat.json +20 -0
  38. package/locales/ja-JP/common.json +1 -0
  39. package/locales/ja-JP/components.json +6 -0
  40. package/locales/ja-JP/plugin.json +1 -0
  41. package/locales/ko-KR/chat.json +20 -0
  42. package/locales/ko-KR/common.json +1 -0
  43. package/locales/ko-KR/components.json +6 -0
  44. package/locales/ko-KR/plugin.json +1 -0
  45. package/locales/nl-NL/chat.json +20 -0
  46. package/locales/nl-NL/common.json +1 -0
  47. package/locales/nl-NL/components.json +6 -0
  48. package/locales/nl-NL/plugin.json +1 -0
  49. package/locales/pl-PL/chat.json +20 -0
  50. package/locales/pl-PL/common.json +1 -0
  51. package/locales/pl-PL/components.json +6 -0
  52. package/locales/pl-PL/plugin.json +1 -0
  53. package/locales/pt-BR/chat.json +20 -0
  54. package/locales/pt-BR/common.json +1 -0
  55. package/locales/pt-BR/components.json +6 -0
  56. package/locales/pt-BR/plugin.json +1 -0
  57. package/locales/ru-RU/chat.json +20 -0
  58. package/locales/ru-RU/common.json +1 -0
  59. package/locales/ru-RU/components.json +6 -0
  60. package/locales/ru-RU/plugin.json +1 -0
  61. package/locales/tr-TR/chat.json +20 -0
  62. package/locales/tr-TR/common.json +1 -0
  63. package/locales/tr-TR/components.json +6 -0
  64. package/locales/tr-TR/plugin.json +1 -0
  65. package/locales/vi-VN/chat.json +20 -0
  66. package/locales/vi-VN/common.json +1 -0
  67. package/locales/vi-VN/components.json +6 -0
  68. package/locales/vi-VN/plugin.json +1 -0
  69. package/locales/zh-CN/chat.json +20 -0
  70. package/locales/zh-CN/common.json +1 -0
  71. package/locales/zh-CN/components.json +6 -0
  72. package/locales/zh-CN/plugin.json +1 -0
  73. package/locales/zh-TW/chat.json +20 -0
  74. package/locales/zh-TW/common.json +1 -0
  75. package/locales/zh-TW/components.json +6 -0
  76. package/locales/zh-TW/plugin.json +1 -0
  77. package/package.json +1 -1
  78. package/packages/agent-runtime/src/core/InterventionChecker.ts +1 -1
  79. package/packages/agent-runtime/src/core/__tests__/InterventionChecker.test.ts +23 -23
  80. package/packages/agent-runtime/src/types/state.ts +7 -1
  81. package/packages/const/src/settings/tool.ts +1 -5
  82. package/packages/file-loaders/src/loaders/docx/index.ts +1 -1
  83. package/packages/model-bank/src/aiModels/wenxin.ts +1348 -291
  84. package/packages/model-runtime/src/providers/wenxin/index.ts +22 -1
  85. package/packages/model-runtime/src/utils/modelParse.ts +6 -0
  86. package/packages/types/src/tool/builtin.ts +9 -0
  87. package/packages/types/src/tool/intervention.ts +32 -2
  88. package/packages/types/src/user/settings/tool.ts +3 -27
  89. package/src/config/modelProviders/wenxin.ts +2 -3
  90. package/src/features/Conversation/MarkdownElements/remarkPlugins/__snapshots__/createRemarkSelfClosingTagPlugin.test.ts.snap +133 -0
  91. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.test.ts +48 -0
  92. package/src/features/Conversation/MarkdownElements/remarkPlugins/createRemarkSelfClosingTagPlugin.ts +2 -1
  93. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/Fallback.tsx +98 -0
  94. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/ModeSelector.tsx +5 -6
  95. package/src/features/Conversation/Messages/Group/Tool/Render/Intervention/index.tsx +40 -36
  96. package/src/features/Conversation/Messages/Group/Tool/Render/index.tsx +25 -18
  97. package/src/features/LocalFile/LocalFile.tsx +55 -5
  98. package/src/locales/default/components.ts +6 -0
  99. package/src/locales/default/plugin.ts +1 -0
  100. package/src/services/electron/localFileService.ts +4 -0
  101. package/src/store/chat/agents/GeneralChatAgent.ts +26 -1
  102. package/src/store/chat/agents/__tests__/GeneralChatAgent.test.ts +173 -0
  103. package/src/store/chat/slices/aiChat/actions/conversationControl.ts +8 -40
  104. package/src/store/chat/slices/aiChat/actions/streamingExecutor.ts +91 -34
  105. package/src/store/user/selectors.ts +1 -0
  106. package/src/store/user/slices/settings/action.ts +12 -0
  107. package/src/store/user/slices/settings/selectors/__snapshots__/settings.test.ts.snap +0 -7
  108. package/src/store/user/slices/settings/selectors/index.ts +1 -0
  109. package/src/store/user/slices/settings/selectors/settings.test.ts +0 -37
  110. package/src/store/user/slices/settings/selectors/settings.ts +0 -5
  111. package/src/store/user/slices/settings/selectors/toolIntervention.ts +17 -0
  112. package/src/tools/interventions.ts +8 -0
  113. package/src/tools/local-system/Intervention/RunCommand/index.tsx +56 -0
  114. package/src/tools/local-system/Intervention/index.tsx +17 -0
  115. package/src/tools/local-system/Render/RunCommand/index.tsx +100 -21
  116. package/src/tools/local-system/Render/index.tsx +2 -0
  117. package/src/tools/local-system/index.ts +180 -0
  118. package/src/tools/local-system/systemRole.ts +61 -7
@@ -0,0 +1,499 @@
1
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ import type { App } from '@/core/App';
4
+
5
+ import ShellCommandCtr from '../ShellCommandCtr';
6
+
7
+ // Mock logger
8
+ vi.mock('@/utils/logger', () => ({
9
+ createLogger: () => ({
10
+ debug: vi.fn(),
11
+ info: vi.fn(),
12
+ warn: vi.fn(),
13
+ error: vi.fn(),
14
+ }),
15
+ }));
16
+
17
+ // Mock child_process
18
+ vi.mock('node:child_process', () => ({
19
+ spawn: vi.fn(),
20
+ }));
21
+
22
+ // Mock crypto
23
+ vi.mock('node:crypto', () => ({
24
+ randomUUID: vi.fn(() => 'test-uuid-123'),
25
+ }));
26
+
27
+ const mockApp = {} as unknown as App;
28
+
29
+ describe('ShellCommandCtr', () => {
30
+ let shellCommandCtr: ShellCommandCtr;
31
+ let mockSpawn: any;
32
+ let mockChildProcess: any;
33
+
34
+ beforeEach(async () => {
35
+ vi.clearAllMocks();
36
+
37
+ // Import mocks
38
+ const childProcessModule = await import('node:child_process');
39
+ mockSpawn = vi.mocked(childProcessModule.spawn);
40
+
41
+ // Create mock child process
42
+ mockChildProcess = {
43
+ stdout: {
44
+ on: vi.fn(),
45
+ },
46
+ stderr: {
47
+ on: vi.fn(),
48
+ },
49
+ on: vi.fn(),
50
+ kill: vi.fn(),
51
+ exitCode: null,
52
+ };
53
+
54
+ mockSpawn.mockReturnValue(mockChildProcess);
55
+
56
+ shellCommandCtr = new ShellCommandCtr(mockApp);
57
+ });
58
+
59
+ describe('handleRunCommand', () => {
60
+ describe('synchronous mode', () => {
61
+ it('should execute command successfully', async () => {
62
+ let exitCallback: (code: number) => void;
63
+ let stdoutCallback: (data: Buffer) => void;
64
+
65
+ mockChildProcess.on.mockImplementation((event: string, callback: any) => {
66
+ if (event === 'exit') {
67
+ exitCallback = callback;
68
+ // Simulate successful exit
69
+ setTimeout(() => exitCallback(0), 10);
70
+ }
71
+ return mockChildProcess;
72
+ });
73
+
74
+ mockChildProcess.stdout.on.mockImplementation((event: string, callback: any) => {
75
+ if (event === 'data') {
76
+ stdoutCallback = callback;
77
+ // Simulate output
78
+ setTimeout(() => stdoutCallback(Buffer.from('test output\n')), 5);
79
+ }
80
+ return mockChildProcess.stdout;
81
+ });
82
+
83
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
84
+
85
+ const result = await shellCommandCtr.handleRunCommand({
86
+ command: 'echo "test"',
87
+ description: 'test command',
88
+ });
89
+
90
+ expect(result.success).toBe(true);
91
+ expect(result.stdout).toBe('test output\n');
92
+ expect(result.exit_code).toBe(0);
93
+ });
94
+
95
+ it('should handle command timeout', async () => {
96
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
97
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
98
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
99
+
100
+ const result = await shellCommandCtr.handleRunCommand({
101
+ command: 'sleep 10',
102
+ description: 'long running command',
103
+ timeout: 100,
104
+ });
105
+
106
+ expect(result.success).toBe(false);
107
+ expect(result.error).toContain('timed out');
108
+ expect(mockChildProcess.kill).toHaveBeenCalled();
109
+ });
110
+
111
+ it('should handle command execution error', async () => {
112
+ let errorCallback: (error: Error) => void;
113
+
114
+ mockChildProcess.on.mockImplementation((event: string, callback: any) => {
115
+ if (event === 'error') {
116
+ errorCallback = callback;
117
+ setTimeout(() => errorCallback(new Error('Command not found')), 10);
118
+ }
119
+ return mockChildProcess;
120
+ });
121
+
122
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
123
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
124
+
125
+ const result = await shellCommandCtr.handleRunCommand({
126
+ command: 'invalid-command',
127
+ description: 'invalid command',
128
+ });
129
+
130
+ expect(result.success).toBe(false);
131
+ expect(result.error).toBe('Command not found');
132
+ });
133
+
134
+ it('should handle non-zero exit code', async () => {
135
+ let exitCallback: (code: number) => void;
136
+
137
+ mockChildProcess.on.mockImplementation((event: string, callback: any) => {
138
+ if (event === 'exit') {
139
+ exitCallback = callback;
140
+ setTimeout(() => exitCallback(1), 10);
141
+ }
142
+ return mockChildProcess;
143
+ });
144
+
145
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
146
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
147
+
148
+ const result = await shellCommandCtr.handleRunCommand({
149
+ command: 'exit 1',
150
+ description: 'failing command',
151
+ });
152
+
153
+ expect(result.success).toBe(false);
154
+ expect(result.exit_code).toBe(1);
155
+ });
156
+
157
+ it('should capture stderr output', async () => {
158
+ let exitCallback: (code: number) => void;
159
+ let stderrCallback: (data: Buffer) => void;
160
+
161
+ mockChildProcess.on.mockImplementation((event: string, callback: any) => {
162
+ if (event === 'exit') {
163
+ exitCallback = callback;
164
+ setTimeout(() => exitCallback(1), 10);
165
+ }
166
+ return mockChildProcess;
167
+ });
168
+
169
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
170
+ mockChildProcess.stderr.on.mockImplementation((event: string, callback: any) => {
171
+ if (event === 'data') {
172
+ stderrCallback = callback;
173
+ setTimeout(() => stderrCallback(Buffer.from('error message\n')), 5);
174
+ }
175
+ return mockChildProcess.stderr;
176
+ });
177
+
178
+ const result = await shellCommandCtr.handleRunCommand({
179
+ command: 'command-with-error',
180
+ description: 'command with stderr',
181
+ });
182
+
183
+ expect(result.stderr).toBe('error message\n');
184
+ });
185
+
186
+ it('should enforce timeout limits', async () => {
187
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
188
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
189
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
190
+
191
+ // Test minimum timeout
192
+ const minResult = await shellCommandCtr.handleRunCommand({
193
+ command: 'sleep 5',
194
+ timeout: 500, // Below 1000ms minimum
195
+ });
196
+
197
+ expect(minResult.success).toBe(false);
198
+ expect(minResult.error).toContain('1000ms'); // Should use 1000ms minimum
199
+ });
200
+ });
201
+
202
+ describe('background mode', () => {
203
+ it('should start command in background', async () => {
204
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
205
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
206
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
207
+
208
+ const result = await shellCommandCtr.handleRunCommand({
209
+ command: 'long-running-task',
210
+ description: 'background task',
211
+ run_in_background: true,
212
+ });
213
+
214
+ expect(result.success).toBe(true);
215
+ expect(result.shell_id).toBe('test-uuid-123');
216
+ });
217
+
218
+ it('should use correct shell on Windows', async () => {
219
+ const originalPlatform = process.platform;
220
+ Object.defineProperty(process, 'platform', { value: 'win32' });
221
+
222
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
223
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
224
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
225
+
226
+ await shellCommandCtr.handleRunCommand({
227
+ command: 'dir',
228
+ description: 'windows command',
229
+ run_in_background: true,
230
+ });
231
+
232
+ expect(mockSpawn).toHaveBeenCalledWith('cmd.exe', ['/c', 'dir'], expect.any(Object));
233
+
234
+ Object.defineProperty(process, 'platform', { value: originalPlatform });
235
+ });
236
+
237
+ it('should use correct shell on Unix', async () => {
238
+ const originalPlatform = process.platform;
239
+ Object.defineProperty(process, 'platform', { value: 'darwin' });
240
+
241
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
242
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
243
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
244
+
245
+ await shellCommandCtr.handleRunCommand({
246
+ command: 'ls',
247
+ description: 'unix command',
248
+ run_in_background: true,
249
+ });
250
+
251
+ expect(mockSpawn).toHaveBeenCalledWith('/bin/sh', ['-c', 'ls'], expect.any(Object));
252
+
253
+ Object.defineProperty(process, 'platform', { value: originalPlatform });
254
+ });
255
+ });
256
+ });
257
+
258
+ describe('handleGetCommandOutput', () => {
259
+ beforeEach(async () => {
260
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
261
+ mockChildProcess.stdout.on.mockImplementation((event: string, callback: any) => {
262
+ if (event === 'data') {
263
+ // Simulate some output
264
+ setTimeout(() => callback(Buffer.from('line 1\n')), 5);
265
+ setTimeout(() => callback(Buffer.from('line 2\n')), 10);
266
+ }
267
+ return mockChildProcess.stdout;
268
+ });
269
+ mockChildProcess.stderr.on.mockImplementation((event: string, callback: any) => {
270
+ if (event === 'data') {
271
+ setTimeout(() => callback(Buffer.from('error line\n')), 7);
272
+ }
273
+ return mockChildProcess.stderr;
274
+ });
275
+
276
+ // Start a background process first
277
+ await shellCommandCtr.handleRunCommand({
278
+ command: 'test-command',
279
+ run_in_background: true,
280
+ });
281
+ });
282
+
283
+ it('should retrieve command output', async () => {
284
+ // Wait for output to be captured
285
+ await new Promise((resolve) => setTimeout(resolve, 20));
286
+
287
+ const result = await shellCommandCtr.handleGetCommandOutput({
288
+ shell_id: 'test-uuid-123',
289
+ });
290
+
291
+ expect(result.success).toBe(true);
292
+ expect(result.stdout).toContain('line 1');
293
+ expect(result.stderr).toContain('error line');
294
+ });
295
+
296
+ it('should return error for non-existent shell_id', async () => {
297
+ const result = await shellCommandCtr.handleGetCommandOutput({
298
+ shell_id: 'non-existent-id',
299
+ });
300
+
301
+ expect(result.success).toBe(false);
302
+ expect(result.error).toContain('not found');
303
+ });
304
+
305
+ it('should filter output with regex', async () => {
306
+ // Wait for output to be captured
307
+ await new Promise((resolve) => setTimeout(resolve, 20));
308
+
309
+ const result = await shellCommandCtr.handleGetCommandOutput({
310
+ shell_id: 'test-uuid-123',
311
+ filter: 'line 1',
312
+ });
313
+
314
+ expect(result.success).toBe(true);
315
+ expect(result.output).toContain('line 1');
316
+ expect(result.output).not.toContain('line 2');
317
+ });
318
+
319
+ it('should only return new output since last read', async () => {
320
+ // Wait for initial output
321
+ await new Promise((resolve) => setTimeout(resolve, 20));
322
+
323
+ // First read
324
+ const firstResult = await shellCommandCtr.handleGetCommandOutput({
325
+ shell_id: 'test-uuid-123',
326
+ });
327
+
328
+ expect(firstResult.stdout).toContain('line 1');
329
+
330
+ // Second read should return empty (no new output)
331
+ const secondResult = await shellCommandCtr.handleGetCommandOutput({
332
+ shell_id: 'test-uuid-123',
333
+ });
334
+
335
+ expect(secondResult.stdout).toBe('');
336
+ expect(secondResult.stderr).toBe('');
337
+ });
338
+
339
+ it('should handle invalid regex filter gracefully', async () => {
340
+ await new Promise((resolve) => setTimeout(resolve, 20));
341
+
342
+ const result = await shellCommandCtr.handleGetCommandOutput({
343
+ shell_id: 'test-uuid-123',
344
+ filter: '[invalid(regex',
345
+ });
346
+
347
+ expect(result.success).toBe(true);
348
+ // Should return unfiltered output when filter is invalid
349
+ });
350
+
351
+ it('should report running status correctly', async () => {
352
+ mockChildProcess.exitCode = null;
353
+
354
+ const runningResult = await shellCommandCtr.handleGetCommandOutput({
355
+ shell_id: 'test-uuid-123',
356
+ });
357
+
358
+ expect(runningResult.running).toBe(true);
359
+
360
+ // Simulate process exit
361
+ mockChildProcess.exitCode = 0;
362
+
363
+ const exitedResult = await shellCommandCtr.handleGetCommandOutput({
364
+ shell_id: 'test-uuid-123',
365
+ });
366
+
367
+ expect(exitedResult.running).toBe(false);
368
+ });
369
+
370
+ it('should track stdout and stderr offsets separately when streaming output', async () => {
371
+ // Create a new background process with manual control over stdout/stderr
372
+ let stdoutCallback: (data: Buffer) => void;
373
+ let stderrCallback: (data: Buffer) => void;
374
+
375
+ mockChildProcess.stdout.on.mockImplementation((event: string, callback: any) => {
376
+ if (event === 'data') {
377
+ stdoutCallback = callback;
378
+ }
379
+ return mockChildProcess.stdout;
380
+ });
381
+
382
+ mockChildProcess.stderr.on.mockImplementation((event: string, callback: any) => {
383
+ if (event === 'data') {
384
+ stderrCallback = callback;
385
+ }
386
+ return mockChildProcess.stderr;
387
+ });
388
+
389
+ // Start a new background process
390
+ await shellCommandCtr.handleRunCommand({
391
+ command: 'test-interleaved',
392
+ run_in_background: true,
393
+ });
394
+
395
+ // Simulate stderr output first
396
+ stderrCallback(Buffer.from('error 1\n'));
397
+ await new Promise((resolve) => setTimeout(resolve, 5));
398
+
399
+ // First read - should get stderr
400
+ const firstRead = await shellCommandCtr.handleGetCommandOutput({
401
+ shell_id: 'test-uuid-123',
402
+ });
403
+ expect(firstRead.stderr).toBe('error 1\n');
404
+ expect(firstRead.stdout).toBe('');
405
+
406
+ // Simulate stdout output after stderr
407
+ stdoutCallback(Buffer.from('output 1\n'));
408
+ await new Promise((resolve) => setTimeout(resolve, 5));
409
+
410
+ // Second read - should get stdout without losing data
411
+ const secondRead = await shellCommandCtr.handleGetCommandOutput({
412
+ shell_id: 'test-uuid-123',
413
+ });
414
+ expect(secondRead.stdout).toBe('output 1\n');
415
+ expect(secondRead.stderr).toBe('');
416
+
417
+ // Simulate more stderr
418
+ stderrCallback(Buffer.from('error 2\n'));
419
+ await new Promise((resolve) => setTimeout(resolve, 5));
420
+
421
+ // Third read - should get new stderr
422
+ const thirdRead = await shellCommandCtr.handleGetCommandOutput({
423
+ shell_id: 'test-uuid-123',
424
+ });
425
+ expect(thirdRead.stderr).toBe('error 2\n');
426
+ expect(thirdRead.stdout).toBe('');
427
+
428
+ // Simulate more stdout
429
+ stdoutCallback(Buffer.from('output 2\n'));
430
+ await new Promise((resolve) => setTimeout(resolve, 5));
431
+
432
+ // Fourth read - should get new stdout
433
+ const fourthRead = await shellCommandCtr.handleGetCommandOutput({
434
+ shell_id: 'test-uuid-123',
435
+ });
436
+ expect(fourthRead.stdout).toBe('output 2\n');
437
+ expect(fourthRead.stderr).toBe('');
438
+ });
439
+ });
440
+
441
+ describe('handleKillCommand', () => {
442
+ beforeEach(async () => {
443
+ mockChildProcess.on.mockImplementation(() => mockChildProcess);
444
+ mockChildProcess.stdout.on.mockImplementation(() => mockChildProcess.stdout);
445
+ mockChildProcess.stderr.on.mockImplementation(() => mockChildProcess.stderr);
446
+
447
+ // Start a background process
448
+ await shellCommandCtr.handleRunCommand({
449
+ command: 'test-command',
450
+ run_in_background: true,
451
+ });
452
+ });
453
+
454
+ it('should kill command successfully', async () => {
455
+ const result = await shellCommandCtr.handleKillCommand({
456
+ shell_id: 'test-uuid-123',
457
+ });
458
+
459
+ expect(result.success).toBe(true);
460
+ expect(mockChildProcess.kill).toHaveBeenCalled();
461
+ });
462
+
463
+ it('should return error for non-existent shell_id', async () => {
464
+ const result = await shellCommandCtr.handleKillCommand({
465
+ shell_id: 'non-existent-id',
466
+ });
467
+
468
+ expect(result.success).toBe(false);
469
+ expect(result.error).toContain('not found');
470
+ });
471
+
472
+ it('should remove process from map after killing', async () => {
473
+ await shellCommandCtr.handleKillCommand({
474
+ shell_id: 'test-uuid-123',
475
+ });
476
+
477
+ // Try to get output from killed process
478
+ const outputResult = await shellCommandCtr.handleGetCommandOutput({
479
+ shell_id: 'test-uuid-123',
480
+ });
481
+
482
+ expect(outputResult.success).toBe(false);
483
+ expect(outputResult.error).toContain('not found');
484
+ });
485
+
486
+ it('should handle kill error gracefully', async () => {
487
+ mockChildProcess.kill.mockImplementation(() => {
488
+ throw new Error('Kill failed');
489
+ });
490
+
491
+ const result = await shellCommandCtr.handleKillCommand({
492
+ shell_id: 'test-uuid-123',
493
+ });
494
+
495
+ expect(result.success).toBe(false);
496
+ expect(result.error).toBe('Kill failed');
497
+ });
498
+ });
499
+ });
package/changelog/v1.json CHANGED
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "children": {
4
+ "improvements": [
5
+ "Update ERNIE-5.0-Thinking-Preview model."
6
+ ]
7
+ },
8
+ "date": "2025-11-13",
9
+ "version": "2.0.0-next.51"
10
+ },
2
11
  {
3
12
  "children": {
4
13
  "fixes": [
@@ -369,6 +369,26 @@
369
369
  "remained": "متبقي",
370
370
  "used": "مستخدم"
371
371
  },
372
+ "tool": {
373
+ "intervention": {
374
+ "approve": "الموافقة",
375
+ "approveAndRemember": "الموافقة والتذكر",
376
+ "approveOnce": "الموافقة لمرة واحدة فقط",
377
+ "mode": {
378
+ "allowList": "قائمة السماح",
379
+ "allowListDesc": "تنفيذ الأدوات المعتمدة فقط تلقائيًا",
380
+ "autoRun": "الموافقة التلقائية",
381
+ "autoRunDesc": "الموافقة تلقائيًا على تنفيذ جميع الأدوات",
382
+ "manual": "يدوي",
383
+ "manualDesc": "يتطلب الموافقة اليدوية في كل مرة يتم فيها الاستدعاء"
384
+ },
385
+ "reject": "رفض",
386
+ "rejectReasonPlaceholder": "إدخال سبب الرفض سيساعد الوكيل على الفهم وتحسين الإجراءات المستقبلية",
387
+ "rejectTitle": "رفض استدعاء الأداة هذه المرة",
388
+ "rejectedWithReason": "تم رفض استدعاء الأداة هذه المرة بشكل يدوي: {{reason}}",
389
+ "toolRejected": "تم رفض استدعاء الأداة هذه المرة بشكل يدوي"
390
+ }
391
+ },
372
392
  "topic": {
373
393
  "checkOpenNewTopic": "هل ترغب في فتح موضوع جديد؟",
374
394
  "checkSaveCurrentMessages": "هل ترغب في حفظ الدردشة الحالية كموضوع؟",
@@ -135,6 +135,7 @@
135
135
  }
136
136
  },
137
137
  "close": "إغلاق",
138
+ "confirm": "تأكيد",
138
139
  "contact": "اتصل بنا",
139
140
  "copy": "نسخ",
140
141
  "copyFail": "فشل في النسخ",
@@ -106,6 +106,12 @@
106
106
  "keyPlaceholder": "المفتاح",
107
107
  "valuePlaceholder": "القيمة"
108
108
  },
109
+ "LocalFile": {
110
+ "action": {
111
+ "open": "فتح",
112
+ "showInFolder": "عرض في المجلد"
113
+ }
114
+ },
109
115
  "MaxTokenSlider": {
110
116
  "unlimited": "غير محدود"
111
117
  },
@@ -255,6 +255,7 @@
255
255
  "moveLocalFiles": "نقل الملفات",
256
256
  "readLocalFile": "قراءة محتوى الملف",
257
257
  "renameLocalFile": "إعادة تسمية",
258
+ "runCommand": "تشغيل الأمر",
258
259
  "searchLocalFiles": "بحث في الملفات",
259
260
  "writeLocalFile": "كتابة في الملف"
260
261
  },
@@ -369,6 +369,26 @@
369
369
  "remained": "Оставащи",
370
370
  "used": "Използвани"
371
371
  },
372
+ "tool": {
373
+ "intervention": {
374
+ "approve": "Одобряване",
375
+ "approveAndRemember": "Одобряване и запомняне",
376
+ "approveOnce": "Одобряване само този път",
377
+ "mode": {
378
+ "allowList": "Бял списък",
379
+ "allowListDesc": "Автоматично се изпълняват само одобрените инструменти",
380
+ "autoRun": "Автоматично одобрение",
381
+ "autoRunDesc": "Автоматично одобряване на всички изпълнения на инструменти",
382
+ "manual": "Ръчно",
383
+ "manualDesc": "Необходимо е ръчно одобрение при всяко извикване"
384
+ },
385
+ "reject": "Отхвърляне",
386
+ "rejectReasonPlaceholder": "Въведете причина за отхвърляне, за да помогнете на агента да разбере и подобри бъдещите действия",
387
+ "rejectTitle": "Отхвърляне на това извикване на инструмент",
388
+ "rejectedWithReason": "Това извикване на инструмент беше умишлено отхвърлено: {{reason}}",
389
+ "toolRejected": "Това извикване на инструмент беше умишлено отхвърлено"
390
+ }
391
+ },
372
392
  "topic": {
373
393
  "checkOpenNewTopic": "Да се отвори ли нова тема?",
374
394
  "checkSaveCurrentMessages": "Искате ли да запазите текущата сесия като тема?",
@@ -135,6 +135,7 @@
135
135
  }
136
136
  },
137
137
  "close": "Затвори",
138
+ "confirm": "Потвърди",
138
139
  "contact": "Свържете се с нас",
139
140
  "copy": "Копирай",
140
141
  "copyFail": "Копирането не е успешно",
@@ -106,6 +106,12 @@
106
106
  "keyPlaceholder": "Ключ",
107
107
  "valuePlaceholder": "Стойност"
108
108
  },
109
+ "LocalFile": {
110
+ "action": {
111
+ "open": "Отвори",
112
+ "showInFolder": "Показване в папката"
113
+ }
114
+ },
109
115
  "MaxTokenSlider": {
110
116
  "unlimited": "Неограничено"
111
117
  },
@@ -255,6 +255,7 @@
255
255
  "moveLocalFiles": "Преместване на файлове",
256
256
  "readLocalFile": "Четене на съдържание на файл",
257
257
  "renameLocalFile": "Преименуване",
258
+ "runCommand": "Изпълни код",
258
259
  "searchLocalFiles": "Търсене на файлове",
259
260
  "writeLocalFile": "Запис в файл"
260
261
  },
@@ -369,6 +369,26 @@
369
369
  "remained": "Verbleibend",
370
370
  "used": "Verwendet"
371
371
  },
372
+ "tool": {
373
+ "intervention": {
374
+ "approve": "Genehmigen",
375
+ "approveAndRemember": "Genehmigen und merken",
376
+ "approveOnce": "Nur dieses Mal genehmigen",
377
+ "mode": {
378
+ "allowList": "Positivliste",
379
+ "allowListDesc": "Nur automatisch genehmigte Tools ausführen",
380
+ "autoRun": "Automatisch genehmigen",
381
+ "autoRunDesc": "Alle Tool-Ausführungen automatisch genehmigen",
382
+ "manual": "Manuell",
383
+ "manualDesc": "Jede Ausführung muss manuell genehmigt werden"
384
+ },
385
+ "reject": "Ablehnen",
386
+ "rejectReasonPlaceholder": "Die Angabe eines Ablehnungsgrundes hilft dem Agenten, zukünftige Aktionen zu verbessern",
387
+ "rejectTitle": "Tool-Ausführung ablehnen",
388
+ "rejectedWithReason": "Die Tool-Ausführung wurde abgelehnt: {{reason}}",
389
+ "toolRejected": "Die Tool-Ausführung wurde abgelehnt"
390
+ }
391
+ },
372
392
  "topic": {
373
393
  "checkOpenNewTopic": "Soll ein neues Thema eröffnet werden?",
374
394
  "checkSaveCurrentMessages": "Möchten Sie die aktuelle Konversation als Thema speichern?",
@@ -135,6 +135,7 @@
135
135
  }
136
136
  },
137
137
  "close": "Schließen",
138
+ "confirm": "Bestätigen",
138
139
  "contact": "Kontakt",
139
140
  "copy": "Kopieren",
140
141
  "copyFail": "Kopieren fehlgeschlagen",
@@ -106,6 +106,12 @@
106
106
  "keyPlaceholder": "Schlüssel",
107
107
  "valuePlaceholder": "Wert"
108
108
  },
109
+ "LocalFile": {
110
+ "action": {
111
+ "open": "Öffnen",
112
+ "showInFolder": "Im Ordner anzeigen"
113
+ }
114
+ },
109
115
  "MaxTokenSlider": {
110
116
  "unlimited": "Unbegrenzt"
111
117
  },