@yeaft/webchat-agent 1.0.83 → 1.0.84
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/package.json +1 -1
- package/yeaft/dream/session-wiring.js +108 -10
package/package.json
CHANGED
|
@@ -145,7 +145,7 @@ function listConversationSessions(roots) {
|
|
|
145
145
|
for (const name of readdirSync(root)) {
|
|
146
146
|
if (name.startsWith('.')) continue;
|
|
147
147
|
try {
|
|
148
|
-
const dir =
|
|
148
|
+
const dir = sessionDir(root, name);
|
|
149
149
|
if (!hasReadableMessages(dir)) continue;
|
|
150
150
|
out.add(name);
|
|
151
151
|
} catch {
|
|
@@ -158,9 +158,10 @@ function listConversationSessions(roots) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
/**
|
|
161
|
-
* Load
|
|
162
|
-
*
|
|
163
|
-
*
|
|
161
|
+
* Load persisted conversation messages for one Yeaft Session. Supports both
|
|
162
|
+
* projections currently present on disk:
|
|
163
|
+
* - ConversationStore markdown/jsonl under `conversation/`
|
|
164
|
+
* - Session append-only audit log under `messages/*.jsonl`
|
|
164
165
|
*
|
|
165
166
|
* @param {string[]} roots session transcript roots in priority order
|
|
166
167
|
* @param {string} sessionId
|
|
@@ -169,19 +170,26 @@ function listConversationSessions(roots) {
|
|
|
169
170
|
function loadSessionConversationMessages(roots, sessionId) {
|
|
170
171
|
const byId = new Map();
|
|
171
172
|
for (const root of roots) {
|
|
172
|
-
const dir =
|
|
173
|
-
for (const m of
|
|
173
|
+
const dir = sessionDir(root, sessionId);
|
|
174
|
+
for (const m of loadSessionDiskMessages(dir, sessionId)) {
|
|
174
175
|
if (!byId.has(m.id)) byId.set(m.id, m);
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
return [...byId.values()].sort(compareMessagesBySeq);
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
function
|
|
181
|
-
return join(root, safeDirComponent(sessionId)
|
|
181
|
+
function sessionDir(root, sessionId) {
|
|
182
|
+
return join(root, safeDirComponent(sessionId));
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
function hasReadableMessages(
|
|
185
|
+
function hasReadableMessages(dir) {
|
|
186
|
+
const conversationDir = join(dir, 'conversation');
|
|
187
|
+
if (hasMarkdownMessages(conversationDir)) return true;
|
|
188
|
+
if (hasJsonlMessages(join(conversationDir, 'segments'))) return true;
|
|
189
|
+
return hasJsonlMessages(join(dir, 'messages'));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function hasMarkdownMessages(conversationDir) {
|
|
185
193
|
return ['messages', 'cold'].some(kind => {
|
|
186
194
|
const dir = join(conversationDir, kind);
|
|
187
195
|
try {
|
|
@@ -192,8 +200,31 @@ function hasReadableMessages(conversationDir) {
|
|
|
192
200
|
});
|
|
193
201
|
}
|
|
194
202
|
|
|
203
|
+
function hasJsonlMessages(dir) {
|
|
204
|
+
try {
|
|
205
|
+
return statSync(dir).isDirectory() && readdirSync(dir).some(f => f.endsWith('.jsonl'));
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function loadSessionDiskMessages(dir, sessionId) {
|
|
212
|
+
const conversationDir = join(dir, 'conversation');
|
|
213
|
+
return [
|
|
214
|
+
// Prefer the canonical ConversationStore projection when it exists: it has
|
|
215
|
+
// normalized role/content/tool metadata. The session append-only audit log
|
|
216
|
+
// is still load-bearing for sessions created before/while conversation rows
|
|
217
|
+
// were being migrated to JSONL.
|
|
218
|
+
...loadConversationDirMessages(conversationDir),
|
|
219
|
+
...loadSessionJsonlMessages(join(dir, 'messages'), sessionId),
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
|
|
195
223
|
function loadConversationDirMessages(conversationDir) {
|
|
196
|
-
return [
|
|
224
|
+
return [
|
|
225
|
+
...['cold', 'messages'].flatMap(kind => loadMessageDir(join(conversationDir, kind))),
|
|
226
|
+
...loadConversationJsonlMessages(join(conversationDir, 'segments')),
|
|
227
|
+
];
|
|
197
228
|
}
|
|
198
229
|
|
|
199
230
|
function loadMessageDir(dir) {
|
|
@@ -207,6 +238,73 @@ function loadMessageDir(dir) {
|
|
|
207
238
|
.filter(m => m && m.id);
|
|
208
239
|
}
|
|
209
240
|
|
|
241
|
+
function loadConversationJsonlMessages(dir) {
|
|
242
|
+
return loadJsonlDir(dir, normalizeConversationJsonlMessage);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function loadSessionJsonlMessages(dir, sessionId) {
|
|
246
|
+
return loadJsonlDir(dir, row => normalizeSessionJsonlMessage(row, sessionId));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function loadJsonlDir(dir, normalize) {
|
|
250
|
+
if (!existsSync(dir)) return [];
|
|
251
|
+
let files = [];
|
|
252
|
+
try {
|
|
253
|
+
files = readdirSync(dir).filter(f => f.endsWith('.jsonl')).sort();
|
|
254
|
+
} catch {
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
const out = [];
|
|
258
|
+
for (const file of files) {
|
|
259
|
+
let raw = '';
|
|
260
|
+
try { raw = readFileSync(join(dir, file), 'utf8'); } catch { continue; }
|
|
261
|
+
for (const line of raw.split('\n')) {
|
|
262
|
+
if (!line) continue;
|
|
263
|
+
try {
|
|
264
|
+
const msg = normalize(JSON.parse(line));
|
|
265
|
+
if (msg && msg.id) out.push(msg);
|
|
266
|
+
} catch {
|
|
267
|
+
// Skip corrupt rows; one bad JSONL line must not make Dream blind.
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function normalizeConversationJsonlMessage(row) {
|
|
275
|
+
if (!row || typeof row !== 'object' || !row.id) return null;
|
|
276
|
+
return {
|
|
277
|
+
id: row.id,
|
|
278
|
+
role: row.role || 'assistant',
|
|
279
|
+
content: stringifyMessageBody(row.content ?? row.text ?? ''),
|
|
280
|
+
time: row.time || row.ts || '',
|
|
281
|
+
sessionId: row.sessionId || null,
|
|
282
|
+
speakerVpId: row.speakerVpId || null,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function normalizeSessionJsonlMessage(row, sessionId) {
|
|
287
|
+
if (!row || typeof row !== 'object' || !row.id) return null;
|
|
288
|
+
const role = row.role || (row.from === 'user' ? 'user' : 'assistant');
|
|
289
|
+
const speakerVpId = role === 'assistant'
|
|
290
|
+
? (row.speakerVpId || row.meta?.senderVpId || (row.from && row.from !== 'user' ? row.from : null))
|
|
291
|
+
: null;
|
|
292
|
+
return {
|
|
293
|
+
id: row.id,
|
|
294
|
+
role,
|
|
295
|
+
content: stringifyMessageBody(row.content ?? row.text ?? ''),
|
|
296
|
+
time: row.time || row.ts || '',
|
|
297
|
+
sessionId,
|
|
298
|
+
speakerVpId,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function stringifyMessageBody(value) {
|
|
303
|
+
if (typeof value === 'string') return value;
|
|
304
|
+
if (value == null) return '';
|
|
305
|
+
try { return JSON.stringify(value); } catch { return String(value); }
|
|
306
|
+
}
|
|
307
|
+
|
|
210
308
|
function compareMessagesBySeq(a, b) {
|
|
211
309
|
const sa = parseSeqFromId(a?.id);
|
|
212
310
|
const sb = parseSeqFromId(b?.id);
|