@yeaft/webchat-agent 0.1.935 → 0.1.937
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 +22 -1
- package/package.json +1 -1
- package/yeaft/config-api.js +192 -0
- package/yeaft/engine.js +15 -8
- package/yeaft/init.js +208 -4
- package/yeaft/session.js +26 -2
- package/yeaft/skills.js +213 -30
- package/yeaft/tools/index.js +8 -2
- package/yeaft/tools/mcp-tools.js +141 -23
- package/yeaft/tools/registry.js +103 -7
- package/yeaft/web-bridge.js +243 -0
package/yeaft/tools/registry.js
CHANGED
|
@@ -11,6 +11,27 @@
|
|
|
11
11
|
|
|
12
12
|
import { formatSize } from '../archive/tool-results.js';
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Collaboration tools are mutually exclusive per Yeaft group shape:
|
|
16
|
+
* single-VP groups use sub-agents; multi-VP groups use VP-to-VP forwarding.
|
|
17
|
+
* Keep the policy names close to the tool registry so LLM exposure and
|
|
18
|
+
* execution gating use the same source of truth.
|
|
19
|
+
*/
|
|
20
|
+
export const COLLAB_TOOL_POLICY = Object.freeze({
|
|
21
|
+
SINGLE_VP: 'single-vp',
|
|
22
|
+
MULTI_VP: 'multi-vp',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const SUB_AGENT_TOOL_NAMES = Object.freeze([
|
|
26
|
+
'SpawnAgent',
|
|
27
|
+
'PromptAgent',
|
|
28
|
+
'WaitAgent',
|
|
29
|
+
'CloseAgent',
|
|
30
|
+
'ListAgents',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
export const FORWARD_TOOL_NAMES = Object.freeze(['RouteForward']);
|
|
34
|
+
|
|
14
35
|
/**
|
|
15
36
|
* Per-tool-result hard cap.
|
|
16
37
|
*
|
|
@@ -208,6 +229,20 @@ function runWithTimeout(promise, timeoutMs, toolName) {
|
|
|
208
229
|
});
|
|
209
230
|
}
|
|
210
231
|
|
|
232
|
+
export function normalizeCollabToolPolicy(policy) {
|
|
233
|
+
if (policy === COLLAB_TOOL_POLICY.SINGLE_VP || policy === COLLAB_TOOL_POLICY.MULTI_VP) {
|
|
234
|
+
return policy;
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function isToolHiddenByCollabPolicy(toolName, policy) {
|
|
240
|
+
const normalized = normalizeCollabToolPolicy(policy);
|
|
241
|
+
if (!normalized) return false;
|
|
242
|
+
if (normalized === COLLAB_TOOL_POLICY.SINGLE_VP) return FORWARD_TOOL_NAMES.includes(toolName);
|
|
243
|
+
return SUB_AGENT_TOOL_NAMES.includes(toolName);
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
export class ToolRegistry {
|
|
212
247
|
/** @type {Map<string, import('./types.js').ToolDef>} */
|
|
213
248
|
#tools = new Map();
|
|
@@ -293,17 +328,36 @@ export class ToolRegistry {
|
|
|
293
328
|
|
|
294
329
|
/**
|
|
295
330
|
* Get tool definitions for the LLM adapter.
|
|
296
|
-
* Returns all registered tools
|
|
331
|
+
* Returns all registered tools unless a collaboration policy hides one of
|
|
332
|
+
* the mutually-exclusive orchestration tool families.
|
|
297
333
|
* @param {string} [language='en']
|
|
334
|
+
* @param {{ collabToolPolicy?: string }} [opts]
|
|
298
335
|
* @returns {{ name: string, description: string, parameters: object }[]}
|
|
299
336
|
*/
|
|
300
|
-
getToolDefs(language = 'en') {
|
|
337
|
+
getToolDefs(language = 'en', opts = {}) {
|
|
301
338
|
const lang = normalizeLanguage(language);
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
339
|
+
const collabToolPolicy = normalizeCollabToolPolicy(opts?.collabToolPolicy);
|
|
340
|
+
return this.getAllTools()
|
|
341
|
+
.filter(t => !isToolHiddenByCollabPolicy(t.name, collabToolPolicy))
|
|
342
|
+
.map(t => ({
|
|
343
|
+
name: t.name,
|
|
344
|
+
description: localizeVisibleText(t.description, lang, t.name),
|
|
345
|
+
parameters: localizeParameters(t.parameters, lang, t.name),
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Check whether a tool may be called under the current collaboration policy.
|
|
351
|
+
* Unknown / absent policy keeps the historical behavior: all registered
|
|
352
|
+
* tools remain callable.
|
|
353
|
+
* @param {string} name
|
|
354
|
+
* @param {{ collabToolPolicy?: string }} [opts]
|
|
355
|
+
* @returns {boolean}
|
|
356
|
+
*/
|
|
357
|
+
isAllowed(name, opts = {}) {
|
|
358
|
+
const tool = this.#tools.get(name);
|
|
359
|
+
if (!tool) return false;
|
|
360
|
+
return !isToolHiddenByCollabPolicy(tool.name, opts?.collabToolPolicy);
|
|
307
361
|
}
|
|
308
362
|
|
|
309
363
|
/**
|
|
@@ -359,6 +413,48 @@ export class ToolRegistry {
|
|
|
359
413
|
get names() {
|
|
360
414
|
return this.getAllTools().map(t => t.name);
|
|
361
415
|
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Hot-swap the flattened MCP tool set.
|
|
419
|
+
*
|
|
420
|
+
* Removes every currently registered tool whose canonical name starts
|
|
421
|
+
* with `mcp__` (the flattened MCP tool naming convention used by
|
|
422
|
+
* `buildMcpFlattenedTools()`), then re-registers a fresh set built from
|
|
423
|
+
* the live MCPManager. Called by the MCP web-bridge after a successful
|
|
424
|
+
* connect/disconnect/reload so the running engine's next turn sees the
|
|
425
|
+
* new tool catalogue without needing a full session restart.
|
|
426
|
+
*
|
|
427
|
+
* Why "starts with `mcp__`": that prefix is the canonical Claude Code
|
|
428
|
+
* naming for flattened MCP tools. It cleanly distinguishes them from
|
|
429
|
+
* built-in tools (Bash, FileRead, etc.) and from the legacy meta-tools
|
|
430
|
+
* (`mcp_list_tools`, `mcp_call_tool` — single underscore, NOT removed
|
|
431
|
+
* here) which a caller may still opt into.
|
|
432
|
+
*
|
|
433
|
+
* @param {import('../mcp.js').MCPManager} mcpManager
|
|
434
|
+
* @param {(mgr: import('../mcp.js').MCPManager) => import('./types.js').ToolDef[]} buildFlattened
|
|
435
|
+
* — the builder from `./mcp-tools.js`. Injected so this registry file
|
|
436
|
+
* stays free of circular `import` to mcp-tools.js (mcp-tools imports
|
|
437
|
+
* `defineTool` from types.js, which lives alongside this file).
|
|
438
|
+
* @returns {{ removed: number, added: number }}
|
|
439
|
+
*/
|
|
440
|
+
replaceMcpTools(mcpManager, buildFlattened) {
|
|
441
|
+
let removed = 0;
|
|
442
|
+
for (const name of [...this.#tools.keys()]) {
|
|
443
|
+
if (name.startsWith('mcp__')) {
|
|
444
|
+
this.#tools.delete(name);
|
|
445
|
+
removed += 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
let added = 0;
|
|
449
|
+
if (typeof buildFlattened === 'function' && mcpManager) {
|
|
450
|
+
const fresh = buildFlattened(mcpManager) || [];
|
|
451
|
+
for (const tool of fresh) {
|
|
452
|
+
this.register(tool);
|
|
453
|
+
added += 1;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
return { removed, added };
|
|
457
|
+
}
|
|
362
458
|
}
|
|
363
459
|
|
|
364
460
|
/**
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import { join } from 'node:path';
|
|
22
|
+
import { COLLAB_TOOL_POLICY } from './tools/registry.js';
|
|
22
23
|
import { existsSync } from 'node:fs';
|
|
23
24
|
import { randomUUID } from 'node:crypto';
|
|
24
25
|
import { buildDreamOutputSnapshot } from './dream/output-snapshot.js';
|
|
@@ -65,6 +66,8 @@ import { pairSanitize } from './pair-sanitize.js';
|
|
|
65
66
|
import { filterSnapshotForVp } from './snapshot-filter.js';
|
|
66
67
|
import { createVpStatusBroker } from './vp-status-broker.js';
|
|
67
68
|
import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
|
|
69
|
+
import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
|
|
70
|
+
import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
68
71
|
|
|
69
72
|
/** @type {import('./session.js').Session | null} */
|
|
70
73
|
let session = null;
|
|
@@ -2614,6 +2617,14 @@ async function waitForVpDrivers(_groupId, driverKeys = []) {
|
|
|
2614
2617
|
* `route_forward` can extend `causedBy` chains correctly. Optional only
|
|
2615
2618
|
* for pre-707 callers that no longer exist in production.
|
|
2616
2619
|
*/
|
|
2620
|
+
function resolveCollabToolPolicy(sessionMeta) {
|
|
2621
|
+
if (!sessionMeta || typeof sessionMeta !== 'object' || !Array.isArray(sessionMeta.roster)) {
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2624
|
+
const vpCount = new Set(sessionMeta.roster.filter(v => typeof v === 'string' && v.trim())).size;
|
|
2625
|
+
return vpCount > 1 ? COLLAB_TOOL_POLICY.MULTI_VP : COLLAB_TOOL_POLICY.SINGLE_VP;
|
|
2626
|
+
}
|
|
2627
|
+
|
|
2617
2628
|
export function buildVpQueryOpts({ vpId, sessionCoordinator, sessionId, envelope, threadId = 'main' }) {
|
|
2618
2629
|
// Read the group meta once and reuse for both defaultVpId fallback and
|
|
2619
2630
|
// announcement injection. Each .getMeta() reload reads + parses the
|
|
@@ -2654,6 +2665,8 @@ export function buildVpQueryOpts({ vpId, sessionCoordinator, sessionId, envelope
|
|
|
2654
2665
|
if (typeof sessionId === 'string' && sessionId.trim()) {
|
|
2655
2666
|
out.sessionId = sessionId.trim();
|
|
2656
2667
|
}
|
|
2668
|
+
const collabToolPolicy = resolveCollabToolPolicy(sessionMeta);
|
|
2669
|
+
if (collabToolPolicy) out.collabToolPolicy = collabToolPolicy;
|
|
2657
2670
|
// task-334-group-editor: surface the group announcement to the engine so
|
|
2658
2671
|
// buildWorkerPrompt can inject it as a CLAUDE.md-style shared prefix.
|
|
2659
2672
|
// Empty/missing reads as '' and prompts.js skips the section.
|
|
@@ -4186,3 +4199,233 @@ export async function resetYeaftSession() {
|
|
|
4186
4199
|
console.error('[Yeaft] Failed to re-initialize session after reset:', err.message);
|
|
4187
4200
|
}
|
|
4188
4201
|
}
|
|
4202
|
+
|
|
4203
|
+
// ────────────────────────────────────────────────────────────
|
|
4204
|
+
// MCP CRUD wire handlers (Claude-Code-style Settings → MCP tab)
|
|
4205
|
+
//
|
|
4206
|
+
// Wire types: `yeaft_mcp_list` / `yeaft_mcp_add` / `yeaft_mcp_remove` /
|
|
4207
|
+
// `yeaft_mcp_reload`. Each:
|
|
4208
|
+
// 1. Reads / writes ~/.yeaft/config.json `mcpServers` via config-api.
|
|
4209
|
+
// 2. Calls `session.mcpManager.connect|disconnect` to apply at runtime.
|
|
4210
|
+
// 3. Hot-swaps the live `toolRegistry` via `replaceMcpTools(...)` so the
|
|
4211
|
+
// next LLM turn sees the new tool catalogue WITHOUT a session restart.
|
|
4212
|
+
// 4. Broadcasts `yeaft_mcp_updated` so any subscribed web client (the
|
|
4213
|
+
// Settings panel + any open Yeaft view) refreshes its badge without
|
|
4214
|
+
// a manual reload.
|
|
4215
|
+
//
|
|
4216
|
+
// The handlers do NOT block on `ensureSessionLoaded()` — the session may
|
|
4217
|
+
// not yet be initialised when the user opens Settings before sending the
|
|
4218
|
+
// first message. In that case `session` is null and we operate ONLY on
|
|
4219
|
+
// the on-disk config; the live runtime takes effect on the next session
|
|
4220
|
+
// boot. When `session` IS available, we apply the runtime change too.
|
|
4221
|
+
//
|
|
4222
|
+
// Wire shape per response: always `{ type: 'yeaft_mcp_*', servers, runtime?, error? }`.
|
|
4223
|
+
// Frontend reducer should treat `error` as a non-empty string failure.
|
|
4224
|
+
// ────────────────────────────────────────────────────────────
|
|
4225
|
+
|
|
4226
|
+
/**
|
|
4227
|
+
* Snapshot the live MCP runtime so the UI can render per-server
|
|
4228
|
+
* connection state next to the configured servers. Safe to call when
|
|
4229
|
+
* the session hasn't been initialised yet — returns an empty runtime.
|
|
4230
|
+
*/
|
|
4231
|
+
function mcpRuntimeSnapshot() {
|
|
4232
|
+
if (!session?.mcpManager) {
|
|
4233
|
+
return { connected: false, toolCount: 0, perServer: [] };
|
|
4234
|
+
}
|
|
4235
|
+
const status = session.mcpManager.status() || [];
|
|
4236
|
+
const toolCount = typeof session.mcpManager.toolCount === 'number'
|
|
4237
|
+
? session.mcpManager.toolCount
|
|
4238
|
+
: status.reduce((sum, s) => sum + (s.toolCount || 0), 0);
|
|
4239
|
+
return {
|
|
4240
|
+
connected: !!session.mcpManager.hasServers,
|
|
4241
|
+
toolCount,
|
|
4242
|
+
perServer: status.map(s => ({
|
|
4243
|
+
name: s.name,
|
|
4244
|
+
ready: !!s.ready,
|
|
4245
|
+
toolCount: s.toolCount || 0,
|
|
4246
|
+
})),
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
4249
|
+
|
|
4250
|
+
/**
|
|
4251
|
+
* Re-flatten MCP tools into the live ToolRegistry. No-op when the session
|
|
4252
|
+
* (or its registry) hasn't been created yet — the next session boot will
|
|
4253
|
+
* pick up the change.
|
|
4254
|
+
*/
|
|
4255
|
+
function hotSwapMcpTools() {
|
|
4256
|
+
if (!session?.toolRegistry || typeof session.toolRegistry.replaceMcpTools !== 'function') {
|
|
4257
|
+
return { removed: 0, added: 0, skipped: true };
|
|
4258
|
+
}
|
|
4259
|
+
try {
|
|
4260
|
+
const result = session.toolRegistry.replaceMcpTools(session.mcpManager, buildMcpFlattenedTools);
|
|
4261
|
+
return { ...result, skipped: false };
|
|
4262
|
+
} catch (err) {
|
|
4263
|
+
console.warn('[Yeaft] hot-swap MCP tools failed:', err?.message || err);
|
|
4264
|
+
return { removed: 0, added: 0, skipped: true, error: err?.message || String(err) };
|
|
4265
|
+
}
|
|
4266
|
+
}
|
|
4267
|
+
|
|
4268
|
+
/**
|
|
4269
|
+
* Broadcast a `yeaft_mcp_updated` event so any client subscribed to the
|
|
4270
|
+
* Yeaft view (Settings panel, status badge) refreshes without needing
|
|
4271
|
+
* to re-open the panel. The current list+runtime are included so the UI
|
|
4272
|
+
* is single-source (no separate fetch round-trip needed).
|
|
4273
|
+
*/
|
|
4274
|
+
function broadcastMcpUpdated(extra = {}) {
|
|
4275
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4276
|
+
const listed = listMcpServers(yeaftDir);
|
|
4277
|
+
sendToServer({
|
|
4278
|
+
type: 'yeaft_mcp_updated',
|
|
4279
|
+
servers: listed.servers || [],
|
|
4280
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4281
|
+
...extra,
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
|
|
4285
|
+
export function handleYeaftMcpList(msg = {}) {
|
|
4286
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4287
|
+
const listed = listMcpServers(yeaftDir);
|
|
4288
|
+
sendToServer({
|
|
4289
|
+
type: 'yeaft_mcp_list_result',
|
|
4290
|
+
requestId: msg.requestId || null,
|
|
4291
|
+
servers: listed.servers || [],
|
|
4292
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4293
|
+
error: listed.error || null,
|
|
4294
|
+
});
|
|
4295
|
+
}
|
|
4296
|
+
|
|
4297
|
+
export async function handleYeaftMcpAdd(msg = {}) {
|
|
4298
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4299
|
+
const result = upsertMcpServer(msg.server || {}, yeaftDir);
|
|
4300
|
+
if (result.error) {
|
|
4301
|
+
sendToServer({
|
|
4302
|
+
type: 'yeaft_mcp_add_result',
|
|
4303
|
+
requestId: msg.requestId || null,
|
|
4304
|
+
servers: [],
|
|
4305
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4306
|
+
error: result.error,
|
|
4307
|
+
});
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
|
|
4311
|
+
// Apply at runtime when the session is live. The MCPManager's
|
|
4312
|
+
// `connect(serverConfig)` already disconnects-and-reconnects if a
|
|
4313
|
+
// server with the same name was already registered.
|
|
4314
|
+
let connectError = null;
|
|
4315
|
+
if (session?.mcpManager) {
|
|
4316
|
+
try {
|
|
4317
|
+
await session.mcpManager.connect(result.server);
|
|
4318
|
+
} catch (err) {
|
|
4319
|
+
connectError = err?.message || String(err);
|
|
4320
|
+
console.warn(`[Yeaft] MCP connect "${result.server.name}" failed:`, connectError);
|
|
4321
|
+
}
|
|
4322
|
+
}
|
|
4323
|
+
|
|
4324
|
+
const swap = hotSwapMcpTools();
|
|
4325
|
+
|
|
4326
|
+
sendToServer({
|
|
4327
|
+
type: 'yeaft_mcp_add_result',
|
|
4328
|
+
requestId: msg.requestId || null,
|
|
4329
|
+
servers: result.servers,
|
|
4330
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4331
|
+
swap,
|
|
4332
|
+
connectError,
|
|
4333
|
+
error: null,
|
|
4334
|
+
});
|
|
4335
|
+
broadcastMcpUpdated({ reason: 'add', name: result.server.name, connectError });
|
|
4336
|
+
}
|
|
4337
|
+
|
|
4338
|
+
export async function handleYeaftMcpRemove(msg = {}) {
|
|
4339
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4340
|
+
const name = typeof msg.name === 'string' ? msg.name : '';
|
|
4341
|
+
const result = removeMcpServer(name, yeaftDir);
|
|
4342
|
+
if (result.error) {
|
|
4343
|
+
sendToServer({
|
|
4344
|
+
type: 'yeaft_mcp_remove_result',
|
|
4345
|
+
requestId: msg.requestId || null,
|
|
4346
|
+
servers: [],
|
|
4347
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4348
|
+
error: result.error,
|
|
4349
|
+
});
|
|
4350
|
+
return;
|
|
4351
|
+
}
|
|
4352
|
+
|
|
4353
|
+
if (session?.mcpManager) {
|
|
4354
|
+
try {
|
|
4355
|
+
await session.mcpManager.disconnect(name);
|
|
4356
|
+
} catch (err) {
|
|
4357
|
+
console.warn(`[Yeaft] MCP disconnect "${name}" failed:`, err?.message || err);
|
|
4358
|
+
}
|
|
4359
|
+
}
|
|
4360
|
+
|
|
4361
|
+
const swap = hotSwapMcpTools();
|
|
4362
|
+
|
|
4363
|
+
sendToServer({
|
|
4364
|
+
type: 'yeaft_mcp_remove_result',
|
|
4365
|
+
requestId: msg.requestId || null,
|
|
4366
|
+
servers: result.servers,
|
|
4367
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4368
|
+
removed: !!result.removed,
|
|
4369
|
+
swap,
|
|
4370
|
+
error: null,
|
|
4371
|
+
});
|
|
4372
|
+
broadcastMcpUpdated({ reason: 'remove', name });
|
|
4373
|
+
}
|
|
4374
|
+
|
|
4375
|
+
export async function handleYeaftMcpReload(msg = {}) {
|
|
4376
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4377
|
+
const targetName = typeof msg.name === 'string' && msg.name ? msg.name : null;
|
|
4378
|
+
|
|
4379
|
+
if (!session?.mcpManager) {
|
|
4380
|
+
// Session not yet alive — just echo the current config + an empty
|
|
4381
|
+
// runtime so the UI knows to wait for session boot.
|
|
4382
|
+
const listed = listMcpServers(yeaftDir);
|
|
4383
|
+
sendToServer({
|
|
4384
|
+
type: 'yeaft_mcp_reload_result',
|
|
4385
|
+
requestId: msg.requestId || null,
|
|
4386
|
+
servers: listed.servers || [],
|
|
4387
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4388
|
+
error: null,
|
|
4389
|
+
});
|
|
4390
|
+
return;
|
|
4391
|
+
}
|
|
4392
|
+
|
|
4393
|
+
const listed = listMcpServers(yeaftDir);
|
|
4394
|
+
const configured = listed.servers || [];
|
|
4395
|
+
|
|
4396
|
+
// Per-server reload: disconnect + reconnect the named server only.
|
|
4397
|
+
// Whole-set reload: disconnect everything, then reconnect from current
|
|
4398
|
+
// config.json. The latter is what the user clicks "Reload all" for.
|
|
4399
|
+
const failures = [];
|
|
4400
|
+
try {
|
|
4401
|
+
if (targetName) {
|
|
4402
|
+
const cfg = configured.find(s => s.name === targetName);
|
|
4403
|
+
try { await session.mcpManager.disconnect(targetName); } catch { /* ignore */ }
|
|
4404
|
+
if (cfg) {
|
|
4405
|
+
try { await session.mcpManager.connect(cfg); }
|
|
4406
|
+
catch (err) { failures.push({ name: targetName, error: err?.message || String(err) }); }
|
|
4407
|
+
}
|
|
4408
|
+
} else {
|
|
4409
|
+
try { await session.mcpManager.disconnectAll(); } catch { /* ignore */ }
|
|
4410
|
+
for (const cfg of configured) {
|
|
4411
|
+
try { await session.mcpManager.connect(cfg); }
|
|
4412
|
+
catch (err) { failures.push({ name: cfg.name, error: err?.message || String(err) }); }
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4415
|
+
} catch (err) {
|
|
4416
|
+
console.warn('[Yeaft] MCP reload failed:', err?.message || err);
|
|
4417
|
+
}
|
|
4418
|
+
|
|
4419
|
+
const swap = hotSwapMcpTools();
|
|
4420
|
+
|
|
4421
|
+
sendToServer({
|
|
4422
|
+
type: 'yeaft_mcp_reload_result',
|
|
4423
|
+
requestId: msg.requestId || null,
|
|
4424
|
+
servers: configured,
|
|
4425
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4426
|
+
failures,
|
|
4427
|
+
swap,
|
|
4428
|
+
error: null,
|
|
4429
|
+
});
|
|
4430
|
+
broadcastMcpUpdated({ reason: 'reload', name: targetName, failures });
|
|
4431
|
+
}
|