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.
- package/bridge-runtime/.env.example +2 -0
- package/bridge-runtime/connector-version.json +1 -1
- package/bridge-runtime/dist/agent-completion-push.d.ts +18 -6
- package/bridge-runtime/dist/agent-completion-push.js +186 -41
- package/bridge-runtime/dist/agent-completion-readiness.d.ts +19 -0
- package/bridge-runtime/dist/agent-completion-readiness.js +42 -0
- package/bridge-runtime/dist/chat-display-store.d.ts +32 -7
- package/bridge-runtime/dist/chat-display-store.js +96 -21
- package/bridge-runtime/dist/chat-display.d.ts +36 -0
- package/bridge-runtime/dist/chat-display.js +287 -24
- package/bridge-runtime/dist/chat-sync.d.ts +3 -1
- package/bridge-runtime/dist/chat-sync.js +20 -0
- package/bridge-runtime/dist/debug-chats-page.d.ts +1 -1
- package/bridge-runtime/dist/debug-chats-page.js +148 -26
- package/bridge-runtime/dist/dom-transcript-store.d.ts +2 -0
- package/bridge-runtime/dist/dom-transcript-store.js +17 -2
- package/bridge-runtime/dist/extract-page.js +5 -4
- package/bridge-runtime/dist/lenta-capture.d.ts +46 -0
- package/bridge-runtime/dist/lenta-capture.js +146 -0
- package/bridge-runtime/dist/lenta-debug.d.ts +42 -0
- package/bridge-runtime/dist/lenta-debug.js +221 -0
- package/bridge-runtime/dist/lenta-delivery.d.ts +3 -0
- package/bridge-runtime/dist/lenta-delivery.js +10 -0
- package/bridge-runtime/dist/lenta-seq-journal.d.ts +48 -0
- package/bridge-runtime/dist/lenta-seq-journal.js +109 -0
- package/bridge-runtime/dist/message-filter.d.ts +5 -0
- package/bridge-runtime/dist/message-filter.js +4 -0
- package/bridge-runtime/dist/relay.d.ts +37 -3
- package/bridge-runtime/dist/relay.js +557 -51
- package/bridge-runtime/dist/types.d.ts +9 -4
- package/dist/bridge-build.js +50 -0
- package/dist/index.js +9 -6
- package/dist/launch.js +5 -1
- package/dist/run-service.js +10 -4
- package/dist/startup-check.js +6 -0
- package/package.json +1 -1
- package/version-policy.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { mkdirSync } from 'fs';
|
|
1
2
|
import express from 'express';
|
|
2
3
|
import { createServer } from 'http';
|
|
3
4
|
import { randomBytes, timingSafeEqual } from 'crypto';
|
|
@@ -7,7 +8,7 @@ import { Server as SocketServer } from 'socket.io';
|
|
|
7
8
|
import { resolveJsonlFilePath } from './agent-title-match.js';
|
|
8
9
|
import { ChatDisplayStore } from './chat-display-store.js';
|
|
9
10
|
import { resolveHistoryLimit } from './history-request.js';
|
|
10
|
-
import { filterClientDisplayList, prepareChatMessagesForDisplay, } from './chat-display.js';
|
|
11
|
+
import { assertMonotonicLenta, filterClientDisplayList, prepareChatMessagesForDisplay, } from './chat-display.js';
|
|
11
12
|
import { isKeepAwakeActive } from './keep-awake.js';
|
|
12
13
|
function sleepMs(ms) {
|
|
13
14
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -20,8 +21,12 @@ import { RelayUpstream } from './relay-upstream.js';
|
|
|
20
21
|
import { bridgePipelineLog, bridgePipelineReportLines, bridgePipelineSnapshot, } from './history-pipeline-log.js';
|
|
21
22
|
import { DEBUG_CHATS_PAGE_HTML } from './debug-chats-page.js';
|
|
22
23
|
import { isChatHistoryFromJsonl } from './chat-history-mode.js';
|
|
23
|
-
import { isChatSyncedWithCursor } from './chat-sync.js';
|
|
24
|
+
import { isAgentGenerating, isChatSyncedWithCursor, isComposerUuid, resolveCursorActiveComposerId, } from './chat-sync.js';
|
|
24
25
|
import { readJsonlLiveSnapshot } from './jsonl-live-debug.js';
|
|
26
|
+
import { checkMonotonicOrder, checkOverlayAfterArchive, checkOverlayDuplicatesArchive, checkPayloadCompose, checkStoreCompose, checkSyncGate, compareDomTailToLenta, previewRow, } from './lenta-debug.js';
|
|
27
|
+
import { getLastLentaEmit, getLentaSeqJournal, getLentaSeqLogPath, messagesFingerprint, recordLentaSeqEmit, } from './lenta-seq-journal.js';
|
|
28
|
+
import { isLentaDeliveryPending } from './lenta-delivery.js';
|
|
29
|
+
import { captureLentaIfEnabled, captureSessionDir, listCaptureSteps, readCaptureConfig, writeCaptureConfig, clearCaptureConfig, } from './lenta-capture.js';
|
|
25
30
|
export class Relay {
|
|
26
31
|
stateManager;
|
|
27
32
|
commandExecutor;
|
|
@@ -49,7 +54,12 @@ export class Relay {
|
|
|
49
54
|
lastEmittedLentaSig = new Map();
|
|
50
55
|
/** Display bubble count last sent — block regression 214→74 style wipes on phone. */
|
|
51
56
|
lastEmittedHistLen = new Map();
|
|
57
|
+
lentaPendingSince = new Map();
|
|
58
|
+
static LENTA_PENDING_FORCE_MS = 12_000;
|
|
59
|
+
/** Socket `agent:messages` coalesce — store ingest is immediate (see ingestDomOverlayFromState). */
|
|
60
|
+
static DOM_OVERLAY_EMIT_MS = parseInt(process.env.DOM_OVERLAY_EMIT_MS ?? '50', 10);
|
|
52
61
|
domOverlayTimer = null;
|
|
62
|
+
cachedLentaPendingByAgent = {};
|
|
53
63
|
constructor(config, stateManager, commandExecutor, cdpBridge, jsonlIndex, messageDebugStore, domExtractor) {
|
|
54
64
|
this.stateManager = stateManager;
|
|
55
65
|
this.commandExecutor = commandExecutor;
|
|
@@ -73,7 +83,15 @@ export class Relay {
|
|
|
73
83
|
this.upstream = new RelayUpstream(this.config, (event, ...args) => {
|
|
74
84
|
this.handleRemoteClientEvent(event, ...args);
|
|
75
85
|
}, () => this.flushPendingPushPayloads());
|
|
76
|
-
this.agentCompletionPush = new AgentCompletionPush((payload) => this.emitAgentCompletedPush(payload), () => this.lastJsonlIndex)
|
|
86
|
+
this.agentCompletionPush = new AgentCompletionPush((payload) => this.emitAgentCompletedPush(payload), () => this.lastJsonlIndex, () => this.stateManager.getState(), {
|
|
87
|
+
readiness: {
|
|
88
|
+
isSubscribed: (agentId) => this.jsonlIndex.getSubscribedAgents().has(agentId),
|
|
89
|
+
getSubscribeTitle: (agentId) => this.jsonlIndex.getSubscribedAgents().get(agentId)?.title,
|
|
90
|
+
getDisplayMessages: (agentId) => this.chatDisplay.getDisplayMessages(agentId),
|
|
91
|
+
getJsonlHistory: (agentId) => this.chatDisplay.getJsonlHistory(agentId),
|
|
92
|
+
},
|
|
93
|
+
requestContentSync: (agentId, state) => this.requestAgentCompletionContentSync(agentId, state),
|
|
94
|
+
});
|
|
77
95
|
this.upstream.connect();
|
|
78
96
|
}
|
|
79
97
|
}
|
|
@@ -81,6 +99,7 @@ export class Relay {
|
|
|
81
99
|
emitAgentCompletedPush(payload) {
|
|
82
100
|
if (this.upstream?.connected) {
|
|
83
101
|
this.upstream.emit('push:agent-completed', payload);
|
|
102
|
+
console.log(`[agent-completion-push] upstream emit agentId=${payload.agentId.slice(0, 12)} title=${payload.chatTitle.slice(0, 40)}`);
|
|
84
103
|
return;
|
|
85
104
|
}
|
|
86
105
|
this.pendingPushPayloads.push(payload);
|
|
@@ -101,6 +120,10 @@ export class Relay {
|
|
|
101
120
|
observeAgentCompletionForPush() {
|
|
102
121
|
this.agentCompletionPush?.observe(this.stateManager.getState());
|
|
103
122
|
}
|
|
123
|
+
requestAgentCompletionContentSync(agentId, state) {
|
|
124
|
+
void this.ensureJsonlBaselineForAgent(agentId, state);
|
|
125
|
+
this.flushLentaDelivery(agentId, 'push_ready');
|
|
126
|
+
}
|
|
104
127
|
listen() {
|
|
105
128
|
return new Promise((resolve) => {
|
|
106
129
|
this.httpServer.listen(this.config.serverPort, this.config.serverHost, () => {
|
|
@@ -112,23 +135,33 @@ export class Relay {
|
|
|
112
135
|
get authEnabled() {
|
|
113
136
|
return this.config.webappPassword.length > 0;
|
|
114
137
|
}
|
|
138
|
+
lentaAgentIds(state) {
|
|
139
|
+
const subscribed = [...this.jsonlIndex.getSubscribedAgents().keys()];
|
|
140
|
+
return new Set([
|
|
141
|
+
...subscribed,
|
|
142
|
+
...(state.activeComposerId ? [state.activeComposerId] : []),
|
|
143
|
+
]);
|
|
144
|
+
}
|
|
115
145
|
/** Read-only view of in-memory bridge state (no writes to stores). */
|
|
116
146
|
readOnlyChatSnapshot() {
|
|
117
147
|
const state = this.stateManager.getState();
|
|
118
148
|
const subscribed = [...this.jsonlIndex.getSubscribedAgents().entries()].map(([agentId, meta]) => ({ agentId, title: meta.title }));
|
|
119
149
|
const displayCache = {};
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
150
|
+
const domOverlay = {};
|
|
151
|
+
const displayMessages = {};
|
|
152
|
+
const domRaw = {};
|
|
153
|
+
const lentaByAgent = {};
|
|
154
|
+
for (const agentId of this.lentaAgentIds(state)) {
|
|
155
|
+
const lenta = this.chatDisplay.getLentaDebug(agentId);
|
|
156
|
+
lentaByAgent[agentId] = lenta;
|
|
157
|
+
if (lenta.jsonlHistory.length)
|
|
158
|
+
displayCache[agentId] = lenta.jsonlHistory;
|
|
159
|
+
if (lenta.domOverlay.length)
|
|
160
|
+
domOverlay[agentId] = lenta.domOverlay;
|
|
161
|
+
if (lenta.messages.length)
|
|
162
|
+
displayMessages[agentId] = lenta.messages;
|
|
163
|
+
if (lenta.domRaw.length)
|
|
164
|
+
domRaw[agentId] = lenta.domRaw;
|
|
132
165
|
}
|
|
133
166
|
return {
|
|
134
167
|
at: Date.now(),
|
|
@@ -138,6 +171,7 @@ export class Relay {
|
|
|
138
171
|
activeChatTitle: state.activeChatTitle,
|
|
139
172
|
updatedAt: state.updatedAt,
|
|
140
173
|
lastError: state.lastError,
|
|
174
|
+
agentWorking: state.agentWorking,
|
|
141
175
|
tabs: state.tabs,
|
|
142
176
|
composerIdByTitle: state.composerIdByTitle,
|
|
143
177
|
domMessageCount: state.messages.length,
|
|
@@ -148,9 +182,83 @@ export class Relay {
|
|
|
148
182
|
ringSize: this.messageDebugStore.list().length,
|
|
149
183
|
},
|
|
150
184
|
subscribed,
|
|
185
|
+
lentaByAgent,
|
|
151
186
|
displayCache,
|
|
187
|
+
domOverlay,
|
|
188
|
+
displayMessages,
|
|
189
|
+
domRaw,
|
|
190
|
+
/** @deprecated use `domOverlay` */
|
|
191
|
+
domTranscript: domOverlay,
|
|
152
192
|
chatHistoryJsonl: isChatHistoryFromJsonl(),
|
|
153
|
-
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
async buildLentaDebugReport(agentId, title, opts) {
|
|
196
|
+
const state = this.stateManager.getState();
|
|
197
|
+
const subMeta = this.jsonlIndex.getSubscribedAgents().get(agentId);
|
|
198
|
+
const subTitle = title ?? subMeta?.title;
|
|
199
|
+
const synced = isChatSyncedWithCursor(agentId, subTitle, state);
|
|
200
|
+
if (opts?.reloadJsonl) {
|
|
201
|
+
const history = await this.jsonlIndex.loadHistory(agentId, {
|
|
202
|
+
title: subTitle,
|
|
203
|
+
composerIdByTitle: state.composerIdByTitle,
|
|
204
|
+
});
|
|
205
|
+
this.chatDisplay.setJsonlBaseline(agentId, history.messages);
|
|
206
|
+
}
|
|
207
|
+
this.syncGeneratingFlags(state);
|
|
208
|
+
if (state.messages?.length)
|
|
209
|
+
this.ingestDomOverlayFromState(state);
|
|
210
|
+
const lenta = this.chatDisplay.getLentaDebug(agentId);
|
|
211
|
+
const snap = this.agentMessagesSnapshot(agentId);
|
|
212
|
+
const issues = [
|
|
213
|
+
...checkStoreCompose(lenta.jsonlHistory, lenta.domOverlay, lenta.messages),
|
|
214
|
+
...checkPayloadCompose(snap.messages, snap.historyMessages, snap.liveMessages),
|
|
215
|
+
...checkMonotonicOrder(lenta.messages, 'store'),
|
|
216
|
+
...checkOverlayAfterArchive(lenta.jsonlHistory, lenta.domOverlay),
|
|
217
|
+
...checkOverlayDuplicatesArchive(lenta.jsonlHistory, lenta.domOverlay),
|
|
218
|
+
...checkSyncGate(synced, lenta.domOverlay, lenta.domRaw.length),
|
|
219
|
+
];
|
|
220
|
+
const activeId = state.activeComposerId;
|
|
221
|
+
const domViewport = activeId && activeId !== agentId
|
|
222
|
+
? []
|
|
223
|
+
: state.messages.map((m) => previewRow(m, 'dom-viewport'));
|
|
224
|
+
const domNote = activeId && activeId !== agentId
|
|
225
|
+
? `DOM viewport is active ${activeId.slice(0, 8)}, not ${agentId.slice(0, 8)}`
|
|
226
|
+
: null;
|
|
227
|
+
const domCmp = synced
|
|
228
|
+
? compareDomTailToLenta(domViewport, lenta.messages.map((m) => previewRow(m, 'lenta')), 12, {
|
|
229
|
+
hasDomOverlay: lenta.domOverlay.length > 0,
|
|
230
|
+
hasJsonlArchive: lenta.jsonlHistory.length > 0,
|
|
231
|
+
agentWorking: state.agentWorking,
|
|
232
|
+
})
|
|
233
|
+
: { issues: [], domTail: [], lentaTail: [] };
|
|
234
|
+
issues.push(...domCmp.issues);
|
|
235
|
+
return {
|
|
236
|
+
at: new Date().toISOString(),
|
|
237
|
+
agentId,
|
|
238
|
+
synced,
|
|
239
|
+
domNote,
|
|
240
|
+
health: { cdp: this.cdpBridge.getClient()?.isConnected() ?? false },
|
|
241
|
+
activeComposerId: activeId,
|
|
242
|
+
activeChatTitle: state.activeChatTitle,
|
|
243
|
+
agentWorking: state.agentWorking,
|
|
244
|
+
agentGenerating: lenta.agentGenerating,
|
|
245
|
+
jsonlRowCount: lenta.jsonlRowCount,
|
|
246
|
+
counts: {
|
|
247
|
+
jsonl: lenta.jsonlHistory.length,
|
|
248
|
+
domOverlay: lenta.domOverlay.length,
|
|
249
|
+
domRaw: lenta.domRaw.length,
|
|
250
|
+
messages: lenta.messages.length,
|
|
251
|
+
domViewport: domViewport.length,
|
|
252
|
+
},
|
|
253
|
+
lenta,
|
|
254
|
+
emitSnapshot: snap,
|
|
255
|
+
issues,
|
|
256
|
+
match: issues.length === 0,
|
|
257
|
+
domTail: domCmp.domTail,
|
|
258
|
+
lentaTail: domCmp.lentaTail,
|
|
259
|
+
seqJournal: getLentaSeqJournal(agentId).slice(-24),
|
|
260
|
+
seqLogFile: getLentaSeqLogPath(),
|
|
261
|
+
currentSeq: this.historySeqByAgent.get(agentId) ?? 0,
|
|
154
262
|
};
|
|
155
263
|
}
|
|
156
264
|
/** Push JSONL file updates to every subscribed route id (e.g. sidebar-0) for that composer. */
|
|
@@ -172,16 +280,18 @@ export class Relay {
|
|
|
172
280
|
this.emitJsonlLiveForAgent(agentId, messages, totalMessages);
|
|
173
281
|
}
|
|
174
282
|
}
|
|
175
|
-
/** JSONL baseline + DOM
|
|
283
|
+
/** JSONL baseline + DOM tail (`liveMessages`); `messages` = append-only compose. */
|
|
176
284
|
lentaSnapshot(agentId, totalMessages) {
|
|
177
285
|
const historyMessages = this.chatDisplay.getJsonlHistory(agentId);
|
|
178
286
|
const liveMessages = this.chatDisplay.getDomLive(agentId);
|
|
287
|
+
const messages = this.chatDisplay.getDisplayMessages(agentId);
|
|
288
|
+
assertMonotonicLenta(messages, agentId.slice(0, 8));
|
|
179
289
|
const source = liveMessages.length ? 'hybrid' : 'jsonl';
|
|
180
290
|
return {
|
|
181
291
|
historyMessages,
|
|
182
292
|
liveMessages,
|
|
183
|
-
messages
|
|
184
|
-
totalMessages: totalMessages ??
|
|
293
|
+
messages,
|
|
294
|
+
totalMessages: totalMessages ?? messages.length,
|
|
185
295
|
source: source,
|
|
186
296
|
};
|
|
187
297
|
}
|
|
@@ -190,7 +300,12 @@ export class Relay {
|
|
|
190
300
|
const historyOut = opts.historyPayload ?? snap.historyMessages;
|
|
191
301
|
if (!historyOut.length && !snap.liveMessages.length)
|
|
192
302
|
return;
|
|
193
|
-
this.emitAgentMessages(agentId, historyOut, snap.liveMessages, snap.source,
|
|
303
|
+
this.emitAgentMessages(agentId, historyOut, snap.liveMessages, snap.source, {
|
|
304
|
+
totalMessages: opts.totalMessages,
|
|
305
|
+
append: opts.append,
|
|
306
|
+
reason: opts.reason,
|
|
307
|
+
forceSeq: opts.forceSeq,
|
|
308
|
+
});
|
|
194
309
|
}
|
|
195
310
|
lentaTailSignature(messages) {
|
|
196
311
|
return messages
|
|
@@ -202,16 +317,23 @@ export class Relay {
|
|
|
202
317
|
emitJsonlLiveForAgent(agentId, rows, totalMessages) {
|
|
203
318
|
const prevTotal = this.lastEmittedJsonlRows.get(agentId) ?? 0;
|
|
204
319
|
const prevSig = this.lastEmittedLentaSig.get(agentId);
|
|
320
|
+
const state = this.stateManager.getState();
|
|
321
|
+
this.chatDisplay.setAgentGenerating(agentId, isAgentGenerating(agentId, state));
|
|
205
322
|
this.chatDisplay.setJsonlBaseline(agentId, rows);
|
|
206
323
|
this.lastEmittedJsonlRows.set(agentId, totalMessages);
|
|
324
|
+
this.writeLentaCapture(agentId, 'jsonl_file');
|
|
207
325
|
const historyMessages = this.chatDisplay.getJsonlHistory(agentId);
|
|
208
|
-
|
|
326
|
+
const liveN = this.chatDisplay.getDomLive(agentId).length;
|
|
327
|
+
if (!historyMessages.length && !liveN)
|
|
209
328
|
return;
|
|
210
329
|
if (!this.jsonlIndex.getSubscribedAgents().has(agentId))
|
|
211
330
|
return;
|
|
331
|
+
const storeMessages = this.chatDisplay.getDisplayMessages(agentId);
|
|
332
|
+
const deliveryPending = isLentaDeliveryPending(agentId, storeMessages);
|
|
212
333
|
const sig = this.lentaTailSignature(historyMessages);
|
|
213
334
|
const totalGrew = totalMessages > prevTotal;
|
|
214
|
-
|
|
335
|
+
const generating = isAgentGenerating(agentId, state);
|
|
336
|
+
if (!deliveryPending && !generating && !totalGrew && sig === prevSig && prevSig)
|
|
215
337
|
return;
|
|
216
338
|
const histLen = historyMessages.length;
|
|
217
339
|
const prevHistLen = this.lastEmittedHistLen.get(agentId) ?? 0;
|
|
@@ -225,40 +347,191 @@ export class Relay {
|
|
|
225
347
|
});
|
|
226
348
|
return;
|
|
227
349
|
}
|
|
228
|
-
this.lastEmittedLentaSig.set(agentId, sig);
|
|
229
|
-
this.lastEmittedHistLen.set(agentId, Math.max(prevHistLen, histLen));
|
|
230
350
|
this.emitLentaForAgent(agentId, {
|
|
231
351
|
totalMessages,
|
|
232
|
-
|
|
352
|
+
reason: 'jsonl_live',
|
|
353
|
+
forceSeq: deliveryPending || generating,
|
|
233
354
|
});
|
|
355
|
+
this.lastEmittedLentaSig.set(agentId, sig);
|
|
356
|
+
this.lastEmittedHistLen.set(agentId, Math.max(prevHistLen, histLen));
|
|
357
|
+
if (isLentaDeliveryPending(agentId, this.chatDisplay.getDisplayMessages(agentId))) {
|
|
358
|
+
this.flushLentaDelivery(agentId, 'delivery_flush');
|
|
359
|
+
}
|
|
360
|
+
this.agentCompletionPush?.retryPendingContent(state);
|
|
234
361
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
clearTimeout(this.domOverlayTimer);
|
|
238
|
-
this.domOverlayTimer = setTimeout(() => {
|
|
239
|
-
this.domOverlayTimer = null;
|
|
240
|
-
this.emitDomOverlayForSyncedSubscribers();
|
|
241
|
-
}, 120);
|
|
242
|
-
}
|
|
243
|
-
/** DOM poll while agent works — overlay until the same turn lands in JSONL. */
|
|
244
|
-
emitDomOverlayForSyncedSubscribers() {
|
|
245
|
-
const state = this.stateManager.getState();
|
|
362
|
+
/** Ingest CDP viewport into ChatDisplayStore — no debounce (debug + compose). */
|
|
363
|
+
ingestDomOverlayFromState(state) {
|
|
246
364
|
if (!state.messages?.length)
|
|
247
365
|
return;
|
|
248
366
|
const domRows = this.prepareStateMessages(state.messages);
|
|
367
|
+
const targets = this.resolveDomOverlayAgentIds(state);
|
|
368
|
+
if (!targets.size)
|
|
369
|
+
return;
|
|
370
|
+
for (const agentId of targets) {
|
|
371
|
+
this.chatDisplay.setAgentGenerating(agentId, isAgentGenerating(agentId, state));
|
|
372
|
+
if (!this.chatDisplay.getJsonlHistory(agentId).length) {
|
|
373
|
+
void this.ensureJsonlBaselineForAgent(agentId, state);
|
|
374
|
+
}
|
|
375
|
+
const prevLive = this.chatDisplay.getDomLive(agentId).length;
|
|
376
|
+
this.chatDisplay.mergeLiveForAgent(agentId, domRows);
|
|
377
|
+
const liveN = this.chatDisplay.getDomLive(agentId).length;
|
|
378
|
+
if (liveN > 0 && liveN !== prevLive) {
|
|
379
|
+
bridgePipelineLog({
|
|
380
|
+
dir: 'internal',
|
|
381
|
+
event: 'dom:overlay:ingest',
|
|
382
|
+
agentId,
|
|
383
|
+
msgs: liveN,
|
|
384
|
+
detail: `domRows=${domRows.length} generating=${isAgentGenerating(agentId, state)} viewport=${domRows.length}`,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
this.writeLentaCapture(agentId, 'dom_ingest');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
/** Push lenta with DOM live to clients while generation is in flight. */
|
|
391
|
+
emitDomLiveWhileGenerating(state) {
|
|
392
|
+
for (const agentId of this.resolveDomOverlayAgentIds(state)) {
|
|
393
|
+
if (!isAgentGenerating(agentId, state))
|
|
394
|
+
continue;
|
|
395
|
+
if (!this.jsonlIndex.getSubscribedAgents().has(agentId))
|
|
396
|
+
continue;
|
|
397
|
+
const meta = this.jsonlIndex.getSubscribedAgents().get(agentId);
|
|
398
|
+
if (!meta || !isChatSyncedWithCursor(agentId, meta.title, state))
|
|
399
|
+
continue;
|
|
400
|
+
const snap = this.lentaSnapshot(agentId, this.lastEmittedJsonlRows.get(agentId) ?? undefined);
|
|
401
|
+
if (!snap.liveMessages.length)
|
|
402
|
+
continue;
|
|
403
|
+
this.lastEmittedLentaSig.delete(agentId);
|
|
404
|
+
this.emitLentaForAgent(agentId, {
|
|
405
|
+
totalMessages: snap.totalMessages,
|
|
406
|
+
reason: 'dom_overlay',
|
|
407
|
+
forceSeq: true,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
syncGeneratingFlags(state) {
|
|
412
|
+
const targets = this.resolveDomOverlayAgentIds(state);
|
|
413
|
+
for (const agentId of targets) {
|
|
414
|
+
this.chatDisplay.setAgentGenerating(agentId, isAgentGenerating(agentId, state));
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/** Push composed lenta to subscribed clients (debounced). */
|
|
418
|
+
emitDomOverlaySocketForSubscribers(state) {
|
|
249
419
|
for (const [subId, meta] of this.jsonlIndex.getSubscribedAgents()) {
|
|
250
420
|
if (!isChatSyncedWithCursor(subId, meta.title, state))
|
|
251
421
|
continue;
|
|
252
|
-
this.chatDisplay.mergeLiveForAgent(subId, domRows);
|
|
253
422
|
const snap = this.lentaSnapshot(subId, this.lastEmittedJsonlRows.get(subId) ?? undefined);
|
|
254
|
-
|
|
423
|
+
const deliveryPending = isLentaDeliveryPending(subId, snap.messages);
|
|
424
|
+
if (!snap.liveMessages.length && !state.agentWorking && !deliveryPending)
|
|
255
425
|
continue;
|
|
256
426
|
this.lastEmittedLentaSig.delete(subId);
|
|
257
427
|
this.emitLentaForAgent(subId, {
|
|
258
428
|
totalMessages: snap.totalMessages,
|
|
259
|
-
|
|
429
|
+
reason: 'dom_overlay',
|
|
430
|
+
forceSeq: deliveryPending,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
/** While generating, push overlay growth without waiting for emit debounce tail. */
|
|
435
|
+
maybeEmitDomOverlayLeading(state) {
|
|
436
|
+
if (!state.agentWorking)
|
|
437
|
+
return;
|
|
438
|
+
for (const [subId, meta] of this.jsonlIndex.getSubscribedAgents()) {
|
|
439
|
+
if (!isChatSyncedWithCursor(subId, meta.title, state))
|
|
440
|
+
continue;
|
|
441
|
+
if (!this.chatDisplay.getDomLive(subId).length)
|
|
442
|
+
continue;
|
|
443
|
+
const snap = this.lentaSnapshot(subId, this.lastEmittedJsonlRows.get(subId) ?? undefined);
|
|
444
|
+
const sig = messagesFingerprint(snap.messages);
|
|
445
|
+
const last = getLastLentaEmit(subId);
|
|
446
|
+
if (last?.messagesSig === sig)
|
|
447
|
+
continue;
|
|
448
|
+
this.emitLentaForAgent(subId, {
|
|
449
|
+
totalMessages: snap.totalMessages,
|
|
450
|
+
reason: 'dom_overlay',
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
applyDomOverlayFromState(state) {
|
|
455
|
+
this.syncGeneratingFlags(state);
|
|
456
|
+
this.ingestDomOverlayFromState(state);
|
|
457
|
+
this.emitDomLiveWhileGenerating(state);
|
|
458
|
+
this.maybeEmitDomOverlayLeading(state);
|
|
459
|
+
this.scheduleDomOverlayEmit();
|
|
460
|
+
}
|
|
461
|
+
scheduleDomOverlayEmit() {
|
|
462
|
+
if (this.domOverlayTimer)
|
|
463
|
+
clearTimeout(this.domOverlayTimer);
|
|
464
|
+
this.domOverlayTimer = setTimeout(() => {
|
|
465
|
+
this.domOverlayTimer = null;
|
|
466
|
+
this.emitDomOverlaySocketForSubscribers(this.stateManager.getState());
|
|
467
|
+
}, Relay.DOM_OVERLAY_EMIT_MS);
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Agent ids that should receive DOM ingest: active Cursor composer + synced subscriptions.
|
|
471
|
+
* Store must update even without phone subscribe (debug/lenta, later subscribe).
|
|
472
|
+
*/
|
|
473
|
+
resolveDomOverlayAgentIds(state) {
|
|
474
|
+
const targets = new Set();
|
|
475
|
+
const activeTab = state.tabs.find((t) => t.active);
|
|
476
|
+
const activeId = resolveCursorActiveComposerId(state);
|
|
477
|
+
if (isComposerUuid(activeId))
|
|
478
|
+
targets.add(activeId);
|
|
479
|
+
for (const [subId, meta] of this.jsonlIndex.getSubscribedAgents()) {
|
|
480
|
+
if (!isChatSyncedWithCursor(subId, meta.title, state))
|
|
481
|
+
continue;
|
|
482
|
+
targets.add(subId);
|
|
483
|
+
if (isComposerUuid(subId))
|
|
484
|
+
continue;
|
|
485
|
+
const path = resolveJsonlFilePath(this.config.cursorProjectsDir, subId, {
|
|
486
|
+
title: meta.title,
|
|
487
|
+
composerIdByTitle: state.composerIdByTitle,
|
|
488
|
+
activeComposerId: state.activeComposerId,
|
|
489
|
+
activeTabTitle: activeTab?.title,
|
|
260
490
|
});
|
|
491
|
+
if (path)
|
|
492
|
+
targets.add(basename(path, '.jsonl'));
|
|
261
493
|
}
|
|
494
|
+
return targets;
|
|
495
|
+
}
|
|
496
|
+
writeLentaCapture(agentId, reason, extra) {
|
|
497
|
+
const state = this.stateManager.getState();
|
|
498
|
+
const lenta = this.chatDisplay.getLentaDebug(agentId);
|
|
499
|
+
const activeTab = state.tabs.find((t) => t.active);
|
|
500
|
+
const jsonlPath = resolveJsonlFilePath(this.config.cursorProjectsDir, agentId, {
|
|
501
|
+
title: this.jsonlIndex.getSubscribedAgents().get(agentId)?.title,
|
|
502
|
+
composerIdByTitle: state.composerIdByTitle,
|
|
503
|
+
activeComposerId: state.activeComposerId,
|
|
504
|
+
activeTabTitle: activeTab?.title,
|
|
505
|
+
});
|
|
506
|
+
const domViewport = resolveCursorActiveComposerId(state) === agentId
|
|
507
|
+
? this.prepareStateMessages(state.messages)
|
|
508
|
+
: [];
|
|
509
|
+
captureLentaIfEnabled(agentId, {
|
|
510
|
+
reason,
|
|
511
|
+
source: lenta.source,
|
|
512
|
+
emitSeq: extra?.emitSeq,
|
|
513
|
+
seqHeld: extra?.seqHeld,
|
|
514
|
+
transition: extra?.transition,
|
|
515
|
+
jsonlRowCount: lenta.jsonlRowCount,
|
|
516
|
+
historyMessages: lenta.jsonlHistory,
|
|
517
|
+
liveMessages: lenta.domOverlay,
|
|
518
|
+
messages: lenta.messages,
|
|
519
|
+
domRaw: lenta.domRaw,
|
|
520
|
+
domViewport,
|
|
521
|
+
jsonlFilePath: jsonlPath ?? undefined,
|
|
522
|
+
activeComposerId: state.activeComposerId,
|
|
523
|
+
agentWorking: state.agentWorking,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
resolveHistoryOpts(agentId) {
|
|
527
|
+
const state = this.stateManager.getState();
|
|
528
|
+
const activeTab = state.tabs.find((t) => t.active);
|
|
529
|
+
return {
|
|
530
|
+
title: this.jsonlIndex.getSubscribedAgents().get(agentId)?.title,
|
|
531
|
+
composerIdByTitle: state.composerIdByTitle,
|
|
532
|
+
activeComposerId: state.activeComposerId,
|
|
533
|
+
activeTabTitle: activeTab?.title,
|
|
534
|
+
};
|
|
262
535
|
}
|
|
263
536
|
agentMessagesSnapshot(agentId, totalMessages) {
|
|
264
537
|
return this.lentaSnapshot(agentId, totalMessages);
|
|
@@ -280,6 +553,8 @@ export class Relay {
|
|
|
280
553
|
keepAwake: isKeepAwakeActive(),
|
|
281
554
|
debugChats: '/debug/chats',
|
|
282
555
|
debugSnapshot: '/debug/chat-snapshot',
|
|
556
|
+
debugLenta: '/debug/lenta?agentId=',
|
|
557
|
+
debugLentaSeq: '/debug/lenta-seq?agentId=',
|
|
283
558
|
debugJsonlLive: '/debug/jsonl-live',
|
|
284
559
|
});
|
|
285
560
|
});
|
|
@@ -299,6 +574,67 @@ export class Relay {
|
|
|
299
574
|
this.app.get('/debug/chat-snapshot', (_req, res) => {
|
|
300
575
|
res.json(this.readOnlyChatSnapshot());
|
|
301
576
|
});
|
|
577
|
+
this.app.get('/debug/lenta', async (req, res) => {
|
|
578
|
+
const agentId = typeof req.query.agentId === 'string' ? req.query.agentId.trim() : '';
|
|
579
|
+
if (!agentId) {
|
|
580
|
+
res.status(400).json({ error: 'agentId required' });
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const title = typeof req.query.title === 'string' ? req.query.title.trim() : undefined;
|
|
584
|
+
const reloadJsonl = req.query.reloadJsonl === '1' || req.query.reloadJsonl === 'true';
|
|
585
|
+
try {
|
|
586
|
+
const report = await this.buildLentaDebugReport(agentId, title, { reloadJsonl });
|
|
587
|
+
res.json(report);
|
|
588
|
+
}
|
|
589
|
+
catch (err) {
|
|
590
|
+
res.status(500).json({ error: err.message });
|
|
591
|
+
}
|
|
592
|
+
});
|
|
593
|
+
this.app.post('/debug/lenta-capture/start', express.json(), (req, res) => {
|
|
594
|
+
const body = (req.body ?? {});
|
|
595
|
+
const agentId = String(body.agentId ?? (typeof req.query.agentId === 'string' ? req.query.agentId : '') ?? '').trim();
|
|
596
|
+
if (!agentId) {
|
|
597
|
+
res.status(400).json({ error: 'agentId required' });
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
writeCaptureConfig({
|
|
601
|
+
enabled: true,
|
|
602
|
+
agentId,
|
|
603
|
+
title: body?.title?.trim(),
|
|
604
|
+
});
|
|
605
|
+
mkdirSync(captureSessionDir(agentId), { recursive: true });
|
|
606
|
+
res.json({
|
|
607
|
+
ok: true,
|
|
608
|
+
agentId,
|
|
609
|
+
sessionDir: captureSessionDir(agentId),
|
|
610
|
+
hint: 'Reproduce chat activity; snapshots append under sessionDir',
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
this.app.post('/debug/lenta-capture/stop', (_req, res) => {
|
|
614
|
+
const cfg = readCaptureConfig();
|
|
615
|
+
clearCaptureConfig();
|
|
616
|
+
res.json({ ok: true, stopped: cfg?.agentId ?? null });
|
|
617
|
+
});
|
|
618
|
+
this.app.get('/debug/lenta-capture/status', (_req, res) => {
|
|
619
|
+
const cfg = readCaptureConfig();
|
|
620
|
+
res.json({
|
|
621
|
+
config: cfg,
|
|
622
|
+
sessionDir: cfg ? captureSessionDir(cfg.agentId) : null,
|
|
623
|
+
steps: cfg ? listCaptureSteps(cfg.agentId) : [],
|
|
624
|
+
});
|
|
625
|
+
});
|
|
626
|
+
this.app.get('/debug/lenta-seq', (req, res) => {
|
|
627
|
+
const agentId = typeof req.query.agentId === 'string' ? req.query.agentId.trim() : undefined;
|
|
628
|
+
const journal = getLentaSeqJournal(agentId || undefined);
|
|
629
|
+
res.json({
|
|
630
|
+
at: new Date().toISOString(),
|
|
631
|
+
agentId: agentId ?? null,
|
|
632
|
+
logFile: getLentaSeqLogPath(),
|
|
633
|
+
currentSeq: agentId ? this.historySeqByAgent.get(agentId) ?? 0 : undefined,
|
|
634
|
+
journal,
|
|
635
|
+
handoffs: journal.filter((e) => e.transition === 'dom_to_jsonl'),
|
|
636
|
+
});
|
|
637
|
+
});
|
|
302
638
|
this.app.get('/debug/jsonl-live', async (req, res) => {
|
|
303
639
|
if (!this.checkMediaAuth(req)) {
|
|
304
640
|
res.status(401).json({ error: 'Unauthorized' });
|
|
@@ -615,20 +951,59 @@ export class Relay {
|
|
|
615
951
|
this.historySeqByAgent.set(agentId, n);
|
|
616
952
|
return n;
|
|
617
953
|
}
|
|
618
|
-
emitAgentMessages(agentId, historyMessages, liveMessages, source,
|
|
619
|
-
const outSeq = seq ?? this.nextHistorySeq(agentId);
|
|
954
|
+
emitAgentMessages(agentId, historyMessages, liveMessages, source, opts) {
|
|
620
955
|
const messages = [...historyMessages, ...liveMessages];
|
|
956
|
+
const sig = messagesFingerprint(messages);
|
|
957
|
+
const prevSeq = this.historySeqByAgent.get(agentId) ?? 0;
|
|
958
|
+
const prevHeld = getLastLentaEmit(agentId);
|
|
959
|
+
let outSeq;
|
|
960
|
+
let seqHeld = false;
|
|
961
|
+
if (opts?.forceSeq) {
|
|
962
|
+
outSeq = this.nextHistorySeq(agentId);
|
|
963
|
+
}
|
|
964
|
+
else if (prevHeld && prevHeld.messagesSig === sig) {
|
|
965
|
+
outSeq = prevHeld.seq;
|
|
966
|
+
seqHeld = true;
|
|
967
|
+
this.historySeqByAgent.set(agentId, outSeq);
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
outSeq = this.nextHistorySeq(agentId);
|
|
971
|
+
}
|
|
972
|
+
const entry = recordLentaSeqEmit(agentId, outSeq, prevSeq, seqHeld, messages, {
|
|
973
|
+
reason: opts?.reason ?? 'emit',
|
|
974
|
+
source,
|
|
975
|
+
historyLen: historyMessages.length,
|
|
976
|
+
liveLen: liveMessages.length,
|
|
977
|
+
messagesLen: messages.length,
|
|
978
|
+
append: opts?.append,
|
|
979
|
+
});
|
|
980
|
+
bridgePipelineLog({
|
|
981
|
+
dir: 'out',
|
|
982
|
+
event: 'lenta:seq',
|
|
983
|
+
agentId,
|
|
984
|
+
msgs: messages.length,
|
|
985
|
+
detail: `seq=${outSeq} prev=${prevSeq} held=${seqHeld ? 1 : 0} ` +
|
|
986
|
+
`reason=${entry.reason} transition=${entry.transition} ` +
|
|
987
|
+
`hist=${entry.historyLen} live=${entry.liveLen} src=${source}`,
|
|
988
|
+
});
|
|
621
989
|
this.broadcast('agent:messages', {
|
|
622
990
|
agentId,
|
|
623
991
|
historyMessages,
|
|
624
992
|
liveMessages,
|
|
625
993
|
messages,
|
|
626
|
-
totalMessages: totalMessages ?? messages.length,
|
|
994
|
+
totalMessages: opts?.totalMessages ?? messages.length,
|
|
627
995
|
source,
|
|
628
996
|
updatedAt: Date.now(),
|
|
629
997
|
seq: outSeq,
|
|
630
|
-
append: append || undefined,
|
|
998
|
+
append: opts?.append || undefined,
|
|
999
|
+
});
|
|
1000
|
+
this.writeLentaCapture(agentId, 'emit_agent_messages', {
|
|
1001
|
+
emitSeq: outSeq,
|
|
1002
|
+
seqHeld,
|
|
1003
|
+
transition: entry.transition,
|
|
631
1004
|
});
|
|
1005
|
+
this.noteLentaDelivery(agentId, messages);
|
|
1006
|
+
this.refreshLentaPendingPatch();
|
|
632
1007
|
}
|
|
633
1008
|
prepareStateMessages(raw) {
|
|
634
1009
|
return filterClientDisplayList(prepareChatMessagesForDisplay(raw));
|
|
@@ -641,18 +1016,125 @@ export class Relay {
|
|
|
641
1016
|
messages: this.prepareStateMessages(payload.messages),
|
|
642
1017
|
};
|
|
643
1018
|
}
|
|
1019
|
+
buildLentaPendingByAgent() {
|
|
1020
|
+
const state = this.stateManager.getState();
|
|
1021
|
+
const out = {};
|
|
1022
|
+
for (const [agentId, meta] of this.jsonlIndex.getSubscribedAgents()) {
|
|
1023
|
+
if (!isChatSyncedWithCursor(agentId, meta.title, state))
|
|
1024
|
+
continue;
|
|
1025
|
+
const messages = this.chatDisplay.getDisplayMessages(agentId);
|
|
1026
|
+
if (isLentaDeliveryPending(agentId, messages))
|
|
1027
|
+
out[agentId] = true;
|
|
1028
|
+
}
|
|
1029
|
+
return out;
|
|
1030
|
+
}
|
|
1031
|
+
refreshLentaPendingCache() {
|
|
1032
|
+
this.cachedLentaPendingByAgent = this.buildLentaPendingByAgent();
|
|
1033
|
+
}
|
|
1034
|
+
withRelayState(payload) {
|
|
1035
|
+
const base = this.withDisplayState(payload);
|
|
1036
|
+
if (payload.messages?.length) {
|
|
1037
|
+
this.refreshLentaPendingCache();
|
|
1038
|
+
}
|
|
1039
|
+
return {
|
|
1040
|
+
...base,
|
|
1041
|
+
lentaPendingByAgent: this.cachedLentaPendingByAgent,
|
|
1042
|
+
updatedAt: base.updatedAt ?? Date.now(),
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
refreshLentaPendingPatch() {
|
|
1046
|
+
this.refreshLentaPendingCache();
|
|
1047
|
+
this.broadcast('state:patch', {
|
|
1048
|
+
lentaPendingByAgent: this.cachedLentaPendingByAgent,
|
|
1049
|
+
updatedAt: Date.now(),
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
noteLentaDelivery(agentId, storeMessages) {
|
|
1053
|
+
if (isLentaDeliveryPending(agentId, storeMessages)) {
|
|
1054
|
+
if (!this.lentaPendingSince.has(agentId)) {
|
|
1055
|
+
this.lentaPendingSince.set(agentId, Date.now());
|
|
1056
|
+
}
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
this.lentaPendingSince.delete(agentId);
|
|
1060
|
+
this.agentCompletionPush?.retryPendingContent(this.stateManager.getState());
|
|
1061
|
+
}
|
|
1062
|
+
/** Push `agent:messages` when store is ahead of last emit (subscribed + synced). */
|
|
1063
|
+
flushLentaDelivery(agentId, reason) {
|
|
1064
|
+
if (!this.jsonlIndex.getSubscribedAgents().has(agentId))
|
|
1065
|
+
return false;
|
|
1066
|
+
const state = this.stateManager.getState();
|
|
1067
|
+
const meta = this.jsonlIndex.getSubscribedAgents().get(agentId);
|
|
1068
|
+
if (!meta || !isChatSyncedWithCursor(agentId, meta.title, state))
|
|
1069
|
+
return false;
|
|
1070
|
+
const messages = this.chatDisplay.getDisplayMessages(agentId);
|
|
1071
|
+
if (!isLentaDeliveryPending(agentId, messages)) {
|
|
1072
|
+
this.lentaPendingSince.delete(agentId);
|
|
1073
|
+
return false;
|
|
1074
|
+
}
|
|
1075
|
+
const since = this.lentaPendingSince.get(agentId) ?? Date.now();
|
|
1076
|
+
if (!this.lentaPendingSince.has(agentId))
|
|
1077
|
+
this.lentaPendingSince.set(agentId, since);
|
|
1078
|
+
bridgePipelineLog({
|
|
1079
|
+
dir: 'internal',
|
|
1080
|
+
event: 'lenta:delivery:FLUSH',
|
|
1081
|
+
agentId,
|
|
1082
|
+
msgs: messages.length,
|
|
1083
|
+
detail: `reason=${reason} ageMs=${Date.now() - since}`,
|
|
1084
|
+
});
|
|
1085
|
+
this.emitLentaForAgent(agentId, {
|
|
1086
|
+
totalMessages: this.lastEmittedJsonlRows.get(agentId) ?? messages.length,
|
|
1087
|
+
reason,
|
|
1088
|
+
forceSeq: true,
|
|
1089
|
+
});
|
|
1090
|
+
this.agentCompletionPush?.retryPendingContent(this.stateManager.getState());
|
|
1091
|
+
return true;
|
|
1092
|
+
}
|
|
1093
|
+
flushPendingLentaForSubscribers(reason) {
|
|
1094
|
+
for (const agentId of this.jsonlIndex.getSubscribedAgents().keys()) {
|
|
1095
|
+
this.flushLentaDelivery(agentId, reason);
|
|
1096
|
+
}
|
|
1097
|
+
this.refreshLentaPendingPatch();
|
|
1098
|
+
}
|
|
1099
|
+
maybeForceStaleLentaDelivery() {
|
|
1100
|
+
const now = Date.now();
|
|
1101
|
+
for (const agentId of this.jsonlIndex.getSubscribedAgents().keys()) {
|
|
1102
|
+
const since = this.lentaPendingSince.get(agentId);
|
|
1103
|
+
if (since == null)
|
|
1104
|
+
continue;
|
|
1105
|
+
if (now - since < Relay.LENTA_PENDING_FORCE_MS)
|
|
1106
|
+
continue;
|
|
1107
|
+
this.flushLentaDelivery(agentId, 'delivery_flush');
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
644
1110
|
wireEvents() {
|
|
645
1111
|
this.stateManager.on('state:full', (state) => {
|
|
646
|
-
this.
|
|
1112
|
+
this.refreshLentaPendingCache();
|
|
1113
|
+
this.broadcast('state:full', this.withRelayState(state));
|
|
647
1114
|
this.emitAgentsIndex();
|
|
648
1115
|
this.observeAgentCompletionForPush();
|
|
1116
|
+
if (state.messages?.length)
|
|
1117
|
+
this.applyDomOverlayFromState(state);
|
|
1118
|
+
if (!state.agentWorking)
|
|
1119
|
+
this.flushPendingLentaForSubscribers('dom_overlay');
|
|
1120
|
+
this.maybeForceStaleLentaDelivery();
|
|
649
1121
|
});
|
|
650
1122
|
this.stateManager.on('state:patch', (patch) => {
|
|
651
|
-
this.broadcast('state:patch', this.
|
|
1123
|
+
this.broadcast('state:patch', this.withRelayState(patch));
|
|
652
1124
|
this.observeAgentCompletionForPush();
|
|
653
|
-
|
|
654
|
-
|
|
1125
|
+
const state = this.stateManager.getState();
|
|
1126
|
+
if (patch.messages?.length || state.messages?.length) {
|
|
1127
|
+
this.applyDomOverlayFromState(state);
|
|
1128
|
+
}
|
|
1129
|
+
if (patch.agentWorking === false || (!state.agentWorking && patch.agentWorking !== true)) {
|
|
1130
|
+
this.syncGeneratingFlags(state);
|
|
1131
|
+
this.flushPendingLentaForSubscribers('jsonl_live');
|
|
1132
|
+
this.refreshLentaPendingCache();
|
|
1133
|
+
}
|
|
1134
|
+
else if (state.agentWorking) {
|
|
1135
|
+
this.syncGeneratingFlags(state);
|
|
655
1136
|
}
|
|
1137
|
+
this.maybeForceStaleLentaDelivery();
|
|
656
1138
|
if (patch.sidebarRepos || patch.composerIdByTitle) {
|
|
657
1139
|
const key = JSON.stringify([patch.sidebarRepos, patch.composerIdByTitle]);
|
|
658
1140
|
if (key !== this.lastSidebarIndexKey) {
|
|
@@ -672,6 +1154,7 @@ export class Relay {
|
|
|
672
1154
|
this.jsonlIndex.on('agent:jsonl:updated', (payload) => {
|
|
673
1155
|
const total = payload.totalMessages ?? payload.messages.length;
|
|
674
1156
|
this.syncJsonlToSubscribedAgents(payload.agentId, payload.messages, total);
|
|
1157
|
+
this.writeLentaCapture(payload.agentId, 'jsonl_file');
|
|
675
1158
|
});
|
|
676
1159
|
this.jsonlIndex.on('agent:history', (history) => {
|
|
677
1160
|
const total = history.totalMessages ?? history.messages.length;
|
|
@@ -721,7 +1204,7 @@ export class Relay {
|
|
|
721
1204
|
});
|
|
722
1205
|
}
|
|
723
1206
|
pushFullStateToRemote() {
|
|
724
|
-
this.broadcast('state:full', this.
|
|
1207
|
+
this.broadcast('state:full', this.withRelayState(this.stateManager.getState()));
|
|
725
1208
|
if (this.lastJsonlIndex.updatedAt > 0) {
|
|
726
1209
|
this.emitAgentsIndex(true);
|
|
727
1210
|
return;
|
|
@@ -758,7 +1241,7 @@ export class Relay {
|
|
|
758
1241
|
this.dispatchClientEvent(event, args, reply);
|
|
759
1242
|
}
|
|
760
1243
|
onConnect(socket) {
|
|
761
|
-
socket.emit('state:full', this.
|
|
1244
|
+
socket.emit('state:full', this.withRelayState(this.stateManager.getState()));
|
|
762
1245
|
const sendIndex = (index) => {
|
|
763
1246
|
socket.emit('agents:index', mergeSidebarWithJsonl(this.stateManager.getState().sidebarRepos, index, this.stateManager.getState().composerIdByTitle));
|
|
764
1247
|
};
|
|
@@ -869,7 +1352,7 @@ export class Relay {
|
|
|
869
1352
|
this.emitJsonlLiveForAgent(agentId, full.messages, full.totalMessages);
|
|
870
1353
|
const snap = this.agentMessagesSnapshot(agentId, full.totalMessages);
|
|
871
1354
|
if (!isChatHistoryFromJsonl()) {
|
|
872
|
-
const seq = this.historySeqByAgent.get(agentId) ??
|
|
1355
|
+
const seq = this.historySeqByAgent.get(agentId) ?? 0;
|
|
873
1356
|
const ms = Date.now() - t0;
|
|
874
1357
|
console.log(`[relay] agents:history jsonl agentId=${agentId} hist=${snap.historyMessages.length} total=${full.totalMessages} ms=${ms} rid=${requestId ?? '-'}`);
|
|
875
1358
|
reply('agents:history', {
|
|
@@ -895,7 +1378,7 @@ export class Relay {
|
|
|
895
1378
|
msgs: history.messages?.length ?? 0,
|
|
896
1379
|
detail: `total=${history.totalMessages ?? '?'} ms=${ms}`,
|
|
897
1380
|
});
|
|
898
|
-
const seq = this.
|
|
1381
|
+
const seq = this.historySeqByAgent.get(history.agentId) ?? 0;
|
|
899
1382
|
reply('agents:history', {
|
|
900
1383
|
...history,
|
|
901
1384
|
historyMessages: snap.historyMessages,
|
|
@@ -949,6 +1432,26 @@ export class Relay {
|
|
|
949
1432
|
}
|
|
950
1433
|
void this.refreshDomChatOnSubscribe(agentId, title, { clear: !alreadySubscribed });
|
|
951
1434
|
}
|
|
1435
|
+
/** Load JSONL into store when DOM ingest runs but baseline empty (e.g. after bridge restart). */
|
|
1436
|
+
async ensureJsonlBaselineForAgent(agentId, state) {
|
|
1437
|
+
if (this.chatDisplay.getJsonlHistory(agentId).length > 0)
|
|
1438
|
+
return;
|
|
1439
|
+
const sub = this.jsonlIndex.getSubscribedAgents().get(agentId);
|
|
1440
|
+
try {
|
|
1441
|
+
const history = await this.jsonlIndex.loadHistory(agentId, {
|
|
1442
|
+
title: sub?.title,
|
|
1443
|
+
composerIdByTitle: state.composerIdByTitle,
|
|
1444
|
+
});
|
|
1445
|
+
if (!history.messages.length)
|
|
1446
|
+
return;
|
|
1447
|
+
this.chatDisplay.setJsonlBaseline(agentId, history.messages);
|
|
1448
|
+
this.lastEmittedJsonlRows.set(agentId, history.totalMessages);
|
|
1449
|
+
this.domExtractor.pollNow();
|
|
1450
|
+
}
|
|
1451
|
+
catch (err) {
|
|
1452
|
+
console.warn('[relay] ensureJsonlBaseline (non-fatal):', err.message);
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
952
1455
|
/** Открытие чата: JSONL baseline + focus/scroll (DOM poll только для state: working/approve). */
|
|
953
1456
|
async refreshDomChatOnSubscribe(agentId, title, opts) {
|
|
954
1457
|
try {
|
|
@@ -957,6 +1460,7 @@ export class Relay {
|
|
|
957
1460
|
this.lastEmittedJsonlRows.delete(agentId);
|
|
958
1461
|
this.lastEmittedLentaSig.delete(agentId);
|
|
959
1462
|
this.lastEmittedHistLen.delete(agentId);
|
|
1463
|
+
this.lentaPendingSince.delete(agentId);
|
|
960
1464
|
}
|
|
961
1465
|
const history = await this.jsonlIndex.loadHistory(agentId, {
|
|
962
1466
|
title,
|
|
@@ -978,6 +1482,8 @@ export class Relay {
|
|
|
978
1482
|
this.lastEmittedJsonlRows.delete(agentId);
|
|
979
1483
|
this.lastEmittedLentaSig.delete(agentId);
|
|
980
1484
|
this.lastEmittedHistLen.delete(agentId);
|
|
1485
|
+
this.lentaPendingSince.delete(agentId);
|
|
1486
|
+
this.refreshLentaPendingPatch();
|
|
981
1487
|
}
|
|
982
1488
|
}
|
|
983
1489
|
async runAgentsFocus({ agentId, title }, reply) {
|