tlc-claude-code 1.4.2 → 1.4.5

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 (113) hide show
  1. package/dashboard/dist/App.js +28 -2
  2. package/dashboard/dist/api/health-diagnostics.d.ts +26 -0
  3. package/dashboard/dist/api/health-diagnostics.js +85 -0
  4. package/dashboard/dist/api/health-diagnostics.test.d.ts +1 -0
  5. package/dashboard/dist/api/health-diagnostics.test.js +126 -0
  6. package/dashboard/dist/api/index.d.ts +5 -0
  7. package/dashboard/dist/api/index.js +5 -0
  8. package/dashboard/dist/api/notes-api.d.ts +18 -0
  9. package/dashboard/dist/api/notes-api.js +68 -0
  10. package/dashboard/dist/api/notes-api.test.d.ts +1 -0
  11. package/dashboard/dist/api/notes-api.test.js +113 -0
  12. package/dashboard/dist/api/safeFetch.d.ts +50 -0
  13. package/dashboard/dist/api/safeFetch.js +135 -0
  14. package/dashboard/dist/api/safeFetch.test.d.ts +1 -0
  15. package/dashboard/dist/api/safeFetch.test.js +215 -0
  16. package/dashboard/dist/api/tasks-api.d.ts +32 -0
  17. package/dashboard/dist/api/tasks-api.js +98 -0
  18. package/dashboard/dist/api/tasks-api.test.d.ts +1 -0
  19. package/dashboard/dist/api/tasks-api.test.js +383 -0
  20. package/dashboard/dist/components/BugsPane.d.ts +20 -0
  21. package/dashboard/dist/components/BugsPane.js +210 -0
  22. package/dashboard/dist/components/BugsPane.test.d.ts +1 -0
  23. package/dashboard/dist/components/BugsPane.test.js +256 -0
  24. package/dashboard/dist/components/HealthPane.d.ts +3 -1
  25. package/dashboard/dist/components/HealthPane.js +44 -6
  26. package/dashboard/dist/components/HealthPane.test.js +105 -2
  27. package/dashboard/dist/components/RouterPane.d.ts +4 -3
  28. package/dashboard/dist/components/RouterPane.js +60 -57
  29. package/dashboard/dist/components/RouterPane.test.js +150 -96
  30. package/dashboard/dist/components/UpdateBanner.d.ts +26 -0
  31. package/dashboard/dist/components/UpdateBanner.js +30 -0
  32. package/dashboard/dist/components/UpdateBanner.test.d.ts +1 -0
  33. package/dashboard/dist/components/UpdateBanner.test.js +96 -0
  34. package/dashboard/dist/components/accessibility.test.d.ts +1 -0
  35. package/dashboard/dist/components/accessibility.test.js +116 -0
  36. package/dashboard/dist/components/layout/MobileNav.d.ts +16 -0
  37. package/dashboard/dist/components/layout/MobileNav.js +31 -0
  38. package/dashboard/dist/components/layout/MobileNav.test.d.ts +1 -0
  39. package/dashboard/dist/components/layout/MobileNav.test.js +111 -0
  40. package/dashboard/dist/components/performance.test.d.ts +1 -0
  41. package/dashboard/dist/components/performance.test.js +114 -0
  42. package/dashboard/dist/components/responsive.test.d.ts +1 -0
  43. package/dashboard/dist/components/responsive.test.js +114 -0
  44. package/dashboard/dist/components/ui/Dropdown.d.ts +22 -0
  45. package/dashboard/dist/components/ui/Dropdown.js +109 -0
  46. package/dashboard/dist/components/ui/Dropdown.test.d.ts +1 -0
  47. package/dashboard/dist/components/ui/Dropdown.test.js +105 -0
  48. package/dashboard/dist/components/ui/EmptyState.d.ts +14 -0
  49. package/dashboard/dist/components/ui/EmptyState.js +58 -0
  50. package/dashboard/dist/components/ui/EmptyState.test.d.ts +1 -0
  51. package/dashboard/dist/components/ui/EmptyState.test.js +97 -0
  52. package/dashboard/dist/components/ui/ErrorState.d.ts +17 -0
  53. package/dashboard/dist/components/ui/ErrorState.js +80 -0
  54. package/dashboard/dist/components/ui/ErrorState.test.d.ts +1 -0
  55. package/dashboard/dist/components/ui/ErrorState.test.js +166 -0
  56. package/dashboard/dist/components/ui/Modal.d.ts +13 -0
  57. package/dashboard/dist/components/ui/Modal.js +25 -0
  58. package/dashboard/dist/components/ui/Modal.test.d.ts +1 -0
  59. package/dashboard/dist/components/ui/Modal.test.js +91 -0
  60. package/dashboard/dist/components/ui/Skeleton.d.ts +32 -0
  61. package/dashboard/dist/components/ui/Skeleton.js +48 -0
  62. package/dashboard/dist/components/ui/Skeleton.test.d.ts +1 -0
  63. package/dashboard/dist/components/ui/Skeleton.test.js +125 -0
  64. package/dashboard/dist/components/ui/Toast.d.ts +32 -0
  65. package/dashboard/dist/components/ui/Toast.js +21 -0
  66. package/dashboard/dist/components/ui/Toast.test.d.ts +1 -0
  67. package/dashboard/dist/components/ui/Toast.test.js +118 -0
  68. package/dashboard/dist/hooks/useTheme.d.ts +37 -0
  69. package/dashboard/dist/hooks/useTheme.js +96 -0
  70. package/dashboard/dist/hooks/useTheme.test.d.ts +1 -0
  71. package/dashboard/dist/hooks/useTheme.test.js +94 -0
  72. package/dashboard/dist/hooks/useWebSocket.d.ts +17 -0
  73. package/dashboard/dist/hooks/useWebSocket.js +100 -0
  74. package/dashboard/dist/hooks/useWebSocket.test.d.ts +1 -0
  75. package/dashboard/dist/hooks/useWebSocket.test.js +115 -0
  76. package/dashboard/dist/stores/projectStore.d.ts +44 -0
  77. package/dashboard/dist/stores/projectStore.js +76 -0
  78. package/dashboard/dist/stores/projectStore.test.d.ts +1 -0
  79. package/dashboard/dist/stores/projectStore.test.js +114 -0
  80. package/dashboard/dist/stores/uiStore.d.ts +29 -0
  81. package/dashboard/dist/stores/uiStore.js +72 -0
  82. package/dashboard/dist/stores/uiStore.test.d.ts +1 -0
  83. package/dashboard/dist/stores/uiStore.test.js +93 -0
  84. package/dashboard/package.json +6 -3
  85. package/docker-compose.dev.yml +6 -1
  86. package/package.json +1 -1
  87. package/server/dashboard/index.html +1545 -791
  88. package/server/index.js +64 -0
  89. package/server/lib/api-provider.js +104 -186
  90. package/server/lib/api-provider.test.js +238 -336
  91. package/server/lib/cli-detector.js +90 -166
  92. package/server/lib/cli-detector.test.js +114 -269
  93. package/server/lib/cli-provider.js +142 -212
  94. package/server/lib/cli-provider.test.js +196 -349
  95. package/server/lib/debug.test.js +1 -1
  96. package/server/lib/devserver-router-api.js +54 -249
  97. package/server/lib/devserver-router-api.test.js +126 -426
  98. package/server/lib/introspect.js +309 -0
  99. package/server/lib/introspect.test.js +286 -0
  100. package/server/lib/model-router.js +107 -245
  101. package/server/lib/model-router.test.js +122 -313
  102. package/server/lib/output-schemas.js +146 -269
  103. package/server/lib/output-schemas.test.js +106 -307
  104. package/server/lib/provider-interface.js +99 -153
  105. package/server/lib/provider-interface.test.js +228 -394
  106. package/server/lib/provider-queue.js +164 -158
  107. package/server/lib/provider-queue.test.js +186 -315
  108. package/server/lib/router-config.js +99 -221
  109. package/server/lib/router-config.test.js +83 -237
  110. package/server/lib/router-setup-command.js +94 -419
  111. package/server/lib/router-setup-command.test.js +96 -375
  112. package/server/lib/router-status-api.js +93 -0
  113. package/server/lib/router-status-api.test.js +270 -0
