cursorconnect 0.1.2 → 0.1.5
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 +6 -5
- 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/big-code.js +36 -5
- package/dist/bridge-dir.js +6 -1
- package/dist/cli-version.js +13 -0
- package/dist/diagnose.js +224 -0
- package/dist/index.js +56 -92
- package/dist/launch.js +52 -14
- package/dist/pairing-code.js +18 -0
- package/dist/pairing-identity.js +6 -8
- package/dist/pairing-ttl.js +3 -0
- package/dist/print-pairing.js +18 -25
- package/dist/relay-config.js +49 -0
- package/dist/repo-root.js +2 -2
- package/dist/semver.js +21 -0
- package/dist/version-check.js +31 -0
- package/package.json +7 -3
- package/version-policy.json +8 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const RING = [];
|
|
2
|
+
const MAX = 150;
|
|
3
|
+
export function bridgePipelineLog(entry) {
|
|
4
|
+
const row = {
|
|
5
|
+
layer: 'bridge',
|
|
6
|
+
at: entry.at ?? Date.now(),
|
|
7
|
+
dir: entry.dir,
|
|
8
|
+
event: entry.event,
|
|
9
|
+
requestId: entry.requestId,
|
|
10
|
+
agentId: entry.agentId,
|
|
11
|
+
bytes: entry.bytes,
|
|
12
|
+
msgs: entry.msgs,
|
|
13
|
+
detail: entry.detail,
|
|
14
|
+
};
|
|
15
|
+
RING.push(row);
|
|
16
|
+
while (RING.length > MAX)
|
|
17
|
+
RING.shift();
|
|
18
|
+
const rid = row.requestId ? ` rid=${row.requestId}` : '';
|
|
19
|
+
console.log(`[history-pipe] bridge ${row.dir} ${row.event}${rid} agent=${row.agentId ?? '-'} bytes=${row.bytes ?? '-'} msgs=${row.msgs ?? '-'} ${row.detail ?? ''}`.trim());
|
|
20
|
+
}
|
|
21
|
+
export function bridgePipelineSnapshot() {
|
|
22
|
+
return [...RING];
|
|
23
|
+
}
|
|
24
|
+
export function bridgePipelineReportLines() {
|
|
25
|
+
return RING.map((r) => {
|
|
26
|
+
const t = new Date(r.at).toISOString().slice(11, 23);
|
|
27
|
+
return `${t} bridge ${r.dir} ${r.event} rid=${r.requestId ?? '-'} agent=${r.agentId ?? '-'} bytes=${r.bytes ?? '-'} msgs=${r.msgs ?? '-'} ${r.detail ?? ''}`.trim();
|
|
28
|
+
});
|
|
29
|
+
}
|
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
import { EventEmitter } from 'events';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AgentsIndex, HistoryMessage } from './types.js';
|
|
3
3
|
export declare class JsonlIndex extends EventEmitter {
|
|
4
4
|
private projectsDir;
|
|
5
5
|
private debounceTimer;
|
|
6
6
|
private subscribed;
|
|
7
7
|
private mtimeByAgent;
|
|
8
8
|
private pollTimer;
|
|
9
|
+
/** Skip poll `agent:history` while serving explicit `agents:history` (avoids relay race). */
|
|
10
|
+
readonly historyReplyInFlight: Set<string>;
|
|
9
11
|
constructor(projectsDir: string);
|
|
10
12
|
start(): void;
|
|
11
13
|
stop(): void;
|
|
12
14
|
subscribe(agentId: string, title?: string): void;
|
|
13
15
|
unsubscribe(agentId: string): void;
|
|
16
|
+
getSubscribedAgents(): ReadonlyMap<string, {
|
|
17
|
+
title?: string;
|
|
18
|
+
}>;
|
|
14
19
|
private pollSubscribed;
|
|
15
20
|
private emitHistoryForAgent;
|
|
16
21
|
private emitHistoryForFile;
|
|
17
22
|
private scheduleRebuild;
|
|
18
|
-
rebuild(
|
|
23
|
+
rebuild(opts?: {
|
|
24
|
+
broadcast?: boolean;
|
|
25
|
+
}): Promise<AgentsIndex>;
|
|
19
26
|
findProjectDirForAgent(agentId: string): string | null;
|
|
20
27
|
loadHistory(agentId: string, opts?: {
|
|
21
28
|
title?: string;
|
|
22
29
|
composerIdByTitle?: Record<string, string>;
|
|
23
|
-
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): Promise<{
|
|
32
|
+
agentId: string;
|
|
33
|
+
messages: HistoryMessage[];
|
|
34
|
+
totalMessages: number;
|
|
35
|
+
}>;
|
|
24
36
|
}
|
|
@@ -4,13 +4,16 @@ import { basename, join } from 'path';
|
|
|
4
4
|
import { createInterface } from 'readline';
|
|
5
5
|
import chokidar from 'chokidar';
|
|
6
6
|
import { resolveJsonlFilePath } from './agent-title-match.js';
|
|
7
|
-
import { cleanUserText, filterAssistantJsonlParts, isNoiseChatText, } from './message-filter.js';
|
|
7
|
+
import { cleanUserText, parseUserImagePaths, filterAssistantJsonlParts, isNoiseChatText, stripJsonlRedactionArtifacts, } from './message-filter.js';
|
|
8
|
+
import { bridgePipelineLog } from './history-pipeline-log.js';
|
|
8
9
|
export class JsonlIndex extends EventEmitter {
|
|
9
10
|
projectsDir;
|
|
10
11
|
debounceTimer = null;
|
|
11
12
|
subscribed = new Map();
|
|
12
13
|
mtimeByAgent = new Map();
|
|
13
14
|
pollTimer = null;
|
|
15
|
+
/** Skip poll `agent:history` while serving explicit `agents:history` (avoids relay race). */
|
|
16
|
+
historyReplyInFlight = new Set();
|
|
14
17
|
constructor(projectsDir) {
|
|
15
18
|
super();
|
|
16
19
|
this.projectsDir = projectsDir;
|
|
@@ -44,6 +47,9 @@ export class JsonlIndex extends EventEmitter {
|
|
|
44
47
|
this.subscribed.delete(agentId);
|
|
45
48
|
this.mtimeByAgent.delete(agentId);
|
|
46
49
|
}
|
|
50
|
+
getSubscribedAgents() {
|
|
51
|
+
return this.subscribed;
|
|
52
|
+
}
|
|
47
53
|
async pollSubscribed() {
|
|
48
54
|
for (const [agentId, meta] of this.subscribed) {
|
|
49
55
|
const filePath = resolveJsonlFilePath(this.projectsDir, agentId, {
|
|
@@ -57,7 +63,7 @@ export class JsonlIndex extends EventEmitter {
|
|
|
57
63
|
if (prev === mtime)
|
|
58
64
|
continue;
|
|
59
65
|
this.mtimeByAgent.set(agentId, mtime);
|
|
60
|
-
await this.emitHistoryForFile(filePath);
|
|
66
|
+
await this.emitHistoryForFile(filePath, agentId);
|
|
61
67
|
}
|
|
62
68
|
catch {
|
|
63
69
|
/* skip */
|
|
@@ -83,6 +89,15 @@ export class JsonlIndex extends EventEmitter {
|
|
|
83
89
|
if (!fileAgentId || fileAgentId.includes('/'))
|
|
84
90
|
return;
|
|
85
91
|
const agentId = replyAgentId ?? fileAgentId;
|
|
92
|
+
if (this.historyReplyInFlight.has(agentId) || this.historyReplyInFlight.has(fileAgentId)) {
|
|
93
|
+
bridgePipelineLog({
|
|
94
|
+
dir: 'internal',
|
|
95
|
+
event: 'poll:agent:history:SKIP',
|
|
96
|
+
agentId,
|
|
97
|
+
detail: `inFlight file=${fileAgentId.slice(0, 8)}`,
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
86
101
|
try {
|
|
87
102
|
const messages = await parseJsonlFile(filePath);
|
|
88
103
|
this.mtimeByAgent.set(agentId, statSync(filePath).mtimeMs);
|
|
@@ -97,11 +112,13 @@ export class JsonlIndex extends EventEmitter {
|
|
|
97
112
|
clearTimeout(this.debounceTimer);
|
|
98
113
|
this.debounceTimer = setTimeout(() => void this.rebuild(), 500);
|
|
99
114
|
}
|
|
100
|
-
async rebuild() {
|
|
115
|
+
async rebuild(opts) {
|
|
116
|
+
const broadcast = opts?.broadcast !== false;
|
|
101
117
|
const repos = new Map();
|
|
102
118
|
if (!existsSync(this.projectsDir)) {
|
|
103
119
|
const empty = { repos: [], updatedAt: Date.now() };
|
|
104
|
-
|
|
120
|
+
if (broadcast)
|
|
121
|
+
this.emit('agents:index', empty);
|
|
105
122
|
return empty;
|
|
106
123
|
}
|
|
107
124
|
for (const entry of readdirSync(this.projectsDir, { withFileTypes: true })) {
|
|
@@ -126,8 +143,10 @@ export class JsonlIndex extends EventEmitter {
|
|
|
126
143
|
const index = {
|
|
127
144
|
repos: [...repos.values()].sort((a, b) => a.name.localeCompare(b.name)),
|
|
128
145
|
updatedAt: Date.now(),
|
|
146
|
+
listSource: 'jsonl',
|
|
129
147
|
};
|
|
130
|
-
|
|
148
|
+
if (broadcast)
|
|
149
|
+
this.emit('agents:index', index);
|
|
131
150
|
return index;
|
|
132
151
|
}
|
|
133
152
|
findProjectDirForAgent(agentId) {
|
|
@@ -142,10 +161,15 @@ export class JsonlIndex extends EventEmitter {
|
|
|
142
161
|
async loadHistory(agentId, opts) {
|
|
143
162
|
const filePath = resolveJsonlFilePath(this.projectsDir, agentId, opts);
|
|
144
163
|
if (!filePath) {
|
|
145
|
-
return { agentId, messages: [] };
|
|
164
|
+
return { agentId, messages: [], totalMessages: 0 };
|
|
146
165
|
}
|
|
147
166
|
const messages = await parseJsonlFile(filePath);
|
|
148
|
-
|
|
167
|
+
const totalMessages = messages.length;
|
|
168
|
+
const limit = opts?.limit;
|
|
169
|
+
const trimmed = limit && limit > 0 && messages.length > limit
|
|
170
|
+
? messages.slice(-limit)
|
|
171
|
+
: messages;
|
|
172
|
+
return { agentId, messages: trimmed, totalMessages };
|
|
149
173
|
}
|
|
150
174
|
}
|
|
151
175
|
function shouldIgnoreWatchPath(p) {
|
|
@@ -207,7 +231,7 @@ function peekFirstUserMessage(filePath) {
|
|
|
207
231
|
if (row.role !== 'user')
|
|
208
232
|
continue;
|
|
209
233
|
const text = row.message?.content?.find((c) => c.type === 'text')?.text ?? '';
|
|
210
|
-
const cleaned = text
|
|
234
|
+
const cleaned = cleanUserText(text);
|
|
211
235
|
if (cleaned)
|
|
212
236
|
return cleaned.slice(0, 200);
|
|
213
237
|
}
|
|
@@ -272,8 +296,10 @@ async function parseJsonlFile(filePath) {
|
|
|
272
296
|
try {
|
|
273
297
|
const row = JSON.parse(line);
|
|
274
298
|
const parts = [];
|
|
299
|
+
const rawParts = [];
|
|
275
300
|
for (const c of row.message?.content ?? []) {
|
|
276
301
|
if (c.type === 'text' && c.text) {
|
|
302
|
+
rawParts.push(c.text);
|
|
277
303
|
const t = cleanUserText(c.text);
|
|
278
304
|
if (t)
|
|
279
305
|
parts.push(t);
|
|
@@ -281,10 +307,20 @@ async function parseJsonlFile(filePath) {
|
|
|
281
307
|
// tool_use, tool_result — ignore
|
|
282
308
|
}
|
|
283
309
|
if (row.role === 'user') {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
310
|
+
const raw = rawParts.join('\n');
|
|
311
|
+
const images = parseUserImagePaths(raw);
|
|
312
|
+
const text = stripJsonlRedactionArtifacts(cleanUserText(parts.join('\n').trim()));
|
|
313
|
+
const hasImages = images.length > 0;
|
|
314
|
+
if ((!text || isNoiseChatText(text)) && !hasImages)
|
|
315
|
+
continue;
|
|
316
|
+
if (text && isNoiseChatText(text) && !hasImages)
|
|
317
|
+
continue;
|
|
318
|
+
messages.push({
|
|
319
|
+
role: 'user',
|
|
320
|
+
text: text || (hasImages ? 'Изображение' : ''),
|
|
321
|
+
images: hasImages ? images : undefined,
|
|
322
|
+
ts: lineNo,
|
|
323
|
+
});
|
|
288
324
|
continue;
|
|
289
325
|
}
|
|
290
326
|
if (row.role === 'assistant') {
|
|
@@ -9,11 +9,21 @@ export declare const WORKING_STATUS_LINE: RegExp;
|
|
|
9
9
|
/** Short DOM status line only — not a sentence inside a chat message. */
|
|
10
10
|
export declare function matchBackgroundWorkStatusText(text: string): string | undefined;
|
|
11
11
|
export declare function isBackgroundWorkStatusText(text: string): boolean;
|
|
12
|
+
/** Chat line that is only passive-work status (not prose mentioning it). */
|
|
13
|
+
export declare function isPassiveStatusChatLine(text: string): boolean;
|
|
12
14
|
/** DOM/system lines that mean the agent is still busy */
|
|
13
15
|
export declare function textImpliesAgentWorking(text: string): boolean;
|
|
16
|
+
/** Cursor JSONL redacts tool/thinking tails as `[REDACTED]` — not shown in live DOM. */
|
|
17
|
+
export declare function stripJsonlRedactionArtifacts(text: string): string;
|
|
14
18
|
export declare function isNoiseChatText(text: string): boolean;
|
|
19
|
+
/** Chain-of-thought / tool trace rows — not user-facing answers (DOM + JSONL). */
|
|
20
|
+
export declare function isAssistantReflectionText(text: string): boolean;
|
|
15
21
|
/** Short agent status worth showing (not tool log). */
|
|
16
22
|
export declare function isMeaningfulAssistantText(text: string): boolean;
|
|
23
|
+
/** Paths from Cursor `<image_files>` blocks (JSONL + DOM text). */
|
|
24
|
+
export declare function parseUserImagePaths(text: string): string[];
|
|
25
|
+
/** Cursor injects image metadata into user bubbles — hide it in the app. */
|
|
26
|
+
export declare function stripUserImageMetadata(text: string): string;
|
|
17
27
|
/** Keep in sync with `cleanUserText` in `extract-page.ts` (CDP runs in-page). */
|
|
18
28
|
export declare function cleanUserText(text: string): string;
|
|
19
29
|
export declare function filterAssistantJsonlParts(parts: string[]): string[];
|
|
@@ -22,12 +22,28 @@ export function matchBackgroundWorkStatusText(text) {
|
|
|
22
22
|
export function isBackgroundWorkStatusText(text) {
|
|
23
23
|
return matchBackgroundWorkStatusText(text) !== undefined;
|
|
24
24
|
}
|
|
25
|
+
/** Chat line that is only passive-work status (not prose mentioning it). */
|
|
26
|
+
export function isPassiveStatusChatLine(text) {
|
|
27
|
+
const t = text.trim().replace(/\s+/g, ' ');
|
|
28
|
+
if (!t || t.length > 80 || t.includes('?'))
|
|
29
|
+
return false;
|
|
30
|
+
if (BACKGROUND_WORK_STATUS.test(t) || WORKING_STATUS_LINE.test(t))
|
|
31
|
+
return true;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
25
34
|
/** DOM/system lines that mean the agent is still busy */
|
|
26
35
|
export function textImpliesAgentWorking(text) {
|
|
27
36
|
return isBackgroundWorkStatusText(text);
|
|
28
37
|
}
|
|
38
|
+
/** Cursor JSONL redacts tool/thinking tails as `[REDACTED]` — not shown in live DOM. */
|
|
39
|
+
export function stripJsonlRedactionArtifacts(text) {
|
|
40
|
+
return text
|
|
41
|
+
.replace(/(?:\n|\r\n)*\[REDACTED\]\s*$/g, '')
|
|
42
|
+
.replace(/^\[REDACTED\]\s*$/g, '')
|
|
43
|
+
.trim();
|
|
44
|
+
}
|
|
29
45
|
export function isNoiseChatText(text) {
|
|
30
|
-
const t = text.trim().replace(/\s+/g, ' ');
|
|
46
|
+
const t = stripJsonlRedactionArtifacts(text).trim().replace(/\s+/g, ' ');
|
|
31
47
|
if (!t || t.length < 2)
|
|
32
48
|
return true;
|
|
33
49
|
if (NOISE_LINE.test(t))
|
|
@@ -56,10 +72,29 @@ export function isNoiseChatText(text) {
|
|
|
56
72
|
return true;
|
|
57
73
|
return false;
|
|
58
74
|
}
|
|
75
|
+
/** Chain-of-thought / tool trace rows — not user-facing answers (DOM + JSONL). */
|
|
76
|
+
export function isAssistantReflectionText(text) {
|
|
77
|
+
const t = stripJsonlRedactionArtifacts(text).trim().replace(/\s+/g, ' ');
|
|
78
|
+
if (!t || isNoiseChatText(t))
|
|
79
|
+
return true;
|
|
80
|
+
if (/^Thought\s*for\s*\d/i.test(t) && t.length < 160)
|
|
81
|
+
return true;
|
|
82
|
+
if (/^(Exploring|Grepped|Searched|Listed|Read |Ran |Edited |Loading|Planning|Using image)/i.test(t)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
const toolHits = t.match(/\b(Explored|Grepped|Searched|Listed|Read )\b/gi);
|
|
86
|
+
if (toolHits && toolHits.length >= 2)
|
|
87
|
+
return true;
|
|
88
|
+
if (t.length > 160 &&
|
|
89
|
+
/\b(state\.messages|flat-index|mapKeyedChildren|humanEl|extract-page|userMessagesEquivalent)\b/i.test(t)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
59
94
|
/** Short agent status worth showing (not tool log). */
|
|
60
95
|
export function isMeaningfulAssistantText(text) {
|
|
61
96
|
const t = text.trim();
|
|
62
|
-
if (!t || isNoiseChatText(t))
|
|
97
|
+
if (!t || isNoiseChatText(t) || isAssistantReflectionText(t))
|
|
63
98
|
return false;
|
|
64
99
|
// Markdown prose / explanation
|
|
65
100
|
if (t.length >= 20 && /\s/.test(t))
|
|
@@ -69,15 +104,40 @@ export function isMeaningfulAssistantText(text) {
|
|
|
69
104
|
return true;
|
|
70
105
|
return t.length >= 50;
|
|
71
106
|
}
|
|
107
|
+
/** Paths from Cursor `<image_files>` blocks (JSONL + DOM text). */
|
|
108
|
+
export function parseUserImagePaths(text) {
|
|
109
|
+
const paths = [];
|
|
110
|
+
const scope = text.match(/<image_files>[\s\S]*?<\/image_files>/i)?.[0] ?? text;
|
|
111
|
+
const re = /\d+\.\s+(\/?[^\s\n]+?\.(?:png|jpe?g|gif|webp))/gi;
|
|
112
|
+
let m;
|
|
113
|
+
while ((m = re.exec(scope))) {
|
|
114
|
+
const p = m[1]?.trim();
|
|
115
|
+
if (p && !paths.includes(p))
|
|
116
|
+
paths.push(p);
|
|
117
|
+
}
|
|
118
|
+
return paths;
|
|
119
|
+
}
|
|
120
|
+
/** Cursor injects image metadata into user bubbles — hide it in the app. */
|
|
121
|
+
export function stripUserImageMetadata(text) {
|
|
122
|
+
return text
|
|
123
|
+
.replace(/^\[Image\]\s*$/gim, '')
|
|
124
|
+
.replace(/<image_files>[\s\S]*?<\/image_files>/gi, '')
|
|
125
|
+
.replace(/(?:^|\n)\s*The following images? (?:were |has been )?provid(?:ed|ied)[^\n]*(?:\n\s*\d+\.\s*[^\n]+)*/gi, '\n')
|
|
126
|
+
.replace(/(?:^|\n)\s*These images can be copied for use in other locations\.?\s*/gi, '\n')
|
|
127
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
128
|
+
.trim();
|
|
129
|
+
}
|
|
72
130
|
/** Keep in sync with `cleanUserText` in `extract-page.ts` (CDP runs in-page). */
|
|
73
131
|
export function cleanUserText(text) {
|
|
74
|
-
return text
|
|
132
|
+
return stripUserImageMetadata(text
|
|
75
133
|
.replace(/<user_query>\s*/gi, '')
|
|
76
134
|
.replace(/<\/user_query>/gi, '')
|
|
77
135
|
.replace(/([.!?…])(\d+\.\s*)/g, '$1\n$2')
|
|
78
136
|
.replace(/([^\n\d\s])(\d+\.\s+)/g, '$1\n$2')
|
|
79
|
-
.trim();
|
|
137
|
+
.trim());
|
|
80
138
|
}
|
|
81
139
|
export function filterAssistantJsonlParts(parts) {
|
|
82
|
-
return parts
|
|
140
|
+
return parts
|
|
141
|
+
.map((p) => stripJsonlRedactionArtifacts(p))
|
|
142
|
+
.filter((p) => isMeaningfulAssistantText(p));
|
|
83
143
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
export const PAIRING_CODE_LENGTH = 6;
|
|
3
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
|
|
4
|
+
export function generatePairingCode() {
|
|
5
|
+
const bytes = randomBytes(PAIRING_CODE_LENGTH);
|
|
6
|
+
let out = '';
|
|
7
|
+
for (let i = 0; i < PAIRING_CODE_LENGTH; i++) {
|
|
8
|
+
out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
9
|
+
}
|
|
10
|
+
return out;
|
|
11
|
+
}
|
|
12
|
+
export function normalizePairingCode(raw) {
|
|
13
|
+
const code = raw.replace(/[^a-zA-Z0-9]/g, '').toUpperCase();
|
|
14
|
+
if (code.length !== PAIRING_CODE_LENGTH)
|
|
15
|
+
return null;
|
|
16
|
+
return code;
|
|
17
|
+
}
|
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from 'crypto';
|
|
2
|
+
import { generatePairingCode } from './pairing-code.js';
|
|
2
3
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
3
4
|
import { homedir, hostname } from 'os';
|
|
4
5
|
import { join } from 'path';
|
|
5
6
|
const DIR = join(homedir(), '.cursorconnect');
|
|
6
7
|
const FILE = join(DIR, 'identity.json');
|
|
7
|
-
function formatPairingCode() {
|
|
8
|
-
const n = randomBytes(3).readUIntBE(0, 3) % 1_000_000;
|
|
9
|
-
return String(n).padStart(6, '0');
|
|
10
|
-
}
|
|
11
8
|
export function pairingIdentityPath() {
|
|
12
9
|
return FILE;
|
|
13
10
|
}
|
|
@@ -35,7 +32,7 @@ export function ensurePairingIdentity(machineLabel) {
|
|
|
35
32
|
roomId: randomUUID(),
|
|
36
33
|
clientToken: randomBytes(32).toString('hex'),
|
|
37
34
|
machineLabel: machineLabel?.trim() || defaultMachineLabel(),
|
|
38
|
-
pairingCode:
|
|
35
|
+
pairingCode: generatePairingCode(),
|
|
39
36
|
pairingCodeExpiresAt: Date.now() + 10 * 60_000,
|
|
40
37
|
createdAt: now,
|
|
41
38
|
updatedAt: now,
|
|
@@ -47,7 +44,7 @@ export function refreshPairingCode(identity, machineLabel) {
|
|
|
47
44
|
const next = {
|
|
48
45
|
...identity,
|
|
49
46
|
machineLabel: machineLabel?.trim() || identity.machineLabel,
|
|
50
|
-
pairingCode:
|
|
47
|
+
pairingCode: generatePairingCode(),
|
|
51
48
|
pairingCodeExpiresAt: Date.now() + 10 * 60_000,
|
|
52
49
|
updatedAt: new Date().toISOString(),
|
|
53
50
|
};
|
|
@@ -58,7 +55,7 @@ export function rotateClientToken(identity) {
|
|
|
58
55
|
const next = {
|
|
59
56
|
...identity,
|
|
60
57
|
clientToken: randomBytes(32).toString('hex'),
|
|
61
|
-
pairingCode:
|
|
58
|
+
pairingCode: generatePairingCode(),
|
|
62
59
|
pairingCodeExpiresAt: Date.now() + 10 * 60_000,
|
|
63
60
|
updatedAt: new Date().toISOString(),
|
|
64
61
|
};
|
|
@@ -13,12 +13,17 @@ export declare class Relay {
|
|
|
13
13
|
private messageDebugStore;
|
|
14
14
|
private domExtractor;
|
|
15
15
|
private lastJsonlIndex;
|
|
16
|
+
private indexEmitTimer;
|
|
17
|
+
private lastIndexBroadcastAt;
|
|
18
|
+
private lastSidebarIndexKey;
|
|
19
|
+
private lastJsonlIndexKey;
|
|
16
20
|
private config;
|
|
17
21
|
private app;
|
|
18
22
|
private httpServer;
|
|
19
23
|
private io;
|
|
20
24
|
private tokens;
|
|
21
25
|
private upstream;
|
|
26
|
+
private readonly chatDisplay;
|
|
22
27
|
constructor(config: ServerConfig, stateManager: StateManager, commandExecutor: CommandExecutor, cdpBridge: CDPBridge, jsonlIndex: JsonlIndex, messageDebugStore: MessageDebugStore, domExtractor: DOMExtractor);
|
|
23
28
|
listen(): Promise<void>;
|
|
24
29
|
private get authEnabled();
|
|
@@ -26,8 +31,11 @@ export declare class Relay {
|
|
|
26
31
|
private setupHttp;
|
|
27
32
|
private setupSocket;
|
|
28
33
|
private broadcast;
|
|
34
|
+
private prepareStateMessages;
|
|
35
|
+
private withDisplayState;
|
|
29
36
|
private wireEvents;
|
|
30
37
|
private emitAgentsIndex;
|
|
38
|
+
private refreshAgentsIndex;
|
|
31
39
|
private pushFullStateToRemote;
|
|
32
40
|
private trySwitchWindowForAgent;
|
|
33
41
|
private handleRemoteClientEvent;
|