@yeaft/webchat-agent 0.1.922 → 0.1.923
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 +5 -0
- package/package.json +1 -1
- package/yeaft/status-cache.js +170 -0
- package/yeaft/web-bridge.js +4 -0
|
@@ -38,6 +38,7 @@ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
|
|
|
38
38
|
import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
|
|
39
39
|
import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
|
|
40
40
|
import { handleYeaftSessionSend, 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, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager } from '../yeaft/web-bridge.js';
|
|
41
|
+
import { startYeaftStatusRefresh, refreshYeaftStatus } from '../yeaft/status-cache.js';
|
|
41
42
|
|
|
42
43
|
export async function handleMessage(msg) {
|
|
43
44
|
switch (msg.type) {
|
|
@@ -64,6 +65,7 @@ export async function handleMessage(msg) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
sendConversationList();
|
|
68
|
+
startYeaftStatusRefresh();
|
|
67
69
|
|
|
68
70
|
// fix-yeaft-session-per-agent: eagerly broadcast this agent's
|
|
69
71
|
// yeaft session snapshot on register so the unified sidebar can
|
|
@@ -358,6 +360,9 @@ export async function handleMessage(msg) {
|
|
|
358
360
|
if (!result.error && incomingLanguage) {
|
|
359
361
|
broadcastLanguageChange(result.language);
|
|
360
362
|
}
|
|
363
|
+
if (!result.error) {
|
|
364
|
+
refreshYeaftStatus({ reason: 'llm_config_updated' }).catch(() => {});
|
|
365
|
+
}
|
|
361
366
|
sendToServer({ type: 'llm_config_updated', ...result });
|
|
362
367
|
break;
|
|
363
368
|
}
|
package/package.json
CHANGED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* status-cache.js — agent-lifecycle Yeaft capability snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Model candidates are an agent capability, not a page lifecycle side-effect.
|
|
5
|
+
* Keep the last good snapshot in memory, refresh it in the background, and
|
|
6
|
+
* never clear the model list just because a refresh failed.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import ctx from '../context.js';
|
|
10
|
+
import { sendToServer } from '../connection/buffer.js';
|
|
11
|
+
import { loadConfig } from './config.js';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
|
|
14
|
+
|
|
15
|
+
function normalizeAvailableModels(models) {
|
|
16
|
+
if (!Array.isArray(models)) return [];
|
|
17
|
+
return models
|
|
18
|
+
.map((m) => {
|
|
19
|
+
if (typeof m === 'string') return { id: m, label: m };
|
|
20
|
+
if (!m || typeof m !== 'object' || typeof m.id !== 'string' || !m.id) return null;
|
|
21
|
+
return { ...m, label: m.label || m.id };
|
|
22
|
+
})
|
|
23
|
+
.filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function buildEvent(snapshot) {
|
|
27
|
+
return {
|
|
28
|
+
type: 'yeaft_status',
|
|
29
|
+
model: snapshot.model || null,
|
|
30
|
+
availableModels: normalizeAvailableModels(snapshot.availableModels),
|
|
31
|
+
skills: snapshot.skills,
|
|
32
|
+
mcpServers: snapshot.mcpServers,
|
|
33
|
+
tools: snapshot.tools,
|
|
34
|
+
yeaftDir: snapshot.yeaftDir || null,
|
|
35
|
+
refreshedAt: snapshot.refreshedAt || null,
|
|
36
|
+
refreshStartedAt: snapshot.refreshStartedAt || null,
|
|
37
|
+
refreshError: snapshot.refreshError || null,
|
|
38
|
+
refreshing: !!snapshot.refreshing,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a status cache. Tests inject clock/timer/config loading; production
|
|
44
|
+
* uses the exported singleton wrappers below.
|
|
45
|
+
*/
|
|
46
|
+
export function createYeaftStatusCache(options = {}) {
|
|
47
|
+
const load = options.loadConfig || loadConfig;
|
|
48
|
+
const emit = options.emit || ((event) => sendToServer({ type: 'yeaft_output', event }));
|
|
49
|
+
const now = options.now || (() => Date.now());
|
|
50
|
+
const setTimer = options.setInterval || globalThis.setInterval.bind(globalThis);
|
|
51
|
+
const clearTimer = options.clearInterval || globalThis.clearInterval.bind(globalThis);
|
|
52
|
+
const intervalMs = options.intervalMs || DEFAULT_REFRESH_INTERVAL_MS;
|
|
53
|
+
let snapshot = null;
|
|
54
|
+
let timer = null;
|
|
55
|
+
let inFlight = null;
|
|
56
|
+
|
|
57
|
+
function current() {
|
|
58
|
+
return snapshot ? { ...snapshot, availableModels: normalizeAvailableModels(snapshot.availableModels) } : null;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function emitSnapshot(extra = {}) {
|
|
62
|
+
if (!snapshot) return null;
|
|
63
|
+
const event = buildEvent({ ...snapshot, ...extra });
|
|
64
|
+
emit(event);
|
|
65
|
+
return event;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function refresh({ reason = 'manual', emitRefreshing = true, sessionStatus = null } = {}) {
|
|
69
|
+
if (inFlight) return inFlight;
|
|
70
|
+
const startedAt = now();
|
|
71
|
+
if (emitRefreshing && snapshot) {
|
|
72
|
+
snapshot = { ...snapshot, refreshing: true, refreshStartedAt: startedAt, refreshReason: reason };
|
|
73
|
+
emitSnapshot();
|
|
74
|
+
}
|
|
75
|
+
inFlight = Promise.resolve()
|
|
76
|
+
.then(async () => {
|
|
77
|
+
const yeaftDir = options.getYeaftDir ? options.getYeaftDir() : ctx.CONFIG?.yeaftDir;
|
|
78
|
+
const config = await load({ ...(yeaftDir && { dir: yeaftDir }) });
|
|
79
|
+
const previous = snapshot || {};
|
|
80
|
+
snapshot = {
|
|
81
|
+
...previous,
|
|
82
|
+
model: config.model || config.primaryModel || previous.model || null,
|
|
83
|
+
availableModels: normalizeAvailableModels(config.availableModels),
|
|
84
|
+
yeaftDir: config.dir || yeaftDir || previous.yeaftDir || null,
|
|
85
|
+
skills: sessionStatus?.skills ?? previous.skills,
|
|
86
|
+
mcpServers: sessionStatus?.mcpServers ?? previous.mcpServers,
|
|
87
|
+
tools: sessionStatus?.tools ?? previous.tools,
|
|
88
|
+
refreshedAt: now(),
|
|
89
|
+
refreshStartedAt: startedAt,
|
|
90
|
+
refreshReason: reason,
|
|
91
|
+
refreshError: null,
|
|
92
|
+
refreshing: false,
|
|
93
|
+
};
|
|
94
|
+
return emitSnapshot();
|
|
95
|
+
})
|
|
96
|
+
.catch((err) => {
|
|
97
|
+
const message = err?.message || String(err);
|
|
98
|
+
const previous = snapshot || {};
|
|
99
|
+
snapshot = {
|
|
100
|
+
...previous,
|
|
101
|
+
availableModels: normalizeAvailableModels(previous.availableModels),
|
|
102
|
+
refreshedAt: previous.refreshedAt || null,
|
|
103
|
+
refreshStartedAt: startedAt,
|
|
104
|
+
refreshReason: reason,
|
|
105
|
+
refreshError: message,
|
|
106
|
+
refreshing: false,
|
|
107
|
+
};
|
|
108
|
+
return emitSnapshot();
|
|
109
|
+
})
|
|
110
|
+
.finally(() => { inFlight = null; });
|
|
111
|
+
return inFlight;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function hydrateFromSession(sessionLike, { reason = 'session_ready', emitEvent = true } = {}) {
|
|
115
|
+
if (!sessionLike) return null;
|
|
116
|
+
const previous = snapshot || {};
|
|
117
|
+
snapshot = {
|
|
118
|
+
...previous,
|
|
119
|
+
model: sessionLike.config?.model || previous.model || null,
|
|
120
|
+
availableModels: normalizeAvailableModels(sessionLike.config?.availableModels || previous.availableModels),
|
|
121
|
+
yeaftDir: sessionLike.yeaftDir || sessionLike.config?.dir || previous.yeaftDir || null,
|
|
122
|
+
skills: sessionLike.status?.skills ?? previous.skills,
|
|
123
|
+
mcpServers: sessionLike.status?.mcpServers ?? previous.mcpServers,
|
|
124
|
+
tools: sessionLike.status?.tools ?? previous.tools,
|
|
125
|
+
refreshedAt: now(),
|
|
126
|
+
refreshStartedAt: previous.refreshStartedAt || null,
|
|
127
|
+
refreshReason: reason,
|
|
128
|
+
refreshError: null,
|
|
129
|
+
refreshing: false,
|
|
130
|
+
};
|
|
131
|
+
return emitEvent ? emitSnapshot() : buildEvent(snapshot);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function start() {
|
|
135
|
+
if (timer) return timer;
|
|
136
|
+
refresh({ reason: 'startup', emitRefreshing: false }).catch(() => {});
|
|
137
|
+
timer = setTimer(() => { refresh({ reason: 'interval' }).catch(() => {}); }, intervalMs);
|
|
138
|
+
if (timer && typeof timer.unref === 'function') timer.unref();
|
|
139
|
+
return timer;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function stop() {
|
|
143
|
+
if (timer) clearTimer(timer);
|
|
144
|
+
timer = null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { current, refresh, hydrateFromSession, start, stop, emitSnapshot };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export const yeaftStatusCache = createYeaftStatusCache();
|
|
151
|
+
|
|
152
|
+
export function startYeaftStatusRefresh() {
|
|
153
|
+
return yeaftStatusCache.start();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function stopYeaftStatusRefresh() {
|
|
157
|
+
return yeaftStatusCache.stop();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function refreshYeaftStatus(options) {
|
|
161
|
+
return yeaftStatusCache.refresh(options);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function hydrateYeaftStatusFromSession(sessionLike, options) {
|
|
165
|
+
return yeaftStatusCache.hydrateFromSession(sessionLike, options);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function getCachedYeaftStatus() {
|
|
169
|
+
return yeaftStatusCache.current();
|
|
170
|
+
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -25,6 +25,7 @@ import { Engine } from './engine.js';
|
|
|
25
25
|
import { loadSession } from './session.js';
|
|
26
26
|
import { sendToServer } from '../connection/buffer.js';
|
|
27
27
|
import ctx from '../context.js';
|
|
28
|
+
import { hydrateYeaftStatusFromSession } from './status-cache.js';
|
|
28
29
|
import { handleVpSubscribe } from './vp/vp-bridge.js';
|
|
29
30
|
import { createVp, updateVp, deleteVp, readVp, VpCrudError } from './vp/vp-crud.js';
|
|
30
31
|
import { scanVpLibrary } from './vp/vp-store.js';
|
|
@@ -2678,6 +2679,7 @@ async function ensureSessionLoaded() {
|
|
|
2678
2679
|
}
|
|
2679
2680
|
|
|
2680
2681
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
2682
|
+
hydrateYeaftStatusFromSession(session, { reason: 'session_ready', emitEvent: true });
|
|
2681
2683
|
|
|
2682
2684
|
// Per-group history is hydrated lazily on first `getOrCreateSessionHistory`
|
|
2683
2685
|
// — there's no global "all conversations" tape any more.
|
|
@@ -3772,6 +3774,7 @@ export async function handleYeaftLoadHistory(msg) {
|
|
|
3772
3774
|
installYeaftRuntimeBridge(session);
|
|
3773
3775
|
|
|
3774
3776
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
3777
|
+
hydrateYeaftStatusFromSession(session, { reason: 'history_load', emitEvent: true });
|
|
3775
3778
|
|
|
3776
3779
|
// Per-group history hydrates lazily via getOrCreateSessionHistory.
|
|
3777
3780
|
// When the load-history call carries a sessionId, force-refresh THAT
|
|
@@ -4077,6 +4080,7 @@ export async function resetYeaftSession() {
|
|
|
4077
4080
|
installYeaftRuntimeBridge(session);
|
|
4078
4081
|
|
|
4079
4082
|
yeaftConversationId = `yeaft-${Date.now()}`;
|
|
4083
|
+
hydrateYeaftStatusFromSession(session, { reason: 'reset', emitEvent: true });
|
|
4080
4084
|
|
|
4081
4085
|
// Per-group history hydrates lazily via getOrCreateSessionHistory on
|
|
4082
4086
|
// first read. Nothing to seed here.
|