clawdex-mobile 5.1.3-internal.9 → 5.2.3

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 (48) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +6 -55
  3. package/bin/clawdex.js +2 -2
  4. package/docs/setup-and-operations.md +13 -100
  5. package/docs/troubleshooting.md +2 -71
  6. package/package.json +17 -6
  7. package/scripts/setup-secure-dev.sh +5 -79
  8. package/scripts/setup-wizard.sh +37 -87
  9. package/scripts/start-bridge-secure.js +9 -269
  10. package/services/cursor-app-server/README.md +39 -0
  11. package/services/cursor-app-server/dist/appServer.d.ts +52 -0
  12. package/services/cursor-app-server/dist/appServer.js +780 -0
  13. package/services/cursor-app-server/dist/appServer.js.map +1 -0
  14. package/services/cursor-app-server/dist/cursorWorkspace.d.ts +7 -0
  15. package/services/cursor-app-server/dist/cursorWorkspace.js +126 -0
  16. package/services/cursor-app-server/dist/cursorWorkspace.js.map +1 -0
  17. package/services/cursor-app-server/dist/index.d.ts +4 -0
  18. package/services/cursor-app-server/dist/index.js +4 -0
  19. package/services/cursor-app-server/dist/index.js.map +1 -0
  20. package/services/cursor-app-server/dist/input.d.ts +27 -0
  21. package/services/cursor-app-server/dist/input.js +143 -0
  22. package/services/cursor-app-server/dist/input.js.map +1 -0
  23. package/services/cursor-app-server/dist/jsonRpc.d.ts +15 -0
  24. package/services/cursor-app-server/dist/jsonRpc.js +93 -0
  25. package/services/cursor-app-server/dist/jsonRpc.js.map +1 -0
  26. package/services/cursor-app-server/dist/projection.d.ts +8 -0
  27. package/services/cursor-app-server/dist/projection.js +507 -0
  28. package/services/cursor-app-server/dist/projection.js.map +1 -0
  29. package/services/cursor-app-server/dist/sdkDriver.d.ts +50 -0
  30. package/services/cursor-app-server/dist/sdkDriver.js +166 -0
  31. package/services/cursor-app-server/dist/sdkDriver.js.map +1 -0
  32. package/services/cursor-app-server/dist/stdio.d.ts +2 -0
  33. package/services/cursor-app-server/dist/stdio.js +7 -0
  34. package/services/cursor-app-server/dist/stdio.js.map +1 -0
  35. package/services/cursor-app-server/dist/types.d.ts +218 -0
  36. package/services/cursor-app-server/dist/types.js +2 -0
  37. package/services/cursor-app-server/dist/types.js.map +1 -0
  38. package/services/cursor-app-server/package.json +43 -0
  39. package/services/rust-bridge/Cargo.lock +1 -1
  40. package/services/rust-bridge/Cargo.toml +1 -1
  41. package/services/rust-bridge/src/main.rs +1703 -353
  42. package/vendor/bridge-binaries/darwin-arm64/codex-rust-bridge +0 -0
  43. package/vendor/bridge-binaries/darwin-x64/codex-rust-bridge +0 -0
  44. package/vendor/bridge-binaries/linux-arm64/codex-rust-bridge +0 -0
  45. package/vendor/bridge-binaries/linux-armv7l/codex-rust-bridge +0 -0
  46. package/vendor/bridge-binaries/linux-x64/codex-rust-bridge +0 -0
  47. package/vendor/bridge-binaries/win32-x64/codex-rust-bridge.exe +0 -0
  48. package/scripts/codespaces-bootstrap.js +0 -297
