@yeaft/webchat-agent 1.0.290 → 1.0.292
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +94 -90
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/yeaft/engine.js +64 -17
- package/yeaft/memory/preflow.js +8 -2
- package/yeaft/sessions/pre-flow.js +5 -0
- package/yeaft/web-bridge.js +1 -0
|
Binary file
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -84,6 +84,8 @@ const AMS_ADJUST_TIMEOUT_MS = 30_000;
|
|
|
84
84
|
/** Maximum silence while a visible turn waits for a result-producing task. */
|
|
85
85
|
const DEFAULT_ASYNC_TASK_WAIT_TIMEOUT_MS = 120_000;
|
|
86
86
|
|
|
87
|
+
const DEFAULT_MEMORY_RECALL_LIMIT = 8;
|
|
88
|
+
|
|
87
89
|
// ─── LLM retry policy defaults ──────────────────────────────────
|
|
88
90
|
// Hard-coded floor / ceiling for retry behaviour. The engine reads the
|
|
89
91
|
// effective policy from `config.llmRetry` so users can dial these via
|
|
@@ -442,6 +444,47 @@ function isZhRuntimeLanguage(language) {
|
|
|
442
444
|
return String(language || '').toLowerCase().startsWith('zh');
|
|
443
445
|
}
|
|
444
446
|
|
|
447
|
+
function resolveMemoryRecallLimit(config) {
|
|
448
|
+
const raw = config?.memoryRecallLimit ?? config?.dreamMemoryRecallLimit;
|
|
449
|
+
if (!Number.isFinite(raw) || raw <= 0) return DEFAULT_MEMORY_RECALL_LIMIT;
|
|
450
|
+
return Math.max(1, Math.floor(raw));
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function loadedMemoryDebugEntries(snapshot) {
|
|
454
|
+
const snap = snapshot || {};
|
|
455
|
+
return [
|
|
456
|
+
...loadedResidentDebugEntries(snap.resident || []),
|
|
457
|
+
...loadedSegmentDebugEntries(snap.recent || [], 'recent'),
|
|
458
|
+
...loadedSegmentDebugEntries(snap.onDemand || [], 'onDemand'),
|
|
459
|
+
];
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function loadedResidentDebugEntries(entries) {
|
|
463
|
+
return (entries || []).map((entry, index) => ({
|
|
464
|
+
id: `resident:${entry.scope || index}`,
|
|
465
|
+
layer: 'resident',
|
|
466
|
+
scope: entry.scope || null,
|
|
467
|
+
label: memoryScopeLabel(entry.scope || ''),
|
|
468
|
+
kind: 'summary',
|
|
469
|
+
score: null,
|
|
470
|
+
tags: [],
|
|
471
|
+
body: entry.summary || '',
|
|
472
|
+
})).filter(entry => entry.body);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function loadedSegmentDebugEntries(segments, layer) {
|
|
476
|
+
return (segments || []).map((seg, index) => ({
|
|
477
|
+
id: seg.id || `${layer}:${index}`,
|
|
478
|
+
layer,
|
|
479
|
+
scope: seg.scope || null,
|
|
480
|
+
label: memoryScopeLabel(seg.scope || ''),
|
|
481
|
+
kind: seg.kind || null,
|
|
482
|
+
score: typeof seg.score === 'number' ? seg.score : null,
|
|
483
|
+
tags: Array.isArray(seg.tags) ? seg.tags : [],
|
|
484
|
+
body: seg.body || '',
|
|
485
|
+
})).filter(entry => entry.body);
|
|
486
|
+
}
|
|
487
|
+
|
|
445
488
|
export class Engine {
|
|
446
489
|
/** @type {import('./llm/adapter.js').LLMAdapter} */
|
|
447
490
|
#adapter;
|
|
@@ -906,6 +949,7 @@ export class Engine {
|
|
|
906
949
|
* ownVpId: string|null,
|
|
907
950
|
* scopes: string[],
|
|
908
951
|
* snapshotBlock: string,
|
|
952
|
+
* snapshot: import('./memory/ams.js').AmsSnapshot,
|
|
909
953
|
* residentEntries: Array<{scope:string, summary:string}>,
|
|
910
954
|
* } | null}
|
|
911
955
|
*/
|
|
@@ -938,14 +982,15 @@ export class Engine {
|
|
|
938
982
|
ams.setOnDemand(segs);
|
|
939
983
|
|
|
940
984
|
// (c) Snapshot — render the AMS layers as a single prompt block.
|
|
941
|
-
const
|
|
985
|
+
const snapshot = ams.snapshot({ userMsg: args.userMsg || '' });
|
|
986
|
+
const snapshotBlock = this.#renderAmsSnapshot(snapshot, this.#config.language || 'en');
|
|
942
987
|
|
|
943
988
|
const scopes = buildRelevantScopes({
|
|
944
989
|
sessionId: args.sessionId,
|
|
945
990
|
vpId: ownVpId,
|
|
946
991
|
});
|
|
947
992
|
|
|
948
|
-
return { ams, sessionKey, ownVpId, scopes, snapshotBlock, residentEntries };
|
|
993
|
+
return { ams, sessionKey, ownVpId, scopes, snapshotBlock, snapshot, residentEntries };
|
|
949
994
|
}
|
|
950
995
|
|
|
951
996
|
/**
|
|
@@ -953,13 +998,11 @@ export class Engine {
|
|
|
953
998
|
* injection. Mirrors the heading style of the existing memory blocks
|
|
954
999
|
* so the LLM sees a consistent layout.
|
|
955
1000
|
*
|
|
956
|
-
* @param {import('./memory/ams.js').
|
|
1001
|
+
* @param {import('./memory/ams.js').AmsSnapshot} snap
|
|
957
1002
|
* @param {string} [language]
|
|
958
|
-
* @param {string} [userMsg]
|
|
959
1003
|
* @returns {string}
|
|
960
1004
|
*/
|
|
961
|
-
#renderAmsSnapshot(
|
|
962
|
-
const snap = ams.snapshot({ userMsg });
|
|
1005
|
+
#renderAmsSnapshot(snap, language = 'en') {
|
|
963
1006
|
if (!snap) return '';
|
|
964
1007
|
const parts = [];
|
|
965
1008
|
if (snap.resident.length === 0 && snap.recent.length === 0 && snap.onDemand.length === 0) {
|
|
@@ -1342,7 +1385,7 @@ export class Engine {
|
|
|
1342
1385
|
* @returns {Promise<{ profile: string, entries: object[], formatted: string }|null>}
|
|
1343
1386
|
*/
|
|
1344
1387
|
async #recallMemory(prompt, ctx = {}) {
|
|
1345
|
-
const memory = { profile: '', entries: [], formatted: '' };
|
|
1388
|
+
const memory = { profile: '', entries: [], formatted: '', meta: {} };
|
|
1346
1389
|
if (!this.#memoryIndex) return memory;
|
|
1347
1390
|
try {
|
|
1348
1391
|
const result = runMemoryPreflow(this.#memoryIndex, {
|
|
@@ -1351,11 +1394,13 @@ export class Engine {
|
|
|
1351
1394
|
chatId: ctx.chatId || this.#chatId,
|
|
1352
1395
|
vpId: ctx.vpId,
|
|
1353
1396
|
extraScopes: ctx.extraScopes,
|
|
1397
|
+
pickLimit: resolveMemoryRecallLimit(this.#config),
|
|
1354
1398
|
fallbackOnEmpty: true,
|
|
1355
1399
|
});
|
|
1356
1400
|
memory.profile = result.profile || '';
|
|
1357
1401
|
memory.entries = result.entries || [];
|
|
1358
1402
|
memory.formatted = result.formatted || '';
|
|
1403
|
+
memory.meta = result.meta || {};
|
|
1359
1404
|
} catch {
|
|
1360
1405
|
// Fail soft — empty injection.
|
|
1361
1406
|
}
|
|
@@ -2029,6 +2074,7 @@ export class Engine {
|
|
|
2029
2074
|
if (amsContext && amsContext.snapshotBlock) {
|
|
2030
2075
|
memoryInjection = amsContext.snapshotBlock;
|
|
2031
2076
|
}
|
|
2077
|
+
const loadedMemoryForDebug = loadedMemoryDebugEntries(amsContext?.snapshot);
|
|
2032
2078
|
|
|
2033
2079
|
// Diagnostic payload for the Dream debug panel. The full AMS Resident
|
|
2034
2080
|
// layer can include user and per-VP summaries, but the browser-facing
|
|
@@ -2282,19 +2328,20 @@ export class Engine {
|
|
|
2282
2328
|
yield { type: 'skill_error', turnId: queryTurnId, skillName: explicitSkillName, message: skillResolutionError };
|
|
2283
2329
|
}
|
|
2284
2330
|
|
|
2285
|
-
// Surface memory
|
|
2286
|
-
//
|
|
2287
|
-
//
|
|
2288
|
-
|
|
2289
|
-
if (recallResult && Array.isArray(recallResult.entries) && recallResult.entries.length > 0) {
|
|
2331
|
+
// Surface the exact memory that entered the prompt. This must be based on
|
|
2332
|
+
// the AMS snapshot, not raw FTS candidates, otherwise debug can claim memory
|
|
2333
|
+
// was loaded even when prompt cleanup, dedupe, or token budget dropped it.
|
|
2334
|
+
if (loadedMemoryForDebug.length > 0) {
|
|
2290
2335
|
yield {
|
|
2291
2336
|
type: 'memory_used',
|
|
2292
2337
|
turnId: queryTurnId,
|
|
2293
|
-
loaded:
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2338
|
+
loaded: loadedMemoryForDebug,
|
|
2339
|
+
meta: {
|
|
2340
|
+
recallLimit: resolveMemoryRecallLimit(this.#config),
|
|
2341
|
+
recallCandidates: Number.isFinite(recallResult?.meta?.hitCount)
|
|
2342
|
+
? recallResult.meta.hitCount
|
|
2343
|
+
: (recallResult && Array.isArray(recallResult.entries) ? recallResult.entries.length : 0),
|
|
2344
|
+
},
|
|
2298
2345
|
};
|
|
2299
2346
|
}
|
|
2300
2347
|
|
package/yeaft/memory/preflow.js
CHANGED
|
@@ -19,6 +19,8 @@ import { extractKeywords } from './keywords.js';
|
|
|
19
19
|
import { approxTokens } from './budget.js';
|
|
20
20
|
import { isVpForeign } from './store.js';
|
|
21
21
|
|
|
22
|
+
export const DEFAULT_PICK_LIMIT = 8;
|
|
23
|
+
|
|
22
24
|
/**
|
|
23
25
|
* @typedef {object} PreflowOptions
|
|
24
26
|
* @property {string} userMsg
|
|
@@ -27,6 +29,7 @@ import { isVpForeign } from './store.js';
|
|
|
27
29
|
* @property {string[]} [currentTags] tags from the current group/feature context
|
|
28
30
|
* @property {number} [topK] max FTS rows to fetch (default 50)
|
|
29
31
|
* @property {number} [budgetTokens] onDemand budget (caller-supplied)
|
|
32
|
+
* @property {number} [pickLimit] max picked segments (default 8)
|
|
30
33
|
*/
|
|
31
34
|
|
|
32
35
|
/**
|
|
@@ -54,6 +57,8 @@ export function runPreflow(index, opts) {
|
|
|
54
57
|
const topK = Number.isFinite(opts.topK) && opts.topK > 0 ? opts.topK : 50;
|
|
55
58
|
const budgetTokens = Number.isFinite(opts.budgetTokens) && opts.budgetTokens > 0
|
|
56
59
|
? opts.budgetTokens : Infinity;
|
|
60
|
+
const pickLimit = Number.isFinite(opts.pickLimit) && opts.pickLimit > 0
|
|
61
|
+
? Math.floor(opts.pickLimit) : DEFAULT_PICK_LIMIT;
|
|
57
62
|
|
|
58
63
|
const keywords = extractKeywords(userMsg);
|
|
59
64
|
if (keywords.length === 0) {
|
|
@@ -80,7 +85,7 @@ export function runPreflow(index, opts) {
|
|
|
80
85
|
let dropped = 0;
|
|
81
86
|
for (const h of reranked) {
|
|
82
87
|
const tk = approxTokens(h.body);
|
|
83
|
-
if (cost + tk <= budgetTokens) {
|
|
88
|
+
if (picked.length < pickLimit && cost + tk <= budgetTokens) {
|
|
84
89
|
picked.push(toSegment(h));
|
|
85
90
|
cost += tk;
|
|
86
91
|
} else {
|
|
@@ -152,7 +157,7 @@ export function rerank(hits, ctx) {
|
|
|
152
157
|
return { ...h, _score: score };
|
|
153
158
|
})
|
|
154
159
|
.sort((a, b) => a._score - b._score)
|
|
155
|
-
.map(({ _score, ...rest }) => rest);
|
|
160
|
+
.map(({ _score, ...rest }) => ({ ...rest, score: _score }));
|
|
156
161
|
}
|
|
157
162
|
|
|
158
163
|
function toSegment(h) {
|
|
@@ -163,6 +168,7 @@ function toSegment(h) {
|
|
|
163
168
|
tags: h.tags,
|
|
164
169
|
sourceMessages: h.sourceMessages,
|
|
165
170
|
body: h.body,
|
|
171
|
+
score: typeof h.score === 'number' ? h.score : (typeof h.rank === 'number' ? h.rank : undefined),
|
|
166
172
|
createdAt: h.createdAt,
|
|
167
173
|
updatedAt: h.updatedAt,
|
|
168
174
|
};
|
|
@@ -259,6 +259,7 @@ export function formatPickedForInjection(picked) {
|
|
|
259
259
|
* @property {string[]} [currentTags] Contextual tags for rerank
|
|
260
260
|
* @property {number} [topK] Max FTS rows fetched (default 50)
|
|
261
261
|
* @property {number} [budgetTokens] Token budget for picked segments
|
|
262
|
+
* @property {number} [pickLimit] Max picked segments (default 8)
|
|
262
263
|
* @property {boolean} [fallbackOnEmpty] Include bounded recent scoped segments when FTS has no hits
|
|
263
264
|
* @property {number} [fallbackPerScope] Max fallback segments per scope
|
|
264
265
|
*/
|
|
@@ -353,6 +354,7 @@ export function runMemoryPreflow(index, opts) {
|
|
|
353
354
|
currentTags: opts.currentTags || [],
|
|
354
355
|
topK: opts.topK,
|
|
355
356
|
budgetTokens: opts.budgetTokens,
|
|
357
|
+
pickLimit: opts.pickLimit,
|
|
356
358
|
});
|
|
357
359
|
|
|
358
360
|
let fallbackUsed = false;
|
|
@@ -362,6 +364,7 @@ export function runMemoryPreflow(index, opts) {
|
|
|
362
364
|
ownVpId: opts.vpId || null,
|
|
363
365
|
budgetTokens: opts.budgetTokens,
|
|
364
366
|
perScope: opts.fallbackPerScope,
|
|
367
|
+
pickLimit: opts.pickLimit,
|
|
365
368
|
});
|
|
366
369
|
if (fallback.length > 0) {
|
|
367
370
|
fallbackUsed = true;
|
|
@@ -400,6 +403,7 @@ function fallbackScopedSegments(index, opts) {
|
|
|
400
403
|
const scopes = prioritizeFallbackScopes(filterScopes(opts.relevantScopes || [], opts.ownVpId || null));
|
|
401
404
|
const perScope = Number.isFinite(opts.perScope) && opts.perScope > 0 ? Math.floor(opts.perScope) : 2;
|
|
402
405
|
const budgetTokens = Number.isFinite(opts.budgetTokens) && opts.budgetTokens > 0 ? opts.budgetTokens : 1200;
|
|
406
|
+
const pickLimit = Number.isFinite(opts.pickLimit) && opts.pickLimit > 0 ? Math.floor(opts.pickLimit) : 8;
|
|
403
407
|
const buckets = [];
|
|
404
408
|
for (const scope of scopes) {
|
|
405
409
|
let segs = [];
|
|
@@ -419,6 +423,7 @@ function fallbackScopedSegments(index, opts) {
|
|
|
419
423
|
if (!seg) continue;
|
|
420
424
|
const tk = approxTokens(seg.body || '');
|
|
421
425
|
if (tk <= 0 || cost + tk > budgetTokens) continue;
|
|
426
|
+
if (out.length >= pickLimit) return out;
|
|
422
427
|
out.push(seg);
|
|
423
428
|
cost += tk;
|
|
424
429
|
}
|