@yeaft/webchat-agent 0.1.797 → 0.1.798

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/unify/web-bridge.js +66 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.797",
3
+ "version": "0.1.798",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -52,6 +52,7 @@ import {
52
52
  } from './history-compact.js';
53
53
  import { persistUnifyAttachments, attachmentsForPersistence } from './attachments.js';
54
54
  import { parseSeqFromId } from './conversation/persist.js';
55
+ import { sliceLastNTurns } from './turn-utils.js';
55
56
  import { createVpStatusBroker } from './vp-status-broker.js';
56
57
  import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
57
58
 
@@ -523,6 +524,54 @@ function projectPersistedToHistoryEntry(m) {
523
524
  return entry;
524
525
  }
525
526
 
527
+ function projectPersistedToVisibleHistoryEntry(m) {
528
+ const entry = projectPersistedToHistoryEntry(m);
529
+ return entry && (entry.role === 'user' || entry.role === 'assistant') ? entry : null;
530
+ }
531
+
532
+ function loadVisibleGroupHistoryPage(store, groupId, limit, beforeSeq = null) {
533
+ if (!store || !groupId || !(limit > 0)) return { messages: [], oldestSeq: null, hasMore: false };
534
+
535
+ let rows = [];
536
+ try {
537
+ if (typeof store.loadOlderByGroup === 'function') {
538
+ // Use an unbounded raw prefix, then project/slice visible rows below.
539
+ // This preserves loadOlderByGroup's hot+cold scan without letting raw
540
+ // reflection/internal rows consume the UI-visible page window.
541
+ rows = store.loadOlderByGroup(groupId, beforeSeq, Infinity).messages || [];
542
+ } else if (Number.isFinite(beforeSeq)) {
543
+ const all = typeof store.loadAllByGroup === 'function'
544
+ ? store.loadAllByGroup(groupId)
545
+ : store.loadRecentByGroup(groupId, Infinity);
546
+ rows = all.filter(m => parseSeqFromId(m?.id) < beforeSeq);
547
+ } else if (typeof store.loadAllByGroup === 'function') {
548
+ rows = store.loadAllByGroup(groupId);
549
+ } else {
550
+ rows = store.loadRecentByGroup(groupId, Infinity);
551
+ }
552
+ } catch (err) {
553
+ console.error('[Unify] visible history page load failed:', err?.message || err);
554
+ return { messages: [], oldestSeq: null, hasMore: false };
555
+ }
556
+
557
+ const visible = rows
558
+ .map(projectPersistedToVisibleHistoryEntry)
559
+ .filter(Boolean);
560
+ const messages = sliceLastNTurns(visible, limit);
561
+ const oldestSeq = messages.length ? parseSeqFromId(messages[0].id) : null;
562
+ const firstVisibleSeq = visible.length ? parseSeqFromId(visible[0].id) : null;
563
+ const hasMore = messages.length > 0
564
+ && Number.isFinite(oldestSeq)
565
+ && Number.isFinite(firstVisibleSeq)
566
+ && oldestSeq > firstVisibleSeq;
567
+
568
+ return {
569
+ messages,
570
+ oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
571
+ hasMore,
572
+ };
573
+ }
574
+
526
575
  /**
527
576
  * Hydrate a freshly-created GroupContext's history from the on-disk
528
577
  * conversation store. Returns an empty array if the session isn't
@@ -3283,12 +3332,17 @@ export async function handleUnifyLoadHistory(msg) {
3283
3332
  // ~20–25 turns; in the turn-count world 50 turns of UI scrollback is
3284
3333
  // still cheap and matches what the frontend already passes through.
3285
3334
  const limit = (typeof msg.limit === 'number') ? msg.limit : 50;
3286
- const messages = limit > 0 ? pickRecent(session.conversationStore, limit) : [];
3335
+ const visiblePage = groupId
3336
+ ? loadVisibleGroupHistoryPage(session.conversationStore, groupId, limit)
3337
+ : { messages: limit > 0 ? pickRecent(session.conversationStore, limit) : [], oldestSeq: null, hasMore: false };
3287
3338
  const compactSummary = session.conversationStore.readCompactSummary();
3339
+ const replayEntries = groupId
3340
+ ? visiblePage.messages
3341
+ : visiblePage.messages
3342
+ .map(projectPersistedToVisibleHistoryEntry)
3343
+ .filter(Boolean);
3288
3344
 
3289
- for (const m of messages) {
3290
- const entry = projectPersistedToHistoryEntry(m);
3291
- if (!entry) continue;
3345
+ for (const entry of replayEntries) {
3292
3346
  if (entry.role === 'user') {
3293
3347
  sendUnifyOutput({ type: 'user', message: { content: entry.content, id: entry.id || null } }, { groupId: entry.groupId || null });
3294
3348
  } else if (entry.role === 'assistant') {
@@ -3310,33 +3364,19 @@ export async function handleUnifyLoadHistory(msg) {
3310
3364
 
3311
3365
  // Compute the pagination cursor for the bootstrap load so the frontend
3312
3366
  // knows whether a "Load older messages" hint should be shown and where
3313
- // to start the next page. The cursor is the seq of the oldest replayed
3314
- // message; `hasMore` is true iff there's an earlier message in the
3315
- // group that we did NOT replay.
3367
+ // to start the next page. For group history, this is computed from the
3368
+ // visible projected page, not raw persisted rows, so reflection/internal
3369
+ // tail rows cannot consume the bootstrap window or create false hasMore.
3316
3370
  let hasMore = false;
3317
3371
  let oldestSeq = null;
3318
- if (groupId && messages.length > 0) {
3319
- const firstId = messages[0].id;
3320
- const seq = parseSeqFromId(firstId);
3321
- // Defend against malformed ids: a NaN cursor would round-trip back as
3322
- // a poison `beforeSeq` and degrade subsequent paginations to "give me
3323
- // the newest page again". Surface as null instead.
3324
- oldestSeq = Number.isFinite(seq) ? seq : null;
3325
- if (oldestSeq != null) {
3326
- // Consult the store for whether anything older exists in the same
3327
- // group. Cheap: a single extra `loadOlderByGroup` with turns=1.
3328
- try {
3329
- const probe = session.conversationStore.loadOlderByGroup(groupId, oldestSeq, 1);
3330
- hasMore = probe.messages.length > 0;
3331
- } catch (err) {
3332
- console.error('[Unify] history-load probe failed:', err.message);
3333
- }
3334
- }
3372
+ if (groupId) {
3373
+ hasMore = visiblePage.hasMore;
3374
+ oldestSeq = visiblePage.oldestSeq;
3335
3375
  }
3336
3376
 
3337
3377
  sendUnifyEvent({
3338
3378
  type: 'history_loaded',
3339
- count: messages.length,
3379
+ count: replayEntries.length,
3340
3380
  hasCompactSummary: !!compactSummary,
3341
3381
  totalHot: session.conversationStore.countHot(),
3342
3382
  totalCold: session.conversationStore.countCold(),
@@ -3378,7 +3418,7 @@ export async function handleUnifyLoadMoreHistory(msg) {
3378
3418
 
3379
3419
  let result;
3380
3420
  try {
3381
- result = session.conversationStore.loadOlderByGroup(groupId, beforeSeq, turns);
3421
+ result = loadVisibleGroupHistoryPage(session.conversationStore, groupId, turns, beforeSeq);
3382
3422
  } catch (err) {
3383
3423
  console.error('[Unify] loadOlderByGroup failed:', err.message);
3384
3424
  result = { messages: [], oldestSeq: null, hasMore: false };
@@ -3389,8 +3429,6 @@ export async function handleUnifyLoadMoreHistory(msg) {
3389
3429
  // server-side, and stable ids + speaker attribution ride with each row
3390
3430
  // so older-history prepend renders exactly like refresh replay.
3391
3431
  const projected = (result.messages || [])
3392
- .map(projectPersistedToHistoryEntry)
3393
- .filter(m => m && (m.role === 'user' || m.role === 'assistant'))
3394
3432
  .map(m => ({
3395
3433
  ...(m.id ? { id: m.id } : {}),
3396
3434
  role: m.role,