@yeaft/webchat-agent 0.1.903 → 0.1.904
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/connection/message-router.js +10 -9
- package/package.json +1 -1
- package/yeaft/compact/compactor.js +2 -2
- package/yeaft/config-api.js +16 -6
- package/yeaft/config.js +15 -2
- package/yeaft/conversation/persist.js +173 -60
- package/yeaft/engine.js +3 -3
- package/yeaft/llm/router.js +1 -1
- package/yeaft/memory/ams-registry.js +18 -19
- package/yeaft/pair-sanitize.js +1 -1
- package/yeaft/session.js +10 -1
- package/yeaft/snapshot-filter.js +88 -0
- package/yeaft/web-bridge.js +145 -132
package/yeaft/web-bridge.js
CHANGED
|
@@ -55,6 +55,8 @@ import {
|
|
|
55
55
|
import { persistYeaftAttachments, attachmentsForPersistence, persistedAttachmentPreviewPayload } from './attachments.js';
|
|
56
56
|
import { parseSeqFromId } from './conversation/persist.js';
|
|
57
57
|
import { sliceLastNTurns } from './turn-utils.js';
|
|
58
|
+
import { pairSanitize } from './pair-sanitize.js';
|
|
59
|
+
import { filterSnapshotForVp } from './snapshot-filter.js';
|
|
58
60
|
import { createVpStatusBroker } from './vp-status-broker.js';
|
|
59
61
|
import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
|
|
60
62
|
|
|
@@ -221,7 +223,7 @@ const vpCurrentTodos = new Map();
|
|
|
221
223
|
* router: ReturnType<typeof createRouter>,
|
|
222
224
|
* sessionHandle: object }>}
|
|
223
225
|
*/
|
|
224
|
-
const
|
|
226
|
+
const sessionContexts = new Map();
|
|
225
227
|
|
|
226
228
|
function vpKey(sessionId, vpId) {
|
|
227
229
|
return `${sessionId}::${vpId}`;
|
|
@@ -356,7 +358,7 @@ async function waitForRoutePromises(msgId) {
|
|
|
356
358
|
*/
|
|
357
359
|
function invalidateGroupContext(sessionId) {
|
|
358
360
|
if (!sessionId) return;
|
|
359
|
-
|
|
361
|
+
sessionContexts.delete(sessionId);
|
|
360
362
|
const prefix = `${sessionId}::`;
|
|
361
363
|
for (const [k, ctrl] of vpAborts) {
|
|
362
364
|
if (!k.startsWith(prefix)) continue;
|
|
@@ -458,7 +460,7 @@ let _vpUnsubscribe = null;
|
|
|
458
460
|
|
|
459
461
|
/**
|
|
460
462
|
* Per-group conversation history lives on the GroupContext entry
|
|
461
|
-
* (`
|
|
463
|
+
* (`sessionContexts.get(sessionId).history`). The pre-refactor module-level
|
|
462
464
|
* `conversationMessages` was a single array shared across every group —
|
|
463
465
|
* a user prompt in group-A would leak into group-B's next-turn snapshot
|
|
464
466
|
* because the bridge appended every turn to the same array regardless
|
|
@@ -466,7 +468,7 @@ let _vpUnsubscribe = null;
|
|
|
466
468
|
* the in-memory tape was unified.
|
|
467
469
|
*
|
|
468
470
|
* Post-refactor: each GroupContext owns its own `history`, lazily
|
|
469
|
-
* hydrated from `conversationStore.
|
|
471
|
+
* hydrated from `conversationStore.loadRecentBySession(sessionId)` on first
|
|
470
472
|
* access. Group-A and group-B are isolated.
|
|
471
473
|
*
|
|
472
474
|
* @typedef {Array<{role:'user'|'assistant'|'tool', content:string|Array, toolCalls?:Array, toolCallId?:string, isError?:boolean}>} GroupHistory
|
|
@@ -482,7 +484,7 @@ let _vpUnsubscribe = null;
|
|
|
482
484
|
* from disk (or explicitly assigned). The flag is required because an
|
|
483
485
|
* empty array is legitimate post-consolidate / post-clear state and
|
|
484
486
|
* MUST NOT trigger a re-hydrate. Without the flag, a partial entry
|
|
485
|
-
* would short-circuit `
|
|
487
|
+
* would short-circuit `getOrCreateSessionHistory` on truthy `[]` and skip
|
|
486
488
|
* the disk load.
|
|
487
489
|
*/
|
|
488
490
|
|
|
@@ -602,26 +604,26 @@ function loadVisibleGroupHistoryPage(store, sessionId, limit, beforeSeq = null)
|
|
|
602
604
|
|
|
603
605
|
let rows = [];
|
|
604
606
|
try {
|
|
605
|
-
if (typeof store.
|
|
606
|
-
const page = store.
|
|
607
|
+
if (typeof store.loadVisibleBySession === 'function') {
|
|
608
|
+
const page = store.loadVisibleBySession(sessionId, beforeSeq, limit);
|
|
607
609
|
return {
|
|
608
610
|
messages: (page.messages || []).map(projectPersistedToVisibleHistoryEntry).filter(Boolean),
|
|
609
611
|
oldestSeq: (typeof page.oldestSeq === 'number') ? page.oldestSeq : null,
|
|
610
612
|
hasMore: !!page.hasMore,
|
|
611
613
|
};
|
|
612
|
-
} else if (typeof store.
|
|
614
|
+
} else if (typeof store.loadOlderBySession === 'function') {
|
|
613
615
|
// Compatibility fallback for older test doubles: use an unbounded raw
|
|
614
616
|
// prefix, then project/slice visible rows below.
|
|
615
|
-
rows = store.
|
|
617
|
+
rows = store.loadOlderBySession(sessionId, beforeSeq, Infinity).messages || [];
|
|
616
618
|
} else if (Number.isFinite(beforeSeq)) {
|
|
617
|
-
const all = typeof store.
|
|
618
|
-
? store.
|
|
619
|
-
: store.
|
|
619
|
+
const all = typeof store.loadAllBySession === 'function'
|
|
620
|
+
? store.loadAllBySession(sessionId)
|
|
621
|
+
: store.loadRecentBySession(sessionId, Infinity);
|
|
620
622
|
rows = all.filter(m => parseSeqFromId(m?.id) < beforeSeq);
|
|
621
|
-
} else if (typeof store.
|
|
622
|
-
rows = store.
|
|
623
|
+
} else if (typeof store.loadAllBySession === 'function') {
|
|
624
|
+
rows = store.loadAllBySession(sessionId);
|
|
623
625
|
} else {
|
|
624
|
-
rows = store.
|
|
626
|
+
rows = store.loadRecentBySession(sessionId, Infinity);
|
|
625
627
|
}
|
|
626
628
|
} catch (err) {
|
|
627
629
|
console.error('[Yeaft] visible history page load failed:', err?.message || err);
|
|
@@ -658,7 +660,7 @@ function hydrateGroupHistory(sessionId) {
|
|
|
658
660
|
if (!session?.conversationStore || !sessionId) return [];
|
|
659
661
|
let recent;
|
|
660
662
|
try {
|
|
661
|
-
recent = session.conversationStore.
|
|
663
|
+
recent = session.conversationStore.loadRecentBySession(sessionId);
|
|
662
664
|
} catch (err) {
|
|
663
665
|
console.warn('[Yeaft] hydrateGroupHistory failed (sessionId=%s):', sessionId, err?.message || err);
|
|
664
666
|
return [];
|
|
@@ -674,7 +676,7 @@ function hydrateGroupHistory(sessionId) {
|
|
|
674
676
|
/**
|
|
675
677
|
* Get-or-create the per-group history array. Used everywhere the bridge
|
|
676
678
|
* needs to read/append/snapshot a group's conversation tape. Lazily
|
|
677
|
-
* inserts an entry into `
|
|
679
|
+
* inserts an entry into `sessionContexts` on first access — no
|
|
678
680
|
* `sessionHandle` required (history is independent of coord/router
|
|
679
681
|
* lifecycle, so a sub-agent / route_forward path that hasn't yet
|
|
680
682
|
* opened the group can still read history).
|
|
@@ -687,9 +689,9 @@ function hydrateGroupHistory(sessionId) {
|
|
|
687
689
|
* @param {string} sessionId
|
|
688
690
|
* @returns {GroupHistory}
|
|
689
691
|
*/
|
|
690
|
-
function
|
|
692
|
+
function getOrCreateSessionHistory(sessionId) {
|
|
691
693
|
if (!sessionId) return [];
|
|
692
|
-
let entry =
|
|
694
|
+
let entry = sessionContexts.get(sessionId);
|
|
693
695
|
// Use `historyHydrated` rather than truthiness on `history` itself —
|
|
694
696
|
// an empty array (post-consolidate, post-clear, or a partial entry
|
|
695
697
|
// seeded by an early `getOrCreateSessionContext` call before data was
|
|
@@ -699,7 +701,7 @@ function getOrCreateGroupHistory(sessionId) {
|
|
|
699
701
|
if (entry && entry.historyHydrated) return entry.history;
|
|
700
702
|
if (!entry) {
|
|
701
703
|
entry = makeGroupContextStub();
|
|
702
|
-
|
|
704
|
+
sessionContexts.set(sessionId, entry);
|
|
703
705
|
}
|
|
704
706
|
entry.history = hydrateGroupHistory(sessionId);
|
|
705
707
|
entry.historyHydrated = true;
|
|
@@ -720,10 +722,10 @@ function getOrCreateGroupHistory(sessionId) {
|
|
|
720
722
|
*/
|
|
721
723
|
function setGroupHistory(sessionId, next) {
|
|
722
724
|
if (!sessionId) return;
|
|
723
|
-
let entry =
|
|
725
|
+
let entry = sessionContexts.get(sessionId);
|
|
724
726
|
if (!entry) {
|
|
725
727
|
entry = makeGroupContextStub();
|
|
726
|
-
|
|
728
|
+
sessionContexts.set(sessionId, entry);
|
|
727
729
|
}
|
|
728
730
|
entry.history = next;
|
|
729
731
|
entry.historyHydrated = true;
|
|
@@ -737,7 +739,7 @@ function setGroupHistory(sessionId, next) {
|
|
|
737
739
|
* @param {string} sessionId
|
|
738
740
|
*/
|
|
739
741
|
export function __testGroupHistory(sessionId) {
|
|
740
|
-
return
|
|
742
|
+
return getOrCreateSessionHistory(sessionId);
|
|
741
743
|
}
|
|
742
744
|
|
|
743
745
|
/**
|
|
@@ -758,12 +760,12 @@ export function __testSetSession(sessionLike) {
|
|
|
758
760
|
/**
|
|
759
761
|
* Test-only: peek at the GroupContext entry for a group (or undefined
|
|
760
762
|
* if never seeded). Lets tests assert the `historyHydrated` flag without
|
|
761
|
-
* exporting the entire `
|
|
763
|
+
* exporting the entire `sessionContexts` Map.
|
|
762
764
|
*
|
|
763
765
|
* @param {string} sessionId
|
|
764
766
|
*/
|
|
765
767
|
export function __testGroupContextEntry(sessionId) {
|
|
766
|
-
return
|
|
768
|
+
return sessionContexts.get(sessionId);
|
|
767
769
|
}
|
|
768
770
|
|
|
769
771
|
/**
|
|
@@ -887,9 +889,9 @@ function getOrCreateVpEngine(sessionId, vpId, threadId = 'main') {
|
|
|
887
889
|
* @returns {{ coord: object, router: object, sessionHandle: object }}
|
|
888
890
|
*/
|
|
889
891
|
function getOrCreateSessionContext(sessionId, sessionHandle) {
|
|
890
|
-
let entry =
|
|
892
|
+
let entry = sessionContexts.get(sessionId);
|
|
891
893
|
if (entry && entry.coord && entry.router) return entry;
|
|
892
|
-
// Either no entry, or a partial entry seeded by `
|
|
894
|
+
// Either no entry, or a partial entry seeded by `getOrCreateSessionHistory`
|
|
893
895
|
// (no coord/router yet). Build the coord/router and merge into the
|
|
894
896
|
// existing record so the per-group history reference and hydration
|
|
895
897
|
// flag are preserved.
|
|
@@ -899,13 +901,13 @@ function getOrCreateSessionContext(sessionId, sessionHandle) {
|
|
|
899
901
|
const router = createRouter({ coordinator: coord });
|
|
900
902
|
if (!entry) {
|
|
901
903
|
entry = makeGroupContextStub();
|
|
902
|
-
|
|
904
|
+
sessionContexts.set(sessionId, entry);
|
|
903
905
|
}
|
|
904
906
|
entry.coord = coord;
|
|
905
907
|
entry.router = router;
|
|
906
908
|
entry.sessionHandle = sessionHandle;
|
|
907
909
|
// Defend against a future caller that builds a coord/router without
|
|
908
|
-
// having gone through `
|
|
910
|
+
// having gone through `getOrCreateSessionHistory` first: a partial entry
|
|
909
911
|
// could exist with `historyHydrated:false`, so do the load now.
|
|
910
912
|
if (!entry.historyHydrated) {
|
|
911
913
|
entry.history = hydrateGroupHistory(sessionId);
|
|
@@ -1048,8 +1050,13 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1048
1050
|
vpAborts.set(key, vpAbort);
|
|
1049
1051
|
turnAbortCtrls.set(turnId, vpAbort);
|
|
1050
1052
|
turnAbortMeta.set(turnId, { sessionId, vpId, threadId: thread.threadId, key });
|
|
1051
|
-
const baseSnapshot =
|
|
1052
|
-
|
|
1053
|
+
const baseSnapshot = pairSanitize(
|
|
1054
|
+
filterSnapshotForVp(
|
|
1055
|
+
getOrCreateSessionHistory(sessionId)
|
|
1056
|
+
.filter((m) => !m.threadId || m.threadId === 'main' || m.threadId === thread.threadId),
|
|
1057
|
+
vpId,
|
|
1058
|
+
),
|
|
1059
|
+
);
|
|
1053
1060
|
const trigger = envelope?.trigger || 'fallback';
|
|
1054
1061
|
const { text, prompt, promptParts } = buildVpPromptPayload(vpId, envelope);
|
|
1055
1062
|
|
|
@@ -1104,7 +1111,7 @@ function ensureDriverRunning(sessionId, vpId, threadId = 'main') {
|
|
|
1104
1111
|
try {
|
|
1105
1112
|
if (text && envelope?.msg) {
|
|
1106
1113
|
sendYeaftEvent({
|
|
1107
|
-
type: '
|
|
1114
|
+
type: 'session_message',
|
|
1108
1115
|
sessionId,
|
|
1109
1116
|
vpId,
|
|
1110
1117
|
threadId: thread.threadId,
|
|
@@ -1226,7 +1233,7 @@ export async function __testResetVpState() {
|
|
|
1226
1233
|
vpDrivers.clear();
|
|
1227
1234
|
vpEngines.clear();
|
|
1228
1235
|
vpAborts.clear();
|
|
1229
|
-
|
|
1236
|
+
sessionContexts.clear();
|
|
1230
1237
|
vpCurrentTodos.clear();
|
|
1231
1238
|
threadClassifier = defaultClassifyThread;
|
|
1232
1239
|
// Per-group compact in-flight + pending state lives on the session's
|
|
@@ -1413,31 +1420,20 @@ export function handleYeaftVpRead(msg) {
|
|
|
1413
1420
|
}
|
|
1414
1421
|
|
|
1415
1422
|
/**
|
|
1416
|
-
*
|
|
1423
|
+
* Session CRUD wired to WS events.
|
|
1417
1424
|
*/
|
|
1418
|
-
function
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
if (out.sessionId != null && out.groupId == null) out.groupId = out.sessionId;
|
|
1424
|
-
if (out.groupId != null && out.sessionId == null) out.sessionId = out.groupId;
|
|
1425
|
-
sendYeaftEvent({ type: 'group_crud_result', ...out });
|
|
1426
|
-
sendYeaftEvent({ type: 'session_crud_result', ...out });
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
function sendGroupSnapshotBroadcast() {
|
|
1425
|
+
function sendSessionCrudResult(payload) {
|
|
1426
|
+
sendYeaftEvent({ type: 'session_crud_result', ...payload });
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
function sendSessionSnapshotBroadcast() {
|
|
1430
1430
|
try {
|
|
1431
1431
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1432
1432
|
if (!yeaftDir) return;
|
|
1433
1433
|
const sessions = snapshotSessions(yeaftDir);
|
|
1434
|
-
// Wire-compat: emit both legacy `group_list_updated` (groups field)
|
|
1435
|
-
// and new `session_list_updated` (sessions field). Old web bundles
|
|
1436
|
-
// listen on the former.
|
|
1437
|
-
sendYeaftEvent({ type: 'group_list_updated', groups: sessions });
|
|
1438
1434
|
sendYeaftEvent({ type: 'session_list_updated', sessions });
|
|
1439
1435
|
} catch (err) {
|
|
1440
|
-
console.warn('[Yeaft]
|
|
1436
|
+
console.warn('[Yeaft] sendSessionSnapshotBroadcast failed:', err?.message || err);
|
|
1441
1437
|
}
|
|
1442
1438
|
}
|
|
1443
1439
|
|
|
@@ -1451,28 +1447,26 @@ function sendGroupSnapshotBroadcast() {
|
|
|
1451
1447
|
*
|
|
1452
1448
|
* fix-yeaft-session-per-agent: previously, Agent B's sessions were
|
|
1453
1449
|
* invisible in the unified sidebar until the user clicked into B's
|
|
1454
|
-
* Yeaft view, because `
|
|
1450
|
+
* Yeaft view, because `sendSessionSnapshotBroadcast` only fired from
|
|
1455
1451
|
* `ensureSessionLoaded`. That made the cross-agent list look broken
|
|
1456
1452
|
* ("I see A but not B even though B is online") and was a major
|
|
1457
1453
|
* contributor to the "session list disappears on switch" symptom.
|
|
1458
1454
|
*/
|
|
1459
|
-
export {
|
|
1455
|
+
export { sendSessionSnapshotBroadcast as broadcastYeaftSessionSnapshotEager };
|
|
1460
1456
|
|
|
1461
|
-
function
|
|
1457
|
+
function sendSessionRosterChanged(session) {
|
|
1462
1458
|
if (!session) return;
|
|
1463
1459
|
const payload = {
|
|
1464
1460
|
sessionId: session.id,
|
|
1465
|
-
groupId: session.id, // wire-compat for old web bundles
|
|
1466
1461
|
name: session.name,
|
|
1467
1462
|
roster: session.roster,
|
|
1468
1463
|
defaultVpId: session.defaultVpId,
|
|
1469
1464
|
workDir: session.workDir || '',
|
|
1470
1465
|
};
|
|
1471
|
-
sendYeaftEvent({ type: 'group_roster_changed', ...payload });
|
|
1472
1466
|
sendYeaftEvent({ type: 'session_roster_changed', ...payload });
|
|
1473
1467
|
}
|
|
1474
1468
|
|
|
1475
|
-
function
|
|
1469
|
+
function sessionErrorPayload(err) {
|
|
1476
1470
|
let code = 'unknown';
|
|
1477
1471
|
if (err instanceof SessionCrudError) code = err.code;
|
|
1478
1472
|
else if (err instanceof SessionConfigError) code = err.code;
|
|
@@ -1488,9 +1482,9 @@ export function handleYeaftListSessions(msg) {
|
|
|
1488
1482
|
try {
|
|
1489
1483
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1490
1484
|
const groups = snapshotSessions(yeaftDir);
|
|
1491
|
-
|
|
1485
|
+
sendSessionCrudResult({ op: 'list', requestId, ok: true, sessions: groups });
|
|
1492
1486
|
} catch (err) {
|
|
1493
|
-
|
|
1487
|
+
sendSessionCrudResult({ op: 'list', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1494
1488
|
}
|
|
1495
1489
|
}
|
|
1496
1490
|
|
|
@@ -1500,10 +1494,10 @@ export function handleYeaftCreateSession(msg) {
|
|
|
1500
1494
|
try {
|
|
1501
1495
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1502
1496
|
const group = createSessionFromSpec(yeaftDir, payload);
|
|
1503
|
-
|
|
1504
|
-
|
|
1497
|
+
sendSessionCrudResult({ op: 'create', requestId, ok: true, session: group });
|
|
1498
|
+
sendSessionSnapshotBroadcast();
|
|
1505
1499
|
} catch (err) {
|
|
1506
|
-
|
|
1500
|
+
sendSessionCrudResult({ op: 'create', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1507
1501
|
}
|
|
1508
1502
|
}
|
|
1509
1503
|
|
|
@@ -1515,10 +1509,10 @@ export function handleYeaftRenameSession(msg) {
|
|
|
1515
1509
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1516
1510
|
const group = renameSession(yeaftDir, sessionId, name);
|
|
1517
1511
|
invalidateGroupContext(sessionId);
|
|
1518
|
-
|
|
1519
|
-
|
|
1512
|
+
sendSessionCrudResult({ op: 'rename', requestId, ok: true, session: group });
|
|
1513
|
+
sendSessionSnapshotBroadcast();
|
|
1520
1514
|
} catch (err) {
|
|
1521
|
-
|
|
1515
|
+
sendSessionCrudResult({ op: 'rename', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1522
1516
|
}
|
|
1523
1517
|
}
|
|
1524
1518
|
|
|
@@ -1555,10 +1549,10 @@ export function handleYeaftUpdateSession(msg) {
|
|
|
1555
1549
|
group = updateSessionAnnouncement(yeaftDir, sessionId, patch.announcement);
|
|
1556
1550
|
}
|
|
1557
1551
|
invalidateGroupContext(sessionId);
|
|
1558
|
-
|
|
1559
|
-
|
|
1552
|
+
sendSessionCrudResult({ op: 'update', requestId, ok: true, session: group });
|
|
1553
|
+
sendSessionSnapshotBroadcast();
|
|
1560
1554
|
} catch (err) {
|
|
1561
|
-
|
|
1555
|
+
sendSessionCrudResult({ op: 'update', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1562
1556
|
}
|
|
1563
1557
|
}
|
|
1564
1558
|
|
|
@@ -1585,10 +1579,10 @@ export function handleYeaftUpdateSessionConfig(msg) {
|
|
|
1585
1579
|
if (k.startsWith(prefix)) vpEngines.delete(k);
|
|
1586
1580
|
}
|
|
1587
1581
|
invalidateGroupContext(sessionId);
|
|
1588
|
-
|
|
1589
|
-
|
|
1582
|
+
sendSessionCrudResult({ op: 'update_config', requestId, ok: true, sessionId, config: savedConfig });
|
|
1583
|
+
sendSessionSnapshotBroadcast();
|
|
1590
1584
|
} catch (err) {
|
|
1591
|
-
|
|
1585
|
+
sendSessionCrudResult({ op: 'update_config', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1592
1586
|
}
|
|
1593
1587
|
}
|
|
1594
1588
|
|
|
@@ -1599,10 +1593,10 @@ export function handleYeaftArchiveSession(msg) {
|
|
|
1599
1593
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1600
1594
|
const result = archiveSession(yeaftDir, sessionId);
|
|
1601
1595
|
invalidateGroupContext(sessionId);
|
|
1602
|
-
|
|
1603
|
-
|
|
1596
|
+
sendSessionCrudResult({ op: 'archive', requestId, ok: true, sessionId: result.sessionId });
|
|
1597
|
+
sendSessionSnapshotBroadcast();
|
|
1604
1598
|
} catch (err) {
|
|
1605
|
-
|
|
1599
|
+
sendSessionCrudResult({ op: 'archive', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1606
1600
|
}
|
|
1607
1601
|
}
|
|
1608
1602
|
|
|
@@ -1633,16 +1627,16 @@ export function handleYeaftDeleteSession(msg) {
|
|
|
1633
1627
|
for (const k of Array.from(vpEngines.keys())) {
|
|
1634
1628
|
if (k.startsWith(prefix)) vpEngines.delete(k);
|
|
1635
1629
|
}
|
|
1636
|
-
|
|
1630
|
+
sendSessionCrudResult({
|
|
1637
1631
|
op: 'delete',
|
|
1638
1632
|
requestId,
|
|
1639
1633
|
ok: true,
|
|
1640
1634
|
sessionId: result.sessionId,
|
|
1641
1635
|
messagesRemoved,
|
|
1642
1636
|
});
|
|
1643
|
-
|
|
1637
|
+
sendSessionSnapshotBroadcast();
|
|
1644
1638
|
} catch (err) {
|
|
1645
|
-
|
|
1639
|
+
sendSessionCrudResult({ op: 'delete', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1646
1640
|
}
|
|
1647
1641
|
}
|
|
1648
1642
|
|
|
@@ -1654,10 +1648,10 @@ export function handleYeaftSessionAddMember(msg) {
|
|
|
1654
1648
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1655
1649
|
const group = addMember(yeaftDir, sessionId, vpId);
|
|
1656
1650
|
invalidateGroupContext(sessionId);
|
|
1657
|
-
|
|
1658
|
-
|
|
1651
|
+
sendSessionCrudResult({ op: 'add_member', requestId, ok: true, session: group });
|
|
1652
|
+
sendSessionRosterChanged(group);
|
|
1659
1653
|
} catch (err) {
|
|
1660
|
-
|
|
1654
|
+
sendSessionCrudResult({ op: 'add_member', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1661
1655
|
}
|
|
1662
1656
|
}
|
|
1663
1657
|
|
|
@@ -1675,10 +1669,10 @@ export function handleYeaftSessionRemoveMember(msg) {
|
|
|
1675
1669
|
for (const key of Array.from(vpEngines.keys())) {
|
|
1676
1670
|
if (key.startsWith(removedPrefix)) vpEngines.delete(key);
|
|
1677
1671
|
}
|
|
1678
|
-
|
|
1679
|
-
|
|
1672
|
+
sendSessionCrudResult({ op: 'remove_member', requestId, ok: true, session: group });
|
|
1673
|
+
sendSessionRosterChanged(group);
|
|
1680
1674
|
} catch (err) {
|
|
1681
|
-
|
|
1675
|
+
sendSessionCrudResult({ op: 'remove_member', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1682
1676
|
}
|
|
1683
1677
|
}
|
|
1684
1678
|
|
|
@@ -1690,10 +1684,10 @@ export function handleYeaftSessionSetDefaultVp(msg) {
|
|
|
1690
1684
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
1691
1685
|
const group = setSessionDefaultVp(yeaftDir, sessionId, vpId);
|
|
1692
1686
|
invalidateGroupContext(sessionId);
|
|
1693
|
-
|
|
1694
|
-
|
|
1687
|
+
sendSessionCrudResult({ op: 'set_default_vp', requestId, ok: true, session: group });
|
|
1688
|
+
sendSessionRosterChanged(group);
|
|
1695
1689
|
} catch (err) {
|
|
1696
|
-
|
|
1690
|
+
sendSessionCrudResult({ op: 'set_default_vp', requestId, ok: false, error: sessionErrorPayload(err) });
|
|
1697
1691
|
}
|
|
1698
1692
|
}
|
|
1699
1693
|
|
|
@@ -2170,20 +2164,21 @@ function handleEngineEvent(event, hctx) {
|
|
|
2170
2164
|
}
|
|
2171
2165
|
|
|
2172
2166
|
/**
|
|
2173
|
-
* Handle a
|
|
2167
|
+
* Handle a yeaft_session_chat message from the web UI — the SOLE Yeaft
|
|
2174
2168
|
* conversation entry point.
|
|
2175
2169
|
*
|
|
2176
2170
|
* Contract (post-consolidation, was previously split between handleYeaftChat
|
|
2177
2171
|
* and handleYeaftSessionSend):
|
|
2178
|
-
* - Frontend ALWAYS sends `
|
|
2172
|
+
* - Frontend ALWAYS sends `yeaft_session_chat`. There is no `yeaft_chat`.
|
|
2179
2173
|
* - `sessionId` defaults to `'grp_default'` if missing — Yeaft is a single
|
|
2180
|
-
* conversation backed by the default
|
|
2181
|
-
* a
|
|
2182
|
-
* -
|
|
2183
|
-
*
|
|
2174
|
+
* conversation backed by the default session; the user is never "outside"
|
|
2175
|
+
* a session.
|
|
2176
|
+
* - Sessions are created up-front via `handleYeaftCreateSession`; this
|
|
2177
|
+
* handler does NOT seed missing sessions on the fly. An unknown
|
|
2178
|
+
* sessionId surfaces a clear "session not found" error to the UI.
|
|
2184
2179
|
* - Coordinator is MANDATORY (this is what guarantees ctx.router is wired
|
|
2185
2180
|
* so the `route_forward` tool can never trip `router_unavailable`).
|
|
2186
|
-
* - No legacy "no-
|
|
2181
|
+
* - No legacy "no-session" fallback paths — they were the source of the
|
|
2187
2182
|
* router_unavailable bug fixed in v0.1.671.
|
|
2188
2183
|
*/
|
|
2189
2184
|
export async function handleYeaftSessionSend(msg) {
|
|
@@ -2228,12 +2223,11 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2228
2223
|
|
|
2229
2224
|
await ensureSessionLoaded();
|
|
2230
2225
|
|
|
2231
|
-
// Open the
|
|
2232
|
-
//
|
|
2233
|
-
//
|
|
2226
|
+
// Open the session. The default `grp_default` no longer self-seeds
|
|
2227
|
+
// here — session creation happens up-front via `handleYeaftCreateSession`,
|
|
2228
|
+
// so a missing dir surfaces a clear error rather than masking it.
|
|
2234
2229
|
let sessionHandle = null;
|
|
2235
2230
|
let sessionRoot = null;
|
|
2236
|
-
let seedFailed = false;
|
|
2237
2231
|
try {
|
|
2238
2232
|
const groupYeaftDir = resolveSessionYeaftDir(yeaftDir, sessionId);
|
|
2239
2233
|
sessionRoot = sessionsRoot(groupYeaftDir);
|
|
@@ -2247,19 +2241,16 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2247
2241
|
// default-group row across agents. Now we surface the not-found
|
|
2248
2242
|
// case so the web can show an "agent offline / session missing"
|
|
2249
2243
|
// hint instead of silently creating a different session.
|
|
2250
|
-
console.warn('[Yeaft]
|
|
2244
|
+
console.warn('[Yeaft] yeaft_session_chat: sessionId %s not found', sessionId);
|
|
2251
2245
|
}
|
|
2252
2246
|
} catch (err) {
|
|
2253
|
-
console.warn('[Yeaft]
|
|
2247
|
+
console.warn('[Yeaft] yeaft_session_chat: session open failed', err?.message || err);
|
|
2254
2248
|
}
|
|
2255
2249
|
|
|
2256
2250
|
if (!sessionHandle) {
|
|
2257
|
-
const errText = seedFailed
|
|
2258
|
-
? `⚠️ Failed to seed default group ${sessionId} — check group .yeaft permissions.`
|
|
2259
|
-
: `⚠️ Group ${sessionId} not found.`;
|
|
2260
2251
|
sendYeaftOutput({
|
|
2261
2252
|
type: 'assistant',
|
|
2262
|
-
message: { content: [{ type: 'text', text:
|
|
2253
|
+
message: { content: [{ type: 'text', text: `⚠️ Session ${sessionId} not found.` }] },
|
|
2263
2254
|
}, { sessionId });
|
|
2264
2255
|
sendYeaftOutput({ type: 'result', result_text: '' }, { sessionId });
|
|
2265
2256
|
return;
|
|
@@ -2284,7 +2275,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2284
2275
|
if (rosterMutated) {
|
|
2285
2276
|
try { sessionHandle.close && sessionHandle.close(); } catch { /* best-effort */ }
|
|
2286
2277
|
sessionHandle = openSession(sessionRoot, sessionId);
|
|
2287
|
-
|
|
2278
|
+
sendSessionRosterChanged(sessionHandle.getMeta());
|
|
2288
2279
|
}
|
|
2289
2280
|
}
|
|
2290
2281
|
const meta2 = sessionHandle.getMeta();
|
|
@@ -2293,12 +2284,12 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2293
2284
|
setSessionDefaultVp(yeaftDir, sessionId, meta2.roster[0]);
|
|
2294
2285
|
try { sessionHandle.close && sessionHandle.close(); } catch { /* best-effort */ }
|
|
2295
2286
|
sessionHandle = openSession(sessionRoot, sessionId);
|
|
2296
|
-
|
|
2287
|
+
sendSessionRosterChanged(sessionHandle.getMeta());
|
|
2297
2288
|
rosterMutated = true;
|
|
2298
2289
|
} catch { /* best-effort */ }
|
|
2299
2290
|
}
|
|
2300
2291
|
} catch (err) {
|
|
2301
|
-
console.warn('[Yeaft]
|
|
2292
|
+
console.warn('[Yeaft] yeaft_session_chat: auto-roster heal failed', err?.message || err);
|
|
2302
2293
|
}
|
|
2303
2294
|
|
|
2304
2295
|
// task-707: per-group persistent coordinator/router. Created once per
|
|
@@ -2307,7 +2298,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2307
2298
|
// we replace the cached coord so it points at the freshly-opened
|
|
2308
2299
|
// sessionHandle.
|
|
2309
2300
|
if (rosterMutated) {
|
|
2310
|
-
|
|
2301
|
+
sessionContexts.delete(sessionId);
|
|
2311
2302
|
}
|
|
2312
2303
|
const sessionCtx = getOrCreateSessionContext(sessionId, sessionHandle);
|
|
2313
2304
|
const coord = sessionCtx.coord;
|
|
@@ -2331,7 +2322,7 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2331
2322
|
try {
|
|
2332
2323
|
attachmentBundle = persistYeaftAttachments(inboundFiles, { subdir: sessionId });
|
|
2333
2324
|
} catch (err) {
|
|
2334
|
-
console.warn('[Yeaft]
|
|
2325
|
+
console.warn('[Yeaft] yeaft_session_chat: attachment persist failed', err?.message || err);
|
|
2335
2326
|
}
|
|
2336
2327
|
}
|
|
2337
2328
|
// Surface partial / total upload failures to the user. We don't abort
|
|
@@ -2370,10 +2361,10 @@ export async function handleYeaftSessionSend(msg) {
|
|
|
2370
2361
|
_promptSuffix: attachmentBundle.promptSuffix,
|
|
2371
2362
|
});
|
|
2372
2363
|
} catch (err) {
|
|
2373
|
-
console.warn('[Yeaft]
|
|
2364
|
+
console.warn('[Yeaft] yeaft_session_chat: coord.ingest failed', err?.message || err);
|
|
2374
2365
|
sendYeaftOutput({
|
|
2375
2366
|
type: 'assistant',
|
|
2376
|
-
message: { content: [{ type: 'text', text: `⚠️
|
|
2367
|
+
message: { content: [{ type: 'text', text: `⚠️ Session dispatch error: ${err?.message || err}` }] },
|
|
2377
2368
|
}, { sessionId });
|
|
2378
2369
|
sendYeaftOutput({ type: 'result', result_text: '' }, { sessionId });
|
|
2379
2370
|
return;
|
|
@@ -2574,7 +2565,7 @@ async function ensureSessionLoaded() {
|
|
|
2574
2565
|
|
|
2575
2566
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
2576
2567
|
|
|
2577
|
-
// Per-group history is hydrated lazily on first `
|
|
2568
|
+
// Per-group history is hydrated lazily on first `getOrCreateSessionHistory`
|
|
2578
2569
|
// — there's no global "all conversations" tape any more.
|
|
2579
2570
|
|
|
2580
2571
|
sendYeaftEvent({
|
|
@@ -2587,7 +2578,7 @@ async function ensureSessionLoaded() {
|
|
|
2587
2578
|
tools: session.status.tools,
|
|
2588
2579
|
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
2589
2580
|
});
|
|
2590
|
-
|
|
2581
|
+
sendSessionSnapshotBroadcast();
|
|
2591
2582
|
// vp-status: rebuild frontend status table from authoritative agent
|
|
2592
2583
|
// memory. Sent unconditionally so reconnect/refresh paths get the same
|
|
2593
2584
|
// bootstrap as first-load (the broker dedup logic makes a redundant
|
|
@@ -2713,7 +2704,7 @@ async function raceWithEscalation(inner, { deadlineMs, onEscalate }) {
|
|
|
2713
2704
|
* appended to `conversationMessages`.
|
|
2714
2705
|
*
|
|
2715
2706
|
* task-707: takes a coordinator `envelope` rather than the coordinator
|
|
2716
|
-
* itself; the persistent coord lives in `
|
|
2707
|
+
* itself; the persistent coord lives in `sessionContexts[sessionId]`. Uses
|
|
2717
2708
|
* `getOrCreateVpEngine(sessionId, vpId)` so each VP runs against its own
|
|
2718
2709
|
* Engine instance — private state (`#currentAbortCtrl`, `#__queryCounter`,
|
|
2719
2710
|
* `#pendingT2`, `#abortReason`, `#adjustRanByGroup`, `#execLog`) does not
|
|
@@ -2794,10 +2785,10 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
2794
2785
|
|
|
2795
2786
|
// task-707: per-VP engine + persistent group coord. The coord is
|
|
2796
2787
|
// created in handleYeaftSessionSend via getOrCreateSessionContext and
|
|
2797
|
-
// cached on `
|
|
2788
|
+
// cached on `sessionContexts`; we pull it here so route_forward
|
|
2798
2789
|
// (router built from this same coord) lands envelopes back on the
|
|
2799
2790
|
// right inbox set.
|
|
2800
|
-
const sessionCtx =
|
|
2791
|
+
const sessionCtx = sessionContexts.get(sessionId);
|
|
2801
2792
|
const sessionCoordinator = sessionCtx?.coord || null;
|
|
2802
2793
|
const queryOpts = buildVpQueryOpts({
|
|
2803
2794
|
vpId,
|
|
@@ -2856,7 +2847,7 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
2856
2847
|
}
|
|
2857
2848
|
|
|
2858
2849
|
// Turn completed — atomically append this VP's output to shared history.
|
|
2859
|
-
|
|
2850
|
+
appendTurnToSessionHistory(sessionId, threadId, vpId, [prompt, ...appendedUserPrompts], assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum);
|
|
2860
2851
|
|
|
2861
2852
|
sendYeaftOutput({
|
|
2862
2853
|
type: 'assistant',
|
|
@@ -2974,23 +2965,31 @@ async function runVpTurn({ prompt, promptParts = null, sessionId, vpId, threadId
|
|
|
2974
2965
|
* exactly once, with the same threadId as the running thread. The
|
|
2975
2966
|
* engine's own `conversationMessages` (with T1/T2 collapse applied)
|
|
2976
2967
|
* is persisted to disk via stop-hooks, so the next turn's history is
|
|
2977
|
-
* read from disk via `
|
|
2968
|
+
* read from disk via `loadRecentBySession` on next session boot. Within
|
|
2978
2969
|
* a session, this in-memory tape carries the un-collapsed form — which
|
|
2979
2970
|
* is fine because each VP turn's `engine.query` re-collapses on the fly.
|
|
2980
2971
|
*/
|
|
2981
|
-
function
|
|
2972
|
+
function appendTurnToSessionHistory(sessionId, threadId, vpId, prompts, assistantTextParts, toolCallsAccum, toolResultsAccum, thinkingBlocksAccum) {
|
|
2982
2973
|
if (!sessionId) return;
|
|
2983
|
-
const history =
|
|
2974
|
+
const history = getOrCreateSessionHistory(sessionId);
|
|
2984
2975
|
const promptList = Array.isArray(prompts) ? prompts : [prompts];
|
|
2985
2976
|
for (const prompt of promptList) {
|
|
2986
2977
|
if (typeof prompt === 'string' && prompt.trim()) {
|
|
2978
|
+
// user rows intentionally carry NO speakerVpId — every VP in the
|
|
2979
|
+
// session should see the prompt in their history.
|
|
2987
2980
|
history.push({ role: 'user', content: prompt, threadId: threadId || 'main' });
|
|
2988
2981
|
}
|
|
2989
2982
|
}
|
|
2990
2983
|
|
|
2991
2984
|
const fullText = assistantTextParts.join('');
|
|
2992
2985
|
if (fullText || toolCallsAccum.length > 0) {
|
|
2986
|
+
// Stamp speakerVpId on assistant + tool rows so the in-memory
|
|
2987
|
+
// baseSnapshot filter (filterSnapshotForVp) can mirror the disk
|
|
2988
|
+
// replay's per-VP isolation rules. Without this stamp, the next
|
|
2989
|
+
// VP turn would inherit the previous VP's tool_use ids without
|
|
2990
|
+
// matching tool_result rows → Anthropic API 422.
|
|
2993
2991
|
const assistantMsg = { role: 'assistant', content: fullText, threadId: threadId || 'main' };
|
|
2992
|
+
if (vpId) assistantMsg.speakerVpId = vpId;
|
|
2994
2993
|
if (toolCallsAccum.length > 0) {
|
|
2995
2994
|
assistantMsg.toolCalls = toolCallsAccum.map(tc => ({
|
|
2996
2995
|
id: tc.id,
|
|
@@ -3002,7 +3001,9 @@ function appendTurnToGroupHistory(sessionId, threadId, prompts, assistantTextPar
|
|
|
3002
3001
|
// requires us to echo them back on the next request or the API
|
|
3003
3002
|
// returns "content[].thinking in the thinking mode must be passed
|
|
3004
3003
|
// back to the API". The signature is server-private — it stays in
|
|
3005
|
-
// this in-memory history and in agent-side persistence only.
|
|
3004
|
+
// this in-memory history and in agent-side persistence only. The
|
|
3005
|
+
// signature is also VP-private; filterSnapshotForVp drops it from
|
|
3006
|
+
// OTHER VPs' rows before each turn's payload is built.
|
|
3006
3007
|
if (Array.isArray(thinkingBlocksAccum) && thinkingBlocksAccum.length > 0) {
|
|
3007
3008
|
assistantMsg.thinkingBlocks = thinkingBlocksAccum.map(tb => (
|
|
3008
3009
|
tb.redacted
|
|
@@ -3013,13 +3014,15 @@ function appendTurnToGroupHistory(sessionId, threadId, prompts, assistantTextPar
|
|
|
3013
3014
|
history.push(assistantMsg);
|
|
3014
3015
|
|
|
3015
3016
|
for (const tr of toolResultsAccum) {
|
|
3016
|
-
|
|
3017
|
+
const toolMsg = {
|
|
3017
3018
|
role: 'tool',
|
|
3018
3019
|
toolCallId: tr.toolCallId,
|
|
3019
3020
|
content: tr.content,
|
|
3020
3021
|
isError: tr.isError,
|
|
3021
3022
|
threadId: threadId || 'main',
|
|
3022
|
-
}
|
|
3023
|
+
};
|
|
3024
|
+
if (vpId) toolMsg.speakerVpId = vpId;
|
|
3025
|
+
history.push(toolMsg);
|
|
3023
3026
|
}
|
|
3024
3027
|
}
|
|
3025
3028
|
}
|
|
@@ -3303,6 +3306,16 @@ export function __testGetRegisteredThreadIds() {
|
|
|
3303
3306
|
*/
|
|
3304
3307
|
export const __testRaceWithEscalation = raceWithEscalation;
|
|
3305
3308
|
|
|
3309
|
+
/**
|
|
3310
|
+
* Test-only: invoke `appendTurnToSessionHistory` directly. Lets the VP
|
|
3311
|
+
* stamp contract (`speakerVpId` on assistant + tool rows, none on user
|
|
3312
|
+
* rows) be pinned with a table-driven test instead of booting a full
|
|
3313
|
+
* session. See `test/agent/yeaft/web-bridge-append-turn-vp-stamp.test.js`.
|
|
3314
|
+
*/
|
|
3315
|
+
export function __testAppendTurnToSessionHistory(...args) {
|
|
3316
|
+
return appendTurnToSessionHistory(...args);
|
|
3317
|
+
}
|
|
3318
|
+
|
|
3306
3319
|
/**
|
|
3307
3320
|
* Manual dream trigger.
|
|
3308
3321
|
*
|
|
@@ -3628,11 +3641,11 @@ export function handleYeaftModelSwitch(msg) {
|
|
|
3628
3641
|
export async function handleYeaftLoadHistory(msg) {
|
|
3629
3642
|
const sessionId = (msg && typeof msg.sessionId === 'string' && msg.sessionId) || null;
|
|
3630
3643
|
// `lim` is now expressed in TURNS, not raw messages. `loadRecent` and
|
|
3631
|
-
// `
|
|
3644
|
+
// `loadRecentBySession` use turn-based slicing so the cut never lands
|
|
3632
3645
|
// mid-tool-arc. Pass `undefined` to use the persistence-layer default
|
|
3633
3646
|
// (DEFAULT_RECENT_TURNS = 20 turns).
|
|
3634
3647
|
const pickRecent = (store, lim) =>
|
|
3635
|
-
sessionId ? store.
|
|
3648
|
+
sessionId ? store.loadRecentBySession(sessionId, lim) : store.loadRecent(lim);
|
|
3636
3649
|
|
|
3637
3650
|
if (!session) {
|
|
3638
3651
|
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
@@ -3646,7 +3659,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3646
3659
|
|
|
3647
3660
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
3648
3661
|
|
|
3649
|
-
// Per-group history hydrates lazily via
|
|
3662
|
+
// Per-group history hydrates lazily via getOrCreateSessionHistory.
|
|
3650
3663
|
// When the load-history call carries a sessionId, force-refresh THAT
|
|
3651
3664
|
// group's tape so the next user message sees on-disk state. When
|
|
3652
3665
|
// it doesn't (legacy callers), do nothing — the per-group lazy
|
|
@@ -3670,7 +3683,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3670
3683
|
tools: session.status.tools,
|
|
3671
3684
|
yeaftDir: ctx.CONFIG?.yeaftDir || null,
|
|
3672
3685
|
});
|
|
3673
|
-
|
|
3686
|
+
sendSessionSnapshotBroadcast();
|
|
3674
3687
|
// vp-status: replay the authoritative table on reconnect so a refreshed
|
|
3675
3688
|
// frontend doesn't have to wait for the next transition to learn each
|
|
3676
3689
|
// VP's current state.
|
|
@@ -3795,8 +3808,8 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3795
3808
|
// replay, only scoped per-(group, vp) summaries count; legacy compact.md is
|
|
3796
3809
|
// reserved for non-group / pre-scoped 1:1 callers.
|
|
3797
3810
|
let hasCompactSummaryFlag = !!compactSummary;
|
|
3798
|
-
if (sessionId && typeof session.conversationStore.
|
|
3799
|
-
hasCompactSummaryFlag = session.conversationStore.
|
|
3811
|
+
if (sessionId && typeof session.conversationStore.hasAnyCompactSummaryForSession === 'function') {
|
|
3812
|
+
hasCompactSummaryFlag = session.conversationStore.hasAnyCompactSummaryForSession(sessionId);
|
|
3800
3813
|
}
|
|
3801
3814
|
|
|
3802
3815
|
// Latest seq cursor in the recent-mode reply lets the frontend stamp its
|
|
@@ -3856,7 +3869,7 @@ export async function handleYeaftLoadMoreHistory(msg) {
|
|
|
3856
3869
|
try {
|
|
3857
3870
|
result = loadVisibleGroupHistoryPage(session.conversationStore, sessionId, turns, beforeSeq);
|
|
3858
3871
|
} catch (err) {
|
|
3859
|
-
console.error('[Yeaft]
|
|
3872
|
+
console.error('[Yeaft] loadOlderBySession failed:', err.message);
|
|
3860
3873
|
result = { messages: [], oldestSeq: null, hasMore: false };
|
|
3861
3874
|
}
|
|
3862
3875
|
|
|
@@ -3902,7 +3915,7 @@ export async function resetYeaftSession() {
|
|
|
3902
3915
|
session = null;
|
|
3903
3916
|
}
|
|
3904
3917
|
yeaftConversationId = null;
|
|
3905
|
-
// Per-group histories live on
|
|
3918
|
+
// Per-group histories live on sessionContexts entries — clearing the
|
|
3906
3919
|
// map (a few lines below) drops every group's history with it. No
|
|
3907
3920
|
// separate global tape to clear.
|
|
3908
3921
|
// Re-arm the permission warning. The user might have fixed the
|
|
@@ -3922,7 +3935,7 @@ export async function resetYeaftSession() {
|
|
|
3922
3935
|
vpInboxes.clear();
|
|
3923
3936
|
vpDrivers.clear();
|
|
3924
3937
|
vpEngines.clear();
|
|
3925
|
-
|
|
3938
|
+
sessionContexts.clear();
|
|
3926
3939
|
vpCurrentTodos.clear();
|
|
3927
3940
|
threadClassifier = defaultClassifyThread;
|
|
3928
3941
|
// History-dedup cache is keyed by per-session coordinator msg ids;
|
|
@@ -3951,7 +3964,7 @@ export async function resetYeaftSession() {
|
|
|
3951
3964
|
|
|
3952
3965
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
3953
3966
|
|
|
3954
|
-
// Per-group history hydrates lazily via
|
|
3967
|
+
// Per-group history hydrates lazily via getOrCreateSessionHistory on
|
|
3955
3968
|
// first read. Nothing to seed here.
|
|
3956
3969
|
|
|
3957
3970
|
sendYeaftEvent({
|