@yeaft/webchat-agent 0.1.851 → 0.1.852

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.851",
3
+ "version": "0.1.852",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,7 +22,7 @@ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, rename
22
22
  import { join, basename } from 'path';
23
23
  import { isPermissionError } from '../init.js';
24
24
  import { pairSanitize } from '../pair-sanitize.js';
25
- import { sliceLastNTurns } from '../turn-utils.js';
25
+ import { indexOfNthTurnFromEnd, sliceLastNTurns } from '../turn-utils.js';
26
26
 
27
27
  /**
28
28
  * Default cold-start "recent window" size, expressed in TURNS (not raw
@@ -870,6 +870,43 @@ export class ConversationStore {
870
870
  return { messages: sliced, oldestSeq, hasMore };
871
871
  }
872
872
 
873
+ /**
874
+ * Visible UI pagination read for one group. Unlike `loadOlderByGroup`, this
875
+ * projects out internal/reflection/system rows BEFORE applying the turn
876
+ * window, so a dense run of hidden metadata cannot force the first screen to
877
+ * scan and materialize the group's entire history in the web bridge.
878
+ *
879
+ * @param {string} groupId
880
+ * @param {number|null} beforeSeq — exclusive upper bound, or null for newest
881
+ * @param {number} [turnsLimit=DEFAULT_RECENT_TURNS]
882
+ * @returns {{ messages: object[], oldestSeq: number|null, hasMore: boolean }}
883
+ */
884
+ loadVisibleByGroup(groupId, beforeSeq, turnsLimit = DEFAULT_RECENT_TURNS) {
885
+ if (!groupId || !(turnsLimit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
886
+
887
+ const cutoff = Number.isFinite(beforeSeq) ? beforeSeq : Infinity;
888
+ const hot = this.#loadVisibleFromDirByGroup(this.#msgDir, groupId, cutoff);
889
+ const cold = this.#loadVisibleFromDirByGroup(this.#coldDir, groupId, cutoff);
890
+ const visible = [...cold, ...hot];
891
+ if (visible.length === 0) return { messages: [], oldestSeq: null, hasMore: false };
892
+
893
+ const startIdx = indexOfNthTurnFromEnd(visible, turnsLimit);
894
+ const start = startIdx === -1 ? 0 : startIdx;
895
+ const messages = pairSanitize(visible.slice(start));
896
+ const oldestSeq = messages.length ? parseSeqFromId(messages[0].id) : null;
897
+ const firstVisibleSeq = parseSeqFromId(visible[0].id);
898
+ const hasMore = messages.length > 0
899
+ && Number.isFinite(oldestSeq)
900
+ && Number.isFinite(firstVisibleSeq)
901
+ && oldestSeq > firstVisibleSeq;
902
+
903
+ return {
904
+ messages,
905
+ oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
906
+ hasMore,
907
+ };
908
+ }
909
+
873
910
  /**
874
911
  * Count hot messages.
875
912
  *
@@ -1319,6 +1356,32 @@ export class ConversationStore {
1319
1356
  return messages;
1320
1357
  }
1321
1358
 
1359
+ #loadVisibleFromDirByGroup(dir, groupId, beforeSeq) {
1360
+ if (!existsSync(dir)) return [];
1361
+
1362
+ const files = readdirSync(dir)
1363
+ .filter(f => f.endsWith('.md'))
1364
+ .sort();
1365
+
1366
+ const out = [];
1367
+ for (const file of files) {
1368
+ const seq = parseSeqFromId(basename(file, '.md'));
1369
+ if (!Number.isFinite(seq) || seq >= beforeSeq) continue;
1370
+
1371
+ const raw = readFileSync(join(dir, file), 'utf8');
1372
+ if (!raw.includes(`groupId: ${groupId}`)) continue;
1373
+ if (!raw.includes('role: user') && !raw.includes('role: assistant')) continue;
1374
+
1375
+ const parsed = parseMessage(raw);
1376
+ if (!parsed || parsed.groupId !== groupId) continue;
1377
+ if (parsed._reflection || parsed.internal || parsed.systemOnly || parsed.systemOnlyMessage) continue;
1378
+ if (parsed.role !== 'user' && parsed.role !== 'assistant') continue;
1379
+ out.push(parsed);
1380
+ }
1381
+
1382
+ return out;
1383
+ }
1384
+
1322
1385
  /**
1323
1386
  * Determine the next sequence number by scanning existing files.
1324
1387
  * @returns {number}
@@ -602,10 +602,16 @@ function loadVisibleGroupHistoryPage(store, groupId, limit, beforeSeq = null) {
602
602
 
603
603
  let rows = [];
604
604
  try {
605
- if (typeof store.loadOlderByGroup === 'function') {
606
- // Use an unbounded raw prefix, then project/slice visible rows below.
607
- // This preserves loadOlderByGroup's hot+cold scan without letting raw
608
- // reflection/internal rows consume the UI-visible page window.
605
+ if (typeof store.loadVisibleByGroup === 'function') {
606
+ const page = store.loadVisibleByGroup(groupId, beforeSeq, limit);
607
+ return {
608
+ messages: (page.messages || []).map(projectPersistedToVisibleHistoryEntry).filter(Boolean),
609
+ oldestSeq: (typeof page.oldestSeq === 'number') ? page.oldestSeq : null,
610
+ hasMore: !!page.hasMore,
611
+ };
612
+ } else if (typeof store.loadOlderByGroup === 'function') {
613
+ // Compatibility fallback for older test doubles: use an unbounded raw
614
+ // prefix, then project/slice visible rows below.
609
615
  rows = store.loadOlderByGroup(groupId, beforeSeq, Infinity).messages || [];
610
616
  } else if (Number.isFinite(beforeSeq)) {
611
617
  const all = typeof store.loadAllByGroup === 'function'
@@ -3582,11 +3588,10 @@ export async function handleUnifyLoadHistory(msg) {
3582
3588
  }
3583
3589
 
3584
3590
  // `msg.limit` is the replay-scrollback request from the frontend (UI
3585
- // history pane, not engine context). Semantics changed (2026-05-01):
3586
- // now expressed in TURNS. The previous default (50 messages) maps to
3587
- // ~20–25 turns; in the turn-count world 50 turns of UI scrollback is
3588
- // still cheap and matches what the frontend already passes through.
3589
- const limit = (typeof msg.limit === 'number') ? msg.limit : 50;
3591
+ // history pane, not engine context). Keep the bootstrap window small so
3592
+ // opening a group can paint the latest messages quickly; older rows are
3593
+ // paged via `unify_load_more_history` when the user scrolls upward.
3594
+ const limit = (typeof msg.limit === 'number') ? msg.limit : 10;
3590
3595
  const visiblePage = groupId
3591
3596
  ? loadVisibleGroupHistoryPage(session.conversationStore, groupId, limit)
3592
3597
  : { messages: limit > 0 ? pickRecent(session.conversationStore, limit) : [], oldestSeq: null, hasMore: false };
@@ -3691,7 +3696,7 @@ export async function handleUnifyLoadMoreHistory(msg) {
3691
3696
  }
3692
3697
 
3693
3698
  const beforeSeq = (typeof msg.beforeSeq === 'number') ? msg.beforeSeq : null;
3694
- const turns = (typeof msg.turns === 'number' && msg.turns > 0) ? msg.turns : 20;
3699
+ const turns = (typeof msg.turns === 'number' && msg.turns > 0) ? msg.turns : 10;
3695
3700
 
3696
3701
  let result;
3697
3702
  try {