@yeaft/webchat-agent 1.0.165 → 1.0.166
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 +9 -1
- package/package.json +1 -1
- package/yeaft/conversation/persist.js +126 -0
- package/yeaft/web-bridge.js +77 -0
|
@@ -29,7 +29,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
|
29
29
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
30
30
|
import { discoverLlmModels } from '../llm-model-discovery.js';
|
|
31
31
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
32
|
-
import { handleYeaftSessionSend, handleYeaftAskUserAnswer, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
|
|
32
|
+
import { handleYeaftSessionSend, handleYeaftAskUserAnswer, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, handleYeaftLoadHistory, handleYeaftSearchHistory, handleYeaftLoadHistoryWindow, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
|
|
33
33
|
import { startYeaftStatusRefresh, forceRefreshYeaftStatus } from '../yeaft/status-cache.js';
|
|
34
34
|
import { handleWorkCenterRequest } from '../yeaft/work-center/bridge.js';
|
|
35
35
|
|
|
@@ -479,6 +479,14 @@ export async function handleMessage(msg) {
|
|
|
479
479
|
await handleYeaftLoadHistory(msg);
|
|
480
480
|
break;
|
|
481
481
|
|
|
482
|
+
case 'yeaft_search_history':
|
|
483
|
+
await handleYeaftSearchHistory(msg);
|
|
484
|
+
break;
|
|
485
|
+
|
|
486
|
+
case 'yeaft_load_history_window':
|
|
487
|
+
await handleYeaftLoadHistoryWindow(msg);
|
|
488
|
+
break;
|
|
489
|
+
|
|
482
490
|
case 'yeaft_load_more_history':
|
|
483
491
|
case 'unify_load_more_history':
|
|
484
492
|
await handleYeaftLoadMoreHistory(msg);
|
package/package.json
CHANGED
|
@@ -1395,6 +1395,105 @@ export class ConversationStore {
|
|
|
1395
1395
|
return Number.isFinite(seq) ? seq : null;
|
|
1396
1396
|
}
|
|
1397
1397
|
|
|
1398
|
+
/**
|
|
1399
|
+
* Search user-visible messages inside one Session. The scan is newest-first
|
|
1400
|
+
* and stops as soon as one page plus a `hasMore` sentinel is found, so a
|
|
1401
|
+
* common recent hit does not materialize the full transcript.
|
|
1402
|
+
*
|
|
1403
|
+
* @param {string} sessionId
|
|
1404
|
+
* @param {string} query
|
|
1405
|
+
* @param {{ limit?: number, beforeSeq?: number|null }} [opts]
|
|
1406
|
+
* @returns {{ results: object[], hasMore: boolean, nextBeforeSeq: number|null }}
|
|
1407
|
+
*/
|
|
1408
|
+
searchVisibleBySession(sessionId, query, opts = {}) {
|
|
1409
|
+
const needle = typeof query === 'string' ? query.trim().toLocaleLowerCase() : '';
|
|
1410
|
+
if (!sessionId || needle.length < 2) return { results: [], hasMore: false, nextBeforeSeq: null };
|
|
1411
|
+
|
|
1412
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(opts.limit) ? Math.floor(opts.limit) : 20));
|
|
1413
|
+
const beforeSeq = Number.isFinite(opts.beforeSeq) ? opts.beforeSeq : Infinity;
|
|
1414
|
+
const results = [];
|
|
1415
|
+
const seen = new Set();
|
|
1416
|
+
let hasMore = false;
|
|
1417
|
+
|
|
1418
|
+
for (const message of this.#iterateSessionRows(sessionId, { beforeSeq, desc: true })) {
|
|
1419
|
+
if (!message || message.sessionId !== sessionId || isHiddenConversationRow(message)) continue;
|
|
1420
|
+
if (message.role !== 'user' && message.role !== 'assistant') continue;
|
|
1421
|
+
if (!message.id || seen.has(message.id)) continue;
|
|
1422
|
+
seen.add(message.id);
|
|
1423
|
+
|
|
1424
|
+
const text = this.#visibleSearchText(message.content);
|
|
1425
|
+
const matchIndex = text.toLocaleLowerCase().indexOf(needle);
|
|
1426
|
+
if (matchIndex < 0) continue;
|
|
1427
|
+
if (results.length >= limit) {
|
|
1428
|
+
hasMore = true;
|
|
1429
|
+
break;
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
const seq = parseSeqFromId(message.id);
|
|
1433
|
+
if (!Number.isFinite(seq)) continue;
|
|
1434
|
+
results.push({
|
|
1435
|
+
messageId: message.id,
|
|
1436
|
+
turnId: message.turnId || message.threadId || message.id,
|
|
1437
|
+
seq,
|
|
1438
|
+
role: message.role,
|
|
1439
|
+
speakerVpId: message.speakerVpId || null,
|
|
1440
|
+
timestamp: message.ts || message.time || null,
|
|
1441
|
+
snippet: this.#searchSnippet(text, matchIndex, needle.length),
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
return {
|
|
1446
|
+
results,
|
|
1447
|
+
hasMore,
|
|
1448
|
+
nextBeforeSeq: hasMore && results.length > 0 ? results[results.length - 1].seq : null,
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
/**
|
|
1453
|
+
* Load a bounded visible window around a search hit. This deliberately does
|
|
1454
|
+
* not mutate normal older-history cursors: the web client merges the window
|
|
1455
|
+
* into its cache solely to mount and focus the requested virtual-list item.
|
|
1456
|
+
*
|
|
1457
|
+
* @param {string} sessionId
|
|
1458
|
+
* @param {number} anchorSeq
|
|
1459
|
+
* @param {{ beforeTurns?: number, afterTurns?: number }} [opts]
|
|
1460
|
+
* @returns {{ messages: object[], oldestSeq: number|null, hasMoreBefore: boolean }}
|
|
1461
|
+
*/
|
|
1462
|
+
loadVisibleWindowBySession(sessionId, anchorSeq, opts = {}) {
|
|
1463
|
+
if (!sessionId || !Number.isFinite(anchorSeq)) {
|
|
1464
|
+
return { messages: [], oldestSeq: null, hasMoreBefore: false };
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
const beforeTurns = Math.min(10, Math.max(1, Number.isFinite(opts.beforeTurns) ? Math.floor(opts.beforeTurns) : 3));
|
|
1468
|
+
const afterTurns = Math.min(10, Math.max(1, Number.isFinite(opts.afterTurns) ? Math.floor(opts.afterTurns) : 3));
|
|
1469
|
+
const before = this.loadVisibleBySession(sessionId, anchorSeq + 1, beforeTurns + 1);
|
|
1470
|
+
const messages = before.messages.slice();
|
|
1471
|
+
const seen = new Set(messages.map(message => message?.id).filter(Boolean));
|
|
1472
|
+
let followingUserTurns = 0;
|
|
1473
|
+
|
|
1474
|
+
for (const message of this.#iterateSessionRows(sessionId, { afterSeq: anchorSeq, desc: false })) {
|
|
1475
|
+
if (!message || message.sessionId !== sessionId || isHiddenConversationRow(message)) continue;
|
|
1476
|
+
if (message.role !== 'user' && message.role !== 'assistant') continue;
|
|
1477
|
+
if (message.role === 'user') {
|
|
1478
|
+
followingUserTurns += 1;
|
|
1479
|
+
if (followingUserTurns > afterTurns) break;
|
|
1480
|
+
}
|
|
1481
|
+
if (message.id && seen.has(message.id)) continue;
|
|
1482
|
+
const projected = this.#projectVisibleMessage(message);
|
|
1483
|
+
if (!projected) continue;
|
|
1484
|
+
if (projected.id) seen.add(projected.id);
|
|
1485
|
+
messages.push(projected);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
messages.sort(compareMessagesBySeq);
|
|
1489
|
+
const oldestSeq = messages.length > 0 ? parseSeqFromId(messages[0].id) : null;
|
|
1490
|
+
return {
|
|
1491
|
+
messages,
|
|
1492
|
+
oldestSeq: Number.isFinite(oldestSeq) ? oldestSeq : null,
|
|
1493
|
+
hasMoreBefore: before.hasMore,
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1398
1497
|
/**
|
|
1399
1498
|
* Count hot messages.
|
|
1400
1499
|
*
|
|
@@ -2115,6 +2214,33 @@ export class ConversationStore {
|
|
|
2115
2214
|
};
|
|
2116
2215
|
}
|
|
2117
2216
|
|
|
2217
|
+
#visibleSearchText(content) {
|
|
2218
|
+
if (typeof content === 'string') return content.replace(/\s+/g, ' ').trim();
|
|
2219
|
+
if (!Array.isArray(content)) return '';
|
|
2220
|
+
return content
|
|
2221
|
+
.filter(part => part && typeof part === 'object' && part.type === 'text')
|
|
2222
|
+
.map(part => typeof part.text === 'string' ? part.text : '')
|
|
2223
|
+
.join(' ')
|
|
2224
|
+
.replace(/\s+/g, ' ')
|
|
2225
|
+
.trim();
|
|
2226
|
+
}
|
|
2227
|
+
|
|
2228
|
+
#searchSnippet(text, matchIndex, needleLength) {
|
|
2229
|
+
const radius = 90;
|
|
2230
|
+
const start = Math.max(0, matchIndex - radius);
|
|
2231
|
+
const end = Math.min(text.length, matchIndex + needleLength + radius);
|
|
2232
|
+
return `${start > 0 ? '…' : ''}${text.slice(start, end)}${end < text.length ? '…' : ''}`;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
#projectVisibleMessage(message) {
|
|
2236
|
+
if (!message || (message.role !== 'user' && message.role !== 'assistant')) return null;
|
|
2237
|
+
if (message.role !== 'assistant' || !Array.isArray(message.toolCalls) || message.toolCalls.length === 0) {
|
|
2238
|
+
return message;
|
|
2239
|
+
}
|
|
2240
|
+
const { toolCalls, ...rest } = message;
|
|
2241
|
+
return { ...rest, toolSummaryCount: toolCalls.length };
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2118
2244
|
#readSegmentRows(conversationDir, opts = {}) {
|
|
2119
2245
|
return this.#segmentStoreForConversationDir(conversationDir).readAll(opts);
|
|
2120
2246
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -50,6 +50,7 @@ import {
|
|
|
50
50
|
restoreSessionToRegistry,
|
|
51
51
|
readWorkDirRegistry,
|
|
52
52
|
migrateRegisteredWorkDirSessions,
|
|
53
|
+
resolveSessionYeaftDir,
|
|
53
54
|
} from './sessions/session-crud.js';
|
|
54
55
|
import { openSession, loadSessionMeta } from './sessions/session-store.js';
|
|
55
56
|
import { loadSessionConfig, resolveSessionConfig, SessionConfigError } from './sessions/session-config.js';
|
|
@@ -5872,6 +5873,82 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
5872
5873
|
*
|
|
5873
5874
|
* @param {object} msg — { sessionId, beforeSeq, turns }
|
|
5874
5875
|
*/
|
|
5876
|
+
export async function handleYeaftSearchHistory(msg) {
|
|
5877
|
+
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
5878
|
+
const query = typeof msg?.query === 'string' ? msg.query.trim().slice(0, 500) : '';
|
|
5879
|
+
const requestId = typeof msg?.requestId === 'string' ? msg.requestId : null;
|
|
5880
|
+
const beforeSeq = Number.isFinite(msg?.beforeSeq) ? msg.beforeSeq : null;
|
|
5881
|
+
const limit = Math.min(50, Math.max(1, Number.isFinite(msg?.limit) ? Math.floor(msg.limit) : 20));
|
|
5882
|
+
const response = {
|
|
5883
|
+
type: 'yeaft_history_search_result',
|
|
5884
|
+
requestId,
|
|
5885
|
+
sessionId: sessionId || null,
|
|
5886
|
+
query,
|
|
5887
|
+
results: [],
|
|
5888
|
+
hasMore: false,
|
|
5889
|
+
nextBeforeSeq: null,
|
|
5890
|
+
_requestClientId: msg?._requestClientId || null,
|
|
5891
|
+
};
|
|
5892
|
+
|
|
5893
|
+
if (!sessionId || query.length < 2) {
|
|
5894
|
+
sendToServer(response);
|
|
5895
|
+
return;
|
|
5896
|
+
}
|
|
5897
|
+
|
|
5898
|
+
try {
|
|
5899
|
+
const defaultYeaftDir = ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
5900
|
+
const storeDir = resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
5901
|
+
const store = new ConversationStore(storeDir);
|
|
5902
|
+
const result = store.searchVisibleBySession(sessionId, query, { limit, beforeSeq });
|
|
5903
|
+
sendToServer({ ...response, ...result });
|
|
5904
|
+
} catch (err) {
|
|
5905
|
+
console.error('[Yeaft] Session history search failed:', err?.message || err);
|
|
5906
|
+
sendToServer({ ...response, error: 'search_failed' });
|
|
5907
|
+
}
|
|
5908
|
+
}
|
|
5909
|
+
|
|
5910
|
+
export async function handleYeaftLoadHistoryWindow(msg) {
|
|
5911
|
+
const sessionId = typeof msg?.sessionId === 'string' ? msg.sessionId.trim() : '';
|
|
5912
|
+
const requestId = typeof msg?.requestId === 'string' ? msg.requestId : null;
|
|
5913
|
+
const anchorSeq = Number(msg?.anchorSeq);
|
|
5914
|
+
const anchorMessageId = typeof msg?.anchorMessageId === 'string' ? msg.anchorMessageId : null;
|
|
5915
|
+
const response = {
|
|
5916
|
+
type: 'yeaft_history_window',
|
|
5917
|
+
requestId,
|
|
5918
|
+
conversationId: ensureYeaftConversationId(),
|
|
5919
|
+
sessionId: sessionId || null,
|
|
5920
|
+
anchorMessageId,
|
|
5921
|
+
anchorSeq: Number.isFinite(anchorSeq) ? anchorSeq : null,
|
|
5922
|
+
messages: [],
|
|
5923
|
+
oldestSeq: null,
|
|
5924
|
+
hasMoreBefore: false,
|
|
5925
|
+
_requestClientId: msg?._requestClientId || null,
|
|
5926
|
+
};
|
|
5927
|
+
|
|
5928
|
+
if (!sessionId || !Number.isFinite(anchorSeq)) {
|
|
5929
|
+
sendToServer({ ...response, error: 'invalid_anchor' });
|
|
5930
|
+
return;
|
|
5931
|
+
}
|
|
5932
|
+
|
|
5933
|
+
try {
|
|
5934
|
+
const defaultYeaftDir = ctx.CONFIG?.yeaftDir || DEFAULT_YEAFT_DIR;
|
|
5935
|
+
const storeDir = resolveSessionYeaftDir(defaultYeaftDir, sessionId);
|
|
5936
|
+
const store = new ConversationStore(storeDir);
|
|
5937
|
+
const window = store.loadVisibleWindowBySession(sessionId, anchorSeq, {
|
|
5938
|
+
beforeTurns: msg?.beforeTurns,
|
|
5939
|
+
afterTurns: msg?.afterTurns,
|
|
5940
|
+
});
|
|
5941
|
+
sendToServer({
|
|
5942
|
+
...response,
|
|
5943
|
+
...window,
|
|
5944
|
+
messages: projectVisibleHistoryChunkMessages(window.messages),
|
|
5945
|
+
});
|
|
5946
|
+
} catch (err) {
|
|
5947
|
+
console.error('[Yeaft] Session history anchor load failed:', err?.message || err);
|
|
5948
|
+
sendToServer({ ...response, error: 'window_load_failed' });
|
|
5949
|
+
}
|
|
5950
|
+
}
|
|
5951
|
+
|
|
5875
5952
|
export async function handleYeaftLoadMoreHistory(msg) {
|
|
5876
5953
|
const sessionId = (msg && typeof msg.sessionId === 'string' && msg.sessionId) || null;
|
|
5877
5954
|
const perfTraceId = typeof msg?.perfTraceId === 'string' && msg.perfTraceId.trim() ? msg.perfTraceId.trim() : null;
|