@yeaft/webchat-agent 1.0.91 → 1.0.94
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/output-snapshot.js +21 -2
- package/yeaft/dream/runner.js +10 -4
- package/yeaft/dream/session-wiring.js +48 -28
- package/yeaft/dream/state.js +13 -0
package/package.json
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
import { join } from 'node:path';
|
|
11
11
|
|
|
12
12
|
import { readMemory, readSummary } from '../memory/store.js';
|
|
13
|
-
import { readSessionState } from './state.js';
|
|
13
|
+
import { readDreamError, readSessionState } from './state.js';
|
|
14
|
+
import { buildRunDreamOpts } from './session-wiring.js';
|
|
14
15
|
|
|
15
16
|
export const DREAM_SNAPSHOT_TEXT_LIMIT = 6000;
|
|
16
17
|
|
|
@@ -32,7 +33,7 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
32
33
|
const scope = `sessions/${sessionId}`;
|
|
33
34
|
const memoryScope = { kind: 'session', id: sessionId };
|
|
34
35
|
const root = join(sessionLike.yeaftDir, 'memory');
|
|
35
|
-
const [memoryRaw, summaryRaw, state] = await Promise.all([
|
|
36
|
+
const [memoryRaw, summaryRaw, state, lastError] = await Promise.all([
|
|
36
37
|
readMemory(memoryScope, { root }).catch(() => ''),
|
|
37
38
|
readSummary(memoryScope, { root }).catch(() => ''),
|
|
38
39
|
readSessionState(root, sessionId).catch(() => ({
|
|
@@ -40,7 +41,9 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
40
41
|
lastDreamAt: null,
|
|
41
42
|
messageCount: 0,
|
|
42
43
|
})),
|
|
44
|
+
readDreamError(root, scope).catch(() => null),
|
|
43
45
|
]);
|
|
46
|
+
const totalMessageCount = await countSessionMessages(sessionLike, sessionId);
|
|
44
47
|
const memory = truncateDreamText(memoryRaw);
|
|
45
48
|
const summary = truncateDreamText(summaryRaw);
|
|
46
49
|
return {
|
|
@@ -50,10 +53,26 @@ export async function buildDreamOutputSnapshot(sessionLike, sessionId) {
|
|
|
50
53
|
lastDreamAt: state?.lastDreamAt || null,
|
|
51
54
|
lastDreamMessageId: state?.lastDreamMessageId || null,
|
|
52
55
|
messageCount: Number.isFinite(state?.messageCount) ? state.messageCount : 0,
|
|
56
|
+
totalMessageCount,
|
|
53
57
|
hasOutput: !!(memoryRaw || summaryRaw),
|
|
58
|
+
lastError,
|
|
54
59
|
memoryText: memory.text,
|
|
55
60
|
memoryTruncated: memory.truncated,
|
|
56
61
|
summaryText: summary.text,
|
|
57
62
|
summaryTruncated: summary.truncated,
|
|
58
63
|
};
|
|
59
64
|
}
|
|
65
|
+
|
|
66
|
+
async function countSessionMessages(sessionLike, sessionId) {
|
|
67
|
+
try {
|
|
68
|
+
const opts = buildRunDreamOpts({
|
|
69
|
+
yeaftDir: sessionLike.yeaftDir,
|
|
70
|
+
config: sessionLike.config || {},
|
|
71
|
+
adapter: { call: async () => ({ text: '', usage: {} }) },
|
|
72
|
+
});
|
|
73
|
+
const n = await opts.countMessages(sessionId);
|
|
74
|
+
return Number.isFinite(n) ? n : 0;
|
|
75
|
+
} catch {
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
78
|
+
}
|
package/yeaft/dream/runner.js
CHANGED
|
@@ -45,7 +45,7 @@ import { listScopes, readSummary } from '../memory/store.js';
|
|
|
45
45
|
import {
|
|
46
46
|
DEFAULT_LIMITS,
|
|
47
47
|
} from './limits.js';
|
|
48
|
-
import { readSessionState, writeSessionState, writeDreamError } from './state.js';
|
|
48
|
+
import { clearDreamError, readSessionState, writeSessionState, writeDreamError } from './state.js';
|
|
49
49
|
import { segmentDiff, truncateMessage, estimateMessagesTokens } from './segment.js';
|
|
50
50
|
import { triageGroupSegments } from './triage.js';
|
|
51
51
|
import { mergeByTarget } from './merge.js';
|
|
@@ -166,7 +166,7 @@ export async function runDream(opts) {
|
|
|
166
166
|
sessionId,
|
|
167
167
|
segments,
|
|
168
168
|
topicSummaries,
|
|
169
|
-
llm: opts.llm,
|
|
169
|
+
llm: dreamLlmForSession(opts.llm, sessionId),
|
|
170
170
|
onProgress,
|
|
171
171
|
language: opts.language,
|
|
172
172
|
});
|
|
@@ -208,13 +208,14 @@ export async function runDream(opts) {
|
|
|
208
208
|
const r = await applyMergedTarget(merged, {
|
|
209
209
|
root: opts.root,
|
|
210
210
|
ts,
|
|
211
|
-
llm: opts.llm,
|
|
211
|
+
llm: dreamLlmForSession(opts.llm, sourceSessionId(merged)),
|
|
212
212
|
limits,
|
|
213
213
|
nowIso: opts.nowIso || (() => nowIso),
|
|
214
214
|
onProgress,
|
|
215
215
|
siblingTopicsFor: opts.siblingTopicsFor,
|
|
216
216
|
language: opts.language,
|
|
217
217
|
});
|
|
218
|
+
await clearDreamError(opts.root, merged.target);
|
|
218
219
|
targetsReport.push({ ...r, sources: merged.sources.length, status: 'done' });
|
|
219
220
|
} catch (err) {
|
|
220
221
|
targetsReport.push({
|
|
@@ -252,11 +253,12 @@ export async function runDream(opts) {
|
|
|
252
253
|
sessionId: triage.sessionId,
|
|
253
254
|
messages: triage.diff,
|
|
254
255
|
targets,
|
|
255
|
-
llm: opts.llm,
|
|
256
|
+
llm: dreamLlmForSession(opts.llm, triage.sessionId),
|
|
256
257
|
language: opts.language,
|
|
257
258
|
nowIso: opts.nowIso || (() => nowIso),
|
|
258
259
|
segmentIndex: opts.segmentIndex || null,
|
|
259
260
|
});
|
|
261
|
+
await clearDreamError(opts.root, `sessions/${triage.sessionId}`);
|
|
260
262
|
segmentReports.push({ sessionId: triage.sessionId, status: 'done', ...r });
|
|
261
263
|
onProgress({ phase: 'extract-segments', sessionId: triage.sessionId, status: 'done', ...r });
|
|
262
264
|
} catch (err) {
|
|
@@ -335,6 +337,10 @@ function deriveSessionFilter(filter) {
|
|
|
335
337
|
return sessions.length > 0 ? new Set(sessions) : null;
|
|
336
338
|
}
|
|
337
339
|
|
|
340
|
+
function dreamLlmForSession(llm, sessionId) {
|
|
341
|
+
return (req = {}) => llm({ ...req, sessionId: req.sessionId || sessionId || null });
|
|
342
|
+
}
|
|
343
|
+
|
|
338
344
|
function sourceSessionId(mergedTarget) {
|
|
339
345
|
const src = Array.isArray(mergedTarget?.sources) ? mergedTarget.sources[0] : null;
|
|
340
346
|
return src && typeof src.sessionId === 'string' ? src.sessionId : '';
|
|
@@ -43,6 +43,8 @@ import { join } from 'path';
|
|
|
43
43
|
import { runDream } from './runner.js';
|
|
44
44
|
import { createDreamScheduler } from './schedule.js';
|
|
45
45
|
import { parseMessage, parseSeqFromId } from '../conversation/persist.js';
|
|
46
|
+
import { loadSessionConfig, resolveSessionConfig } from '../sessions/session-config.js';
|
|
47
|
+
import { listSessions as listSessionMetas } from '../sessions/session-store.js';
|
|
46
48
|
import { readSessionState } from './state.js';
|
|
47
49
|
import { DREAM_NUDGE_AFTER_MESSAGES, DREAM_INTERVAL_HOURS } from './limits.js';
|
|
48
50
|
|
|
@@ -83,7 +85,7 @@ export function buildRunDreamOpts(session, onProgress) {
|
|
|
83
85
|
segmentIndex: session.memoryIndex || null,
|
|
84
86
|
llm: makeLlm(session),
|
|
85
87
|
listSessions: async () => {
|
|
86
|
-
try { return
|
|
88
|
+
try { return listRegisteredSessions([sessionConversationsRoot, legacySessionConversationsRoot]); }
|
|
87
89
|
catch { return []; }
|
|
88
90
|
},
|
|
89
91
|
countMessages: async (sessionId) => {
|
|
@@ -132,15 +134,25 @@ function translateSessionConversationMessage(m) {
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
|
-
* Enumerate
|
|
136
|
-
*
|
|
137
|
+
* Enumerate sessions from authoritative session metadata first, then fall back
|
|
138
|
+
* to legacy transcript-only directories. Message presence is not used as the
|
|
139
|
+
* source of truth: a stale/corrupt JSONL index must not make Dream forget that
|
|
140
|
+
* a Session exists. The runner's .dream-state cursor decides whether there is
|
|
141
|
+
* anything new to process.
|
|
137
142
|
*
|
|
138
143
|
* @param {string[]} roots session transcript roots in priority order
|
|
139
144
|
* @returns {string[]}
|
|
140
145
|
*/
|
|
141
|
-
function
|
|
146
|
+
function listRegisteredSessions(roots) {
|
|
142
147
|
const out = new Set();
|
|
143
|
-
|
|
148
|
+
const [sessionsRoot, ...legacyRoots] = roots;
|
|
149
|
+
try {
|
|
150
|
+
for (const meta of listSessionMetas(sessionsRoot)) {
|
|
151
|
+
if (meta?.id) out.add(meta.id);
|
|
152
|
+
}
|
|
153
|
+
} catch { /* fall through to legacy fallback */ }
|
|
154
|
+
|
|
155
|
+
for (const root of legacyRoots) {
|
|
144
156
|
if (!existsSync(root)) continue;
|
|
145
157
|
for (const name of readdirSync(root)) {
|
|
146
158
|
if (name.startsWith('.')) continue;
|
|
@@ -149,8 +161,8 @@ function listConversationSessions(roots) {
|
|
|
149
161
|
if (!hasReadableMessages(dir)) continue;
|
|
150
162
|
out.add(name);
|
|
151
163
|
} catch {
|
|
152
|
-
// Ignore partial
|
|
153
|
-
//
|
|
164
|
+
// Ignore partial old directories. Canonical sessions come from
|
|
165
|
+
// sessions/<id>/session.json above; this branch is read-only legacy.
|
|
154
166
|
}
|
|
155
167
|
}
|
|
156
168
|
}
|
|
@@ -210,14 +222,13 @@ function hasJsonlMessages(dir) {
|
|
|
210
222
|
|
|
211
223
|
function loadSessionDiskMessages(dir, sessionId) {
|
|
212
224
|
const conversationDir = join(dir, 'conversation');
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
];
|
|
225
|
+
// Canonical conversation history is the source Dream needs: it contains the
|
|
226
|
+
// full user/assistant timeline. The session audit log under messages/ is a
|
|
227
|
+
// fallback for older or partially migrated sessions, not a second source to
|
|
228
|
+
// union in — otherwise the same user turn can appear twice with different ids.
|
|
229
|
+
const canonical = loadConversationDirMessages(conversationDir);
|
|
230
|
+
if (canonical.length > 0) return canonical;
|
|
231
|
+
return loadSessionJsonlMessages(join(dir, 'messages'), sessionId);
|
|
221
232
|
}
|
|
222
233
|
|
|
223
234
|
function loadConversationDirMessages(conversationDir) {
|
|
@@ -323,9 +334,13 @@ function safeDirComponent(s) {
|
|
|
323
334
|
* @param {Object} session
|
|
324
335
|
*/
|
|
325
336
|
function makeLlm(session) {
|
|
326
|
-
return async ({ pass, prompt, system }) => {
|
|
337
|
+
return async ({ pass, prompt, system, sessionId }) => {
|
|
327
338
|
const adapter = session.adapter;
|
|
328
|
-
const
|
|
339
|
+
const effectiveConfig = resolveDreamSessionConfig(session, sessionId);
|
|
340
|
+
const model = effectiveConfig?.model || effectiveConfig?.primaryModel;
|
|
341
|
+
if (!model) {
|
|
342
|
+
throw new Error(`dream: no session model configured (pass=${pass}, sessionId=${sessionId || 'unknown'})`);
|
|
343
|
+
}
|
|
329
344
|
if (!adapter || typeof adapter.call !== 'function') {
|
|
330
345
|
throw new Error(`dream: no adapter.call available (pass=${pass})`);
|
|
331
346
|
}
|
|
@@ -335,7 +350,7 @@ function makeLlm(session) {
|
|
|
335
350
|
session._dreamLoopCounter = (session._dreamLoopCounter || 0) + 1;
|
|
336
351
|
const loopNumber = session._dreamLoopCounter;
|
|
337
352
|
const turnId = session._dreamTurnId || 'dream';
|
|
338
|
-
const effectiveSystem = system || (String(
|
|
353
|
+
const effectiveSystem = system || (String(effectiveConfig?.language || '').toLowerCase().startsWith('zh')
|
|
339
354
|
? `你是梦境流水线 — pass: ${pass}。请用中文生成自然语言内容;JSON key 保持英文。`
|
|
340
355
|
: `You are the dream pipeline — pass: ${pass}.`);
|
|
341
356
|
|
|
@@ -344,6 +359,7 @@ function makeLlm(session) {
|
|
|
344
359
|
system: effectiveSystem,
|
|
345
360
|
messages: [{ role: 'user', content: prompt }],
|
|
346
361
|
maxTokens: 2048,
|
|
362
|
+
modelEffort: effectiveConfig?.modelEffort || undefined,
|
|
347
363
|
});
|
|
348
364
|
|
|
349
365
|
// Emit complete loop event (request + response) to the debug panel.
|
|
@@ -366,6 +382,7 @@ function makeLlm(session) {
|
|
|
366
382
|
loopNumber,
|
|
367
383
|
pass,
|
|
368
384
|
model: model || 'unknown',
|
|
385
|
+
sessionId: sessionId || null,
|
|
369
386
|
systemPrompt: effectiveSystem,
|
|
370
387
|
messages: [{ role: 'user', content: prompt }],
|
|
371
388
|
response: typeof r?.text === 'string' ? r.text : '',
|
|
@@ -387,6 +404,15 @@ function makeLlm(session) {
|
|
|
387
404
|
}
|
|
388
405
|
|
|
389
406
|
|
|
407
|
+
function resolveDreamSessionConfig(session, sessionId) {
|
|
408
|
+
const base = session?.config || {};
|
|
409
|
+
if (!sessionId || !session?.yeaftDir) return { ...base };
|
|
410
|
+
const sessionConfig = loadSessionConfig(session.yeaftDir, sessionId);
|
|
411
|
+
if (!sessionConfig || Object.keys(sessionConfig).length === 0) return { ...base };
|
|
412
|
+
return resolveSessionConfig(base, sessionConfig);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
|
|
390
416
|
function stampDreamScope(session, evt) {
|
|
391
417
|
if (!evt || typeof evt !== 'object') return evt;
|
|
392
418
|
if (evt.sessionId || !session?._dreamActiveGroupId) return evt;
|
|
@@ -646,7 +672,7 @@ export async function bootInitEmptyGroups(args) {
|
|
|
646
672
|
if (!args || !args.memoryIndex || !args.dreamScheduler) return out;
|
|
647
673
|
const sessionsRoot = join(args.yeaftDir, 'sessions');
|
|
648
674
|
let ids;
|
|
649
|
-
try { ids =
|
|
675
|
+
try { ids = listSessionMetas(sessionsRoot).map(g => g.id); }
|
|
650
676
|
catch { return out; }
|
|
651
677
|
const empty = [];
|
|
652
678
|
for (const gid of ids) {
|
|
@@ -656,11 +682,7 @@ export async function bootInitEmptyGroups(args) {
|
|
|
656
682
|
if (segCount > 0) continue;
|
|
657
683
|
let hasMessages = false;
|
|
658
684
|
try {
|
|
659
|
-
|
|
660
|
-
// Any message at all is enough — pull the first record off the
|
|
661
|
-
// iterator and stop.
|
|
662
|
-
const first = h.streamMessages().next();
|
|
663
|
-
hasMessages = !first.done;
|
|
685
|
+
hasMessages = loadSessionConversationMessages([sessionsRoot], gid).length > 0;
|
|
664
686
|
} catch { continue; }
|
|
665
687
|
if (!hasMessages) continue;
|
|
666
688
|
empty.push(`sessions/${gid}`);
|
|
@@ -716,7 +738,7 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
716
738
|
const now = args.now ?? Date.now();
|
|
717
739
|
|
|
718
740
|
let sessionIds;
|
|
719
|
-
try { sessionIds =
|
|
741
|
+
try { sessionIds = listSessionMetas(sessionsRoot).map(g => g.id); }
|
|
720
742
|
catch { return out; }
|
|
721
743
|
|
|
722
744
|
// Find the newest lastDreamAt across all groups.
|
|
@@ -732,9 +754,7 @@ export async function bootCatchUpStaleDream(args) {
|
|
|
732
754
|
}
|
|
733
755
|
if (!anyTraffic) {
|
|
734
756
|
try {
|
|
735
|
-
|
|
736
|
-
const first = h.streamMessages().next();
|
|
737
|
-
if (!first.done) anyTraffic = true;
|
|
757
|
+
if (loadSessionConversationMessages([sessionsRoot], gid).length > 0) anyTraffic = true;
|
|
738
758
|
} catch { /* keep going */ }
|
|
739
759
|
}
|
|
740
760
|
}
|
package/yeaft/dream/state.js
CHANGED
|
@@ -187,6 +187,19 @@ export async function writeDreamError(root, scope, info) {
|
|
|
187
187
|
}
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Clear a scope's last Dream error after a successful pass. Best-effort: a
|
|
192
|
+
* missing file is fine and I/O failure must not turn success into failure.
|
|
193
|
+
*
|
|
194
|
+
* @param {string} root
|
|
195
|
+
* @param {string} scope
|
|
196
|
+
* @returns {Promise<void>}
|
|
197
|
+
*/
|
|
198
|
+
export async function clearDreamError(root, scope) {
|
|
199
|
+
try { await fsp.unlink(join(scopeDirFor(root, scope), ERROR_FILE)); }
|
|
200
|
+
catch { /* best-effort */ }
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
/**
|
|
191
204
|
* Read the last dream error JSON for a scope, or null if absent. Used
|
|
192
205
|
* by the debug panel and by tests. Tolerates a malformed file by
|