@@ -0,0 +1,780 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { extname } from 'node:path';
4
+ import { findCursorTranscriptWorkspaceCwd } from './cursorWorkspace.js';
5
+ import { parseListParams, parseThreadIdParams, parseThreadStartParams, parseTurnStartParams, } from './input.js';
6
+ import { isGenericCursorAgentName, messagesToTurns, projectAgentInfoToThread, readMessageText, streamMessageToThreadItem, toPreview, } from './projection.js';
7
+ import { CursorSdkDriver } from './sdkDriver.js';
8
+ export class CursorAppServer {
9
+ runtime;
10
+ driver;
11
+ configuredCwd;
12
+ apiKey;
13
+ defaultModel;
14
+ cursorProjectsDir;
15
+ events = new EventEmitter();
16
+ liveThreads = new Map();
17
+ knownThreadCwds = new Map();
18
+ knownThreadStoreCwds = new Map();
19
+ constructor(options) {
20
+ if (options.runtime !== 'local') {
21
+ throw new Error(`unsupported Cursor runtime: ${String(options.runtime)}`);
22
+ }
23
+ this.runtime = options.runtime;
24
+ this.driver = options.driver ?? new CursorSdkDriver();
25
+ this.configuredCwd = normalizeString(options.cwd);
26
+ this.apiKey = normalizeString(options.apiKey);
27
+ this.defaultModel = normalizeString(options.defaultModel);
28
+ this.cursorProjectsDir = normalizeString(options.cursorProjectsDir) ?? undefined;
29
+ }
30
+ onNotification(listener) {
31
+ this.events.on('notification', listener);
32
+ return () => {
33
+ this.events.off('notification', listener);
34
+ };
35
+ }
36
+ async request(method, params) {
37
+ switch (method) {
38
+ case 'thread/list':
39
+ return this.listThreads(params);
40
+ case 'thread/loaded/list':
41
+ return this.listLoadedThreads();
42
+ case 'thread/read':
43
+ return this.readThread(params);
44
+ case 'thread/start':
45
+ return this.startThread(params);
46
+ case 'turn/start':
47
+ return this.startTurn(params);
48
+ case 'turn/interrupt':
49
+ return this.interruptTurn(params);
50
+ case 'model/list':
51
+ return this.listModels();
52
+ case 'initialize':
53
+ case 'initialized':
54
+ return this.initialize();
55
+ default:
56
+ throw new Error(`unsupported Cursor app-server method: ${method}`);
57
+ }
58
+ }
59
+ async listThreads(params) {
60
+ const parsed = parseListParams(params);
61
+ this.requireApiKey();
62
+ const requestedCwd = normalizeString(parsed.cwd);
63
+ const cwds = requestedCwd
64
+ ? uniqueStrings([requestedCwd, ...this.listKnownWorkspaceCwds()])
65
+ : this.listKnownWorkspaceCwds();
66
+ const limit = parsed.limit ?? 100;
67
+ const entries = new Map();
68
+ let nextCursor = null;
69
+ for (const cwd of cwds) {
70
+ const result = await this.driver.listAgents({
71
+ cwd,
72
+ limit,
73
+ cursor: cwds.length === 1 ? parsed.cursor : undefined,
74
+ });
75
+ if (cwds.length === 1) {
76
+ nextCursor = result.nextCursor ?? null;
77
+ }
78
+ for (const agent of result.items) {
79
+ const effectiveCwd = await this.resolveAgentEffectiveCwd(agent, cwd, requestedCwd);
80
+ if (requestedCwd && effectiveCwd !== requestedCwd) {
81
+ continue;
82
+ }
83
+ const projectedAgent = { ...agent, cwd: effectiveCwd };
84
+ this.rememberAgentCwd(projectedAgent, cwd, effectiveCwd);
85
+ const existing = entries.get(agent.agentId);
86
+ if (!existing || projectedAgent.lastModified > existing.lastModified) {
87
+ entries.set(agent.agentId, projectedAgent);
88
+ }
89
+ }
90
+ }
91
+ return {
92
+ data: [...entries.values()]
93
+ .sort(compareCursorAgentsByUpdatedAtDesc)
94
+ .slice(0, limit)
95
+ .map((agent) => projectAgentInfoToThread(agent, this.resolveKnownThreadWorkspace(agent.agentId, agent.cwd ?? requestedCwd).cwd)),
96
+ nextCursor,
97
+ backwardsCursor: null,
98
+ };
99
+ }
100
+ async readThread(params) {
101
+ const parsed = parseThreadIdParams(params);
102
+ const apiKey = this.requireApiKey();
103
+ const live = this.liveThreads.get(parsed.threadId);
104
+ if (live) {
105
+ return {
106
+ thread: projectAgentInfoToThread(live.info, live.cwd, live.turns),
107
+ };
108
+ }
109
+ const workspace = this.resolveKnownThreadWorkspace(parsed.threadId, parsed.cwd);
110
+ const { agent, messages, cwd, storeCwd } = await this.readPersistedThread(parsed.threadId, workspace, apiKey);
111
+ this.rememberAgentCwd(agent, storeCwd, cwd);
112
+ return {
113
+ thread: projectAgentInfoToThread(agent, cwd, messagesToTurns(messages)),
114
+ };
115
+ }
116
+ listLoadedThreads() {
117
+ return {
118
+ data: [...this.liveThreads.keys()],
119
+ };
120
+ }
121
+ async startThread(params) {
122
+ const parsed = parseThreadStartParams(params);
123
+ const cwd = this.requireCwd(parsed.cwd);
124
+ const model = this.requireModel(parsed.model);
125
+ const apiKey = this.requireApiKey();
126
+ const agent = await this.driver.createAgent({
127
+ cwd,
128
+ apiKey,
129
+ name: parsed.name ?? undefined,
130
+ model,
131
+ });
132
+ const now = Date.now();
133
+ const info = {
134
+ agentId: agent.agentId,
135
+ name: parsed.name ?? '',
136
+ summary: '',
137
+ lastModified: now,
138
+ createdAt: now,
139
+ status: 'finished',
140
+ runtime: this.runtime,
141
+ cwd,
142
+ };
143
+ const state = {
144
+ agent,
145
+ info,
146
+ cwd,
147
+ storeCwd: cwd,
148
+ model,
149
+ turns: [],
150
+ nameLocked: Boolean(parsed.name),
151
+ };
152
+ this.liveThreads.set(agent.agentId, state);
153
+ this.knownThreadCwds.set(agent.agentId, cwd);
154
+ this.knownThreadStoreCwds.set(agent.agentId, cwd);
155
+ this.emit('thread/started', { threadId: agent.agentId });
156
+ return {
157
+ thread: projectAgentInfoToThread(info, cwd, state.turns),
158
+ };
159
+ }
160
+ async startTurn(params) {
161
+ const parsed = parseTurnStartParams(params);
162
+ this.requireApiKey();
163
+ const state = await this.getOrResumeLiveThread(parsed.threadId, parsed.cwd);
164
+ const model = this.requireTurnModel(parsed.model, state.model);
165
+ const cursorPrompt = applyCursorModeToPrompt(parsed.prompt, parsed.collaborationMode);
166
+ if (!state.nameLocked) {
167
+ state.info.name = toPreview(parsed.prompt);
168
+ state.nameLocked = true;
169
+ }
170
+ const run = await state.agent.send(await this.buildUserMessage(cursorPrompt, parsed.imagePaths), { model });
171
+ state.model = model;
172
+ const turn = {
173
+ id: run.id,
174
+ status: 'in_progress',
175
+ items: [
176
+ {
177
+ type: 'userMessage',
178
+ id: `${run.id}-user`,
179
+ content: this.buildUserThreadContent(parsed.prompt, parsed.imagePaths),
180
+ },
181
+ ],
182
+ };
183
+ state.turns.push(turn);
184
+ state.activeRun = run;
185
+ state.info.status = 'running';
186
+ state.info.lastModified = Date.now();
187
+ this.emit('turn/started', {
188
+ threadId: parsed.threadId,
189
+ turnId: run.id,
190
+ });
191
+ this.emit('thread/status/changed', {
192
+ threadId: parsed.threadId,
193
+ status: 'running',
194
+ });
195
+ void this.consumeRun(state, turn, run);
196
+ return {
197
+ turn: {
198
+ id: run.id,
199
+ },
200
+ };
201
+ }
202
+ async interruptTurn(params) {
203
+ const parsed = parseThreadIdParams(params);
204
+ const state = this.liveThreads.get(parsed.threadId);
205
+ if (!state?.activeRun) {
206
+ throw new Error(`no active Cursor run for thread: ${parsed.threadId}`);
207
+ }
208
+ await state.activeRun.cancel();
209
+ state.info.status = 'finished';
210
+ state.info.lastModified = Date.now();
211
+ state.activeRun = undefined;
212
+ this.emit('turn/completed', {
213
+ threadId: parsed.threadId,
214
+ turnId: null,
215
+ status: 'cancelled',
216
+ });
217
+ this.emit('thread/status/changed', {
218
+ threadId: parsed.threadId,
219
+ status: 'idle',
220
+ });
221
+ return {};
222
+ }
223
+ async listModels() {
224
+ const apiKey = this.requireApiKey();
225
+ return {
226
+ data: await this.driver.listModels({ apiKey }),
227
+ };
228
+ }
229
+ initialize() {
230
+ return {
231
+ serverInfo: {
232
+ name: '@clawdex/cursor-app-server',
233
+ title: 'Clawdex Cursor App Server',
234
+ version: '0.1.0',
235
+ },
236
+ capabilities: {
237
+ experimentalApi: true,
238
+ },
239
+ };
240
+ }
241
+ async getOrResumeLiveThread(threadId, requestCwd = null) {
242
+ const live = this.liveThreads.get(threadId);
243
+ if (live) {
244
+ return live;
245
+ }
246
+ const workspace = this.resolveKnownThreadWorkspace(threadId, requestCwd);
247
+ const apiKey = this.requireApiKey();
248
+ const configuredModel = this.configuredModelOrUndefined();
249
+ const { agent, info, messages, persistedModel, cwd, storeCwd } = await this.resumePersistedThread(threadId, workspace, apiKey, configuredModel);
250
+ const state = {
251
+ agent,
252
+ info,
253
+ cwd,
254
+ storeCwd,
255
+ model: agent.model ?? configuredModel ?? persistedModel,
256
+ turns: messagesToTurns(messages),
257
+ nameLocked: !isGenericCursorAgentName(info.name, info.agentId),
258
+ };
259
+ this.liveThreads.set(threadId, state);
260
+ this.rememberAgentCwd(info, storeCwd, cwd);
261
+ return state;
262
+ }
263
+ async consumeRun(state, turn, run) {
264
+ try {
265
+ for await (const message of run.stream()) {
266
+ this.applyStreamMessage(state.info.agentId, turn, run.id, message);
267
+ }
268
+ const result = await run.wait();
269
+ await run.conversation();
270
+ this.applyRunResult(state, turn, run, result);
271
+ }
272
+ catch (error) {
273
+ const message = error instanceof Error ? error.message : String(error);
274
+ turn.status = 'failed';
275
+ turn.error = { message };
276
+ state.info.status = 'error';
277
+ state.info.lastModified = Date.now();
278
+ state.activeRun = undefined;
279
+ this.emit('turn/completed', {
280
+ threadId: state.info.agentId,
281
+ turnId: run.id,
282
+ status: 'failed',
283
+ error: { message },
284
+ });
285
+ this.emit('thread/status/changed', {
286
+ threadId: state.info.agentId,
287
+ status: 'failed',
288
+ error: { message },
289
+ });
290
+ }
291
+ }
292
+ applyStreamMessage(threadId, turn, runId, message) {
293
+ if (message.type === 'assistant') {
294
+ const text = readMessageText(message.message);
295
+ if (!text.trim()) {
296
+ return;
297
+ }
298
+ const item = this.findOrCreateTextItem(turn, 'agentMessage', `${runId}-assistant`);
299
+ const merged = mergeStreamingText(item.text ?? '', text);
300
+ item.text = merged.text;
301
+ if (merged.delta) {
302
+ this.emit('item/agentMessage/delta', {
303
+ threadId,
304
+ itemId: item.id,
305
+ delta: merged.delta,
306
+ });
307
+ }
308
+ return;
309
+ }
310
+ const item = streamMessageToThreadItem(message);
311
+ if (!item) {
312
+ return;
313
+ }
314
+ if (item.type === 'reasoning') {
315
+ const textItem = this.findOrCreateTextItem(turn, 'reasoning', item.id);
316
+ const merged = mergeStreamingText(textItem.text ?? '', item.text ?? '');
317
+ textItem.text = merged.text;
318
+ textItem.status = item.status;
319
+ if (merged.delta) {
320
+ this.emit('item/reasoning/textDelta', {
321
+ threadId,
322
+ itemId: textItem.id,
323
+ delta: merged.delta,
324
+ });
325
+ }
326
+ return;
327
+ }
328
+ if (item.type === 'toolCall') {
329
+ const created = this.upsertToolCallItem(turn, item);
330
+ if (created) {
331
+ this.emit('item/started', {
332
+ threadId,
333
+ item,
334
+ });
335
+ }
336
+ if (isTerminalToolCallStatus(item.status)) {
337
+ this.emit('item/completed', {
338
+ threadId,
339
+ item,
340
+ });
341
+ }
342
+ }
343
+ }
344
+ applyRunResult(state, turn, run, result) {
345
+ if (result.result?.trim()) {
346
+ const assistantItem = this.findAssistantItem(turn, run.id);
347
+ if (assistantItem) {
348
+ assistantItem.text = mergeFinalAssistantText(assistantItem.text ?? '', result.result);
349
+ }
350
+ else {
351
+ turn.items.push({
352
+ type: 'agentMessage',
353
+ id: `${run.id}-assistant`,
354
+ text: result.result,
355
+ });
356
+ }
357
+ }
358
+ const gitItem = runResultToGitThreadItem(run.id, result);
359
+ if (gitItem) {
360
+ this.upsertToolCallItem(turn, gitItem);
361
+ this.emit('item/completed', {
362
+ threadId: state.info.agentId,
363
+ item: gitItem,
364
+ });
365
+ }
366
+ turn.items = orderCompletedCursorTurnItems(turn.items);
367
+ turn.status =
368
+ result.status === 'finished'
369
+ ? 'completed'
370
+ : result.status === 'cancelled'
371
+ ? 'cancelled'
372
+ : 'failed';
373
+ if (turn.status === 'failed' && !turn.error) {
374
+ turn.error = { message: 'Cursor run failed' };
375
+ }
376
+ state.info.status = turn.status === 'failed' ? 'error' : 'finished';
377
+ const assistantText = this.findAssistantItem(turn, run.id)?.text;
378
+ state.info.summary = assistantText ? toPreview(assistantText) : state.info.summary;
379
+ state.info.lastModified = Date.now();
380
+ state.activeRun = undefined;
381
+ this.emit('turn/completed', {
382
+ threadId: state.info.agentId,
383
+ turnId: run.id,
384
+ status: turn.status === 'completed' ? 'completed' : turn.status,
385
+ ...(turn.error ? { error: turn.error } : {}),
386
+ });
387
+ this.emit('thread/status/changed', {
388
+ threadId: state.info.agentId,
389
+ status: turn.status === 'failed' ? 'failed' : 'idle',
390
+ });
391
+ }
392
+ findOrCreateTextItem(turn, type, id) {
393
+ const existing = turn.items.find((item) => item.type === type && item.id === id);
394
+ if (existing) {
395
+ return existing;
396
+ }
397
+ const item = {
398
+ type,
399
+ id,
400
+ text: '',
401
+ };
402
+ turn.items.push(item);
403
+ return item;
404
+ }
405
+ upsertToolCallItem(turn, item) {
406
+ const existing = turn.items.find((entry) => entry.type === 'toolCall' && entry.id === item.id);
407
+ if (!existing) {
408
+ turn.items.push(item);
409
+ return true;
410
+ }
411
+ existing.tool = item.tool;
412
+ existing.status = item.status;
413
+ existing.args = item.args;
414
+ existing.result = item.result;
415
+ existing.truncated = item.truncated;
416
+ return false;
417
+ }
418
+ findAssistantItem(turn, runId) {
419
+ return (turn.items.find((item) => item.type === 'agentMessage' && item.id === `${runId}-assistant`) ?? [...turn.items].reverse().find((item) => item.type === 'agentMessage'));
420
+ }
421
+ requireCwd(requestCwd) {
422
+ const cwd = normalizeString(requestCwd) ?? this.configuredCwd;
423
+ if (!cwd) {
424
+ throw new Error('CURSOR_WORKDIR or per-request cwd is required; no workspace fallback is allowed');
425
+ }
426
+ return cwd;
427
+ }
428
+ resolveKnownThreadWorkspace(threadId, requestCwd) {
429
+ const live = this.liveThreads.get(threadId);
430
+ const cwd = live?.cwd ??
431
+ normalizeString(requestCwd) ??
432
+ this.knownThreadCwds.get(threadId) ??
433
+ this.configuredCwd;
434
+ if (!cwd) {
435
+ throw new Error('CURSOR_WORKDIR or per-request cwd is required; no workspace fallback is allowed');
436
+ }
437
+ return {
438
+ cwd,
439
+ storeCwd: live?.storeCwd ??
440
+ this.knownThreadStoreCwds.get(threadId) ??
441
+ this.knownThreadCwds.get(threadId) ??
442
+ cwd,
443
+ };
444
+ }
445
+ rememberAgentCwd(agent, storeCwd, effectiveCwd) {
446
+ const normalizedStoreCwd = normalizeString(storeCwd) ?? normalizeString(agent.cwd);
447
+ const cwd = normalizeString(effectiveCwd) ?? normalizeString(agent.cwd) ?? normalizedStoreCwd;
448
+ if (!cwd) {
449
+ return;
450
+ }
451
+ this.knownThreadCwds.set(agent.agentId, cwd);
452
+ if (normalizedStoreCwd) {
453
+ this.knownThreadStoreCwds.set(agent.agentId, normalizedStoreCwd);
454
+ }
455
+ }
456
+ listKnownWorkspaceCwds() {
457
+ const cwds = new Set();
458
+ if (this.configuredCwd) {
459
+ cwds.add(this.configuredCwd);
460
+ }
461
+ for (const state of this.liveThreads.values()) {
462
+ cwds.add(state.cwd);
463
+ }
464
+ for (const cwd of this.knownThreadCwds.values()) {
465
+ cwds.add(cwd);
466
+ }
467
+ for (const cwd of this.knownThreadStoreCwds.values()) {
468
+ cwds.add(cwd);
469
+ }
470
+ if (cwds.size === 0) {
471
+ return [this.requireCwd(null)];
472
+ }
473
+ return [...cwds];
474
+ }
475
+ async resolveAgentEffectiveCwd(agent, storeCwd, requestCwd) {
476
+ const agentCwd = normalizeString(agent.cwd);
477
+ const transcriptCwd = await findCursorTranscriptWorkspaceCwd({
478
+ agentId: agent.agentId,
479
+ projectsDir: this.cursorProjectsDir,
480
+ knownCwds: uniqueStrings([
481
+ ...(requestCwd ? [requestCwd] : []),
482
+ ...(agentCwd ? [agentCwd] : []),
483
+ storeCwd,
484
+ ...this.listKnownWorkspaceCwds(),
485
+ ]),
486
+ });
487
+ return transcriptCwd ?? this.knownThreadCwds.get(agent.agentId) ?? agentCwd ?? storeCwd;
488
+ }
489
+ async readPersistedThread(threadId, workspace, apiKey) {
490
+ let lastError;
491
+ for (const storeCwd of this.candidateStoreCwds(threadId, workspace)) {
492
+ try {
493
+ const [rawAgent, messages] = await Promise.all([
494
+ this.driver.getAgent(threadId, { cwd: storeCwd, apiKey }),
495
+ this.driver.listMessages(threadId, { cwd: storeCwd, limit: 1000 }),
496
+ ]);
497
+ const cwd = await this.resolveAgentEffectiveCwd(rawAgent, storeCwd, workspace.cwd);
498
+ return {
499
+ agent: { ...rawAgent, cwd },
500
+ messages,
501
+ cwd,
502
+ storeCwd,
503
+ };
504
+ }
505
+ catch (error) {
506
+ lastError = error;
507
+ }
508
+ }
509
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
510
+ }
511
+ async resumePersistedThread(threadId, workspace, apiKey, configuredModel) {
512
+ let lastError;
513
+ for (const storeCwd of this.candidateStoreCwds(threadId, workspace)) {
514
+ try {
515
+ const [agent, rawInfo, messages, persistedModel] = await Promise.all([
516
+ this.driver.resumeAgent(threadId, {
517
+ cwd: workspace.cwd,
518
+ ...(storeCwd === workspace.cwd ? {} : { storeCwd }),
519
+ apiKey,
520
+ model: configuredModel,
521
+ }),
522
+ this.driver.getAgent(threadId, { cwd: storeCwd, apiKey }),
523
+ this.driver.listMessages(threadId, { cwd: storeCwd, limit: 1000 }),
524
+ this.latestPersistedRunModel(threadId, storeCwd),
525
+ ]);
526
+ const cwd = await this.resolveAgentEffectiveCwd(rawInfo, storeCwd, workspace.cwd);
527
+ return {
528
+ agent,
529
+ info: { ...rawInfo, cwd },
530
+ messages,
531
+ persistedModel,
532
+ cwd,
533
+ storeCwd,
534
+ };
535
+ }
536
+ catch (error) {
537
+ lastError = error;
538
+ }
539
+ }
540
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
541
+ }
542
+ candidateStoreCwds(threadId, workspace) {
543
+ return uniqueStrings([
544
+ workspace.storeCwd,
545
+ this.knownThreadStoreCwds.get(threadId),
546
+ this.configuredCwd,
547
+ workspace.cwd,
548
+ ]);
549
+ }
550
+ requireModel(requestModel) {
551
+ const model = normalizeString(requestModel) ?? this.defaultModel;
552
+ if (!model) {
553
+ throw new Error('CURSOR_MODEL or per-request model is required for local Cursor agents');
554
+ }
555
+ return this.toModelSelection(model);
556
+ }
557
+ requireTurnModel(requestModel, threadModel) {
558
+ const model = normalizeString(requestModel);
559
+ if (model) {
560
+ return this.toModelSelection(model);
561
+ }
562
+ if (threadModel) {
563
+ return threadModel;
564
+ }
565
+ const configured = this.configuredModelOrUndefined();
566
+ if (configured) {
567
+ return configured;
568
+ }
569
+ throw new Error('CURSOR_MODEL, per-request model, or thread model is required for local Cursor agents');
570
+ }
571
+ configuredModelOrUndefined() {
572
+ return this.defaultModel ? this.toModelSelection(this.defaultModel) : undefined;
573
+ }
574
+ async latestPersistedRunModel(threadId, cwd) {
575
+ const result = await this.driver.listRuns(threadId, { cwd, limit: 50 });
576
+ let latest = null;
577
+ for (const [index, run] of result.items.entries()) {
578
+ if (!run.model) {
579
+ continue;
580
+ }
581
+ const createdAt = typeof run.createdAt === 'number' && Number.isFinite(run.createdAt)
582
+ ? run.createdAt
583
+ : index;
584
+ if (!latest ||
585
+ createdAt > latest.createdAt ||
586
+ (createdAt === latest.createdAt && index > latest.index)) {
587
+ latest = {
588
+ index,
589
+ createdAt,
590
+ model: run.model,
591
+ };
592
+ }
593
+ }
594
+ if (!latest) {
595
+ return undefined;
596
+ }
597
+ return latest.model;
598
+ }
599
+ requireApiKey() {
600
+ if (!this.apiKey) {
601
+ throw new Error('CURSOR_API_KEY is required for Cursor SDK operations');
602
+ }
603
+ return this.apiKey;
604
+ }
605
+ async buildUserMessage(text, imagePaths) {
606
+ if (imagePaths.length === 0) {
607
+ return { text };
608
+ }
609
+ const images = [];
610
+ for (const imagePath of imagePaths) {
611
+ const mimeType = inferImageMimeType(imagePath);
612
+ const data = await readFile(imagePath, 'base64').catch((error) => {
613
+ const message = error instanceof Error ? error.message : String(error);
614
+ throw new Error(`failed to read Cursor local image ${imagePath}: ${message}`);
615
+ });
616
+ images.push({ data, mimeType });
617
+ }
618
+ return { text, images };
619
+ }
620
+ buildUserThreadContent(text, imagePaths) {
621
+ return [
622
+ { type: 'text', text },
623
+ ...imagePaths.map((path) => ({ type: 'localImage', path })),
624
+ ];
625
+ }
626
+ toModelSelection(model) {
627
+ return { id: model };
628
+ }
629
+ emit(method, params) {
630
+ this.events.emit('notification', { method, params });
631
+ }
632
+ }
633
+ export function createCursorAppServerFromEnv(env = process.env) {
634
+ const options = {
635
+ runtime: 'local',
636
+ };
637
+ const cwd = normalizeString(env.CURSOR_WORKDIR);
638
+ const apiKey = normalizeString(env.CURSOR_API_KEY);
639
+ const defaultModel = normalizeString(env.CURSOR_MODEL);
640
+ if (cwd) {
641
+ options.cwd = cwd;
642
+ }
643
+ if (apiKey) {
644
+ options.apiKey = apiKey;
645
+ }
646
+ if (defaultModel) {
647
+ options.defaultModel = defaultModel;
648
+ }
649
+ return new CursorAppServer(options);
650
+ }
651
+ function normalizeString(value) {
652
+ const trimmed = value?.trim();
653
+ return trimmed ? trimmed : null;
654
+ }
655
+ function applyCursorModeToPrompt(prompt, mode) {
656
+ if (mode === 'ask') {
657
+ return [
658
+ 'Cursor mode: Ask. Answer questions and explain the codebase without modifying files or running mutating commands. If implementation is needed, describe the change instead of making it.',
659
+ prompt,
660
+ ].join('\n\n');
661
+ }
662
+ if (mode === 'plan') {
663
+ return [
664
+ 'Cursor mode: Plan. Inspect the codebase and propose a concrete plan before implementation. Do not modify files or run mutating commands in this turn.',
665
+ prompt,
666
+ ].join('\n\n');
667
+ }
668
+ return prompt;
669
+ }
670
+ function compareCursorAgentsByUpdatedAtDesc(left, right) {
671
+ const updatedDelta = right.lastModified - left.lastModified;
672
+ return updatedDelta === 0 ? left.agentId.localeCompare(right.agentId) : updatedDelta;
673
+ }
674
+ function uniqueStrings(values) {
675
+ const unique = new Set();
676
+ for (const value of values) {
677
+ const normalized = normalizeString(value);
678
+ if (normalized) {
679
+ unique.add(normalized);
680
+ }
681
+ }
682
+ return [...unique];
683
+ }
684
+ function mergeStreamingText(current, incoming) {
685
+ if (!incoming) {
686
+ return { text: current, delta: '' };
687
+ }
688
+ if (!current) {
689
+ return { text: incoming, delta: incoming };
690
+ }
691
+ if (incoming === current || current.endsWith(incoming)) {
692
+ return { text: current, delta: '' };
693
+ }
694
+ if (incoming.startsWith(current)) {
695
+ return {
696
+ text: incoming,
697
+ delta: incoming.slice(current.length),
698
+ };
699
+ }
700
+ return {
701
+ text: `${current}${incoming}`,
702
+ delta: incoming,
703
+ };
704
+ }
705
+ function mergeFinalAssistantText(current, finalText) {
706
+ if (!current.trim()) {
707
+ return finalText;
708
+ }
709
+ if (current === finalText || current.startsWith(finalText)) {
710
+ return current;
711
+ }
712
+ if (finalText.startsWith(current)) {
713
+ return finalText;
714
+ }
715
+ return finalText;
716
+ }
717
+ function orderCompletedCursorTurnItems(items) {
718
+ return items
719
+ .map((item, index) => ({ item, index }))
720
+ .sort((left, right) => {
721
+ const rankDelta = completedCursorTurnItemRank(left.item) - completedCursorTurnItemRank(right.item);
722
+ return rankDelta === 0 ? left.index - right.index : rankDelta;
723
+ })
724
+ .map((entry) => entry.item);
725
+ }
726
+ function completedCursorTurnItemRank(item) {
727
+ switch (item.type) {
728
+ case 'userMessage':
729
+ return 0;
730
+ case 'toolCall':
731
+ return 1;
732
+ case 'reasoning':
733
+ return 2;
734
+ case 'agentMessage':
735
+ return 3;
736
+ default:
737
+ return 2;
738
+ }
739
+ }
740
+ function isTerminalToolCallStatus(status) {
741
+ const normalized = normalizeString(status)?.toLowerCase();
742
+ return (normalized === 'completed' ||
743
+ normalized === 'complete' ||
744
+ normalized === 'error' ||
745
+ normalized === 'failed');
746
+ }
747
+ function runResultToGitThreadItem(runId, result) {
748
+ const branches = result.git?.branches ?? [];
749
+ if (branches.length === 0) {
750
+ return null;
751
+ }
752
+ return {
753
+ type: 'toolCall',
754
+ id: `${runId}-git`,
755
+ tool: 'git',
756
+ status: 'completed',
757
+ result: {
758
+ branches,
759
+ },
760
+ };
761
+ }
762
+ function inferImageMimeType(path) {
763
+ switch (extname(path).toLowerCase()) {
764
+ case '.png':
765
+ return 'image/png';
766
+ case '.jpg':
767
+ case '.jpeg':
768
+ return 'image/jpeg';
769
+ case '.webp':
770
+ return 'image/webp';
771
+ case '.gif':
772
+ return 'image/gif';
773
+ default:
774
+ throw new Error(`unsupported Cursor local image type: ${path}`);
775
+ }
776
+ }
777
+ export function cursorStreamMessageText(message) {
778
+ return message.type === 'assistant' ? readMessageText(message.message) : '';
779
+ }
780
+ //# sourceMappingURL=appServer.js.map