@cursorpool-dev/cli 0.5.6

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 (105) hide show
  1. package/bin/cursor-pool.mjs +9 -0
  2. package/bin/cursor-pool.ts +169 -0
  3. package/node_modules/@cursor-pool/extension/dist/extension.js +2910 -0
  4. package/node_modules/@cursor-pool/extension/package.json +64 -0
  5. package/node_modules/@cursor-pool/extension/resources/cursor-pool.svg +6 -0
  6. package/node_modules/@cursor-pool/extension/src/api.ts +545 -0
  7. package/node_modules/@cursor-pool/extension/src/extension.ts +104 -0
  8. package/node_modules/@cursor-pool/extension/src/index.ts +1 -0
  9. package/node_modules/@cursor-pool/extension/src/panel.ts +569 -0
  10. package/node_modules/@cursor-pool/extension/src/runtime.ts +22 -0
  11. package/node_modules/@cursor-pool/extension/test/panel.test.ts +1785 -0
  12. package/node_modules/@cursor-pool/patcher/package.json +17 -0
  13. package/node_modules/@cursor-pool/patcher/src/alwaysLocalMarker.ts +86 -0
  14. package/node_modules/@cursor-pool/patcher/src/hash.ts +7 -0
  15. package/node_modules/@cursor-pool/patcher/src/index.ts +55 -0
  16. package/node_modules/@cursor-pool/patcher/src/marker.ts +159 -0
  17. package/node_modules/@cursor-pool/patcher/src/patchCursorAgentExec.ts +154 -0
  18. package/node_modules/@cursor-pool/patcher/src/patchCursorAlwaysLocal.ts +142 -0
  19. package/node_modules/@cursor-pool/patcher/src/patchCursorWorkbenchAuthGate.ts +140 -0
  20. package/node_modules/@cursor-pool/patcher/src/restoreCursorAgentExec.ts +52 -0
  21. package/node_modules/@cursor-pool/patcher/src/restoreCursorAlwaysLocal.ts +52 -0
  22. package/node_modules/@cursor-pool/patcher/src/restoreCursorWorkbenchAuthGate.ts +70 -0
  23. package/node_modules/@cursor-pool/patcher/src/workbenchAuthGateMarker.ts +243 -0
  24. package/node_modules/@cursor-pool/patcher/test/patchCursorAgentExec.test.ts +630 -0
  25. package/node_modules/@cursor-pool/patcher/test/patchCursorAlwaysLocal.test.ts +144 -0
  26. package/node_modules/@cursor-pool/patcher/test/patchCursorWorkbench.test.ts +770 -0
  27. package/node_modules/@cursor-pool/patcher/test/restoreCursorAgentExec.test.ts +139 -0
  28. package/node_modules/@cursor-pool/service/package.json +17 -0
  29. package/node_modules/@cursor-pool/service/src/canary.ts +61 -0
  30. package/node_modules/@cursor-pool/service/src/diagnostics.ts +385 -0
  31. package/node_modules/@cursor-pool/service/src/entry.ts +161 -0
  32. package/node_modules/@cursor-pool/service/src/health.ts +10 -0
  33. package/node_modules/@cursor-pool/service/src/index.ts +29 -0
  34. package/node_modules/@cursor-pool/service/src/metadata.ts +22 -0
  35. package/node_modules/@cursor-pool/service/src/platformSession.ts +1178 -0
  36. package/node_modules/@cursor-pool/service/src/requestCheck.ts +81 -0
  37. package/node_modules/@cursor-pool/service/src/requestGate.ts +100 -0
  38. package/node_modules/@cursor-pool/service/src/requestGateway.ts +441 -0
  39. package/node_modules/@cursor-pool/service/src/runtime.ts +48 -0
  40. package/node_modules/@cursor-pool/service/src/server.ts +939 -0
  41. package/node_modules/@cursor-pool/service/src/takeover.ts +111 -0
  42. package/node_modules/@cursor-pool/service/test/canary.test.ts +140 -0
  43. package/node_modules/@cursor-pool/service/test/diagnostics.test.ts +506 -0
  44. package/node_modules/@cursor-pool/service/test/metadata.test.ts +63 -0
  45. package/node_modules/@cursor-pool/service/test/platformSession.test.ts +2428 -0
  46. package/node_modules/@cursor-pool/service/test/requestCheck.test.ts +152 -0
  47. package/node_modules/@cursor-pool/service/test/requestGate.test.ts +207 -0
  48. package/node_modules/@cursor-pool/service/test/requestGateway.test.ts +466 -0
  49. package/node_modules/@cursor-pool/service/test/runtime.test.ts +47 -0
  50. package/node_modules/@cursor-pool/service/test/server.test.ts +2570 -0
  51. package/node_modules/@cursor-pool/shared/package.json +17 -0
  52. package/node_modules/@cursor-pool/shared/src/clientConfig.ts +49 -0
  53. package/node_modules/@cursor-pool/shared/src/index.ts +14 -0
  54. package/node_modules/@cursor-pool/shared/src/manifest.ts +36 -0
  55. package/node_modules/@cursor-pool/shared/src/metadata.ts +19 -0
  56. package/node_modules/@cursor-pool/shared/src/paths.ts +5 -0
  57. package/node_modules/@cursor-pool/shared/src/runtime.ts +3 -0
  58. package/node_modules/@cursor-pool/shared/test/index.test.ts +56 -0
  59. package/node_modules/@cursor-pool/shared/test/manifest.test.ts +65 -0
  60. package/node_modules/@cursor-pool/shared/test/metadata.test.ts +25 -0
  61. package/node_modules/@cursor-pool/shared/test/runtime.test.ts +8 -0
  62. package/package.json +28 -0
  63. package/src/adHocResign.ts +65 -0
  64. package/src/autostart.ts +240 -0
  65. package/src/compat.ts +282 -0
  66. package/src/confirm.ts +76 -0
  67. package/src/cursor.ts +94 -0
  68. package/src/diagnostics.ts +558 -0
  69. package/src/environment.ts +18 -0
  70. package/src/extensionBundle.ts +111 -0
  71. package/src/extensionLink.ts +168 -0
  72. package/src/index.ts +23 -0
  73. package/src/install.ts +614 -0
  74. package/src/installRecord.ts +105 -0
  75. package/src/launch.ts +182 -0
  76. package/src/patchSet.ts +182 -0
  77. package/src/platform.ts +132 -0
  78. package/src/repair.ts +383 -0
  79. package/src/restore.ts +153 -0
  80. package/src/serviceCommands.ts +79 -0
  81. package/src/serviceProcess.ts +188 -0
  82. package/src/status.ts +241 -0
  83. package/src/target.ts +37 -0
  84. package/src/trial.ts +133 -0
  85. package/src/uninstall.ts +213 -0
  86. package/test/autostart.test.ts +151 -0
  87. package/test/compat.test.ts +192 -0
  88. package/test/confirm.test.ts +114 -0
  89. package/test/cursor-pool-bin.test.ts +658 -0
  90. package/test/cursor.test.ts +20 -0
  91. package/test/diagnostics.test.ts +709 -0
  92. package/test/e2e-install.test.ts +773 -0
  93. package/test/extensionBundle.test.ts +161 -0
  94. package/test/extensionLink.test.ts +209 -0
  95. package/test/install.test.ts +862 -0
  96. package/test/installRecord.test.ts +107 -0
  97. package/test/launch.test.ts +138 -0
  98. package/test/platform.test.ts +226 -0
  99. package/test/repair.test.ts +575 -0
  100. package/test/restore.test.ts +211 -0
  101. package/test/serviceCommands.test.ts +135 -0
  102. package/test/serviceProcess.test.ts +280 -0
  103. package/test/status.test.ts +615 -0
  104. package/test/target.test.ts +49 -0
  105. package/test/trial.test.ts +146 -0
