cursorconnect 0.1.8 → 0.1.10

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 (37) hide show
  1. package/bridge-runtime/.env.example +2 -0
  2. package/bridge-runtime/connector-version.json +1 -1
  3. package/bridge-runtime/dist/agent-completion-push.d.ts +18 -6
  4. package/bridge-runtime/dist/agent-completion-push.js +186 -41
  5. package/bridge-runtime/dist/agent-completion-readiness.d.ts +19 -0
  6. package/bridge-runtime/dist/agent-completion-readiness.js +42 -0
  7. package/bridge-runtime/dist/chat-display-store.d.ts +32 -7
  8. package/bridge-runtime/dist/chat-display-store.js +96 -21
  9. package/bridge-runtime/dist/chat-display.d.ts +36 -0
  10. package/bridge-runtime/dist/chat-display.js +287 -24
  11. package/bridge-runtime/dist/chat-sync.d.ts +3 -1
  12. package/bridge-runtime/dist/chat-sync.js +20 -0
  13. package/bridge-runtime/dist/debug-chats-page.d.ts +1 -1
  14. package/bridge-runtime/dist/debug-chats-page.js +148 -26
  15. package/bridge-runtime/dist/dom-transcript-store.d.ts +2 -0
  16. package/bridge-runtime/dist/dom-transcript-store.js +17 -2
  17. package/bridge-runtime/dist/extract-page.js +5 -4
  18. package/bridge-runtime/dist/lenta-capture.d.ts +46 -0
  19. package/bridge-runtime/dist/lenta-capture.js +146 -0
  20. package/bridge-runtime/dist/lenta-debug.d.ts +42 -0
  21. package/bridge-runtime/dist/lenta-debug.js +221 -0
  22. package/bridge-runtime/dist/lenta-delivery.d.ts +3 -0
  23. package/bridge-runtime/dist/lenta-delivery.js +10 -0
  24. package/bridge-runtime/dist/lenta-seq-journal.d.ts +48 -0
  25. package/bridge-runtime/dist/lenta-seq-journal.js +109 -0
  26. package/bridge-runtime/dist/message-filter.d.ts +5 -0
  27. package/bridge-runtime/dist/message-filter.js +4 -0
  28. package/bridge-runtime/dist/relay.d.ts +37 -3
  29. package/bridge-runtime/dist/relay.js +557 -51
  30. package/bridge-runtime/dist/types.d.ts +9 -4
  31. package/dist/bridge-build.js +50 -0
  32. package/dist/index.js +9 -6
  33. package/dist/launch.js +5 -1
  34. package/dist/run-service.js +10 -4
  35. package/dist/startup-check.js +6 -0
  36. package/package.json +1 -1
  37. package/version-policy.json +1 -1
