@yeaft/webchat-agent 1.0.507 → 1.0.508

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.
Binary file
@@ -17,6 +17,6 @@
17
17
  </head>
18
18
  <body>
19
19
  <div id="app"></div>
20
- <script type="module" src="app.bundle.js?v=c13a2178"></script>
20
+ <script type="module" src="app.bundle.js?v=c0c1e6d3"></script>
21
21
  </body>
22
22
  </html>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "1.0.507",
3
+ "version": "1.0.508",
4
4
  "description": "Remote worker agent for Yeaft Web Code Agent — connects the native Yeaft engine, CLI providers, and workbench tools",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/yeaft/engine.js CHANGED
@@ -2103,11 +2103,16 @@ export class Engine {
2103
2103
  ...(internalTrigger ? { internal: true } : { userAuthored: true }),
2104
2104
  }, { sessionId: runtimeSessionId });
2105
2105
  }
2106
- // Native Session history, including internal wakeups, never comes from
2107
- // Dream. Work Center and scoped child agents keep their memory contracts.
2106
+ // Native Session history, including internal wakeups, comes from the
2107
+ // canonical message transcript. Dream memory loading is temporarily
2108
+ // disabled for every scenario (including Work Center and child agents)
2109
+ // while message-history recall is evaluated as its replacement. Keep the
2110
+ // downstream memory pipeline intact behind this single switch so it can be
2111
+ // restored without migrating or deleting persisted memory data.
2108
2112
  const useMessageHistory = scenario !== 'work-item' && !!runtimeSessionId && !vpPersona?.subAgent;
2109
- const useDreamMemory = scenario === 'work-item' || !!vpPersona?.subAgent
2110
- || (!runtimeSessionId && !internalTrigger);
2113
+ // const useDreamMemory = scenario === 'work-item' || !!vpPersona?.subAgent
2114
+ // || (!runtimeSessionId && !internalTrigger);
2115
+ const useDreamMemory = false;
2111
2116
  const recentTurnCap = this.#config.yeaft?.recentTurnsLimit ?? 20;
2112
2117
  const relatedTurnCap = this.#config.yeaft?.relatedTurnsLimit ?? 8;
2113
2118
  let relatedHistoryTurns = [];
@@ -27,6 +27,7 @@ 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 MINIMUM_RECENT_PROVIDER_TURNS = 20;
30
31
  const IMAGE_PART_TOKEN_COST = 1024;
31
32
  const DOCUMENT_PART_TOKEN_COST = 2048;
32
33
  const CONTENT_PART_FRAME_TOKENS = 2;
