feat: 插件-工具合并 — 创建 pkg/plugins 共享模块并移除 tool-engine

- 新增 backend/pkg/plugins/ 共享模块:SDK 接口、PluginManager、ToolRegistry(含环形缓冲区调用日志)
- 13 个通用插件从 plugin-manager 迁移至共享模块(import 路径统一)
- ai-core 切换至共享 ToolRegistry,进程内执行(零网络开销),包装 6 个专属工具
- plugin-manager 迁移至共享模块,保留管理 REST API
- 新增 DevTools 插件管理面板(侧边栏 → 🔌 插件管理)
- 移除 tool-engine 服务(从 go.work、DevTools 配置、编译系统)
- 工具调用记录 API 从 Tool-Engine 迁至 AI-Core(/api/v1/tools/calls)
- ai-core ContextStore 启动时从 PostgreSQL 恢复会话历史
- 清理所有过时引用和备份文件

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-25 20:52:39 +08:00
parent 5325eaca3f
commit 673ff752c5
78 changed files with 1313 additions and 5187 deletions
+1 -15
View File
@@ -57,6 +57,7 @@ export const DEVTOOLS_PORT = process.env.DEVTOOLS_PORT || 9090;
export const LOGS_DIR = path.resolve(__dirname, '../logs');
export const GATEWAY_URL = process.env.GATEWAY_URL || 'http://localhost:8080';
export const TOOL_ENGINE_URL = process.env.TOOL_ENGINE_URL || 'http://localhost:8092';
export const PLUGIN_MANAGER_URL = process.env.PLUGIN_MANAGER_URL || 'http://localhost:8094';
export const ADMIN_USERNAME = process.env.ADMIN_USERNAME || 'admin';
export const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'cyrene-dev-admin';
@@ -124,21 +125,6 @@ export const SERVICES = {
buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'],
goBin: GO_BIN,
},
'tool-engine': {
name: '工具引擎',
cwd: path.join(ROOT, 'backend/tool-engine'),
command: './main',
env: {
PORT: '8092',
DB_URL: process.env.DB_URL || 'postgres://cyrene:cyrene_pass@localhost:5432/cyrene_ai?sslmode=disable',
IOT_SERVICE_URL: process.env.IOT_SERVICE_URL || process.env.IOT_DEBUG_SERVICE_URL || 'http://localhost:8083',
},
healthUrl: 'http://localhost:8092/api/v1/health',
port: 8092,
buildCommand: 'go',
buildArgs: ['build', '-o', isWin ? 'main.exe' : 'main', './cmd/main.go'],
goBin: GO_BIN,
},
'voice-service': {
name: '语音识别服务',
cwd: path.join(ROOT, 'backend/voice-service'),
+60 -14
View File
@@ -17,7 +17,7 @@ import { execSync, spawn } from 'child_process';
import { processManager } from './process-manager.js';
import { performanceMonitor } from './performance.js';
import { SERVICES, DEVTOOLS_PORT, LOGS_DIR, logFile, GATEWAY_URL, TOOL_ENGINE_URL, ADMIN_USERNAME, ADMIN_PASSWORD } from './config.js';
import { SERVICES, DEVTOOLS_PORT, LOGS_DIR, logFile, GATEWAY_URL, PLUGIN_MANAGER_URL, ADMIN_USERNAME, ADMIN_PASSWORD } from './config.js';
const AI_CORE_URL = process.env.AI_CORE_URL || 'http://localhost:8081';
const MEMORY_SERVICE_URL = process.env.MEMORY_SERVICE_URL || 'http://localhost:8091';
@@ -594,16 +594,14 @@ app.get('/api/iot/devices/:id/history', async (req, res) => {
res.status(result.status).json(result.body);
});
// ---- 工具调用记录代理 (转发到 tool-engine) ----
// ---- 插件管理代理 (转发到 plugin-manager) ----
/**
* 代理请求到 Tool-Engine
* @param {string} path - Tool-Engine API 路径
* @param {object} opts - fetch 选项
* 代理请求到 Plugin-Manager
*/
async function proxyToToolEngine(path, opts = {}) {
const url = `${TOOL_ENGINE_URL}${path}`;
const logPrefix = `[ToolEngine代理]`;
async function proxyToPluginManager(path, opts = {}) {
const url = `${PLUGIN_MANAGER_URL}${path}`;
const logPrefix = `[PluginManager代理]`;
try {
console.log(`${logPrefix} ${opts.method || 'GET'} ${path}`);
const resp = await fetch(url, {
@@ -622,11 +620,11 @@ async function proxyToToolEngine(path, opts = {}) {
return {
status: 502,
body: {
error: `Tool-Engine 不可达: ${err.message}`,
errorType: isConnRefused ? 'tool_engine_not_running' : 'tool_engine_unreachable',
error: `插件管理器不可达: ${err.message}`,
errorType: isConnRefused ? 'plugin_manager_not_running' : 'plugin_manager_unreachable',
hint: isConnRefused
? 'Tool-Engine 服务未启动,请先在「服务管理」面板中启动 Tool-Engine'
: 'Tool-Engine 服务无响应,请检查网络连接和服务状态',
? '插件管理器服务未启动,请先在「服务管理」面板中启动 plugin-manager'
: '插件管理器服务无响应,请检查网络连接和服务状态',
},
};
}
@@ -808,6 +806,18 @@ app.get('/api/model-config/fetch-models/:name', async (req, res) => {
res.status(result.status).json(result.body);
});
// ---- 思考调度配置代理 ----
app.get('/api/thinking-schedule', async (_req, res) => {
const result = await proxyToGateway('/api/v1/admin/thinking-schedule');
res.status(result.status).json(result.body);
});
app.put('/api/thinking-schedule', async (req, res) => {
const result = await proxyToGateway('/api/v1/admin/thinking-schedule', {
method: 'PUT', body: JSON.stringify(req.body),
});
res.status(result.status).json(result.body);
});
// GET /api/tool-calls — 查询工具调用记录
app.get('/api/tool-calls', async (req, res) => {
const { tool_name, page, limit } = req.query;
@@ -815,13 +825,49 @@ app.get('/api/tool-calls', async (req, res) => {
if (tool_name) params.set('tool_name', tool_name);
params.set('page', page || '1');
params.set('limit', limit || '20');
const result = await proxyToToolEngine(`/api/v1/tools/calls?${params.toString()}`);
const result = await proxyToAICore(`/api/v1/tools/calls?${params.toString()}`);
res.status(result.status).json(result.body);
});
// GET /api/tool-calls/stats — 工具调用统计
app.get('/api/tool-calls/stats', async (_req, res) => {
const result = await proxyToToolEngine('/api/v1/tools/calls/stats');
const result = await proxyToAICore('/api/v1/tools/calls/stats');
res.status(result.status).json(result.body);
});
// ---- 插件管理代理 (转发到 plugin-manager) ----
app.get('/api/plugins', async (_req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins');
res.status(result.status).json(result.body);
});
app.get('/api/plugins/:id', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins/' + req.params.id);
res.status(result.status).json(result.body);
});
app.post('/api/plugins/:id/enable', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins/' + req.params.id + '/enable', { method: 'POST' });
res.status(result.status).json(result.body);
});
app.post('/api/plugins/:id/disable', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins/' + req.params.id + '/disable', { method: 'POST' });
res.status(result.status).json(result.body);
});
app.post('/api/plugins/:id/reload', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins/' + req.params.id + '/reload', { method: 'POST' });
res.status(result.status).json(result.body);
});
app.get('/api/plugins/:id/tools', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/plugins/' + req.params.id + '/tools');
res.status(result.status).json(result.body);
});
app.get('/api/tools', async (_req, res) => {
const result = await proxyToPluginManager('/api/v1/tools');
res.status(result.status).json(result.body);
});
app.post('/api/tools/:id/execute', async (req, res) => {
const result = await proxyToPluginManager('/api/v1/tools/' + req.params.id + '/execute', {
method: 'POST', body: JSON.stringify(req.body),
});
res.status(result.status).json(result.body);
});
+3 -3
View File
@@ -134,7 +134,7 @@ class ProcessManager extends EventEmitter {
}
// 对需要数据库的服务做前置检查
if (['gateway', 'ai-core', 'memory-service', 'tool-engine', 'plugin-manager', 'platform-bridge'].includes(serviceId)) {
if (['gateway', 'ai-core', 'memory-service', 'plugin-manager', 'platform-bridge'].includes(serviceId)) {
this.emit('log', serviceId, 'system', '检查数据库连接状态...');
await ensureDBOnline(serviceId, this);
}
@@ -450,11 +450,11 @@ class ProcessManager extends EventEmitter {
}
/**
* 按顺序启动所有服务 (memory → tool-engine → iot → voice → ai-core → gateway → frontend)
* 按顺序启动所有服务 (memory → iot → voice → ai-core → gateway → frontend)
* 每步等待健康检查通过后再启动下一个
*/
async startAllSequential() {
const order = ['memory-service', 'tool-engine', 'plugin-manager', 'iot-debug-service', 'voice-service', 'ai-core', 'platform-bridge', 'gateway', 'frontend'];
const order = ['memory-service', 'plugin-manager', 'iot-debug-service', 'voice-service', 'ai-core', 'platform-bridge', 'gateway', 'frontend'];
const results = [];
for (const id of order) {