@yeaft/webchat-agent 1.0.409 → 1.0.411

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.
@@ -26,11 +26,11 @@ import { sendToServer, flushMessageBuffer } from './buffer.js';
26
26
  import { sendAgentMetricsSnapshot } from '../metrics.js';
27
27
  import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
28
28
  import { loadMcpServers, updateMcpConfig } from '../mcp.js';
29
- import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getTelemetrySettings, updateTelemetrySettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
29
+ import { getLlmConfig, updateLlmConfig, getYeaftSettings, updateYeaftSettings, getPluginConfig, updatePluginConfig, getTelemetrySettings, updateTelemetrySettings, getSearchSettings, updateSearchSettings, fetchTavilyUsage } from '../yeaft/config-api.js';
30
30
  import { loadConfig } from '../yeaft/config.js';
31
31
  import { discoverLlmModels } from '../llm-model-discovery.js';
32
32
  import { fetchModelsDev } from '../yeaft/llm/models-dev.js';
33
- import { handleYeaftSessionSend, handleYeaftAskUserAnswer, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, refreshLiveSessionConfig, handleYeaftLoadHistory, handleYeaftLoadHistoryOutline, handleYeaftSearchHistory, handleYeaftLoadHistoryWindow, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftProjectContextSync, handleYeaftProjectMutation, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, broadcastYeaftVpSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
33
+ import { handleYeaftSessionSend, handleYeaftAskUserAnswer, handleYeaftSubAgentPrompt, handleYeaftTaskCancel, handleYeaftModeSwitch, handleYeaftModelSwitch, resetYeaftSession, refreshLiveSessionConfig, handleYeaftLoadHistory, handleYeaftLoadHistoryOutline, handleYeaftSearchHistory, handleYeaftLoadHistoryWindow, handleYeaftLoadMoreHistory, handleYeaftAbortThread, handleYeaftAbortAll, handleYeaftAbortTurn, handleYeaftVpSubscribe, handleYeaftVpCreate, handleYeaftVpUpdate, handleYeaftVpDelete, handleYeaftVpRead, handleYeaftListSessions, handleYeaftProjectContextSync, handleYeaftProjectMutation, handleYeaftCreateSession, handleYeaftRenameSession, handleYeaftUpdateSession, handleYeaftUpdateSessionConfig, handleYeaftArchiveSession, handleYeaftDeleteSession, handleYeaftSessionAddMember, handleYeaftSessionRemoveMember, handleYeaftSessionSetDefaultVp, handleYeaftScanWorkdirSessions, handleYeaftRestoreSession, handleYeaftDreamTrigger, handleYeaftFetchToolStats, handleYeaftFetchDebugHistory, handleYeaftMcpList, handleYeaftMcpAdd, handleYeaftMcpRemove, handleYeaftMcpReload, handleYeaftPluginCatalog, broadcastLanguageChange, broadcastYeaftSessionSnapshotEager, broadcastYeaftVpSnapshotEager, preloadYeaftSkillSlashCommands } from '../yeaft/web-bridge.js';
34
34
  import { startYeaftStatusRefresh, forceRefreshYeaftStatus } from '../yeaft/status-cache.js';
35
35
  import { handleWorkCenterRequest } from '../yeaft/work-center/bridge.js';
36
36
 
@@ -434,6 +434,24 @@ export async function handleMessage(msg) {
434
434
  break;
435
435
  }
436
436
 
437
+ case 'get_yeaft_plugins': {
438
+ const result = getPluginConfig(ctx.CONFIG?.yeaftDir);
439
+ sendToServer({ type: 'yeaft_plugins', requestId: msg.requestId || null, ...result });
440
+ break;
441
+ }
442
+
443
+ case 'update_yeaft_plugins': {
444
+ const hasPlugins = Object.prototype.hasOwnProperty.call(msg, 'plugins');
445
+ const hasConfig = Object.prototype.hasOwnProperty.call(msg, 'config');
446
+ const payload = hasPlugins ? msg.plugins : (hasConfig ? msg.config : {});
447
+ const result = updatePluginConfig(payload, ctx.CONFIG?.yeaftDir);
448
+ if (!result.error) {
449
+ try { await refreshLiveSessionConfig(); } catch { /* next runtime load still sees disk config */ }
450
+ }
451
+ sendToServer({ type: 'yeaft_plugins_updated', requestId: msg.requestId || null, ...result });
452
+ break;
453
+ }
454
+
437
455
  case 'update_yeaft_settings':
438
456
  case 'update_unify_settings': {
439
457
  const result = updateYeaftSettings(msg.settings || msg.config || {}, ctx.CONFIG?.yeaftDir);
@@ -611,6 +629,10 @@ export async function handleMessage(msg) {
611
629
  // All four reply via `vp_crud_result`; VpLoader's rescan emits the
612
630
  // authoritative `vp_updated` / `vp_removed` events so the store stays
613
631
  // in sync without a bespoke ack path.
632
+ case 'yeaft_plugin_catalog':
633
+ handleYeaftPluginCatalog(msg);
634
+ break;
635
+
614
636
  case 'yeaft_vp_create':
615
637
  case 'unify_vp_create':
616
638
  handleYeaftVpCreate(msg);
package/llm-config-cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
2
2
  import { dirname, join } from 'path';
3
3
  import { homedir } from 'os';
4
+ import { normalizePluginConfig } from './yeaft/plugins.js';
4
5
  import {
5
6
  discoverGitHubCopilotModels,
6
7
  discoverOpenAICompatibleModels,
@@ -21,17 +22,49 @@ export function getDefaultYeaftConfigPath() {
21
22
  export function readLocalLlmConfig(configPath = getDefaultYeaftConfigPath()) {
22
23
  if (!existsSync(configPath)) return {};
23
24
  const raw = readFileSync(configPath, 'utf8');
24
- if (!raw.trim()) return {};
25
+ if (!raw.trim()) {
26
+ throw new Error(`Invalid config file: expected JSON object at ${configPath}`);
27
+ }
25
28
  const parsed = JSON.parse(raw);
26
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
29
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)
30
+ || Object.getPrototypeOf(parsed) !== Object.prototype) {
27
31
  throw new Error(`Invalid config file: expected JSON object at ${configPath}`);
28
32
  }
33
+ if (Object.prototype.hasOwnProperty.call(parsed, 'plugins')) {
34
+ try {
35
+ normalizePluginConfig(parsed.plugins);
36
+ } catch (err) {
37
+ throw new Error(`Invalid config file: ${err?.message || err}`);
38
+ }
39
+ }
29
40
  return parsed;
30
41
  }
31
42
 
32
43
  export function writeLocalLlmConfig(config, configPath = getDefaultYeaftConfigPath()) {
44
+ // The CLI exposes this writer publicly, so do not rely on callers having
45
+ // already used readLocalLlmConfig(). An existing invalid Plugins policy must
46
+ // remain on disk and keep runtime fail-closed until the user repairs it.
47
+ const existing = existsSync(configPath) ? readLocalLlmConfig(configPath) : null;
48
+ if (!config || typeof config !== 'object' || Array.isArray(config)
49
+ || Object.getPrototypeOf(config) !== Object.prototype) {
50
+ throw new Error(`Invalid config file: expected JSON object at ${configPath}`);
51
+ }
52
+ const next = { ...config };
53
+ if (next.plugins === undefined
54
+ && Object.prototype.hasOwnProperty.call(existing || {}, 'plugins')) {
55
+ // LLM commands change provider/model fields. They must not remove a
56
+ // separately-managed Plugin allowlist merely because their payload omits it.
57
+ next.plugins = existing.plugins;
58
+ }
59
+ if (next.plugins !== undefined) {
60
+ try {
61
+ normalizePluginConfig(next.plugins);
62
+ } catch (err) {
63
+ throw new Error(`Invalid config file: ${err?.message || err}`);
64
+ }
65
+ }
33
66
  mkdirSync(dirname(configPath), { recursive: true });
34
- writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
67
+ writeFileSync(configPath, `${JSON.stringify(next, null, 2)}\n`, 'utf8');
35
68
  }
36
69
 
37
70
  export function parseModelsCsv(value) {
@@ -865,6 +865,20 @@ export async function handleAgentOutput(agentId, agent, msg) {
865
865
  }
866
866
  break;
867
867
 
868
+ case 'yeaft_plugin_catalog_result': {
869
+ const targetClient = msg._requestClientId ? webClients.get(msg._requestClientId) : null;
870
+ if (targetClient?.authenticated && (CONFIG.skipAuth || targetClient.userId === agent.ownerId)) {
871
+ await sendToWebClient(targetClient, {
872
+ type: 'yeaft_plugin_catalog_result',
873
+ agentId,
874
+ requestId: msg.requestId || null,
875
+ catalog: msg.catalog || { tools: [], skills: [], mcpServers: [] },
876
+ error: msg.error || null,
877
+ });
878
+ }
879
+ break;
880
+ }
881
+
868
882
  case 'yeaft_tool_stats':
869
883
  // 2026-05-13: relay tool-call counters from the agent to the web
870
884
  // client that requested them. Whitelist `snapshot`/`registered`/
@@ -356,10 +356,14 @@ export async function handleAgentSync(agentId, agent, msg) {
356
356
  break;
357
357
  }
358
358
 
359
+ // Agent-level plugin selection. The Agent owns config.json; the server
360
+ // only stamps ownership and relays request-scoped replies to the UI.
361
+ case 'yeaft_plugins':
362
+ case 'yeaft_plugins_updated':
359
363
  // Local telemetry settings relay. Only bounded config is forwarded;
360
364
  // trace payloads stay on the Agent.
361
365
  case 'telemetry_settings':
362
- case 'telemetry_settings_updated':
366
+ case 'telemetry_settings_updated': {
363
367
  for (const [, client] of webClients) {
364
368
  if (client.authenticated && (CONFIG.skipAuth ||
365
369
  (agent.ownerId && client.userId === agent.ownerId) ||
@@ -369,6 +373,7 @@ export async function handleAgentSync(agentId, agent, msg) {
369
373
  }
370
374
  }
371
375
  break;
376
+ }
372
377
 
373
378
  // Search settings + Tavily usage relays. We pass the whole msg
374
379
  // through (minus agentId, which we set ourselves) — the payload
@@ -1651,6 +1651,10 @@ export async function handleClientConversation(clientId, client, msg, checkAgent
1651
1651
  // router is the authoritative consumer of the payload shape.
1652
1652
  const { agentId: _discard, ...rest } = msg;
1653
1653
  rest.type = relayType;
1654
+ // Direct catalog replies are request-scoped. Carry the browser client
1655
+ // identity through the Agent so another owner tab cannot accidentally
1656
+ // consume a response for this picker request.
1657
+ if (relayType === 'yeaft_plugin_catalog') rest._requestClientId = clientId;
1654
1658
  if (rest.type === 'yeaft_session_send' || rest.type === 'yeaft_session_chat') {
1655
1659
  trackUserTurn(client.userId, Buffer.byteLength(JSON.stringify(msg)));
1656
1660
  const sessionId = typeof rest.sessionId === 'string' ? rest.sessionId.trim() : '';
@@ -183,6 +183,33 @@ export async function handleClientMisc(clientId, client, msg, checkAgentAccess)
183
183
  break;
184
184
  }
185
185
 
186
+ case 'get_yeaft_plugins': {
187
+ const targetAgentId = msg.agentId || client.currentAgent;
188
+ if (!targetAgentId) break;
189
+ if (!await checkAgentAccess(targetAgentId)) break;
190
+ await forwardToAgent(targetAgentId, {
191
+ type: 'get_yeaft_plugins',
192
+ requestId: msg.requestId || null,
193
+ });
194
+ break;
195
+ }
196
+
197
+ case 'update_yeaft_plugins': {
198
+ const targetAgentId = msg.agentId || client.currentAgent;
199
+ if (!targetAgentId) break;
200
+ if (!await checkAgentAccess(targetAgentId)) break;
201
+ const hasPlugins = Object.prototype.hasOwnProperty.call(msg, 'plugins');
202
+ const hasConfig = Object.prototype.hasOwnProperty.call(msg, 'config');
203
+ await forwardToAgent(targetAgentId, {
204
+ type: 'update_yeaft_plugins',
205
+ requestId: msg.requestId || null,
206
+ // Preserve explicit falsy values so Agent-side schema validation can
207
+ // reject them. Only an absent payload is the legacy empty selection.
208
+ plugins: hasPlugins ? msg.plugins : (hasConfig ? msg.config : {}),
209
+ });
210
+ break;
211
+ }
212
+
186
213
  case 'update_yeaft_settings': {
187
214
  const targetAgentId = msg.agentId || client.currentAgent;
188
215
  if (!targetAgentId) break;
@@ -1 +1 @@
1
- {"version":"1.0.409"}
1
+ {"version":"1.0.411"}