@@ -0,0 +1,146 @@
1
+ import { copyFileSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { messagesFingerprint } from './lenta-seq-journal.js';
5
+ import { resolveJsonlFilePath } from './agent-title-match.js';
6
+ const CAPTURE_ROOT = join(homedir(), '.cursorconnect', 'capture');
7
+ export function captureConfigPath() {
8
+ return join(CAPTURE_ROOT, 'config.json');
9
+ }
10
+ export function readCaptureConfig() {
11
+ const path = captureConfigPath();
12
+ if (!existsSync(path))
13
+ return null;
14
+ try {
15
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
16
+ if (!raw.enabled || !raw.agentId?.trim())
17
+ return null;
18
+ return { ...raw, agentId: raw.agentId.trim() };
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ export function writeCaptureConfig(cfg) {
25
+ mkdirSync(CAPTURE_ROOT, { recursive: true });
26
+ writeFileSync(captureConfigPath(), `${JSON.stringify(cfg, null, 2)}\n`);
27
+ }
28
+ export function clearCaptureConfig() {
29
+ writeCaptureConfig({ enabled: false, agentId: '' });
30
+ }
31
+ export function captureSessionDir(agentId) {
32
+ return join(CAPTURE_ROOT, 'sessions', agentId);
33
+ }
34
+ export class LentaCaptureSession {
35
+ agentId;
36
+ step = 0;
37
+ lastSig = '';
38
+ constructor(agentId) {
39
+ this.agentId = agentId;
40
+ const dir = captureSessionDir(agentId);
41
+ mkdirSync(dir, { recursive: true });
42
+ const manifestPath = join(dir, 'manifest.jsonl');
43
+ if (!existsSync(manifestPath)) {
44
+ writeFileSync(manifestPath, `${JSON.stringify({
45
+ startedAt: new Date().toISOString(),
46
+ agentId,
47
+ note: 'Full JSONL+DOM snapshots per bridge event',
48
+ })}\n`);
49
+ }
50
+ }
51
+ shouldCapture(targetAgentId) {
52
+ return targetAgentId === this.agentId;
53
+ }
54
+ record(payload) {
55
+ const sig = messagesFingerprint(payload.messages);
56
+ if (sig === this.lastSig && payload.reason !== 'jsonl_file') {
57
+ return null;
58
+ }
59
+ this.lastSig = sig;
60
+ this.step += 1;
61
+ const stepId = `${String(this.step).padStart(5, '0')}-${payload.reason}`;
62
+ const dir = join(captureSessionDir(this.agentId), stepId);
63
+ mkdirSync(dir, { recursive: true });
64
+ const meta = {
65
+ at: new Date().toISOString(),
66
+ step: this.step,
67
+ stepId,
68
+ agentId: this.agentId,
69
+ reason: payload.reason,
70
+ source: payload.source,
71
+ emitSeq: payload.emitSeq,
72
+ seqHeld: payload.seqHeld,
73
+ transition: payload.transition,
74
+ jsonlRowCount: payload.jsonlRowCount,
75
+ counts: {
76
+ messages: payload.messages.length,
77
+ history: payload.historyMessages.length,
78
+ live: payload.liveMessages.length,
79
+ domRaw: payload.domRaw?.length ?? 0,
80
+ domViewport: payload.domViewport?.length ?? 0,
81
+ jsonlRaw: payload.jsonlRawRows?.length ?? 0,
82
+ },
83
+ messagesSig: sig.slice(0, 500),
84
+ activeComposerId: payload.activeComposerId,
85
+ agentWorking: payload.agentWorking,
86
+ jsonlFile: payload.jsonlFilePath,
87
+ };
88
+ writeFileSync(join(dir, 'meta.json'), `${JSON.stringify(meta, null, 2)}\n`);
89
+ writeFileSync(join(dir, 'messages.json'), `${JSON.stringify(payload.messages, null, 2)}\n`);
90
+ writeFileSync(join(dir, 'historyMessages.json'), `${JSON.stringify(payload.historyMessages, null, 2)}\n`);
91
+ writeFileSync(join(dir, 'liveMessages.json'), `${JSON.stringify(payload.liveMessages, null, 2)}\n`);
92
+ if (payload.domRaw?.length) {
93
+ writeFileSync(join(dir, 'domRaw.json'), `${JSON.stringify(payload.domRaw, null, 2)}\n`);
94
+ }
95
+ if (payload.domViewport?.length) {
96
+ writeFileSync(join(dir, 'domViewport.json'), `${JSON.stringify(payload.domViewport, null, 2)}\n`);
97
+ }
98
+ if (payload.jsonlRawRows?.length) {
99
+ writeFileSync(join(dir, 'jsonl.rows.json'), `${JSON.stringify(payload.jsonlRawRows, null, 2)}\n`);
100
+ }
101
+ if (payload.jsonlFilePath && existsSync(payload.jsonlFilePath)) {
102
+ try {
103
+ copyFileSync(payload.jsonlFilePath, join(dir, 'transcript.jsonl'));
104
+ }
105
+ catch {
106
+ /* non-fatal */
107
+ }
108
+ }
109
+ const manifestLine = JSON.stringify(meta);
110
+ writeFileSync(join(captureSessionDir(this.agentId), 'manifest.jsonl'), manifestLine + '\n', { flag: 'a' });
111
+ bridgeCaptureLog(stepId, meta.counts, payload.reason);
112
+ return dir;
113
+ }
114
+ }
115
+ function bridgeCaptureLog(stepId, counts, reason) {
116
+ console.log(`[lenta-capture] ${stepId} reason=${reason} msgs=${counts.messages} hist=${counts.history} live=${counts.live} domRaw=${counts.domRaw}`);
117
+ }
118
+ let session = null;
119
+ let sessionAgentId = '';
120
+ export function getLentaCaptureSession(agentId) {
121
+ const cfg = readCaptureConfig();
122
+ if (!cfg || cfg.agentId !== agentId)
123
+ return null;
124
+ if (!session || sessionAgentId !== agentId) {
125
+ session = new LentaCaptureSession(agentId);
126
+ sessionAgentId = agentId;
127
+ }
128
+ return session;
129
+ }
130
+ export function captureLentaIfEnabled(agentId, payload) {
131
+ const cap = getLentaCaptureSession(agentId);
132
+ if (!cap)
133
+ return;
134
+ cap.record(payload);
135
+ }
136
+ export function resolveAgentJsonlPath(projectsDir, agentId, opts) {
137
+ return resolveJsonlFilePath(projectsDir, agentId, opts) ?? undefined;
138
+ }
139
+ export function listCaptureSteps(agentId) {
140
+ const dir = captureSessionDir(agentId);
141
+ if (!existsSync(dir))
142
+ return [];
143
+ return readdirSync(dir)
144
+ .filter((n) => /^\d{5}-/.test(n))
145
+ .sort();
146
+ }
@@ -0,0 +1,42 @@
1
+ import type { ChatMessage } from './types.js';
2
+ export type LentaRowPreview = {
3
+ source: string;
4
+ role: string;
5
+ flat: number | null;
6
+ domSeq: number | null;
7
+ id: string;
8
+ len: number;
9
+ head: string;
10
+ };
11
+ export type LentaInvariantIssue = {
12
+ type: string;
13
+ detail?: string;
14
+ dom?: LentaRowPreview;
15
+ bridge?: LentaRowPreview;
16
+ skipped?: string[];
17
+ index?: number;
18
+ };
19
+ export declare function normPreviewText(t: string | undefined): string;
20
+ export declare function previewRow(m: ChatMessage, source: string): LentaRowPreview;
21
+ /** `messages` must equal `[...historyMessages, ...liveMessages]` (socket/HTTP payload). */
22
+ export declare function checkPayloadCompose(messages: ChatMessage[], historyMessages: ChatMessage[], liveMessages: ChatMessage[]): LentaInvariantIssue[];
23
+ /** In-memory store: `messages` = jsonlHistory + domOverlay. */
24
+ export declare function checkStoreCompose(jsonlHistory: ChatMessage[], domOverlay: ChatMessage[], messages: ChatMessage[]): LentaInvariantIssue[];
25
+ export declare function checkMonotonicOrder(messages: ChatMessage[], label: string): LentaInvariantIssue[];
26
+ /** DOM overlay must not repeat a turn already in JSONL archive. */
27
+ export declare function checkOverlayDuplicatesArchive(jsonlHistory: ChatMessage[], domOverlay: ChatMessage[]): LentaInvariantIssue[];
28
+ export declare function checkOverlayAfterArchive(jsonlHistory: ChatMessage[], domOverlay: ChatMessage[]): LentaInvariantIssue[];
29
+ /** When not synced with Cursor, DOM overlay for subscribed chat should stay empty. */
30
+ export declare function checkSyncGate(synced: boolean, domOverlay: ChatMessage[], domRawCount: number): LentaInvariantIssue[];
31
+ export declare function rowSimilar(a: LentaRowPreview, b: LentaRowPreview): boolean;
32
+ /** DOM viewport vs full lenta — match each viewport row to lenta from the end (avoids duplicate-text false gaps). */
33
+ export declare function compareDomTailToLenta(domRows: LentaRowPreview[], lentaRows: LentaRowPreview[], tail: number, opts?: {
34
+ hasDomOverlay?: boolean;
35
+ hasJsonlArchive?: boolean;
36
+ agentWorking?: boolean;
37
+ }): {
38
+ issues: LentaInvariantIssue[];
39
+ domTail: LentaRowPreview[];
40
+ lentaTail: LentaRowPreview[];
41
+ };
42
+ export declare function checkApiMatchesStore(apiMessages: ChatMessage[], storeMessages: ChatMessage[]): LentaInvariantIssue[];
@@ -0,0 +1,221 @@
1
+ import { archiveCoversOverlay, messageOrderKey } from './chat-display.js';
2
+ export function normPreviewText(t) {
3
+ return String(t ?? '')
4
+ .replace(/\s+/g, ' ')
5
+ .trim()
6
+ .slice(0, 400);
7
+ }
8
+ export function previewRow(m, source) {
9
+ const text = normPreviewText(m.text);
10
+ return {
11
+ source,
12
+ role: m.role ?? '?',
13
+ flat: m.flatIndex ?? null,
14
+ domSeq: m.domSeq ?? null,
15
+ id: (m.id ?? '').slice(0, 28),
16
+ len: text.length,
17
+ head: text.slice(0, 72),
18
+ };
19
+ }
20
+ function rowFingerprint(m) {
21
+ const text = normPreviewText(m.text);
22
+ return `${m.role}|${m.id ?? ''}|${text.length}|${text.slice(0, 64)}`;
23
+ }
24
+ function rowsFingerprint(rows) {
25
+ return rows.map(rowFingerprint);
26
+ }
27
+ /** `messages` must equal `[...historyMessages, ...liveMessages]` (socket/HTTP payload). */
28
+ export function checkPayloadCompose(messages, historyMessages, liveMessages) {
29
+ const issues = [];
30
+ const expectedLen = historyMessages.length + liveMessages.length;
31
+ if (messages.length !== expectedLen) {
32
+ issues.push({
33
+ type: 'PAYLOAD_COMPOSE_LEN',
34
+ detail: `messages=${messages.length} history+live=${expectedLen} (${historyMessages.length}+${liveMessages.length})`,
35
+ });
36
+ }
37
+ const composed = [...historyMessages, ...liveMessages];
38
+ const a = rowsFingerprint(messages);
39
+ const b = rowsFingerprint(composed);
40
+ const n = Math.min(a.length, b.length);
41
+ for (let i = 0; i < n; i++) {
42
+ if (a[i] !== b[i]) {
43
+ issues.push({
44
+ type: 'PAYLOAD_COMPOSE_ROW',
45
+ index: i,
46
+ detail: `messages[${i}] !== concat(history,live)[${i}]`,
47
+ });
48
+ break;
49
+ }
50
+ }
51
+ if (a.length !== b.length && !issues.some((x) => x.type === 'PAYLOAD_COMPOSE_LEN')) {
52
+ issues.push({
53
+ type: 'PAYLOAD_COMPOSE_ROW',
54
+ detail: `fingerprint len ${a.length} vs ${b.length}`,
55
+ });
56
+ }
57
+ return issues;
58
+ }
59
+ /** In-memory store: `messages` = jsonlHistory + domOverlay. */
60
+ export function checkStoreCompose(jsonlHistory, domOverlay, messages) {
61
+ const issues = [];
62
+ const expectedLen = jsonlHistory.length + domOverlay.length;
63
+ if (messages.length !== expectedLen) {
64
+ issues.push({
65
+ type: 'STORE_COMPOSE_LEN',
66
+ detail: `messages=${messages.length} jsonl+overlay=${expectedLen}`,
67
+ });
68
+ }
69
+ const composed = [...jsonlHistory, ...domOverlay];
70
+ const a = rowsFingerprint(messages);
71
+ const b = rowsFingerprint(composed);
72
+ for (let i = 0; i < Math.min(a.length, b.length); i++) {
73
+ if (a[i] !== b[i]) {
74
+ issues.push({
75
+ type: 'STORE_COMPOSE_ROW',
76
+ index: i,
77
+ detail: `messages[${i}] !== jsonl+overlay[${i}]`,
78
+ });
79
+ break;
80
+ }
81
+ }
82
+ return issues;
83
+ }
84
+ export function checkMonotonicOrder(messages, label) {
85
+ const issues = [];
86
+ for (let i = 1; i < messages.length; i++) {
87
+ const prev = messageOrderKey(messages[i - 1]);
88
+ const cur = messageOrderKey(messages[i]);
89
+ if (cur < prev) {
90
+ issues.push({
91
+ type: 'LENTA_ORDER_INVERSION',
92
+ index: i,
93
+ detail: `${label} i=${i} ${prev}>${cur}`,
94
+ });
95
+ break;
96
+ }
97
+ }
98
+ return issues;
99
+ }
100
+ /** DOM overlay must not repeat a turn already in JSONL archive. */
101
+ export function checkOverlayDuplicatesArchive(jsonlHistory, domOverlay) {
102
+ const issues = [];
103
+ for (const dom of domOverlay) {
104
+ const hit = jsonlHistory.find((h) => archiveCoversOverlay(h, dom));
105
+ if (hit) {
106
+ issues.push({
107
+ type: 'OVERLAY_DUPLICATES_ARCHIVE',
108
+ detail: `dom flat=${dom.flatIndex ?? '?'} covered by jsonl flat=${hit.flatIndex ?? '?'}`,
109
+ dom: previewRow(dom, 'domOverlay'),
110
+ bridge: previewRow(hit, 'jsonlHistory'),
111
+ });
112
+ }
113
+ }
114
+ return issues;
115
+ }
116
+ export function checkOverlayAfterArchive(jsonlHistory, domOverlay) {
117
+ if (!jsonlHistory.length || !domOverlay.length)
118
+ return [];
119
+ let floor = 0;
120
+ for (const m of jsonlHistory) {
121
+ floor = Math.max(floor, messageOrderKey(m));
122
+ }
123
+ const minOverlay = Math.min(...domOverlay.map((m) => messageOrderKey(m)));
124
+ if (minOverlay <= floor) {
125
+ return [
126
+ {
127
+ type: 'OVERLAY_NOT_AFTER_ARCHIVE',
128
+ detail: `archive floor=${floor} overlay min key=${minOverlay}`,
129
+ },
130
+ ];
131
+ }
132
+ return [];
133
+ }
134
+ /** When not synced with Cursor, DOM overlay for subscribed chat should stay empty. */
135
+ export function checkSyncGate(synced, domOverlay, domRawCount) {
136
+ if (synced)
137
+ return [];
138
+ if (domOverlay.length > 0) {
139
+ return [
140
+ {
141
+ type: 'OVERLAY_WHILE_UNSYNCED',
142
+ detail: `overlay=${domOverlay.length} domRaw=${domRawCount}`,
143
+ },
144
+ ];
145
+ }
146
+ return [];
147
+ }
148
+ export function rowSimilar(a, b) {
149
+ if (a.role !== b.role)
150
+ return false;
151
+ const ah = a.head.toLowerCase();
152
+ const bh = b.head.toLowerCase();
153
+ if (!ah || !bh)
154
+ return ah === bh;
155
+ if (ah === bh)
156
+ return true;
157
+ const short = ah.length <= bh.length ? ah : bh;
158
+ const long = ah.length <= bh.length ? bh : ah;
159
+ if (short.length >= 8 && long.includes(short))
160
+ return true;
161
+ return long.includes(short) && short.length >= 16;
162
+ }
163
+ /** DOM viewport vs full lenta — match each viewport row to lenta from the end (avoids duplicate-text false gaps). */
164
+ export function compareDomTailToLenta(domRows, lentaRows, tail, opts) {
165
+ const issues = [];
166
+ const domTailN = opts?.hasJsonlArchive ? Math.min(tail, 4) : tail;
167
+ const d = domRows.slice(-domTailN);
168
+ const full = lentaRows;
169
+ const lentaTail = full.slice(-Math.max(tail, 16));
170
+ if (!d.length || !full.length) {
171
+ return { issues, domTail: d, lentaTail };
172
+ }
173
+ const searchWindow = Math.min(full.length, Math.max(tail * 10, full.length > 80 ? 120 : 48));
174
+ let searchEnd = full.length;
175
+ for (let di = d.length - 1; di >= 0; di--) {
176
+ const dom = d[di];
177
+ let found = -1;
178
+ const from = Math.max(0, searchEnd - searchWindow);
179
+ for (let j = searchEnd - 1; j >= from; j--) {
180
+ if (rowSimilar(dom, full[j])) {
181
+ found = j;
182
+ break;
183
+ }
184
+ }
185
+ if (found < 0) {
186
+ const domFlat = dom.flat ?? 0;
187
+ const maxLentaFlat = full.reduce((m, r) => Math.max(m, r.flat ?? 0), 0);
188
+ const type = !opts?.hasDomOverlay && (opts?.agentWorking || domFlat > maxLentaFlat + 5)
189
+ ? 'NEEDS_DOM_OVERLAY'
190
+ : 'DOM_MISSING_IN_LENTA';
191
+ issues.push({ type, dom, detail: `domFlat=${domFlat} lentaMax=${maxLentaFlat}` });
192
+ continue;
193
+ }
194
+ searchEnd = found;
195
+ }
196
+ return { issues, domTail: d, lentaTail };
197
+ }
198
+ export function checkApiMatchesStore(apiMessages, storeMessages) {
199
+ const a = rowsFingerprint(apiMessages);
200
+ const b = rowsFingerprint(storeMessages);
201
+ if (a.length !== b.length) {
202
+ return [
203
+ {
204
+ type: 'API_STORE_LEN',
205
+ detail: `api=${a.length} store=${b.length}`,
206
+ },
207
+ ];
208
+ }
209
+ for (let i = Math.max(0, a.length - 8); i < a.length; i++) {
210
+ if (a[i] !== b[i]) {
211
+ return [
212
+ {
213
+ type: 'API_STORE_TAIL',
214
+ index: i,
215
+ detail: `tail mismatch at ${i}`,
216
+ },
217
+ ];
218
+ }
219
+ }
220
+ return [];
221
+ }
@@ -0,0 +1,3 @@
1
+ import type { ChatMessage } from './types.js';
2
+ /** Canonical lenta in store vs last socket `agent:messages` emit. */
3
+ export declare function isLentaDeliveryPending(agentId: string, storeMessages: ChatMessage[]): boolean;
@@ -0,0 +1,10 @@
1
+ import { getLastLentaEmit, messagesFingerprint } from './lenta-seq-journal.js';
2
+ /** Canonical lenta in store vs last socket `agent:messages` emit. */
3
+ export function isLentaDeliveryPending(agentId, storeMessages) {
4
+ if (!storeMessages.length)
5
+ return false;
6
+ const last = getLastLentaEmit(agentId);
7
+ if (!last)
8
+ return true;
9
+ return messagesFingerprint(storeMessages) !== last.messagesSig;
10
+ }
@@ -0,0 +1,48 @@
1
+ import type { ChatMessage } from './types.js';
2
+ export type LentaSeqReason = 'dom_overlay' | 'jsonl_live' | 'agents_history' | 'subscribe_refresh' | 'delivery_flush' | 'push_ready' | 'emit';
3
+ export type LentaSeqTransition = 'dom_to_jsonl' | 'overlay_grow' | 'overlay_shrink' | 'jsonl_grow' | 'none';
4
+ export interface LentaSeqEmitMeta {
5
+ reason: LentaSeqReason;
6
+ source: 'dom' | 'jsonl' | 'hybrid';
7
+ historyLen: number;
8
+ liveLen: number;
9
+ messagesLen: number;
10
+ append?: boolean;
11
+ }
12
+ export interface LentaSeqEntry {
13
+ at: number;
14
+ agentId: string;
15
+ seq: number;
16
+ prevSeq: number;
17
+ seqHeld: boolean;
18
+ reason: LentaSeqReason;
19
+ transition: LentaSeqTransition;
20
+ source: string;
21
+ historyLen: number;
22
+ liveLen: number;
23
+ messagesLen: number;
24
+ messagesSig: string;
25
+ tailPreview: string;
26
+ append?: boolean;
27
+ }
28
+ export declare function messagesFingerprint(messages: ChatMessage[]): string;
29
+ export declare function tailPreview(messages: ChatMessage[], n?: number): string;
30
+ export declare function detectSeqTransition(prev: {
31
+ historyLen: number;
32
+ liveLen: number;
33
+ source: string;
34
+ } | undefined, next: {
35
+ historyLen: number;
36
+ liveLen: number;
37
+ source: string;
38
+ }): LentaSeqTransition;
39
+ export declare function recordLentaSeqEmit(agentId: string, seq: number, prevSeq: number, seqHeld: boolean, messages: ChatMessage[], meta: LentaSeqEmitMeta): LentaSeqEntry;
40
+ export declare function getLastLentaEmit(agentId: string): {
41
+ seq: number;
42
+ messagesSig: string;
43
+ historyLen: number;
44
+ liveLen: number;
45
+ source: string;
46
+ } | undefined;
47
+ export declare function getLentaSeqJournal(agentId?: string): LentaSeqEntry[];
48
+ export declare function getLentaSeqLogPath(): string;
@@ -0,0 +1,109 @@
1
+ import { appendFileSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ const RING_BY_AGENT = new Map();
5
+ const LAST_BY_AGENT = new Map();
6
+ const MAX_PER_AGENT = 80;
7
+ const LOG_DIR = join(homedir(), '.cursorconnect');
8
+ const LOG_FILE = join(LOG_DIR, 'lenta-seq.jsonl');
9
+ export function messagesFingerprint(messages) {
10
+ return messages
11
+ .map((m) => {
12
+ const t = String(m.text ?? '')
13
+ .replace(/\s+/g, ' ')
14
+ .trim()
15
+ .slice(0, 80);
16
+ return `${m.role}|${m.id ?? ''}|${t.length}|${t}`;
17
+ })
18
+ .join(';;');
19
+ }
20
+ export function tailPreview(messages, n = 3) {
21
+ return messages
22
+ .slice(-n)
23
+ .map((m) => `${m.role}:${(m.text ?? '').replace(/\s+/g, ' ').trim().slice(0, 48)}`)
24
+ .join(' | ');
25
+ }
26
+ export function detectSeqTransition(prev, next) {
27
+ if (!prev)
28
+ return 'none';
29
+ if (prev.liveLen > 0 && next.liveLen === 0 && next.historyLen >= prev.historyLen) {
30
+ return 'dom_to_jsonl';
31
+ }
32
+ if (next.liveLen > prev.liveLen && next.historyLen === prev.historyLen) {
33
+ return 'overlay_grow';
34
+ }
35
+ if (next.liveLen < prev.liveLen && next.historyLen >= prev.historyLen) {
36
+ return 'overlay_shrink';
37
+ }
38
+ if (next.historyLen > prev.historyLen)
39
+ return 'jsonl_grow';
40
+ return 'none';
41
+ }
42
+ export function recordLentaSeqEmit(agentId, seq, prevSeq, seqHeld, messages, meta) {
43
+ const prev = LAST_BY_AGENT.get(agentId);
44
+ const messagesSig = messagesFingerprint(messages);
45
+ const transition = detectSeqTransition(prev, {
46
+ historyLen: meta.historyLen,
47
+ liveLen: meta.liveLen,
48
+ source: meta.source,
49
+ });
50
+ const entry = {
51
+ at: Date.now(),
52
+ agentId,
53
+ seq,
54
+ prevSeq,
55
+ seqHeld,
56
+ reason: meta.reason,
57
+ transition,
58
+ source: meta.source,
59
+ historyLen: meta.historyLen,
60
+ liveLen: meta.liveLen,
61
+ messagesLen: meta.messagesLen,
62
+ messagesSig: messagesSig.slice(0, 400),
63
+ tailPreview: tailPreview(messages),
64
+ append: meta.append,
65
+ };
66
+ LAST_BY_AGENT.set(agentId, {
67
+ seq,
68
+ messagesSig,
69
+ historyLen: meta.historyLen,
70
+ liveLen: meta.liveLen,
71
+ source: meta.source,
72
+ });
73
+ let ring = RING_BY_AGENT.get(agentId);
74
+ if (!ring) {
75
+ ring = [];
76
+ RING_BY_AGENT.set(agentId, ring);
77
+ }
78
+ ring.push(entry);
79
+ while (ring.length > MAX_PER_AGENT)
80
+ ring.shift();
81
+ try {
82
+ mkdirSync(LOG_DIR, { recursive: true });
83
+ appendFileSync(LOG_FILE, `${JSON.stringify(entry)}\n`);
84
+ }
85
+ catch {
86
+ /* non-fatal */
87
+ }
88
+ if (process.env.LENTA_SEQ_ASSERT === '1') {
89
+ if (!seqHeld && prev && prev.messagesSig === messagesSig) {
90
+ console.warn(`[lenta-seq] seq bump without lenta change agent=${agentId.slice(0, 8)} seq ${prevSeq}→${seq} transition=${transition}`);
91
+ }
92
+ if (transition === 'dom_to_jsonl' && !seqHeld && prevSeq === seq) {
93
+ console.warn(`[lenta-seq] dom→jsonl handoff but seq held agent=${agentId.slice(0, 8)} seq=${seq}`);
94
+ }
95
+ }
96
+ return entry;
97
+ }
98
+ export function getLastLentaEmit(agentId) {
99
+ return LAST_BY_AGENT.get(agentId);
100
+ }
101
+ export function getLentaSeqJournal(agentId) {
102
+ if (!agentId) {
103
+ return [...RING_BY_AGENT.values()].flat().sort((a, b) => a.at - b.at).slice(-MAX_PER_AGENT);
104
+ }
105
+ return [...(RING_BY_AGENT.get(agentId) ?? [])];
106
+ }
107
+ export function getLentaSeqLogPath() {
108
+ return LOG_FILE;
109
+ }
@@ -9,6 +9,11 @@ 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
+ /** Paused for background shell — not active generation (no list spinner / agentWorking). */
13
+ export declare function isPassiveBackgroundShellState(state: {
14
+ agentStatus?: string;
15
+ agentWorking?: boolean;
16
+ }): boolean;
12
17
  /** Chat line that is only passive-work status (not prose mentioning it). */
13
18
  export declare function isPassiveStatusChatLine(text: string): boolean;
14
19
  /** DOM/system lines that mean the agent is still busy */
@@ -22,6 +22,10 @@ export function matchBackgroundWorkStatusText(text) {
22
22
  export function isBackgroundWorkStatusText(text) {
23
23
  return matchBackgroundWorkStatusText(text) !== undefined;
24
24
  }
25
+ /** Paused for background shell — not active generation (no list spinner / agentWorking). */
26
+ export function isPassiveBackgroundShellState(state) {
27
+ return (state.agentStatus === AGENT_STATUS_BACKGROUND_SHELL && state.agentWorking !== true);
28
+ }
25
29
  /** Chat line that is only passive-work status (not prose mentioning it). */
26
30
  export function isPassiveStatusChatLine(text) {
27
31
  const t = text.trim().replace(/\s+/g, ' ');
@@ -32,26 +32,49 @@ export declare class Relay {
32
32
  private readonly lastEmittedLentaSig;
33
33
  /** Display bubble count last sent — block regression 214→74 style wipes on phone. */
34
34
  private readonly lastEmittedHistLen;
35
+ private readonly lentaPendingSince;
36
+ private static readonly LENTA_PENDING_FORCE_MS;
37
+ /** Socket `agent:messages` coalesce — store ingest is immediate (see ingestDomOverlayFromState). */
38
+ private static readonly DOM_OVERLAY_EMIT_MS;
35
39
  private domOverlayTimer;
40
+ private cachedLentaPendingByAgent;
36
41
  constructor(config: ServerConfig, stateManager: StateManager, commandExecutor: CommandExecutor, cdpBridge: CDPBridge, jsonlIndex: JsonlIndex, messageDebugStore: MessageDebugStore, domExtractor: DOMExtractor);
37
42
  private emitAgentCompletedPush;
38
43
  private flushPendingPushPayloads;
39
44
  private observeAgentCompletionForPush;
45
+ private requestAgentCompletionContentSync;
40
46
  listen(): Promise<void>;
41
47
  private get authEnabled();
48
+ private lentaAgentIds;
42
49
  /** Read-only view of in-memory bridge state (no writes to stores). */
43
50
  private readOnlyChatSnapshot;
51
+ private buildLentaDebugReport;
44
52
  /** Push JSONL file updates to every subscribed route id (e.g. sidebar-0) for that composer. */
45
53
  private syncJsonlToSubscribedAgents;
46
- /** JSONL baseline + DOM rows not yet in file (`liveMessages`). */
54
+ /** JSONL baseline + DOM tail (`liveMessages`); `messages` = append-only compose. */
47
55
  private lentaSnapshot;
48
56
  private emitLentaForAgent;
49
57
  private lentaTailSignature;
50
58
  /** Live JSONL → `agent:messages` for subscribed chats (phone lenta). */
51
59
  private emitJsonlLiveForAgent;
60
+ /** Ingest CDP viewport into ChatDisplayStore — no debounce (debug + compose). */
61
+ private ingestDomOverlayFromState;
62
+ /** Push lenta with DOM live to clients while generation is in flight. */
63
+ private emitDomLiveWhileGenerating;
64
+ private syncGeneratingFlags;
65
+ /** Push composed lenta to subscribed clients (debounced). */
66
+ private emitDomOverlaySocketForSubscribers;
67
+ /** While generating, push overlay growth without waiting for emit debounce tail. */
68
+ private maybeEmitDomOverlayLeading;
69
+ private applyDomOverlayFromState;
52
70
  private scheduleDomOverlayEmit;
53
- /** DOM poll while agent works — overlay until the same turn lands in JSONL. */
54
- private emitDomOverlayForSyncedSubscribers;
71
+ /**
72
+ * Agent ids that should receive DOM ingest: active Cursor composer + synced subscriptions.
73
+ * Store must update even without phone subscribe (debug/lenta, later subscribe).
74
+ */
75
+ private resolveDomOverlayAgentIds;
76
+ private writeLentaCapture;
77
+ private resolveHistoryOpts;
55
78
  private agentMessagesSnapshot;
56
79
  private checkMediaAuth;
57
80
  private setupHttp;
@@ -62,6 +85,15 @@ export declare class Relay {
62
85
  private emitAgentMessages;
63
86
  private prepareStateMessages;
64
87
  private withDisplayState;
88
+ private buildLentaPendingByAgent;
89
+ private refreshLentaPendingCache;
90
+ private withRelayState;
91
+ private refreshLentaPendingPatch;
92
+ private noteLentaDelivery;
93
+ /** Push `agent:messages` when store is ahead of last emit (subscribed + synced). */
94
+ private flushLentaDelivery;
95
+ private flushPendingLentaForSubscribers;
96
+ private maybeForceStaleLentaDelivery;
65
97
  private wireEvents;
66
98
  private emitAgentsIndex;
67
99
  private refreshAgentsIndex;
@@ -73,6 +105,8 @@ export declare class Relay {
73
105
  private runCommand;
74
106
  private runAgentsHistory;
75
107
  private runAgentsSubscribe;
108
+ /** Load JSONL into store when DOM ingest runs but baseline empty (e.g. after bridge restart). */
109
+ private ensureJsonlBaselineForAgent;
76
110
  /** Открытие чата: JSONL baseline + focus/scroll (DOM poll только для state: working/approve). */
77
111
  private refreshDomChatOnSubscribe;
78
112
  private runAgentsUnsubscribe;