ai-cli-mcp 2.3.0 → 2.3.1

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 (36) hide show
  1. package/.claude/settings.local.json +2 -1
  2. package/dist/__tests__/e2e.test.js +232 -0
  3. package/dist/__tests__/edge-cases.test.js +135 -0
  4. package/dist/__tests__/error-cases.test.js +291 -0
  5. package/dist/__tests__/mocks.js +32 -0
  6. package/dist/__tests__/model-alias.test.js +36 -0
  7. package/dist/__tests__/process-management.test.js +630 -0
  8. package/dist/__tests__/server.test.js +681 -0
  9. package/dist/__tests__/setup.js +11 -0
  10. package/dist/__tests__/utils/claude-mock.js +80 -0
  11. package/dist/__tests__/utils/mcp-client.js +104 -0
  12. package/dist/__tests__/utils/persistent-mock.js +25 -0
  13. package/dist/__tests__/utils/test-helpers.js +11 -0
  14. package/dist/__tests__/validation.test.js +235 -0
  15. package/dist/__tests__/version-print.test.js +69 -0
  16. package/dist/__tests__/wait.test.js +229 -0
  17. package/dist/parsers.js +68 -0
  18. package/dist/server.js +774 -0
  19. package/package.json +1 -1
  20. package/src/__tests__/e2e.test.ts +16 -24
  21. package/src/__tests__/error-cases.test.ts +8 -17
  22. package/src/__tests__/process-management.test.ts +22 -24
  23. package/src/__tests__/validation.test.ts +58 -36
  24. package/src/server.ts +6 -2
  25. package/data/rooms/refactor-haiku-alias-main/messages.jsonl +0 -5
  26. package/data/rooms/refactor-haiku-alias-main/presence.json +0 -20
  27. package/data/rooms.json +0 -10
  28. package/hello.txt +0 -3
  29. package/implementation-log.md +0 -110
  30. package/implementation-plan.md +0 -189
  31. package/investigation-report.md +0 -135
  32. package/quality-score.json +0 -47
  33. package/refactoring-requirements.md +0 -25
  34. package/review-report.md +0 -132
  35. package/test-results.md +0 -119
  36. package/xx.txt +0 -1