@@ -0,0 +1,2570 @@
1
+ import assert from 'node:assert/strict';
2
+ import { createServer as createNodeServer, request as createHttpRequest, type IncomingMessage, type ServerResponse } from 'node:http';
3
+ import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import test from 'node:test';
7
+ import { startServer } from '../src/server';
8
+ import { writeClientConfig } from '../../shared/src/clientConfig';
9
+
10
+ async function requestJson(
11
+ port: number,
12
+ path: string,
13
+ options: { method?: string; body?: Record<string, unknown>; headers?: Record<string, string> } = {},
14
+ ) {
15
+ const response = await fetch(`http://127.0.0.1:${port}${path}`, {
16
+ method: options.method ?? 'GET',
17
+ headers: {
18
+ ...(options.body ? { 'content-type': 'application/json' } : {}),
19
+ ...options.headers,
20
+ },
21
+ body: options.body ? JSON.stringify(options.body) : undefined,
22
+ });
23
+
24
+ return {
25
+ status: response.status,
26
+ body: await response.json(),
27
+ };
28
+ }
29
+
30
+ async function requestRaw(
31
+ port: number,
32
+ path: string,
33
+ options: { method?: string; headers?: Record<string, string> } = {},
34
+ ) {
35
+ return fetch(`http://127.0.0.1:${port}${path}`, {
36
+ method: options.method ?? 'GET',
37
+ headers: options.headers,
38
+ });
39
+ }
40
+
41
+ async function requestText(
42
+ port: number,
43
+ path: string,
44
+ options: { method?: string; body?: Record<string, unknown>; headers?: Record<string, string> } = {},
45
+ ) {
46
+ const response = await fetch(`http://127.0.0.1:${port}${path}`, {
47
+ method: options.method ?? 'GET',
48
+ headers: {
49
+ ...(options.body ? { 'content-type': 'application/json' } : {}),
50
+ ...options.headers,
51
+ },
52
+ body: options.body ? JSON.stringify(options.body) : undefined,
53
+ });
54
+
55
+ return {
56
+ status: response.status,
57
+ text: await response.text(),
58
+ };
59
+ }
60
+
61
+ async function postJsonAndAbort(port: number, path: string, body: Record<string, unknown>, abortAfterMs = 20) {
62
+ await new Promise<void>((resolve) => {
63
+ const payload = JSON.stringify(body);
64
+ const request = createHttpRequest({
65
+ host: '127.0.0.1',
66
+ port,
67
+ path,
68
+ method: 'POST',
69
+ headers: {
70
+ 'content-type': 'application/json',
71
+ 'content-length': Buffer.byteLength(payload),
72
+ },
73
+ });
74
+ request.on('error', () => resolve());
75
+ request.write(payload);
76
+ request.end();
77
+ setTimeout(() => {
78
+ request.destroy();
79
+ resolve();
80
+ }, abortAfterMs);
81
+ });
82
+ }
83
+
84
+ async function waitFor(predicate: () => boolean, timeoutMs = 500) {
85
+ const started = Date.now();
86
+ while (!predicate()) {
87
+ if (Date.now() - started > timeoutMs) {
88
+ return false;
89
+ }
90
+ await new Promise((resolve) => setTimeout(resolve, 10));
91
+ }
92
+ return true;
93
+ }
94
+
95
+ function writeJson(response: ServerResponse, statusCode: number, body: Record<string, unknown>) {
96
+ const payload = JSON.stringify(body);
97
+ response.writeHead(statusCode, {
98
+ 'content-type': 'application/json',
99
+ 'content-length': Buffer.byteLength(payload),
100
+ });
101
+ response.end(payload);
102
+ }
103
+
104
+ async function createPlatformApiServer(handler: (request: IncomingMessage, response: ServerResponse) => void) {
105
+ const server = createNodeServer(handler);
106
+ await new Promise<void>((resolve) => {
107
+ server.listen(0, '127.0.0.1', resolve);
108
+ });
109
+ const address = server.address();
110
+ assert.equal(typeof address, 'object');
111
+
112
+ return {
113
+ apiBaseUrl: `http://127.0.0.1:${address?.port}`,
114
+ close: () => new Promise<void>((resolve, reject) => {
115
+ server.close((error) => (error ? reject(error) : resolve()));
116
+ }),
117
+ };
118
+ }
119
+
120
+ test('local service exposes health, metadata, extension status, and mode endpoints on loopback', async () => {
121
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-'));
122
+ const runtimeFile = join(tempDir, 'runtime.json');
123
+ const forwarded: Record<string, unknown>[] = [];
124
+ const service = await startServer({
125
+ runtimeFile,
126
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
127
+ onMetadata(metadata) {
128
+ forwarded.push(metadata);
129
+ },
130
+ });
131
+
132
+ try {
133
+ const address = service.server.address();
134
+ assert.equal(typeof address, 'object');
135
+ assert.equal(address?.address, '127.0.0.1');
136
+ assert.equal(service.host, '127.0.0.1');
137
+
138
+ const health = await requestJson(service.port, '/health');
139
+ assert.equal(health.status, 200);
140
+ assert.equal(health.body.host, '127.0.0.1');
141
+ assert.equal(health.body.port, service.port);
142
+ assert.equal(health.body.runtimeId, service.runtimeId);
143
+ assert.equal(health.body.ok, true);
144
+
145
+ const metadata = await requestJson(service.port, '/cursor-metadata', {
146
+ method: 'POST',
147
+ body: {
148
+ requestId: 'req-1',
149
+ requestType: 'agent',
150
+ source: 'cursor-agent-exec',
151
+ prompt: 'secret prompt',
152
+ apiKey: 'secret-key',
153
+ },
154
+ });
155
+ assert.equal(metadata.status, 200);
156
+ assert.deepEqual(forwarded[0], {
157
+ requestId: 'req-1',
158
+ model: 'unknown',
159
+ requestType: 'agent',
160
+ source: 'cursor-agent-exec',
161
+ });
162
+ assert.deepEqual(metadata.body.metadata, forwarded[0]);
163
+
164
+ const extensionStatus = await requestJson(service.port, '/extension/status', {
165
+ method: 'POST',
166
+ body: { connected: true, cursorVersion: '1.0.0', token: 'discard-me' },
167
+ });
168
+ assert.equal(extensionStatus.status, 200);
169
+ assert.equal(extensionStatus.body.ok, true);
170
+ assert.deepEqual(extensionStatus.body.status, {
171
+ connected: true,
172
+ cursorVersion: '1.0.0',
173
+ });
174
+
175
+ const started = await requestJson(service.port, '/mode/start', { method: 'POST' });
176
+ assert.equal(started.status, 200);
177
+ assert.deepEqual(started.body, { state: 'logged-out' });
178
+
179
+ const stopped = await requestJson(service.port, '/mode/stop', { method: 'POST' });
180
+ assert.equal(stopped.status, 200);
181
+ assert.deepEqual(stopped.body, { state: 'inactive' });
182
+
183
+ const shutdown = await requestJson(service.port, '/shutdown', { method: 'POST' });
184
+ assert.equal(shutdown.status, 200);
185
+ assert.equal(shutdown.body.ok, true);
186
+
187
+ await assert.rejects(fetch(`http://127.0.0.1:${service.port}/health`), /fetch failed/);
188
+ } finally {
189
+ await service.stop();
190
+ await rm(tempDir, { recursive: true, force: true });
191
+ }
192
+ });
193
+
194
+ test('shutdown endpoint calls the process shutdown hook after closing the server', async () => {
195
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-shutdown-'));
196
+ const runtimeFile = join(tempDir, 'runtime.json');
197
+ let shutdownCalled = false;
198
+ const service = await startServer({
199
+ runtimeFile,
200
+ onShutdown() {
201
+ shutdownCalled = true;
202
+ },
203
+ });
204
+
205
+ try {
206
+ const shutdown = await requestJson(service.port, '/shutdown', { method: 'POST' });
207
+ assert.equal(shutdown.status, 200);
208
+ assert.equal(shutdown.body.ok, true);
209
+ assert.equal(await waitFor(() => shutdownCalled), true);
210
+ } finally {
211
+ await service.stop();
212
+ await rm(tempDir, { recursive: true, force: true });
213
+ }
214
+ });
215
+
216
+ test('local service exposes platform session routes on loopback', async () => {
217
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-platform-'));
218
+ const platformSessionFile = join(tempDir, 'platform-session.json');
219
+ const calls: Record<string, unknown>[] = [];
220
+ const platformApi = await createPlatformApiServer((request, response) => {
221
+ if (request.method === 'GET' && request.url === '/me') {
222
+ assert.equal(request.headers.authorization, 'Bearer cp_dev_secret');
223
+ writeJson(response, 200, {
224
+ user: { id: 'usr_1', email: 'dev@example.com' },
225
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:00:30Z' },
226
+ });
227
+ return;
228
+ }
229
+
230
+ if (request.method === 'GET' && request.url === '/account/summary') {
231
+ assert.equal(request.headers.authorization, 'Bearer cp_dev_secret');
232
+ writeJson(response, 200, { credits: 1000 });
233
+ return;
234
+ }
235
+
236
+ if (request.method === 'GET' && request.url === '/products') {
237
+ assert.equal(request.headers.authorization, 'Bearer cp_dev_secret');
238
+ writeJson(response, 200, {
239
+ products: [
240
+ {
241
+ id: 'prod_basic',
242
+ name: '基础组',
243
+ description: '开发态基础商品,用于验证扩展展示。',
244
+ status: 'available',
245
+ minCredits: 100,
246
+ usageLabel: '按请求消耗,倍率 1x',
247
+ },
248
+ ],
249
+ });
250
+ return;
251
+ }
252
+
253
+ writeJson(response, 404, { ok: false });
254
+ });
255
+ const service = await startServer({
256
+ runtimeFile: join(tempDir, 'runtime.json'),
257
+ platformSessionFile,
258
+ platformApiBaseUrl: `${platformApi.apiBaseUrl}/`,
259
+ platformExchangePasswordDeviceToken(request) {
260
+ calls.push({ type: 'login', request });
261
+ return Promise.resolve({
262
+ deviceToken: 'cp_dev_secret',
263
+ user: { id: 'usr_1', email: 'dev@example.com' },
264
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:00:00Z' },
265
+ });
266
+ },
267
+ platformPostHeartbeat(request) {
268
+ calls.push({ type: 'heartbeat', request });
269
+ return Promise.resolve({
270
+ ok: true,
271
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:01:00Z' },
272
+ });
273
+ },
274
+ platformPostLogout(request) {
275
+ calls.push({ type: 'logout', request });
276
+ return Promise.resolve();
277
+ },
278
+ platformStartPoolSession(request) {
279
+ calls.push({ type: 'pool-start', request });
280
+ return Promise.resolve({
281
+ state: 'started',
282
+ session: {
283
+ id: 'pool_sess_1',
284
+ productId: request.productId,
285
+ providerType: 'mock-provider',
286
+ status: 'active',
287
+ startedAt: '2026-05-31T00:00:00.000Z',
288
+ expiresAt: '2026-05-31T01:00:00.000Z',
289
+ routeTokenExpiresAt: '2026-05-31T00:10:00.000Z',
290
+ routeStrategy: 'platform-gateway',
291
+ bannedModels: [],
292
+ capabilities: {
293
+ streaming: true,
294
+ usageEstimate: false,
295
+ hardSpendLimit: false,
296
+ },
297
+ },
298
+ routeToken: 'rt_secret_value',
299
+ });
300
+ },
301
+ platformStopPoolSession(request) {
302
+ calls.push({ type: 'pool-stop', request });
303
+ return Promise.resolve({ state: 'stopped', sessionId: request.poolSessionId });
304
+ },
305
+ });
306
+
307
+ try {
308
+ const initial = await requestJson(service.port, '/platform/status');
309
+ assert.equal(initial.status, 200);
310
+ assert.deepEqual(initial.body, { state: 'logged-out' });
311
+
312
+ const initialCatalog = await requestJson(service.port, '/platform/catalog');
313
+ assert.equal(initialCatalog.status, 200);
314
+ assert.deepEqual(initialCatalog.body, { state: 'logged-out', products: [] });
315
+
316
+ const login = await requestJson(service.port, '/platform/login', {
317
+ method: 'POST',
318
+ body: {
319
+ email: 'dev@example.com',
320
+ password: 'correct-password',
321
+ device: { name: 'Lin Mac', os: 'darwin', arch: 'arm64' },
322
+ },
323
+ });
324
+ assert.equal(login.status, 200);
325
+ assert.equal(login.body.state, 'logged-in');
326
+ assert.deepEqual(login.body.user, { id: 'usr_1', email: 'dev@example.com' });
327
+ assert.equal(login.body.device.lastHeartbeatAt, '2026-05-30T00:00:00Z');
328
+
329
+ assert.equal(calls[0]?.type, 'login');
330
+ assert.deepEqual((calls[0]?.request as Record<string, unknown>).device, {
331
+ name: 'Lin Mac',
332
+ os: 'darwin',
333
+ arch: 'arm64',
334
+ });
335
+ assert.equal((calls[0]?.request as Record<string, unknown>).email, 'dev@example.com');
336
+ assert.equal((calls[0]?.request as Record<string, unknown>).password, 'correct-password');
337
+ assert.equal((calls[0]?.request as Record<string, unknown>).apiBaseUrl, `${platformApi.apiBaseUrl}/`);
338
+
339
+ const saved = JSON.parse(await readFile(platformSessionFile, 'utf8')) as Record<string, unknown>;
340
+ assert.equal(saved.apiBaseUrl, platformApi.apiBaseUrl);
341
+ assert.equal(saved.deviceToken, 'cp_dev_secret');
342
+ assert.deepEqual(saved.user, { id: 'usr_1', email: 'dev@example.com' });
343
+
344
+ const loggedInStatus = await requestJson(service.port, '/platform/status');
345
+ assert.equal(loggedInStatus.status, 200);
346
+ assert.equal(loggedInStatus.body.state, 'logged-in');
347
+ assert.deepEqual(loggedInStatus.body.user, { id: 'usr_1', email: 'dev@example.com' });
348
+ assert.equal(loggedInStatus.body.device.lastHeartbeatAt, '2026-05-30T00:00:30Z');
349
+ assert.deepEqual(loggedInStatus.body.mode, { state: 'inactive' });
350
+
351
+ const loggedInCatalog = await requestJson(service.port, '/platform/catalog');
352
+ assert.equal(loggedInCatalog.status, 200);
353
+ assert.deepEqual(loggedInCatalog.body, {
354
+ state: 'logged-in',
355
+ account: { credits: 1000 },
356
+ mode: { state: 'inactive' },
357
+ products: [
358
+ {
359
+ id: 'prod_basic',
360
+ name: '基础组',
361
+ description: '开发态基础商品,用于验证扩展展示。',
362
+ status: 'available',
363
+ minCredits: 100,
364
+ usageLabel: '按请求消耗,倍率 1x',
365
+ },
366
+ ],
367
+ });
368
+
369
+ const selection = await requestJson(service.port, '/platform/selection', {
370
+ method: 'POST',
371
+ body: { productId: 'prod_basic' },
372
+ });
373
+ assert.equal(selection.status, 200);
374
+ assert.deepEqual(selection.body, {
375
+ state: 'selected',
376
+ selectedProductId: 'prod_basic',
377
+ });
378
+
379
+ const selectedCatalog = await requestJson(service.port, '/platform/catalog');
380
+ assert.equal(selectedCatalog.status, 200);
381
+ assert.equal(selectedCatalog.body.state, 'logged-in');
382
+ assert.equal(selectedCatalog.body.selectedProductId, 'prod_basic');
383
+ assert.deepEqual(selectedCatalog.body.mode, { state: 'inactive' });
384
+ assert.equal(calls.some((call) => call.type === 'pool-start'), false);
385
+
386
+ const modeStart = await requestJson(service.port, '/mode/start', { method: 'POST' });
387
+ assert.equal(modeStart.status, 200);
388
+ assert.equal(modeStart.body.state, 'active');
389
+ assert.equal(modeStart.body.productId, 'prod_basic');
390
+ assert.equal(calls[1]?.type, 'pool-start');
391
+ assert.equal(
392
+ ((calls[1]?.request as Record<string, unknown>).session as Record<string, unknown>).deviceToken,
393
+ 'cp_dev_secret',
394
+ );
395
+ assert.equal((calls[1]?.request as Record<string, unknown>).productId, 'prod_basic');
396
+
397
+ const activeCatalog = await requestJson(service.port, '/platform/catalog');
398
+ assert.equal(activeCatalog.status, 200);
399
+ assert.equal(activeCatalog.body.mode.state, 'active');
400
+ assert.equal(activeCatalog.body.mode.productId, 'prod_basic');
401
+
402
+ const modeStop = await requestJson(service.port, '/mode/stop', { method: 'POST' });
403
+ assert.equal(modeStop.status, 200);
404
+ assert.deepEqual(modeStop.body, { state: 'inactive' });
405
+ assert.equal(calls[2]?.type, 'pool-stop');
406
+ assert.equal((calls[2]?.request as Record<string, unknown>).poolSessionId, 'pool_sess_1');
407
+ assert.equal((calls[2]?.request as Record<string, unknown>).reason, 'user-stop');
408
+
409
+ const invalidSelection = await requestJson(service.port, '/platform/selection', {
410
+ method: 'POST',
411
+ body: {},
412
+ });
413
+ assert.equal(invalidSelection.status, 400);
414
+ assert.deepEqual(invalidSelection.body, { ok: false, error: 'invalid request' });
415
+
416
+ const heartbeat = await requestJson(service.port, '/platform/heartbeat', {
417
+ method: 'POST',
418
+ body: { serviceStatus: 'running' },
419
+ });
420
+ assert.equal(heartbeat.status, 200);
421
+ assert.equal(heartbeat.body.state, 'logged-in');
422
+ assert.equal(heartbeat.body.device.lastHeartbeatAt, '2026-05-30T00:01:00Z');
423
+ assert.equal(calls[3]?.type, 'heartbeat');
424
+ assert.equal(
425
+ ((calls[3]?.request as Record<string, unknown>).session as Record<string, unknown>).deviceToken,
426
+ 'cp_dev_secret',
427
+ );
428
+ assert.deepEqual((calls[3]?.request as Record<string, unknown>).payload, { serviceStatus: 'running' });
429
+
430
+ const logout = await requestJson(service.port, '/platform/logout', { method: 'POST' });
431
+ assert.equal(logout.status, 200);
432
+ assert.deepEqual(logout.body, { state: 'logged-out' });
433
+ assert.equal(calls[4]?.type, 'logout');
434
+
435
+ const finalStatus = await requestJson(service.port, '/platform/status');
436
+ assert.equal(finalStatus.status, 200);
437
+ assert.deepEqual(finalStatus.body, { state: 'logged-out' });
438
+
439
+ const finalCatalog = await requestJson(service.port, '/platform/catalog');
440
+ assert.equal(finalCatalog.status, 200);
441
+ assert.deepEqual(finalCatalog.body, { state: 'logged-out', products: [] });
442
+ } finally {
443
+ await service.stop();
444
+ await platformApi.close();
445
+ await rm(tempDir, { recursive: true, force: true });
446
+ }
447
+ });
448
+
449
+ test('local service logs in with account password without accepting API URL from client body', async () => {
450
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-password-'));
451
+ const platformSessionFile = join(tempDir, 'platform-session.json');
452
+ const calls: Record<string, unknown>[] = [];
453
+ const service = await startServer({
454
+ runtimeFile: join(tempDir, 'runtime.json'),
455
+ platformSessionFile,
456
+ platformApiBaseUrl: 'https://platform.example.test/',
457
+ platformExchangePasswordDeviceToken(request) {
458
+ calls.push(request);
459
+ return Promise.resolve({
460
+ deviceToken: 'cp_dev_secret',
461
+ user: { id: 'usr_1', email: 'dev@example.com' },
462
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:00:00Z' },
463
+ });
464
+ },
465
+ });
466
+
467
+ try {
468
+ const login = await requestJson(service.port, '/platform/login', {
469
+ method: 'POST',
470
+ body: {
471
+ email: 'dev@example.com',
472
+ password: 'correct-password',
473
+ apiBaseUrl: 'https://attacker.example.test',
474
+ code: 'SHOULD-NOT-BE-USED',
475
+ device: { name: 'Lin Mac', os: 'darwin', arch: 'arm64' },
476
+ },
477
+ });
478
+
479
+ assert.equal(login.status, 200);
480
+ assert.equal(login.body.state, 'logged-in');
481
+ assert.deepEqual(calls[0], {
482
+ email: 'dev@example.com',
483
+ password: 'correct-password',
484
+ apiBaseUrl: 'https://platform.example.test/',
485
+ device: { name: 'Lin Mac', os: 'darwin', arch: 'arm64' },
486
+ });
487
+
488
+ const saved = JSON.parse(await readFile(platformSessionFile, 'utf8')) as Record<string, unknown>;
489
+ assert.equal(saved.apiBaseUrl, 'https://platform.example.test');
490
+ assert.equal(saved.deviceToken, 'cp_dev_secret');
491
+ } finally {
492
+ await service.stop();
493
+ await rm(tempDir, { recursive: true, force: true });
494
+ }
495
+ });
496
+
497
+ test('local service logs in with persisted client API URL when env is absent', async () => {
498
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-password-config-'));
499
+ const platformSessionFile = join(tempDir, 'platform-session.json');
500
+ const clientConfigFile = join(tempDir, 'client-config.json');
501
+ const calls: Record<string, unknown>[] = [];
502
+ const platformApi = await createPlatformApiServer((request, response) => {
503
+ if (request.method === 'GET' && request.url === '/me') {
504
+ writeJson(response, 200, {
505
+ user: { id: 'usr_1', email: 'dev@example.com' },
506
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:00:00Z' },
507
+ });
508
+ return;
509
+ }
510
+ writeJson(response, 404, { ok: false });
511
+ });
512
+ await writeClientConfig({ apiBaseUrl: `${platformApi.apiBaseUrl}/` }, { configFile: clientConfigFile });
513
+ const service = await startServer({
514
+ runtimeFile: join(tempDir, 'runtime.json'),
515
+ platformSessionFile,
516
+ clientConfigFile,
517
+ platformExchangePasswordDeviceToken(request) {
518
+ calls.push(request);
519
+ return Promise.resolve({
520
+ deviceToken: 'cp_dev_secret',
521
+ user: { id: 'usr_1', email: 'dev@example.com' },
522
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-30T00:00:00Z' },
523
+ });
524
+ },
525
+ });
526
+
527
+ try {
528
+ const login = await requestJson(service.port, '/platform/login', {
529
+ method: 'POST',
530
+ body: {
531
+ email: 'dev@example.com',
532
+ password: 'correct-password',
533
+ device: { name: 'Lin Mac', os: 'darwin', arch: 'arm64' },
534
+ },
535
+ });
536
+
537
+ assert.equal(login.status, 200);
538
+ assert.equal(login.body.state, 'logged-in');
539
+ assert.equal(calls[0]?.apiBaseUrl, platformApi.apiBaseUrl);
540
+ } finally {
541
+ await service.stop();
542
+ await platformApi.close();
543
+ await rm(tempDir, { recursive: true, force: true });
544
+ }
545
+ });
546
+
547
+ test('local service reports platform password login error code', async () => {
548
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-password-error-'));
549
+ const service = await startServer({
550
+ runtimeFile: join(tempDir, 'runtime.json'),
551
+ platformSessionFile: join(tempDir, 'platform-session.json'),
552
+ platformApiBaseUrl: 'https://platform.example.test/',
553
+ platformExchangePasswordDeviceToken() {
554
+ const error = new Error('Request failed with status 401') as Error & {
555
+ status?: number;
556
+ platformCode?: string;
557
+ };
558
+ error.status = 401;
559
+ error.platformCode = 'PASSWORD_LOGIN_INVALID';
560
+ throw error;
561
+ },
562
+ });
563
+
564
+ try {
565
+ const login = await requestJson(service.port, '/platform/login', {
566
+ method: 'POST',
567
+ body: {
568
+ email: 'dev@example.com',
569
+ password: 'wrong-password',
570
+ },
571
+ });
572
+
573
+ assert.equal(login.status, 400);
574
+ assert.equal(login.body.error, 'platform login failed');
575
+ assert.equal(login.body.code, 'PASSWORD_LOGIN_INVALID');
576
+ } finally {
577
+ await service.stop();
578
+ await rm(tempDir, { recursive: true, force: true });
579
+ }
580
+ });
581
+
582
+ test('local service takeover rejects when platform mode is inactive', async () => {
583
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-takeover-inactive-'));
584
+ const service = await startServer({
585
+ runtimeFile: join(tempDir, 'runtime.json'),
586
+ platformSessionFile: join(tempDir, 'missing-session.json'),
587
+ });
588
+
589
+ try {
590
+ const initial = await requestJson(service.port, '/agent/takeover/latest');
591
+ assert.equal(initial.status, 200);
592
+ assert.deepEqual(initial.body, { ok: true, takeover: null });
593
+
594
+ const response = await requestJson(service.port, '/agent/takeover', {
595
+ method: 'POST',
596
+ body: {
597
+ requestId: 'takeover-1',
598
+ source: 'cursor-always-local',
599
+ model: 'gpt-test',
600
+ messages: [{ role: 'user', content: 'secret prompt' }],
601
+ },
602
+ });
603
+
604
+ assert.equal(response.status, 200);
605
+ assert.equal(response.body.state, 'rejected');
606
+ assert.equal(response.body.reason, 'logged-out');
607
+ assert.equal(JSON.stringify(response.body).includes('secret prompt'), false);
608
+
609
+ const latest = await requestJson(service.port, '/agent/takeover/latest');
610
+ assert.equal(latest.status, 200);
611
+ assert.deepEqual(latest.body, { ok: true, takeover: response.body });
612
+ } finally {
613
+ await service.stop();
614
+ await rm(tempDir, { recursive: true, force: true });
615
+ }
616
+ });
617
+
618
+ test('local service takeover returns fake provider answer when platform mode is active', async () => {
619
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-takeover-active-'));
620
+ const platformSessionFile = join(tempDir, 'platform-session.json');
621
+ const service = await startServer({
622
+ runtimeFile: join(tempDir, 'runtime.json'),
623
+ platformSessionFile,
624
+ });
625
+ await writeFile(
626
+ platformSessionFile,
627
+ `${JSON.stringify({
628
+ apiBaseUrl: 'http://127.0.0.1:9',
629
+ deviceToken: 'cp_dev_secret',
630
+ createdAt: '2026-06-01T00:00:00.000Z',
631
+ user: { id: 'usr_1', email: 'dev@example.com' },
632
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
633
+ selectedProductId: 'prod_basic',
634
+ platformMode: 'active',
635
+ activeProductId: 'prod_basic',
636
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
637
+ routeToken: {
638
+ token: 'rt_secret_value',
639
+ expiresAt: '2999-06-01T00:10:00.000Z',
640
+ },
641
+ }, null, 2)}\n`,
642
+ 'utf8',
643
+ );
644
+
645
+ try {
646
+ const response = await requestJson(service.port, '/agent/takeover', {
647
+ method: 'POST',
648
+ body: {
649
+ requestId: 'takeover-active-1',
650
+ source: 'cursor-always-local',
651
+ model: 'gpt-test',
652
+ messages: [{ role: 'user', content: 'secret prompt' }],
653
+ },
654
+ });
655
+
656
+ assert.equal(response.status, 200);
657
+ assert.deepEqual(response.body, {
658
+ state: 'answered',
659
+ requestId: 'takeover-active-1',
660
+ source: 'cursor-always-local',
661
+ model: 'gpt-test',
662
+ content: 'Cursor Pool provider 暂时没有返回可显示内容',
663
+ });
664
+ assert.equal(JSON.stringify(response.body).includes('secret prompt'), false);
665
+ assert.equal(JSON.stringify(response.body).includes('rt_secret_value'), false);
666
+
667
+ const latest = await requestJson(service.port, '/agent/takeover/latest');
668
+ assert.equal(latest.status, 200);
669
+ assert.deepEqual(latest.body, { ok: true, takeover: response.body });
670
+ } finally {
671
+ await service.stop();
672
+ await rm(tempDir, { recursive: true, force: true });
673
+ }
674
+ });
675
+
676
+ test('local service takeover returns provider answer content from gateway forwarder', async () => {
677
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-takeover-provider-'));
678
+ const platformSessionFile = join(tempDir, 'platform-session.json');
679
+ const service = await startServer({
680
+ runtimeFile: join(tempDir, 'runtime.json'),
681
+ platformSessionFile,
682
+ gatewayForwarder: async (request) => {
683
+ assert.equal(request.routeToken, 'rt_secret_value');
684
+ assert.equal(request.poolSessionId, 'pool_session_1');
685
+ assert.equal(request.productId, 'prod_basic');
686
+ assert.equal(request.requestId, 'takeover-provider-1');
687
+ assert.equal(JSON.stringify(request.gateway).includes('rt_secret_value'), false);
688
+ return {
689
+ state: 'forwarded',
690
+ upstreamRequestId: 'gw_req_provider_1',
691
+ acceptedAt: '2026-06-01T00:30:00.000Z',
692
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
693
+ content: '来自临时单账号 provider 的回答',
694
+ };
695
+ },
696
+ });
697
+ await writeFile(
698
+ platformSessionFile,
699
+ `${JSON.stringify({
700
+ apiBaseUrl: 'http://127.0.0.1:9',
701
+ deviceToken: 'cp_dev_secret',
702
+ createdAt: '2026-06-01T00:00:00.000Z',
703
+ user: { id: 'usr_1', email: 'dev@example.com' },
704
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
705
+ selectedProductId: 'prod_basic',
706
+ platformMode: 'active',
707
+ activeProductId: 'prod_basic',
708
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
709
+ poolSession: {
710
+ id: 'pool_session_1',
711
+ productId: 'prod_basic',
712
+ providerType: 'development-single-provider',
713
+ status: 'active',
714
+ startedAt: '2026-06-01T00:05:00.000Z',
715
+ expiresAt: '2999-06-01T01:05:00.000Z',
716
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
717
+ routeStrategy: 'platform-gateway',
718
+ bannedModels: [],
719
+ availableModels: ['gpt-test'],
720
+ capabilities: {
721
+ streaming: false,
722
+ usageEstimate: true,
723
+ hardSpendLimit: true,
724
+ },
725
+ },
726
+ routeToken: {
727
+ token: 'rt_secret_value',
728
+ expiresAt: '2999-06-01T00:10:00.000Z',
729
+ },
730
+ }, null, 2)}\n`,
731
+ 'utf8',
732
+ );
733
+
734
+ try {
735
+ const response = await requestJson(service.port, '/agent/takeover', {
736
+ method: 'POST',
737
+ body: {
738
+ requestId: 'takeover-provider-1',
739
+ source: 'cursor-agent-exec',
740
+ model: 'gpt-test',
741
+ },
742
+ });
743
+
744
+ assert.equal(response.status, 200);
745
+ assert.deepEqual(response.body, {
746
+ state: 'answered',
747
+ requestId: 'takeover-provider-1',
748
+ source: 'cursor-agent-exec',
749
+ model: 'gpt-test',
750
+ content: '来自临时单账号 provider 的回答',
751
+ });
752
+ assert.equal(JSON.stringify(response.body).includes('rt_secret_value'), false);
753
+ } finally {
754
+ await service.stop();
755
+ await rm(tempDir, { recursive: true, force: true });
756
+ }
757
+ });
758
+
759
+ test('local service allows Cursor workbench renderer preflight and takeover POST', async () => {
760
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-takeover-cors-'));
761
+ const platformSessionFile = join(tempDir, 'platform-session.json');
762
+ const service = await startServer({
763
+ runtimeFile: join(tempDir, 'runtime.json'),
764
+ platformSessionFile,
765
+ });
766
+ await writeFile(
767
+ platformSessionFile,
768
+ `${JSON.stringify({
769
+ apiBaseUrl: 'http://127.0.0.1:9',
770
+ deviceToken: 'cp_dev_secret',
771
+ createdAt: '2026-06-01T00:00:00.000Z',
772
+ user: { id: 'usr_1', email: 'dev@example.com' },
773
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
774
+ platformMode: 'active',
775
+ activeProductId: 'prod_basic',
776
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
777
+ routeToken: {
778
+ token: 'rt_secret_value',
779
+ expiresAt: '2999-06-01T00:10:00.000Z',
780
+ },
781
+ }, null, 2)}\n`,
782
+ 'utf8',
783
+ );
784
+
785
+ try {
786
+ const blockedPreflight = await requestRaw(service.port, '/agent/takeover', {
787
+ method: 'OPTIONS',
788
+ headers: {
789
+ origin: 'https://evil.example',
790
+ 'access-control-request-method': 'POST',
791
+ 'access-control-request-headers': 'content-type',
792
+ },
793
+ });
794
+
795
+ assert.equal(blockedPreflight.status, 403);
796
+ assert.equal(blockedPreflight.headers.get('access-control-allow-origin'), null);
797
+
798
+ const preflight = await requestRaw(service.port, '/agent/takeover', {
799
+ method: 'OPTIONS',
800
+ headers: {
801
+ origin: 'vscode-file://vscode-app',
802
+ 'access-control-request-method': 'POST',
803
+ 'access-control-request-headers': 'content-type',
804
+ },
805
+ });
806
+
807
+ assert.equal(preflight.status, 204);
808
+ assert.equal(preflight.headers.get('access-control-allow-origin'), 'vscode-file://vscode-app');
809
+ assert.match(preflight.headers.get('access-control-allow-methods') ?? '', /POST/);
810
+ assert.match(preflight.headers.get('access-control-allow-headers') ?? '', /content-type/i);
811
+
812
+ const response = await requestJson(service.port, '/agent/takeover', {
813
+ method: 'POST',
814
+ headers: { origin: 'vscode-file://vscode-app' },
815
+ body: {
816
+ requestId: 'takeover-cors-1',
817
+ source: 'cursor-agent-exec',
818
+ model: 'default',
819
+ },
820
+ });
821
+
822
+ assert.equal(response.status, 200);
823
+ assert.equal(response.body.state, 'answered');
824
+ assert.equal(response.body.requestId, 'takeover-cors-1');
825
+ assert.equal(response.body.content, 'Cursor Pool provider 暂时没有返回可显示内容');
826
+ } finally {
827
+ await service.stop();
828
+ await rm(tempDir, { recursive: true, force: true });
829
+ }
830
+ });
831
+
832
+ test('local service stores and returns the latest sanitized agent canary', async () => {
833
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-canary-'));
834
+ const platformSessionFile = join(tempDir, 'platform-session.json');
835
+ const service = await startServer({
836
+ runtimeFile: join(tempDir, 'runtime.json'),
837
+ platformSessionFile,
838
+ });
839
+ await writeFile(
840
+ platformSessionFile,
841
+ `${JSON.stringify({
842
+ apiBaseUrl: 'http://127.0.0.1:9',
843
+ deviceToken: 'cp_dev_secret',
844
+ createdAt: '2026-05-31T00:00:00.000Z',
845
+ user: { id: 'usr_1', email: 'dev@example.com' },
846
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
847
+ platformMode: 'active',
848
+ activeProductId: 'prod_basic',
849
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
850
+ routeToken: {
851
+ token: 'rt_secret_value',
852
+ expiresAt: '2999-05-31T00:10:00.000Z',
853
+ },
854
+ }, null, 2)}\n`,
855
+ 'utf8',
856
+ );
857
+
858
+ try {
859
+ const initial = await requestJson(service.port, '/agent/canary/latest');
860
+ assert.equal(initial.status, 200);
861
+ assert.deepEqual(initial.body, { ok: true, canary: null });
862
+
863
+ const posted = await requestJson(service.port, '/agent/canary', {
864
+ method: 'POST',
865
+ body: {
866
+ requestId: 'req-1',
867
+ requestType: 'agent',
868
+ source: 'cursor-agent-exec',
869
+ model: 'gpt-test',
870
+ prompt: 'discard this',
871
+ token: 'discard this too',
872
+ },
873
+ });
874
+
875
+ assert.equal(posted.status, 200);
876
+ assert.equal(posted.body.ok, true);
877
+ assert.equal(posted.body.canary.requestId, 'req-1');
878
+ assert.equal(posted.body.canary.requestType, 'agent');
879
+ assert.equal(posted.body.canary.source, 'cursor-agent-exec');
880
+ assert.equal(posted.body.canary.model, 'gpt-test');
881
+ assert.equal(posted.body.canary.runtimeId, service.runtimeId);
882
+ assert.match(posted.body.canary.receivedAt, /^\d{4}-\d{2}-\d{2}T/);
883
+ assert.deepEqual(posted.body.canary.gate, {
884
+ state: 'allowed',
885
+ productId: 'prod_basic',
886
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
887
+ });
888
+ assert.equal(Object.hasOwn(posted.body.canary, 'prompt'), false);
889
+ assert.equal(Object.hasOwn(posted.body.canary, 'token'), false);
890
+
891
+ const latest = await requestJson(service.port, '/agent/canary/latest');
892
+ assert.equal(latest.status, 200);
893
+ assert.deepEqual(latest.body, posted.body);
894
+ } finally {
895
+ await service.stop();
896
+ await rm(tempDir, { recursive: true, force: true });
897
+ }
898
+ });
899
+
900
+ test('local service reports blocked gate for logged-out agent canary', async () => {
901
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-canary-'));
902
+ const service = await startServer({
903
+ runtimeFile: join(tempDir, 'runtime.json'),
904
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
905
+ });
906
+
907
+ try {
908
+ const posted = await requestJson(service.port, '/agent/canary', {
909
+ method: 'POST',
910
+ body: {
911
+ requestId: 'req-logged-out',
912
+ model: 'gpt-test',
913
+ },
914
+ });
915
+
916
+ assert.equal(posted.status, 200);
917
+ assert.equal(posted.body.ok, true);
918
+ assert.deepEqual(posted.body.canary.gate, { state: 'blocked', reason: 'logged-out' });
919
+
920
+ const latest = await requestJson(service.port, '/agent/canary/latest');
921
+ assert.equal(latest.status, 200);
922
+ assert.deepEqual(latest.body.canary.gate, { state: 'blocked', reason: 'logged-out' });
923
+ } finally {
924
+ await service.stop();
925
+ await rm(tempDir, { recursive: true, force: true });
926
+ }
927
+ });
928
+
929
+ test('local service appends sanitized agent canary diagnostics', async () => {
930
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-canary-'));
931
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
932
+ const service = await startServer({
933
+ runtimeFile: join(tempDir, 'runtime.json'),
934
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
935
+ diagnosticsFile,
936
+ });
937
+
938
+ try {
939
+ const posted = await requestJson(service.port, '/agent/canary', {
940
+ method: 'POST',
941
+ body: {
942
+ requestId: 'req-1',
943
+ model: 'gpt-test',
944
+ prompt: 'discard this',
945
+ apiKey: 'discard this too',
946
+ },
947
+ });
948
+
949
+ assert.equal(posted.status, 200);
950
+
951
+ const content = await readFile(diagnosticsFile, 'utf8');
952
+ const saved = JSON.parse(content.trim()) as Record<string, unknown>;
953
+ assert.equal(saved.requestId, 'req-1');
954
+ assert.equal(saved.requestType, 'agent');
955
+ assert.equal(saved.source, 'cursor-agent-exec');
956
+ assert.equal(saved.model, 'gpt-test');
957
+ assert.equal(saved.runtimeId, service.runtimeId);
958
+ assert.deepEqual(saved.gate, { state: 'blocked', reason: 'logged-out' });
959
+ assert.equal(Object.hasOwn(saved, 'prompt'), false);
960
+ assert.equal(Object.hasOwn(saved, 'apiKey'), false);
961
+ } finally {
962
+ await service.stop();
963
+ await rm(tempDir, { recursive: true, force: true });
964
+ }
965
+ });
966
+
967
+ test('local service keeps canary endpoint available when diagnostics write fails', async () => {
968
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-canary-'));
969
+ const service = await startServer({
970
+ runtimeFile: join(tempDir, 'runtime.json'),
971
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
972
+ diagnosticsFile: tempDir,
973
+ });
974
+
975
+ try {
976
+ const posted = await requestJson(service.port, '/agent/canary', {
977
+ method: 'POST',
978
+ body: { requestId: 'req-1' },
979
+ });
980
+
981
+ assert.equal(posted.status, 200);
982
+ assert.equal(posted.body.ok, true);
983
+ assert.equal(posted.body.canary.requestId, 'req-1');
984
+
985
+ const latest = await requestJson(service.port, '/agent/canary/latest');
986
+ assert.equal(latest.status, 200);
987
+ assert.deepEqual(latest.body, posted.body);
988
+ } finally {
989
+ await service.stop();
990
+ await rm(tempDir, { recursive: true, force: true });
991
+ }
992
+ });
993
+
994
+ test('local service request-check returns allowed decision and safe diagnostics', async () => {
995
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-request-check-'));
996
+ const platformSessionFile = join(tempDir, 'platform-session.json');
997
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
998
+ const service = await startServer({
999
+ runtimeFile: join(tempDir, 'runtime.json'),
1000
+ platformSessionFile,
1001
+ diagnosticsFile,
1002
+ });
1003
+ await writeFile(
1004
+ platformSessionFile,
1005
+ `${JSON.stringify({
1006
+ apiBaseUrl: 'http://127.0.0.1:9',
1007
+ deviceToken: 'cp_dev_secret',
1008
+ createdAt: '2026-05-31T00:00:00.000Z',
1009
+ user: { id: 'usr_1', email: 'dev@example.com' },
1010
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1011
+ platformMode: 'active',
1012
+ activeProductId: 'prod_basic',
1013
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1014
+ routeToken: {
1015
+ token: 'rt_secret_value',
1016
+ expiresAt: '2999-05-31T00:10:00.000Z',
1017
+ },
1018
+ }, null, 2)}\n`,
1019
+ 'utf8',
1020
+ );
1021
+
1022
+ try {
1023
+ const posted = await requestJson(service.port, '/agent/request-check', {
1024
+ method: 'POST',
1025
+ body: {
1026
+ requestId: 'req-check-1',
1027
+ source: 'manual-check',
1028
+ model: 'gpt-test',
1029
+ prompt: 'discard this',
1030
+ apiKey: 'discard this too',
1031
+ },
1032
+ });
1033
+
1034
+ assert.equal(posted.status, 200);
1035
+ assert.equal(posted.body.ok, true);
1036
+ assert.equal(posted.body.check.requestId, 'req-check-1');
1037
+ assert.equal(posted.body.check.requestType, 'agent');
1038
+ assert.equal(posted.body.check.source, 'manual-check');
1039
+ assert.equal(posted.body.check.model, 'gpt-test');
1040
+ assert.equal(posted.body.check.runtimeId, service.runtimeId);
1041
+ assert.match(posted.body.check.receivedAt, /^\d{4}-\d{2}-\d{2}T/);
1042
+ assert.deepEqual(posted.body.check.decision, {
1043
+ state: 'allowed',
1044
+ productId: 'prod_basic',
1045
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
1046
+ });
1047
+ assert.deepEqual(posted.body.check.route, {
1048
+ state: 'ready',
1049
+ expiresAt: '2999-05-31T00:10:00.000Z',
1050
+ });
1051
+ assert.equal(Object.hasOwn(posted.body.check, 'prompt'), false);
1052
+ assert.equal(Object.hasOwn(posted.body.check, 'apiKey'), false);
1053
+ assert.doesNotMatch(JSON.stringify(posted.body), /rt_secret_value|routeToken/);
1054
+
1055
+ const content = await readFile(diagnosticsFile, 'utf8');
1056
+ const saved = JSON.parse(content.trim()) as Record<string, unknown>;
1057
+ assert.equal(saved.kind, 'agent-request-check');
1058
+ assert.equal(saved.requestId, 'req-check-1');
1059
+ assert.deepEqual(saved.decision, posted.body.check.decision);
1060
+ assert.deepEqual(saved.route, posted.body.check.route);
1061
+ assert.equal(Object.hasOwn(saved, 'prompt'), false);
1062
+ assert.equal(Object.hasOwn(saved, 'apiKey'), false);
1063
+ assert.doesNotMatch(JSON.stringify(saved), /rt_secret_value|routeToken/);
1064
+ } finally {
1065
+ await service.stop();
1066
+ await rm(tempDir, { recursive: true, force: true });
1067
+ }
1068
+ });
1069
+
1070
+ test('local service returns latest request-check state', async () => {
1071
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-request-check-'));
1072
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1073
+ const service = await startServer({
1074
+ runtimeFile: join(tempDir, 'runtime.json'),
1075
+ platformSessionFile,
1076
+ });
1077
+ await writeFile(
1078
+ platformSessionFile,
1079
+ `${JSON.stringify({
1080
+ apiBaseUrl: 'http://127.0.0.1:9',
1081
+ deviceToken: 'cp_dev_secret',
1082
+ createdAt: '2026-05-31T00:00:00.000Z',
1083
+ user: { id: 'usr_1', email: 'dev@example.com' },
1084
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1085
+ platformMode: 'active',
1086
+ activeProductId: 'prod_basic',
1087
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1088
+ }, null, 2)}\n`,
1089
+ 'utf8',
1090
+ );
1091
+
1092
+ try {
1093
+ const initial = await requestJson(service.port, '/agent/request-check/latest');
1094
+ assert.equal(initial.status, 200);
1095
+ assert.deepEqual(initial.body, { ok: true, check: null });
1096
+
1097
+ const posted = await requestJson(service.port, '/agent/request-check', {
1098
+ method: 'POST',
1099
+ body: {
1100
+ requestId: 'req-check-latest',
1101
+ source: 'cursor-agent-exec',
1102
+ model: 'gpt-test',
1103
+ prompt: 'discard this',
1104
+ apiKey: 'discard this too',
1105
+ },
1106
+ });
1107
+
1108
+ assert.equal(posted.status, 200);
1109
+ assert.equal(posted.body.ok, true);
1110
+
1111
+ const latest = await requestJson(service.port, '/agent/request-check/latest');
1112
+ assert.equal(latest.status, 200);
1113
+ assert.deepEqual(latest.body, { ok: true, check: posted.body.check });
1114
+ assert.equal(Object.hasOwn(latest.body.check, 'prompt'), false);
1115
+ assert.equal(Object.hasOwn(latest.body.check, 'apiKey'), false);
1116
+ } finally {
1117
+ await service.stop();
1118
+ await rm(tempDir, { recursive: true, force: true });
1119
+ }
1120
+ });
1121
+
1122
+ test('local service request-check returns blocked decision for logged-out session', async () => {
1123
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-request-check-'));
1124
+ const service = await startServer({
1125
+ runtimeFile: join(tempDir, 'runtime.json'),
1126
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
1127
+ });
1128
+
1129
+ try {
1130
+ const posted = await requestJson(service.port, '/agent/request-check', {
1131
+ method: 'POST',
1132
+ body: {
1133
+ requestId: 'req-logged-out',
1134
+ model: 'sk-live-secret-token',
1135
+ },
1136
+ });
1137
+
1138
+ assert.equal(posted.status, 200);
1139
+ assert.equal(posted.body.ok, false);
1140
+ assert.equal(posted.body.check.requestId, 'req-logged-out');
1141
+ assert.equal(posted.body.check.model, 'unknown');
1142
+ assert.deepEqual(posted.body.check.decision, { state: 'blocked', reason: 'logged-out' });
1143
+ assert.deepEqual(posted.body.check.route, { state: 'missing' });
1144
+ } finally {
1145
+ await service.stop();
1146
+ await rm(tempDir, { recursive: true, force: true });
1147
+ }
1148
+ });
1149
+
1150
+ test('local service request-check rejects invalid JSON', async () => {
1151
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-request-check-'));
1152
+ const service = await startServer({ runtimeFile: join(tempDir, 'runtime.json') });
1153
+
1154
+ try {
1155
+ const response = await fetch(`http://127.0.0.1:${service.port}/agent/request-check`, {
1156
+ method: 'POST',
1157
+ headers: { 'content-type': 'application/json' },
1158
+ body: '{not-json',
1159
+ });
1160
+ const body = await response.json();
1161
+
1162
+ assert.equal(response.status, 400);
1163
+ assert.deepEqual(body, { ok: false, error: 'invalid request' });
1164
+ } finally {
1165
+ await service.stop();
1166
+ await rm(tempDir, { recursive: true, force: true });
1167
+ }
1168
+ });
1169
+
1170
+ test('local service keeps request-check available when diagnostics write fails', async () => {
1171
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-request-check-'));
1172
+ const service = await startServer({
1173
+ runtimeFile: join(tempDir, 'runtime.json'),
1174
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
1175
+ diagnosticsFile: tempDir,
1176
+ });
1177
+
1178
+ try {
1179
+ const posted = await requestJson(service.port, '/agent/request-check', {
1180
+ method: 'POST',
1181
+ body: { requestId: 'req-check-1' },
1182
+ });
1183
+
1184
+ assert.equal(posted.status, 200);
1185
+ assert.equal(posted.body.ok, false);
1186
+ assert.equal(posted.body.check.requestId, 'req-check-1');
1187
+ assert.deepEqual(posted.body.check.decision, { state: 'blocked', reason: 'logged-out' });
1188
+ } finally {
1189
+ await service.stop();
1190
+ await rm(tempDir, { recursive: true, force: true });
1191
+ }
1192
+ });
1193
+
1194
+ test('local service request-gateway returns accepted decision and safe diagnostics', async () => {
1195
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
1196
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1197
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
1198
+ const service = await startServer({
1199
+ runtimeFile: join(tempDir, 'runtime.json'),
1200
+ platformSessionFile,
1201
+ diagnosticsFile,
1202
+ });
1203
+ await writeFile(
1204
+ platformSessionFile,
1205
+ `${JSON.stringify({
1206
+ apiBaseUrl: 'http://127.0.0.1:9',
1207
+ deviceToken: 'cp_dev_secret',
1208
+ createdAt: '2026-05-31T00:00:00.000Z',
1209
+ user: { id: 'usr_1', email: 'dev@example.com' },
1210
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1211
+ platformMode: 'active',
1212
+ activeProductId: 'prod_basic',
1213
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1214
+ routeToken: {
1215
+ token: 'rt_secret_value',
1216
+ expiresAt: '2999-05-31T00:10:00.000Z',
1217
+ },
1218
+ }, null, 2)}\n`,
1219
+ 'utf8',
1220
+ );
1221
+
1222
+ try {
1223
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
1224
+ method: 'POST',
1225
+ body: {
1226
+ requestId: 'req-gateway-1',
1227
+ source: 'cursor-agent-exec',
1228
+ model: 'gpt-test',
1229
+ prompt: 'discard this',
1230
+ apiKey: 'discard this too',
1231
+ routeToken: 'rt_secret_value',
1232
+ },
1233
+ });
1234
+
1235
+ assert.equal(posted.status, 200);
1236
+ assert.equal(posted.body.ok, false);
1237
+ assert.equal(posted.body.gateway.requestId, 'req-gateway-1');
1238
+ assert.equal(posted.body.gateway.requestType, 'agent');
1239
+ assert.equal(posted.body.gateway.source, 'cursor-agent-exec');
1240
+ assert.equal(posted.body.gateway.model, 'gpt-test');
1241
+ assert.equal(posted.body.gateway.runtimeId, service.runtimeId);
1242
+ assert.deepEqual(posted.body.gateway.decision, {
1243
+ state: 'accepted',
1244
+ productId: 'prod_basic',
1245
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
1246
+ route: { state: 'ready', expiresAt: '2999-05-31T00:10:00.000Z' },
1247
+ });
1248
+ assert.deepEqual(posted.body.gateway.forward, { state: 'network-error' });
1249
+ assert.doesNotMatch(JSON.stringify(posted.body), /prompt|apiKey|rt_secret_value|routeToken/);
1250
+
1251
+ const saved = JSON.parse((await readFile(diagnosticsFile, 'utf8')).trim()) as Record<string, unknown>;
1252
+ assert.equal(saved.kind, 'agent-request-gateway');
1253
+ assert.deepEqual(saved.decision, posted.body.gateway.decision);
1254
+ assert.deepEqual(saved.forward, { state: 'network-error' });
1255
+ assert.doesNotMatch(JSON.stringify(saved), /prompt|apiKey|rt_secret_value|routeToken/);
1256
+ } finally {
1257
+ await service.stop();
1258
+ await rm(tempDir, { recursive: true, force: true });
1259
+ }
1260
+ });
1261
+
1262
+ test('local service request-gateway forwards to platform API by default', async () => {
1263
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
1264
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1265
+ const forwarded: Record<string, unknown>[] = [];
1266
+ const platformApi = await createPlatformApiServer(async (request, response) => {
1267
+ if (request.method !== 'POST' || request.url !== '/api/client/gateway/agent') {
1268
+ writeJson(response, 404, { ok: false });
1269
+ return;
1270
+ }
1271
+
1272
+ assert.equal(request.headers.authorization, 'Bearer cp_dev_secret');
1273
+ const chunks: Buffer[] = [];
1274
+ for await (const chunk of request) {
1275
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1276
+ }
1277
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>;
1278
+ forwarded.push(body);
1279
+ assert.equal(body.routeToken, 'rt_secret_value');
1280
+ assert.equal(body.poolSessionId, 'pool_session_1');
1281
+ assert.equal(body.productId, 'prod_basic');
1282
+ assert.equal(body.model, 'gpt-test');
1283
+ assert.equal(body.requestId, 'req-gateway-default');
1284
+ assert.equal(Object.hasOwn(body, 'prompt'), false);
1285
+ assert.equal(Object.hasOwn(body, 'messages'), false);
1286
+ assert.equal(Object.hasOwn(body, 'apiKey'), false);
1287
+ assert.equal((body.gateway as Record<string, unknown>).requestId, 'req-gateway-default');
1288
+ assert.equal(JSON.stringify(body.gateway).includes('rt_secret_value'), false);
1289
+ assert.equal(JSON.stringify(body.gateway).includes('discard this'), false);
1290
+
1291
+ writeJson(response, 200, {
1292
+ state: 'forwarded',
1293
+ upstreamRequestId: 'gw_req_default',
1294
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1295
+ routeExpiresAt: '2999-05-31T00:10:00.000Z',
1296
+ });
1297
+ });
1298
+ const service = await startServer({
1299
+ runtimeFile: join(tempDir, 'runtime.json'),
1300
+ platformSessionFile,
1301
+ });
1302
+ await writeFile(
1303
+ platformSessionFile,
1304
+ `${JSON.stringify({
1305
+ apiBaseUrl: platformApi.apiBaseUrl,
1306
+ deviceToken: 'cp_dev_secret',
1307
+ createdAt: '2026-05-31T00:00:00.000Z',
1308
+ user: { id: 'usr_1', email: 'dev@example.com' },
1309
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1310
+ platformMode: 'active',
1311
+ activeProductId: 'prod_basic',
1312
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1313
+ poolSession: {
1314
+ id: 'pool_session_1',
1315
+ productId: 'prod_basic',
1316
+ providerType: 'development',
1317
+ status: 'active',
1318
+ startedAt: '2026-05-31T00:05:00.000Z',
1319
+ expiresAt: '2999-05-31T00:20:00.000Z',
1320
+ routeTokenExpiresAt: '2999-05-31T00:10:00.000Z',
1321
+ routeStrategy: 'platform-gateway',
1322
+ bannedModels: [],
1323
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1324
+ },
1325
+ routeToken: {
1326
+ token: 'rt_secret_value',
1327
+ expiresAt: '2999-05-31T00:10:00.000Z',
1328
+ },
1329
+ }, null, 2)}\n`,
1330
+ 'utf8',
1331
+ );
1332
+
1333
+ try {
1334
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
1335
+ method: 'POST',
1336
+ body: {
1337
+ requestId: 'req-gateway-default',
1338
+ source: 'cursor-agent-exec',
1339
+ model: 'gpt-test',
1340
+ prompt: 'discard this',
1341
+ messages: [{ role: 'user', content: 'discard this too' }],
1342
+ apiKey: 'discard this too',
1343
+ },
1344
+ });
1345
+
1346
+ assert.equal(posted.status, 200);
1347
+ assert.equal(posted.body.ok, true);
1348
+ assert.deepEqual(posted.body.gateway.forward, {
1349
+ state: 'forwarded',
1350
+ upstreamRequestId: 'gw_req_default',
1351
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1352
+ });
1353
+ assert.equal(forwarded.length, 1);
1354
+ assert.doesNotMatch(JSON.stringify(posted.body), /rt_secret_value|discard this|routeToken/);
1355
+ } finally {
1356
+ await service.stop();
1357
+ await platformApi.close();
1358
+ await rm(tempDir, { recursive: true, force: true });
1359
+ }
1360
+ });
1361
+
1362
+ test('local service request-gateway accepts patch-like payload without leaking route token', async () => {
1363
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
1364
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1365
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
1366
+ const forwarded: Record<string, unknown>[] = [];
1367
+ const platformApi = await createPlatformApiServer(async (request, response) => {
1368
+ if (request.method !== 'POST' || request.url !== '/api/client/gateway/agent') {
1369
+ writeJson(response, 404, { ok: false });
1370
+ return;
1371
+ }
1372
+
1373
+ assert.equal(request.headers.authorization, 'Bearer cp_dev_secret');
1374
+ const chunks: Buffer[] = [];
1375
+ for await (const chunk of request) {
1376
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1377
+ }
1378
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>;
1379
+ forwarded.push(body);
1380
+ assert.equal(body.routeToken, 'rt_secret_value');
1381
+ assert.equal(body.poolSessionId, 'pool_session_1');
1382
+ assert.equal(body.productId, 'prod_basic');
1383
+ assert.equal(body.requestId, 'req-patch-like');
1384
+ assert.equal(Object.hasOwn(body, 'prompt'), false);
1385
+ assert.equal(Object.hasOwn(body, 'messages'), false);
1386
+ assert.equal(Object.hasOwn(body, 'apiKey'), false);
1387
+
1388
+ const gateway = body.gateway as Record<string, unknown>;
1389
+ assert.equal(gateway.requestId, 'req-patch-like');
1390
+ assert.equal(gateway.requestType, 'agent');
1391
+ assert.equal(gateway.source, 'cursor-agent-exec');
1392
+ assert.equal(JSON.stringify(gateway).includes('rt_secret_value'), false);
1393
+ assert.equal(JSON.stringify(gateway).includes('routeToken'), false);
1394
+ assert.equal(JSON.stringify(gateway).includes('prompt'), false);
1395
+ assert.equal(JSON.stringify(gateway).includes('messages'), false);
1396
+
1397
+ writeJson(response, 200, {
1398
+ state: 'forwarded',
1399
+ upstreamRequestId: 'gw_req_patch_like',
1400
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1401
+ routeExpiresAt: '2999-05-31T00:10:00.000Z',
1402
+ });
1403
+ });
1404
+ const service = await startServer({
1405
+ runtimeFile: join(tempDir, 'runtime.json'),
1406
+ platformSessionFile,
1407
+ diagnosticsFile,
1408
+ });
1409
+ await writeFile(
1410
+ platformSessionFile,
1411
+ `${JSON.stringify({
1412
+ apiBaseUrl: platformApi.apiBaseUrl,
1413
+ deviceToken: 'cp_dev_secret',
1414
+ createdAt: '2026-05-31T00:00:00.000Z',
1415
+ user: { id: 'usr_1', email: 'dev@example.com' },
1416
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1417
+ platformMode: 'active',
1418
+ activeProductId: 'prod_basic',
1419
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1420
+ poolSession: {
1421
+ id: 'pool_session_1',
1422
+ productId: 'prod_basic',
1423
+ providerType: 'development',
1424
+ status: 'active',
1425
+ startedAt: '2026-05-31T00:05:00.000Z',
1426
+ expiresAt: '2999-05-31T00:20:00.000Z',
1427
+ routeTokenExpiresAt: '2999-05-31T00:10:00.000Z',
1428
+ routeStrategy: 'platform-gateway',
1429
+ bannedModels: [],
1430
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1431
+ },
1432
+ routeToken: {
1433
+ token: 'rt_secret_value',
1434
+ expiresAt: '2999-05-31T00:10:00.000Z',
1435
+ },
1436
+ }, null, 2)}\n`,
1437
+ 'utf8',
1438
+ );
1439
+
1440
+ try {
1441
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
1442
+ method: 'POST',
1443
+ body: {
1444
+ requestId: 'req-patch-like',
1445
+ requestType: 'agent',
1446
+ source: 'cursor-agent-exec',
1447
+ },
1448
+ });
1449
+
1450
+ assert.equal(posted.status, 200);
1451
+ assert.equal(posted.body.ok, true);
1452
+ assert.equal(posted.body.gateway.requestId, 'req-patch-like');
1453
+ assert.equal(posted.body.gateway.requestType, 'agent');
1454
+ assert.equal(posted.body.gateway.source, 'cursor-agent-exec');
1455
+ assert.equal(posted.body.gateway.runtimeId, service.runtimeId);
1456
+ assert.deepEqual(posted.body.gateway.decision, {
1457
+ state: 'accepted',
1458
+ productId: 'prod_basic',
1459
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
1460
+ route: { state: 'ready', expiresAt: '2999-05-31T00:10:00.000Z' },
1461
+ });
1462
+ assert.deepEqual(posted.body.gateway.forward, {
1463
+ state: 'forwarded',
1464
+ upstreamRequestId: 'gw_req_patch_like',
1465
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1466
+ });
1467
+ assert.equal(forwarded.length, 1);
1468
+ assert.doesNotMatch(JSON.stringify(posted.body), /rt_secret_value|routeToken|prompt|messages/);
1469
+
1470
+ const saved = JSON.parse((await readFile(diagnosticsFile, 'utf8')).trim()) as Record<string, unknown>;
1471
+ assert.equal(saved.kind, 'agent-request-gateway');
1472
+ assert.deepEqual(saved.decision, posted.body.gateway.decision);
1473
+ assert.deepEqual(saved.forward, posted.body.gateway.forward);
1474
+ assert.doesNotMatch(JSON.stringify(saved), /rt_secret_value|routeToken|prompt|messages/);
1475
+ } finally {
1476
+ await service.stop();
1477
+ await platformApi.close();
1478
+ await rm(tempDir, { recursive: true, force: true });
1479
+ }
1480
+ });
1481
+
1482
+ test('local service request-gateway returns forwarded when injected forwarder accepts', async () => {
1483
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
1484
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1485
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
1486
+ const service = await startServer({
1487
+ runtimeFile: join(tempDir, 'runtime.json'),
1488
+ platformSessionFile,
1489
+ diagnosticsFile,
1490
+ gatewayForwarder: async (request) => {
1491
+ assert.equal(request.routeToken, 'rt_secret_value');
1492
+ assert.equal(request.poolSessionId, 'pool_session_1');
1493
+ assert.equal(request.productId, 'prod_basic');
1494
+ assert.equal(request.gateway.requestId, 'req-gateway-1');
1495
+ assert.equal(JSON.stringify(request.gateway).includes('rt_secret_value'), false);
1496
+ return {
1497
+ state: 'forwarded',
1498
+ upstreamRequestId: 'gw_req_1',
1499
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1500
+ routeExpiresAt: '2999-05-31T00:10:00.000Z',
1501
+ };
1502
+ },
1503
+ });
1504
+ await writeFile(
1505
+ platformSessionFile,
1506
+ `${JSON.stringify({
1507
+ apiBaseUrl: 'http://127.0.0.1:9',
1508
+ deviceToken: 'cp_dev_secret',
1509
+ createdAt: '2026-05-31T00:00:00.000Z',
1510
+ user: { id: 'usr_1', email: 'dev@example.com' },
1511
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
1512
+ platformMode: 'active',
1513
+ activeProductId: 'prod_basic',
1514
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
1515
+ poolSession: {
1516
+ id: 'pool_session_1',
1517
+ productId: 'prod_basic',
1518
+ providerType: 'mock',
1519
+ status: 'active',
1520
+ startedAt: '2026-05-31T00:05:00.000Z',
1521
+ expiresAt: '2999-05-31T00:20:00.000Z',
1522
+ routeTokenExpiresAt: '2999-05-31T00:10:00.000Z',
1523
+ routeStrategy: 'platform-gateway',
1524
+ bannedModels: [],
1525
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1526
+ },
1527
+ routeToken: {
1528
+ token: 'rt_secret_value',
1529
+ expiresAt: '2999-05-31T00:10:00.000Z',
1530
+ },
1531
+ }, null, 2)}\n`,
1532
+ 'utf8',
1533
+ );
1534
+
1535
+ try {
1536
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
1537
+ method: 'POST',
1538
+ body: {
1539
+ requestId: 'req-gateway-1',
1540
+ source: 'cursor-agent-exec',
1541
+ model: 'gpt-test',
1542
+ prompt: 'must not persist',
1543
+ routeToken: 'rt_secret_value',
1544
+ },
1545
+ });
1546
+
1547
+ assert.equal(posted.status, 200);
1548
+ assert.equal(posted.body.ok, true);
1549
+ assert.deepEqual(posted.body.gateway.forward, {
1550
+ state: 'forwarded',
1551
+ upstreamRequestId: 'gw_req_1',
1552
+ acceptedAt: '2026-05-31T00:30:00.000Z',
1553
+ });
1554
+ assert.doesNotMatch(JSON.stringify(posted.body), /rt_secret_value|must not persist/);
1555
+
1556
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
1557
+ assert.deepEqual(latest.body.gateway.forward, posted.body.gateway.forward);
1558
+
1559
+ const saved = JSON.parse((await readFile(diagnosticsFile, 'utf8')).trim()) as Record<string, unknown>;
1560
+ assert.deepEqual(saved.forward, posted.body.gateway.forward);
1561
+ } finally {
1562
+ await service.stop();
1563
+ await rm(tempDir, { recursive: true, force: true });
1564
+ }
1565
+ });
1566
+
1567
+ test('local service exposes OpenAI-compatible models for Cursor native local agent', async () => {
1568
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-models-'));
1569
+ const service = await startServer({
1570
+ runtimeFile: join(tempDir, 'runtime.json'),
1571
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
1572
+ });
1573
+
1574
+ try {
1575
+ const response = await requestJson(service.port, '/models', {
1576
+ headers: { authorization: 'Bearer cursor-pool-local' },
1577
+ });
1578
+
1579
+ assert.equal(response.status, 200);
1580
+ assert.equal(Array.isArray(response.body.data), true);
1581
+ assert.equal(response.body.data[0].id, 'gpt-test');
1582
+ assert.deepEqual(response.body.data[0].api_types, ['chat_completions']);
1583
+ assert.equal(response.body.data[0].capabilities.supports_tool_use, true);
1584
+ assert.equal(response.body.data[0].capabilities.supports_streaming, true);
1585
+ assert.deepEqual(response.body.data[0].capabilities.output_modalities, ['text']);
1586
+ } finally {
1587
+ await service.stop();
1588
+ await rm(tempDir, { recursive: true, force: true });
1589
+ }
1590
+ });
1591
+
1592
+ test('local service OpenAI-compatible chat completions forwards through platform gateway', async () => {
1593
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-chat-'));
1594
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1595
+ const service = await startServer({
1596
+ runtimeFile: join(tempDir, 'runtime.json'),
1597
+ platformSessionFile,
1598
+ gatewayForwarder: async (request) => {
1599
+ assert.equal(request.routeToken, 'rt_secret_value');
1600
+ assert.equal(request.poolSessionId, 'pool_session_1');
1601
+ assert.equal(request.productId, 'prod_basic');
1602
+ assert.equal(request.model, 'gpt-test');
1603
+ assert.equal(request.gateway.source, 'cursor-agent-exec');
1604
+ assert.equal(JSON.stringify(request.gateway).includes('create temp file'), false);
1605
+ return {
1606
+ state: 'forwarded',
1607
+ upstreamRequestId: 'gw_req_openai_1',
1608
+ acceptedAt: '2026-06-01T00:30:00.000Z',
1609
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
1610
+ content: '来自 native local agent 的号池回答',
1611
+ };
1612
+ },
1613
+ });
1614
+ await writeFile(
1615
+ platformSessionFile,
1616
+ `${JSON.stringify({
1617
+ apiBaseUrl: 'http://127.0.0.1:9',
1618
+ deviceToken: 'cp_dev_secret',
1619
+ createdAt: '2026-06-01T00:00:00.000Z',
1620
+ user: { id: 'usr_1', email: 'dev@example.com' },
1621
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
1622
+ platformMode: 'active',
1623
+ activeProductId: 'prod_basic',
1624
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
1625
+ poolSession: {
1626
+ id: 'pool_session_1',
1627
+ productId: 'prod_basic',
1628
+ providerType: 'development',
1629
+ status: 'active',
1630
+ startedAt: '2026-06-01T00:05:00.000Z',
1631
+ expiresAt: '2999-06-01T00:20:00.000Z',
1632
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
1633
+ routeStrategy: 'platform-gateway',
1634
+ bannedModels: [],
1635
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1636
+ },
1637
+ routeToken: {
1638
+ token: 'rt_secret_value',
1639
+ expiresAt: '2999-06-01T00:10:00.000Z',
1640
+ },
1641
+ }, null, 2)}\n`,
1642
+ 'utf8',
1643
+ );
1644
+
1645
+ try {
1646
+ const response = await requestJson(service.port, '/chat/completions', {
1647
+ method: 'POST',
1648
+ headers: { authorization: 'Bearer cursor-pool-local' },
1649
+ body: {
1650
+ model: 'gpt-test',
1651
+ stream: false,
1652
+ messages: [{ role: 'user', content: 'create temp file' }],
1653
+ tools: [{ type: 'function', function: { name: 'edit_file' } }],
1654
+ },
1655
+ });
1656
+
1657
+ assert.equal(response.status, 200);
1658
+ assert.equal(response.body.object, 'chat.completion');
1659
+ assert.equal(response.body.model, 'gpt-test');
1660
+ assert.equal(response.body.choices[0].message.role, 'assistant');
1661
+ assert.equal(response.body.choices[0].message.content, '来自 native local agent 的号池回答');
1662
+
1663
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
1664
+ assert.equal(latest.body.gateway.forward.upstreamRequestId, 'gw_req_openai_1');
1665
+ assert.doesNotMatch(JSON.stringify(latest.body), /create temp file|messages|rt_secret_value/);
1666
+ } finally {
1667
+ await service.stop();
1668
+ await rm(tempDir, { recursive: true, force: true });
1669
+ }
1670
+ });
1671
+
1672
+ test('local service OpenAI-compatible chat completions completes settlement after client response', async () => {
1673
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-complete-'));
1674
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1675
+ const calls: Array<{ url: string; body: Record<string, unknown> }> = [];
1676
+ const platformApi = await createPlatformApiServer(async (request, response) => {
1677
+ const chunks: Buffer[] = [];
1678
+ for await (const chunk of request) {
1679
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1680
+ }
1681
+ const body = JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>;
1682
+ calls.push({ url: request.url || '', body });
1683
+
1684
+ if (request.method === 'POST' && request.url === '/api/client/gateway/agent') {
1685
+ writeJson(response, 200, {
1686
+ state: 'forwarded',
1687
+ upstreamRequestId: 'gw_req_openai_complete_1',
1688
+ acceptedAt: '2026-06-01T00:30:00.000Z',
1689
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
1690
+ usageRequestId: 'ur_complete_1',
1691
+ content: 'OK',
1692
+ });
1693
+ return;
1694
+ }
1695
+
1696
+ if (request.method === 'POST' && request.url === '/api/client/gateway/agent/req-openai-complete/complete') {
1697
+ writeJson(response, 200, {
1698
+ state: 'charged',
1699
+ usageRequestId: 'ur_complete_1',
1700
+ chargeStatus: 'charged',
1701
+ chargeCredits: 1,
1702
+ });
1703
+ return;
1704
+ }
1705
+
1706
+ writeJson(response, 404, { ok: false });
1707
+ });
1708
+ const service = await startServer({
1709
+ runtimeFile: join(tempDir, 'runtime.json'),
1710
+ platformSessionFile,
1711
+ });
1712
+ await writeFile(
1713
+ platformSessionFile,
1714
+ `${JSON.stringify({
1715
+ apiBaseUrl: platformApi.apiBaseUrl,
1716
+ deviceToken: 'cp_dev_secret',
1717
+ createdAt: '2026-06-01T00:00:00.000Z',
1718
+ user: { id: 'usr_1', email: 'dev@example.com' },
1719
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
1720
+ platformMode: 'active',
1721
+ activeProductId: 'prod_basic',
1722
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
1723
+ poolSession: {
1724
+ id: 'pool_session_1',
1725
+ productId: 'prod_basic',
1726
+ providerType: 'development',
1727
+ status: 'active',
1728
+ startedAt: '2026-06-01T00:05:00.000Z',
1729
+ expiresAt: '2999-06-01T00:20:00.000Z',
1730
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
1731
+ routeStrategy: 'platform-gateway',
1732
+ bannedModels: [],
1733
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1734
+ },
1735
+ routeToken: {
1736
+ token: 'rt_secret_value',
1737
+ expiresAt: '2999-06-01T00:10:00.000Z',
1738
+ },
1739
+ }, null, 2)}\n`,
1740
+ 'utf8',
1741
+ );
1742
+
1743
+ try {
1744
+ const response = await requestJson(service.port, '/chat/completions', {
1745
+ method: 'POST',
1746
+ headers: { authorization: 'Bearer cursor-pool-local' },
1747
+ body: {
1748
+ requestId: 'req-openai-complete',
1749
+ model: 'gpt-test',
1750
+ stream: false,
1751
+ messages: [{ role: 'user', content: '只回复 OK' }],
1752
+ },
1753
+ });
1754
+
1755
+ assert.equal(response.status, 200);
1756
+ assert.equal(response.body.choices[0].message.content, 'OK');
1757
+ assert.equal(calls[0].body.routeToken, 'rt_secret_value');
1758
+ assert.equal(calls[0].body.poolSessionId, 'pool_session_1');
1759
+ assert.equal(calls[0].body.productId, 'prod_basic');
1760
+ assert.equal(calls[0].body.settlementMode, 'client_response');
1761
+ assert.equal(await waitFor(() => calls.length >= 2), true);
1762
+ assert.deepEqual(calls.map((call) => call.url), [
1763
+ '/api/client/gateway/agent',
1764
+ '/api/client/gateway/agent/req-openai-complete/complete',
1765
+ ]);
1766
+ assert.deepEqual(calls[1].body, {
1767
+ routeToken: 'rt_secret_value',
1768
+ poolSessionId: 'pool_session_1',
1769
+ productId: 'prod_basic',
1770
+ });
1771
+ } finally {
1772
+ await service.stop();
1773
+ await platformApi.close();
1774
+ await rm(tempDir, { recursive: true, force: true });
1775
+ }
1776
+ });
1777
+
1778
+ test('local service OpenAI-compatible chat completions skips settlement when client disconnects', async () => {
1779
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-disconnect-'));
1780
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1781
+ const calls: Array<{ url: string; body: Record<string, unknown> }> = [];
1782
+ const platformApi = await createPlatformApiServer(async (request, response) => {
1783
+ const chunks: Buffer[] = [];
1784
+ for await (const chunk of request) {
1785
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1786
+ }
1787
+ const body = chunks.length > 0
1788
+ ? JSON.parse(Buffer.concat(chunks).toString('utf8')) as Record<string, unknown>
1789
+ : {};
1790
+ calls.push({ url: request.url || '', body });
1791
+
1792
+ if (request.method === 'POST' && request.url === '/api/client/gateway/agent') {
1793
+ await new Promise((resolve) => setTimeout(resolve, 80));
1794
+ writeJson(response, 200, {
1795
+ state: 'forwarded',
1796
+ upstreamRequestId: 'gw_req_openai_disconnect_1',
1797
+ acceptedAt: '2026-06-01T00:30:00.000Z',
1798
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
1799
+ usageRequestId: 'ur_disconnect_1',
1800
+ content: 'OK',
1801
+ });
1802
+ return;
1803
+ }
1804
+
1805
+ if (request.method === 'POST' && request.url === '/api/client/gateway/agent/req-openai-disconnect/complete') {
1806
+ writeJson(response, 200, {
1807
+ state: 'charged',
1808
+ usageRequestId: 'ur_disconnect_1',
1809
+ chargeStatus: 'charged',
1810
+ chargeCredits: 1,
1811
+ });
1812
+ return;
1813
+ }
1814
+
1815
+ writeJson(response, 404, { ok: false });
1816
+ });
1817
+ const service = await startServer({
1818
+ runtimeFile: join(tempDir, 'runtime.json'),
1819
+ platformSessionFile,
1820
+ });
1821
+ await writeFile(
1822
+ platformSessionFile,
1823
+ `${JSON.stringify({
1824
+ apiBaseUrl: platformApi.apiBaseUrl,
1825
+ deviceToken: 'cp_dev_secret',
1826
+ createdAt: '2026-06-01T00:00:00.000Z',
1827
+ user: { id: 'usr_1', email: 'dev@example.com' },
1828
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
1829
+ platformMode: 'active',
1830
+ activeProductId: 'prod_basic',
1831
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
1832
+ poolSession: {
1833
+ id: 'pool_session_1',
1834
+ productId: 'prod_basic',
1835
+ providerType: 'real_http',
1836
+ status: 'active',
1837
+ startedAt: '2026-06-01T00:05:00.000Z',
1838
+ expiresAt: '2999-06-01T00:20:00.000Z',
1839
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
1840
+ routeStrategy: 'platform-gateway',
1841
+ bannedModels: [],
1842
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1843
+ },
1844
+ routeToken: {
1845
+ token: 'rt_secret_value',
1846
+ expiresAt: '2999-06-01T00:10:00.000Z',
1847
+ },
1848
+ }, null, 2)}\n`,
1849
+ 'utf8',
1850
+ );
1851
+
1852
+ try {
1853
+ await postJsonAndAbort(service.port, '/chat/completions', {
1854
+ requestId: 'req-openai-disconnect',
1855
+ model: 'gpt-test',
1856
+ stream: false,
1857
+ messages: [{ role: 'user', content: '只回复 OK' }],
1858
+ });
1859
+
1860
+ assert.equal(await waitFor(() => calls.length >= 1), true);
1861
+ await new Promise((resolve) => setTimeout(resolve, 150));
1862
+ assert.deepEqual(calls.map((call) => call.url), ['/api/client/gateway/agent']);
1863
+ } finally {
1864
+ await service.stop();
1865
+ await platformApi.close();
1866
+ await rm(tempDir, { recursive: true, force: true });
1867
+ }
1868
+ });
1869
+
1870
+ test('local service OpenAI-compatible chat completions refreshes expired pool route before answering', async () => {
1871
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-refresh-route-'));
1872
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1873
+ const service = await startServer({
1874
+ runtimeFile: join(tempDir, 'runtime.json'),
1875
+ platformSessionFile,
1876
+ platformStartPoolSession: async () => ({
1877
+ session: {
1878
+ id: 'pool_session_fresh',
1879
+ productId: 'prod_basic',
1880
+ providerType: 'real_http',
1881
+ status: 'active',
1882
+ startedAt: '2026-06-01T00:31:00.000Z',
1883
+ expiresAt: '2999-06-01T01:31:00.000Z',
1884
+ routeTokenExpiresAt: '2999-06-01T00:46:00.000Z',
1885
+ routeStrategy: 'platform-gateway',
1886
+ bannedModels: [],
1887
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1888
+ },
1889
+ routeToken: 'rt_fresh_value',
1890
+ }),
1891
+ gatewayForwarder: async (request) => {
1892
+ assert.equal(request.routeToken, 'rt_fresh_value');
1893
+ assert.equal(request.poolSessionId, 'pool_session_fresh');
1894
+ return {
1895
+ state: 'forwarded',
1896
+ upstreamRequestId: 'gw_req_openai_fresh_1',
1897
+ acceptedAt: '2026-06-01T00:31:10.000Z',
1898
+ routeExpiresAt: '2999-06-01T00:46:00.000Z',
1899
+ content: 'OK',
1900
+ };
1901
+ },
1902
+ });
1903
+ await writeFile(
1904
+ platformSessionFile,
1905
+ `${JSON.stringify({
1906
+ apiBaseUrl: 'http://127.0.0.1:9',
1907
+ deviceToken: 'cp_dev_secret',
1908
+ createdAt: '2026-06-01T00:00:00.000Z',
1909
+ user: { id: 'usr_1', email: 'dev@example.com' },
1910
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
1911
+ selectedProductId: 'prod_basic',
1912
+ platformMode: 'active',
1913
+ activeProductId: 'prod_basic',
1914
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
1915
+ poolSession: {
1916
+ id: 'pool_session_expired',
1917
+ productId: 'prod_basic',
1918
+ providerType: 'real_http',
1919
+ status: 'active',
1920
+ startedAt: '2026-06-01T00:05:00.000Z',
1921
+ expiresAt: '2999-06-01T00:20:00.000Z',
1922
+ routeTokenExpiresAt: '2026-06-01T00:10:00.000Z',
1923
+ routeStrategy: 'platform-gateway',
1924
+ bannedModels: [],
1925
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
1926
+ },
1927
+ routeToken: {
1928
+ token: 'rt_expired_value',
1929
+ expiresAt: '2026-06-01T00:10:00.000Z',
1930
+ },
1931
+ }, null, 2)}\n`,
1932
+ 'utf8',
1933
+ );
1934
+
1935
+ try {
1936
+ const response = await requestJson(service.port, '/chat/completions', {
1937
+ method: 'POST',
1938
+ headers: { authorization: 'Bearer cursor-pool-local' },
1939
+ body: {
1940
+ model: 'gpt-test',
1941
+ stream: false,
1942
+ messages: [{ role: 'user', content: '只回复 OK' }],
1943
+ },
1944
+ });
1945
+
1946
+ assert.equal(response.status, 200);
1947
+ assert.equal(response.body.choices[0].message.content, 'OK');
1948
+
1949
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
1950
+ assert.equal(latest.body.gateway.forward.upstreamRequestId, 'gw_req_openai_fresh_1');
1951
+ } finally {
1952
+ await service.stop();
1953
+ await rm(tempDir, { recursive: true, force: true });
1954
+ }
1955
+ });
1956
+
1957
+ test('local service OpenAI-compatible chat completions rejects instead of answering when platform mode is inactive', async () => {
1958
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-inactive-'));
1959
+ const service = await startServer({
1960
+ runtimeFile: join(tempDir, 'runtime.json'),
1961
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
1962
+ });
1963
+
1964
+ try {
1965
+ const response = await requestJson(service.port, '/chat/completions', {
1966
+ method: 'POST',
1967
+ headers: { authorization: 'Bearer cursor-pool-local' },
1968
+ body: {
1969
+ model: 'gpt-test',
1970
+ stream: false,
1971
+ messages: [{ role: 'user', content: '你好' }],
1972
+ },
1973
+ });
1974
+
1975
+ assert.equal(response.status, 409);
1976
+ assert.deepEqual(response.body, {
1977
+ error: {
1978
+ message: 'Cursor Pool platform mode is inactive; use the official Cursor path.',
1979
+ type: 'cursor_pool_bypass',
1980
+ code: 'cursor_pool_bypass',
1981
+ },
1982
+ });
1983
+ } finally {
1984
+ await service.stop();
1985
+ await rm(tempDir, { recursive: true, force: true });
1986
+ }
1987
+ });
1988
+
1989
+ test('local service OpenAI-compatible chat completions answers fail-closed when provider needs manual review', async () => {
1990
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-manual-review-'));
1991
+ const platformSessionFile = join(tempDir, 'platform-session.json');
1992
+ const service = await startServer({
1993
+ runtimeFile: join(tempDir, 'runtime.json'),
1994
+ platformSessionFile,
1995
+ gatewayForwarder: async () => ({
1996
+ state: 'rejected',
1997
+ reason: 'manual-review-required',
1998
+ }),
1999
+ });
2000
+ await writeFile(
2001
+ platformSessionFile,
2002
+ `${JSON.stringify({
2003
+ apiBaseUrl: 'http://127.0.0.1:9',
2004
+ deviceToken: 'cp_dev_secret',
2005
+ createdAt: '2026-06-01T00:00:00.000Z',
2006
+ user: { id: 'usr_1', email: 'dev@example.com' },
2007
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
2008
+ platformMode: 'active',
2009
+ activeProductId: 'prod_basic',
2010
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
2011
+ poolSession: {
2012
+ id: 'pool_session_1',
2013
+ productId: 'prod_basic',
2014
+ providerType: 'development',
2015
+ status: 'active',
2016
+ startedAt: '2026-06-01T00:05:00.000Z',
2017
+ expiresAt: '2999-06-01T00:20:00.000Z',
2018
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
2019
+ routeStrategy: 'platform-gateway',
2020
+ bannedModels: [],
2021
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
2022
+ },
2023
+ routeToken: {
2024
+ token: 'rt_secret_value',
2025
+ expiresAt: '2999-06-01T00:10:00.000Z',
2026
+ },
2027
+ }, null, 2)}\n`,
2028
+ 'utf8',
2029
+ );
2030
+
2031
+ try {
2032
+ const response = await requestJson(service.port, '/chat/completions', {
2033
+ method: 'POST',
2034
+ headers: { authorization: 'Bearer cursor-pool-local' },
2035
+ body: {
2036
+ model: 'gpt-test',
2037
+ stream: false,
2038
+ messages: [{ role: 'user', content: 'two sum' }],
2039
+ },
2040
+ });
2041
+
2042
+ assert.equal(response.status, 200);
2043
+ assert.equal(response.body.object, 'chat.completion');
2044
+ assert.match(response.body.choices[0].message.content, /号池 provider 未就绪/);
2045
+
2046
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
2047
+ assert.deepEqual(latest.body.gateway.forward, {
2048
+ state: 'rejected',
2049
+ reason: 'manual-review-required',
2050
+ });
2051
+ } finally {
2052
+ await service.stop();
2053
+ await rm(tempDir, { recursive: true, force: true });
2054
+ }
2055
+ });
2056
+
2057
+ test('local service OpenAI-compatible chat completions passes raw request only to gateway forwarder', async () => {
2058
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-raw-forwarder-'));
2059
+ const platformSessionFile = join(tempDir, 'platform-session.json');
2060
+ const service = await startServer({
2061
+ runtimeFile: join(tempDir, 'runtime.json'),
2062
+ platformSessionFile,
2063
+ gatewayForwarder: async (request) => {
2064
+ assert.deepEqual(request.openAiRequest, {
2065
+ model: 'gpt-test',
2066
+ stream: false,
2067
+ messages: [{ role: 'user', content: 'create temp file' }],
2068
+ tools: [{ type: 'function', function: { name: 'edit_file' } }],
2069
+ });
2070
+ return {
2071
+ state: 'forwarded',
2072
+ upstreamRequestId: 'gw_req_openai_raw_1',
2073
+ acceptedAt: '2026-06-01T00:30:00.000Z',
2074
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
2075
+ openAiResponse: {
2076
+ id: 'chatcmpl-provider-tool',
2077
+ object: 'chat.completion',
2078
+ created: 1780306600,
2079
+ model: 'gpt-test',
2080
+ choices: [
2081
+ {
2082
+ index: 0,
2083
+ message: {
2084
+ role: 'assistant',
2085
+ content: null,
2086
+ tool_calls: [
2087
+ {
2088
+ id: 'call_1',
2089
+ type: 'function',
2090
+ function: {
2091
+ name: 'edit_file',
2092
+ arguments: '{"path":"tmp/cursor-pool-agent-test.txt"}',
2093
+ },
2094
+ },
2095
+ ],
2096
+ },
2097
+ finish_reason: 'tool_calls',
2098
+ },
2099
+ ],
2100
+ },
2101
+ };
2102
+ },
2103
+ });
2104
+ await writeFile(
2105
+ platformSessionFile,
2106
+ `${JSON.stringify({
2107
+ apiBaseUrl: 'http://127.0.0.1:9',
2108
+ deviceToken: 'cp_dev_secret',
2109
+ createdAt: '2026-06-01T00:00:00.000Z',
2110
+ user: { id: 'usr_1', email: 'dev@example.com' },
2111
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
2112
+ platformMode: 'active',
2113
+ activeProductId: 'prod_basic',
2114
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
2115
+ poolSession: {
2116
+ id: 'pool_session_1',
2117
+ productId: 'prod_basic',
2118
+ providerType: 'development',
2119
+ status: 'active',
2120
+ startedAt: '2026-06-01T00:05:00.000Z',
2121
+ expiresAt: '2999-06-01T00:20:00.000Z',
2122
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
2123
+ routeStrategy: 'platform-gateway',
2124
+ bannedModels: [],
2125
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
2126
+ },
2127
+ routeToken: {
2128
+ token: 'rt_secret_value',
2129
+ expiresAt: '2999-06-01T00:10:00.000Z',
2130
+ },
2131
+ }, null, 2)}\n`,
2132
+ 'utf8',
2133
+ );
2134
+
2135
+ try {
2136
+ const response = await requestJson(service.port, '/chat/completions', {
2137
+ method: 'POST',
2138
+ headers: { authorization: 'Bearer cursor-pool-local' },
2139
+ body: {
2140
+ model: 'gpt-test',
2141
+ stream: false,
2142
+ messages: [{ role: 'user', content: 'create temp file' }],
2143
+ tools: [{ type: 'function', function: { name: 'edit_file' } }],
2144
+ },
2145
+ });
2146
+
2147
+ assert.equal(response.status, 200);
2148
+ assert.equal(response.body.choices[0].message.tool_calls[0].function.name, 'edit_file');
2149
+
2150
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
2151
+ assert.equal(latest.body.gateway.forward.upstreamRequestId, 'gw_req_openai_raw_1');
2152
+ assert.doesNotMatch(JSON.stringify(latest.body), /create temp file|messages|edit_file|tool_calls/);
2153
+ } finally {
2154
+ await service.stop();
2155
+ await rm(tempDir, { recursive: true, force: true });
2156
+ }
2157
+ });
2158
+
2159
+ test('local service OpenAI-compatible chat completions supports Cursor streaming mode', async () => {
2160
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-openai-stream-'));
2161
+ const platformSessionFile = join(tempDir, 'platform-session.json');
2162
+ const service = await startServer({
2163
+ runtimeFile: join(tempDir, 'runtime.json'),
2164
+ platformSessionFile,
2165
+ gatewayForwarder: async () => ({
2166
+ state: 'forwarded',
2167
+ upstreamRequestId: 'gw_req_stream_1',
2168
+ acceptedAt: '2026-06-01T00:30:00.000Z',
2169
+ routeExpiresAt: '2999-06-01T00:10:00.000Z',
2170
+ content: 'streamed provider answer',
2171
+ }),
2172
+ });
2173
+ await writeFile(
2174
+ platformSessionFile,
2175
+ `${JSON.stringify({
2176
+ apiBaseUrl: 'http://127.0.0.1:9',
2177
+ deviceToken: 'cp_dev_secret',
2178
+ createdAt: '2026-06-01T00:00:00.000Z',
2179
+ user: { id: 'usr_1', email: 'dev@example.com' },
2180
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-06-01T00:00:00.000Z' },
2181
+ platformMode: 'active',
2182
+ activeProductId: 'prod_basic',
2183
+ platformModeStartedAt: '2026-06-01T00:05:00.000Z',
2184
+ poolSession: {
2185
+ id: 'pool_session_1',
2186
+ productId: 'prod_basic',
2187
+ providerType: 'development',
2188
+ status: 'active',
2189
+ startedAt: '2026-06-01T00:05:00.000Z',
2190
+ expiresAt: '2999-06-01T00:20:00.000Z',
2191
+ routeTokenExpiresAt: '2999-06-01T00:10:00.000Z',
2192
+ routeStrategy: 'platform-gateway',
2193
+ bannedModels: [],
2194
+ capabilities: { streaming: false, usageEstimate: false, hardSpendLimit: false },
2195
+ },
2196
+ routeToken: {
2197
+ token: 'rt_secret_value',
2198
+ expiresAt: '2999-06-01T00:10:00.000Z',
2199
+ },
2200
+ }, null, 2)}\n`,
2201
+ 'utf8',
2202
+ );
2203
+
2204
+ try {
2205
+ const response = await requestText(service.port, '/chat/completions', {
2206
+ method: 'POST',
2207
+ headers: { authorization: 'Bearer cursor-pool-local' },
2208
+ body: {
2209
+ model: 'gpt-test',
2210
+ stream: true,
2211
+ messages: [{ role: 'user', content: 'stream smoke' }],
2212
+ },
2213
+ });
2214
+
2215
+ assert.equal(response.status, 200);
2216
+ assert.match(response.text, /^data: /);
2217
+ assert.match(response.text, /"object":"chat.completion.chunk"/);
2218
+ assert.match(response.text, /"content":"streamed provider answer"/);
2219
+ assert.match(response.text, /data: \[DONE\]/);
2220
+ } finally {
2221
+ await service.stop();
2222
+ await rm(tempDir, { recursive: true, force: true });
2223
+ }
2224
+ });
2225
+
2226
+ test('local service request-gateway does not call forwarder for non-accepted decisions', async () => {
2227
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2228
+ let calls = 0;
2229
+ const service = await startServer({
2230
+ runtimeFile: join(tempDir, 'runtime.json'),
2231
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
2232
+ gatewayForwarder: async () => {
2233
+ calls += 1;
2234
+ return {
2235
+ state: 'forwarded',
2236
+ upstreamRequestId: 'gw_req_1',
2237
+ acceptedAt: '2026-05-31T00:30:00.000Z',
2238
+ routeExpiresAt: '2999-05-31T00:10:00.000Z',
2239
+ };
2240
+ },
2241
+ });
2242
+
2243
+ try {
2244
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2245
+ method: 'POST',
2246
+ body: {
2247
+ requestId: 'req-gateway-1',
2248
+ source: 'manual-check',
2249
+ model: 'gpt-test',
2250
+ },
2251
+ });
2252
+
2253
+ assert.equal(posted.status, 200);
2254
+ assert.equal(posted.body.ok, false);
2255
+ assert.equal(calls, 0);
2256
+ assert.deepEqual(posted.body.gateway.forward, { state: 'skipped', reason: 'not-accepted' });
2257
+ } finally {
2258
+ await service.stop();
2259
+ await rm(tempDir, { recursive: true, force: true });
2260
+ }
2261
+ });
2262
+
2263
+ test('local service request-gateway rejects unsafe request id before response and diagnostics', async () => {
2264
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2265
+ const diagnosticsFile = join(tempDir, 'diagnostics.jsonl');
2266
+ const service = await startServer({
2267
+ runtimeFile: join(tempDir, 'runtime.json'),
2268
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
2269
+ diagnosticsFile,
2270
+ });
2271
+
2272
+ try {
2273
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2274
+ method: 'POST',
2275
+ body: {
2276
+ requestId: 'req-1 apiKey=secret',
2277
+ source: 'manual-check',
2278
+ model: 'gpt-test',
2279
+ },
2280
+ });
2281
+
2282
+ assert.equal(posted.status, 200);
2283
+ assert.equal(posted.body.ok, false);
2284
+ assert.notEqual(posted.body.gateway.requestId, 'req-1 apiKey=secret');
2285
+ assert.doesNotMatch(JSON.stringify(posted.body), /apiKey|secret/);
2286
+
2287
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
2288
+ assert.deepEqual(latest.body, { ok: true, gateway: posted.body.gateway });
2289
+ assert.doesNotMatch(JSON.stringify(latest.body), /apiKey|secret/);
2290
+
2291
+ const saved = JSON.parse((await readFile(diagnosticsFile, 'utf8')).trim()) as Record<string, unknown>;
2292
+ assert.equal(saved.kind, 'agent-request-gateway');
2293
+ assert.equal(saved.requestId, posted.body.gateway.requestId);
2294
+ assert.doesNotMatch(JSON.stringify(saved), /apiKey|secret/);
2295
+ } finally {
2296
+ await service.stop();
2297
+ await rm(tempDir, { recursive: true, force: true });
2298
+ }
2299
+ });
2300
+
2301
+ test('local service request-gateway returns route-missing and latest state', async () => {
2302
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2303
+ const platformSessionFile = join(tempDir, 'platform-session.json');
2304
+ const service = await startServer({
2305
+ runtimeFile: join(tempDir, 'runtime.json'),
2306
+ platformSessionFile,
2307
+ });
2308
+ await writeFile(
2309
+ platformSessionFile,
2310
+ `${JSON.stringify({
2311
+ apiBaseUrl: 'http://127.0.0.1:9',
2312
+ deviceToken: 'cp_dev_secret',
2313
+ createdAt: '2026-05-31T00:00:00.000Z',
2314
+ user: { id: 'usr_1', email: 'dev@example.com' },
2315
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
2316
+ platformMode: 'active',
2317
+ activeProductId: 'prod_basic',
2318
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
2319
+ }, null, 2)}\n`,
2320
+ 'utf8',
2321
+ );
2322
+
2323
+ try {
2324
+ const initial = await requestJson(service.port, '/agent/request-gateway/latest');
2325
+ assert.deepEqual(initial.body, { ok: true, gateway: null });
2326
+
2327
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2328
+ method: 'POST',
2329
+ body: { requestId: 'req-gateway-missing', source: 'manual-check' },
2330
+ });
2331
+
2332
+ assert.equal(posted.status, 200);
2333
+ assert.equal(posted.body.ok, false);
2334
+ assert.deepEqual(posted.body.gateway.decision, {
2335
+ state: 'route-missing',
2336
+ productId: 'prod_basic',
2337
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
2338
+ route: { state: 'missing' },
2339
+ });
2340
+
2341
+ const latest = await requestJson(service.port, '/agent/request-gateway/latest');
2342
+ assert.deepEqual(latest.body, { ok: true, gateway: posted.body.gateway });
2343
+ } finally {
2344
+ await service.stop();
2345
+ await rm(tempDir, { recursive: true, force: true });
2346
+ }
2347
+ });
2348
+
2349
+ test('local service request-gateway returns blocked decision for logged-out session', async () => {
2350
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2351
+ const service = await startServer({
2352
+ runtimeFile: join(tempDir, 'runtime.json'),
2353
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
2354
+ });
2355
+
2356
+ try {
2357
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2358
+ method: 'POST',
2359
+ body: { requestId: 'req-gateway-logged-out', model: 'sk-live-secret-token' },
2360
+ });
2361
+
2362
+ assert.equal(posted.status, 200);
2363
+ assert.equal(posted.body.ok, false);
2364
+ assert.equal(posted.body.gateway.model, 'unknown');
2365
+ assert.deepEqual(posted.body.gateway.decision, {
2366
+ state: 'blocked',
2367
+ reason: 'logged-out',
2368
+ route: { state: 'missing' },
2369
+ });
2370
+ } finally {
2371
+ await service.stop();
2372
+ await rm(tempDir, { recursive: true, force: true });
2373
+ }
2374
+ });
2375
+
2376
+ test('local service request-gateway returns route-expired decision', async () => {
2377
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2378
+ const platformSessionFile = join(tempDir, 'platform-session.json');
2379
+ const service = await startServer({
2380
+ runtimeFile: join(tempDir, 'runtime.json'),
2381
+ platformSessionFile,
2382
+ });
2383
+ await writeFile(
2384
+ platformSessionFile,
2385
+ `${JSON.stringify({
2386
+ apiBaseUrl: 'http://127.0.0.1:9',
2387
+ deviceToken: 'cp_dev_secret',
2388
+ createdAt: '2026-05-31T00:00:00.000Z',
2389
+ user: { id: 'usr_1', email: 'dev@example.com' },
2390
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
2391
+ platformMode: 'active',
2392
+ activeProductId: 'prod_basic',
2393
+ platformModeStartedAt: '2026-05-31T00:05:00.000Z',
2394
+ routeToken: {
2395
+ token: 'rt_secret_value',
2396
+ expiresAt: '2000-01-01T00:00:00.000Z',
2397
+ },
2398
+ }, null, 2)}\n`,
2399
+ 'utf8',
2400
+ );
2401
+
2402
+ try {
2403
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2404
+ method: 'POST',
2405
+ body: { requestId: 'req-gateway-expired', source: 'manual-check' },
2406
+ });
2407
+
2408
+ assert.equal(posted.status, 200);
2409
+ assert.equal(posted.body.ok, false);
2410
+ assert.deepEqual(posted.body.gateway.decision, {
2411
+ state: 'route-expired',
2412
+ productId: 'prod_basic',
2413
+ modeStartedAt: '2026-05-31T00:05:00.000Z',
2414
+ route: { state: 'expired', expiresAt: '2000-01-01T00:00:00.000Z' },
2415
+ });
2416
+ assert.doesNotMatch(JSON.stringify(posted.body), /rt_secret_value|routeToken/);
2417
+ } finally {
2418
+ await service.stop();
2419
+ await rm(tempDir, { recursive: true, force: true });
2420
+ }
2421
+ });
2422
+
2423
+ test('local service request-gateway rejects invalid JSON', async () => {
2424
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2425
+ const service = await startServer({ runtimeFile: join(tempDir, 'runtime.json') });
2426
+
2427
+ try {
2428
+ const response = await fetch(`http://127.0.0.1:${service.port}/agent/request-gateway`, {
2429
+ method: 'POST',
2430
+ headers: { 'content-type': 'application/json' },
2431
+ body: '{not-json',
2432
+ });
2433
+ const body = await response.json();
2434
+
2435
+ assert.equal(response.status, 400);
2436
+ assert.deepEqual(body, { ok: false, error: 'invalid request' });
2437
+ } finally {
2438
+ await service.stop();
2439
+ await rm(tempDir, { recursive: true, force: true });
2440
+ }
2441
+ });
2442
+
2443
+ test('local service keeps request-gateway available when diagnostics write fails', async () => {
2444
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-gateway-'));
2445
+ const service = await startServer({
2446
+ runtimeFile: join(tempDir, 'runtime.json'),
2447
+ platformSessionFile: join(tempDir, 'missing-platform-session.json'),
2448
+ diagnosticsFile: tempDir,
2449
+ });
2450
+
2451
+ try {
2452
+ const posted = await requestJson(service.port, '/agent/request-gateway', {
2453
+ method: 'POST',
2454
+ body: { requestId: 'req-gateway-1' },
2455
+ });
2456
+
2457
+ assert.equal(posted.status, 200);
2458
+ assert.equal(posted.body.ok, false);
2459
+ assert.equal(posted.body.gateway.requestId, 'req-gateway-1');
2460
+ assert.deepEqual(posted.body.gateway.decision, {
2461
+ state: 'blocked',
2462
+ reason: 'logged-out',
2463
+ route: { state: 'missing' },
2464
+ });
2465
+ } finally {
2466
+ await service.stop();
2467
+ await rm(tempDir, { recursive: true, force: true });
2468
+ }
2469
+ });
2470
+
2471
+ test('local service returns release reason from platform status route', async () => {
2472
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-'));
2473
+ const sessionFile = join(tempDir, 'session.json');
2474
+ const service = await startServer({ platformSessionFile: sessionFile });
2475
+ await writeFile(
2476
+ sessionFile,
2477
+ `${JSON.stringify({
2478
+ apiBaseUrl: 'http://127.0.0.1:9',
2479
+ deviceToken: 'cp_dev_secret',
2480
+ createdAt: '2026-05-31T00:00:00.000Z',
2481
+ user: { id: 'usr_1', email: 'dev@example.com' },
2482
+ device: { id: 'dev_1', status: 'active', lastHeartbeatAt: '2026-05-31T00:00:00.000Z' },
2483
+ lastModeReleaseReason: 'invalid-token',
2484
+ lastModeReleasedAt: '2026-05-31T00:05:00.000Z',
2485
+ }, null, 2)}\n`,
2486
+ 'utf8',
2487
+ );
2488
+
2489
+ try {
2490
+ const response = await fetch(`http://${service.host}:${service.port}/platform/status`);
2491
+ const body = await response.json() as {
2492
+ state: string;
2493
+ mode?: { state: string; releaseReason?: string; releasedAt?: string };
2494
+ };
2495
+
2496
+ assert.equal(response.status, 200);
2497
+ assert.equal(body.state, 'offline');
2498
+ assert.deepEqual(body.mode, {
2499
+ state: 'inactive',
2500
+ releaseReason: 'invalid-token',
2501
+ releasedAt: '2026-05-31T00:05:00.000Z',
2502
+ });
2503
+ } finally {
2504
+ await service.stop();
2505
+ await rm(tempDir, { recursive: true, force: true });
2506
+ }
2507
+ });
2508
+
2509
+ test('startServer finds a free loopback port when the requested port is busy', async () => {
2510
+ const occupied = createNodeServer();
2511
+ await new Promise<void>((resolve) => {
2512
+ occupied.listen(0, '127.0.0.1', resolve);
2513
+ });
2514
+ const address = occupied.address();
2515
+ assert.equal(typeof address, 'object');
2516
+ const occupiedPort = address?.port as number;
2517
+
2518
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-'));
2519
+ const service = await startServer({
2520
+ port: occupiedPort,
2521
+ runtimeFile: join(tempDir, 'runtime.json'),
2522
+ });
2523
+
2524
+ try {
2525
+ assert.equal(service.host, '127.0.0.1');
2526
+ assert.notEqual(service.port, occupiedPort);
2527
+ const health = await requestJson(service.port, '/health');
2528
+ assert.equal(health.body.ok, true);
2529
+ } finally {
2530
+ await service.stop();
2531
+ await new Promise<void>((resolve, reject) => {
2532
+ occupied.close((error) => (error ? reject(error) : resolve()));
2533
+ });
2534
+ await rm(tempDir, { recursive: true, force: true });
2535
+ }
2536
+ });
2537
+
2538
+ test('startServer uses the stable Cursor workbench takeover port by default', async () => {
2539
+ const tempDir = await mkdtemp(join(tmpdir(), 'cursor-pool-service-default-port-'));
2540
+ const portProbe = createNodeServer((_request, response) => {
2541
+ response.end('occupied');
2542
+ });
2543
+ const stablePortAvailable = await new Promise<boolean>((resolve) => {
2544
+ portProbe.once('error', () => resolve(false));
2545
+ portProbe.listen(56393, '127.0.0.1', () => resolve(true));
2546
+ });
2547
+ if (stablePortAvailable) {
2548
+ await new Promise<void>((resolve, reject) => {
2549
+ portProbe.close((error) => (error ? reject(error) : resolve()));
2550
+ });
2551
+ }
2552
+ const service = await startServer({
2553
+ runtimeFile: join(tempDir, 'runtime.json'),
2554
+ });
2555
+
2556
+ try {
2557
+ assert.equal(service.host, '127.0.0.1');
2558
+ if (stablePortAvailable) {
2559
+ assert.equal(service.port, 56393);
2560
+ } else {
2561
+ assert.notEqual(service.port, 56393);
2562
+ }
2563
+ const health = await requestJson(service.port, '/health');
2564
+ assert.equal(health.status, 200);
2565
+ assert.equal(health.body.port, service.port);
2566
+ } finally {
2567
+ await service.stop();
2568
+ await rm(tempDir, { recursive: true, force: true });
2569
+ }
2570
+ });