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:
2025-12-12 21:23:01 +08:00
parent 9365e07df1
commit a225e66ad7
13 changed files with 2447 additions and 5 deletions
+602
View File
@@ -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
);
}
});
+1
View File
@@ -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';