cursorconnect 0.1.2 → 0.1.4
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.
- package/README.md +5 -4
- package/bridge-runtime/dist/agent-title-match.js +16 -0
- package/bridge-runtime/dist/chat-display-store.d.ts +13 -0
- package/bridge-runtime/dist/chat-display-store.js +29 -0
- package/bridge-runtime/dist/chat-display.d.ts +11 -0
- package/bridge-runtime/dist/chat-display.js +290 -0
- package/bridge-runtime/dist/chat-sync.d.ts +6 -0
- package/bridge-runtime/dist/chat-sync.js +88 -0
- package/bridge-runtime/dist/extract-page.js +99 -3
- package/bridge-runtime/dist/history-pipeline-log.d.ts +16 -0
- package/bridge-runtime/dist/history-pipeline-log.js +29 -0
- package/bridge-runtime/dist/jsonl-index.d.ts +15 -3
- package/bridge-runtime/dist/jsonl-index.js +48 -12
- package/bridge-runtime/dist/message-filter.d.ts +10 -0
- package/bridge-runtime/dist/message-filter.js +65 -5
- package/bridge-runtime/dist/pairing-code.d.ts +3 -0
- package/bridge-runtime/dist/pairing-code.js +17 -0
- package/bridge-runtime/dist/pairing-identity.js +4 -7
- package/bridge-runtime/dist/relay.d.ts +8 -0
- package/bridge-runtime/dist/relay.js +254 -25
- package/bridge-runtime/dist/sidebar-merge.js +2 -2
- package/bridge-runtime/dist/types.d.ts +9 -1
- package/config.env.defaults +3 -0
- package/dist/bridge-dir.js +5 -0
- package/dist/cli-version.js +13 -0
- package/dist/diagnose.js +224 -0
- package/dist/index.js +56 -55
- package/dist/launch.js +45 -13
- package/dist/pairing-code.js +18 -0
- package/dist/pairing-identity.js +3 -6
- package/dist/print-pairing.js +9 -7
- package/dist/relay-config.js +49 -0
- package/dist/semver.js +21 -0
- package/dist/version-check.js +31 -0
- package/package.json +6 -2
- package/version-policy.json +8 -0
|
@@ -4,10 +4,14 @@ import { randomBytes, timingSafeEqual } from 'crypto';
|
|
|
4
4
|
import { basename } from 'path';
|
|
5
5
|
import { readAllowedMediaFile, resolveMediaPathParam } from './media-path.js';
|
|
6
6
|
import { Server as SocketServer } from 'socket.io';
|
|
7
|
+
import { ChatDisplayStore } from './chat-display-store.js';
|
|
8
|
+
import { filterClientDisplayList, prepareChatMessagesForDisplay, } from './chat-display.js';
|
|
9
|
+
import { isChatSyncedWithCursor } from './chat-sync.js';
|
|
7
10
|
import { mergeSidebarWithJsonl } from './sidebar-merge.js';
|
|
8
11
|
import { transcribeAudioBuffer } from './openai-transcribe.js';
|
|
9
12
|
import { extensionForMime, saveUploadedImage } from './image-upload-store.js';
|
|
10
13
|
import { RelayUpstream } from './relay-upstream.js';
|
|
14
|
+
import { bridgePipelineLog, bridgePipelineReportLines, bridgePipelineSnapshot, } from './history-pipeline-log.js';
|
|
11
15
|
export class Relay {
|
|
12
16
|
stateManager;
|
|
13
17
|
commandExecutor;
|
|
@@ -16,12 +20,17 @@ export class Relay {
|
|
|
16
20
|
messageDebugStore;
|
|
17
21
|
domExtractor;
|
|
18
22
|
lastJsonlIndex = { repos: [], updatedAt: 0 };
|
|
23
|
+
indexEmitTimer = null;
|
|
24
|
+
lastIndexBroadcastAt = 0;
|
|
25
|
+
lastSidebarIndexKey = '';
|
|
26
|
+
lastJsonlIndexKey = '';
|
|
19
27
|
config;
|
|
20
28
|
app = express();
|
|
21
29
|
httpServer = createServer(this.app);
|
|
22
30
|
io;
|
|
23
31
|
tokens = new Set();
|
|
24
32
|
upstream = null;
|
|
33
|
+
chatDisplay = new ChatDisplayStore();
|
|
25
34
|
constructor(config, stateManager, commandExecutor, cdpBridge, jsonlIndex, messageDebugStore, domExtractor) {
|
|
26
35
|
this.stateManager = stateManager;
|
|
27
36
|
this.commandExecutor = commandExecutor;
|
|
@@ -32,6 +41,7 @@ export class Relay {
|
|
|
32
41
|
this.config = config;
|
|
33
42
|
this.io = new SocketServer(this.httpServer, {
|
|
34
43
|
cors: { origin: true, credentials: true },
|
|
44
|
+
maxHttpBufferSize: 20e6,
|
|
35
45
|
});
|
|
36
46
|
this.setupHttp();
|
|
37
47
|
this.setupSocket();
|
|
@@ -82,6 +92,91 @@ export class Relay {
|
|
|
82
92
|
lastError: state.lastError,
|
|
83
93
|
});
|
|
84
94
|
});
|
|
95
|
+
this.app.get('/api/agents/index', async (req, res) => {
|
|
96
|
+
if (!this.checkMediaAuth(req)) {
|
|
97
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
try {
|
|
101
|
+
const stale = Date.now() - (this.lastJsonlIndex.updatedAt ?? 0) > 60_000;
|
|
102
|
+
const index = this.lastJsonlIndex.repos.length && !stale
|
|
103
|
+
? this.lastJsonlIndex
|
|
104
|
+
: await this.jsonlIndex.rebuild({ broadcast: false });
|
|
105
|
+
this.lastJsonlIndex = index;
|
|
106
|
+
const merged = mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle);
|
|
107
|
+
const bytes = JSON.stringify(merged).length;
|
|
108
|
+
bridgePipelineLog({
|
|
109
|
+
dir: 'out',
|
|
110
|
+
event: 'http:agents:index',
|
|
111
|
+
bytes,
|
|
112
|
+
detail: `repos=${merged.repos?.length ?? 0}`,
|
|
113
|
+
});
|
|
114
|
+
res.json(merged);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
res.status(500).json({ error: err.message });
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
this.app.get('/api/agents/history', async (req, res) => {
|
|
121
|
+
if (!this.checkMediaAuth(req)) {
|
|
122
|
+
res.status(401).json({ error: 'Unauthorized' });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const agentId = String(req.query.agentId ?? '').trim();
|
|
126
|
+
const title = typeof req.query.title === 'string' ? req.query.title.trim() : undefined;
|
|
127
|
+
const limitRaw = Number(req.query.limit ?? 0);
|
|
128
|
+
const limit = Number.isFinite(limitRaw) && limitRaw > 0 ? limitRaw : undefined;
|
|
129
|
+
const requestId = typeof req.query.requestId === 'string' ? req.query.requestId.trim() : undefined;
|
|
130
|
+
if (!agentId) {
|
|
131
|
+
res.status(400).json({ error: 'agentId required' });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
bridgePipelineLog({
|
|
135
|
+
dir: 'in',
|
|
136
|
+
event: 'http:agents:history',
|
|
137
|
+
requestId,
|
|
138
|
+
agentId,
|
|
139
|
+
detail: `limit=${limit ?? 'all'} title=${title?.slice(0, 32) ?? '-'}`,
|
|
140
|
+
});
|
|
141
|
+
try {
|
|
142
|
+
const history = await this.jsonlIndex.loadHistory(agentId, {
|
|
143
|
+
title,
|
|
144
|
+
composerIdByTitle: this.stateManager.getState().composerIdByTitle,
|
|
145
|
+
limit,
|
|
146
|
+
});
|
|
147
|
+
const messages = this.chatDisplay.applyHistory(agentId, history.messages);
|
|
148
|
+
const body = { ...history, messages, requestId, updatedAt: Date.now() };
|
|
149
|
+
const bytes = JSON.stringify(body).length;
|
|
150
|
+
bridgePipelineLog({
|
|
151
|
+
dir: 'out',
|
|
152
|
+
event: 'http:agents:history',
|
|
153
|
+
requestId,
|
|
154
|
+
agentId,
|
|
155
|
+
bytes,
|
|
156
|
+
msgs: history.messages?.length ?? 0,
|
|
157
|
+
detail: `total=${history.totalMessages ?? '?'}`,
|
|
158
|
+
});
|
|
159
|
+
res.json(body);
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
bridgePipelineLog({
|
|
163
|
+
dir: 'internal',
|
|
164
|
+
event: 'http:agents:history:ERR',
|
|
165
|
+
requestId,
|
|
166
|
+
agentId,
|
|
167
|
+
detail: err.message,
|
|
168
|
+
});
|
|
169
|
+
res.status(500).json({ error: err.message });
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
this.app.get('/api/debug/history-pipeline', (_req, res) => {
|
|
173
|
+
res.json({
|
|
174
|
+
ok: true,
|
|
175
|
+
at: Date.now(),
|
|
176
|
+
lines: bridgePipelineReportLines(),
|
|
177
|
+
entries: bridgePipelineSnapshot(),
|
|
178
|
+
});
|
|
179
|
+
});
|
|
85
180
|
this.app.get('/media/file', (req, res) => {
|
|
86
181
|
if (!this.checkMediaAuth(req)) {
|
|
87
182
|
res.status(401).json({ error: 'Unauthorized' });
|
|
@@ -212,37 +307,108 @@ export class Relay {
|
|
|
212
307
|
this.io.on('connection', (socket) => this.onConnect(socket));
|
|
213
308
|
}
|
|
214
309
|
broadcast(event, payload) {
|
|
310
|
+
if (event === 'agents:history' || event === 'agent:history') {
|
|
311
|
+
const h = payload;
|
|
312
|
+
const bytes = JSON.stringify(payload ?? {}).length;
|
|
313
|
+
bridgePipelineLog({
|
|
314
|
+
dir: 'out',
|
|
315
|
+
event: `broadcast:${event}`,
|
|
316
|
+
requestId: h?.requestId,
|
|
317
|
+
agentId: h?.agentId,
|
|
318
|
+
bytes,
|
|
319
|
+
msgs: h?.messages?.length ?? 0,
|
|
320
|
+
detail: `upstream=${this.upstream?.connected ?? false}`,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
215
323
|
this.io.emit(event, payload);
|
|
216
324
|
this.upstream?.emit(event, payload);
|
|
217
325
|
}
|
|
326
|
+
prepareStateMessages(raw, patch) {
|
|
327
|
+
const state = { ...this.stateManager.getState(), ...patch };
|
|
328
|
+
for (const [agentId, meta] of this.jsonlIndex.getSubscribedAgents()) {
|
|
329
|
+
if (isChatSyncedWithCursor(agentId, meta.title, state)) {
|
|
330
|
+
return this.chatDisplay.mergeLiveForAgent(agentId, raw);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return filterClientDisplayList(prepareChatMessagesForDisplay(raw));
|
|
334
|
+
}
|
|
335
|
+
withDisplayState(payload) {
|
|
336
|
+
if (!payload.messages?.length)
|
|
337
|
+
return payload;
|
|
338
|
+
return {
|
|
339
|
+
...payload,
|
|
340
|
+
messages: this.prepareStateMessages(payload.messages, payload),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
218
343
|
wireEvents() {
|
|
219
344
|
this.stateManager.on('state:full', (state) => {
|
|
220
|
-
this.broadcast('state:full', state);
|
|
345
|
+
this.broadcast('state:full', this.withDisplayState(state));
|
|
221
346
|
this.emitAgentsIndex();
|
|
222
347
|
});
|
|
223
348
|
this.stateManager.on('state:patch', (patch) => {
|
|
224
|
-
this.broadcast('state:patch', patch);
|
|
225
|
-
if (patch.sidebarRepos || patch.composerIdByTitle)
|
|
226
|
-
|
|
349
|
+
this.broadcast('state:patch', this.withDisplayState(patch));
|
|
350
|
+
if (patch.sidebarRepos || patch.composerIdByTitle) {
|
|
351
|
+
const key = JSON.stringify([patch.sidebarRepos, patch.composerIdByTitle]);
|
|
352
|
+
if (key !== this.lastSidebarIndexKey) {
|
|
353
|
+
this.lastSidebarIndexKey = key;
|
|
354
|
+
this.emitAgentsIndex();
|
|
355
|
+
}
|
|
356
|
+
}
|
|
227
357
|
});
|
|
228
358
|
this.jsonlIndex.on('agents:index', (index) => {
|
|
359
|
+
const key = JSON.stringify(index.repos?.map((r) => [r.id, r.agents.length]));
|
|
360
|
+
if (key === this.lastJsonlIndexKey)
|
|
361
|
+
return;
|
|
362
|
+
this.lastJsonlIndexKey = key;
|
|
229
363
|
this.lastJsonlIndex = index;
|
|
230
364
|
this.emitAgentsIndex();
|
|
231
365
|
});
|
|
232
|
-
this.jsonlIndex.on('agent:history', (history) =>
|
|
366
|
+
this.jsonlIndex.on('agent:history', (history) => {
|
|
367
|
+
const messages = this.chatDisplay.applyHistory(history.agentId, history.messages, {
|
|
368
|
+
mergeWithCache: true,
|
|
369
|
+
});
|
|
370
|
+
this.broadcast('agent:history', { ...history, messages });
|
|
371
|
+
});
|
|
233
372
|
}
|
|
234
|
-
emitAgentsIndex() {
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
373
|
+
emitAgentsIndex(force = false) {
|
|
374
|
+
const send = () => {
|
|
375
|
+
const merged = mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, this.lastJsonlIndex, this.stateManager.getState().composerIdByTitle);
|
|
376
|
+
this.broadcast('agents:index', merged);
|
|
377
|
+
this.lastIndexBroadcastAt = Date.now();
|
|
378
|
+
};
|
|
379
|
+
if (force) {
|
|
380
|
+
if (this.indexEmitTimer)
|
|
381
|
+
clearTimeout(this.indexEmitTimer);
|
|
382
|
+
this.indexEmitTimer = null;
|
|
383
|
+
send();
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const elapsed = Date.now() - this.lastIndexBroadcastAt;
|
|
387
|
+
if (elapsed >= 60_000) {
|
|
388
|
+
send();
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (this.indexEmitTimer)
|
|
392
|
+
return;
|
|
393
|
+
this.indexEmitTimer = setTimeout(() => {
|
|
394
|
+
this.indexEmitTimer = null;
|
|
395
|
+
send();
|
|
396
|
+
}, 60_000 - elapsed);
|
|
238
397
|
}
|
|
239
|
-
|
|
240
|
-
this.broadcast
|
|
241
|
-
void this.jsonlIndex.rebuild().then((index) => {
|
|
398
|
+
refreshAgentsIndex(force = false) {
|
|
399
|
+
void this.jsonlIndex.rebuild({ broadcast: false }).then((index) => {
|
|
242
400
|
this.lastJsonlIndex = index;
|
|
243
|
-
this.
|
|
401
|
+
this.emitAgentsIndex(force);
|
|
244
402
|
});
|
|
245
403
|
}
|
|
404
|
+
pushFullStateToRemote() {
|
|
405
|
+
this.broadcast('state:full', this.withDisplayState(this.stateManager.getState()));
|
|
406
|
+
if (this.lastJsonlIndex.updatedAt > 0) {
|
|
407
|
+
this.emitAgentsIndex(true);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
this.refreshAgentsIndex(true);
|
|
411
|
+
}
|
|
246
412
|
async trySwitchWindowForAgent(agentId) {
|
|
247
413
|
const projectDir = this.jsonlIndex.findProjectDirForAgent(agentId);
|
|
248
414
|
if (!projectDir)
|
|
@@ -273,11 +439,19 @@ export class Relay {
|
|
|
273
439
|
this.dispatchClientEvent(event, args, reply);
|
|
274
440
|
}
|
|
275
441
|
onConnect(socket) {
|
|
276
|
-
socket.emit('state:full', this.stateManager.getState());
|
|
277
|
-
|
|
278
|
-
this.lastJsonlIndex = index;
|
|
442
|
+
socket.emit('state:full', this.withDisplayState(this.stateManager.getState()));
|
|
443
|
+
const sendIndex = (index) => {
|
|
279
444
|
socket.emit('agents:index', mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle));
|
|
280
|
-
}
|
|
445
|
+
};
|
|
446
|
+
if (this.lastJsonlIndex.updatedAt > 0) {
|
|
447
|
+
sendIndex(this.lastJsonlIndex);
|
|
448
|
+
}
|
|
449
|
+
else {
|
|
450
|
+
void this.jsonlIndex.rebuild({ broadcast: false }).then((index) => {
|
|
451
|
+
this.lastJsonlIndex = index;
|
|
452
|
+
sendIndex(index);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
281
455
|
const reply = (ev, payload) => {
|
|
282
456
|
socket.emit(ev, payload);
|
|
283
457
|
};
|
|
@@ -297,7 +471,7 @@ export class Relay {
|
|
|
297
471
|
await this.runAgentsFocus(data, reply);
|
|
298
472
|
});
|
|
299
473
|
socket.on('agents:refresh', () => {
|
|
300
|
-
|
|
474
|
+
this.refreshAgentsIndex(true);
|
|
301
475
|
});
|
|
302
476
|
socket.on('disconnect', () => {
|
|
303
477
|
/* subscriptions stay global — one phone session typical */
|
|
@@ -325,7 +499,7 @@ export class Relay {
|
|
|
325
499
|
return;
|
|
326
500
|
}
|
|
327
501
|
if (event === 'agents:refresh') {
|
|
328
|
-
|
|
502
|
+
this.refreshAgentsIndex(true);
|
|
329
503
|
}
|
|
330
504
|
}
|
|
331
505
|
async runCommand(payload, reply) {
|
|
@@ -353,12 +527,65 @@ export class Relay {
|
|
|
353
527
|
this.domExtractor.pollNow();
|
|
354
528
|
}
|
|
355
529
|
}
|
|
356
|
-
async runAgentsHistory({ agentId, title }, reply) {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
530
|
+
async runAgentsHistory({ agentId, title, requestId, limit, }, reply) {
|
|
531
|
+
const t0 = Date.now();
|
|
532
|
+
const viaUpstream = this.upstream?.connected ?? false;
|
|
533
|
+
const socketLimit = limit && limit > 0 ? limit : 15;
|
|
534
|
+
console.log(`[relay] agents:history req agentId=${agentId} title=${title?.slice(0, 48) ?? '-'} limit=${socketLimit} rid=${requestId ?? '-'} upstream=${viaUpstream}`);
|
|
535
|
+
this.jsonlIndex.historyReplyInFlight.add(agentId);
|
|
536
|
+
bridgePipelineLog({
|
|
537
|
+
dir: 'in',
|
|
538
|
+
event: 'agents:history:req',
|
|
539
|
+
requestId,
|
|
540
|
+
agentId,
|
|
541
|
+
detail: `limit=${socketLimit} title=${title?.slice(0, 32) ?? '-'} upstream=${viaUpstream}`,
|
|
360
542
|
});
|
|
361
|
-
|
|
543
|
+
try {
|
|
544
|
+
const history = await this.jsonlIndex.loadHistory(agentId, {
|
|
545
|
+
title,
|
|
546
|
+
composerIdByTitle: this.stateManager.getState().composerIdByTitle,
|
|
547
|
+
limit: socketLimit,
|
|
548
|
+
});
|
|
549
|
+
const messages = this.chatDisplay.applyHistory(agentId, history.messages);
|
|
550
|
+
const ms = Date.now() - t0;
|
|
551
|
+
const bytes = JSON.stringify(messages).length;
|
|
552
|
+
console.log(`[relay] agents:history ok agentId=${history.agentId} msgs=${history.messages?.length ?? 0}/${history.totalMessages ?? '?'} bytes=${bytes} ms=${ms} rid=${requestId ?? '-'}`);
|
|
553
|
+
bridgePipelineLog({
|
|
554
|
+
dir: 'out',
|
|
555
|
+
event: 'agents:history:reply',
|
|
556
|
+
requestId,
|
|
557
|
+
agentId: history.agentId,
|
|
558
|
+
bytes,
|
|
559
|
+
msgs: history.messages?.length ?? 0,
|
|
560
|
+
detail: `total=${history.totalMessages ?? '?'} ms=${ms}`,
|
|
561
|
+
});
|
|
562
|
+
reply('agents:history', {
|
|
563
|
+
...history,
|
|
564
|
+
messages,
|
|
565
|
+
requestId,
|
|
566
|
+
updatedAt: Date.now(),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
catch (err) {
|
|
570
|
+
console.error(`[relay] agents:history fail agentId=${agentId} ms=${Date.now() - t0} rid=${requestId ?? '-'}:`, err.message);
|
|
571
|
+
bridgePipelineLog({
|
|
572
|
+
dir: 'internal',
|
|
573
|
+
event: 'agents:history:fail',
|
|
574
|
+
requestId,
|
|
575
|
+
agentId,
|
|
576
|
+
detail: err.message,
|
|
577
|
+
});
|
|
578
|
+
reply('agents:history', {
|
|
579
|
+
agentId,
|
|
580
|
+
messages: [],
|
|
581
|
+
totalMessages: 0,
|
|
582
|
+
requestId,
|
|
583
|
+
updatedAt: Date.now(),
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
finally {
|
|
587
|
+
this.jsonlIndex.historyReplyInFlight.delete(agentId);
|
|
588
|
+
}
|
|
362
589
|
}
|
|
363
590
|
async runAgentsSubscribe({ agentId, title, focus, }) {
|
|
364
591
|
if (!agentId)
|
|
@@ -379,8 +606,10 @@ export class Relay {
|
|
|
379
606
|
}
|
|
380
607
|
}
|
|
381
608
|
runAgentsUnsubscribe({ agentId }) {
|
|
382
|
-
if (agentId)
|
|
609
|
+
if (agentId) {
|
|
383
610
|
this.jsonlIndex.unsubscribe(agentId);
|
|
611
|
+
this.chatDisplay.clearAgent(agentId);
|
|
612
|
+
}
|
|
384
613
|
}
|
|
385
614
|
async runAgentsFocus({ agentId, title }, reply) {
|
|
386
615
|
if (!agentId)
|
|
@@ -3,7 +3,7 @@ export { normalizeAgentTitle } from './agent-title-match.js';
|
|
|
3
3
|
/** Prefer Cursor DOM sidebar order/names; attach JSONL ids when titles match. */
|
|
4
4
|
export function mergeSidebarWithJsonl(sidebarRepos, jsonl, composerIdByTitle) {
|
|
5
5
|
if (!sidebarRepos?.length)
|
|
6
|
-
return jsonl;
|
|
6
|
+
return { ...jsonl, listSource: 'jsonl' };
|
|
7
7
|
const byTitle = new Map();
|
|
8
8
|
for (const repo of jsonl.repos) {
|
|
9
9
|
for (const agent of repo.agents) {
|
|
@@ -43,5 +43,5 @@ export function mergeSidebarWithJsonl(sidebarRepos, jsonl, composerIdByTitle) {
|
|
|
43
43
|
};
|
|
44
44
|
}),
|
|
45
45
|
}));
|
|
46
|
-
return { repos, updatedAt: Date.now() };
|
|
46
|
+
return { repos, updatedAt: Date.now(), listSource: 'sidebar' };
|
|
47
47
|
}
|
|
@@ -229,15 +229,23 @@ export interface RepoGroup {
|
|
|
229
229
|
export interface AgentsIndex {
|
|
230
230
|
repos: RepoGroup[];
|
|
231
231
|
updatedAt: number;
|
|
232
|
+
/** `sidebar` = порядок/названия из CDP; `jsonl` = архив на диске (не как в Cursor). */
|
|
233
|
+
listSource?: 'sidebar' | 'jsonl';
|
|
232
234
|
}
|
|
233
235
|
export interface HistoryMessage {
|
|
234
236
|
role: 'user' | 'assistant' | 'system';
|
|
235
237
|
text: string;
|
|
238
|
+
html?: string;
|
|
239
|
+
/** Absolute paths from JSONL `<image_files>` (served via GET /media/file). */
|
|
240
|
+
images?: string[];
|
|
236
241
|
ts?: number;
|
|
237
242
|
}
|
|
238
243
|
export interface AgentHistory {
|
|
239
244
|
agentId: string;
|
|
240
|
-
|
|
245
|
+
/** Display-ready chat rows (normalized, deduped, filtered on bridge). */
|
|
246
|
+
messages: ChatMessage[];
|
|
241
247
|
updatedAt?: number;
|
|
248
|
+
requestId?: string;
|
|
249
|
+
totalMessages?: number;
|
|
242
250
|
}
|
|
243
251
|
export type ExtractedPageState = Omit<CursorState, 'connected' | 'windows' | 'activeWindowId' | 'windowSnapshots' | 'updatedAt'>;
|
package/dist/bridge-dir.js
CHANGED
|
@@ -65,6 +65,11 @@ export function ensureUserConfig() {
|
|
|
65
65
|
copyFileSync(devEnv, USER_CONFIG_ENV);
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
+
const bundled = join(resolvePackageRoot(), 'config.env.defaults');
|
|
69
|
+
if (existsSync(bundled)) {
|
|
70
|
+
copyFileSync(bundled, USER_CONFIG_ENV);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
68
73
|
const template = join(resolvePackageRoot(), 'bridge-runtime', '.env.example');
|
|
69
74
|
if (existsSync(template)) {
|
|
70
75
|
copyFileSync(template, USER_CONFIG_ENV);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { dirname, join } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json');
|
|
5
|
+
export const CLI_VERSION = (() => {
|
|
6
|
+
try {
|
|
7
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
8
|
+
return pkg.version?.trim() || '0.0.0';
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return '0.0.0';
|
|
12
|
+
}
|
|
13
|
+
})();
|
package/dist/diagnose.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import { resolveRelayConfig, DEFAULT_RELAY_URL } from './relay-config.js';
|
|
3
|
+
import { loadPairingIdentity } from './pairing-identity.js';
|
|
4
|
+
function loadRelayUrl() {
|
|
5
|
+
return resolveRelayConfig().relayUrl || DEFAULT_RELAY_URL;
|
|
6
|
+
}
|
|
7
|
+
function socketProbe(relayUrl, auth, transports, expectIndex) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
const socket = io(relayUrl, {
|
|
10
|
+
transports,
|
|
11
|
+
auth,
|
|
12
|
+
reconnection: false,
|
|
13
|
+
timeout: 12_000,
|
|
14
|
+
});
|
|
15
|
+
let indexRepos = null;
|
|
16
|
+
const transportLabel = transports.join('+');
|
|
17
|
+
const done = (ok, detail) => {
|
|
18
|
+
socket.disconnect();
|
|
19
|
+
resolve({ ok, detail });
|
|
20
|
+
};
|
|
21
|
+
socket.on('connect', () => {
|
|
22
|
+
if (!expectIndex) {
|
|
23
|
+
done(true, `connect id=${socket.id} transport=${socket.io.engine.transport.name}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
socket.emit('agents:refresh');
|
|
27
|
+
});
|
|
28
|
+
socket.on('agents:index', (idx) => {
|
|
29
|
+
indexRepos = idx?.repos?.length ?? 0;
|
|
30
|
+
done(true, `connect transport=${socket.io.engine.transport.name} agents:index repos=${indexRepos}`);
|
|
31
|
+
});
|
|
32
|
+
socket.on('connect_error', (err) => {
|
|
33
|
+
done(false, `${transportLabel}: ${err.message}`);
|
|
34
|
+
});
|
|
35
|
+
setTimeout(() => {
|
|
36
|
+
if (socket.connected && !expectIndex)
|
|
37
|
+
return;
|
|
38
|
+
if (socket.connected && expectIndex) {
|
|
39
|
+
done(false, `${transportLabel}: connect ok, agents:index timeout (wrong room?)`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
done(false, `${transportLabel}: timeout`);
|
|
43
|
+
}, expectIndex ? 10_000 : 8_000);
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function historyProbe(relayUrl, auth, agentId, title) {
|
|
47
|
+
return new Promise((resolve) => {
|
|
48
|
+
const socket = io(relayUrl, {
|
|
49
|
+
transports: ['websocket', 'polling'],
|
|
50
|
+
auth,
|
|
51
|
+
reconnection: false,
|
|
52
|
+
timeout: 15_000,
|
|
53
|
+
});
|
|
54
|
+
const t0 = Date.now();
|
|
55
|
+
const done = (ok, detail) => {
|
|
56
|
+
socket.disconnect();
|
|
57
|
+
resolve({ ok, detail });
|
|
58
|
+
};
|
|
59
|
+
socket.on('connect', () => {
|
|
60
|
+
socket.emit('agents:history', { agentId, title });
|
|
61
|
+
});
|
|
62
|
+
socket.on('agents:history', (h) => {
|
|
63
|
+
done(true, `${h.messages?.length ?? 0} msgs for ${h.agentId ?? agentId} in ${Date.now() - t0}ms`);
|
|
64
|
+
});
|
|
65
|
+
socket.on('connect_error', (err) => done(false, err.message));
|
|
66
|
+
setTimeout(() => done(false, 'agents:history timeout (20s)'), 20_000);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
export async function runDiagnose() {
|
|
70
|
+
const identity = loadPairingIdentity();
|
|
71
|
+
const relayUrl = (loadRelayUrl() || 'https://cc.fanpay.online').replace(/\/$/, '');
|
|
72
|
+
const checks = [];
|
|
73
|
+
if (!identity) {
|
|
74
|
+
return {
|
|
75
|
+
at: new Date().toISOString(),
|
|
76
|
+
relayUrl,
|
|
77
|
+
roomId: '',
|
|
78
|
+
checks: [
|
|
79
|
+
{
|
|
80
|
+
id: 'identity',
|
|
81
|
+
ok: false,
|
|
82
|
+
detail: 'Нет ~/.cursorconnect/identity.json',
|
|
83
|
+
hint: 'cursorconnect start',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
const roomId = identity.roomId;
|
|
89
|
+
const token = identity.clientToken;
|
|
90
|
+
// 1 Relay HTTP
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(`${relayUrl}/health`);
|
|
93
|
+
const j = (await res.json());
|
|
94
|
+
checks.push({
|
|
95
|
+
id: 'relay.health',
|
|
96
|
+
ok: Boolean(j.ok),
|
|
97
|
+
detail: `ok=${j.ok} connector=${j.connector} peers=${JSON.stringify(j.peers)}`,
|
|
98
|
+
hint: j.connector ? undefined : 'cursorconnect start на Mac',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
checks.push({
|
|
103
|
+
id: 'relay.health',
|
|
104
|
+
ok: false,
|
|
105
|
+
detail: e.message,
|
|
106
|
+
hint: 'Проверьте RELAY_URL и интернет',
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// 2 Room diagnostics API
|
|
110
|
+
try {
|
|
111
|
+
const q = new URLSearchParams({ roomId, token });
|
|
112
|
+
const res = await fetch(`${relayUrl}/api/diagnostics?${q}`);
|
|
113
|
+
const j = (await res.json());
|
|
114
|
+
const ok = Boolean(j.ok);
|
|
115
|
+
checks.push({
|
|
116
|
+
id: 'relay.room',
|
|
117
|
+
ok: ok && Boolean(j.connectorInRoom) && Boolean(j.tokenRegistered),
|
|
118
|
+
detail: JSON.stringify(j),
|
|
119
|
+
hint: !j.connectorInRoom
|
|
120
|
+
? 'Bridge не в этой комнате на relay'
|
|
121
|
+
: !j.tokenRegistered
|
|
122
|
+
? 'Токен не зарегистрирован — перезапустите cursorconnect start'
|
|
123
|
+
: !j.tokenAccepted
|
|
124
|
+
? 'Токен не в whitelist комнаты'
|
|
125
|
+
: undefined,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
checks.push({
|
|
130
|
+
id: 'relay.room',
|
|
131
|
+
ok: false,
|
|
132
|
+
detail: e.message,
|
|
133
|
+
hint: 'Обновите relay: npm run deploy:relay',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// 3 Local bridge
|
|
137
|
+
try {
|
|
138
|
+
const res = await fetch('http://127.0.0.1:3847/health');
|
|
139
|
+
const j = (await res.json());
|
|
140
|
+
checks.push({
|
|
141
|
+
id: 'bridge.local',
|
|
142
|
+
ok: Boolean(j.ok),
|
|
143
|
+
detail: `ok=${j.ok} cdp=${j.cdp}`,
|
|
144
|
+
hint: j.cdp ? undefined : 'Cursor с --remote-debugging-port=9222',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
catch (e) {
|
|
148
|
+
checks.push({
|
|
149
|
+
id: 'bridge.local',
|
|
150
|
+
ok: false,
|
|
151
|
+
detail: e.message,
|
|
152
|
+
hint: 'cursorconnect start',
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
// 4 Pairing code (не вызываем POST /api/pair — код одноразовый)
|
|
156
|
+
const code = identity.pairingCode?.replace(/[^A-Z0-9]/gi, '').toUpperCase() ?? '';
|
|
157
|
+
const codeValid = code.length === 6 && identity.pairingCodeExpiresAt > Date.now();
|
|
158
|
+
if (codeValid) {
|
|
159
|
+
const secLeft = Math.max(0, Math.floor((identity.pairingCodeExpiresAt - Date.now()) / 1000));
|
|
160
|
+
checks.push({
|
|
161
|
+
id: 'pair.code',
|
|
162
|
+
ok: true,
|
|
163
|
+
detail: `active ${code} (~${secLeft}s)`,
|
|
164
|
+
hint: 'Введите в app до истечения; повторный start — новый код',
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
checks.push({
|
|
169
|
+
id: 'pair.code',
|
|
170
|
+
ok: false,
|
|
171
|
+
detail: 'Код истёк или отсутствует',
|
|
172
|
+
hint: 'cursorconnect start',
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const auth = { token, roomId };
|
|
176
|
+
const ws = await socketProbe(relayUrl, auth, ['websocket'], true);
|
|
177
|
+
checks.push({
|
|
178
|
+
id: 'socket.ws+room',
|
|
179
|
+
ok: ws.ok,
|
|
180
|
+
detail: ws.detail,
|
|
181
|
+
hint: ws.ok ? undefined : 'Телефон: нужен polling+websocket в app',
|
|
182
|
+
});
|
|
183
|
+
const poll = await socketProbe(relayUrl, auth, ['polling'], true);
|
|
184
|
+
checks.push({
|
|
185
|
+
id: 'socket.poll+room',
|
|
186
|
+
ok: poll.ok,
|
|
187
|
+
detail: poll.detail,
|
|
188
|
+
hint: poll.ok ? undefined : 'Relay/nginx блокирует long-polling?',
|
|
189
|
+
});
|
|
190
|
+
const wrongRoom = await socketProbe(relayUrl, { token, roomId: 'default' }, ['websocket', 'polling'], true);
|
|
191
|
+
checks.push({
|
|
192
|
+
id: 'socket.wrong-room',
|
|
193
|
+
ok: !wrongRoom.ok,
|
|
194
|
+
detail: wrongRoom.detail,
|
|
195
|
+
hint: wrongRoom.ok
|
|
196
|
+
? 'App подключается к room=default вместо вашего roomId'
|
|
197
|
+
: undefined,
|
|
198
|
+
});
|
|
199
|
+
const history = await historyProbe(relayUrl, auth, 'sidebar-0', 'Git repository creation and project deployment');
|
|
200
|
+
checks.push({
|
|
201
|
+
id: 'socket.history',
|
|
202
|
+
ok: history.ok,
|
|
203
|
+
detail: history.detail,
|
|
204
|
+
hint: history.ok ? undefined : 'connectorInRoom false или bridge не отвечает — cursorconnect start',
|
|
205
|
+
});
|
|
206
|
+
return { at: new Date().toISOString(), relayUrl, roomId, checks };
|
|
207
|
+
}
|
|
208
|
+
export function formatDiagnoseReport(report) {
|
|
209
|
+
const lines = [
|
|
210
|
+
`CursorConnect diagnose @ ${report.at}`,
|
|
211
|
+
`relay=${report.relayUrl}`,
|
|
212
|
+
`room=${report.roomId}`,
|
|
213
|
+
'',
|
|
214
|
+
];
|
|
215
|
+
for (const c of report.checks) {
|
|
216
|
+
lines.push(`${c.ok ? 'PASS' : 'FAIL'} ${c.id}`);
|
|
217
|
+
lines.push(` ${c.detail}`);
|
|
218
|
+
if (c.hint)
|
|
219
|
+
lines.push(` → ${c.hint}`);
|
|
220
|
+
}
|
|
221
|
+
const failed = report.checks.filter((c) => !c.ok).length;
|
|
222
|
+
lines.push('', failed === 0 ? 'Итог: все проверки пройдены' : `Итог: ${failed} проблем(а)`);
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|