@yeaft/webchat-agent 1.0.83 → 1.0.85
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/yeaft/web-bridge.js +44 -8
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);
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -91,6 +91,10 @@ let sessionLoadPromise = null;
|
|
|
91
91
|
|
|
92
92
|
let threadClassifier = defaultClassifyThread;
|
|
93
93
|
|
|
94
|
+
function liveConfigRoot() {
|
|
95
|
+
return session?.config?.dir || ctx.CONFIG?.yeaftDir || session?.yeaftDir;
|
|
96
|
+
}
|
|
97
|
+
|
|
94
98
|
function applyLiveLanguage(language) {
|
|
95
99
|
if (!language || typeof language !== 'string') return;
|
|
96
100
|
if (session?.config && typeof session.config === 'object') {
|
|
@@ -105,7 +109,7 @@ function applyLiveLanguage(language) {
|
|
|
105
109
|
function refreshLiveSessionConfig() {
|
|
106
110
|
if (!session) return;
|
|
107
111
|
try {
|
|
108
|
-
const freshConfig = loadConfig({ dir:
|
|
112
|
+
const freshConfig = loadConfig({ dir: liveConfigRoot() });
|
|
109
113
|
const freshModels = Array.isArray(freshConfig.availableModels) ? freshConfig.availableModels : [];
|
|
110
114
|
session.config.availableModels = freshModels;
|
|
111
115
|
if (freshConfig.language) {
|
|
@@ -317,6 +321,8 @@ const vpInboxes = new Map();
|
|
|
317
321
|
const vpDrivers = new Map();
|
|
318
322
|
/** @type {Map<string, import('./engine.js').Engine>} */
|
|
319
323
|
const vpEngines = new Map();
|
|
324
|
+
/** @type {Map<string, string>} */
|
|
325
|
+
const vpEngineConfigKeys = new Map();
|
|
320
326
|
/** @type {Map<string, AbortController>} */
|
|
321
327
|
const vpAborts = new Map();
|
|
322
328
|
|
|
@@ -404,6 +410,17 @@ function threadKey(sessionId, vpId, threadId) {
|
|
|
404
410
|
return `${sessionId}::${vpId}::${threadId || 'main'}`;
|
|
405
411
|
}
|
|
406
412
|
|
|
413
|
+
function engineConfigKey(config) {
|
|
414
|
+
return JSON.stringify({
|
|
415
|
+
model: config?.model || '',
|
|
416
|
+
primaryModel: config?.primaryModel || '',
|
|
417
|
+
modelEffort: config?.modelEffort || '',
|
|
418
|
+
fastModel: config?.fastModel || '',
|
|
419
|
+
fastModelId: config?.fastModelId || '',
|
|
420
|
+
fallbackModel: config?.fallbackModel || '',
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
407
424
|
function normalizeSessionWorkDir(workDir) {
|
|
408
425
|
return typeof workDir === 'string' && workDir.trim() ? workDir.trim() : '';
|
|
409
426
|
}
|
|
@@ -1192,7 +1209,7 @@ export function __testGroupHistory(sessionId) {
|
|
|
1192
1209
|
|
|
1193
1210
|
export function __testResolveVpEffectiveConfig(sessionId) {
|
|
1194
1211
|
if (!session) return null;
|
|
1195
|
-
const sessionConfigRoot =
|
|
1212
|
+
const sessionConfigRoot = liveConfigRoot();
|
|
1196
1213
|
return resolveSessionConfig(session.config, loadSessionConfig(sessionConfigRoot, sessionId));
|
|
1197
1214
|
}
|
|
1198
1215
|
|
|
@@ -1296,17 +1313,23 @@ function isPermissionErrorMsg(msg) {
|
|
|
1296
1313
|
*/
|
|
1297
1314
|
function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
|
|
1298
1315
|
const key = threadKey(sessionId, vpId, threadId);
|
|
1299
|
-
let eng = vpEngines.get(key);
|
|
1300
|
-
if (eng) return eng;
|
|
1301
1316
|
if (!session) throw new Error('getOrCreateVpEngine: session not loaded');
|
|
1302
1317
|
// Per-session config overlay (v1: model only). Falls back to the
|
|
1303
1318
|
// session's user-level config when no override is set. Prefer the agent-local
|
|
1304
1319
|
// config root: sessionConfigPath() resolves registered workDir-backed
|
|
1305
1320
|
// sessions from there, while still allowing a later agent-local session in
|
|
1306
1321
|
// the same bridge runtime to read its own override after a workDir-first boot.
|
|
1307
|
-
const sessionConfigRoot =
|
|
1322
|
+
const sessionConfigRoot = liveConfigRoot();
|
|
1308
1323
|
const groupCfg = loadSessionConfig(sessionConfigRoot, sessionId);
|
|
1309
1324
|
const effectiveConfig = resolveSessionConfig(session.config, groupCfg);
|
|
1325
|
+
const configKey = engineConfigKey(effectiveConfig);
|
|
1326
|
+
let eng = vpEngines.get(key);
|
|
1327
|
+
if (eng && vpEngineConfigKeys.get(key) === configKey) return eng;
|
|
1328
|
+
if (eng) {
|
|
1329
|
+
try { eng.abort?.('config_changed'); } catch { /* best-effort */ }
|
|
1330
|
+
vpEngines.delete(key);
|
|
1331
|
+
vpEngineConfigKeys.delete(key);
|
|
1332
|
+
}
|
|
1310
1333
|
eng = new Engine({
|
|
1311
1334
|
adapter: session.adapter,
|
|
1312
1335
|
trace: session.trace,
|
|
@@ -1342,6 +1365,7 @@ function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
|
|
|
1342
1365
|
}
|
|
1343
1366
|
} catch { /* coordinator is best-effort plumbing, never block engine creation */ }
|
|
1344
1367
|
vpEngines.set(key, eng);
|
|
1368
|
+
vpEngineConfigKeys.set(key, configKey);
|
|
1345
1369
|
return eng;
|
|
1346
1370
|
}
|
|
1347
1371
|
|
|
@@ -1845,6 +1869,7 @@ export async function __testResetVpState() {
|
|
|
1845
1869
|
vpInboxes.clear();
|
|
1846
1870
|
vpDrivers.clear();
|
|
1847
1871
|
vpEngines.clear();
|
|
1872
|
+
vpEngineConfigKeys.clear();
|
|
1848
1873
|
asyncTaskOwners.clear();
|
|
1849
1874
|
vpAborts.clear();
|
|
1850
1875
|
sessionContexts.clear();
|
|
@@ -2573,7 +2598,10 @@ export function handleYeaftUpdateSessionConfig(msg) {
|
|
|
2573
2598
|
// Drop cached engines so the next VP turn rebuilds with the new model.
|
|
2574
2599
|
const prefix = `${sessionId}::`;
|
|
2575
2600
|
for (const k of Array.from(vpEngines.keys())) {
|
|
2576
|
-
if (k.startsWith(prefix))
|
|
2601
|
+
if (k.startsWith(prefix)) {
|
|
2602
|
+
vpEngines.delete(k);
|
|
2603
|
+
vpEngineConfigKeys.delete(k);
|
|
2604
|
+
}
|
|
2577
2605
|
}
|
|
2578
2606
|
invalidateGroupContext(sessionId);
|
|
2579
2607
|
sendSessionCrudResult({ op: 'update_config', requestId, ok: true, sessionId, config: savedConfig });
|
|
@@ -2630,7 +2658,10 @@ export function handleYeaftDeleteSession(msg) {
|
|
|
2630
2658
|
invalidateGroupContext(sessionId);
|
|
2631
2659
|
const prefix = `${sessionId}::`;
|
|
2632
2660
|
for (const k of Array.from(vpEngines.keys())) {
|
|
2633
|
-
if (k.startsWith(prefix))
|
|
2661
|
+
if (k.startsWith(prefix)) {
|
|
2662
|
+
vpEngines.delete(k);
|
|
2663
|
+
vpEngineConfigKeys.delete(k);
|
|
2664
|
+
}
|
|
2634
2665
|
}
|
|
2635
2666
|
sendSessionCrudResult({
|
|
2636
2667
|
op: 'delete',
|
|
@@ -2675,7 +2706,10 @@ export function handleYeaftSessionRemoveMember(msg) {
|
|
|
2675
2706
|
// added back they should start with fresh per-thread state.
|
|
2676
2707
|
const removedPrefix = `${sessionId}::${vpId}::`;
|
|
2677
2708
|
for (const key of Array.from(vpEngines.keys())) {
|
|
2678
|
-
if (key.startsWith(removedPrefix))
|
|
2709
|
+
if (key.startsWith(removedPrefix)) {
|
|
2710
|
+
vpEngines.delete(key);
|
|
2711
|
+
vpEngineConfigKeys.delete(key);
|
|
2712
|
+
}
|
|
2679
2713
|
}
|
|
2680
2714
|
sendSessionCrudResult({ op: 'remove_member', requestId, ok: true, session: group });
|
|
2681
2715
|
sendSessionRosterChanged(group);
|
|
@@ -5487,6 +5521,7 @@ export function handleYeaftModelSwitch(msg) {
|
|
|
5487
5521
|
// Engines would otherwise keep the old effective config and drop newly
|
|
5488
5522
|
// selected effort values until process restart.
|
|
5489
5523
|
vpEngines.clear();
|
|
5524
|
+
vpEngineConfigKeys.clear();
|
|
5490
5525
|
asyncTaskOwners.clear();
|
|
5491
5526
|
|
|
5492
5527
|
sendSessionEvent({
|
|
@@ -5843,6 +5878,7 @@ export async function resetYeaftSession() {
|
|
|
5843
5878
|
vpInboxes.clear();
|
|
5844
5879
|
vpDrivers.clear();
|
|
5845
5880
|
vpEngines.clear();
|
|
5881
|
+
vpEngineConfigKeys.clear();
|
|
5846
5882
|
asyncTaskOwners.clear();
|
|
5847
5883
|
sessionContexts.clear();
|
|
5848
5884
|
vpCurrentTodos.clear();
|