@yeaft/webchat-agent 1.0.510 → 1.0.512
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 +128 -123
- 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/cli.js +1 -1
- package/yeaft/config.js +1 -1
- package/yeaft/conversation/persist.js +2 -2
- package/yeaft/engine.js +2 -2
- package/yeaft/history-window.js +63 -12
- package/yeaft/session.js +1 -1
- package/yeaft/web-bridge.js +1 -1
|
Binary file
|
package/package.json
CHANGED
package/yeaft/cli.js
CHANGED
|
@@ -380,7 +380,7 @@ async function runREPL(config, args) {
|
|
|
380
380
|
|
|
381
381
|
// Load persisted conversation as initial messages. `loadRecent` is now
|
|
382
382
|
// turn-based (one user round-trip = one turn; multi-VP fan-out collapses
|
|
383
|
-
// into one turn).
|
|
383
|
+
// into one turn). 10 turns is the bootstrap window. Provider requests
|
|
384
384
|
// apply deterministic history-window trimming; persisted history stays
|
|
385
385
|
// authoritative and no LLM conversation summary is generated.
|
|
386
386
|
let conversationMessages = conversationStore.loadRecent().map(m => ({
|
package/yeaft/config.js
CHANGED
|
@@ -53,7 +53,7 @@ const DEFAULTS = {
|
|
|
53
53
|
// ConversationStore.loadRecentBySession / loadSessionHistoryForVp
|
|
54
54
|
// bring back after boot or reconnect. Older transcript remains available
|
|
55
55
|
// through history pagination/search. Range: 1–500.
|
|
56
|
-
yeaftRecentTurnsLimit:
|
|
56
|
+
yeaftRecentTurnsLimit: 10,
|
|
57
57
|
// Same-Session related Q&A turns, selected by deterministic full-text rules.
|
|
58
58
|
// Range: 0–5; 0 disables related recall without changing recent history.
|
|
59
59
|
yeaftRelatedTurnsLimit: 5,
|
|
@@ -42,7 +42,7 @@ import { markConversationDirty } from './history-index-state.js';
|
|
|
42
42
|
* Chat-Completions adapter. Turn-based slicing always cuts at a user-
|
|
43
43
|
* message boundary, which is pair-safe by construction.
|
|
44
44
|
*
|
|
45
|
-
*
|
|
45
|
+
* 10 turns is the default bootstrap window.
|
|
46
46
|
* It is the cold-start replay window after a fresh boot or reconnect. Runtime
|
|
47
47
|
* provider requests apply a separate deterministic history-window transform;
|
|
48
48
|
* no LLM summary is required for recovery.
|
|
@@ -57,7 +57,7 @@ import { markConversationDirty } from './history-index-state.js';
|
|
|
57
57
|
* at module load (`const cap = DEFAULT_RECENT_TURNS`) would not see
|
|
58
58
|
* runtime overrides. The reader function makes that always-correct.
|
|
59
59
|
*/
|
|
60
|
-
let DEFAULT_RECENT_TURNS =
|
|
60
|
+
let DEFAULT_RECENT_TURNS = 10;
|
|
61
61
|
|
|
62
62
|
// Circuit breaker for newest-to-oldest session scans. This bounds event-loop
|
|
63
63
|
// starvation when the newest transcript tail is dense with hidden/internal or
|
package/yeaft/engine.js
CHANGED
|
@@ -2132,7 +2132,7 @@ export class Engine {
|
|
|
2132
2132
|
// const useDreamMemory = scenario === 'work-item' || !!vpPersona?.subAgent
|
|
2133
2133
|
// || (!runtimeSessionId && !internalTrigger);
|
|
2134
2134
|
const useDreamMemory = false;
|
|
2135
|
-
const recentTurnCap = Math.max(
|
|
2135
|
+
const recentTurnCap = Math.max(10, this.#config.yeaft?.recentTurnsLimit ?? 10);
|
|
2136
2136
|
const relatedTurnCap = Math.min(5, this.#config.yeaft?.relatedTurnsLimit ?? 5);
|
|
2137
2137
|
let relatedHistoryTurns = [];
|
|
2138
2138
|
let historyRecallMeta = { source: 'messages', status: 'disabled' };
|
|
@@ -2915,7 +2915,7 @@ export class Engine {
|
|
|
2915
2915
|
const buckets = useMessageHistory ? buildHistoryBuckets(conversationMessages, {
|
|
2916
2916
|
prompt,
|
|
2917
2917
|
relatedTurns: relatedHistoryTurns,
|
|
2918
|
-
recentTurnCap: Math.max(
|
|
2918
|
+
recentTurnCap: Math.max(10, requestConfig.yeaft?.recentTurnsLimit ?? 10),
|
|
2919
2919
|
relatedTurnCap: Math.min(5, requestConfig.yeaft?.relatedTurnsLimit ?? 5),
|
|
2920
2920
|
messageTokenBudget: historyBudget,
|
|
2921
2921
|
currentTurnStartIndex: turnStartIdx,
|
package/yeaft/history-window.js
CHANGED
|
@@ -27,6 +27,8 @@ export const DEFAULT_RUNTIME_CACHE_TURN_CAP = 25;
|
|
|
27
27
|
export const DEFAULT_RUNTIME_CACHE_TOKEN_BUDGET = 32768;
|
|
28
28
|
export const DEFAULT_RUNTIME_CACHE_MESSAGE_CAP = 256;
|
|
29
29
|
|
|
30
|
+
const DEFAULT_PROVIDER_RECENT_TURNS = 10;
|
|
31
|
+
const MINIMUM_RECENT_PROVIDER_TURNS = 3;
|
|
30
32
|
const IMAGE_PART_TOKEN_COST = 1024;
|
|
31
33
|
const DOCUMENT_PART_TOKEN_COST = 2048;
|
|
32
34
|
const CONTENT_PART_FRAME_TOKENS = 2;
|
|
@@ -893,16 +895,56 @@ function describeBucket(turns, messages = turns.flatMap(turn => turn.text)) {
|
|
|
893
895
|
};
|
|
894
896
|
}
|
|
895
897
|
|
|
898
|
+
/**
|
|
899
|
+
* Emergency projection for the three-turn floor. It reuses the existing
|
|
900
|
+
* deterministic message fitter against a disposable provider copy, while
|
|
901
|
+
* sharing rows/tokens across the newest three human boundaries. The durable
|
|
902
|
+
* transcript is never rewritten.
|
|
903
|
+
*/
|
|
904
|
+
function compressRecentTurns(turns, tokenBudget, messageCap) {
|
|
905
|
+
const fitted = [];
|
|
906
|
+
let tokens = tokenBudget;
|
|
907
|
+
let rows = messageCap;
|
|
908
|
+
for (let index = 0; index < turns.length; index += 1) {
|
|
909
|
+
const turn = turns[index];
|
|
910
|
+
const remaining = turns.length - index;
|
|
911
|
+
let turnTokens = Math.floor(tokens / remaining);
|
|
912
|
+
const minimumRowTokens = Array.isArray(turn.text[0]?.content) ? 5 : 3;
|
|
913
|
+
const turnRows = Math.min(Math.floor(rows / remaining), Math.floor(turnTokens / minimumRowTokens));
|
|
914
|
+
if (turnTokens < minimumRowTokens || turnRows < 1) continue;
|
|
915
|
+
const selected = turn.text.length <= turnRows ? turn.text
|
|
916
|
+
: [turn.text[0], ...(turnRows > 1 ? turn.text.slice(-(turnRows - 1)) : [])];
|
|
917
|
+
const text = [];
|
|
918
|
+
for (let row = 0; row < selected.length; row += 1) {
|
|
919
|
+
const allowance = Math.floor(turnTokens / (selected.length - row));
|
|
920
|
+
const message = shrinkMessageToBudget(selected[row], allowance);
|
|
921
|
+
const cost = estimateMessageTokens(message);
|
|
922
|
+
if (cost <= allowance && hasProviderContent(message.content)) {
|
|
923
|
+
text.push(message);
|
|
924
|
+
turnTokens -= cost;
|
|
925
|
+
} else if (row === 0) break;
|
|
926
|
+
}
|
|
927
|
+
if (!text.some(bucketUserBoundary)) continue;
|
|
928
|
+
const cost = estimateMessagesTokens(text);
|
|
929
|
+
fitted.push({ ...turn, text, tokens: cost });
|
|
930
|
+
tokens -= cost;
|
|
931
|
+
rows -= text.length;
|
|
932
|
+
}
|
|
933
|
+
return fitted;
|
|
934
|
+
}
|
|
935
|
+
|
|
896
936
|
/**
|
|
897
937
|
* Recompute provider history from untrimmed candidates; never mutate/cache the
|
|
898
938
|
* result in the transcript. Past human turn boundaries are retained when the
|
|
899
939
|
* configured recent window fits; tools are optional enrichment, newest first.
|
|
900
940
|
*
|
|
901
941
|
* The active turn is outside both buckets and outside the history budget. Its
|
|
902
|
-
* opening user row is protected by the later whole-request fitter.
|
|
903
|
-
*
|
|
904
|
-
*
|
|
905
|
-
*
|
|
942
|
+
* opening user row is protected by the later whole-request fitter. Prefer ten
|
|
943
|
+
* complete recent turns, reducing the oldest end down to three. If that floor
|
|
944
|
+
* cannot fit, omit recall and compress only those three turns; historical tool
|
|
945
|
+
* replay then narrows from the normal newest three turns to the newest one.
|
|
946
|
+
* Related recall otherwise uses remaining history budget and stays optional and
|
|
947
|
+
* complete. External recall must have
|
|
906
948
|
* comparable userSeq/source identities to establish
|
|
907
949
|
* that it predates recent/current history; unknown chronology fails closed.
|
|
908
950
|
*
|
|
@@ -917,7 +959,7 @@ export function buildHistoryBuckets(snapshot, options = {}) {
|
|
|
917
959
|
const source = Array.isArray(snapshot) ? snapshot : [];
|
|
918
960
|
const tokenBudget = bucketCap(options.messageTokenBudget, DEFAULT_MESSAGE_TOKEN_BUDGET);
|
|
919
961
|
const messageCap = bucketCap(options.maxMessageCount, DEFAULT_RUNTIME_CACHE_MESSAGE_CAP);
|
|
920
|
-
const recentCap = bucketCap(options.recentTurnCap,
|
|
962
|
+
const recentCap = bucketCap(options.recentTurnCap, DEFAULT_PROVIDER_RECENT_TURNS);
|
|
921
963
|
const relatedCap = bucketCap(options.relatedTurnCap, 5, 5);
|
|
922
964
|
const keepToolTurns = bucketCap(options.keepToolTurns, DEFAULT_KEEP_TOOL_TURNS);
|
|
923
965
|
const allTurns = splitBucketTurns(source);
|
|
@@ -997,11 +1039,12 @@ export function buildHistoryBuckets(snapshot, options = {}) {
|
|
|
997
1039
|
// past-turn boundary is still a safe fence; never guess from text/time.
|
|
998
1040
|
|| (turn.index == null && currentIdentity.userSeq == null && past.length > 0
|
|
999
1041
|
&& bucketBefore(turn, past.at(-1)))));
|
|
1000
|
-
// Recent text has first claim on history budget. Drop whole oldest turns
|
|
1001
|
-
//
|
|
1002
|
-
|
|
1042
|
+
// Recent text has first claim on history budget. Drop whole oldest turns down
|
|
1043
|
+
// to the three-turn floor before projecting that floor more aggressively.
|
|
1044
|
+
let recent = [];
|
|
1003
1045
|
let recentTokens = 0;
|
|
1004
1046
|
let recentRows = 0;
|
|
1047
|
+
let compressedRecentFloor = false;
|
|
1005
1048
|
const recentCandidates = recentCap > 0 ? past.slice(-recentCap) : [];
|
|
1006
1049
|
for (let index = recentCandidates.length - 1; index >= 0; index -= 1) {
|
|
1007
1050
|
const turn = recentCandidates[index];
|
|
@@ -1011,10 +1054,15 @@ export function buildHistoryBuckets(snapshot, options = {}) {
|
|
|
1011
1054
|
recentTokens += turn.tokens;
|
|
1012
1055
|
recentRows += turn.text.length;
|
|
1013
1056
|
}
|
|
1057
|
+
const recentFloor = Math.min(MINIMUM_RECENT_PROVIDER_TURNS, recentCandidates.length);
|
|
1058
|
+
if (recent.length < recentFloor) {
|
|
1059
|
+
compressedRecentFloor = true;
|
|
1060
|
+
recent = compressRecentTurns(recentCandidates.slice(-recentFloor), availableTokens, availableRows);
|
|
1061
|
+
}
|
|
1014
1062
|
let remainingTokens = availableTokens - recent.reduce((total, turn) => total + turn.tokens, 0);
|
|
1015
1063
|
let remainingRows = availableRows - recent.reduce((total, turn) => total + turn.text.length, 0);
|
|
1016
1064
|
const related = [];
|
|
1017
|
-
const ranked = eligible.slice().sort((a, b) => b.score - a.score
|
|
1065
|
+
const ranked = compressedRecentFloor ? [] : eligible.slice().sort((a, b) => b.score - a.score
|
|
1018
1066
|
|| (a.userSeq ?? a.index ?? 0) - (b.userSeq ?? b.index ?? 0));
|
|
1019
1067
|
for (const turn of ranked) {
|
|
1020
1068
|
if (related.length >= relatedCap) break;
|
|
@@ -1035,8 +1083,9 @@ export function buildHistoryBuckets(snapshot, options = {}) {
|
|
|
1035
1083
|
// Enrich only after both complete-text buckets and the active turn are paid.
|
|
1036
1084
|
// Tool protocol is useful only for immediate continuity; unlike visible text,
|
|
1037
1085
|
// it never reaches farther back than the configured recent tool window.
|
|
1038
|
-
const
|
|
1039
|
-
|
|
1086
|
+
const effectiveKeepToolTurns = compressedRecentFloor ? Math.min(1, keepToolTurns) : keepToolTurns;
|
|
1087
|
+
const recentToolSource = effectiveKeepToolTurns > 0
|
|
1088
|
+
? recent.slice(-effectiveKeepToolTurns).flatMap(turn => turn.messages).filter(isVisibleConversationRow)
|
|
1040
1089
|
: [];
|
|
1041
1090
|
const recentMessages = withoutHistorySourceIndexes(addOptionalRecentToolPairs(
|
|
1042
1091
|
recentBaseline,
|
|
@@ -1065,7 +1114,9 @@ export function buildHistoryBuckets(snapshot, options = {}) {
|
|
|
1065
1114
|
budget: {
|
|
1066
1115
|
messageTokenBudget: tokenBudget, maxMessageCount: messageCap,
|
|
1067
1116
|
recentTurnCap: recentCap, relatedTurnCap: relatedCap,
|
|
1068
|
-
minimumRecentTurns:
|
|
1117
|
+
minimumRecentTurns: recentFloor,
|
|
1118
|
+
compressedRecentFloor,
|
|
1119
|
+
effectiveKeepToolTurns,
|
|
1069
1120
|
relatedReservedTokens: 0, availableHistoryTokens: availableTokens,
|
|
1070
1121
|
usedTokens: estimateMessagesTokens([...relatedMessages, ...recentMessages]),
|
|
1071
1122
|
usedMessages: relatedMessages.length + recentMessages.length,
|
package/yeaft/session.js
CHANGED
|
@@ -180,7 +180,7 @@ export async function loadSession(options = {}) {
|
|
|
180
180
|
if (typeof dreamEnabled === 'boolean') config.dream.enabled = dreamEnabled;
|
|
181
181
|
|
|
182
182
|
// Propagate the (clamped) cold-start replay window to the conversation
|
|
183
|
-
// store. The default is
|
|
183
|
+
// store. The default is 10 turns; a user wanting more recall after a
|
|
184
184
|
// fresh boot sets `yeaft.recentTurnsLimit` in ~/.yeaft/config.json.
|
|
185
185
|
// Called once per session boot — subsequent boots overwrite the
|
|
186
186
|
// module-level default safely (single-process model).
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -7174,7 +7174,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
7174
7174
|
// `lim` is now expressed in TURNS, not raw messages. `loadRecent` and
|
|
7175
7175
|
// `loadRecentBySession` use turn-based slicing so the cut never lands
|
|
7176
7176
|
// mid-tool-arc. Pass `undefined` to use the persistence-layer default
|
|
7177
|
-
// (DEFAULT_RECENT_TURNS =
|
|
7177
|
+
// (DEFAULT_RECENT_TURNS = 10 turns).
|
|
7178
7178
|
const pickRecent = (store, lim) =>
|
|
7179
7179
|
sessionId ? store.loadRecentBySession(sessionId, lim) : store.loadRecent(lim);
|
|
7180
7180
|
let historyAlreadyReplayed = false;
|