@yeaft/webchat-agent 0.1.934 → 0.1.936
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/conversation/persist.js +12 -1
- package/yeaft/engine.js +1 -0
- package/yeaft/init.js +208 -4
- package/yeaft/session.js +26 -2
- package/yeaft/skills.js +213 -30
- package/yeaft/stop-hooks.js +6 -0
- package/yeaft/tools/index.js +8 -2
- package/yeaft/tools/mcp-tools.js +141 -23
- package/yeaft/tools/registry.js +42 -0
- package/yeaft/web-bridge.js +232 -0
package/yeaft/web-bridge.js
CHANGED
|
@@ -65,6 +65,8 @@ import { pairSanitize } from './pair-sanitize.js';
|
|
|
65
65
|
import { filterSnapshotForVp } from './snapshot-filter.js';
|
|
66
66
|
import { createVpStatusBroker } from './vp-status-broker.js';
|
|
67
67
|
import { classifyThread as defaultClassifyThread, fallbackTitle } from './vp/thread-classifier.js';
|
|
68
|
+
import { listMcpServers, upsertMcpServer, removeMcpServer } from './config-api.js';
|
|
69
|
+
import { buildMcpFlattenedTools } from './tools/mcp-tools.js';
|
|
68
70
|
|
|
69
71
|
/** @type {import('./session.js').Session | null} */
|
|
70
72
|
let session = null;
|
|
@@ -4186,3 +4188,233 @@ export async function resetYeaftSession() {
|
|
|
4186
4188
|
console.error('[Yeaft] Failed to re-initialize session after reset:', err.message);
|
|
4187
4189
|
}
|
|
4188
4190
|
}
|
|
4191
|
+
|
|
4192
|
+
// ────────────────────────────────────────────────────────────
|
|
4193
|
+
// MCP CRUD wire handlers (Claude-Code-style Settings → MCP tab)
|
|
4194
|
+
//
|
|
4195
|
+
// Wire types: `yeaft_mcp_list` / `yeaft_mcp_add` / `yeaft_mcp_remove` /
|
|
4196
|
+
// `yeaft_mcp_reload`. Each:
|
|
4197
|
+
// 1. Reads / writes ~/.yeaft/config.json `mcpServers` via config-api.
|
|
4198
|
+
// 2. Calls `session.mcpManager.connect|disconnect` to apply at runtime.
|
|
4199
|
+
// 3. Hot-swaps the live `toolRegistry` via `replaceMcpTools(...)` so the
|
|
4200
|
+
// next LLM turn sees the new tool catalogue WITHOUT a session restart.
|
|
4201
|
+
// 4. Broadcasts `yeaft_mcp_updated` so any subscribed web client (the
|
|
4202
|
+
// Settings panel + any open Yeaft view) refreshes its badge without
|
|
4203
|
+
// a manual reload.
|
|
4204
|
+
//
|
|
4205
|
+
// The handlers do NOT block on `ensureSessionLoaded()` — the session may
|
|
4206
|
+
// not yet be initialised when the user opens Settings before sending the
|
|
4207
|
+
// first message. In that case `session` is null and we operate ONLY on
|
|
4208
|
+
// the on-disk config; the live runtime takes effect on the next session
|
|
4209
|
+
// boot. When `session` IS available, we apply the runtime change too.
|
|
4210
|
+
//
|
|
4211
|
+
// Wire shape per response: always `{ type: 'yeaft_mcp_*', servers, runtime?, error? }`.
|
|
4212
|
+
// Frontend reducer should treat `error` as a non-empty string failure.
|
|
4213
|
+
// ────────────────────────────────────────────────────────────
|
|
4214
|
+
|
|
4215
|
+
/**
|
|
4216
|
+
* Snapshot the live MCP runtime so the UI can render per-server
|
|
4217
|
+
* connection state next to the configured servers. Safe to call when
|
|
4218
|
+
* the session hasn't been initialised yet — returns an empty runtime.
|
|
4219
|
+
*/
|
|
4220
|
+
function mcpRuntimeSnapshot() {
|
|
4221
|
+
if (!session?.mcpManager) {
|
|
4222
|
+
return { connected: false, toolCount: 0, perServer: [] };
|
|
4223
|
+
}
|
|
4224
|
+
const status = session.mcpManager.status() || [];
|
|
4225
|
+
const toolCount = typeof session.mcpManager.toolCount === 'number'
|
|
4226
|
+
? session.mcpManager.toolCount
|
|
4227
|
+
: status.reduce((sum, s) => sum + (s.toolCount || 0), 0);
|
|
4228
|
+
return {
|
|
4229
|
+
connected: !!session.mcpManager.hasServers,
|
|
4230
|
+
toolCount,
|
|
4231
|
+
perServer: status.map(s => ({
|
|
4232
|
+
name: s.name,
|
|
4233
|
+
ready: !!s.ready,
|
|
4234
|
+
toolCount: s.toolCount || 0,
|
|
4235
|
+
})),
|
|
4236
|
+
};
|
|
4237
|
+
}
|
|
4238
|
+
|
|
4239
|
+
/**
|
|
4240
|
+
* Re-flatten MCP tools into the live ToolRegistry. No-op when the session
|
|
4241
|
+
* (or its registry) hasn't been created yet — the next session boot will
|
|
4242
|
+
* pick up the change.
|
|
4243
|
+
*/
|
|
4244
|
+
function hotSwapMcpTools() {
|
|
4245
|
+
if (!session?.toolRegistry || typeof session.toolRegistry.replaceMcpTools !== 'function') {
|
|
4246
|
+
return { removed: 0, added: 0, skipped: true };
|
|
4247
|
+
}
|
|
4248
|
+
try {
|
|
4249
|
+
const result = session.toolRegistry.replaceMcpTools(session.mcpManager, buildMcpFlattenedTools);
|
|
4250
|
+
return { ...result, skipped: false };
|
|
4251
|
+
} catch (err) {
|
|
4252
|
+
console.warn('[Yeaft] hot-swap MCP tools failed:', err?.message || err);
|
|
4253
|
+
return { removed: 0, added: 0, skipped: true, error: err?.message || String(err) };
|
|
4254
|
+
}
|
|
4255
|
+
}
|
|
4256
|
+
|
|
4257
|
+
/**
|
|
4258
|
+
* Broadcast a `yeaft_mcp_updated` event so any client subscribed to the
|
|
4259
|
+
* Yeaft view (Settings panel, status badge) refreshes without needing
|
|
4260
|
+
* to re-open the panel. The current list+runtime are included so the UI
|
|
4261
|
+
* is single-source (no separate fetch round-trip needed).
|
|
4262
|
+
*/
|
|
4263
|
+
function broadcastMcpUpdated(extra = {}) {
|
|
4264
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4265
|
+
const listed = listMcpServers(yeaftDir);
|
|
4266
|
+
sendToServer({
|
|
4267
|
+
type: 'yeaft_mcp_updated',
|
|
4268
|
+
servers: listed.servers || [],
|
|
4269
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4270
|
+
...extra,
|
|
4271
|
+
});
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4274
|
+
export function handleYeaftMcpList(msg = {}) {
|
|
4275
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4276
|
+
const listed = listMcpServers(yeaftDir);
|
|
4277
|
+
sendToServer({
|
|
4278
|
+
type: 'yeaft_mcp_list_result',
|
|
4279
|
+
requestId: msg.requestId || null,
|
|
4280
|
+
servers: listed.servers || [],
|
|
4281
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4282
|
+
error: listed.error || null,
|
|
4283
|
+
});
|
|
4284
|
+
}
|
|
4285
|
+
|
|
4286
|
+
export async function handleYeaftMcpAdd(msg = {}) {
|
|
4287
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4288
|
+
const result = upsertMcpServer(msg.server || {}, yeaftDir);
|
|
4289
|
+
if (result.error) {
|
|
4290
|
+
sendToServer({
|
|
4291
|
+
type: 'yeaft_mcp_add_result',
|
|
4292
|
+
requestId: msg.requestId || null,
|
|
4293
|
+
servers: [],
|
|
4294
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4295
|
+
error: result.error,
|
|
4296
|
+
});
|
|
4297
|
+
return;
|
|
4298
|
+
}
|
|
4299
|
+
|
|
4300
|
+
// Apply at runtime when the session is live. The MCPManager's
|
|
4301
|
+
// `connect(serverConfig)` already disconnects-and-reconnects if a
|
|
4302
|
+
// server with the same name was already registered.
|
|
4303
|
+
let connectError = null;
|
|
4304
|
+
if (session?.mcpManager) {
|
|
4305
|
+
try {
|
|
4306
|
+
await session.mcpManager.connect(result.server);
|
|
4307
|
+
} catch (err) {
|
|
4308
|
+
connectError = err?.message || String(err);
|
|
4309
|
+
console.warn(`[Yeaft] MCP connect "${result.server.name}" failed:`, connectError);
|
|
4310
|
+
}
|
|
4311
|
+
}
|
|
4312
|
+
|
|
4313
|
+
const swap = hotSwapMcpTools();
|
|
4314
|
+
|
|
4315
|
+
sendToServer({
|
|
4316
|
+
type: 'yeaft_mcp_add_result',
|
|
4317
|
+
requestId: msg.requestId || null,
|
|
4318
|
+
servers: result.servers,
|
|
4319
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4320
|
+
swap,
|
|
4321
|
+
connectError,
|
|
4322
|
+
error: null,
|
|
4323
|
+
});
|
|
4324
|
+
broadcastMcpUpdated({ reason: 'add', name: result.server.name, connectError });
|
|
4325
|
+
}
|
|
4326
|
+
|
|
4327
|
+
export async function handleYeaftMcpRemove(msg = {}) {
|
|
4328
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4329
|
+
const name = typeof msg.name === 'string' ? msg.name : '';
|
|
4330
|
+
const result = removeMcpServer(name, yeaftDir);
|
|
4331
|
+
if (result.error) {
|
|
4332
|
+
sendToServer({
|
|
4333
|
+
type: 'yeaft_mcp_remove_result',
|
|
4334
|
+
requestId: msg.requestId || null,
|
|
4335
|
+
servers: [],
|
|
4336
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4337
|
+
error: result.error,
|
|
4338
|
+
});
|
|
4339
|
+
return;
|
|
4340
|
+
}
|
|
4341
|
+
|
|
4342
|
+
if (session?.mcpManager) {
|
|
4343
|
+
try {
|
|
4344
|
+
await session.mcpManager.disconnect(name);
|
|
4345
|
+
} catch (err) {
|
|
4346
|
+
console.warn(`[Yeaft] MCP disconnect "${name}" failed:`, err?.message || err);
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
const swap = hotSwapMcpTools();
|
|
4351
|
+
|
|
4352
|
+
sendToServer({
|
|
4353
|
+
type: 'yeaft_mcp_remove_result',
|
|
4354
|
+
requestId: msg.requestId || null,
|
|
4355
|
+
servers: result.servers,
|
|
4356
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4357
|
+
removed: !!result.removed,
|
|
4358
|
+
swap,
|
|
4359
|
+
error: null,
|
|
4360
|
+
});
|
|
4361
|
+
broadcastMcpUpdated({ reason: 'remove', name });
|
|
4362
|
+
}
|
|
4363
|
+
|
|
4364
|
+
export async function handleYeaftMcpReload(msg = {}) {
|
|
4365
|
+
const yeaftDir = ctx.CONFIG?.yeaftDir;
|
|
4366
|
+
const targetName = typeof msg.name === 'string' && msg.name ? msg.name : null;
|
|
4367
|
+
|
|
4368
|
+
if (!session?.mcpManager) {
|
|
4369
|
+
// Session not yet alive — just echo the current config + an empty
|
|
4370
|
+
// runtime so the UI knows to wait for session boot.
|
|
4371
|
+
const listed = listMcpServers(yeaftDir);
|
|
4372
|
+
sendToServer({
|
|
4373
|
+
type: 'yeaft_mcp_reload_result',
|
|
4374
|
+
requestId: msg.requestId || null,
|
|
4375
|
+
servers: listed.servers || [],
|
|
4376
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4377
|
+
error: null,
|
|
4378
|
+
});
|
|
4379
|
+
return;
|
|
4380
|
+
}
|
|
4381
|
+
|
|
4382
|
+
const listed = listMcpServers(yeaftDir);
|
|
4383
|
+
const configured = listed.servers || [];
|
|
4384
|
+
|
|
4385
|
+
// Per-server reload: disconnect + reconnect the named server only.
|
|
4386
|
+
// Whole-set reload: disconnect everything, then reconnect from current
|
|
4387
|
+
// config.json. The latter is what the user clicks "Reload all" for.
|
|
4388
|
+
const failures = [];
|
|
4389
|
+
try {
|
|
4390
|
+
if (targetName) {
|
|
4391
|
+
const cfg = configured.find(s => s.name === targetName);
|
|
4392
|
+
try { await session.mcpManager.disconnect(targetName); } catch { /* ignore */ }
|
|
4393
|
+
if (cfg) {
|
|
4394
|
+
try { await session.mcpManager.connect(cfg); }
|
|
4395
|
+
catch (err) { failures.push({ name: targetName, error: err?.message || String(err) }); }
|
|
4396
|
+
}
|
|
4397
|
+
} else {
|
|
4398
|
+
try { await session.mcpManager.disconnectAll(); } catch { /* ignore */ }
|
|
4399
|
+
for (const cfg of configured) {
|
|
4400
|
+
try { await session.mcpManager.connect(cfg); }
|
|
4401
|
+
catch (err) { failures.push({ name: cfg.name, error: err?.message || String(err) }); }
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
} catch (err) {
|
|
4405
|
+
console.warn('[Yeaft] MCP reload failed:', err?.message || err);
|
|
4406
|
+
}
|
|
4407
|
+
|
|
4408
|
+
const swap = hotSwapMcpTools();
|
|
4409
|
+
|
|
4410
|
+
sendToServer({
|
|
4411
|
+
type: 'yeaft_mcp_reload_result',
|
|
4412
|
+
requestId: msg.requestId || null,
|
|
4413
|
+
servers: configured,
|
|
4414
|
+
runtime: mcpRuntimeSnapshot(),
|
|
4415
|
+
failures,
|
|
4416
|
+
swap,
|
|
4417
|
+
error: null,
|
|
4418
|
+
});
|
|
4419
|
+
broadcastMcpUpdated({ reason: 'reload', name: targetName, failures });
|
|
4420
|
+
}
|