@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,2910 @@
1
+ // packages/extension/src/api.ts
2
+ import { arch, hostname, platform as osPlatform } from "node:os";
3
+
4
+ // packages/service/src/health.ts
5
+ function buildHealth(runtime) {
6
+ return {
7
+ ok: true,
8
+ host: runtime.host,
9
+ port: runtime.port,
10
+ runtimeId: runtime.runtimeId
11
+ };
12
+ }
13
+
14
+ // packages/shared/src/metadata.ts
15
+ var SAFE_METADATA_KEYS = [
16
+ "requestId",
17
+ "model",
18
+ "requestType",
19
+ "source",
20
+ "cursorVersion",
21
+ "clientVersion",
22
+ "entrypoint"
23
+ ];
24
+ function sanitizeRequestMetadata(input) {
25
+ const output = {};
26
+ for (const key of SAFE_METADATA_KEYS) {
27
+ if (input[key] !== void 0) {
28
+ output[key] = input[key];
29
+ }
30
+ }
31
+ return output;
32
+ }
33
+
34
+ // packages/service/src/metadata.ts
35
+ var EXTENSION_STATUS_KEYS = ["connected", "cursorVersion", "clientVersion"];
36
+ function sanitizeServiceMetadata(input) {
37
+ return {
38
+ model: "unknown",
39
+ ...sanitizeRequestMetadata(input)
40
+ };
41
+ }
42
+ function sanitizeExtensionStatus(input) {
43
+ const output = {};
44
+ for (const key of EXTENSION_STATUS_KEYS) {
45
+ if (input[key] !== void 0) {
46
+ output[key] = input[key];
47
+ }
48
+ }
49
+ return output;
50
+ }
51
+
52
+ // packages/service/src/platformSession.ts
53
+ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
54
+ import { homedir } from "node:os";
55
+ import { dirname, join } from "node:path";
56
+ var DEFAULT_PLATFORM_SESSION_FILE = "~/.cursor-pool/session.json";
57
+ var SAFE_POOL_TOKEN_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
58
+ var SECRET_LIKE_PATTERN = /(api[_-]?key|authorization|bearer|cursor[_-]?auth|provider[_-]?secret|secret|token|sk-[A-Za-z0-9])/i;
59
+ function resolvePlatformSessionFile(sessionFile = DEFAULT_PLATFORM_SESSION_FILE) {
60
+ if (sessionFile.startsWith("~/")) {
61
+ return join(homedir(), sessionFile.slice(2));
62
+ }
63
+ return sessionFile;
64
+ }
65
+ function cleanApiBaseUrl(apiBaseUrl) {
66
+ return apiBaseUrl.replace(/\/+$/, "");
67
+ }
68
+ function isSafePoolToken(value) {
69
+ return typeof value === "string" && SAFE_POOL_TOKEN_PATTERN.test(value) && !SECRET_LIKE_PATTERN.test(value);
70
+ }
71
+ function cleanPoolTokens(values) {
72
+ return values.filter(isSafePoolToken);
73
+ }
74
+ function sanitizePoolSessionSummary(value) {
75
+ return {
76
+ id: value.id,
77
+ productId: value.productId,
78
+ providerType: value.providerType,
79
+ status: value.status,
80
+ startedAt: value.startedAt,
81
+ expiresAt: value.expiresAt,
82
+ routeTokenExpiresAt: value.routeTokenExpiresAt,
83
+ routeStrategy: "platform-gateway",
84
+ bannedModels: cleanPoolTokens(value.bannedModels),
85
+ ...value.availableModels !== void 0 ? { availableModels: cleanPoolTokens(value.availableModels) } : {},
86
+ capabilities: {
87
+ streaming: value.capabilities.streaming,
88
+ usageEstimate: value.capabilities.usageEstimate,
89
+ hardSpendLimit: value.capabilities.hardSpendLimit
90
+ }
91
+ };
92
+ }
93
+ function asStatus(session) {
94
+ return {
95
+ state: "logged-in",
96
+ user: session.user,
97
+ device: session.device,
98
+ mode: platformModeFromSession(session)
99
+ };
100
+ }
101
+ function isPlatformModeReleaseReason(value) {
102
+ return value === "product-missing" || value === "product-unavailable" || value === "insufficient-credits" || value === "invalid-token" || value === "device-inactive";
103
+ }
104
+ function inactiveModeFromSession(session) {
105
+ if (isPlatformModeReleaseReason(session?.lastModeReleaseReason) && typeof session.lastModeReleasedAt === "string") {
106
+ return {
107
+ state: "inactive",
108
+ releaseReason: session.lastModeReleaseReason,
109
+ releasedAt: session.lastModeReleasedAt
110
+ };
111
+ }
112
+ return { state: "inactive" };
113
+ }
114
+ function platformModeFromSession(session) {
115
+ if (session?.platformMode === "active" && typeof session.activeProductId === "string" && typeof session.platformModeStartedAt === "string") {
116
+ return {
117
+ state: "active",
118
+ productId: session.activeProductId,
119
+ startedAt: session.platformModeStartedAt
120
+ };
121
+ }
122
+ return inactiveModeFromSession(session);
123
+ }
124
+ function clearPlatformMode(session) {
125
+ const {
126
+ platformMode: _platformMode,
127
+ activeProductId: _activeProductId,
128
+ platformModeStartedAt: _platformModeStartedAt,
129
+ poolSession: _poolSession,
130
+ routeToken: _routeToken,
131
+ ...inactiveSession
132
+ } = session;
133
+ return inactiveSession;
134
+ }
135
+ function clearModeRelease(session) {
136
+ const {
137
+ lastModeReleaseReason: _lastModeReleaseReason,
138
+ lastModeReleasedAt: _lastModeReleasedAt,
139
+ ...activeSession
140
+ } = session;
141
+ return activeSession;
142
+ }
143
+ function releasePlatformMode(session, reason) {
144
+ const releasedAt = (/* @__PURE__ */ new Date()).toISOString();
145
+ return {
146
+ session: {
147
+ ...clearPlatformMode(session),
148
+ lastModeReleaseReason: reason,
149
+ lastModeReleasedAt: releasedAt
150
+ },
151
+ mode: { state: "inactive", releaseReason: reason, releasedAt }
152
+ };
153
+ }
154
+ async function releaseActivePlatformMode(session, reason, options) {
155
+ const mode = platformModeFromSession(session);
156
+ if (mode.state !== "active") {
157
+ return { session, mode };
158
+ }
159
+ const released = releasePlatformMode(session, reason);
160
+ await writePlatformSession(released.session, options);
161
+ return released;
162
+ }
163
+ async function releaseForInactiveDevice(session, options) {
164
+ if (session.device.status === "active") {
165
+ return { session, mode: platformModeFromSession(session) };
166
+ }
167
+ return releaseActivePlatformMode(session, "device-inactive", options);
168
+ }
169
+ function isUserSummary(value) {
170
+ const user = value;
171
+ return typeof user?.id === "string" && typeof user.email === "string";
172
+ }
173
+ function isDeviceSummary(value) {
174
+ const device = value;
175
+ return typeof device?.id === "string" && typeof device.status === "string" && typeof device.lastHeartbeatAt === "string";
176
+ }
177
+ function isProductSummary(value) {
178
+ const product = value;
179
+ return typeof product?.id === "string" && typeof product.name === "string" && typeof product.description === "string" && (product.status === "available" || product.status === "unavailable") && Number.isInteger(product.minCredits) && product.minCredits >= 0 && typeof product.usageLabel === "string";
180
+ }
181
+ function sanitizeProductSummary(value) {
182
+ if (!isProductSummary(value)) {
183
+ return void 0;
184
+ }
185
+ return {
186
+ id: value.id,
187
+ name: value.name,
188
+ description: value.description,
189
+ status: value.status,
190
+ minCredits: value.minCredits,
191
+ usageLabel: value.usageLabel
192
+ };
193
+ }
194
+ function isPoolSessionSummary(value) {
195
+ const session = value;
196
+ return typeof session?.id === "string" && typeof session.productId === "string" && typeof session.providerType === "string" && (session.status === "active" || session.status === "refresh-required" || session.status === "stopping" || session.status === "stopped" || session.status === "expired" || session.status === "failed") && typeof session.startedAt === "string" && typeof session.expiresAt === "string" && typeof session.routeTokenExpiresAt === "string" && session.routeStrategy === "platform-gateway" && Array.isArray(session.bannedModels) && session.bannedModels.every((model) => typeof model === "string") && (session.availableModels === void 0 || Array.isArray(session.availableModels) && session.availableModels.every((model) => typeof model === "string")) && typeof session.capabilities?.streaming === "boolean" && typeof session.capabilities.usageEstimate === "boolean" && typeof session.capabilities.hardSpendLimit === "boolean";
197
+ }
198
+ function isLocalRouteTokenState(value) {
199
+ const route = value;
200
+ return typeof route?.token === "string" && typeof route.expiresAt === "string";
201
+ }
202
+ function isPlatformSession(value) {
203
+ const session = value;
204
+ return typeof session?.apiBaseUrl === "string" && typeof session.deviceToken === "string" && typeof session.createdAt === "string" && isUserSummary(session.user) && isDeviceSummary(session.device) && (session.selectedProductId === void 0 || typeof session.selectedProductId === "string") && (session.platformMode === void 0 || session.platformMode === "active" || session.platformMode === "inactive") && (session.activeProductId === void 0 || typeof session.activeProductId === "string") && (session.platformModeStartedAt === void 0 || typeof session.platformModeStartedAt === "string") && (session.lastModeReleaseReason === void 0 || isPlatformModeReleaseReason(session.lastModeReleaseReason)) && (session.lastModeReleasedAt === void 0 || typeof session.lastModeReleasedAt === "string") && (session.poolSession === void 0 || isPoolSessionSummary(session.poolSession)) && (session.routeToken === void 0 || isLocalRouteTokenState(session.routeToken));
205
+ }
206
+ async function readPlatformSessionSnapshot(options = {}) {
207
+ const sessionFile = resolvePlatformSessionFile(options.sessionFile);
208
+ try {
209
+ const session = JSON.parse(await readFile(sessionFile, "utf8"));
210
+ return isPlatformSession(session) ? { state: "valid", session } : { state: "invalid" };
211
+ } catch (error) {
212
+ if (error.code === "ENOENT") {
213
+ return { state: "missing" };
214
+ }
215
+ if (error instanceof SyntaxError) {
216
+ return { state: "invalid" };
217
+ }
218
+ throw error;
219
+ }
220
+ }
221
+ async function readPlatformGatewayForwardContext(options = {}) {
222
+ const snapshot = await readPlatformSessionSnapshot(options);
223
+ if (snapshot.state !== "valid") {
224
+ return {};
225
+ }
226
+ return {
227
+ apiBaseUrl: snapshot.session.apiBaseUrl,
228
+ deviceToken: snapshot.session.deviceToken,
229
+ routeToken: snapshot.session.routeToken?.token,
230
+ poolSessionId: snapshot.session.poolSession?.id
231
+ };
232
+ }
233
+ async function readPlatformSession(options = {}) {
234
+ const snapshot = await readPlatformSessionSnapshot(options);
235
+ return snapshot.state === "valid" ? snapshot.session : null;
236
+ }
237
+ async function writePlatformSession(session, options = {}) {
238
+ const sessionFile = resolvePlatformSessionFile(options.sessionFile);
239
+ await mkdir(dirname(sessionFile), { recursive: true, mode: 448 });
240
+ await writeFile(sessionFile, `${JSON.stringify(session, null, 2)}
241
+ `, { encoding: "utf8", mode: 384 });
242
+ await chmod(sessionFile, 384);
243
+ }
244
+ async function clearPlatformSession(options = {}) {
245
+ await rm(resolvePlatformSessionFile(options.sessionFile), { force: true });
246
+ }
247
+ async function requestJson(url, options = {}) {
248
+ const headers = {};
249
+ if (options.body) {
250
+ headers["content-type"] = "application/json";
251
+ }
252
+ if (options.token) {
253
+ headers.authorization = `Bearer ${options.token}`;
254
+ }
255
+ const response = await fetch(url, {
256
+ method: options.method ?? "GET",
257
+ headers,
258
+ body: options.body ? JSON.stringify(options.body) : void 0
259
+ });
260
+ if (!response.ok) {
261
+ const error = new Error(`Request failed with status ${response.status}`);
262
+ error.status = response.status;
263
+ try {
264
+ const body = await response.json();
265
+ const detail = body.detail;
266
+ if (typeof detail?.code === "string" && /^[A-Z0-9_:-]{1,96}$/.test(detail.code)) {
267
+ error.platformCode = detail.code;
268
+ }
269
+ } catch {
270
+ }
271
+ throw error;
272
+ }
273
+ return await response.json();
274
+ }
275
+ async function exchangeDeviceToken(request) {
276
+ return requestJson(`${cleanApiBaseUrl(request.apiBaseUrl)}/auth/device-token`, {
277
+ method: "POST",
278
+ body: {
279
+ code: request.code,
280
+ device: request.device
281
+ }
282
+ });
283
+ }
284
+ async function exchangePasswordDeviceToken(request) {
285
+ return requestJson(`${cleanApiBaseUrl(request.apiBaseUrl)}/auth/password-device-token`, {
286
+ method: "POST",
287
+ body: {
288
+ email: request.email,
289
+ password: request.password,
290
+ device: request.device
291
+ }
292
+ });
293
+ }
294
+ async function fetchMe(session) {
295
+ return requestJson(`${cleanApiBaseUrl(session.apiBaseUrl)}/me`, {
296
+ token: session.deviceToken
297
+ });
298
+ }
299
+ async function fetchAccountSummary(session) {
300
+ return requestJson(`${cleanApiBaseUrl(session.apiBaseUrl)}/account/summary`, {
301
+ token: session.deviceToken
302
+ });
303
+ }
304
+ async function fetchProducts(session) {
305
+ const response = await requestJson(`${cleanApiBaseUrl(session.apiBaseUrl)}/products`, {
306
+ token: session.deviceToken
307
+ });
308
+ return {
309
+ products: Array.isArray(response.products) ? response.products.flatMap((product) => {
310
+ const sanitized = sanitizeProductSummary(product);
311
+ return sanitized ? [sanitized] : [];
312
+ }) : []
313
+ };
314
+ }
315
+ async function postHeartbeat(request) {
316
+ return requestJson(`${cleanApiBaseUrl(request.session.apiBaseUrl)}/devices/heartbeat`, {
317
+ method: "POST",
318
+ body: request.payload,
319
+ token: request.session.deviceToken
320
+ });
321
+ }
322
+ async function postLogout(request) {
323
+ await requestJson(`${cleanApiBaseUrl(request.session.apiBaseUrl)}/auth/logout`, {
324
+ method: "POST",
325
+ token: request.session.deviceToken
326
+ });
327
+ }
328
+ async function startPoolSession(request) {
329
+ return requestJson(
330
+ `${cleanApiBaseUrl(request.session.apiBaseUrl)}/pool-sessions/start`,
331
+ {
332
+ method: "POST",
333
+ token: request.session.deviceToken,
334
+ body: {
335
+ productId: request.productId,
336
+ ...request.client ? { client: request.client } : {}
337
+ }
338
+ }
339
+ );
340
+ }
341
+ async function stopPoolSession(request) {
342
+ return requestJson(
343
+ `${cleanApiBaseUrl(request.session.apiBaseUrl)}/pool-sessions/${request.poolSessionId}/stop`,
344
+ {
345
+ method: "POST",
346
+ token: request.session.deviceToken,
347
+ body: {
348
+ reason: request.reason
349
+ }
350
+ }
351
+ );
352
+ }
353
+ function poolFailureToModeResult(reason, product, currentCredits) {
354
+ if (reason === "INSUFFICIENT_CREDITS") {
355
+ return {
356
+ state: "insufficient-credits",
357
+ productId: product.id,
358
+ requiredCredits: product.minCredits,
359
+ currentCredits
360
+ };
361
+ }
362
+ if (reason === "DEVICE_INVALID") {
363
+ return { state: "device-inactive" };
364
+ }
365
+ if (reason === "PRODUCT_UNAVAILABLE") {
366
+ return { state: "product-unavailable", productId: product.id };
367
+ }
368
+ if (reason === "PROVIDER_UNAVAILABLE") {
369
+ return { state: "provider-unavailable" };
370
+ }
371
+ if (reason === "RATE_LIMITED") {
372
+ return { state: "provider-rate-limited" };
373
+ }
374
+ if (reason === "MODEL_BANNED") {
375
+ return { state: "model-banned" };
376
+ }
377
+ return { state: "manual-review-required" };
378
+ }
379
+ async function statusFromRequestError(error, session, options = {}) {
380
+ if (error.status === 401) {
381
+ const currentSession = await readPlatformSession(options) ?? session;
382
+ const { mode } = options.releaseActiveModeOnUnauthorized ? await releaseActivePlatformMode(currentSession, "invalid-token", options) : { mode: platformModeFromSession(currentSession) };
383
+ return { state: "invalid-token", user: currentSession.user, device: currentSession.device, mode };
384
+ }
385
+ return { state: "offline", user: session.user, device: session.device, mode: platformModeFromSession(session) };
386
+ }
387
+ async function loginWithCode(options) {
388
+ const tokenResponse = await (options.exchangeDeviceToken ?? exchangeDeviceToken)({
389
+ code: options.code,
390
+ apiBaseUrl: options.apiBaseUrl,
391
+ device: options.device ?? {}
392
+ });
393
+ const session = {
394
+ apiBaseUrl: cleanApiBaseUrl(options.apiBaseUrl),
395
+ deviceToken: tokenResponse.deviceToken,
396
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
397
+ user: tokenResponse.user,
398
+ device: tokenResponse.device
399
+ };
400
+ await writePlatformSession(session, { sessionFile: options.sessionFile });
401
+ return asStatus(session);
402
+ }
403
+ async function loginWithPassword(options) {
404
+ const tokenResponse = await (options.exchangePasswordDeviceToken ?? exchangePasswordDeviceToken)({
405
+ email: options.email,
406
+ password: options.password,
407
+ apiBaseUrl: options.apiBaseUrl,
408
+ device: options.device ?? {}
409
+ });
410
+ const session = {
411
+ apiBaseUrl: cleanApiBaseUrl(options.apiBaseUrl),
412
+ deviceToken: tokenResponse.deviceToken,
413
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
414
+ user: tokenResponse.user,
415
+ device: tokenResponse.device
416
+ };
417
+ await writePlatformSession(session, { sessionFile: options.sessionFile });
418
+ return asStatus(session);
419
+ }
420
+ async function platformStatus(options = {}) {
421
+ const session = await readPlatformSession(options);
422
+ if (!session) {
423
+ return { state: "logged-out" };
424
+ }
425
+ let me;
426
+ try {
427
+ me = await fetchMe(session);
428
+ } catch (error) {
429
+ return statusFromRequestError(error, session, {
430
+ ...options,
431
+ releaseActiveModeOnUnauthorized: true
432
+ });
433
+ }
434
+ const currentSession = await readPlatformSession(options);
435
+ if (!currentSession) {
436
+ return { state: "logged-out" };
437
+ }
438
+ const updatedSession = { ...currentSession, user: me.user, device: me.device };
439
+ await writePlatformSession(updatedSession, options);
440
+ const released = await releaseForInactiveDevice(updatedSession, options);
441
+ return {
442
+ state: "logged-in",
443
+ user: released.session.user,
444
+ device: released.session.device,
445
+ mode: released.mode
446
+ };
447
+ }
448
+ async function platformCatalog(options = {}) {
449
+ const session = await readPlatformSession(options);
450
+ if (!session) {
451
+ return { state: "logged-out", products: [] };
452
+ }
453
+ const [account, products] = await Promise.allSettled([
454
+ fetchAccountSummary(session),
455
+ fetchProducts(session)
456
+ ]);
457
+ const failures = [account, products].filter((result) => result.status === "rejected");
458
+ if (failures.some((result) => result.reason.status === 401)) {
459
+ const { mode: mode2 } = await releaseActivePlatformMode(session, "invalid-token", options);
460
+ return { state: "invalid-token", mode: mode2, products: [] };
461
+ }
462
+ if (failures.length > 0) {
463
+ return { state: "offline", products: [] };
464
+ }
465
+ const catalogProducts = products.value.products;
466
+ const selectedProductId = catalogProducts.some((product) => product.id === session.selectedProductId) ? session.selectedProductId : void 0;
467
+ let updatedSession = session;
468
+ let mode = platformModeFromSession(session);
469
+ let shouldWriteSession = false;
470
+ if (session.selectedProductId && !selectedProductId) {
471
+ const { selectedProductId: _staleSelection, ...sessionWithoutStaleSelection } = session;
472
+ if (mode.state === "active") {
473
+ const released = releasePlatformMode(sessionWithoutStaleSelection, "product-missing");
474
+ updatedSession = released.session;
475
+ mode = released.mode;
476
+ } else {
477
+ updatedSession = clearPlatformMode(sessionWithoutStaleSelection);
478
+ mode = { state: "inactive" };
479
+ }
480
+ shouldWriteSession = true;
481
+ } else if (mode.state === "active") {
482
+ const activeProduct = catalogProducts.find((product) => product.id === mode.productId);
483
+ const releaseReason = !activeProduct ? "product-missing" : activeProduct.status !== "available" ? "product-unavailable" : account.value.credits < activeProduct.minCredits ? "insufficient-credits" : void 0;
484
+ if (releaseReason) {
485
+ const released = releasePlatformMode(updatedSession, releaseReason);
486
+ updatedSession = released.session;
487
+ mode = released.mode;
488
+ shouldWriteSession = true;
489
+ }
490
+ }
491
+ if (shouldWriteSession) {
492
+ await writePlatformSession(updatedSession, options);
493
+ }
494
+ return {
495
+ state: "logged-in",
496
+ account: { credits: account.value.credits },
497
+ mode,
498
+ ...selectedProductId ? { selectedProductId } : {},
499
+ products: catalogProducts
500
+ };
501
+ }
502
+ async function selectPlatformProduct(options) {
503
+ const session = await readPlatformSession(options);
504
+ if (!session) {
505
+ return { state: "logged-out" };
506
+ }
507
+ const catalog = await platformCatalog(options);
508
+ if (catalog.state === "invalid-token" || catalog.state === "offline") {
509
+ return { state: catalog.state };
510
+ }
511
+ if (catalog.state === "logged-out") {
512
+ return { state: "logged-out" };
513
+ }
514
+ const currentSession = await readPlatformSession(options);
515
+ if (!currentSession) {
516
+ return { state: "logged-out" };
517
+ }
518
+ const product = catalog.products.find((candidate) => candidate.id === options.productId);
519
+ if (!product) {
520
+ return { state: "product-not-found", productId: options.productId };
521
+ }
522
+ if (product.status !== "available") {
523
+ return { state: "product-unavailable", productId: options.productId };
524
+ }
525
+ await writePlatformSession({ ...currentSession, selectedProductId: options.productId }, options);
526
+ return { state: "selected", selectedProductId: options.productId };
527
+ }
528
+ async function startPlatformMode(options = {}) {
529
+ const session = await readPlatformSession(options);
530
+ if (!session) {
531
+ return { state: "logged-out" };
532
+ }
533
+ const catalog = await platformCatalog(options);
534
+ if (catalog.state === "logged-out" || catalog.state === "offline" || catalog.state === "invalid-token") {
535
+ return { state: catalog.state };
536
+ }
537
+ const currentSession = await readPlatformSession(options);
538
+ if (!currentSession) {
539
+ return { state: "logged-out" };
540
+ }
541
+ if (!currentSession.selectedProductId) {
542
+ return { state: "product-not-selected" };
543
+ }
544
+ const product = catalog.products.find((candidate) => candidate.id === currentSession.selectedProductId);
545
+ if (!product) {
546
+ return { state: "product-not-found", productId: currentSession.selectedProductId };
547
+ }
548
+ if (product.status !== "available") {
549
+ return { state: "product-unavailable", productId: product.id };
550
+ }
551
+ if (catalog.account.credits < product.minCredits) {
552
+ return {
553
+ state: "insufficient-credits",
554
+ productId: product.id,
555
+ requiredCredits: product.minCredits,
556
+ currentCredits: catalog.account.credits
557
+ };
558
+ }
559
+ let pool;
560
+ try {
561
+ pool = await (options.startPoolSession ?? startPoolSession)({
562
+ session: currentSession,
563
+ productId: product.id
564
+ });
565
+ } catch {
566
+ return { state: "offline" };
567
+ }
568
+ if (pool.state === "failed") {
569
+ return poolFailureToModeResult(pool.reason, product, catalog.account.credits);
570
+ }
571
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
572
+ await writePlatformSession({
573
+ ...clearModeRelease(currentSession),
574
+ platformMode: "active",
575
+ activeProductId: product.id,
576
+ platformModeStartedAt: startedAt,
577
+ poolSession: sanitizePoolSessionSummary(pool.session),
578
+ routeToken: {
579
+ token: pool.routeToken,
580
+ expiresAt: pool.session.routeTokenExpiresAt
581
+ }
582
+ }, options);
583
+ return { state: "active", productId: product.id, startedAt };
584
+ }
585
+ async function refreshPlatformRoute(options = {}) {
586
+ const session = await readPlatformSession(options);
587
+ if (!session || session.device.status !== "active" || session.platformMode !== "active" || !session.activeProductId) {
588
+ return { state: "not-refreshable" };
589
+ }
590
+ let pool;
591
+ try {
592
+ pool = await (options.startPoolSession ?? startPoolSession)({
593
+ session,
594
+ productId: session.activeProductId
595
+ });
596
+ } catch {
597
+ return { state: "failed", reason: "offline" };
598
+ }
599
+ if (pool.state === "failed") {
600
+ return { state: "failed", reason: poolFailureToModeResult(pool.reason, {
601
+ id: session.activeProductId,
602
+ name: session.activeProductId,
603
+ description: "",
604
+ status: "available",
605
+ minCredits: 0,
606
+ usageLabel: ""
607
+ }, 0).state };
608
+ }
609
+ const startedAt = (/* @__PURE__ */ new Date()).toISOString();
610
+ await writePlatformSession({
611
+ ...clearModeRelease(session),
612
+ platformMode: "active",
613
+ activeProductId: session.activeProductId,
614
+ platformModeStartedAt: startedAt,
615
+ poolSession: sanitizePoolSessionSummary(pool.session),
616
+ routeToken: {
617
+ token: pool.routeToken,
618
+ expiresAt: pool.session.routeTokenExpiresAt
619
+ }
620
+ }, options);
621
+ return { state: "active", productId: session.activeProductId, startedAt };
622
+ }
623
+ async function stopPlatformMode(options = {}) {
624
+ const session = await readPlatformSession(options);
625
+ if (session) {
626
+ if (session.poolSession) {
627
+ try {
628
+ await (options.stopPoolSession ?? stopPoolSession)({
629
+ session,
630
+ poolSessionId: session.poolSession.id,
631
+ reason: "user-stop"
632
+ });
633
+ } catch {
634
+ }
635
+ }
636
+ await writePlatformSession(clearPlatformMode(session), options);
637
+ }
638
+ return { state: "inactive" };
639
+ }
640
+ async function sendHeartbeat(options = {}) {
641
+ const session = await readPlatformSession(options);
642
+ if (!session) {
643
+ return { state: "logged-out" };
644
+ }
645
+ let heartbeat;
646
+ try {
647
+ heartbeat = await (options.postHeartbeat ?? postHeartbeat)({
648
+ session,
649
+ payload: options.payload ?? {}
650
+ });
651
+ } catch (error) {
652
+ return statusFromRequestError(error, session, {
653
+ ...options,
654
+ releaseActiveModeOnUnauthorized: true
655
+ });
656
+ }
657
+ const currentSession = await readPlatformSession(options);
658
+ if (!currentSession) {
659
+ return { state: "logged-out" };
660
+ }
661
+ const updatedSession = { ...currentSession, device: heartbeat.device };
662
+ await writePlatformSession(updatedSession, options);
663
+ const released = await releaseForInactiveDevice(updatedSession, options);
664
+ return {
665
+ state: "logged-in",
666
+ user: released.session.user,
667
+ device: released.session.device,
668
+ mode: released.mode
669
+ };
670
+ }
671
+ async function logoutPlatform(options = {}) {
672
+ const session = await readPlatformSession(options);
673
+ if (session) {
674
+ try {
675
+ await (options.postLogout ?? postLogout)({ session });
676
+ } catch {
677
+ }
678
+ }
679
+ await clearPlatformSession(options);
680
+ return { state: "logged-out" };
681
+ }
682
+
683
+ // packages/service/src/runtime.ts
684
+ import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
685
+ import { homedir as homedir2 } from "node:os";
686
+ import { dirname as dirname2, join as join2 } from "node:path";
687
+ import { randomUUID } from "node:crypto";
688
+
689
+ // packages/shared/src/runtime.ts
690
+ var DEFAULT_RUNTIME_FILE = "~/.cursor-pool/runtime.json";
691
+ var DEFAULT_DIAGNOSTICS_FILE = "~/.cursor-pool/diagnostics.jsonl";
692
+
693
+ // packages/service/src/runtime.ts
694
+ function createRuntimeId() {
695
+ return randomUUID();
696
+ }
697
+ function resolveRuntimeFile(runtimeFile = DEFAULT_RUNTIME_FILE) {
698
+ if (runtimeFile.startsWith("~/")) {
699
+ return join2(homedir2(), runtimeFile.slice(2));
700
+ }
701
+ return runtimeFile;
702
+ }
703
+ async function writeRuntimeInfo(runtime, options = {}) {
704
+ const runtimeFile = resolveRuntimeFile(options.runtimeFile);
705
+ await mkdir2(dirname2(runtimeFile), { recursive: true });
706
+ await writeFile2(runtimeFile, `${JSON.stringify(runtime, null, 2)}
707
+ `, "utf8");
708
+ }
709
+ async function readRuntimeInfo(options = {}) {
710
+ const runtimeFile = resolveRuntimeFile(options.runtimeFile);
711
+ try {
712
+ return JSON.parse(await readFile2(runtimeFile, "utf8"));
713
+ } catch (error) {
714
+ if (error.code === "ENOENT") {
715
+ return null;
716
+ }
717
+ if (error instanceof SyntaxError) {
718
+ return null;
719
+ }
720
+ throw error;
721
+ }
722
+ }
723
+
724
+ // packages/service/src/requestCheck.ts
725
+ import { randomUUID as randomUUID2 } from "node:crypto";
726
+ var SAFE_MODEL_PATTERN = /^[A-Za-z0-9._:-]{1,96}$/;
727
+ var SECRET_PATTERN = /(api[_-]?key|authorization|bearer|cursor[_-]?auth|provider[_-]?secret|secret|token|sk-[A-Za-z0-9])/i;
728
+ function isValidRequestId(value) {
729
+ return typeof value === "string" && value.length > 0 && value.length <= 128;
730
+ }
731
+ function sanitizeSource(value) {
732
+ return value === "cursor-agent-exec" || value === "manual-check" ? value : "manual-check";
733
+ }
734
+ function sanitizeModel(value) {
735
+ if (typeof value !== "string" || !SAFE_MODEL_PATTERN.test(value) || SECRET_PATTERN.test(value)) {
736
+ return "unknown";
737
+ }
738
+ return value;
739
+ }
740
+ function sanitizeAgentRequestCheck(input, options) {
741
+ const requestId = isValidRequestId(input.requestId) ? input.requestId : (options.requestId ?? randomUUID2)();
742
+ return {
743
+ requestId,
744
+ requestType: "agent",
745
+ source: sanitizeSource(input.source),
746
+ model: sanitizeModel(input.model),
747
+ receivedAt: (options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))(),
748
+ runtimeId: options.runtimeId,
749
+ decision: options.decision,
750
+ route: options.route ?? { state: "missing" }
751
+ };
752
+ }
753
+ function createRequestCheckStore() {
754
+ let latestCheck = null;
755
+ return {
756
+ record(check) {
757
+ latestCheck = check;
758
+ return check;
759
+ },
760
+ latest() {
761
+ return latestCheck;
762
+ }
763
+ };
764
+ }
765
+
766
+ // packages/service/src/server.ts
767
+ import { createServer } from "node:http";
768
+
769
+ // packages/service/src/canary.ts
770
+ import { randomUUID as randomUUID3 } from "node:crypto";
771
+ var isValidRequestId2 = (value) => typeof value === "string" && value.length > 0 && value.length <= 128;
772
+ function sanitizeAgentCanary(input, options) {
773
+ const requestId = isValidRequestId2(input.requestId) ? input.requestId : (options.requestId ?? randomUUID3)();
774
+ const model = typeof input.model === "string" && input.model.length > 0 ? input.model : "unknown";
775
+ return {
776
+ requestId,
777
+ requestType: "agent",
778
+ source: "cursor-agent-exec",
779
+ model,
780
+ receivedAt: (options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))(),
781
+ runtimeId: options.runtimeId,
782
+ gate: options.gate
783
+ };
784
+ }
785
+ function createCanaryStore() {
786
+ let latestCanary = null;
787
+ return {
788
+ record(canary) {
789
+ latestCanary = canary;
790
+ return canary;
791
+ },
792
+ latest() {
793
+ return latestCanary;
794
+ }
795
+ };
796
+ }
797
+
798
+ // packages/service/src/diagnostics.ts
799
+ import { mkdir as mkdir3, readFile as readFile3, writeFile as writeFile3 } from "node:fs/promises";
800
+ import { dirname as dirname3, join as join3 } from "node:path";
801
+ import { homedir as homedir3 } from "node:os";
802
+
803
+ // packages/service/src/requestGateway.ts
804
+ import { randomUUID as randomUUID4 } from "node:crypto";
805
+ var SAFE_MODEL_PATTERN2 = /^[A-Za-z0-9._:-]{1,96}$/;
806
+ var SAFE_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
807
+ var SAFE_FORWARD_TOKEN_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
808
+ var SECRET_PATTERN2 = /(api[_-]?key|authorization|bearer|cursor[_-]?auth|provider[_-]?secret|secret|token|sk-[A-Za-z0-9])/i;
809
+ var MAX_RETRY_AFTER_MS = 864e5;
810
+ var MAX_FORWARD_CONTENT_LENGTH = 8e3;
811
+ var FORWARD_REJECT_REASONS = /* @__PURE__ */ new Set([
812
+ "route-token-invalid",
813
+ "pool-session-expired",
814
+ "product-unavailable",
815
+ "model-banned",
816
+ "provider-unavailable",
817
+ "rate-limited",
818
+ "insufficient-credits",
819
+ "manual-review-required"
820
+ ]);
821
+ function isValidRequestId3(value) {
822
+ return typeof value === "string" && SAFE_REQUEST_ID_PATTERN.test(value) && !SECRET_PATTERN2.test(value);
823
+ }
824
+ function sanitizeSource2(value) {
825
+ return value === "cursor-agent-exec" || value === "manual-check" ? value : "unknown";
826
+ }
827
+ function sanitizeModel2(value) {
828
+ if (typeof value !== "string" || !SAFE_MODEL_PATTERN2.test(value) || SECRET_PATTERN2.test(value)) {
829
+ return "unknown";
830
+ }
831
+ return value;
832
+ }
833
+ function isSafeForwardToken(value) {
834
+ return typeof value === "string" && SAFE_FORWARD_TOKEN_PATTERN.test(value) && !SECRET_PATTERN2.test(value);
835
+ }
836
+ function isSafeIsoTimestamp(value) {
837
+ return isSafeForwardToken(value) && !Number.isNaN(Date.parse(value));
838
+ }
839
+ function safeForwardContent(value) {
840
+ if (typeof value !== "string") {
841
+ return void 0;
842
+ }
843
+ const content = value.trim();
844
+ if (!content || SECRET_PATTERN2.test(content)) {
845
+ return void 0;
846
+ }
847
+ return content.slice(0, MAX_FORWARD_CONTENT_LENGTH);
848
+ }
849
+ function evaluateGatewayDecision(gate, route) {
850
+ if (gate.state === "blocked") {
851
+ return {
852
+ ...gate,
853
+ route: { state: "missing" }
854
+ };
855
+ }
856
+ if (route.state === "ready") {
857
+ return {
858
+ state: "accepted",
859
+ productId: gate.productId,
860
+ modeStartedAt: gate.modeStartedAt,
861
+ route
862
+ };
863
+ }
864
+ if (route.state === "expired") {
865
+ return {
866
+ state: "route-expired",
867
+ productId: gate.productId,
868
+ modeStartedAt: gate.modeStartedAt,
869
+ route
870
+ };
871
+ }
872
+ return {
873
+ state: "route-missing",
874
+ productId: gate.productId,
875
+ modeStartedAt: gate.modeStartedAt,
876
+ route
877
+ };
878
+ }
879
+ function sanitizeAgentGateway(input, options) {
880
+ const requestId = isValidRequestId3(input.requestId) ? input.requestId : (options.requestId ?? randomUUID4)();
881
+ return {
882
+ requestId,
883
+ requestType: "agent",
884
+ source: sanitizeSource2(input.source),
885
+ model: sanitizeModel2(input.model),
886
+ receivedAt: (options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))(),
887
+ runtimeId: options.runtimeId,
888
+ decision: options.decision,
889
+ forward: { state: "unknown" }
890
+ };
891
+ }
892
+ function sanitizeGatewayForward(input) {
893
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
894
+ return { state: "unknown" };
895
+ }
896
+ const forward = input;
897
+ if (forward.state === "skipped") {
898
+ return forward.reason === "not-accepted" ? { state: "skipped", reason: "not-accepted" } : { state: "unknown" };
899
+ }
900
+ if (forward.state === "forwarded") {
901
+ if (!isSafeForwardToken(forward.upstreamRequestId) || !isSafeIsoTimestamp(forward.acceptedAt)) {
902
+ return { state: "unknown" };
903
+ }
904
+ const content = safeForwardContent(forward.content);
905
+ return {
906
+ state: "forwarded",
907
+ upstreamRequestId: forward.upstreamRequestId,
908
+ acceptedAt: forward.acceptedAt,
909
+ ...content ? { content } : {}
910
+ };
911
+ }
912
+ if (forward.state === "rejected") {
913
+ if (typeof forward.reason !== "string" || !FORWARD_REJECT_REASONS.has(forward.reason)) {
914
+ return { state: "unknown" };
915
+ }
916
+ const retryAfterMs = forward.retryAfterMs;
917
+ return {
918
+ state: "rejected",
919
+ reason: forward.reason,
920
+ ...Number.isInteger(retryAfterMs) && retryAfterMs >= 0 && retryAfterMs <= MAX_RETRY_AFTER_MS ? { retryAfterMs } : {}
921
+ };
922
+ }
923
+ if (forward.state === "not-configured" || forward.state === "timeout" || forward.state === "network-error") {
924
+ return { state: forward.state };
925
+ }
926
+ return { state: "unknown" };
927
+ }
928
+ async function resolveGatewayForward(options) {
929
+ const { safe } = await resolveGatewayForwardResult(options);
930
+ return safe;
931
+ }
932
+ async function resolveGatewayForwardResult(options) {
933
+ if (options.gateway.decision.state !== "accepted") {
934
+ return {
935
+ result: { state: "not-configured" },
936
+ safe: { state: "skipped", reason: "not-accepted" }
937
+ };
938
+ }
939
+ if (!options.forwarder) {
940
+ return {
941
+ result: { state: "not-configured" },
942
+ safe: { state: "not-configured" }
943
+ };
944
+ }
945
+ try {
946
+ const result = await options.forwarder({
947
+ gateway: options.gateway,
948
+ routeToken: options.routeToken,
949
+ poolSessionId: options.poolSessionId,
950
+ productId: options.productId,
951
+ model: options.model,
952
+ requestId: options.requestId,
953
+ settlementMode: options.settlementMode,
954
+ ...options.openAiRequest ? { openAiRequest: options.openAiRequest } : {}
955
+ });
956
+ return {
957
+ result,
958
+ safe: sanitizeGatewayForward(result)
959
+ };
960
+ } catch {
961
+ return {
962
+ result: { state: "network-error" },
963
+ safe: { state: "network-error" }
964
+ };
965
+ }
966
+ }
967
+ function cleanApiBaseUrl2(apiBaseUrl) {
968
+ return apiBaseUrl.replace(/\/+$/, "");
969
+ }
970
+ function createGatewayHttpForwarder(options) {
971
+ return async (request) => {
972
+ if (!options.apiBaseUrl || !options.deviceToken) {
973
+ return { state: "not-configured" };
974
+ }
975
+ const response = await fetch(`${cleanApiBaseUrl2(options.apiBaseUrl)}/api/client/gateway/agent`, {
976
+ method: "POST",
977
+ headers: {
978
+ "content-type": "application/json",
979
+ authorization: `Bearer ${options.deviceToken}`
980
+ },
981
+ body: JSON.stringify({
982
+ gateway: request.gateway,
983
+ routeToken: request.routeToken,
984
+ ...request.poolSessionId ? { poolSessionId: request.poolSessionId } : {},
985
+ productId: request.productId,
986
+ model: request.model,
987
+ requestId: request.requestId,
988
+ settlementMode: request.settlementMode ?? "provider_forwarded",
989
+ ...request.openAiRequest ? { openAiRequest: request.openAiRequest } : {}
990
+ })
991
+ });
992
+ if (!response.ok) {
993
+ return { state: "network-error" };
994
+ }
995
+ return await response.json();
996
+ };
997
+ }
998
+ async function completeGatewayForward(options) {
999
+ if (!options.apiBaseUrl || !options.deviceToken) {
1000
+ return { state: "not-configured" };
1001
+ }
1002
+ try {
1003
+ const response = await fetch(
1004
+ `${cleanApiBaseUrl2(options.apiBaseUrl)}/api/client/gateway/agent/${encodeURIComponent(options.requestId)}/complete`,
1005
+ {
1006
+ method: "POST",
1007
+ headers: {
1008
+ "content-type": "application/json",
1009
+ authorization: `Bearer ${options.deviceToken}`
1010
+ },
1011
+ body: JSON.stringify({
1012
+ routeToken: options.routeToken,
1013
+ poolSessionId: options.poolSessionId,
1014
+ productId: options.productId
1015
+ })
1016
+ }
1017
+ );
1018
+ if (!response.ok) {
1019
+ return { state: "network-error" };
1020
+ }
1021
+ return await response.json();
1022
+ } catch {
1023
+ return { state: "network-error" };
1024
+ }
1025
+ }
1026
+ function createGatewayStore() {
1027
+ let latestGateway = null;
1028
+ return {
1029
+ record(gateway) {
1030
+ latestGateway = gateway;
1031
+ return gateway;
1032
+ },
1033
+ latest() {
1034
+ return latestGateway;
1035
+ }
1036
+ };
1037
+ }
1038
+
1039
+ // packages/service/src/diagnostics.ts
1040
+ var DEFAULT_MAX_DIAGNOSTICS = 20;
1041
+ var BLOCKED_GATE_REASONS = /* @__PURE__ */ new Set([
1042
+ "logged-out",
1043
+ "mode-inactive",
1044
+ "mode-released",
1045
+ "device-inactive",
1046
+ "invalid-session"
1047
+ ]);
1048
+ var RELEASE_REASONS = /* @__PURE__ */ new Set([
1049
+ "product-missing",
1050
+ "product-unavailable",
1051
+ "insufficient-credits",
1052
+ "invalid-token",
1053
+ "device-inactive"
1054
+ ]);
1055
+ function resolveDiagnosticsFile(diagnosticsFile = DEFAULT_DIAGNOSTICS_FILE) {
1056
+ if (diagnosticsFile.startsWith("~/")) {
1057
+ return join3(homedir3(), diagnosticsFile.slice(2));
1058
+ }
1059
+ return diagnosticsFile;
1060
+ }
1061
+ function sanitizeCanaryForDiagnostics(input) {
1062
+ return {
1063
+ requestId: input.requestId,
1064
+ requestType: "agent",
1065
+ source: "cursor-agent-exec",
1066
+ model: input.model,
1067
+ receivedAt: input.receivedAt,
1068
+ runtimeId: input.runtimeId,
1069
+ gate: sanitizeGateForDiagnostics(input.gate)
1070
+ };
1071
+ }
1072
+ function sanitizeRequestCheckForDiagnostics(input) {
1073
+ return {
1074
+ kind: "agent-request-check",
1075
+ requestId: input.requestId,
1076
+ requestType: "agent",
1077
+ source: input.source === "cursor-agent-exec" ? "cursor-agent-exec" : "manual-check",
1078
+ model: input.model,
1079
+ receivedAt: input.receivedAt,
1080
+ runtimeId: input.runtimeId,
1081
+ decision: sanitizeGateForDiagnostics(input.decision),
1082
+ route: sanitizeRouteForDiagnostics(input.route)
1083
+ };
1084
+ }
1085
+ function sanitizeGatewayForDiagnostics(input) {
1086
+ return {
1087
+ kind: "agent-request-gateway",
1088
+ requestId: input.requestId,
1089
+ requestType: "agent",
1090
+ source: input.source,
1091
+ model: input.model,
1092
+ receivedAt: input.receivedAt,
1093
+ runtimeId: input.runtimeId,
1094
+ decision: sanitizeGatewayDecisionForDiagnostics(input.decision),
1095
+ forward: sanitizeGatewayForward(input.forward)
1096
+ };
1097
+ }
1098
+ function sanitizeGatewayDecisionForDiagnostics(decision) {
1099
+ return sanitizeHistoricalGatewayDecision(decision);
1100
+ }
1101
+ function sanitizeDiagnosticEntry(input) {
1102
+ if (!isRecord(input)) {
1103
+ return null;
1104
+ }
1105
+ if (input.kind === "agent-request-gateway") {
1106
+ const decision = sanitizeHistoricalGatewayDecision(input.decision);
1107
+ const forward = sanitizeGatewayForward(input.forward);
1108
+ if (typeof input.requestId !== "string" || input.requestType !== "agent" || input.source !== "cursor-agent-exec" && input.source !== "manual-check" && input.source !== "unknown" || typeof input.model !== "string" || typeof input.receivedAt !== "string" || typeof input.runtimeId !== "string") {
1109
+ return null;
1110
+ }
1111
+ return {
1112
+ kind: "agent-request-gateway",
1113
+ requestId: input.requestId,
1114
+ requestType: "agent",
1115
+ source: input.source,
1116
+ model: input.model,
1117
+ receivedAt: input.receivedAt,
1118
+ runtimeId: input.runtimeId,
1119
+ decision,
1120
+ forward
1121
+ };
1122
+ }
1123
+ if (input.kind === "agent-request-check") {
1124
+ const decision = sanitizeHistoricalGate(input.decision);
1125
+ const route = sanitizeHistoricalRoute(input.route);
1126
+ if (typeof input.requestId !== "string" || input.requestType !== "agent" || input.source !== "cursor-agent-exec" && input.source !== "manual-check" || typeof input.model !== "string" || typeof input.receivedAt !== "string" || typeof input.runtimeId !== "string" || decision === null) {
1127
+ return null;
1128
+ }
1129
+ return {
1130
+ kind: "agent-request-check",
1131
+ requestId: input.requestId,
1132
+ requestType: "agent",
1133
+ source: input.source,
1134
+ model: input.model,
1135
+ receivedAt: input.receivedAt,
1136
+ runtimeId: input.runtimeId,
1137
+ decision,
1138
+ route
1139
+ };
1140
+ }
1141
+ const gate = sanitizeHistoricalGate(input.gate);
1142
+ if (typeof input.requestId !== "string" || input.requestType !== "agent" || input.source !== "cursor-agent-exec" || typeof input.model !== "string" || typeof input.receivedAt !== "string" || typeof input.runtimeId !== "string" || gate === null) {
1143
+ return null;
1144
+ }
1145
+ return {
1146
+ requestId: input.requestId,
1147
+ requestType: "agent",
1148
+ source: "cursor-agent-exec",
1149
+ model: input.model,
1150
+ receivedAt: input.receivedAt,
1151
+ runtimeId: input.runtimeId,
1152
+ gate
1153
+ };
1154
+ }
1155
+ function sanitizeGateForDiagnostics(gate) {
1156
+ if (gate.state === "allowed") {
1157
+ return {
1158
+ state: "allowed",
1159
+ productId: gate.productId,
1160
+ modeStartedAt: gate.modeStartedAt
1161
+ };
1162
+ }
1163
+ return {
1164
+ state: "blocked",
1165
+ reason: gate.reason,
1166
+ ...gate.releaseReason !== void 0 ? { releaseReason: gate.releaseReason } : {},
1167
+ ...gate.releasedAt !== void 0 ? { releasedAt: gate.releasedAt } : {}
1168
+ };
1169
+ }
1170
+ function sanitizeRouteForDiagnostics(route) {
1171
+ if (route.state === "ready" || route.state === "expired") {
1172
+ return { state: route.state, expiresAt: route.expiresAt };
1173
+ }
1174
+ return { state: "missing" };
1175
+ }
1176
+ function sanitizeHistoricalRoute(input) {
1177
+ if (!isRecord(input)) {
1178
+ return { state: "missing" };
1179
+ }
1180
+ if ((input.state === "ready" || input.state === "expired") && typeof input.expiresAt === "string") {
1181
+ return { state: input.state, expiresAt: input.expiresAt };
1182
+ }
1183
+ return { state: "missing" };
1184
+ }
1185
+ function sanitizeHistoricalGate(input) {
1186
+ if (!isRecord(input)) {
1187
+ return null;
1188
+ }
1189
+ if (input.state === "allowed") {
1190
+ if (typeof input.productId !== "string" || typeof input.modeStartedAt !== "string") {
1191
+ return null;
1192
+ }
1193
+ return {
1194
+ state: "allowed",
1195
+ productId: input.productId,
1196
+ modeStartedAt: input.modeStartedAt
1197
+ };
1198
+ }
1199
+ if (input.state === "blocked") {
1200
+ if (typeof input.reason !== "string" || !BLOCKED_GATE_REASONS.has(input.reason)) {
1201
+ return null;
1202
+ }
1203
+ return {
1204
+ state: "blocked",
1205
+ reason: input.reason,
1206
+ ...typeof input.releaseReason === "string" && RELEASE_REASONS.has(input.releaseReason) ? { releaseReason: input.releaseReason } : {},
1207
+ ...typeof input.releasedAt === "string" ? { releasedAt: input.releasedAt } : {}
1208
+ };
1209
+ }
1210
+ return null;
1211
+ }
1212
+ function sanitizeHistoricalGatewayDecision(input) {
1213
+ if (!isRecord(input)) {
1214
+ return { state: "unknown" };
1215
+ }
1216
+ if (input.state === "accepted") {
1217
+ const route = sanitizeHistoricalRoute(input.route);
1218
+ if (typeof input.productId === "string" && typeof input.modeStartedAt === "string" && route.state === "ready") {
1219
+ return { state: "accepted", productId: input.productId, modeStartedAt: input.modeStartedAt, route };
1220
+ }
1221
+ return { state: "unknown" };
1222
+ }
1223
+ if (input.state === "route-missing") {
1224
+ if (typeof input.productId === "string" && typeof input.modeStartedAt === "string") {
1225
+ return {
1226
+ state: "route-missing",
1227
+ productId: input.productId,
1228
+ modeStartedAt: input.modeStartedAt,
1229
+ route: { state: "missing" }
1230
+ };
1231
+ }
1232
+ return { state: "unknown" };
1233
+ }
1234
+ if (input.state === "route-expired") {
1235
+ const route = sanitizeHistoricalRoute(input.route);
1236
+ if (typeof input.productId === "string" && typeof input.modeStartedAt === "string" && route.state === "expired") {
1237
+ return { state: "route-expired", productId: input.productId, modeStartedAt: input.modeStartedAt, route };
1238
+ }
1239
+ return { state: "unknown" };
1240
+ }
1241
+ if (input.state === "blocked") {
1242
+ const gate = sanitizeHistoricalGate(input);
1243
+ if (gate?.state === "blocked") {
1244
+ return { ...gate, route: { state: "missing" } };
1245
+ }
1246
+ return { state: "unknown" };
1247
+ }
1248
+ return { state: "unknown" };
1249
+ }
1250
+ function isRecord(input) {
1251
+ return typeof input === "object" && input !== null && !Array.isArray(input);
1252
+ }
1253
+ async function readDiagnosticEntries(diagnosticsFile) {
1254
+ try {
1255
+ const content = await readFile3(diagnosticsFile, "utf8");
1256
+ return content.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
1257
+ try {
1258
+ const entry = sanitizeDiagnosticEntry(JSON.parse(line));
1259
+ return entry === null ? [] : [entry];
1260
+ } catch {
1261
+ return [];
1262
+ }
1263
+ });
1264
+ } catch (error) {
1265
+ if (error.code === "ENOENT") {
1266
+ return [];
1267
+ }
1268
+ throw error;
1269
+ }
1270
+ }
1271
+ async function appendAgentCanaryDiagnostic(canary, options = {}) {
1272
+ const diagnosticsFile = resolveDiagnosticsFile(options.diagnosticsFile);
1273
+ const maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_DIAGNOSTICS);
1274
+ const entries = await readDiagnosticEntries(diagnosticsFile);
1275
+ const nextEntries = [...entries, sanitizeCanaryForDiagnostics(canary)].slice(-maxEntries);
1276
+ const content = `${nextEntries.map((entry) => JSON.stringify(entry)).join("\n")}
1277
+ `;
1278
+ await mkdir3(dirname3(diagnosticsFile), { recursive: true });
1279
+ await writeFile3(diagnosticsFile, content, "utf8");
1280
+ }
1281
+ async function appendAgentRequestCheckDiagnostic(check, options = {}) {
1282
+ const diagnosticsFile = resolveDiagnosticsFile(options.diagnosticsFile);
1283
+ const maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_DIAGNOSTICS);
1284
+ const entries = await readDiagnosticEntries(diagnosticsFile);
1285
+ const nextEntries = [...entries, sanitizeRequestCheckForDiagnostics(check)].slice(-maxEntries);
1286
+ const content = `${nextEntries.map((entry) => JSON.stringify(entry)).join("\n")}
1287
+ `;
1288
+ await mkdir3(dirname3(diagnosticsFile), { recursive: true });
1289
+ await writeFile3(diagnosticsFile, content, "utf8");
1290
+ }
1291
+ async function appendAgentGatewayDiagnostic(gateway, options = {}) {
1292
+ const diagnosticsFile = resolveDiagnosticsFile(options.diagnosticsFile);
1293
+ const maxEntries = Math.max(1, options.maxEntries ?? DEFAULT_MAX_DIAGNOSTICS);
1294
+ const entries = await readDiagnosticEntries(diagnosticsFile);
1295
+ const nextEntries = [...entries, sanitizeGatewayForDiagnostics(gateway)].slice(-maxEntries);
1296
+ const content = `${nextEntries.map((entry) => JSON.stringify(entry)).join("\n")}
1297
+ `;
1298
+ await mkdir3(dirname3(diagnosticsFile), { recursive: true });
1299
+ await writeFile3(diagnosticsFile, content, "utf8");
1300
+ }
1301
+
1302
+ // packages/service/src/requestGate.ts
1303
+ var INVALID_SESSION_GATE = { state: "blocked", reason: "invalid-session" };
1304
+ function invalidSessionGate() {
1305
+ return { ...INVALID_SESSION_GATE };
1306
+ }
1307
+ function hasNonEmptyString(value) {
1308
+ return typeof value === "string" && value.length > 0;
1309
+ }
1310
+ async function evaluateRequestGate(options = {}) {
1311
+ const snapshot = await readPlatformSessionSnapshot(options);
1312
+ if (snapshot.state === "missing") {
1313
+ return { state: "blocked", reason: "logged-out" };
1314
+ }
1315
+ if (snapshot.state === "invalid") {
1316
+ return invalidSessionGate();
1317
+ }
1318
+ const { session } = snapshot;
1319
+ if (session.device.status !== "active") {
1320
+ return { state: "blocked", reason: "device-inactive" };
1321
+ }
1322
+ if (session.platformMode !== "active" && session.lastModeReleaseReason && session.lastModeReleasedAt) {
1323
+ return {
1324
+ state: "blocked",
1325
+ reason: "mode-released",
1326
+ releaseReason: session.lastModeReleaseReason,
1327
+ releasedAt: session.lastModeReleasedAt
1328
+ };
1329
+ }
1330
+ if (session.platformMode !== "active" || !hasNonEmptyString(session.activeProductId) || !hasNonEmptyString(session.platformModeStartedAt)) {
1331
+ return { state: "blocked", reason: "mode-inactive" };
1332
+ }
1333
+ return {
1334
+ state: "allowed",
1335
+ productId: session.activeProductId,
1336
+ modeStartedAt: session.platformModeStartedAt
1337
+ };
1338
+ }
1339
+ async function evaluateRouteState(options = {}) {
1340
+ const snapshot = await readPlatformSessionSnapshot(options);
1341
+ if (snapshot.state !== "valid") {
1342
+ return { state: "missing" };
1343
+ }
1344
+ const { routeToken } = snapshot.session;
1345
+ if (!routeToken) {
1346
+ return { state: "missing" };
1347
+ }
1348
+ const expiresAt = Date.parse(routeToken.expiresAt);
1349
+ const now = Date.parse((options.now ?? (() => (/* @__PURE__ */ new Date()).toISOString()))());
1350
+ if (Number.isNaN(expiresAt) || Number.isNaN(now)) {
1351
+ return { state: "missing" };
1352
+ }
1353
+ if (expiresAt <= now) {
1354
+ return { state: "expired", expiresAt: routeToken.expiresAt };
1355
+ }
1356
+ return { state: "ready", expiresAt: routeToken.expiresAt };
1357
+ }
1358
+
1359
+ // packages/service/src/takeover.ts
1360
+ import { randomUUID as randomUUID5 } from "node:crypto";
1361
+ var SAFE_TOKEN_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/;
1362
+ var SAFE_MODEL_PATTERN3 = /^[A-Za-z0-9._:-]{1,96}$/;
1363
+ var SECRET_PATTERN3 = /(api[_-]?key|authorization|bearer|cookie|cursor[_-]?auth|provider[_-]?secret|secret|token|prompt|messages|sk-[A-Za-z0-9])/i;
1364
+ function safeToken(value, fallback) {
1365
+ return typeof value === "string" && SAFE_TOKEN_PATTERN.test(value) && !SECRET_PATTERN3.test(value) ? value : fallback;
1366
+ }
1367
+ function safeSource(value) {
1368
+ return value === "cursor-always-local" || value === "cursor-agent-exec" ? value : "unknown";
1369
+ }
1370
+ function safeModel(value) {
1371
+ return typeof value === "string" && SAFE_MODEL_PATTERN3.test(value) && !SECRET_PATTERN3.test(value) ? value : "unknown";
1372
+ }
1373
+ function buildAgentTakeoverResponse(input, gate, route, forward) {
1374
+ const requestId = safeToken(input.requestId, randomUUID5());
1375
+ if (gate.state === "blocked") {
1376
+ return {
1377
+ state: "rejected",
1378
+ requestId,
1379
+ reason: gate.reason
1380
+ };
1381
+ }
1382
+ if (route.state === "missing") {
1383
+ return { state: "rejected", requestId, reason: "route-missing" };
1384
+ }
1385
+ if (route.state === "expired") {
1386
+ return { state: "rejected", requestId, reason: "route-expired" };
1387
+ }
1388
+ const content = forward?.state === "forwarded" ? forward.content : void 0;
1389
+ return {
1390
+ state: "answered",
1391
+ requestId,
1392
+ source: safeSource(input.source),
1393
+ model: safeModel(input.model),
1394
+ content: content ?? "Cursor Pool provider \u6682\u65F6\u6CA1\u6709\u8FD4\u56DE\u53EF\u663E\u793A\u5185\u5BB9"
1395
+ };
1396
+ }
1397
+ function createAgentTakeoverStore() {
1398
+ let latestTakeover = null;
1399
+ return {
1400
+ record(takeover) {
1401
+ latestTakeover = takeover;
1402
+ return takeover;
1403
+ },
1404
+ latest() {
1405
+ return latestTakeover;
1406
+ }
1407
+ };
1408
+ }
1409
+
1410
+ // packages/shared/src/clientConfig.ts
1411
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
1412
+ import { homedir as homedir4 } from "node:os";
1413
+ import { dirname as dirname4, join as join4 } from "node:path";
1414
+ var DEFAULT_CLIENT_CONFIG_FILE = "~/.cursor-pool/client-config.json";
1415
+ function resolveClientConfigFile(configFile = DEFAULT_CLIENT_CONFIG_FILE) {
1416
+ if (configFile.startsWith("~/")) {
1417
+ return join4(homedir4(), configFile.slice(2));
1418
+ }
1419
+ return configFile;
1420
+ }
1421
+ function normalizeConfig(value) {
1422
+ const record = value;
1423
+ return {
1424
+ ...typeof record?.apiBaseUrl === "string" && record.apiBaseUrl.trim() !== "" ? { apiBaseUrl: record.apiBaseUrl.trim().replace(/\/+$/, "") } : {}
1425
+ };
1426
+ }
1427
+ async function readClientConfig(options = {}) {
1428
+ try {
1429
+ return normalizeConfig(JSON.parse(await readFile4(resolveClientConfigFile(options.configFile), "utf8")));
1430
+ } catch (error) {
1431
+ if (error.code === "ENOENT" || error instanceof SyntaxError) {
1432
+ return {};
1433
+ }
1434
+ throw error;
1435
+ }
1436
+ }
1437
+
1438
+ // packages/service/src/server.ts
1439
+ var LOOPBACK_HOST = "127.0.0.1";
1440
+ var DEFAULT_PORT = 56393;
1441
+ var localServices = /* @__PURE__ */ new Map();
1442
+ async function resolvePlatformApiBaseUrl(options) {
1443
+ if (options.platformApiBaseUrl) {
1444
+ return options.platformApiBaseUrl;
1445
+ }
1446
+ if (process.env.CURSOR_POOL_API_BASE_URL) {
1447
+ return process.env.CURSOR_POOL_API_BASE_URL;
1448
+ }
1449
+ const config = await readClientConfig({ configFile: options.clientConfigFile });
1450
+ return config.apiBaseUrl;
1451
+ }
1452
+ function runtimeKey(runtime) {
1453
+ return `${runtime.host}:${runtime.port}:${runtime.runtimeId}`;
1454
+ }
1455
+ async function readJsonRequest(request) {
1456
+ const chunks = [];
1457
+ for await (const chunk of request) {
1458
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
1459
+ }
1460
+ if (chunks.length === 0) {
1461
+ return {};
1462
+ }
1463
+ const body = Buffer.concat(chunks).toString("utf8");
1464
+ if (!body) {
1465
+ return {};
1466
+ }
1467
+ return JSON.parse(body);
1468
+ }
1469
+ function writeJson(response, statusCode, payload) {
1470
+ const body = JSON.stringify(payload);
1471
+ response.writeHead(statusCode, {
1472
+ "content-type": "application/json",
1473
+ "content-length": Buffer.byteLength(body),
1474
+ "access-control-allow-origin": "*",
1475
+ "access-control-allow-methods": "GET,POST,OPTIONS",
1476
+ "access-control-allow-headers": "content-type"
1477
+ });
1478
+ response.end(body);
1479
+ }
1480
+ function writeCorsPreflight(response) {
1481
+ response.writeHead(204, {
1482
+ "access-control-allow-origin": "*",
1483
+ "access-control-allow-methods": "GET,POST,OPTIONS",
1484
+ "access-control-allow-headers": "content-type",
1485
+ "access-control-max-age": "600"
1486
+ });
1487
+ response.end();
1488
+ }
1489
+ function writeEventStream(response, events) {
1490
+ const body = `${events.map((event) => `data: ${JSON.stringify(event)}
1491
+
1492
+ `).join("")}data: [DONE]
1493
+
1494
+ `;
1495
+ response.writeHead(200, {
1496
+ "content-type": "text/event-stream; charset=utf-8",
1497
+ "cache-control": "no-cache",
1498
+ "connection": "keep-alive",
1499
+ "content-length": Buffer.byteLength(body),
1500
+ "access-control-allow-origin": "*",
1501
+ "access-control-allow-methods": "GET,POST,OPTIONS",
1502
+ "access-control-allow-headers": "content-type,authorization"
1503
+ });
1504
+ response.end(body);
1505
+ }
1506
+ function asRecord(value) {
1507
+ return typeof value === "object" && value !== null ? value : {};
1508
+ }
1509
+ function safeOpenAiModel(value) {
1510
+ return typeof value === "string" && /^[A-Za-z0-9._:-]{1,96}$/.test(value) ? value : "unknown";
1511
+ }
1512
+ function openAiModelsResponse() {
1513
+ return {
1514
+ object: "list",
1515
+ data: [
1516
+ {
1517
+ id: "gpt-test",
1518
+ object: "model",
1519
+ created: 0,
1520
+ owned_by: "cursor-pool",
1521
+ api_types: ["chat_completions"],
1522
+ capabilities: {
1523
+ supports_tool_use: true,
1524
+ supports_streaming: true,
1525
+ output_modalities: ["text"],
1526
+ context_length: 128e3,
1527
+ max_output_tokens: 8192,
1528
+ supports_reasoning: false,
1529
+ supports_vision: false
1530
+ }
1531
+ }
1532
+ ]
1533
+ };
1534
+ }
1535
+ async function resolveOpenAiGatewayForward(body, runtime, options, retryAfterRefresh = true) {
1536
+ let gate;
1537
+ try {
1538
+ gate = await evaluateRequestGate({ sessionFile: options.platformSessionFile });
1539
+ } catch {
1540
+ gate = { ...INVALID_SESSION_GATE };
1541
+ }
1542
+ const route = gate.state === "allowed" ? await evaluateRouteState({ sessionFile: options.platformSessionFile }) : { state: "missing" };
1543
+ const decision = evaluateGatewayDecision(gate, route);
1544
+ const requestId = typeof body.requestId === "string" ? body.requestId : `openai-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
1545
+ const model = safeOpenAiModel(body.model);
1546
+ const gateway = sanitizeAgentGateway({
1547
+ requestId,
1548
+ source: "cursor-agent-exec",
1549
+ model
1550
+ }, {
1551
+ runtimeId: runtime.runtimeId,
1552
+ decision
1553
+ });
1554
+ const forwardContext = decision.state === "accepted" ? await readPlatformGatewayForwardContext({ sessionFile: options.platformSessionFile }) : {};
1555
+ const forwarder = options.gatewayForwarder ?? (decision.state === "accepted" ? createGatewayHttpForwarder({
1556
+ apiBaseUrl: forwardContext.apiBaseUrl,
1557
+ deviceToken: forwardContext.deviceToken
1558
+ }) : void 0);
1559
+ const { result, safe } = await resolveGatewayForwardResult({
1560
+ gateway,
1561
+ routeToken: forwardContext.routeToken ?? "",
1562
+ poolSessionId: forwardContext.poolSessionId,
1563
+ productId: decision.state === "accepted" ? decision.productId : "unknown",
1564
+ model,
1565
+ requestId: gateway.requestId,
1566
+ settlementMode: "client_response",
1567
+ openAiRequest: body,
1568
+ forwarder
1569
+ });
1570
+ if (retryAfterRefresh && (decision.state === "route-expired" || safe.state === "skipped" && safe.reason === "not-accepted" || safe.state === "rejected" && safe.reason === "pool-session-expired")) {
1571
+ const refreshed = await refreshPlatformRoute({
1572
+ sessionFile: options.platformSessionFile,
1573
+ startPoolSession: options.platformStartPoolSession
1574
+ });
1575
+ if (refreshed.state === "active") {
1576
+ return resolveOpenAiGatewayForward(body, runtime, options, false);
1577
+ }
1578
+ }
1579
+ return {
1580
+ gateway: {
1581
+ ...gateway,
1582
+ forward: safe
1583
+ },
1584
+ forward: safe,
1585
+ forwardResult: result,
1586
+ completionContext: {
1587
+ apiBaseUrl: forwardContext.apiBaseUrl,
1588
+ deviceToken: forwardContext.deviceToken,
1589
+ routeToken: forwardContext.routeToken,
1590
+ poolSessionId: forwardContext.poolSessionId,
1591
+ productId: decision.state === "accepted" ? decision.productId : void 0,
1592
+ requestId: gateway.requestId
1593
+ },
1594
+ model
1595
+ };
1596
+ }
1597
+ function openAiChatCompletionResponse(model, content) {
1598
+ const now = Math.floor(Date.now() / 1e3);
1599
+ return {
1600
+ id: `chatcmpl-${now}`,
1601
+ object: "chat.completion",
1602
+ created: now,
1603
+ model,
1604
+ choices: [
1605
+ {
1606
+ index: 0,
1607
+ message: {
1608
+ role: "assistant",
1609
+ content
1610
+ },
1611
+ finish_reason: "stop"
1612
+ }
1613
+ ],
1614
+ usage: {
1615
+ prompt_tokens: 0,
1616
+ completion_tokens: 0,
1617
+ total_tokens: 0
1618
+ }
1619
+ };
1620
+ }
1621
+ function openAiChatCompletionStreamEvents(model, content) {
1622
+ const now = Math.floor(Date.now() / 1e3);
1623
+ const id = `chatcmpl-${now}`;
1624
+ return [
1625
+ {
1626
+ id,
1627
+ object: "chat.completion.chunk",
1628
+ created: now,
1629
+ model,
1630
+ choices: [
1631
+ {
1632
+ index: 0,
1633
+ delta: {
1634
+ role: "assistant"
1635
+ },
1636
+ finish_reason: null
1637
+ }
1638
+ ]
1639
+ },
1640
+ {
1641
+ id,
1642
+ object: "chat.completion.chunk",
1643
+ created: now,
1644
+ model,
1645
+ choices: [
1646
+ {
1647
+ index: 0,
1648
+ delta: {
1649
+ content
1650
+ },
1651
+ finish_reason: null
1652
+ }
1653
+ ]
1654
+ },
1655
+ {
1656
+ id,
1657
+ object: "chat.completion.chunk",
1658
+ created: now,
1659
+ model,
1660
+ choices: [
1661
+ {
1662
+ index: 0,
1663
+ delta: {},
1664
+ finish_reason: "stop"
1665
+ }
1666
+ ]
1667
+ }
1668
+ ];
1669
+ }
1670
+ function openAiBypassResponse() {
1671
+ return {
1672
+ error: {
1673
+ message: "Cursor Pool platform mode is inactive; use the official Cursor path.",
1674
+ type: "cursor_pool_bypass",
1675
+ code: "cursor_pool_bypass"
1676
+ }
1677
+ };
1678
+ }
1679
+ function openAiFailClosedContent(forward) {
1680
+ if (forward.state === "rejected") {
1681
+ return `Cursor Pool \u53F7\u6C60 provider \u672A\u5C31\u7EEA\uFF1A${forward.reason}`;
1682
+ }
1683
+ if (forward.state === "not-configured") {
1684
+ return "Cursor Pool \u53F7\u6C60 provider \u672A\u914D\u7F6E\uFF0C\u5F53\u524D\u8BF7\u6C42\u5DF2\u88AB\u5E73\u53F0\u63A5\u7BA1\u4F46\u65E0\u6CD5\u8F6C\u53D1\u3002";
1685
+ }
1686
+ if (forward.state === "timeout") {
1687
+ return "Cursor Pool \u53F7\u6C60 provider \u54CD\u5E94\u8D85\u65F6\uFF0C\u5F53\u524D\u8BF7\u6C42\u5DF2\u88AB\u5E73\u53F0\u63A5\u7BA1\u4F46\u6CA1\u6709\u5B8C\u6210\u3002";
1688
+ }
1689
+ if (forward.state === "network-error") {
1690
+ return "Cursor Pool \u53F7\u6C60 provider \u7F51\u7EDC\u4E0D\u53EF\u7528\uFF0C\u5F53\u524D\u8BF7\u6C42\u5DF2\u88AB\u5E73\u53F0\u63A5\u7BA1\u4F46\u65E0\u6CD5\u8F6C\u53D1\u3002";
1691
+ }
1692
+ if (forward.state === "skipped") {
1693
+ return "Cursor Pool \u5E73\u53F0\u6A21\u5F0F\u672A\u5141\u8BB8\u672C\u6B21\u8BF7\u6C42\u3002";
1694
+ }
1695
+ return "Cursor Pool provider \u6682\u65F6\u6CA1\u6709\u8FD4\u56DE\u53EF\u663E\u793A\u5185\u5BB9";
1696
+ }
1697
+ function isOpenAiObject(value) {
1698
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1699
+ }
1700
+ function forwardedOpenAiResponse(result) {
1701
+ return result.state === "forwarded" && isOpenAiObject(result.openAiResponse) ? result.openAiResponse : void 0;
1702
+ }
1703
+ function forwardedOpenAiStreamEvents(result) {
1704
+ if (result.state !== "forwarded" || !Array.isArray(result.openAiStreamEvents)) {
1705
+ return void 0;
1706
+ }
1707
+ const events = result.openAiStreamEvents.filter(isOpenAiObject);
1708
+ return events.length > 0 ? events : void 0;
1709
+ }
1710
+ async function completeForwardAfterClientResponse(context) {
1711
+ if (!context.routeToken || !context.poolSessionId || !context.productId) {
1712
+ return;
1713
+ }
1714
+ await completeGatewayForward({
1715
+ apiBaseUrl: context.apiBaseUrl,
1716
+ deviceToken: context.deviceToken,
1717
+ routeToken: context.routeToken,
1718
+ poolSessionId: context.poolSessionId,
1719
+ productId: context.productId,
1720
+ requestId: context.requestId
1721
+ });
1722
+ }
1723
+ async function routeRequest(request, response, runtime, options, canaryStore, requestCheckStore, gatewayStore, takeoverStore, stop) {
1724
+ try {
1725
+ if (request.method === "OPTIONS") {
1726
+ writeCorsPreflight(response);
1727
+ return;
1728
+ }
1729
+ if (request.method === "GET" && request.url === "/health") {
1730
+ writeJson(response, 200, buildHealth(runtime));
1731
+ return;
1732
+ }
1733
+ if (request.method === "GET" && request.url === "/models") {
1734
+ writeJson(response, 200, openAiModelsResponse());
1735
+ return;
1736
+ }
1737
+ if (request.method === "POST" && request.url === "/chat/completions") {
1738
+ let clientClosed = false;
1739
+ let responseFinished = false;
1740
+ response.once("finish", () => {
1741
+ responseFinished = true;
1742
+ });
1743
+ response.once("close", () => {
1744
+ if (!responseFinished) {
1745
+ clientClosed = true;
1746
+ }
1747
+ });
1748
+ const clientConnected = () => !clientClosed && !response.destroyed && !response.writableEnded;
1749
+ const body = await readJsonRequest(request);
1750
+ const { gateway, forward, forwardResult, completionContext, model } = await resolveOpenAiGatewayForward(body, runtime, options);
1751
+ const recordedGateway = gatewayStore.record(gateway);
1752
+ try {
1753
+ await appendAgentGatewayDiagnostic(recordedGateway, {
1754
+ diagnosticsFile: options.diagnosticsFile,
1755
+ maxEntries: options.maxDiagnostics
1756
+ });
1757
+ } catch {
1758
+ }
1759
+ if (!clientConnected()) {
1760
+ return;
1761
+ }
1762
+ if (gateway.decision.state !== "accepted") {
1763
+ writeJson(response, 409, openAiBypassResponse());
1764
+ return;
1765
+ }
1766
+ if (forward.state !== "forwarded") {
1767
+ const content = openAiFailClosedContent(forward);
1768
+ if (body.stream === true) {
1769
+ writeEventStream(response, openAiChatCompletionStreamEvents(model, content));
1770
+ return;
1771
+ }
1772
+ writeJson(response, 200, openAiChatCompletionResponse(model, content));
1773
+ return;
1774
+ }
1775
+ if (body.stream === true) {
1776
+ const providerEvents = forwardedOpenAiStreamEvents(forwardResult);
1777
+ if (providerEvents) {
1778
+ writeEventStream(response, providerEvents);
1779
+ await completeForwardAfterClientResponse(completionContext);
1780
+ return;
1781
+ }
1782
+ writeEventStream(
1783
+ response,
1784
+ openAiChatCompletionStreamEvents(
1785
+ model,
1786
+ forward.content ?? "Cursor Pool provider \u6682\u65F6\u6CA1\u6709\u8FD4\u56DE\u53EF\u663E\u793A\u5185\u5BB9"
1787
+ )
1788
+ );
1789
+ await completeForwardAfterClientResponse(completionContext);
1790
+ return;
1791
+ }
1792
+ const providerResponse = forwardedOpenAiResponse(forwardResult);
1793
+ if (providerResponse) {
1794
+ writeJson(response, 200, providerResponse);
1795
+ await completeForwardAfterClientResponse(completionContext);
1796
+ return;
1797
+ }
1798
+ writeJson(
1799
+ response,
1800
+ 200,
1801
+ openAiChatCompletionResponse(
1802
+ model,
1803
+ forward.content ?? "Cursor Pool provider \u6682\u65F6\u6CA1\u6709\u8FD4\u56DE\u53EF\u663E\u793A\u5185\u5BB9"
1804
+ )
1805
+ );
1806
+ await completeForwardAfterClientResponse(completionContext);
1807
+ return;
1808
+ }
1809
+ if (request.method === "POST" && request.url === "/cursor-metadata") {
1810
+ const metadata = sanitizeServiceMetadata(await readJsonRequest(request));
1811
+ await options.onMetadata?.(metadata);
1812
+ writeJson(response, 200, { ok: true, metadata });
1813
+ return;
1814
+ }
1815
+ if (request.method === "POST" && request.url === "/agent/canary") {
1816
+ let gate;
1817
+ try {
1818
+ gate = await evaluateRequestGate({ sessionFile: options.platformSessionFile });
1819
+ } catch {
1820
+ gate = { ...INVALID_SESSION_GATE };
1821
+ }
1822
+ const canary = canaryStore.record(
1823
+ sanitizeAgentCanary(await readJsonRequest(request), { runtimeId: runtime.runtimeId, gate })
1824
+ );
1825
+ try {
1826
+ await appendAgentCanaryDiagnostic(canary, {
1827
+ diagnosticsFile: options.diagnosticsFile,
1828
+ maxEntries: options.maxDiagnostics
1829
+ });
1830
+ } catch {
1831
+ }
1832
+ writeJson(response, 200, { ok: true, canary });
1833
+ return;
1834
+ }
1835
+ if (request.method === "GET" && request.url === "/agent/canary/latest") {
1836
+ writeJson(response, 200, { ok: true, canary: canaryStore.latest() });
1837
+ return;
1838
+ }
1839
+ if (request.method === "POST" && request.url === "/agent/request-check") {
1840
+ let decision;
1841
+ try {
1842
+ decision = await evaluateRequestGate({ sessionFile: options.platformSessionFile });
1843
+ } catch {
1844
+ decision = { ...INVALID_SESSION_GATE };
1845
+ }
1846
+ const route = decision.state === "allowed" ? await evaluateRouteState({ sessionFile: options.platformSessionFile }) : { state: "missing" };
1847
+ const check = requestCheckStore.record(
1848
+ sanitizeAgentRequestCheck(await readJsonRequest(request), {
1849
+ runtimeId: runtime.runtimeId,
1850
+ decision,
1851
+ route
1852
+ })
1853
+ );
1854
+ try {
1855
+ await appendAgentRequestCheckDiagnostic(check, {
1856
+ diagnosticsFile: options.diagnosticsFile,
1857
+ maxEntries: options.maxDiagnostics
1858
+ });
1859
+ } catch {
1860
+ }
1861
+ writeJson(response, 200, { ok: decision.state === "allowed", check });
1862
+ return;
1863
+ }
1864
+ if (request.method === "GET" && request.url === "/agent/request-check/latest") {
1865
+ writeJson(response, 200, { ok: true, check: requestCheckStore.latest() });
1866
+ return;
1867
+ }
1868
+ if (request.method === "POST" && request.url === "/agent/request-gateway") {
1869
+ let gate;
1870
+ try {
1871
+ gate = await evaluateRequestGate({ sessionFile: options.platformSessionFile });
1872
+ } catch {
1873
+ gate = { ...INVALID_SESSION_GATE };
1874
+ }
1875
+ const route = gate.state === "allowed" ? await evaluateRouteState({ sessionFile: options.platformSessionFile }) : { state: "missing" };
1876
+ const decision = evaluateGatewayDecision(gate, route);
1877
+ const body = await readJsonRequest(request);
1878
+ const gateway = sanitizeAgentGateway(body, {
1879
+ runtimeId: runtime.runtimeId,
1880
+ decision
1881
+ });
1882
+ const forwardContext = decision.state === "accepted" ? await readPlatformGatewayForwardContext({ sessionFile: options.platformSessionFile }) : {};
1883
+ const forwarder = options.gatewayForwarder ?? (decision.state === "accepted" ? createGatewayHttpForwarder({
1884
+ apiBaseUrl: forwardContext.apiBaseUrl,
1885
+ deviceToken: forwardContext.deviceToken
1886
+ }) : void 0);
1887
+ gateway.forward = await resolveGatewayForward({
1888
+ gateway,
1889
+ routeToken: forwardContext.routeToken ?? "",
1890
+ poolSessionId: forwardContext.poolSessionId,
1891
+ productId: decision.state === "accepted" ? decision.productId : "unknown",
1892
+ model: gateway.model,
1893
+ requestId: gateway.requestId,
1894
+ forwarder
1895
+ });
1896
+ const recordedGateway = gatewayStore.record(gateway);
1897
+ try {
1898
+ await appendAgentGatewayDiagnostic(recordedGateway, {
1899
+ diagnosticsFile: options.diagnosticsFile,
1900
+ maxEntries: options.maxDiagnostics
1901
+ });
1902
+ } catch {
1903
+ }
1904
+ writeJson(response, 200, {
1905
+ ok: decision.state === "accepted" && recordedGateway.forward.state === "forwarded",
1906
+ gateway: recordedGateway
1907
+ });
1908
+ return;
1909
+ }
1910
+ if (request.method === "GET" && request.url === "/agent/request-gateway/latest") {
1911
+ writeJson(response, 200, { ok: true, gateway: gatewayStore.latest() });
1912
+ return;
1913
+ }
1914
+ if (request.method === "POST" && request.url === "/agent/takeover") {
1915
+ const body = await readJsonRequest(request);
1916
+ let gate;
1917
+ try {
1918
+ gate = await evaluateRequestGate({ sessionFile: options.platformSessionFile });
1919
+ } catch {
1920
+ gate = { ...INVALID_SESSION_GATE };
1921
+ }
1922
+ const route = gate.state === "allowed" ? await evaluateRouteState({ sessionFile: options.platformSessionFile }) : { state: "missing" };
1923
+ let forward;
1924
+ if (route.state === "ready") {
1925
+ const decision = evaluateGatewayDecision(gate, route);
1926
+ const gateway = sanitizeAgentGateway(body, {
1927
+ runtimeId: runtime.runtimeId,
1928
+ decision
1929
+ });
1930
+ const forwardContext = await readPlatformGatewayForwardContext({ sessionFile: options.platformSessionFile });
1931
+ const forwarder = options.gatewayForwarder ?? createGatewayHttpForwarder({
1932
+ apiBaseUrl: forwardContext.apiBaseUrl,
1933
+ deviceToken: forwardContext.deviceToken
1934
+ });
1935
+ forward = await resolveGatewayForward({
1936
+ gateway,
1937
+ routeToken: forwardContext.routeToken ?? "",
1938
+ poolSessionId: forwardContext.poolSessionId,
1939
+ productId: decision.state === "accepted" ? decision.productId : "unknown",
1940
+ model: gateway.model,
1941
+ requestId: gateway.requestId,
1942
+ forwarder
1943
+ });
1944
+ }
1945
+ const takeover = takeoverStore.record(
1946
+ buildAgentTakeoverResponse(body, gate, route, forward)
1947
+ );
1948
+ writeJson(response, 200, takeover);
1949
+ return;
1950
+ }
1951
+ if (request.method === "GET" && request.url === "/agent/takeover/latest") {
1952
+ writeJson(response, 200, { ok: true, takeover: takeoverStore.latest() });
1953
+ return;
1954
+ }
1955
+ if (request.method === "POST" && request.url === "/extension/status") {
1956
+ const status = sanitizeExtensionStatus(await readJsonRequest(request));
1957
+ writeJson(response, 200, { ok: true, status });
1958
+ return;
1959
+ }
1960
+ if (request.method === "GET" && request.url === "/platform/status") {
1961
+ writeJson(response, 200, await platformStatus({ sessionFile: options.platformSessionFile }));
1962
+ return;
1963
+ }
1964
+ if (request.method === "GET" && request.url === "/platform/catalog") {
1965
+ writeJson(response, 200, await platformCatalog({ sessionFile: options.platformSessionFile }));
1966
+ return;
1967
+ }
1968
+ if (request.method === "POST" && request.url === "/platform/selection") {
1969
+ const body = await readJsonRequest(request);
1970
+ if (typeof body.productId !== "string" || body.productId.trim() === "") {
1971
+ throw new Error("invalid platform selection request");
1972
+ }
1973
+ const selection = await selectPlatformProduct({
1974
+ sessionFile: options.platformSessionFile,
1975
+ productId: body.productId
1976
+ });
1977
+ writeJson(response, 200, selection);
1978
+ return;
1979
+ }
1980
+ if (request.method === "POST" && request.url === "/platform/login") {
1981
+ const body = await readJsonRequest(request);
1982
+ const apiBaseUrl = await resolvePlatformApiBaseUrl(options);
1983
+ if (typeof body.email === "string" || typeof body.password === "string") {
1984
+ if (typeof body.email !== "string" || typeof body.password !== "string" || typeof apiBaseUrl !== "string" || apiBaseUrl.trim() === "") {
1985
+ throw new Error("invalid platform login request");
1986
+ }
1987
+ let login;
1988
+ try {
1989
+ login = await loginWithPassword({
1990
+ email: body.email,
1991
+ password: body.password,
1992
+ apiBaseUrl,
1993
+ sessionFile: options.platformSessionFile,
1994
+ device: asRecord(body.device),
1995
+ exchangePasswordDeviceToken: options.platformExchangePasswordDeviceToken
1996
+ });
1997
+ } catch (error) {
1998
+ const platformCode = error.platformCode;
1999
+ writeJson(response, 400, {
2000
+ ok: false,
2001
+ error: "platform login failed",
2002
+ ...typeof platformCode === "string" ? { code: platformCode } : {}
2003
+ });
2004
+ return;
2005
+ }
2006
+ writeJson(response, 200, login);
2007
+ return;
2008
+ }
2009
+ if (typeof body.code !== "string" || typeof body.apiBaseUrl !== "string") {
2010
+ throw new Error("invalid platform login request");
2011
+ }
2012
+ writeJson(response, 200, await loginWithCode({
2013
+ code: body.code,
2014
+ apiBaseUrl: body.apiBaseUrl,
2015
+ sessionFile: options.platformSessionFile,
2016
+ device: asRecord(body.device),
2017
+ exchangeDeviceToken: options.platformExchangeDeviceToken
2018
+ }));
2019
+ return;
2020
+ }
2021
+ if (request.method === "POST" && request.url === "/platform/heartbeat") {
2022
+ writeJson(response, 200, await sendHeartbeat({
2023
+ sessionFile: options.platformSessionFile,
2024
+ payload: await readJsonRequest(request),
2025
+ postHeartbeat: options.platformPostHeartbeat
2026
+ }));
2027
+ return;
2028
+ }
2029
+ if (request.method === "POST" && request.url === "/platform/logout") {
2030
+ writeJson(response, 200, await logoutPlatform({
2031
+ sessionFile: options.platformSessionFile,
2032
+ postLogout: options.platformPostLogout
2033
+ }));
2034
+ return;
2035
+ }
2036
+ if (request.method === "POST" && request.url === "/mode/start") {
2037
+ writeJson(response, 200, await startPlatformMode({
2038
+ sessionFile: options.platformSessionFile,
2039
+ startPoolSession: options.platformStartPoolSession
2040
+ }));
2041
+ return;
2042
+ }
2043
+ if (request.method === "POST" && request.url === "/mode/stop") {
2044
+ writeJson(response, 200, await stopPlatformMode({
2045
+ sessionFile: options.platformSessionFile,
2046
+ stopPoolSession: options.platformStopPoolSession
2047
+ }));
2048
+ return;
2049
+ }
2050
+ if (request.method === "POST" && request.url === "/shutdown") {
2051
+ writeJson(response, 200, { ok: true });
2052
+ response.once("finish", () => {
2053
+ void stop();
2054
+ });
2055
+ return;
2056
+ }
2057
+ writeJson(response, 404, { ok: false, error: "not found" });
2058
+ } catch {
2059
+ writeJson(response, 400, { ok: false, error: "invalid request" });
2060
+ }
2061
+ }
2062
+ function listen(server, port) {
2063
+ return new Promise((resolve, reject) => {
2064
+ const onError = (error) => {
2065
+ server.off("listening", onListening);
2066
+ reject(error);
2067
+ };
2068
+ const onListening = () => {
2069
+ server.off("error", onError);
2070
+ resolve();
2071
+ };
2072
+ server.once("error", onError);
2073
+ server.once("listening", onListening);
2074
+ server.listen(port, LOOPBACK_HOST);
2075
+ });
2076
+ }
2077
+ async function closeServer(server) {
2078
+ if (!server.listening) {
2079
+ return;
2080
+ }
2081
+ await new Promise((resolve, reject) => {
2082
+ server.close((error) => error ? reject(error) : resolve());
2083
+ });
2084
+ }
2085
+ async function startServer(options = {}) {
2086
+ let requestedPort = options.port ?? DEFAULT_PORT;
2087
+ for (; ; ) {
2088
+ const runtime = {
2089
+ host: LOOPBACK_HOST,
2090
+ port: requestedPort,
2091
+ runtimeId: createRuntimeId()
2092
+ };
2093
+ const canaryStore = createCanaryStore();
2094
+ const requestCheckStore = createRequestCheckStore();
2095
+ const gatewayStore = createGatewayStore();
2096
+ const takeoverStore = createAgentTakeoverStore();
2097
+ const server = createServer((request, response) => {
2098
+ void routeRequest(
2099
+ request,
2100
+ response,
2101
+ runtime,
2102
+ options,
2103
+ canaryStore,
2104
+ requestCheckStore,
2105
+ gatewayStore,
2106
+ takeoverStore,
2107
+ stopService
2108
+ );
2109
+ });
2110
+ const stopService = async () => {
2111
+ await closeServer(server);
2112
+ localServices.delete(runtimeKey(runtime));
2113
+ };
2114
+ try {
2115
+ await listen(server, requestedPort);
2116
+ const address = server.address();
2117
+ if (typeof address !== "object" || address === null) {
2118
+ await closeServer(server);
2119
+ throw new Error("service did not bind a TCP address");
2120
+ }
2121
+ runtime.port = address.port;
2122
+ await writeRuntimeInfo(runtime, { runtimeFile: options.runtimeFile });
2123
+ const service = {
2124
+ ...runtime,
2125
+ server,
2126
+ stop: stopService
2127
+ };
2128
+ localServices.set(runtimeKey(runtime), service);
2129
+ return service;
2130
+ } catch (error) {
2131
+ await closeServer(server);
2132
+ if (error.code !== "EADDRINUSE") {
2133
+ throw error;
2134
+ }
2135
+ requestedPort = 0;
2136
+ }
2137
+ }
2138
+ }
2139
+
2140
+ // packages/extension/src/runtime.ts
2141
+ async function readRuntimeInfo2(options = {}) {
2142
+ return readRuntimeInfo({ runtimeFile: options.runtimeFile });
2143
+ }
2144
+ async function writeRuntimeInfo2(runtime, options = {}) {
2145
+ await writeRuntimeInfo(runtime, { runtimeFile: options.runtimeFile });
2146
+ }
2147
+
2148
+ // packages/extension/src/api.ts
2149
+ var DEFAULT_SERVICE_PORT = 56393;
2150
+ var localExtensionServices = /* @__PURE__ */ new Map();
2151
+ function buildExtensionDeviceInfo() {
2152
+ return {
2153
+ name: hostname(),
2154
+ os: osPlatform(),
2155
+ arch: arch(),
2156
+ extensionVersion: "0.5.6"
2157
+ };
2158
+ }
2159
+ function serviceUrl(runtime, path) {
2160
+ return `http://${runtime.host}:${runtime.port}${path}`;
2161
+ }
2162
+ async function probeRuntime(runtime) {
2163
+ try {
2164
+ const response = await fetch(serviceUrl(runtime, "/health"));
2165
+ const health = await response.json();
2166
+ if (!response.ok || health.ok !== true || typeof health.runtimeId !== "string") {
2167
+ return null;
2168
+ }
2169
+ if (runtime.runtimeId && health.runtimeId !== runtime.runtimeId) {
2170
+ return null;
2171
+ }
2172
+ return { ...runtime, runtimeId: health.runtimeId };
2173
+ } catch {
2174
+ return null;
2175
+ }
2176
+ }
2177
+ async function resolveHealthyRuntime(options = {}) {
2178
+ const runtime = await readRuntimeInfo2({ runtimeFile: options.runtimeFile });
2179
+ const allowFallback = options.runtimeFile === void 0 || options.fallbackPort !== void 0;
2180
+ if (runtime) {
2181
+ const healthyRuntime = await probeRuntime(runtime);
2182
+ if (healthyRuntime) {
2183
+ return healthyRuntime;
2184
+ }
2185
+ }
2186
+ if (!allowFallback) {
2187
+ return null;
2188
+ }
2189
+ const fallbackRuntime = {
2190
+ host: "127.0.0.1",
2191
+ port: options.fallbackPort ?? DEFAULT_SERVICE_PORT,
2192
+ runtimeId: ""
2193
+ };
2194
+ const healthyFallback = await probeRuntime(fallbackRuntime);
2195
+ if (!healthyFallback) {
2196
+ return null;
2197
+ }
2198
+ await writeRuntimeInfo2(healthyFallback, { runtimeFile: options.runtimeFile });
2199
+ return healthyFallback;
2200
+ }
2201
+ async function ensureLocalService(runtimeFile, options = {}) {
2202
+ const healthy = await resolveHealthyRuntime({ runtimeFile });
2203
+ if (healthy) {
2204
+ return healthy;
2205
+ }
2206
+ const key = runtimeFile ?? "~/.cursor-pool/runtime.json";
2207
+ const existing = localExtensionServices.get(key);
2208
+ if (existing) {
2209
+ return { host: existing.host, port: existing.port, runtimeId: existing.runtimeId };
2210
+ }
2211
+ const clientConfig = await readClientConfig();
2212
+ const service = await startServer({
2213
+ runtimeFile,
2214
+ platformApiBaseUrl: options.platformApiBaseUrl ?? process.env.CURSOR_POOL_API_BASE_URL ?? clientConfig.apiBaseUrl
2215
+ });
2216
+ localExtensionServices.set(key, service);
2217
+ return { host: service.host, port: service.port, runtimeId: service.runtimeId };
2218
+ }
2219
+ async function resolveRuntimeForRequest(options = {}) {
2220
+ const runtime = await readRuntimeInfo2({ runtimeFile: options.runtimeFile });
2221
+ const allowFallback = options.runtimeFile === void 0 || options.fallbackPort !== void 0;
2222
+ if (runtime && !allowFallback) {
2223
+ return runtime;
2224
+ }
2225
+ return resolveHealthyRuntime(options);
2226
+ }
2227
+ function isUserSummary2(value) {
2228
+ const user = value;
2229
+ return typeof user?.id === "string" && typeof user.email === "string";
2230
+ }
2231
+ function isDeviceSummary2(value) {
2232
+ const device = value;
2233
+ return typeof device?.id === "string" && typeof device.status === "string" && typeof device.lastHeartbeatAt === "string";
2234
+ }
2235
+ function isProductSummary2(value) {
2236
+ const product = value;
2237
+ return typeof product?.id === "string" && typeof product.name === "string" && typeof product.description === "string" && (product.status === "available" || product.status === "unavailable") && Number.isInteger(product.minCredits) && product.minCredits >= 0 && typeof product.usageLabel === "string";
2238
+ }
2239
+ function isPlatformModeReleaseReason2(value) {
2240
+ return value === "product-missing" || value === "product-unavailable" || value === "insufficient-credits" || value === "invalid-token" || value === "device-inactive";
2241
+ }
2242
+ function normalizePlatformMode(value) {
2243
+ const mode = value;
2244
+ if (mode?.state === "inactive") {
2245
+ return {
2246
+ state: "inactive",
2247
+ ...isPlatformModeReleaseReason2(mode.releaseReason) && typeof mode.releasedAt === "string" ? { releaseReason: mode.releaseReason, releasedAt: mode.releasedAt } : {}
2248
+ };
2249
+ }
2250
+ if (mode?.state === "active" && typeof mode.productId === "string" && typeof mode.startedAt === "string") {
2251
+ return { state: "active", productId: mode.productId, startedAt: mode.startedAt };
2252
+ }
2253
+ return null;
2254
+ }
2255
+ function normalizePlatformModeResult(value) {
2256
+ const mode = normalizePlatformMode(value);
2257
+ if (mode) {
2258
+ return mode;
2259
+ }
2260
+ const result = value;
2261
+ if (result?.state === "logged-out" || result?.state === "offline" || result?.state === "invalid-token" || result?.state === "product-not-selected") {
2262
+ return { state: result.state };
2263
+ }
2264
+ if ((result?.state === "product-not-found" || result?.state === "product-unavailable") && typeof result.productId === "string") {
2265
+ return { state: result.state, productId: result.productId };
2266
+ }
2267
+ if (result?.state === "insufficient-credits" && typeof result.productId === "string" && Number.isFinite(result.requiredCredits) && Number.isFinite(result.currentCredits)) {
2268
+ return {
2269
+ state: "insufficient-credits",
2270
+ productId: result.productId,
2271
+ requiredCredits: result.requiredCredits,
2272
+ currentCredits: result.currentCredits
2273
+ };
2274
+ }
2275
+ return { state: "offline" };
2276
+ }
2277
+ function isPlatformStatus(value) {
2278
+ const status = value;
2279
+ if (status?.state === "logged-out") {
2280
+ return true;
2281
+ }
2282
+ if (status?.state === "logged-in") {
2283
+ return isUserSummary2(status.user) && isDeviceSummary2(status.device) && (status.mode === void 0 || normalizePlatformMode(status.mode) !== null);
2284
+ }
2285
+ if (status?.state === "offline" || status?.state === "invalid-token") {
2286
+ return (status.user === void 0 || isUserSummary2(status.user)) && (status.device === void 0 || isDeviceSummary2(status.device)) && (status.mode === void 0 || normalizePlatformMode(status.mode) !== null);
2287
+ }
2288
+ return false;
2289
+ }
2290
+ function normalizePlatformCatalog(value) {
2291
+ const catalog = value;
2292
+ if (catalog?.state === "logged-out") {
2293
+ return { state: "logged-out", products: [] };
2294
+ }
2295
+ if (catalog?.state === "offline") {
2296
+ const mode = normalizePlatformMode(catalog.mode);
2297
+ return { state: "offline", ...mode ? { mode } : {}, products: [] };
2298
+ }
2299
+ if (catalog?.state === "invalid-token") {
2300
+ const mode = normalizePlatformMode(catalog.mode);
2301
+ return { state: "invalid-token", ...mode ? { mode } : {}, products: [] };
2302
+ }
2303
+ if (catalog?.state === "logged-in" && typeof catalog.account?.credits === "number" && Number.isFinite(catalog.account.credits) && Array.isArray(catalog.products)) {
2304
+ return {
2305
+ state: "logged-in",
2306
+ account: { credits: catalog.account.credits },
2307
+ ...normalizePlatformMode(catalog.mode) ? { mode: normalizePlatformMode(catalog.mode) ?? void 0 } : {},
2308
+ ...typeof catalog.selectedProductId === "string" ? { selectedProductId: catalog.selectedProductId } : {},
2309
+ products: catalog.products.filter(isProductSummary2)
2310
+ };
2311
+ }
2312
+ return { state: "offline", products: [] };
2313
+ }
2314
+ function normalizePlatformSelectionResult(value) {
2315
+ const result = value;
2316
+ if (result?.state === "selected" && typeof result.selectedProductId === "string") {
2317
+ return { state: "selected", selectedProductId: result.selectedProductId };
2318
+ }
2319
+ if (result?.state === "logged-out" || result?.state === "offline" || result?.state === "invalid-token") {
2320
+ return { state: result.state };
2321
+ }
2322
+ if ((result?.state === "product-not-found" || result?.state === "product-unavailable") && typeof result.productId === "string") {
2323
+ return { state: result.state, productId: result.productId };
2324
+ }
2325
+ return { state: "offline" };
2326
+ }
2327
+ function platformLoginErrorMessage(code) {
2328
+ if (code === "PASSWORD_LOGIN_INVALID") {
2329
+ return "\u767B\u5F55\u5931\u8D25\uFF1A\u8D26\u53F7\u6216\u5BC6\u7801\u4E0D\u6B63\u786E";
2330
+ }
2331
+ if (code === "PASSWORD_LOGIN_NOT_CONFIGURED") {
2332
+ return "\u767B\u5F55\u5931\u8D25\uFF1A\u8FD9\u4E2A\u8D26\u53F7\u8FD8\u6CA1\u6709\u8BBE\u7F6E\u7F51\u9875\u767B\u5F55\u5BC6\u7801\uFF0C\u8BF7\u5148\u5728\u7528\u6237\u7AEF\u5B8C\u6210\u6CE8\u518C\u6216\u91CD\u7F6E\u5BC6\u7801";
2333
+ }
2334
+ if (code === "DEVICE_LIMIT_REACHED") {
2335
+ return "\u767B\u5F55\u5931\u8D25\uFF1A\u8BBE\u5907\u6570\u91CF\u5DF2\u8FBE\u4E0A\u9650\uFF0C\u8BF7\u5148\u5728\u7528\u6237\u7AEF\u79FB\u9664\u65E7\u8BBE\u5907";
2336
+ }
2337
+ return "\u767B\u5F55\u5931\u8D25\uFF1A\u672C\u5730\u670D\u52A1\u6CA1\u6709\u8FDE\u4E0A\u5E73\u53F0 API\uFF0C\u6216\u8D26\u53F7\u5BC6\u7801\u4E0D\u6B63\u786E";
2338
+ }
2339
+ async function postJson(options, path, body) {
2340
+ const runtime = await resolveRuntimeForRequest(options);
2341
+ if (!runtime) {
2342
+ throw new Error("Cursor Pool service runtime is unavailable");
2343
+ }
2344
+ const response = await fetch(serviceUrl(runtime, path), {
2345
+ method: "POST",
2346
+ headers: { "content-type": "application/json" },
2347
+ body: JSON.stringify(body ?? {})
2348
+ });
2349
+ if (!response.ok) {
2350
+ if (response.status === 400 && path === "/platform/login") {
2351
+ const errorBody = await response.json().catch(() => ({}));
2352
+ throw new Error(platformLoginErrorMessage(errorBody.code));
2353
+ }
2354
+ throw new Error(`\u672C\u5730\u670D\u52A1\u8BF7\u6C42\u5931\u8D25\uFF1A${response.status}`);
2355
+ }
2356
+ return await response.json();
2357
+ }
2358
+ async function getServiceStatus(runtimeFile, options = {}) {
2359
+ const runtime = await resolveHealthyRuntime({ runtimeFile, ...options });
2360
+ if (!runtime) {
2361
+ return { service: "stopped", runtime: null };
2362
+ }
2363
+ try {
2364
+ const response = await fetch(serviceUrl(runtime, "/extension/status"), {
2365
+ method: "POST",
2366
+ headers: { "content-type": "application/json" },
2367
+ body: JSON.stringify({ connected: true })
2368
+ });
2369
+ if (!response.ok) {
2370
+ return { service: "stopped", runtime };
2371
+ }
2372
+ return { service: "running", runtime };
2373
+ } catch {
2374
+ return { service: "stopped", runtime };
2375
+ }
2376
+ }
2377
+ async function getPlatformStatus(runtimeFile, options = {}) {
2378
+ const runtime = await resolveRuntimeForRequest({ runtimeFile, ...options });
2379
+ if (!runtime) {
2380
+ return { state: "logged-out" };
2381
+ }
2382
+ try {
2383
+ const response = await fetch(serviceUrl(runtime, "/platform/status"));
2384
+ if (!response.ok) {
2385
+ return { state: "offline" };
2386
+ }
2387
+ const status = await response.json();
2388
+ return isPlatformStatus(status) ? status : { state: "offline" };
2389
+ } catch {
2390
+ return { state: "offline" };
2391
+ }
2392
+ }
2393
+ async function getPlatformCatalog(runtimeFile, options = {}) {
2394
+ const runtime = await resolveRuntimeForRequest({ runtimeFile, ...options });
2395
+ if (!runtime) {
2396
+ return { state: "logged-out", products: [] };
2397
+ }
2398
+ try {
2399
+ const response = await fetch(serviceUrl(runtime, "/platform/catalog"));
2400
+ if (!response.ok) {
2401
+ return { state: "offline", products: [] };
2402
+ }
2403
+ return normalizePlatformCatalog(await response.json());
2404
+ } catch {
2405
+ return { state: "offline", products: [] };
2406
+ }
2407
+ }
2408
+ async function startMode(runtimeFile, options = {}) {
2409
+ try {
2410
+ return normalizePlatformModeResult(await postJson({ runtimeFile, ...options }, "/mode/start"));
2411
+ } catch {
2412
+ return { state: "offline" };
2413
+ }
2414
+ }
2415
+ async function stopMode(runtimeFile, options = {}) {
2416
+ try {
2417
+ return normalizePlatformModeResult(await postJson({ runtimeFile, ...options }, "/mode/stop"));
2418
+ } catch {
2419
+ return { state: "offline" };
2420
+ }
2421
+ }
2422
+ async function loginPlatform(options) {
2423
+ return postJson(options, "/platform/login", {
2424
+ email: options.email,
2425
+ password: options.password,
2426
+ device: buildExtensionDeviceInfo()
2427
+ });
2428
+ }
2429
+ async function selectPlatformProduct2(options) {
2430
+ const runtime = await resolveRuntimeForRequest(options);
2431
+ if (!runtime) {
2432
+ return { state: "offline" };
2433
+ }
2434
+ try {
2435
+ const response = await fetch(serviceUrl(runtime, "/platform/selection"), {
2436
+ method: "POST",
2437
+ headers: { "content-type": "application/json" },
2438
+ body: JSON.stringify({ productId: options.productId })
2439
+ });
2440
+ if (!response.ok) {
2441
+ return { state: "offline" };
2442
+ }
2443
+ return normalizePlatformSelectionResult(await response.json());
2444
+ } catch {
2445
+ return { state: "offline" };
2446
+ }
2447
+ }
2448
+ async function logoutPlatform2(runtimeFile, options = {}) {
2449
+ return postJson({ runtimeFile, ...options }, "/platform/logout");
2450
+ }
2451
+
2452
+ // packages/extension/src/panel.ts
2453
+ function createPanelViewModel(status) {
2454
+ const rows = [
2455
+ { label: "patch", value: status.patch },
2456
+ { label: "service", value: status.service },
2457
+ { label: "compat", value: status.compat },
2458
+ { label: "mode", value: status.mode },
2459
+ { label: "platform", value: status.platform ?? "logged-out" }
2460
+ ];
2461
+ if (status.user) {
2462
+ rows.push({ label: "user", value: status.user });
2463
+ }
2464
+ if (status.device) {
2465
+ rows.push({ label: "device", value: status.device });
2466
+ }
2467
+ if (status.heartbeat) {
2468
+ rows.push({ label: "heartbeat", value: status.heartbeat });
2469
+ }
2470
+ if (status.catalog) {
2471
+ rows.push({ label: "catalog", value: status.catalog });
2472
+ }
2473
+ if (status.credits) {
2474
+ rows.push({ label: "credits", value: status.credits });
2475
+ }
2476
+ if (status.selectedProductId) {
2477
+ rows.push({ label: "current product", value: status.selectedProductId });
2478
+ }
2479
+ const platformState = status.platform ?? "logged-out";
2480
+ const takeoverActive = status.mode.startsWith("active ");
2481
+ const platformLoggedIn = platformState === "logged-in" || platformState === "offline" || platformState === "invalid-token";
2482
+ return {
2483
+ title: "Cursor Pool \u53F7\u6C60\u5BA2\u6237\u7AEF",
2484
+ rows,
2485
+ platformControls: {
2486
+ showLogin: platformState === "logged-out" || platformState === "offline",
2487
+ showLogout: platformLoggedIn,
2488
+ showRefresh: true,
2489
+ showEnableTakeover: platformState === "logged-in" && !takeoverActive,
2490
+ showDisableTakeover: platformLoggedIn && takeoverActive
2491
+ },
2492
+ consoleCommand: "cursorPool.openConsole",
2493
+ selectedProductId: status.selectedProductId,
2494
+ products: status.products ?? [],
2495
+ message: status.message,
2496
+ error: status.error
2497
+ };
2498
+ }
2499
+ function formatPanelMode(mode) {
2500
+ if (mode?.state === "active") {
2501
+ return `active ${mode.productId}`;
2502
+ }
2503
+ if (mode?.state === "inactive" && mode.releaseReason) {
2504
+ return `inactive ${mode.releaseReason}`;
2505
+ }
2506
+ if (mode?.state === "inactive") {
2507
+ return "inactive";
2508
+ }
2509
+ return void 0;
2510
+ }
2511
+ function formatPanelPlatformStatus(status) {
2512
+ const mode = formatPanelMode(status.mode);
2513
+ return {
2514
+ platform: status.state,
2515
+ user: status.user?.email,
2516
+ device: status.device ? `${status.device.status} ${status.device.id}` : void 0,
2517
+ heartbeat: status.device?.lastHeartbeatAt,
2518
+ ...mode ? { mode } : {}
2519
+ };
2520
+ }
2521
+ function formatPanelPlatformCatalog(catalog) {
2522
+ const mode = catalog.state === "logged-in" || catalog.state === "offline" || catalog.state === "invalid-token" ? formatPanelMode(catalog.mode) : void 0;
2523
+ return {
2524
+ catalog: catalog.state,
2525
+ credits: catalog.state === "logged-in" ? String(catalog.account.credits) : void 0,
2526
+ selectedProductId: catalog.state === "logged-in" ? catalog.selectedProductId : void 0,
2527
+ products: catalog.state === "logged-in" ? catalog.products : [],
2528
+ ...mode ? { mode } : {}
2529
+ };
2530
+ }
2531
+ function platformSelectionMessage(result) {
2532
+ if (result.state === "selected") {
2533
+ return { message: "\u5546\u54C1\u5DF2\u5207\u6362" };
2534
+ }
2535
+ if (result.state === "logged-out") {
2536
+ return { error: "\u8BF7\u5148\u767B\u5F55\u5E73\u53F0" };
2537
+ }
2538
+ if (result.state === "invalid-token") {
2539
+ return { error: "\u5E73\u53F0\u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" };
2540
+ }
2541
+ if (result.state === "product-not-found") {
2542
+ return { error: "\u5F53\u524D\u5546\u54C1\u5217\u8868\u4E2D\u6CA1\u6709\u8FD9\u4E2A\u5546\u54C1" };
2543
+ }
2544
+ if (result.state === "product-unavailable") {
2545
+ return { error: "\u5546\u54C1\u5F53\u524D\u4E0D\u53EF\u7528" };
2546
+ }
2547
+ return { error: "\u5E73\u53F0\u670D\u52A1\u6216\u7F51\u9875\u7AEF API \u6682\u65F6\u4E0D\u53EF\u7528" };
2548
+ }
2549
+ function platformModeMessage(result, action) {
2550
+ if (result.state === "active") {
2551
+ return { message: "\u5DF2\u5F00\u542F\u5E73\u53F0\u63A5\u7BA1" };
2552
+ }
2553
+ if (result.state === "inactive") {
2554
+ return { message: "\u5DF2\u5207\u56DE\u5B98\u65B9\u6A21\u5F0F" };
2555
+ }
2556
+ if (result.state === "logged-out") {
2557
+ return { error: "\u8BF7\u5148\u767B\u5F55\u5E73\u53F0" };
2558
+ }
2559
+ if (result.state === "invalid-token") {
2560
+ return { error: "\u5E73\u53F0\u767B\u5F55\u5DF2\u5931\u6548\uFF0C\u8BF7\u91CD\u65B0\u767B\u5F55" };
2561
+ }
2562
+ if (result.state === "product-not-selected") {
2563
+ return { error: "\u5F00\u542F\u5E73\u53F0\u63A5\u7BA1\u524D\u8BF7\u5148\u9009\u62E9\u5546\u54C1" };
2564
+ }
2565
+ if (result.state === "product-not-found") {
2566
+ return { error: "\u6240\u9009\u5546\u54C1\u4E0D\u5728\u5F53\u524D\u5546\u54C1\u5217\u8868\u4E2D" };
2567
+ }
2568
+ if (result.state === "product-unavailable") {
2569
+ return { error: "\u6240\u9009\u5546\u54C1\u5F53\u524D\u4E0D\u53EF\u7528" };
2570
+ }
2571
+ if (result.state === "insufficient-credits") {
2572
+ return { error: "\u79EF\u5206\u4E0D\u8DB3\uFF0C\u8BF7\u6253\u5F00\u7F51\u9875\u7AEF\u5904\u7406" };
2573
+ }
2574
+ return { error: action === "start" ? "\u5E73\u53F0\u670D\u52A1\u6216\u7F51\u9875\u7AEF API \u6682\u65F6\u4E0D\u53EF\u7528" : "\u5E73\u53F0\u670D\u52A1\u6682\u65F6\u4E0D\u53EF\u7528" };
2575
+ }
2576
+ function escapeHtml(value) {
2577
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
2578
+ }
2579
+ function buildPanelHtml(viewModel) {
2580
+ const valueFor = (label, fallback = "-") => viewModel.rows.find((row) => row.label === label)?.value ?? fallback;
2581
+ const statusItems = [
2582
+ { label: "\u672C\u5730\u670D\u52A1", value: valueFor("service") },
2583
+ ...valueFor("user", "") ? [{ label: "\u767B\u5F55\u8D26\u53F7", value: valueFor("user") }] : [],
2584
+ ...valueFor("credits", "") ? [{ label: "\u79EF\u5206\u4F59\u989D", value: valueFor("credits") }] : [],
2585
+ ...valueFor("user", "") ? [{
2586
+ label: "\u5F53\u524D\u6A21\u5F0F",
2587
+ value: valueFor("mode").startsWith("active ") ? "\u5E73\u53F0\u63A5\u7BA1" : "\u5B98\u65B9\u6A21\u5F0F"
2588
+ }] : []
2589
+ ];
2590
+ const rows = statusItems.map((row) => `<li><span>${escapeHtml(row.label)}</span><strong>${escapeHtml(row.value)}</strong></li>`).join("");
2591
+ const currentProductName = viewModel.products.find((product) => product.id === viewModel.selectedProductId)?.name;
2592
+ const currentProduct = currentProductName ? `<li><span>\u5F53\u524D\u4F7F\u7528</span><strong>${escapeHtml(currentProductName)}</strong></li>` : "";
2593
+ const platformControls = [
2594
+ viewModel.platformControls.showLogin ? `<section class="panel-section platform-login">
2595
+ <h2>\u53F7\u6C60\u767B\u5F55</h2>
2596
+ <input name="email" placeholder="\u8D26\u53F7/\u90AE\u7BB1" autocomplete="username">
2597
+ <input name="password" type="password" placeholder="\u5BC6\u7801" autocomplete="current-password">
2598
+ <button type="button" data-command="platform/login">\u767B\u5F55</button>
2599
+ </section>` : "",
2600
+ viewModel.platformControls.showLogout ? '<button type="button" data-command="platform/logout">\u9000\u51FA\u767B\u5F55</button>' : "",
2601
+ viewModel.platformControls.showEnableTakeover ? '<button type="button" data-command="takeover/enable">\u5F00\u542F\u5E73\u53F0\u63A5\u7BA1</button>' : "",
2602
+ viewModel.platformControls.showDisableTakeover ? '<button type="button" data-command="takeover/disable">\u5207\u56DE\u5B98\u65B9\u6A21\u5F0F</button>' : "",
2603
+ viewModel.platformControls.showRefresh ? '<button type="button" data-command="platform/refresh">\u5237\u65B0\u72B6\u6001</button>' : ""
2604
+ ].join("");
2605
+ const message = viewModel.message ? `<p class="message">${escapeHtml(viewModel.message)}</p>` : "";
2606
+ const error = viewModel.error ? `<p class="error">${escapeHtml(viewModel.error)}</p>` : "";
2607
+ const productItems = viewModel.products.map(
2608
+ (product) => {
2609
+ const rowClass = product.id === viewModel.selectedProductId ? "product-row is-selected" : "product-row";
2610
+ return `<li class="${rowClass}" title="${escapeHtml(product.description)}">
2611
+ <div class="product-main">
2612
+ <strong>${escapeHtml(product.name)}</strong>
2613
+ <span class="product-description">${escapeHtml(product.description)}</span>
2614
+ </div>
2615
+ <div class="product-meta">
2616
+ <span>${escapeHtml(product.status)}</span>
2617
+ <span>\u51C6\u5165 ${escapeHtml(String(product.minCredits))}</span>
2618
+ <span>${escapeHtml(product.usageLabel)}</span>
2619
+ </div>
2620
+ <div class="product-action">
2621
+ ${product.id === viewModel.selectedProductId ? '<span class="current-badge">\u5F53\u524D\u4F7F\u7528</span>' : product.status === "available" ? `<button type="button" data-command="platform/select-product" data-product-id="${escapeHtml(product.id)}">\u4F7F\u7528</button>` : ""}
2622
+ </div>
2623
+ </li>`;
2624
+ }
2625
+ ).join("");
2626
+ const products = viewModel.products.length > 0 ? `<section class="panel-section products"><h2>\u53EF\u7528\u5546\u54C1</h2><ul class="product-list">${productItems}</ul></section>` : "";
2627
+ return `<!doctype html>
2628
+ <html lang="zh-CN">
2629
+ <head>
2630
+ <meta charset="utf-8">
2631
+ <style>
2632
+ body { font-family: var(--vscode-font-family, sans-serif); padding: 12px; color: var(--vscode-foreground); }
2633
+ h1 { font-size: 15px; margin: 0 0 10px; }
2634
+ h2 { font-size: 12px; margin: 0 0 8px; }
2635
+ ul { list-style: none; margin: 0; padding: 0; }
2636
+ li { margin: 0; }
2637
+ .status-list { display: grid; gap: 6px; }
2638
+ .status-list li { display: flex; justify-content: space-between; gap: 10px; min-width: 0; }
2639
+ .status-list span { color: var(--vscode-descriptionForeground, #777); }
2640
+ .status-list strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2641
+ strong { font-weight: 600; }
2642
+ button { border: 1px solid var(--vscode-button-border, transparent); border-radius: 4px; padding: 3px 8px; color: var(--vscode-button-foreground); background: var(--vscode-button-background); }
2643
+ input { box-sizing: border-box; display: block; width: 100%; margin: 6px 0; }
2644
+ .panel-section { border-top: 1px solid var(--vscode-panel-border, #ddd); padding-top: 10px; margin-top: 10px; }
2645
+ .products { margin-top: 10px; }
2646
+ .product-list { max-height: 260px; overflow: auto; border: 1px solid var(--vscode-panel-border, #ddd); border-radius: 6px; }
2647
+ .product-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 8px; align-items: center; padding: 8px; border-top: 1px solid var(--vscode-panel-border, #ddd); }
2648
+ .product-row:first-child { border-top: 0; }
2649
+ .product-row.is-selected { background: var(--vscode-list-activeSelectionBackground, rgba(45, 91, 209, 0.14)); }
2650
+ .product-main { min-width: 0; }
2651
+ .product-main strong, .product-description { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
2652
+ .product-description, .product-meta { color: var(--vscode-descriptionForeground, #777); font-size: 11px; }
2653
+ .product-meta { display: grid; gap: 1px; white-space: nowrap; }
2654
+ .product-action { min-width: 46px; text-align: right; }
2655
+ .current-badge { color: var(--vscode-testing-iconPassed, #17633f); font-size: 12px; font-weight: 600; white-space: nowrap; }
2656
+ .platform-controls { margin-top: 10px; }
2657
+ .platform-controls button { margin-right: 8px; margin-bottom: 8px; }
2658
+ .message { color: var(--vscode-descriptionForeground, #666); }
2659
+ .error { color: var(--vscode-errorForeground, #b00020); }
2660
+ </style>
2661
+ </head>
2662
+ <body>
2663
+ <h1>${escapeHtml(viewModel.title)}</h1>
2664
+ <section class="panel-section"><h2>\u8FD0\u884C\u72B6\u6001</h2><ul class="status-list">${rows}${currentProduct}</ul></section>
2665
+ ${message}
2666
+ ${error}
2667
+ ${products}
2668
+ <div class="platform-controls">${platformControls}</div>
2669
+ <script>
2670
+ const vscode = acquireVsCodeApi();
2671
+ document.addEventListener('click', (event) => {
2672
+ const target = event.target;
2673
+ if (!(target instanceof HTMLElement) || !target.dataset.command) {
2674
+ return;
2675
+ }
2676
+
2677
+ const payload = { command: target.dataset.command };
2678
+ if (target.dataset.command === 'platform/login') {
2679
+ payload.email = document.querySelector('[name="email"]')?.value || '';
2680
+ payload.password = document.querySelector('[name="password"]')?.value || '';
2681
+ }
2682
+ if (target.dataset.productId) {
2683
+ payload.productId = target.dataset.productId;
2684
+ }
2685
+
2686
+ vscode.postMessage(payload);
2687
+ });
2688
+ </script>
2689
+ </body>
2690
+ </html>`;
2691
+ }
2692
+ function registerStatusPanel(context, vscode, options = {}) {
2693
+ const provider = {
2694
+ async resolveWebviewView(view) {
2695
+ const readServiceStatus = options.getServiceStatus ?? getServiceStatus;
2696
+ const ensureService = options.ensureService ?? (async (runtimeFile) => {
2697
+ await ensureLocalService(runtimeFile);
2698
+ });
2699
+ const readPlatformStatus = options.getPlatformStatus ?? getPlatformStatus;
2700
+ const readPlatformCatalog = options.getPlatformCatalog ?? getPlatformCatalog;
2701
+ const doLoginPlatform = options.loginPlatform ?? loginPlatform;
2702
+ const doLogoutPlatform = options.logoutPlatform ?? logoutPlatform2;
2703
+ const doSelectPlatformProduct = options.selectPlatformProduct ?? selectPlatformProduct2;
2704
+ const doStartMode = options.startMode ?? startMode;
2705
+ const doStopMode = options.stopMode ?? stopMode;
2706
+ let serviceEnsured = false;
2707
+ let initialServiceStatus;
2708
+ view.webview.options = {
2709
+ ...view.webview.options,
2710
+ enableScripts: true
2711
+ };
2712
+ const render = async (feedback = {}, serviceStatus) => {
2713
+ const service = options.status ? options.status.service : (serviceStatus ?? await readServiceStatus(options.runtimeFile)).service;
2714
+ const platform = options.status ? {} : formatPanelPlatformStatus(await readPlatformStatus(options.runtimeFile));
2715
+ const catalog = options.status ? {} : formatPanelPlatformCatalog(await readPlatformCatalog(options.runtimeFile));
2716
+ const status = options.status ? { ...options.status, ...feedback } : {
2717
+ patch: "unknown",
2718
+ service,
2719
+ compat: "unknown",
2720
+ mode: "unknown",
2721
+ ...platform,
2722
+ ...catalog,
2723
+ ...feedback
2724
+ };
2725
+ view.webview.html = buildPanelHtml(createPanelViewModel(status));
2726
+ };
2727
+ const renderError = async (error) => {
2728
+ try {
2729
+ const message = error instanceof Error && error.message === "fetch failed" ? "\u672C\u5730\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 Cursor Pool \u672C\u5730\u670D\u52A1" : error instanceof Error ? error.message : "Cursor Pool service request failed";
2730
+ await render({
2731
+ error: message
2732
+ });
2733
+ } catch {
2734
+ }
2735
+ };
2736
+ const ensureServiceForPanel = async () => {
2737
+ if (serviceEnsured) {
2738
+ return void 0;
2739
+ }
2740
+ const service = await readServiceStatus(options.runtimeFile);
2741
+ if (service.service === "stopped") {
2742
+ await ensureService(options.runtimeFile);
2743
+ serviceEnsured = true;
2744
+ return void 0;
2745
+ }
2746
+ serviceEnsured = true;
2747
+ return service;
2748
+ };
2749
+ if (!options.status) {
2750
+ try {
2751
+ initialServiceStatus = await ensureServiceForPanel();
2752
+ } catch {
2753
+ }
2754
+ }
2755
+ await render({}, initialServiceStatus);
2756
+ view.webview.onDidReceiveMessage?.(async (message) => {
2757
+ try {
2758
+ if (message.command === "mode/start" || message.command === "takeover/enable") {
2759
+ const result = await doStartMode(options.runtimeFile);
2760
+ await render(platformModeMessage(result, "start"));
2761
+ if (result.state === "active") {
2762
+ await vscode.commands?.executeCommand?.("workbench.action.reloadWindow");
2763
+ }
2764
+ return;
2765
+ }
2766
+ if (message.command === "mode/stop" || message.command === "takeover/disable") {
2767
+ const result = await doStopMode(options.runtimeFile);
2768
+ await render(platformModeMessage(result, "stop"));
2769
+ if (result.state === "inactive") {
2770
+ await vscode.commands?.executeCommand?.("workbench.action.reloadWindow");
2771
+ }
2772
+ return;
2773
+ }
2774
+ if (message.command === "platform/login") {
2775
+ if (!message.email) {
2776
+ await render({ error: "\u8BF7\u8F93\u5165\u8D26\u53F7/\u90AE\u7BB1" });
2777
+ return;
2778
+ }
2779
+ if (!message.password) {
2780
+ await render({ error: "\u8BF7\u8F93\u5165\u5BC6\u7801" });
2781
+ return;
2782
+ }
2783
+ await ensureServiceForPanel();
2784
+ try {
2785
+ await doLoginPlatform({
2786
+ runtimeFile: options.runtimeFile,
2787
+ email: message.email,
2788
+ password: message.password
2789
+ });
2790
+ } catch (error) {
2791
+ const current = await readPlatformStatus(options.runtimeFile).catch(() => null);
2792
+ if (current?.state === "logged-in") {
2793
+ await render({ message: "\u5E73\u53F0\u72B6\u6001\u5DF2\u5237\u65B0" });
2794
+ return;
2795
+ }
2796
+ throw error;
2797
+ }
2798
+ await render({ message: "\u5E73\u53F0\u767B\u5F55\u6210\u529F" });
2799
+ return;
2800
+ }
2801
+ if (message.command === "platform/logout") {
2802
+ await doLogoutPlatform(options.runtimeFile);
2803
+ await render({ message: "\u5DF2\u9000\u51FA\u5E73\u53F0\u767B\u5F55" });
2804
+ return;
2805
+ }
2806
+ if (message.command === "platform/select-product") {
2807
+ if (!message.productId) {
2808
+ await render({ error: "\u8BF7\u9009\u62E9\u5546\u54C1" });
2809
+ return;
2810
+ }
2811
+ const result = await doSelectPlatformProduct({
2812
+ runtimeFile: options.runtimeFile,
2813
+ productId: message.productId
2814
+ });
2815
+ await render(platformSelectionMessage(result));
2816
+ return;
2817
+ }
2818
+ if (message.command === "platform/refresh") {
2819
+ await render({ message: "\u5E73\u53F0\u72B6\u6001\u5DF2\u5237\u65B0" });
2820
+ return;
2821
+ }
2822
+ if (message.command === "cursorPool.openConsole") {
2823
+ await vscode.commands?.executeCommand?.("workbench.action.webview.openDeveloperTools");
2824
+ }
2825
+ } catch (error) {
2826
+ await renderError(error);
2827
+ }
2828
+ });
2829
+ }
2830
+ };
2831
+ const subscription = vscode.window?.registerWebviewViewProvider?.("cursorPool.statusPanel", provider);
2832
+ if (subscription) {
2833
+ context.subscriptions?.push(subscription);
2834
+ }
2835
+ return provider;
2836
+ }
2837
+
2838
+ // packages/extension/src/extension.ts
2839
+ async function appendDiagnostic(message) {
2840
+ try {
2841
+ const os = await new Function("specifier", "return import(specifier)")("node:os");
2842
+ const path = await new Function("specifier", "return import(specifier)")("node:path");
2843
+ const fs = await new Function("specifier", "return import(specifier)")("node:fs/promises");
2844
+ const logDir = path.join(os.homedir(), ".cursor-pool/logs");
2845
+ await fs.mkdir(logDir, { recursive: true });
2846
+ await fs.appendFile(
2847
+ path.join(logDir, "extension.log"),
2848
+ `${(/* @__PURE__ */ new Date()).toISOString()} ${message}
2849
+ `,
2850
+ "utf8"
2851
+ );
2852
+ } catch {
2853
+ }
2854
+ }
2855
+ function registerExtensionCommands(context, vscode, options = {}) {
2856
+ void appendDiagnostic("registerExtensionCommands");
2857
+ const openClientSubscription = vscode.commands?.registerCommand?.("cursorPool.openClient", () => {
2858
+ void appendDiagnostic("cursorPool.openClient invoked");
2859
+ try {
2860
+ const result = vscode.commands?.executeCommand?.("workbench.view.extension.cursorPoolClient");
2861
+ void appendDiagnostic("workbench.view.extension.cursorPoolClient dispatched");
2862
+ return result;
2863
+ } catch (error) {
2864
+ void appendDiagnostic(`workbench.view.extension.cursorPoolClient failed: ${error instanceof Error ? error.message : String(error)}`);
2865
+ throw error;
2866
+ }
2867
+ });
2868
+ if (openClientSubscription) {
2869
+ void appendDiagnostic("cursorPool.openClient registered");
2870
+ context.subscriptions?.push(openClientSubscription);
2871
+ } else {
2872
+ void appendDiagnostic("cursorPool.openClient not registered");
2873
+ }
2874
+ const openConsoleSubscription = vscode.commands?.registerCommand?.("cursorPool.openConsole", () => {
2875
+ vscode.window?.showInformationMessage?.("\u8BF7\u5728\u7F51\u9875\u7AEF\u67E5\u770B\u5145\u503C\u3001\u8BB0\u5F55\u3001\u8BBE\u5907\u548C\u8D26\u53F7\u4FE1\u606F\u3002");
2876
+ });
2877
+ if (openConsoleSubscription) {
2878
+ context.subscriptions?.push(openConsoleSubscription);
2879
+ }
2880
+ const reportStatusSubscription = vscode.commands?.registerCommand?.(
2881
+ "cursorPool.reportStatus",
2882
+ async () => {
2883
+ const readStatus = options.getServiceStatus ?? getServiceStatus;
2884
+ const status = await readStatus(options.runtimeFile);
2885
+ vscode.window?.showInformationMessage?.(`Cursor Pool service: ${status.service}`);
2886
+ }
2887
+ );
2888
+ if (reportStatusSubscription) {
2889
+ context.subscriptions?.push(reportStatusSubscription);
2890
+ }
2891
+ }
2892
+ async function activate(context, injectedVscode, options = {}) {
2893
+ void appendDiagnostic("activate start");
2894
+ try {
2895
+ const vscode = injectedVscode ?? await new Function("specifier", "return import(specifier)")("vscode");
2896
+ registerExtensionCommands(context, vscode, options);
2897
+ registerStatusPanel(context, vscode, options);
2898
+ void appendDiagnostic("activate complete");
2899
+ } catch (error) {
2900
+ void appendDiagnostic(`activate failed: ${error instanceof Error ? error.message : String(error)}`);
2901
+ return;
2902
+ }
2903
+ }
2904
+ function deactivate() {
2905
+ }
2906
+ export {
2907
+ activate,
2908
+ deactivate,
2909
+ registerExtensionCommands
2910
+ };