@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,630 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import test from 'node:test';
6
+ import { sha256File } from '../src/hash';
7
+ import {
8
+ buildCursorAgentExecPatchSnippet,
9
+ CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR,
10
+ CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHORS,
11
+ CURSOR_POOL_PATCH_BEGIN_MARKER,
12
+ CURSOR_POOL_PATCH_END_MARKER,
13
+ CURSOR_POOL_PATCH_MARKER,
14
+ CURSOR_POOL_PATCH_SIGNATURE,
15
+ hasCursorPoolMarker,
16
+ injectCursorAgentExecPatchSnippet,
17
+ } from '../src/marker';
18
+ import {
19
+ backupPathForCursorAgentExec,
20
+ patchCursorAgentExec,
21
+ } from '../src/patchCursorAgentExec';
22
+
23
+ const targetRelativePath =
24
+ 'Contents/Resources/app/extensions/cursor-agent-exec/dist/main.js';
25
+
26
+ async function createFixture(
27
+ content = `function main() { return "agent"; }\n${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}\nmain();\n`,
28
+ ) {
29
+ const base = await mkdtemp(join(tmpdir(), 'cursor-pool-patcher-'));
30
+ const appPath = join(base, `Fixture-${Date.now()}-${Math.random()}.app`);
31
+ await mkdir(join(appPath, 'Contents/Resources/app/extensions/cursor-agent-exec/dist'), {
32
+ recursive: true,
33
+ });
34
+ await writeFile(join(appPath, targetRelativePath), content, 'utf8');
35
+
36
+ return {
37
+ appPath,
38
+ targetPath: join(appPath, targetRelativePath),
39
+ };
40
+ }
41
+
42
+ test('patch changes the target hash after computing the original hash and adds a detectable marker', async () => {
43
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-backups-${Date.now()}-${Math.random()}`);
44
+ const fixture = await createFixture();
45
+
46
+ try {
47
+ const beforeHash = await sha256File(fixture.targetPath);
48
+ assert.equal(await hasCursorPoolMarker(fixture.targetPath), false);
49
+
50
+ const result = await patchCursorAgentExec(fixture.appPath, { backupDir: tempDir });
51
+
52
+ const afterPatchHash = await sha256File(fixture.targetPath);
53
+ const markerPresent = await hasCursorPoolMarker(fixture.targetPath);
54
+
55
+ assert.equal(result.beforeHash, beforeHash);
56
+ assert.equal(afterPatchHash !== beforeHash, true);
57
+ assert.equal(result.afterHash, afterPatchHash);
58
+ assert.equal(markerPresent, true);
59
+ assert.match(result.backupPath, new RegExp(`^${tempDir.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`));
60
+ } finally {
61
+ await rm(fixture.appPath, { recursive: true, force: true });
62
+ await rm(tempDir, { recursive: true, force: true });
63
+ }
64
+ });
65
+
66
+ test('patch supports a custom Linux Cursor agent exec relative path', async () => {
67
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-linux-path-${Date.now()}-${Math.random()}`);
68
+ const appPath = join(tempDir, 'squashfs-root');
69
+ const customTargetRelativePath =
70
+ 'usr/share/cursor/resources/app/extensions/cursor-agent-exec/dist/main.js';
71
+ const targetPath = join(appPath, customTargetRelativePath);
72
+
73
+ try {
74
+ await mkdir(dirname(targetPath), { recursive: true });
75
+ await writeFile(
76
+ targetPath,
77
+ `function main() { return "linux"; }\n${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}\nmain();\n`,
78
+ 'utf8',
79
+ );
80
+
81
+ const result = await patchCursorAgentExec(appPath, {
82
+ backupDir: tempDir,
83
+ targetRelativePath: customTargetRelativePath,
84
+ });
85
+
86
+ assert.equal(result.targetPath, targetPath);
87
+ assert.equal(await hasCursorPoolMarker(targetPath), true);
88
+ } finally {
89
+ await rm(tempDir, { recursive: true, force: true });
90
+ }
91
+ });
92
+
93
+ test('patch failure restores original target bytes from runtime backup', async () => {
94
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-rollback-${Date.now()}-${Math.random()}`);
95
+ const fixture = await createFixture();
96
+
97
+ try {
98
+ const beforeHash = await sha256File(fixture.targetPath);
99
+ const beforeBytes = await readFile(fixture.targetPath, 'utf8');
100
+
101
+ await assert.rejects(
102
+ patchCursorAgentExec(fixture.appPath, {
103
+ backupDir: tempDir,
104
+ patchContent: () => {
105
+ throw new Error('synthetic patch failure');
106
+ },
107
+ }),
108
+ /synthetic patch failure/,
109
+ );
110
+
111
+ assert.equal(await sha256File(fixture.targetPath), beforeHash);
112
+ assert.equal(await readFile(fixture.targetPath, 'utf8'), beforeBytes);
113
+ assert.equal(await hasCursorPoolMarker(fixture.targetPath), false);
114
+ } finally {
115
+ await rm(fixture.appPath, { recursive: true, force: true });
116
+ await rm(tempDir, { recursive: true, force: true });
117
+ }
118
+ });
119
+
120
+ test('patch rolls back when patched content lacks a valid marker pair', async () => {
121
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-invalid-marker-${Date.now()}-${Math.random()}`);
122
+ const fixture = await createFixture();
123
+
124
+ try {
125
+ const beforeHash = await sha256File(fixture.targetPath);
126
+ const beforeBytes = await readFile(fixture.targetPath, 'utf8');
127
+
128
+ await assert.rejects(
129
+ patchCursorAgentExec(fixture.appPath, {
130
+ backupDir: tempDir,
131
+ patchContent: (content) => `${content}\n/* ${CURSOR_POOL_PATCH_MARKER}: begin */\n`,
132
+ }),
133
+ /valid patch marker/,
134
+ );
135
+
136
+ assert.equal(await sha256File(fixture.targetPath), beforeHash);
137
+ assert.equal(await readFile(fixture.targetPath, 'utf8'), beforeBytes);
138
+ assert.equal(await hasCursorPoolMarker(fixture.targetPath), false);
139
+ } finally {
140
+ await rm(fixture.appPath, { recursive: true, force: true });
141
+ await rm(tempDir, { recursive: true, force: true });
142
+ }
143
+ });
144
+
145
+ test('patch fails without the Cursor Agent provider registration anchor', async () => {
146
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-missing-anchor-${Date.now()}-${Math.random()}`);
147
+ const fixture = await createFixture('function main() { return "agent"; }\nmain();\n');
148
+
149
+ try {
150
+ await assert.rejects(
151
+ patchCursorAgentExec(fixture.appPath, { backupDir: tempDir }),
152
+ /provider registration anchor/,
153
+ );
154
+ assert.equal(await hasCursorPoolMarker(fixture.targetPath), false);
155
+ } finally {
156
+ await rm(fixture.appPath, { recursive: true, force: true });
157
+ await rm(tempDir, { recursive: true, force: true });
158
+ }
159
+ });
160
+
161
+ test('already-patched target refuses success when backup is missing', async () => {
162
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-missing-backup-${Date.now()}-${Math.random()}`);
163
+ const fixture = await createFixture();
164
+
165
+ try {
166
+ await writeFile(
167
+ fixture.targetPath,
168
+ `${await readFile(fixture.targetPath, 'utf8')}${buildCursorAgentExecPatchSnippet()}`,
169
+ 'utf8',
170
+ );
171
+
172
+ await assert.rejects(
173
+ patchCursorAgentExec(fixture.appPath, { backupDir: tempDir }),
174
+ /backup.*missing/i,
175
+ );
176
+ } finally {
177
+ await rm(fixture.appPath, { recursive: true, force: true });
178
+ await rm(tempDir, { recursive: true, force: true });
179
+ }
180
+ });
181
+
182
+ test('already-patched target refuses success when backup is also patched', async () => {
183
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-invalid-backup-${Date.now()}-${Math.random()}`);
184
+ const fixture = await createFixture();
185
+
186
+ try {
187
+ const patchedContent = `${await readFile(fixture.targetPath, 'utf8')}${buildCursorAgentExecPatchSnippet()}`;
188
+ await writeFile(fixture.targetPath, patchedContent, 'utf8');
189
+
190
+ const backupPath = await backupPathForCursorAgentExec(fixture.appPath, fixture.targetPath, tempDir);
191
+ await mkdir(tempDir, { recursive: true });
192
+ await writeFile(backupPath, patchedContent, 'utf8');
193
+
194
+ await assert.rejects(
195
+ patchCursorAgentExec(fixture.appPath, { backupDir: tempDir }),
196
+ /backup.*patch marker/i,
197
+ );
198
+ } finally {
199
+ await rm(fixture.appPath, { recursive: true, force: true });
200
+ await rm(tempDir, { recursive: true, force: true });
201
+ }
202
+ });
203
+
204
+ test('patch upgrades an older Cursor Pool marker from clean backup instead of stacking snippets', async () => {
205
+ const tempDir = join(tmpdir(), `cursor-pool-patcher-upgrade-${Date.now()}-${Math.random()}`);
206
+ const fixture = await createFixture();
207
+
208
+ try {
209
+ const originalContent = await readFile(fixture.targetPath, 'utf8');
210
+ const backupPath = await backupPathForCursorAgentExec(fixture.appPath, fixture.targetPath, tempDir);
211
+ await mkdir(tempDir, { recursive: true });
212
+ await writeFile(backupPath, originalContent, 'utf8');
213
+ await writeFile(
214
+ fixture.targetPath,
215
+ `${originalContent}
216
+ ${CURSOR_POOL_PATCH_BEGIN_MARKER}
217
+ ;(() => { globalThis.__cursorPoolAgentExecLoopback = true; })();
218
+ ${CURSOR_POOL_PATCH_END_MARKER}
219
+ `,
220
+ 'utf8',
221
+ );
222
+
223
+ const result = await patchCursorAgentExec(fixture.appPath, { backupDir: tempDir });
224
+ const patchedContent = await readFile(fixture.targetPath, 'utf8');
225
+
226
+ assert.equal(result.markerPresent, true);
227
+ assert.equal(await hasCursorPoolMarker(fixture.targetPath), true);
228
+ assert.equal(patchedContent.includes('__cursorPoolAgentExecLoopback'), false);
229
+ assert.equal(patchedContent.includes(CURSOR_POOL_PATCH_SIGNATURE), true);
230
+ assert.equal((patchedContent.match(new RegExp(CURSOR_POOL_PATCH_MARKER, 'g')) ?? []).length, 2);
231
+ assert.equal(await readFile(backupPath, 'utf8'), originalContent);
232
+ } finally {
233
+ await rm(fixture.appPath, { recursive: true, force: true });
234
+ await rm(tempDir, { recursive: true, force: true });
235
+ }
236
+ });
237
+
238
+ test('agent exec patch snippet wraps the Cursor Agent provider instead of sending side-channel events', () => {
239
+ const snippet = buildCursorAgentExecPatchSnippet();
240
+
241
+ for (const expected of [
242
+ CURSOR_POOL_PATCH_MARKER,
243
+ CURSOR_POOL_PATCH_SIGNATURE,
244
+ "require('node:fs')",
245
+ "require('node:os')",
246
+ "require('node:path')",
247
+ "require('node:http')",
248
+ 'CURSOR_POOL_RUNTIME_FILE',
249
+ '.cursor-pool',
250
+ 'runtime.json',
251
+ 'registerAgentExecProvider',
252
+ 'wrapCursorPoolProvider',
253
+ 'requestCursorPoolTakeover',
254
+ '/agent/takeover',
255
+ 'createSession',
256
+ 'runLocalAgent',
257
+ 'fetchLocalProviderModels',
258
+ "runtime.host !== '127.0.0.1'",
259
+ "typeof runtime.port !== 'number'",
260
+ "typeof runtime.runtimeId !== 'string'",
261
+ "hostname: '127.0.0.1'",
262
+ 'port: runtime.port',
263
+ "method: 'POST'",
264
+ 'timeout: 1000',
265
+ "'content-type': 'application/json'",
266
+ "'content-length': Buffer.byteLength(body)",
267
+ "request.on('error'",
268
+ "request.on('timeout'",
269
+ 'request.destroy()',
270
+ 'request.end(body)',
271
+ ]) {
272
+ assert.equal(snippet.includes(expected), true, `expected snippet to include ${expected}`);
273
+ }
274
+
275
+ assert.equal(snippet.includes('createStream(context, requestBytes, abortSignal)'), false);
276
+
277
+ for (const removedSideChannel of [
278
+ "sendLoopback('/agent/canary')",
279
+ "sendLoopback('/agent/request-check')",
280
+ "sendLoopback('/agent/request-gateway')",
281
+ 'response.resume()',
282
+ ]) {
283
+ assert.equal(
284
+ snippet.includes(removedSideChannel),
285
+ false,
286
+ `expected snippet not to include removed side-channel ${removedSideChannel}`,
287
+ );
288
+ }
289
+
290
+ const payloadMatch = snippet.match(
291
+ /const body = JSON\.stringify\(\{\n(?<body>[\s\S]*?)\n \}\);/,
292
+ );
293
+ assert.ok(payloadMatch?.groups?.body, 'expected snippet to build a JSON.stringify payload object');
294
+ assert.equal(
295
+ payloadMatch.groups.body,
296
+ [
297
+ ' requestId,',
298
+ " source: 'cursor-agent-exec',",
299
+ " model: context?.modelName || 'unknown',",
300
+ ' stream: true,',
301
+ " bodyShape: 'redacted',",
302
+ ].join('\n'),
303
+ );
304
+
305
+ for (const forbiddenPayloadKey of [
306
+ 'runtimeId',
307
+ 'routeToken',
308
+ 'receivedAt',
309
+ 'prompt',
310
+ 'messages',
311
+ 'apiKey',
312
+ 'authorization',
313
+ 'cookie',
314
+ 'cursorAuthToken',
315
+ 'providerSecret',
316
+ ]) {
317
+ assert.equal(
318
+ payloadMatch.groups.body.includes(forbiddenPayloadKey),
319
+ false,
320
+ `expected payload not to include ${forbiddenPayloadKey}`,
321
+ );
322
+ }
323
+
324
+ for (const sensitiveKey of [
325
+ 'prompt',
326
+ 'messages',
327
+ 'apiKey',
328
+ 'authorization',
329
+ 'cookie',
330
+ 'cursorAuthToken',
331
+ 'providerSecret',
332
+ ]) {
333
+ assert.equal(
334
+ snippet.includes(sensitiveKey),
335
+ false,
336
+ `expected snippet not to include sensitive key ${sensitiveKey}`,
337
+ );
338
+ }
339
+ });
340
+
341
+ test('agent exec patch injects the wrapper before Cursor registers the provider', () => {
342
+ const content = `before();${CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR}after();`;
343
+ const patched = injectCursorAgentExecPatchSnippet(content);
344
+
345
+ assert.equal(patched.includes(CURSOR_POOL_PATCH_SIGNATURE), true);
346
+ assert.equal(
347
+ patched.indexOf(CURSOR_POOL_PATCH_SIGNATURE) < patched.indexOf(CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHOR),
348
+ true,
349
+ );
350
+ });
351
+
352
+ test('agent exec patch supports the Cursor 3.6 registerAgentExecProvider anchor', () => {
353
+ const anchor = 'const ht=c.cursor.registerAgentExecProvider(mt);';
354
+ assert.equal(CURSOR_POOL_AGENT_EXEC_PROVIDER_REGISTER_ANCHORS.includes(anchor), true);
355
+
356
+ const patched = injectCursorAgentExecPatchSnippet(`before();${anchor}after();`);
357
+
358
+ assert.equal(patched.includes(CURSOR_POOL_PATCH_SIGNATURE), true);
359
+ assert.ok(patched.indexOf(CURSOR_POOL_PATCH_SIGNATURE) < patched.indexOf(anchor));
360
+ });
361
+
362
+ test('agent exec provider wrapper works when Cursor API object is minified as c', async () => {
363
+ const snippet = buildCursorAgentExecPatchSnippet();
364
+ const registered: unknown[] = [];
365
+ const runtime = { host: '127.0.0.1', port: 32123, runtimeId: 'runtime-test' };
366
+ const originalProvider = {
367
+ async runLocalAgent() {
368
+ return 'ok';
369
+ },
370
+ };
371
+
372
+ const fn = new Function(
373
+ 'registered',
374
+ 'originalProvider',
375
+ 'runtime',
376
+ `
377
+ const require = (specifier) => {
378
+ if (specifier === 'node:fs') {
379
+ return {
380
+ existsSync: () => true,
381
+ readFileSync: () => JSON.stringify(runtime),
382
+ };
383
+ }
384
+ if (specifier === 'node:os') {
385
+ return { homedir: () => '/home/test' };
386
+ }
387
+ if (specifier === 'node:path') {
388
+ return { join: (...parts) => parts.join('/') };
389
+ }
390
+ if (specifier === 'node:http') {
391
+ return { request() { return { on() { return this; }, end() {}, destroy() {} }; } };
392
+ }
393
+ throw new Error('unexpected require ' + specifier);
394
+ };
395
+ const process = { env: {} };
396
+ const c = {
397
+ cursor: {
398
+ registerAgentExecProvider(provider) {
399
+ registered.push(provider);
400
+ return { dispose() {} };
401
+ },
402
+ },
403
+ };
404
+ ${snippet}
405
+ c.cursor.registerAgentExecProvider(originalProvider);
406
+ return registered[0];`,
407
+ );
408
+
409
+ const wrappedProvider = fn(registered, originalProvider, runtime) as typeof originalProvider;
410
+
411
+ assert.equal(registered.length, 1);
412
+ assert.notEqual(wrappedProvider, originalProvider);
413
+ assert.equal(await wrappedProvider.runLocalAgent(), 'ok');
414
+ });
415
+
416
+ test('agent exec provider wrapper preserves original runLocalAgent so Cursor local client can consume OpenAI endpoint', async () => {
417
+ const snippet = buildCursorAgentExecPatchSnippet();
418
+ const runtime = { host: '127.0.0.1', port: 32123, runtimeId: 'runtime-test' };
419
+ const calls: string[] = [];
420
+ const originalProvider = {
421
+ async runLocalAgent() {
422
+ calls.push('original-runLocalAgent');
423
+ },
424
+ };
425
+ const registered: unknown[] = [];
426
+ const sandbox = {
427
+ require(specifier: string) {
428
+ if (specifier === 'node:fs') {
429
+ return {
430
+ existsSync: () => true,
431
+ readFileSync: () => JSON.stringify(runtime),
432
+ };
433
+ }
434
+ if (specifier === 'node:os') {
435
+ return { homedir: () => '/home/test' };
436
+ }
437
+ if (specifier === 'node:path') {
438
+ return { join: (...parts: string[]) => parts.join('/') };
439
+ }
440
+ if (specifier === 'node:http') {
441
+ return {
442
+ request(_options: unknown, callback: (response: unknown) => void) {
443
+ const handlers = new Map<string, () => void>();
444
+ return {
445
+ on(event: string, handler: () => void) {
446
+ handlers.set(event, handler);
447
+ return this;
448
+ },
449
+ end(body: string) {
450
+ calls.push(body);
451
+ callback({
452
+ setEncoding() {},
453
+ on(event: string, handler: (chunk?: string) => void) {
454
+ if (event === 'data') {
455
+ handler(JSON.stringify({
456
+ state: 'answered',
457
+ requestId: 'takeover-1',
458
+ source: 'cursor-agent-exec',
459
+ model: 'gpt-test',
460
+ content: '来自 Cursor Pool 本地测试 provider',
461
+ }));
462
+ }
463
+ if (event === 'end') {
464
+ handler();
465
+ }
466
+ },
467
+ });
468
+ },
469
+ destroy() {},
470
+ };
471
+ },
472
+ };
473
+ }
474
+ throw new Error(`unexpected require ${specifier}`);
475
+ },
476
+ process: { env: {} },
477
+ globalThis: {} as Record<string, unknown>,
478
+ F: {
479
+ cursor: {
480
+ registerAgentExecProvider(provider: unknown) {
481
+ registered.push(provider);
482
+ return { dispose() {} };
483
+ },
484
+ },
485
+ },
486
+ };
487
+
488
+ Function(
489
+ 'require',
490
+ 'process',
491
+ 'globalThis',
492
+ 'F',
493
+ 'originalProvider',
494
+ 'registered',
495
+ `${snippet}
496
+ F.cursor.registerAgentExecProvider(originalProvider);
497
+ return registered[0];`,
498
+ ).call(
499
+ sandbox,
500
+ sandbox.require,
501
+ sandbox.process,
502
+ sandbox.globalThis,
503
+ sandbox.F,
504
+ originalProvider,
505
+ registered,
506
+ );
507
+
508
+ const wrappedProvider = registered[0] as typeof originalProvider;
509
+ await wrappedProvider.runLocalAgent(
510
+ {
511
+ runOptions: { generationUUID: 'req-1' },
512
+ modelDetails: undefined,
513
+ defaultModel: undefined,
514
+ },
515
+ {},
516
+ );
517
+
518
+ assert.deepEqual(calls, ['original-runLocalAgent']);
519
+ assert.equal((sandbox.globalThis.__cursorPoolAgentExecTakeover as { lastAnswer?: unknown }).lastAnswer, undefined);
520
+ });
521
+
522
+ test('agent exec provider wrapper preserves original createStream so Cursor stream frames are not swallowed', async () => {
523
+ const snippet = buildCursorAgentExecPatchSnippet();
524
+ const runtime = { host: '127.0.0.1', port: 32123, runtimeId: 'runtime-test' };
525
+ const calls: string[] = [];
526
+ const originalFrame = new Uint8Array([77, 1, 2, 3]);
527
+ const originalProvider = {
528
+ createSession() {
529
+ return {
530
+ createStream() {
531
+ return async function* originalStream() {
532
+ calls.push('original-createStream');
533
+ yield originalFrame;
534
+ };
535
+ },
536
+ };
537
+ },
538
+ };
539
+ const registered: unknown[] = [];
540
+ const sandbox = {
541
+ require(specifier: string) {
542
+ if (specifier === 'node:fs') {
543
+ return {
544
+ existsSync: () => true,
545
+ readFileSync: () => JSON.stringify(runtime),
546
+ };
547
+ }
548
+ if (specifier === 'node:os') {
549
+ return { homedir: () => '/home/test' };
550
+ }
551
+ if (specifier === 'node:path') {
552
+ return { join: (...parts: string[]) => parts.join('/') };
553
+ }
554
+ if (specifier === 'node:http') {
555
+ return {
556
+ request(_options: unknown, callback: (response: unknown) => void) {
557
+ return {
558
+ on() {
559
+ return this;
560
+ },
561
+ end(body: string) {
562
+ calls.push(body);
563
+ callback({
564
+ setEncoding() {},
565
+ on(event: string, handler: (chunk?: string) => void) {
566
+ if (event === 'data') {
567
+ handler(JSON.stringify({
568
+ state: 'answered',
569
+ requestId: 'takeover-1',
570
+ source: 'cursor-agent-exec',
571
+ model: 'gpt-test',
572
+ content: '来自 Cursor Pool 本地测试 provider',
573
+ }));
574
+ }
575
+ if (event === 'end') {
576
+ handler();
577
+ }
578
+ },
579
+ });
580
+ },
581
+ destroy() {},
582
+ };
583
+ },
584
+ };
585
+ }
586
+ throw new Error(`unexpected require ${specifier}`);
587
+ },
588
+ process: { env: {} },
589
+ globalThis: {} as Record<string, unknown>,
590
+ F: {
591
+ cursor: {
592
+ registerAgentExecProvider(provider: unknown) {
593
+ registered.push(provider);
594
+ return { dispose() {} };
595
+ },
596
+ },
597
+ },
598
+ };
599
+
600
+ Function(
601
+ 'require',
602
+ 'process',
603
+ 'globalThis',
604
+ 'F',
605
+ 'originalProvider',
606
+ 'registered',
607
+ `${snippet}
608
+ F.cursor.registerAgentExecProvider(originalProvider);
609
+ return registered[0];`,
610
+ ).call(
611
+ sandbox,
612
+ sandbox.require,
613
+ sandbox.process,
614
+ sandbox.globalThis,
615
+ sandbox.F,
616
+ originalProvider,
617
+ registered,
618
+ );
619
+
620
+ const wrappedProvider = registered[0] as typeof originalProvider;
621
+ const session = wrappedProvider.createSession();
622
+ const frames = [];
623
+ for await (const frame of session.createStream({ requestId: 'stream-1', modelName: 'gpt-test' }, new Uint8Array(), { aborted: false })()) {
624
+ frames.push(frame);
625
+ }
626
+
627
+ assert.deepEqual(calls, ['original-createStream']);
628
+ assert.deepEqual(frames, [originalFrame]);
629
+ assert.equal((sandbox.globalThis.__cursorPoolAgentExecTakeover as { lastAnswer?: unknown }).lastAnswer, undefined);
630
+ });