@yeaft/webchat-agent 0.1.921 → 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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.921",
3
+ "version": "0.1.923",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -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
+ }
@@ -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.