@@ -0,0 +1,630 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import { spawn } from 'node:child_process';
3
+ import { existsSync } from 'node:fs';
4
+ import { homedir } from 'node:os';
5
+ import { EventEmitter } from 'node:events';
6
+ // Mock dependencies
7
+ vi.mock('node:child_process');
8
+ vi.mock('node:fs');
9
+ vi.mock('node:os');
10
+ vi.mock('@modelcontextprotocol/sdk/server/stdio.js');
11
+ vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
12
+ ListToolsRequestSchema: { name: 'listTools' },
13
+ CallToolRequestSchema: { name: 'callTool' },
14
+ ErrorCode: {
15
+ InternalError: 'InternalError',
16
+ MethodNotFound: 'MethodNotFound',
17
+ InvalidParams: 'InvalidParams'
18
+ },
19
+ McpError: class McpError extends Error {
20
+ code;
21
+ constructor(code, message) {
22
+ super(message);
23
+ this.code = code;
24
+ this.name = 'McpError';
25
+ }
26
+ }
27
+ }));
28
+ vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
29
+ Server: vi.fn().mockImplementation(function () {
30
+ return {
31
+ setRequestHandler: vi.fn(),
32
+ connect: vi.fn(),
33
+ close: vi.fn(),
34
+ onerror: undefined,
35
+ };
36
+ }),
37
+ }));
38
+ const mockExistsSync = vi.mocked(existsSync);
39
+ const mockSpawn = vi.mocked(spawn);
40
+ const mockHomedir = vi.mocked(homedir);
41
+ describe('Process Management Tests', () => {
42
+ let consoleErrorSpy;
43
+ let originalEnv;
44
+ let mockServerInstance;
45
+ let handlers;
46
+ beforeEach(() => {
47
+ vi.clearAllMocks();
48
+ vi.resetModules();
49
+ consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => { });
50
+ originalEnv = { ...process.env };
51
+ process.env = { ...originalEnv };
52
+ handlers = new Map();
53
+ // Set up default mocks
54
+ mockHomedir.mockReturnValue('/home/user');
55
+ mockExistsSync.mockReturnValue(true);
56
+ });
57
+ afterEach(() => {
58
+ consoleErrorSpy.mockRestore();
59
+ process.env = originalEnv;
60
+ });
61
+ async function setupServer() {
62
+ const { Server } = await import('@modelcontextprotocol/sdk/server/index.js');
63
+ vi.mocked(Server).mockImplementation(() => {
64
+ mockServerInstance = {
65
+ setRequestHandler: vi.fn((schema, handler) => {
66
+ handlers.set(schema.name, handler);
67
+ }),
68
+ connect: vi.fn(),
69
+ close: vi.fn(),
70
+ onerror: undefined
71
+ };
72
+ return mockServerInstance;
73
+ });
74
+ const module = await import('../server.js');
75
+ const { ClaudeCodeServer } = module;
76
+ const server = new ClaudeCodeServer();
77
+ return { server, module, handlers };
78
+ }
79
+ describe('run tool with PID return', () => {
80
+ it('should return PID immediately when starting a process', async () => {
81
+ const { handlers } = await setupServer();
82
+ // Create a mock process
83
+ const mockProcess = new EventEmitter();
84
+ mockProcess.pid = 12345;
85
+ mockProcess.stdout = new EventEmitter();
86
+ mockProcess.stderr = new EventEmitter();
87
+ mockProcess.kill = vi.fn();
88
+ mockSpawn.mockReturnValue(mockProcess);
89
+ const callToolHandler = handlers.get('callTool');
90
+ const result = await callToolHandler({
91
+ params: {
92
+ name: 'run',
93
+ arguments: {
94
+ prompt: 'test prompt',
95
+ workFolder: '/tmp'
96
+ }
97
+ }
98
+ });
99
+ const response = JSON.parse(result.content[0].text);
100
+ expect(response.pid).toBe(12345);
101
+ expect(response.status).toBe('started');
102
+ expect(response.message).toBe('claude process started successfully');
103
+ });
104
+ it('should handle process with model parameter', async () => {
105
+ const { handlers } = await setupServer();
106
+ const mockProcess = new EventEmitter();
107
+ mockProcess.pid = 12346;
108
+ mockProcess.stdout = new EventEmitter();
109
+ mockProcess.stderr = new EventEmitter();
110
+ mockProcess.kill = vi.fn();
111
+ mockSpawn.mockReturnValue(mockProcess);
112
+ const callToolHandler = handlers.get('callTool');
113
+ await callToolHandler({
114
+ params: {
115
+ name: 'run',
116
+ arguments: {
117
+ prompt: 'test prompt',
118
+ workFolder: '/tmp',
119
+ model: 'opus'
120
+ }
121
+ }
122
+ });
123
+ expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['--model', 'opus']), expect.any(Object));
124
+ });
125
+ it('should handle Japanese prompts with newlines', async () => {
126
+ const { handlers } = await setupServer();
127
+ const mockProcess = new EventEmitter();
128
+ mockProcess.pid = 12360;
129
+ mockProcess.stdout = new EventEmitter();
130
+ mockProcess.stderr = new EventEmitter();
131
+ mockProcess.kill = vi.fn();
132
+ mockSpawn.mockReturnValue(mockProcess);
133
+ const japanesePrompt = `日本語のテストプロンプトです。
134
+ これは改行を含んでいます。
135
+ さらに、特殊文字も含みます:「こんにちは」、『世界』
136
+ 最後の行です。`;
137
+ const callToolHandler = handlers.get('callTool');
138
+ const result = await callToolHandler({
139
+ params: {
140
+ name: 'run',
141
+ arguments: {
142
+ prompt: japanesePrompt,
143
+ workFolder: '/tmp'
144
+ }
145
+ }
146
+ });
147
+ // Verify PID is returned
148
+ const response = JSON.parse(result.content[0].text);
149
+ expect(response.pid).toBe(12360);
150
+ // Verify spawn was called with the correct prompt including newlines
151
+ expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['-p', japanesePrompt]), expect.any(Object));
152
+ // Verify the prompt is stored correctly in process manager
153
+ const getResult = await callToolHandler({
154
+ params: {
155
+ name: 'get_result',
156
+ arguments: {
157
+ pid: 12360
158
+ }
159
+ }
160
+ });
161
+ const processInfo = JSON.parse(getResult.content[0].text);
162
+ expect(processInfo.prompt).toBe(japanesePrompt);
163
+ });
164
+ it('should handle very long Japanese prompts with multiple paragraphs', async () => {
165
+ const { handlers } = await setupServer();
166
+ const mockProcess = new EventEmitter();
167
+ mockProcess.pid = 12361;
168
+ mockProcess.stdout = new EventEmitter();
169
+ mockProcess.stderr = new EventEmitter();
170
+ mockProcess.kill = vi.fn();
171
+ mockSpawn.mockReturnValue(mockProcess);
172
+ const longJapanesePrompt = `# タスク:ファイル管理システムの作成
173
+
174
+ 以下の要件に従って、ファイル管理システムを作成してください:
175
+
176
+ 1. **基本機能**
177
+ - ファイルの作成、読み取り、更新、削除(CRUD)
178
+ - ディレクトリの作成と管理
179
+ - ファイルの検索機能
180
+
181
+ 2. **追加機能**
182
+ - ファイルのバージョン管理
183
+ - アクセス権限の設定
184
+ - ログ記録機能
185
+
186
+ 3. **技術要件**
187
+ - TypeScriptを使用
188
+ - テストコードを含める
189
+ - ドキュメントを日本語で作成
190
+
191
+ 注意事項:
192
+ - エラーハンドリングを適切に行う
193
+ - パフォーマンスを考慮した実装
194
+ - セキュリティに配慮すること
195
+
196
+ よろしくお願いします。`;
197
+ const callToolHandler = handlers.get('callTool');
198
+ const result = await callToolHandler({
199
+ params: {
200
+ name: 'run',
201
+ arguments: {
202
+ prompt: longJapanesePrompt,
203
+ workFolder: '/tmp',
204
+ model: 'sonnet'
205
+ }
206
+ }
207
+ });
208
+ // Verify PID is returned
209
+ const response = JSON.parse(result.content[0].text);
210
+ expect(response.pid).toBe(12361);
211
+ // Verify spawn was called with the complete long prompt
212
+ expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['-p', longJapanesePrompt]), expect.any(Object));
213
+ // Verify list_processes returns basic info
214
+ const listResult = await callToolHandler({
215
+ params: {
216
+ name: 'list_processes',
217
+ arguments: {}
218
+ }
219
+ });
220
+ const processes = JSON.parse(listResult.content[0].text);
221
+ const process = processes.find((p) => p.pid === 12361);
222
+ expect(process.pid).toBe(12361);
223
+ expect(process.agent).toBe('claude');
224
+ expect(process.status).toBe('running');
225
+ });
226
+ it('should handle prompts with special characters and escape sequences', async () => {
227
+ const { handlers } = await setupServer();
228
+ const mockProcess = new EventEmitter();
229
+ mockProcess.pid = 12362;
230
+ mockProcess.stdout = new EventEmitter();
231
+ mockProcess.stderr = new EventEmitter();
232
+ mockProcess.kill = vi.fn();
233
+ mockSpawn.mockReturnValue(mockProcess);
234
+ // Test with various special characters
235
+ const specialPrompt = `特殊文字のテスト:
236
+ \t- タブ文字
237
+ \n- 明示的な改行
238
+ "ダブルクォート" と 'シングルクォート'
239
+ バックスラッシュ: \\
240
+ Unicodeテスト: 🎌 🗾 ✨
241
+ 環境変数風: $HOME と \${USER}`;
242
+ const callToolHandler = handlers.get('callTool');
243
+ const result = await callToolHandler({
244
+ params: {
245
+ name: 'run',
246
+ arguments: {
247
+ prompt: specialPrompt,
248
+ workFolder: '/tmp'
249
+ }
250
+ }
251
+ });
252
+ // Verify PID is returned
253
+ const response = JSON.parse(result.content[0].text);
254
+ expect(response.pid).toBe(12362);
255
+ // Verify spawn was called with the special characters intact
256
+ expect(mockSpawn).toHaveBeenCalledWith(expect.any(String), expect.arrayContaining(['-p', specialPrompt]), expect.any(Object));
257
+ });
258
+ it('should throw error if process fails to start', async () => {
259
+ const { handlers } = await setupServer();
260
+ const mockProcess = new EventEmitter();
261
+ mockProcess.pid = undefined; // No PID means process failed to start
262
+ mockProcess.stdout = new EventEmitter();
263
+ mockProcess.stderr = new EventEmitter();
264
+ mockSpawn.mockReturnValue(mockProcess);
265
+ const callToolHandler = handlers.get('callTool');
266
+ await expect(callToolHandler({
267
+ params: {
268
+ name: 'run',
269
+ arguments: {
270
+ prompt: 'test prompt',
271
+ workFolder: '/tmp/test'
272
+ }
273
+ }
274
+ })).rejects.toThrow('Failed to start claude CLI process');
275
+ });
276
+ });
277
+ describe('list_processes tool', () => {
278
+ it('should list all processes', async () => {
279
+ const { handlers } = await setupServer();
280
+ // Start a process first
281
+ const mockProcess = new EventEmitter();
282
+ mockProcess.pid = 12347;
283
+ mockProcess.stdout = new EventEmitter();
284
+ mockProcess.stderr = new EventEmitter();
285
+ mockProcess.kill = vi.fn();
286
+ mockSpawn.mockReturnValue(mockProcess);
287
+ const callToolHandler = handlers.get('callTool');
288
+ // Start a process
289
+ await callToolHandler({
290
+ params: {
291
+ name: 'run',
292
+ arguments: {
293
+ prompt: 'test prompt for listing',
294
+ workFolder: '/tmp',
295
+ model: 'sonnet'
296
+ }
297
+ }
298
+ });
299
+ // Simulate JSON output with session_id
300
+ const jsonOutput = {
301
+ session_id: 'list-test-session-789',
302
+ status: 'running'
303
+ };
304
+ mockProcess.stdout.emit('data', JSON.stringify(jsonOutput));
305
+ // List processes
306
+ const listResult = await callToolHandler({
307
+ params: {
308
+ name: 'list_processes',
309
+ arguments: {}
310
+ }
311
+ });
312
+ const processes = JSON.parse(listResult.content[0].text);
313
+ expect(processes).toHaveLength(1);
314
+ expect(processes[0].pid).toBe(12347);
315
+ expect(processes[0].status).toBe('running');
316
+ expect(processes[0].agent).toBe('claude');
317
+ });
318
+ it('should list process with correct agent type', async () => {
319
+ const { handlers } = await setupServer();
320
+ const mockProcess = new EventEmitter();
321
+ mockProcess.pid = 12348;
322
+ mockProcess.stdout = new EventEmitter();
323
+ mockProcess.stderr = new EventEmitter();
324
+ mockProcess.kill = vi.fn();
325
+ mockSpawn.mockReturnValue(mockProcess);
326
+ const callToolHandler = handlers.get('callTool');
327
+ // Start a process
328
+ await callToolHandler({
329
+ params: {
330
+ name: 'run',
331
+ arguments: {
332
+ prompt: 'test prompt',
333
+ workFolder: '/tmp'
334
+ }
335
+ }
336
+ });
337
+ // List processes
338
+ const listResult = await callToolHandler({
339
+ params: {
340
+ name: 'list_processes',
341
+ arguments: {}
342
+ }
343
+ });
344
+ const processes = JSON.parse(listResult.content[0].text);
345
+ expect(processes[0].pid).toBe(12348);
346
+ expect(processes[0].agent).toBe('claude');
347
+ expect(processes[0].status).toBe('running');
348
+ });
349
+ });
350
+ describe('get_result tool', () => {
351
+ it('should get process output', async () => {
352
+ const { handlers } = await setupServer();
353
+ const mockProcess = new EventEmitter();
354
+ mockProcess.pid = 12349;
355
+ mockProcess.stdout = new EventEmitter();
356
+ mockProcess.stderr = new EventEmitter();
357
+ mockProcess.kill = vi.fn();
358
+ mockSpawn.mockReturnValue(mockProcess);
359
+ const callToolHandler = handlers.get('callTool');
360
+ // Start a process
361
+ await callToolHandler({
362
+ params: {
363
+ name: 'run',
364
+ arguments: {
365
+ prompt: 'test prompt',
366
+ workFolder: '/tmp'
367
+ }
368
+ }
369
+ });
370
+ // Simulate JSON output from Claude CLI
371
+ const claudeJsonOutput = {
372
+ session_id: 'test-session-123',
373
+ status: 'success',
374
+ message: 'Task completed'
375
+ };
376
+ mockProcess.stdout.emit('data', JSON.stringify(claudeJsonOutput));
377
+ mockProcess.stderr.emit('data', 'Warning from stderr\n');
378
+ // Get result
379
+ const result = await callToolHandler({
380
+ params: {
381
+ name: 'get_result',
382
+ arguments: {
383
+ pid: 12349
384
+ }
385
+ }
386
+ });
387
+ const processInfo = JSON.parse(result.content[0].text);
388
+ expect(processInfo.pid).toBe(12349);
389
+ expect(processInfo.status).toBe('running');
390
+ expect(processInfo.agentOutput).toEqual(claudeJsonOutput);
391
+ expect(processInfo.session_id).toBe('test-session-123');
392
+ });
393
+ it('should show completed status when process exits', async () => {
394
+ const { handlers } = await setupServer();
395
+ const mockProcess = new EventEmitter();
396
+ mockProcess.pid = 12350;
397
+ mockProcess.stdout = new EventEmitter();
398
+ mockProcess.stderr = new EventEmitter();
399
+ mockProcess.kill = vi.fn();
400
+ mockSpawn.mockReturnValue(mockProcess);
401
+ const callToolHandler = handlers.get('callTool');
402
+ // Start a process
403
+ await callToolHandler({
404
+ params: {
405
+ name: 'run',
406
+ arguments: {
407
+ prompt: 'test prompt',
408
+ workFolder: '/tmp'
409
+ }
410
+ }
411
+ });
412
+ // Simulate process completion with JSON output
413
+ const completedJsonOutput = {
414
+ session_id: 'completed-session-456',
415
+ status: 'completed',
416
+ files_created: ['test.txt'],
417
+ summary: 'Created test file successfully'
418
+ };
419
+ mockProcess.stdout.emit('data', JSON.stringify(completedJsonOutput));
420
+ mockProcess.emit('close', 0);
421
+ // Get result
422
+ const result = await callToolHandler({
423
+ params: {
424
+ name: 'get_result',
425
+ arguments: {
426
+ pid: 12350
427
+ }
428
+ }
429
+ });
430
+ const processInfo = JSON.parse(result.content[0].text);
431
+ expect(processInfo.status).toBe('completed');
432
+ expect(processInfo.exitCode).toBe(0);
433
+ expect(processInfo.agentOutput).toEqual(completedJsonOutput);
434
+ expect(processInfo.session_id).toBe('completed-session-456');
435
+ });
436
+ it('should throw error for non-existent PID', async () => {
437
+ const { handlers } = await setupServer();
438
+ const callToolHandler = handlers.get('callTool');
439
+ await expect(callToolHandler({
440
+ params: {
441
+ name: 'get_result',
442
+ arguments: {
443
+ pid: 99999
444
+ }
445
+ }
446
+ })).rejects.toThrow('Process with PID 99999 not found');
447
+ });
448
+ it('should throw error for invalid PID parameter', async () => {
449
+ const { handlers } = await setupServer();
450
+ const callToolHandler = handlers.get('callTool');
451
+ await expect(callToolHandler({
452
+ params: {
453
+ name: 'get_result',
454
+ arguments: {
455
+ pid: 'not-a-number'
456
+ }
457
+ }
458
+ })).rejects.toThrow('Missing or invalid required parameter: pid');
459
+ });
460
+ it('should handle non-JSON output gracefully', async () => {
461
+ const { handlers } = await setupServer();
462
+ const mockProcess = new EventEmitter();
463
+ mockProcess.pid = 12355;
464
+ mockProcess.stdout = new EventEmitter();
465
+ mockProcess.stderr = new EventEmitter();
466
+ mockProcess.kill = vi.fn();
467
+ mockSpawn.mockReturnValue(mockProcess);
468
+ const callToolHandler = handlers.get('callTool');
469
+ // Start a process
470
+ await callToolHandler({
471
+ params: {
472
+ name: 'run',
473
+ arguments: {
474
+ prompt: 'test prompt',
475
+ workFolder: '/tmp'
476
+ }
477
+ }
478
+ });
479
+ // Simulate non-JSON output
480
+ mockProcess.stdout.emit('data', 'This is plain text output, not JSON');
481
+ mockProcess.stderr.emit('data', 'Some error occurred');
482
+ // Get result
483
+ const result = await callToolHandler({
484
+ params: {
485
+ name: 'get_result',
486
+ arguments: {
487
+ pid: 12355
488
+ }
489
+ }
490
+ });
491
+ const processInfo = JSON.parse(result.content[0].text);
492
+ expect(processInfo.pid).toBe(12355);
493
+ expect(processInfo.status).toBe('running');
494
+ expect(processInfo.stdout).toBe('This is plain text output, not JSON');
495
+ expect(processInfo.stderr).toBe('Some error occurred');
496
+ expect(processInfo.claudeOutput).toBeUndefined();
497
+ expect(processInfo.session_id).toBeUndefined();
498
+ });
499
+ });
500
+ describe('kill_process tool', () => {
501
+ it('should kill a running process', async () => {
502
+ const { handlers } = await setupServer();
503
+ const mockProcess = new EventEmitter();
504
+ mockProcess.pid = 12351;
505
+ mockProcess.stdout = new EventEmitter();
506
+ mockProcess.stderr = new EventEmitter();
507
+ mockProcess.kill = vi.fn();
508
+ mockSpawn.mockReturnValue(mockProcess);
509
+ const callToolHandler = handlers.get('callTool');
510
+ // Start a process
511
+ await callToolHandler({
512
+ params: {
513
+ name: 'run',
514
+ arguments: {
515
+ prompt: 'test prompt',
516
+ workFolder: '/tmp'
517
+ }
518
+ }
519
+ });
520
+ // Kill the process
521
+ const killResult = await callToolHandler({
522
+ params: {
523
+ name: 'kill_process',
524
+ arguments: {
525
+ pid: 12351
526
+ }
527
+ }
528
+ });
529
+ const response = JSON.parse(killResult.content[0].text);
530
+ expect(response.status).toBe('terminated');
531
+ expect(response.message).toBe('Process terminated successfully');
532
+ expect(mockProcess.kill).toHaveBeenCalledWith('SIGTERM');
533
+ });
534
+ it('should handle already terminated process', async () => {
535
+ const { handlers } = await setupServer();
536
+ const mockProcess = new EventEmitter();
537
+ mockProcess.pid = 12352;
538
+ mockProcess.stdout = new EventEmitter();
539
+ mockProcess.stderr = new EventEmitter();
540
+ mockProcess.kill = vi.fn();
541
+ mockSpawn.mockReturnValue(mockProcess);
542
+ const callToolHandler = handlers.get('callTool');
543
+ // Start and complete a process
544
+ await callToolHandler({
545
+ params: {
546
+ name: 'run',
547
+ arguments: {
548
+ prompt: 'test prompt',
549
+ workFolder: '/tmp'
550
+ }
551
+ }
552
+ });
553
+ // Simulate process completion
554
+ mockProcess.emit('close', 0);
555
+ // Try to kill the already completed process
556
+ const killResult = await callToolHandler({
557
+ params: {
558
+ name: 'kill_process',
559
+ arguments: {
560
+ pid: 12352
561
+ }
562
+ }
563
+ });
564
+ const response = JSON.parse(killResult.content[0].text);
565
+ expect(response.status).toBe('completed');
566
+ expect(response.message).toBe('Process already terminated');
567
+ expect(mockProcess.kill).not.toHaveBeenCalled();
568
+ });
569
+ it('should throw error for non-existent PID', async () => {
570
+ const { handlers } = await setupServer();
571
+ const callToolHandler = handlers.get('callTool');
572
+ await expect(callToolHandler({
573
+ params: {
574
+ name: 'kill_process',
575
+ arguments: {
576
+ pid: 99999
577
+ }
578
+ }
579
+ })).rejects.toThrow('Process with PID 99999 not found');
580
+ });
581
+ });
582
+ describe('Tool routing', () => {
583
+ it('should throw error for unknown tool', async () => {
584
+ const { handlers } = await setupServer();
585
+ const callToolHandler = handlers.get('callTool');
586
+ await expect(callToolHandler({
587
+ params: {
588
+ name: 'unknown_tool',
589
+ arguments: {}
590
+ }
591
+ })).rejects.toThrow('Tool unknown_tool not found');
592
+ });
593
+ });
594
+ describe('Process error handling', () => {
595
+ it('should handle process errors', async () => {
596
+ const { handlers } = await setupServer();
597
+ const mockProcess = new EventEmitter();
598
+ mockProcess.pid = 12353;
599
+ mockProcess.stdout = new EventEmitter();
600
+ mockProcess.stderr = new EventEmitter();
601
+ mockProcess.kill = vi.fn();
602
+ mockSpawn.mockReturnValue(mockProcess);
603
+ const callToolHandler = handlers.get('callTool');
604
+ // Start a process
605
+ await callToolHandler({
606
+ params: {
607
+ name: 'run',
608
+ arguments: {
609
+ prompt: 'test prompt',
610
+ workFolder: '/tmp'
611
+ }
612
+ }
613
+ });
614
+ // Simulate process error
615
+ mockProcess.emit('error', new Error('spawn error'));
616
+ // Get result to check error was recorded
617
+ const result = await callToolHandler({
618
+ params: {
619
+ name: 'get_result',
620
+ arguments: {
621
+ pid: 12353
622
+ }
623
+ }
624
+ });
625
+ const processInfo = JSON.parse(result.content[0].text);
626
+ expect(processInfo.status).toBe('failed');
627
+ expect(processInfo.stderr).toContain('Process error: spawn error');
628
+ });
629
+ });
630
+ });