feat(agents): 添加 Agent 预设管理功能
- 创建 Server Agents API 路由 (CRUD + presets + defaults) - 添加 UI Agent 类型定义和 API 客户端函数 - 实现 AgentsPanel 组件 (预设/自定义 Agent 列表) - 实现 AgentEditor 组件 (创建/编辑 Agent) - 实现 AgentDefaultsEditor 组件 (全局默认配置) - 集成 AgentsPanel 到 Web 和 Desktop 应用
This commit is contained in:
@@ -0,0 +1,602 @@
|
||||
/**
|
||||
* Agents API Routes
|
||||
*
|
||||
* 提供 Agent 预设管理的 REST API
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import { getConfig } from './config.js';
|
||||
|
||||
// Agent 类型定义(与 Core 对应)
|
||||
type AgentMode = 'primary' | 'subagent' | 'all';
|
||||
|
||||
interface AgentModelConfig {
|
||||
provider?: 'anthropic' | 'deepseek' | 'openai';
|
||||
model?: string;
|
||||
temperature?: number;
|
||||
topP?: number;
|
||||
maxTokens?: number;
|
||||
}
|
||||
|
||||
interface AgentToolConfig {
|
||||
disabled?: string[];
|
||||
enabled?: string[];
|
||||
noTask?: boolean;
|
||||
}
|
||||
|
||||
interface PermissionRule {
|
||||
pattern: string;
|
||||
action: 'allow' | 'deny';
|
||||
}
|
||||
|
||||
interface AgentBashPermission {
|
||||
rules?: PermissionRule[];
|
||||
defaultAction?: 'allow' | 'deny';
|
||||
}
|
||||
|
||||
interface AgentFilePermission {
|
||||
read?: { rules?: PermissionRule[]; defaultAction?: 'allow' | 'deny' };
|
||||
write?: { rules?: PermissionRule[]; defaultAction?: 'allow' | 'deny' };
|
||||
}
|
||||
|
||||
interface AgentGitPermission {
|
||||
commands?: string[];
|
||||
allowPush?: boolean;
|
||||
allowForce?: boolean;
|
||||
}
|
||||
|
||||
interface AgentPermission {
|
||||
bash?: AgentBashPermission;
|
||||
file?: AgentFilePermission;
|
||||
git?: AgentGitPermission;
|
||||
}
|
||||
|
||||
interface AgentInfo {
|
||||
name: string;
|
||||
description: string;
|
||||
mode: AgentMode;
|
||||
prompt?: string;
|
||||
model?: AgentModelConfig;
|
||||
tools?: AgentToolConfig;
|
||||
permission?: AgentPermission;
|
||||
maxSteps?: number;
|
||||
}
|
||||
|
||||
interface AgentConfigFile {
|
||||
defaults?: {
|
||||
maxSteps?: number;
|
||||
model?: AgentModelConfig;
|
||||
permission?: AgentPermission;
|
||||
};
|
||||
agents?: Record<string, Omit<AgentInfo, 'name'>>;
|
||||
}
|
||||
|
||||
// Core Agent 模块类型
|
||||
interface AgentModule {
|
||||
agentRegistry: {
|
||||
init: (workdir: string) => Promise<void>;
|
||||
get: (name: string) => AgentInfo | undefined;
|
||||
list: () => AgentInfo[];
|
||||
};
|
||||
loadAgentConfig: (workdir: string) => Promise<AgentConfigFile | null>;
|
||||
saveAgentConfig: (workdir: string, config: AgentConfigFile, format?: 'json' | 'yaml') => Promise<void>;
|
||||
presetAgents: Record<string, Omit<AgentInfo, 'name'>>;
|
||||
isPresetAgent: (name: string) => boolean;
|
||||
}
|
||||
|
||||
// API 响应类型
|
||||
interface AgentListItem {
|
||||
name: string;
|
||||
description: string;
|
||||
mode: AgentMode;
|
||||
isPreset: boolean;
|
||||
isCustomized: boolean;
|
||||
model?: string;
|
||||
maxSteps?: number;
|
||||
}
|
||||
|
||||
interface AgentDefaults {
|
||||
maxSteps?: number;
|
||||
model?: AgentModelConfig;
|
||||
permission?: AgentPermission;
|
||||
}
|
||||
|
||||
export const agentsRouter = new Hono();
|
||||
|
||||
// Core 模块缓存
|
||||
let agentModule: AgentModule | null = null;
|
||||
let initialized = false;
|
||||
|
||||
/**
|
||||
* 初始化 Agent 模块
|
||||
*/
|
||||
async function initAgentModule(): Promise<AgentModule | null> {
|
||||
if (agentModule) return agentModule;
|
||||
|
||||
try {
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(corePath)) as Record<string, unknown>;
|
||||
|
||||
if (
|
||||
!core.agentRegistry ||
|
||||
typeof core.loadAgentConfig !== 'function' ||
|
||||
typeof core.saveAgentConfig !== 'function' ||
|
||||
!core.presetAgents ||
|
||||
typeof core.isPresetAgent !== 'function'
|
||||
) {
|
||||
console.warn('[Agents] Core module missing Agent exports');
|
||||
return null;
|
||||
}
|
||||
|
||||
agentModule = {
|
||||
agentRegistry: core.agentRegistry as AgentModule['agentRegistry'],
|
||||
loadAgentConfig: core.loadAgentConfig as AgentModule['loadAgentConfig'],
|
||||
saveAgentConfig: core.saveAgentConfig as AgentModule['saveAgentConfig'],
|
||||
presetAgents: core.presetAgents as AgentModule['presetAgents'],
|
||||
isPresetAgent: core.isPresetAgent as AgentModule['isPresetAgent'],
|
||||
};
|
||||
|
||||
console.log('[Agents] Agent module initialized');
|
||||
return agentModule;
|
||||
} catch (error) {
|
||||
console.warn('[Agents] Failed to load Agent module:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保 AgentRegistry 已初始化
|
||||
*/
|
||||
async function ensureRegistryInitialized(): Promise<boolean> {
|
||||
const module = await initAgentModule();
|
||||
if (!module) return false;
|
||||
|
||||
if (!initialized) {
|
||||
const config = getConfig();
|
||||
await module.agentRegistry.init(config.workdir);
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 AgentInfo 转换为 AgentListItem
|
||||
*/
|
||||
function toListItem(agent: AgentInfo, module: AgentModule, customAgentNames: Set<string>): AgentListItem {
|
||||
const isPreset = module.isPresetAgent(agent.name);
|
||||
return {
|
||||
name: agent.name,
|
||||
description: agent.description,
|
||||
mode: agent.mode,
|
||||
isPreset,
|
||||
isCustomized: customAgentNames.has(agent.name),
|
||||
model: agent.model?.model,
|
||||
maxSteps: agent.maxSteps,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /agents - 获取所有 Agent 列表
|
||||
*/
|
||||
agentsRouter.get('/', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to initialize agent registry',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const customAgentNames = new Set(Object.keys(userConfig?.agents || {}));
|
||||
|
||||
const agents = module.agentRegistry.list();
|
||||
const items = agents.map((agent) => toListItem(agent, module, customAgentNames));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: items,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /agents/presets - 获取预设 Agent 列表
|
||||
*/
|
||||
agentsRouter.get('/presets', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const presets = Object.entries(module.presetAgents).map(([name, agent]) => ({
|
||||
name,
|
||||
description: agent.description,
|
||||
mode: agent.mode,
|
||||
isPreset: true,
|
||||
isCustomized: false,
|
||||
model: agent.model?.model,
|
||||
maxSteps: agent.maxSteps,
|
||||
}));
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: presets,
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /agents/defaults - 获取全局默认配置
|
||||
*/
|
||||
agentsRouter.get('/defaults', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: userConfig?.defaults || {},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /agents/defaults - 更新全局默认配置
|
||||
*/
|
||||
agentsRouter.put('/defaults', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const newDefaults = await c.req.json<AgentDefaults>();
|
||||
const config = getConfig();
|
||||
|
||||
// 加载现有配置
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
|
||||
// 更新 defaults
|
||||
userConfig.defaults = newDefaults;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry 以应用新配置
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: newDefaults,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update defaults',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /agents/:name - 获取单个 Agent 详情
|
||||
*/
|
||||
agentsRouter.get('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await ensureRegistryInitialized())) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Failed to initialize agent registry',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
const name = c.req.param('name');
|
||||
const agent = module.agentRegistry.get(name);
|
||||
|
||||
if (!agent) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Agent '${name}' not found`,
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
const isPreset = module.isPresetAgent(name);
|
||||
const isCustomized = !!(userConfig?.agents && name in userConfig.agents);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: {
|
||||
...agent,
|
||||
isPreset,
|
||||
isCustomized,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* POST /agents - 创建新 Agent
|
||||
*/
|
||||
agentsRouter.post('/', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await c.req.json<{ name: string } & Omit<AgentInfo, 'name'>>();
|
||||
const { name, ...agentConfig } = body;
|
||||
|
||||
if (!name || typeof name !== 'string') {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent name is required',
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
// 检查名称是否与预设冲突
|
||||
if (module.isPresetAgent(name)) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Cannot create agent with preset name '${name}'. Use PUT to customize a preset.`,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
const config = getConfig();
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
if (!userConfig.agents) {
|
||||
userConfig.agents = {};
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
if (name in userConfig.agents) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Agent '${name}' already exists`,
|
||||
},
|
||||
409
|
||||
);
|
||||
}
|
||||
|
||||
// 添加新 Agent
|
||||
userConfig.agents[name] = agentConfig;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
const createdAgent = module.agentRegistry.get(name);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: createdAgent
|
||||
? {
|
||||
...createdAgent,
|
||||
isPreset: false,
|
||||
isCustomized: false,
|
||||
}
|
||||
: { name, ...agentConfig, isPreset: false, isCustomized: false },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to create agent',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* PUT /agents/:name - 更新 Agent
|
||||
*/
|
||||
agentsRouter.put('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const name = c.req.param('name');
|
||||
const agentConfig = await c.req.json<Omit<AgentInfo, 'name'>>();
|
||||
|
||||
const config = getConfig();
|
||||
let userConfig = await module.loadAgentConfig(config.workdir);
|
||||
if (!userConfig) {
|
||||
userConfig = {};
|
||||
}
|
||||
if (!userConfig.agents) {
|
||||
userConfig.agents = {};
|
||||
}
|
||||
|
||||
// 更新 Agent(支持覆盖预设)
|
||||
userConfig.agents[name] = agentConfig;
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
const updatedAgent = module.agentRegistry.get(name);
|
||||
const isPreset = module.isPresetAgent(name);
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: updatedAgent
|
||||
? {
|
||||
...updatedAgent,
|
||||
isPreset,
|
||||
isCustomized: true,
|
||||
}
|
||||
: { name, ...agentConfig, isPreset, isCustomized: true },
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to update agent',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /agents/:name - 删除 Agent
|
||||
*/
|
||||
agentsRouter.delete('/:name', async (c) => {
|
||||
const module = await initAgentModule();
|
||||
|
||||
if (!module) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Agent module not available',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const name = c.req.param('name');
|
||||
const config = getConfig();
|
||||
const userConfig = await module.loadAgentConfig(config.workdir);
|
||||
|
||||
if (!userConfig?.agents || !(name in userConfig.agents)) {
|
||||
// 如果是预设 Agent,返回特定错误
|
||||
if (module.isPresetAgent(name)) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Cannot delete preset agent '${name}'`,
|
||||
},
|
||||
400
|
||||
);
|
||||
}
|
||||
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: `Agent '${name}' not found in user configuration`,
|
||||
},
|
||||
404
|
||||
);
|
||||
}
|
||||
|
||||
// 删除 Agent
|
||||
delete userConfig.agents[name];
|
||||
|
||||
// 保存配置
|
||||
await module.saveAgentConfig(config.workdir, userConfig);
|
||||
|
||||
// 重新初始化 registry
|
||||
initialized = false;
|
||||
await ensureRegistryInitialized();
|
||||
|
||||
return c.json({
|
||||
success: true,
|
||||
data: null,
|
||||
});
|
||||
} catch (error) {
|
||||
return c.json(
|
||||
{
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to delete agent',
|
||||
},
|
||||
500
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -11,3 +11,4 @@ export { filesRouter, setWorkingDirectory, getWorkingDirectory } from './files.j
|
||||
export { commandsRouter } from './commands.js';
|
||||
export { mcpRouter } from './mcp.js';
|
||||
export { hooksRouter } from './hooks.js';
|
||||
export { agentsRouter } from './agents.js';
|
||||
|
||||
Reference in New Issue
Block a user