@@ -565,10 +566,11 @@ function truncateToolResultsForModel(messages, options = {}) {
565
566
 
566
567
  const HISTORY_SOURCE_INDEX = Symbol('historySourceIndex');
567
568
 
568
- function withHistorySourceIndexes(messages) {
569
+ function withHistorySourceIndexes(messages, offset = 0) {
569
570
  return messages.map((message, index) => (
570
571
  message && typeof message === 'object'
571
- ? { ...message, [HISTORY_SOURCE_INDEX]: index }
572
+ ? { ...message, [HISTORY_SOURCE_INDEX]: Number.isInteger(message[HISTORY_SOURCE_INDEX])
573
+ ? message[HISTORY_SOURCE_INDEX] : offset + index }
572
574
  : message
573
575
  ));
574
576
  }
@@ -795,12 +797,13 @@ function bucketTextMessages(messages) {
795
797
  }
796
798
 
797
799
  function bucketTurn(messages, index, supplied = {}) {
798
- const user = messages.find(bucketUserBoundary);
799
- const sourceIds = [...new Set(messages.flatMap(bucketSourceIds))];
800
- const keys = [...new Set(messages.filter(bucketUserBoundary).flatMap(bucketUserKeys))];
800
+ const indexedMessages = withHistorySourceIndexes(messages, index ?? 0);
801
+ const user = indexedMessages.find(bucketUserBoundary);
802
+ const sourceIds = [...new Set(indexedMessages.flatMap(bucketSourceIds))];
803
+ const keys = [...new Set(indexedMessages.filter(bucketUserBoundary).flatMap(bucketUserKeys))];
801
804
  if (supplied.id) keys.push(`turn:${supplied.id}`);
802
805
  if (keys.length === 0 && index != null) keys.push(`snapshot:${index}`);
803
- const text = bucketTextMessages(messages);
806
+ const text = bucketTextMessages(indexedMessages);
804
807
  return {
805
808
  ...supplied,
806
809
  id: supplied.id || keys[0] || `snapshot:${index}`,
@@ -808,7 +811,7 @@ function bucketTurn(messages, index, supplied = {}) {
808
811
  sourceIds,
809
812
  userSeq: Number.isFinite(supplied.userSeq) ? supplied.userSeq : bucketSequence(user),
810
813
  index,
811
- messages,
814
+ messages: indexedMessages,
812
815
  text,
813
816
  tokens: estimateMessagesTokens(text),
814
817
  };
@@ -893,19 +896,20 @@ function describeBucket(turns, messages = turns.flatMap(turn => turn.text)) {
893
896
 
894
897
  /**
895
898
  * Recompute provider history from untrimmed candidates; never mutate/cache the
896
- * result in the transcript. Past human turns are atomic text units, including
897
- * every VP's assistant text. Tools are optional enrichment, newest first.
899
+ * result in the transcript. Past human turn boundaries are retained when the
900
+ * configured recent window fits; tools are optional enrichment, newest first.
898
901
  *
899
902
  * The active turn is outside both buckets and consumes the global budget first.
900
- * Its opening user row is protected; an oversized active turn alone may be
901
- * fitted using the legacy protocol-safe transform. Past turns are never fitted.
902
- * External recall must have comparable userSeq/source identities to establish
903
+ * Its opening user row is protected. Oversized recent text is fitted only in the
904
+ * disposable provider copy so one large turn cannot evict the rest of the recent
905
+ * window. Related recall remains optional and complete. External recall must have
906
+ * comparable userSeq/source identities to establish
903
907
  * that it predates recent/current history; unknown chronology fails closed.
904
908
  *
905
909
  * @param {Array<object>} snapshot Untrimmed history plus the active execution.
906
910
  * @param {{ prompt?: string, relatedTurns?: Array<object>, recentTurnCap?: number,
907
911
  * relatedTurnCap?: number, messageTokenBudget?: number, maxMessageCount?: number,
908
- * language?: string, currentTurnStartIndex?: number,
912
+ * keepToolTurns?: number, language?: string, currentTurnStartIndex?: number,
909
913
  * scoreTurn?: (turn: object, prompt: string) => (number|object) }} [options]
910
914
  * @returns {{messages: Array<object>, meta: object}}
911
915
  */
@@ -915,6 +919,7 @@ export function buildHistoryBuckets(snapshot, options = {}) {
915
919
  const messageCap = bucketCap(options.maxMessageCount, DEFAULT_RUNTIME_CACHE_MESSAGE_CAP);
916
920
  const recentCap = bucketCap(options.recentTurnCap, 20);
917
921
  const relatedCap = bucketCap(options.relatedTurnCap, 8, 10);
922
+ const keepToolTurns = bucketCap(options.keepToolTurns, DEFAULT_KEEP_TOOL_TURNS);
918
923
  const allTurns = splitBucketTurns(source);
919
924
  const currentStart = Number.isInteger(options.currentTurnStartIndex)
920
925
  ? Math.max(0, Math.min(source.length, options.currentTurnStartIndex))
@@ -1025,16 +1030,53 @@ export function buildHistoryBuckets(snapshot, options = {}) {
1025
1030
  const reserve = reservable.length ? Math.floor(availableTokens * 0.25) : 0;
1026
1031
  const reservedRows = reservable.length
1027
1032
  ? Math.max(Math.floor(availableRows * 0.25), Math.min(...reservable.map(turn => turn.text.length))) : 0;
1033
+ function fitRecentTurn(turn, tokenLimit) {
1034
+ if (turn.tokens <= tokenLimit) return turn.text;
1035
+ const fitted = [];
1036
+ let tokens = tokenLimit;
1037
+ for (let index = 0; index < turn.text.length; index += 1) {
1038
+ const remainingRows = turn.text.length - index;
1039
+ const allowance = Math.max(2, Math.floor(tokens / remainingRows));
1040
+ const message = shrinkMessageToBudget(turn.text[index], allowance);
1041
+ const rows = dropEmptyAssistantRows([message]);
1042
+ fitted.push(...rows);
1043
+ tokens -= estimateMessagesTokens(rows);
1044
+ }
1045
+ return fitted;
1046
+ }
1028
1047
  function selectRecent(limit, rowLimit = availableRows) {
1048
+ const candidates = past.slice(-recentCap);
1049
+ if (!candidates.length || limit <= 0 || rowLimit <= 0) return [];
1050
+ const preserveBoundaries = candidates.length >= MINIMUM_RECENT_PROVIDER_TURNS
1051
+ && rowLimit >= candidates.reduce((sum, turn) => sum + turn.text.length, 0)
1052
+ && limit >= candidates.length * 2;
1053
+ if (!preserveBoundaries) {
1054
+ const selected = [];
1055
+ let tokens = 0;
1056
+ let rows = 0;
1057
+ for (let index = candidates.length - 1; index >= 0; index -= 1) {
1058
+ const turn = candidates[index];
1059
+ if (tokens + turn.tokens > limit || rows + turn.text.length > rowLimit) break;
1060
+ selected.unshift(turn);
1061
+ tokens += turn.tokens;
1062
+ rows += turn.text.length;
1063
+ }
1064
+ return selected;
1065
+ }
1066
+
1029
1067
  const selected = [];
1030
- let tokens = 0;
1031
- let rows = 0;
1032
- for (let index = past.length - 1; index >= 0 && selected.length < recentCap; index -= 1) {
1033
- const turn = past[index];
1034
- if (tokens + turn.tokens > limit || rows + turn.text.length > rowLimit) break;
1035
- selected.unshift(turn);
1036
- tokens += turn.tokens;
1037
- rows += turn.text.length;
1068
+ let tokens = Math.floor(limit);
1069
+ let rows = Math.floor(rowLimit);
1070
+ for (const turn of candidates) {
1071
+ const remainingTurns = candidates.length - selected.length;
1072
+ if (rows < remainingTurns) break;
1073
+ const allowance = Math.max(2, Math.floor(tokens / remainingTurns));
1074
+ const fitted = fitRecentTurn(turn, allowance);
1075
+ if (!fitted.length || fitted.length > rows - (remainingTurns - 1)) continue;
1076
+ const used = estimateMessagesTokens(fitted);
1077
+ selected.push({ ...turn, text: fitted, tokens: used });
1078
+ tokens -= used;
1079
+ rows -= fitted.length;
1038
1080
  }
1039
1081
  return selected;
1040
1082
  }
@@ -1072,13 +1114,22 @@ export function buildHistoryBuckets(snapshot, options = {}) {
1072
1114
  related.sort((a, b) => bucketBefore(a, b) ? -1 : bucketBefore(b, a) ? 1 : 0);
1073
1115
 
1074
1116
  const relatedMessages = related.flatMap(turn => turn.text);
1075
- const recentText = withHistorySourceIndexes(recent.flatMap(turn => turn.messages)
1076
- .filter(isVisibleConversationRow));
1077
- const recentBaseline = bucketTextMessages(recentText);
1117
+ const recentText = recent.flatMap(turn => turn.messages)
1118
+ .filter(isVisibleConversationRow);
1119
+ const recentWasFitted = recent.some(turn => turn.text !== turn.messages
1120
+ && (turn.text.length !== bucketTextMessages(turn.messages).length
1121
+ || turn.tokens !== estimateMessagesTokens(bucketTextMessages(turn.messages))));
1122
+ const recentBaseline = recent.flatMap(turn => recentWasFitted ? turn.text : bucketTextMessages(turn.messages));
1078
1123
  // Enrich only after both complete-text buckets and the active turn are paid.
1124
+ // Tool protocol is useful only for immediate continuity; unlike visible text,
1125
+ // it never reaches farther back than the configured recent tool window.
1126
+ const toolCutIndex = indexOfNthTurnFromEnd(recentText, keepToolTurns);
1127
+ const recentToolSource = keepToolTurns > 0
1128
+ ? recentText.slice(toolCutIndex < 0 ? 0 : toolCutIndex)
1129
+ : [];
1079
1130
  const recentMessages = withoutHistorySourceIndexes(addOptionalRecentToolPairs(
1080
1131
  recentBaseline,
1081
- truncateToolResultsForModel(recentText, { language: options.language }),
1132
+ truncateToolResultsForModel(recentToolSource, { language: options.language }),
1082
1133
  {
1083
1134
  messageTokenBudget: availableTokens - estimateMessagesTokens(relatedMessages),
1084
1135
  maxMessageCount: availableRows - relatedMessages.length,
package/yeaft/prompts.js CHANGED
@@ -532,6 +532,8 @@ function renderVpPersona(vpPersona, lang, effectiveLang = 'en') {
532
532
  const name = selectVpPersonaName(vpPersona, effectiveLang);
533
533
  if (!name) return '';
534
534
  const body = selectVpPersonaBody(vpPersona, effectiveLang);
535
+ const runtimePreamble = typeof vpPersona.runtimePreamble === 'string'
536
+ ? vpPersona.runtimePreamble.trim() : '';
535
537
 
536
538
  // Persona is the IDENTITY layer (not an overlay). Do not prepend a
537
539
  // generic assistant identity here: the VP soul body is the source of truth.
@@ -540,6 +542,7 @@ function renderVpPersona(vpPersona, lang, effectiveLang = 'en') {
540
542
  const soulHeading = effectiveLang === 'zh' ? '## 灵魂' : '## Soul';
541
543
  const lines = [`# ${name}`, '', soulHeading];
542
544
  if (body) lines.push('', body);
545
+ if (runtimePreamble) lines.push('', runtimePreamble);
543
546
  return lines.join('\n');
544
547
  }
545
548
 
package/yeaft/session.js CHANGED
@@ -49,7 +49,8 @@ import { ensureDefaultSessionIfEmpty, migrateRegisteredWorkDirSessions } from '.
49
49
  import { seedDefaultVps } from './vp/seed-defaults.js';
50
50
  import { topUpDefaultVps } from './vp/seed-topup.js';
51
51
  import { archiveLegacyScopes } from './memory/seed-backfill.js';
52
- import { createV2DreamScheduler, bootInitEmptyGroups, bootCatchUpStaleDream } from './dream/session-wiring.js';
52
+ // Dream scheduler wiring is intentionally not imported while the runtime path is disabled.
53
+ // import { createV2DreamScheduler, bootInitEmptyGroups, bootCatchUpStaleDream } from './dream/session-wiring.js';
53
54
  import { isWorkCenterEnabled } from './work-center/feature.js';
54
55
  import { openSegmentIndex } from './memory/index-db.js';
55
56
  import { syncAll as syncSegmentIndex } from './memory/segment-sync.js';
@@ -473,50 +474,20 @@ export async function loadSession(options = {}) {
473
474
  });
474
475
 
475
476
 
476
- // ─── 9a. Create dream scheduler ────────────
477
- // The legacy R6 dream-scheduler was retired alongside recall-r6;
478
- // dream is the only active path (the `config.memoryV2` opt-out
479
- // flag was retired in task-710 — wiring is unconditional).
480
- // partialSession lets the v2 scheduler dereference adapter/config/
481
- // engine/trace lazily — safe because callers attach more fields
482
- // after this line.
483
- const partialSession = {
484
- yeaftDir,
485
- adapter,
486
- config,
487
- engine,
488
- trace,
489
- };
490
- const dreamScheduler = createV2DreamScheduler(partialSession);
491
-
492
- // task-710: kick a dream pass at boot for any group that has user
493
- // messages but zero memory segments in the FTS index. Without this a
494
- // freshly opened agent had to wait an hour (or for the nudge counter
495
- // to cross 50) before the first segment landed and recall could find
496
- // anything. Fire-and-forget; failure logs at debug only.
497
- if (memoryIndex && !config._readOnly) {
498
- bootInitEmptyGroups({
499
- yeaftDir,
500
- memoryIndex,
501
- dreamScheduler,
502
- config,
503
- }).catch(() => { /* best-effort boot init */ });
504
- }
505
-
506
- // fix/dream-cadence-and-ui-trigger: stale-cadence catch-up. If the
507
- // newest per-group lastDreamAt across all groups is older than
508
- // DREAM_INTERVAL_HOURS (or absent and there's user traffic), fire a
509
- // single non-manual tick now. Independent of the interval timer —
510
- // necessary because production observed 12 days between scheduled
511
- // ticks (the unref'd interval did not fire reliably on long-lived
512
- // server processes).
513
- if (!config._readOnly) {
514
- bootCatchUpStaleDream({
515
- yeaftDir,
516
- dreamScheduler,
517
- config,
518
- }).catch(() => { /* best-effort catch-up */ });
519
- }
477
+ // ─── 9a. Dream runtime temporarily disabled ────────────
478
+ // Message history now supplies turn context. Keep the Dream implementation
479
+ // and persisted data intact, but do not create a scheduler or run boot-time
480
+ // initialization/catch-up while the replacement is evaluated.
481
+ //
482
+ // const partialSession = { yeaftDir, adapter, config, engine, trace };
483
+ // const dreamScheduler = createV2DreamScheduler(partialSession);
484
+ // if (memoryIndex && !config._readOnly) {
485
+ // bootInitEmptyGroups({ yeaftDir, memoryIndex, dreamScheduler, config }).catch(() => {});
486
+ // }
487
+ // if (!config._readOnly) {
488
+ // bootCatchUpStaleDream({ yeaftDir, dreamScheduler, config }).catch(() => {});
489
+ // }
490
+ const dreamScheduler = null;
520
491
 
521
492
  // H2.f.5 retired the old session-level thread engine registry, input queue,
522
493
  // and dispatcher. The session exposes a default `engine`; PR #797 keeps
@@ -221,10 +221,12 @@ export function startSubAgent(agent, deps = {}) {
221
221
  deps.parentVpPersona && typeof deps.parentVpPersona === 'object'
222
222
  ? { ...deps.parentVpPersona }
223
223
  : {};
224
- baseVpPersona.persona =
225
- [(baseVpPersona.persona || '').trim(), preamble.trim()]
226
- .filter(Boolean)
227
- .join('\n\n');
224
+ // Keep the spawned-agent contract outside the inherited VP soul. Stock
225
+ // souls can contain bilingual section markers, and persona rendering selects
226
+ // one language section before provider dispatch; appending the preamble to
227
+ // that source can therefore discard the child identity and mission. The
228
+ // prompt renderer appends this runtime-only block after soul selection.
229
+ baseVpPersona.runtimePreamble = preamble.trim();
228
230
  if (!baseVpPersona.displayName || !String(baseVpPersona.displayName).trim()) {
229
231
  baseVpPersona.displayName = `${deps.parentName || 'Parent'}/${agent.name || 'sub-agent'}`;
230
232
  }