@@ -1,349 +1,196 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import {
3
- createCLIProvider,
4
- buildArgs,
5
- parseOutput,
6
- runLocal,
7
- runViaDevserver,
8
- } from './cli-provider.js';
9
-
10
- // Mock child_process
11
- vi.mock('child_process', () => ({
12
- spawn: vi.fn(),
13
- execSync: vi.fn(),
14
- }));
15
-
16
- // Mock fetch for devserver calls
17
- global.fetch = vi.fn();
18
-
19
- import { spawn } from 'child_process';
20
-
21
- describe('cli-provider', () => {
22
- beforeEach(() => {
23
- vi.clearAllMocks();
24
- });
25
-
26
- describe('createCLIProvider', () => {
27
- it('creates provider with CLI type', () => {
28
- const provider = createCLIProvider({
29
- name: 'claude',
30
- command: 'claude',
31
- headlessArgs: ['-p', '--output-format', 'json'],
32
- capabilities: ['review', 'code-gen'],
33
- });
34
-
35
- expect(provider.type).toBe('cli');
36
- expect(provider.name).toBe('claude');
37
- });
38
-
39
- it('sets detected based on CLI detection', () => {
40
- const provider = createCLIProvider({
41
- name: 'claude',
42
- command: 'claude',
43
- detected: true,
44
- });
45
-
46
- expect(provider.detected).toBe(true);
47
- });
48
-
49
- it('defaults detected to false', () => {
50
- const provider = createCLIProvider({
51
- name: 'claude',
52
- command: 'claude',
53
- });
54
-
55
- expect(provider.detected).toBe(false);
56
- });
57
- });
58
-
59
- describe('runLocal', () => {
60
- it('spawns claude -p with args', async () => {
61
- const mockProcess = {
62
- stdout: { on: vi.fn() },
63
- stderr: { on: vi.fn() },
64
- on: vi.fn(),
65
- };
66
-
67
- spawn.mockReturnValue(mockProcess);
68
-
69
- // Simulate process completion
70
- setTimeout(() => {
71
- const stdoutCallback = mockProcess.stdout.on.mock.calls.find(c => c[0] === 'data')[1];
72
- stdoutCallback(Buffer.from('{"result": "ok"}'));
73
-
74
- const closeCallback = mockProcess.on.mock.calls.find(c => c[0] === 'close')[1];
75
- closeCallback(0);
76
- }, 10);
77
-
78
- const result = await runLocal('claude', 'test prompt', {
79
- headlessArgs: ['-p', '--output-format', 'json'],
80
- });
81
-
82
- expect(spawn).toHaveBeenCalledWith(
83
- 'claude',
84
- expect.arrayContaining(['-p', '--output-format', 'json']),
85
- expect.any(Object)
86
- );
87
- expect(result.exitCode).toBe(0);
88
- });
89
-
90
- it('spawns codex exec with args', async () => {
91
- const mockProcess = {
92
- stdout: { on: vi.fn() },
93
- stderr: { on: vi.fn() },
94
- on: vi.fn(),
95
- };
96
-
97
- spawn.mockReturnValue(mockProcess);
98
-
99
- setTimeout(() => {
100
- const stdoutCallback = mockProcess.stdout.on.mock.calls.find(c => c[0] === 'data')[1];
101
- stdoutCallback(Buffer.from('{"result": "ok"}'));
102
-
103
- const closeCallback = mockProcess.on.mock.calls.find(c => c[0] === 'close')[1];
104
- closeCallback(0);
105
- }, 10);
106
-
107
- await runLocal('codex', 'test prompt', {
108
- headlessArgs: ['exec', '--json', '--sandbox', 'read-only'],
109
- });
110
-
111
- expect(spawn).toHaveBeenCalledWith(
112
- 'codex',
113
- expect.arrayContaining(['exec', '--json', '--sandbox', 'read-only']),
114
- expect.any(Object)
115
- );
116
- });
117
-
118
- it('spawns gemini -p with args', async () => {
119
- const mockProcess = {
120
- stdout: { on: vi.fn() },
121
- stderr: { on: vi.fn() },
122
- on: vi.fn(),
123
- };
124
-
125
- spawn.mockReturnValue(mockProcess);
126
-
127
- setTimeout(() => {
128
- const stdoutCallback = mockProcess.stdout.on.mock.calls.find(c => c[0] === 'data')[1];
129
- stdoutCallback(Buffer.from('{"result": "ok"}'));
130
-
131
- const closeCallback = mockProcess.on.mock.calls.find(c => c[0] === 'close')[1];
132
- closeCallback(0);
133
- }, 10);
134
-
135
- await runLocal('gemini', 'test prompt', {
136
- headlessArgs: ['-p', '--output-format', 'json'],
137
- });
138
-
139
- expect(spawn).toHaveBeenCalledWith(
140
- 'gemini',
141
- expect.arrayContaining(['-p', '--output-format', 'json']),
142
- expect.any(Object)
143
- );
144
- });
145
-
146
- it('parses JSON output', async () => {
147
- const mockProcess = {
148
- stdout: { on: vi.fn() },
149
- stderr: { on: vi.fn() },
150
- on: vi.fn(),
151
- };
152
-
153
- spawn.mockReturnValue(mockProcess);
154
-
155
- setTimeout(() => {
156
- const stdoutCallback = mockProcess.stdout.on.mock.calls.find(c => c[0] === 'data')[1];
157
- stdoutCallback(Buffer.from('{"summary": "LGTM", "score": 85}'));
158
-
159
- const closeCallback = mockProcess.on.mock.calls.find(c => c[0] === 'close')[1];
160
- closeCallback(0);
161
- }, 10);
162
-
163
- const result = await runLocal('claude', 'test', { headlessArgs: ['-p'] });
164
-
165
- expect(result.parsed).toEqual({ summary: 'LGTM', score: 85 });
166
- });
167
-
168
- it('handles non-JSON output', async () => {
169
- const mockProcess = {
170
- stdout: { on: vi.fn() },
171
- stderr: { on: vi.fn() },
172
- on: vi.fn(),
173
- };
174
-
175
- spawn.mockReturnValue(mockProcess);
176
-
177
- setTimeout(() => {
178
- const stdoutCallback = mockProcess.stdout.on.mock.calls.find(c => c[0] === 'data')[1];
179
- stdoutCallback(Buffer.from('Plain text output'));
180
-
181
- const closeCallback = mockProcess.on.mock.calls.find(c => c[0] === 'close')[1];
182
- closeCallback(0);
183
- }, 10);
184
-
185
- const result = await runLocal('claude', 'test', { headlessArgs: ['-p'] });
186
-
187
- expect(result.raw).toBe('Plain text output');
188
- expect(result.parsed).toBeNull();
189
- });
190
-
191
- it('respects timeout', async () => {
192
- const mockProcess = {
193
- stdout: { on: vi.fn() },
194
- stderr: { on: vi.fn() },
195
- on: vi.fn(),
196
- kill: vi.fn(),
197
- };
198
-
199
- spawn.mockReturnValue(mockProcess);
200
-
201
- // Don't complete the process - let it timeout
202
- const promise = runLocal('claude', 'test', {
203
- headlessArgs: ['-p'],
204
- timeout: 50,
205
- });
206
-
207
- await expect(promise).rejects.toThrow(/timeout/i);
208
- });
209
- });
210
-
211
- describe('runViaDevserver', () => {
212
- it('posts to devserver API', async () => {
213
- global.fetch.mockResolvedValue({
214
- ok: true,
215
- json: () => Promise.resolve({ taskId: 'task-123' }),
216
- });
217
-
218
- // Mock polling response
219
- global.fetch
220
- .mockResolvedValueOnce({
221
- ok: true,
222
- json: () => Promise.resolve({ taskId: 'task-123' }),
223
- })
224
- .mockResolvedValueOnce({
225
- ok: true,
226
- json: () => Promise.resolve({
227
- status: 'completed',
228
- result: { raw: '{}', parsed: {}, exitCode: 0 },
229
- }),
230
- });
231
-
232
- const result = await runViaDevserver({
233
- devserverUrl: 'https://devserver.example.com',
234
- provider: 'claude',
235
- prompt: 'test prompt',
236
- opts: {},
237
- });
238
-
239
- expect(global.fetch).toHaveBeenCalledWith(
240
- 'https://devserver.example.com/api/run',
241
- expect.objectContaining({
242
- method: 'POST',
243
- })
244
- );
245
- });
246
-
247
- it('polls for result', async () => {
248
- global.fetch
249
- .mockResolvedValueOnce({
250
- ok: true,
251
- json: () => Promise.resolve({ taskId: 'task-123' }),
252
- })
253
- .mockResolvedValueOnce({
254
- ok: true,
255
- json: () => Promise.resolve({ status: 'running' }),
256
- })
257
- .mockResolvedValueOnce({
258
- ok: true,
259
- json: () => Promise.resolve({
260
- status: 'completed',
261
- result: { raw: '{"done": true}', parsed: { done: true }, exitCode: 0 },
262
- }),
263
- });
264
-
265
- const result = await runViaDevserver({
266
- devserverUrl: 'https://devserver.example.com',
267
- provider: 'claude',
268
- prompt: 'test',
269
- opts: {},
270
- pollInterval: 10,
271
- });
272
-
273
- expect(global.fetch).toHaveBeenCalledTimes(3);
274
- expect(result.parsed).toEqual({ done: true });
275
- });
276
- });
277
-
278
- describe('buildArgs', () => {
279
- it('includes output-format json', () => {
280
- const args = buildArgs('claude', 'test prompt', {
281
- headlessArgs: ['-p', '--output-format', 'json'],
282
- });
283
-
284
- expect(args).toContain('--output-format');
285
- expect(args).toContain('json');
286
- });
287
-
288
- it('includes sandbox for codex', () => {
289
- const args = buildArgs('codex', 'test prompt', {
290
- headlessArgs: ['exec', '--json', '--sandbox', 'read-only'],
291
- });
292
-
293
- expect(args).toContain('--sandbox');
294
- expect(args).toContain('read-only');
295
- });
296
-
297
- it('includes prompt in args', () => {
298
- const args = buildArgs('claude', 'review this code', {
299
- headlessArgs: ['-p'],
300
- });
301
-
302
- expect(args).toContain('review this code');
303
- });
304
-
305
- it('includes cwd option', () => {
306
- const args = buildArgs('claude', 'test', {
307
- headlessArgs: ['-p'],
308
- cwd: '/project/dir',
309
- });
310
-
311
- // cwd is passed to spawn options, not args
312
- // But buildArgs should handle it
313
- expect(args).toBeDefined();
314
- });
315
- });
316
-
317
- describe('parseOutput', () => {
318
- it('parses valid JSON', () => {
319
- const result = parseOutput('{"key": "value"}');
320
- expect(result).toEqual({ key: 'value' });
321
- });
322
-
323
- it('returns null for invalid JSON', () => {
324
- const result = parseOutput('not json');
325
- expect(result).toBeNull();
326
- });
327
-
328
- it('handles empty output', () => {
329
- const result = parseOutput('');
330
- expect(result).toBeNull();
331
- });
332
-
333
- it('handles multiline JSON', () => {
334
- const result = parseOutput(`{
335
- "key": "value",
336
- "nested": {
337
- "array": [1, 2, 3]
338
- }
339
- }`);
340
- expect(result.nested.array).toEqual([1, 2, 3]);
341
- });
342
-
343
- it('extracts JSON from mixed output', () => {
344
- // Some CLIs may output text before/after JSON
345
- const result = parseOutput('Some text\n{"result": "ok"}\nMore text');
346
- expect(result).toEqual({ result: 'ok' });
347
- });
348
- });
349
- });
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { CLIProvider, buildArgs } from './cli-provider.js';
3
+
4
+ describe('CLI Provider', () => {
5
+ describe('runLocal', () => {
6
+ it('spawns claude -p with args', async () => {
7
+ const provider = new CLIProvider({
8
+ name: 'claude',
9
+ command: 'claude',
10
+ headlessArgs: ['-p', '--output-format', 'json'],
11
+ });
12
+
13
+ // Mock spawn
14
+ provider._spawn = vi.fn().mockResolvedValue({
15
+ stdout: '{"result": "test"}',
16
+ exitCode: 0,
17
+ });
18
+
19
+ await provider.runLocal('test prompt');
20
+
21
+ expect(provider._spawn).toHaveBeenCalledWith(
22
+ 'claude',
23
+ expect.arrayContaining(['-p', '--output-format', 'json'])
24
+ );
25
+ });
26
+
27
+ it('spawns codex exec with args', async () => {
28
+ const provider = new CLIProvider({
29
+ name: 'codex',
30
+ command: 'codex',
31
+ headlessArgs: ['exec', '--json'],
32
+ });
33
+
34
+ provider._spawn = vi.fn().mockResolvedValue({
35
+ stdout: '{"result": "test"}',
36
+ exitCode: 0,
37
+ });
38
+
39
+ await provider.runLocal('test prompt');
40
+
41
+ expect(provider._spawn).toHaveBeenCalledWith(
42
+ 'codex',
43
+ expect.arrayContaining(['exec', '--json'])
44
+ );
45
+ });
46
+
47
+ it('spawns gemini -p with args', async () => {
48
+ const provider = new CLIProvider({
49
+ name: 'gemini',
50
+ command: 'gemini',
51
+ headlessArgs: ['-p', '--output-format', 'json'],
52
+ });
53
+
54
+ provider._spawn = vi.fn().mockResolvedValue({
55
+ stdout: '{"result": "test"}',
56
+ exitCode: 0,
57
+ });
58
+
59
+ await provider.runLocal('test prompt');
60
+
61
+ expect(provider._spawn).toHaveBeenCalledWith(
62
+ 'gemini',
63
+ expect.arrayContaining(['-p', '--output-format', 'json'])
64
+ );
65
+ });
66
+
67
+ it('parses JSON output', async () => {
68
+ const provider = new CLIProvider({
69
+ name: 'claude',
70
+ command: 'claude',
71
+ headlessArgs: ['-p'],
72
+ });
73
+
74
+ provider._spawn = vi.fn().mockResolvedValue({
75
+ stdout: '{"summary": "Test result", "score": 85}',
76
+ exitCode: 0,
77
+ });
78
+
79
+ const result = await provider.runLocal('test');
80
+
81
+ expect(result.parsed).toEqual({ summary: 'Test result', score: 85 });
82
+ });
83
+
84
+ it('handles non-JSON output', async () => {
85
+ const provider = new CLIProvider({
86
+ name: 'claude',
87
+ command: 'claude',
88
+ headlessArgs: ['-p'],
89
+ });
90
+
91
+ provider._spawn = vi.fn().mockResolvedValue({
92
+ stdout: 'Plain text response',
93
+ exitCode: 0,
94
+ });
95
+
96
+ const result = await provider.runLocal('test');
97
+
98
+ expect(result.raw).toBe('Plain text response');
99
+ expect(result.parsed).toBeNull();
100
+ });
101
+
102
+ it('respects timeout', async () => {
103
+ const provider = new CLIProvider({
104
+ name: 'claude',
105
+ command: 'claude',
106
+ headlessArgs: ['-p'],
107
+ timeout: 100,
108
+ });
109
+
110
+ provider._spawn = vi.fn().mockImplementation(() =>
111
+ new Promise(r => setTimeout(r, 1000))
112
+ );
113
+
114
+ await expect(provider.runLocal('test')).rejects.toThrow(/timeout/i);
115
+ });
116
+ });
117
+
118
+ describe('runViaDevserver', () => {
119
+ it('posts to devserver', async () => {
120
+ const provider = new CLIProvider({
121
+ name: 'claude',
122
+ command: 'claude',
123
+ devserverUrl: 'https://dev.example.com',
124
+ });
125
+
126
+ provider._fetch = vi.fn().mockImplementation((url) => {
127
+ if (url.includes('/api/run')) {
128
+ return Promise.resolve({
129
+ ok: true,
130
+ json: () => Promise.resolve({ taskId: '123' }),
131
+ });
132
+ }
133
+ // Return completed immediately for polling
134
+ return Promise.resolve({
135
+ ok: true,
136
+ json: () => Promise.resolve({ status: 'completed', result: {} }),
137
+ });
138
+ });
139
+
140
+ await provider.runViaDevserver('test prompt');
141
+
142
+ expect(provider._fetch).toHaveBeenCalledWith(
143
+ 'https://dev.example.com/api/run',
144
+ expect.objectContaining({ method: 'POST' })
145
+ );
146
+ });
147
+
148
+ it('polls for result', async () => {
149
+ const provider = new CLIProvider({
150
+ name: 'claude',
151
+ command: 'claude',
152
+ devserverUrl: 'https://dev.example.com',
153
+ });
154
+
155
+ let pollCount = 0;
156
+ provider._fetch = vi.fn().mockImplementation((url) => {
157
+ if (url.includes('/api/run')) {
158
+ return Promise.resolve({
159
+ ok: true,
160
+ json: () => Promise.resolve({ taskId: '123' }),
161
+ });
162
+ }
163
+ pollCount++;
164
+ if (pollCount < 2) {
165
+ return Promise.resolve({
166
+ ok: true,
167
+ json: () => Promise.resolve({ status: 'running' }),
168
+ });
169
+ }
170
+ return Promise.resolve({
171
+ ok: true,
172
+ json: () => Promise.resolve({ status: 'completed', result: { data: 'test' } }),
173
+ });
174
+ });
175
+
176
+ const result = await provider.runViaDevserver('test');
177
+
178
+ expect(pollCount).toBeGreaterThanOrEqual(2);
179
+ expect(result.parsed).toEqual({ data: 'test' });
180
+ });
181
+ });
182
+
183
+ describe('buildArgs', () => {
184
+ it('includes output-format json', () => {
185
+ const args = buildArgs('claude', 'test prompt', { outputFormat: 'json' });
186
+ expect(args).toContain('--output-format');
187
+ expect(args).toContain('json');
188
+ });
189
+
190
+ it('includes sandbox for codex', () => {
191
+ const args = buildArgs('codex', 'test prompt', { sandbox: 'read-only' });
192
+ expect(args).toContain('--sandbox');
193
+ expect(args).toContain('read-only');
194
+ });
195
+ });
196
+ });
@@ -2,7 +2,7 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
2
2
  import {
3
3
  findOrphanedAgents,
4
4
  resetCleanup,
5
- } from './server/lib/agent-cleanup.js';
5
+ } from './agent-cleanup.js';
6
6
  import { getAgentRegistry, resetRegistry } from './server/lib/agent-registry.js';
7
7
  import { STATES } from './server/lib/agent-state.js';
8
8