feat: 添加多代理系统和 Task 子任务功能

Agent 模块:
- types.ts: Agent 类型定义 (AgentInfo, AgentPermission 等)
- permission-merger.ts: 三级权限合并逻辑 (系统->全局->Agent)
- config-loader.ts: 支持 YAML/JSON 配置文件加载
- registry.ts: Agent 注册表,管理预设和自定义 Agent
- executor.ts: Agent 执行器,支持工具过滤和权限控制
- presets/: 预设 Agent (general, explore, code-reviewer, build, plan)

Task 工具:
- task.ts: 执行子任务,委派给指定 Agent 处理
- 支持子会话创建和管理

会话扩展:
- 支持父子会话关系 (parentId, agentName)
- 新增 createChildSession, saveChildSession 方法

配置:
- 支持 .ai-assist/agents.yaml 用户自定义 Agent
- 支持通配符模式的 Bash 权限规则
This commit is contained in:
2025-12-11 11:21:08 +08:00
parent c6f8ba95ec
commit 82f0a0ccde
22 changed files with 1600 additions and 2 deletions
+27
View File
@@ -16,6 +16,7 @@
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.1.0", "commander": "^12.1.0",
"inquirer": "^12.0.0", "inquirer": "^12.0.0",
"js-yaml": "^4.1.1",
"ora": "^8.1.0", "ora": "^8.1.0",
"tree-sitter-bash": "^0.25.1", "tree-sitter-bash": "^0.25.1",
"vscode-jsonrpc": "^8.2.1", "vscode-jsonrpc": "^8.2.1",
@@ -27,6 +28,7 @@
"ai-assist": "dist/index.js" "ai-assist": "dist/index.js"
}, },
"devDependencies": { "devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"tsx": "^4.19.0", "tsx": "^4.19.0",
"typescript": "^5.6.0" "typescript": "^5.6.0"
@@ -912,6 +914,13 @@
"js-tiktoken": "^1.0.14" "js-tiktoken": "^1.0.14"
} }
}, },
"node_modules/@types/js-yaml": {
"version": "4.0.9",
"resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
"integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "22.19.2", "version": "22.19.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
@@ -985,6 +994,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "url": "https://github.com/chalk/ansi-styles?sponsor=1"
} }
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -1540,6 +1555,18 @@
"base64-js": "^1.5.1" "base64-js": "^1.5.1"
} }
}, },
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-schema": { "node_modules/json-schema": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+2
View File
@@ -30,6 +30,7 @@
"chalk": "^5.3.0", "chalk": "^5.3.0",
"commander": "^12.1.0", "commander": "^12.1.0",
"inquirer": "^12.0.0", "inquirer": "^12.0.0",
"js-yaml": "^4.1.1",
"ora": "^8.1.0", "ora": "^8.1.0",
"tree-sitter-bash": "^0.25.1", "tree-sitter-bash": "^0.25.1",
"vscode-jsonrpc": "^8.2.1", "vscode-jsonrpc": "^8.2.1",
@@ -38,6 +39,7 @@
"zod": "^4.1.13" "zod": "^4.1.13"
}, },
"devDependencies": { "devDependencies": {
"@types/js-yaml": "^4.0.9",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"tsx": "^4.19.0", "tsx": "^4.19.0",
"typescript": "^5.6.0" "typescript": "^5.6.0"
+169
View File
@@ -0,0 +1,169 @@
import * as fs from 'fs';
import * as path from 'path';
import type { AgentConfigFile } from './types.js';
/**
* 配置文件搜索路径
*/
const CONFIG_PATHS = [
'.ai-assist/agents.yaml',
'.ai-assist/agents.yml',
'.ai-assist/agents.json',
'.ai-assist.yaml',
'.ai-assist.yml',
];
/**
* 解析 YAML 内容
*/
async function parseYaml(content: string): Promise<unknown> {
try {
const yaml = await import('js-yaml');
return yaml.load(content);
} catch {
console.warn('解析 YAML 配置文件失败');
return null;
}
}
/**
* 加载用户自定义 Agent 配置
* @param workdir 工作目录
* @returns 配置对象或 null
*/
export async function loadAgentConfig(workdir: string): Promise<AgentConfigFile | null> {
for (const configPath of CONFIG_PATHS) {
const fullPath = path.join(workdir, configPath);
try {
if (!fs.existsSync(fullPath)) {
continue;
}
const content = await fs.promises.readFile(fullPath, 'utf-8');
let config: unknown;
if (configPath.endsWith('.json')) {
config = JSON.parse(content);
} else if (configPath.endsWith('.yaml') || configPath.endsWith('.yml')) {
config = await parseYaml(content);
if (!config) continue;
} else {
continue;
}
// 验证配置格式
if (isValidAgentConfig(config)) {
return config;
} else {
console.warn(`Agent 配置格式无效: ${fullPath}`);
}
} catch (error) {
console.warn(`加载 Agent 配置失败: ${fullPath}`, error);
}
}
return null;
}
/**
* 验证配置格式是否有效
*/
function isValidAgentConfig(config: unknown): config is AgentConfigFile {
if (typeof config !== 'object' || config === null) {
return false;
}
const obj = config as Record<string, unknown>;
// defaults 是可选的
if (obj.defaults !== undefined && typeof obj.defaults !== 'object') {
return false;
}
// agents 是可选的
if (obj.agents !== undefined && typeof obj.agents !== 'object') {
return false;
}
return true;
}
/**
* 保存 Agent 配置到文件
* @param workdir 工作目录
* @param config 配置对象
* @param format 文件格式
*/
export async function saveAgentConfig(
workdir: string,
config: AgentConfigFile,
format: 'json' | 'yaml' = 'json'
): Promise<void> {
const dir = path.join(workdir, '.ai-assist');
// 确保目录存在
if (!fs.existsSync(dir)) {
await fs.promises.mkdir(dir, { recursive: true });
}
const filename = format === 'json' ? 'agents.json' : 'agents.yaml';
const fullPath = path.join(dir, filename);
let content: string;
if (format === 'json') {
content = JSON.stringify(config, null, 2);
} else {
try {
const yaml = await import('js-yaml');
content = yaml.dump(config, { indent: 2, lineWidth: 120 });
} catch {
// 回退到 JSON
content = JSON.stringify(config, null, 2);
console.warn('保存 YAML 失败,已保存为 JSON 格式');
}
}
await fs.promises.writeFile(fullPath, content, 'utf-8');
}
/**
* 获取配置文件模板
*/
export function getConfigTemplate(): AgentConfigFile {
return {
defaults: {
maxSteps: 15,
model: {
temperature: 0.7,
},
permission: {
bash: {
rules: [
{ pattern: 'rm -rf *', action: 'deny' },
{ pattern: 'git push --force*', action: 'deny' },
],
},
},
},
agents: {
'custom-agent': {
description: '自定义 Agent 示例',
mode: 'subagent',
prompt: '你是一个自定义助手。',
tools: {
disabled: ['bash'],
},
permission: {
file: {
read: 'allow',
write: 'ask',
},
},
maxSteps: 10,
},
},
};
}
+300
View File
@@ -0,0 +1,300 @@
import { createAnthropic } from '@ai-sdk/anthropic';
import { createDeepSeek } from '@ai-sdk/deepseek';
import {
generateText,
streamText,
stepCountIs,
type ModelMessage,
type Tool as AITool,
type LanguageModel,
} from 'ai';
import type { Tool, ToolResult, ProviderType, AgentConfig } from '../types/index.js';
import { buildZodSchema } from '../types/index.js';
import { ToolRegistry } from '../tools/registry.js';
import type {
AgentInfo,
AgentExecutionContext,
AgentExecutionResult,
} from './types.js';
import { checkBashPermission } from './permission-merger.js';
// Provider 工厂函数类型
type ProviderFactory = (apiKey: string) => (model: string) => LanguageModel;
// Provider 注册表
const providers: Record<ProviderType, ProviderFactory> = {
anthropic: (apiKey) => {
const client = createAnthropic({ apiKey });
return (model) => client(model);
},
deepseek: (apiKey) => {
const client = createDeepSeek({ apiKey });
return (model) => client(model);
},
};
/**
* Agent 执行器
* 根据 Agent 配置执行任务,支持工具过滤和权限控制
*/
export class AgentExecutor {
private agentInfo: AgentInfo;
private baseConfig: AgentConfig;
private toolRegistry: ToolRegistry;
private getModel: (model: string) => LanguageModel;
constructor(
agentInfo: AgentInfo,
baseConfig: AgentConfig,
toolRegistry: ToolRegistry
) {
this.agentInfo = agentInfo;
this.baseConfig = baseConfig;
this.toolRegistry = toolRegistry;
// 获取模型工厂
const provider = agentInfo.model?.provider ?? baseConfig.provider;
const providerFactory = providers[provider];
if (!providerFactory) {
throw new Error(`不支持的 provider: ${provider}`);
}
this.getModel = providerFactory(baseConfig.apiKey);
}
/**
* 执行任务
*/
async execute(
prompt: string,
context: AgentExecutionContext
): Promise<AgentExecutionResult> {
const { onStream, onToolCall, onToolResult } = context;
// 获取过滤后的工具
const tools = this.getFilteredTools();
const vercelTools = this.buildVercelTools(tools);
// 构建系统提示词
const systemPrompt = this.buildSystemPrompt();
// 获取模型配置
const modelName = this.agentInfo.model?.model ?? this.baseConfig.model;
const maxSteps = this.agentInfo.maxSteps ?? 10;
const maxTokens = this.agentInfo.model?.maxTokens ?? this.baseConfig.maxTokens;
// 构建初始消息
const messages: ModelMessage[] = [
{
role: 'user',
content: prompt,
},
];
let fullResponse = '';
let steps = 0;
try {
if (onStream) {
// 流式模式
const result = streamText({
model: this.getModel(modelName),
system: systemPrompt,
messages,
tools: vercelTools,
maxOutputTokens: maxTokens,
stopWhen: stepCountIs(maxSteps),
onChunk: ({ chunk }) => {
if (chunk.type === 'tool-call') {
steps++;
const toolArgs = 'input' in chunk ? chunk.input : {};
onToolCall?.(chunk.toolName, toolArgs as Record<string, unknown>);
onStream(`\n[调用工具: ${chunk.toolName}]\n`);
} else if (chunk.type === 'tool-result') {
const output = (chunk as { output?: ToolResult }).output;
onToolResult?.(
(chunk as { toolName?: string }).toolName ?? 'unknown',
output
);
if (output && typeof output === 'object') {
if (output.success) {
const displayOutput =
output.output.length > 500
? output.output.substring(0, 500) + '...(截断)'
: output.output;
onStream(`[结果: ${displayOutput}]\n`);
} else {
onStream(`[错误: ${output.error}]\n`);
}
}
}
},
});
for await (const chunk of result.textStream) {
fullResponse += chunk;
onStream(chunk);
}
await result.response;
} else {
// 非流式模式
const result = await generateText({
model: this.getModel(modelName),
system: systemPrompt,
messages,
tools: vercelTools,
maxOutputTokens: maxTokens,
stopWhen: stepCountIs(maxSteps),
});
fullResponse = result.text;
steps = result.steps.length;
}
return {
success: true,
text: fullResponse,
steps,
sessionId: context.parentSessionId ?? 'standalone',
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
success: false,
text: '',
steps,
sessionId: context.parentSessionId ?? 'standalone',
error: errorMessage,
};
}
}
/**
* 获取过滤后的工具列表
*/
private getFilteredTools(): Tool[] {
const allTools = this.toolRegistry.getAllTools();
const toolConfig = this.agentInfo.tools;
// 如果没有工具配置,返回所有工具
if (!toolConfig) {
return allTools;
}
let filteredTools = allTools;
// 如果指定了 enabled,只保留这些工具
if (toolConfig.enabled && toolConfig.enabled.length > 0) {
const enabledSet = new Set(toolConfig.enabled);
filteredTools = filteredTools.filter((t) => enabledSet.has(t.name));
}
// 移除 disabled 的工具
if (toolConfig.disabled && toolConfig.disabled.length > 0) {
const disabledSet = new Set(toolConfig.disabled);
filteredTools = filteredTools.filter((t) => !disabledSet.has(t.name));
}
// 如果禁止嵌套 Task,移除 task 工具
if (toolConfig.noTask) {
filteredTools = filteredTools.filter((t) => t.name !== 'task');
}
return filteredTools;
}
/**
* 构建 Vercel AI SDK 工具格式
*/
private buildVercelTools(tools: Tool[]): Record<string, AITool> {
const vercelTools: Record<string, AITool> = {};
for (const tool of tools) {
const schema = buildZodSchema(tool.parameters);
vercelTools[tool.name] = {
description: tool.description,
inputSchema: schema,
execute: async (params) => {
// 权限检查
const permissionResult = await this.checkToolPermission(
tool.name,
params as Record<string, unknown>
);
if (!permissionResult.allowed) {
return {
success: false,
output: '',
error: `权限拒绝: ${permissionResult.reason}`,
};
}
return tool.execute(params as Record<string, unknown>);
},
} as AITool;
}
return vercelTools;
}
/**
* 检查工具调用权限
*/
private async checkToolPermission(
toolName: string,
params: Record<string, unknown>
): Promise<{ allowed: boolean; reason?: string }> {
const permission = this.agentInfo.permission;
if (!permission) {
return { allowed: true };
}
// Bash 权限检查
if (toolName === 'bash' && permission.bash) {
const command = params.command as string;
if (!command) {
return { allowed: true };
}
const action = checkBashPermission(command, permission.bash);
if (action === 'deny') {
return { allowed: false, reason: `命令被禁止: ${command}` };
}
// ask 在这里视为允许(实际的 ask 逻辑在权限管理器中处理)
}
// 文件写入权限检查
if (['write_file', 'edit_file', 'delete_file'].includes(toolName)) {
const filePermission = permission.file;
if (filePermission) {
const operation = toolName === 'write_file' ? 'write' :
toolName === 'edit_file' ? 'edit' : 'delete';
const action = filePermission[operation];
if (action === 'deny') {
return { allowed: false, reason: `${operation} 操作被禁止` };
}
}
}
// Git 写操作权限检查
const gitWriteTools = ['git_add', 'git_commit', 'git_push', 'git_checkout', 'git_stash'];
if (gitWriteTools.includes(toolName) && permission.git?.write === 'deny') {
return { allowed: false, reason: 'Git 写操作被禁止' };
}
return { allowed: true };
}
/**
* 构建系统提示词
*/
private buildSystemPrompt(): string {
// 如果 Agent 有自定义 prompt,使用它
if (this.agentInfo.prompt) {
return this.agentInfo.prompt;
}
// 否则使用基础配置的 systemPrompt
return this.baseConfig.systemPrompt;
}
}
+50
View File
@@ -0,0 +1,50 @@
// Types
export type {
AgentMode,
PermissionAction,
PermissionRule,
AgentBashPermission,
AgentFilePermission,
AgentGitPermission,
AgentPermission,
AgentModelConfig,
AgentToolConfig,
AgentInfo,
AgentConfigFile,
AgentExecutionContext,
AgentExecutionResult,
} from './types.js';
// Registry
export { AgentRegistry, agentRegistry } from './registry.js';
// Executor
export { AgentExecutor } from './executor.js';
// Permission Merger
export {
SYSTEM_DEFAULT_PERMISSION,
mergePermissions,
matchRule,
checkBashPermission,
checkFilePathPermission,
} from './permission-merger.js';
// Config Loader
export {
loadAgentConfig,
saveAgentConfig,
getConfigTemplate,
} from './config-loader.js';
// Presets
export {
presetAgents,
getPresetAgentNames,
isPresetAgent,
generalAgent,
exploreAgent,
codeReviewerAgent,
buildAgent,
planAgent,
} from './presets/index.js';
+221
View File
@@ -0,0 +1,221 @@
import type {
AgentPermission,
AgentFilePermission,
AgentBashPermission,
AgentGitPermission,
PermissionAction,
PermissionRule,
} from './types.js';
/**
* 系统默认权限配置
*/
export const SYSTEM_DEFAULT_PERMISSION: AgentPermission = {
file: {
read: 'allow',
write: 'ask',
edit: 'ask',
delete: 'ask',
},
bash: {
enabled: true,
default: 'ask',
rules: [
// 安全命令
{ pattern: 'ls *', action: 'allow' },
{ pattern: 'pwd', action: 'allow' },
{ pattern: 'cat *', action: 'allow' },
{ pattern: 'head *', action: 'allow' },
{ pattern: 'tail *', action: 'allow' },
{ pattern: 'wc *', action: 'allow' },
{ pattern: 'echo *', action: 'allow' },
{ pattern: 'which *', action: 'allow' },
{ pattern: 'type *', action: 'allow' },
// 危险命令
{ pattern: 'rm -rf *', action: 'deny' },
{ pattern: 'rm -fr *', action: 'deny' },
{ pattern: 'sudo *', action: 'deny' },
{ pattern: 'chmod 777 *', action: 'deny' },
],
},
web: 'ask',
git: {
read: 'allow',
write: 'ask',
dangerous: 'deny',
},
};
/**
* 合并单个权限值
* 优先级: agent > global > system
*/
function mergeAction(
system: PermissionAction | undefined,
global: PermissionAction | undefined,
agent: PermissionAction | undefined
): PermissionAction {
return agent ?? global ?? system ?? 'ask';
}
/**
* 合并规则数组
* Agent 规则优先,然后是 global,最后是 system
*/
function mergeRules(
system: PermissionRule[] | undefined,
global: PermissionRule[] | undefined,
agent: PermissionRule[] | undefined
): PermissionRule[] {
return [
...(agent ?? []),
...(global ?? []),
...(system ?? []),
];
}
/**
* 合并文件权限配置
*/
function mergeFilePermission(
system: AgentFilePermission | undefined,
global: AgentFilePermission | undefined,
agent: AgentFilePermission | undefined
): AgentFilePermission {
return {
read: mergeAction(system?.read, global?.read, agent?.read),
write: mergeAction(system?.write, global?.write, agent?.write),
edit: mergeAction(system?.edit, global?.edit, agent?.edit),
delete: mergeAction(system?.delete, global?.delete, agent?.delete),
sensitivePaths: mergeRules(
system?.sensitivePaths,
global?.sensitivePaths,
agent?.sensitivePaths
),
};
}
/**
* 合并 Bash 权限配置
*/
function mergeBashPermission(
system: AgentBashPermission | undefined,
global: AgentBashPermission | undefined,
agent: AgentBashPermission | undefined
): AgentBashPermission {
// 如果 agent 显式禁用,直接返回
if (agent?.enabled === false) {
return { enabled: false };
}
// 如果 global 禁用且 agent 没有覆盖
if (global?.enabled === false && agent?.enabled === undefined) {
return { enabled: false };
}
return {
enabled: agent?.enabled ?? global?.enabled ?? system?.enabled ?? true,
rules: mergeRules(system?.rules, global?.rules, agent?.rules),
default: mergeAction(system?.default, global?.default, agent?.default),
};
}
/**
* 合并 Git 权限配置
*/
function mergeGitPermission(
system: AgentGitPermission | undefined,
global: AgentGitPermission | undefined,
agent: AgentGitPermission | undefined
): AgentGitPermission {
return {
read: mergeAction(system?.read, global?.read, agent?.read),
write: mergeAction(system?.write, global?.write, agent?.write),
dangerous: mergeAction(system?.dangerous, global?.dangerous, agent?.dangerous),
};
}
/**
* 合并完整的权限配置
* 优先级: Agent 配置 > 全局默认 > 系统默认
*/
export function mergePermissions(
systemDefault: AgentPermission,
globalConfig: AgentPermission | undefined,
agentConfig: AgentPermission | undefined
): AgentPermission {
return {
file: mergeFilePermission(
systemDefault.file,
globalConfig?.file,
agentConfig?.file
),
bash: mergeBashPermission(
systemDefault.bash,
globalConfig?.bash,
agentConfig?.bash
),
web: mergeAction(systemDefault.web, globalConfig?.web, agentConfig?.web),
git: mergeGitPermission(
systemDefault.git,
globalConfig?.git,
agentConfig?.git
),
};
}
/**
* 检查命令是否匹配规则
* 支持通配符 * 匹配任意字符
*/
export function matchRule(command: string, pattern: string): boolean {
// 将通配符模式转换为正则表达式
const regexPattern = pattern
.replace(/[.+^${}()|[\]\\]/g, '\\$&') // 转义特殊字符
.replace(/\*/g, '.*') // * 转换为 .*
.replace(/\?/g, '.'); // ? 转换为 .
const regex = new RegExp(`^${regexPattern}$`, 'i');
return regex.test(command);
}
/**
* 根据规则列表检查命令权限
*/
export function checkBashPermission(
command: string,
permission: AgentBashPermission
): PermissionAction {
// 如果禁用,直接拒绝
if (permission.enabled === false) {
return 'deny';
}
// 按顺序检查规则
for (const rule of permission.rules ?? []) {
if (matchRule(command, rule.pattern)) {
return rule.action;
}
}
// 返回默认策略
return permission.default ?? 'ask';
}
/**
* 根据规则列表检查文件路径权限
*/
export function checkFilePathPermission(
filePath: string,
sensitivePaths: PermissionRule[] | undefined
): PermissionAction | null {
if (!sensitivePaths) return null;
for (const rule of sensitivePaths) {
if (matchRule(filePath, rule.pattern)) {
return rule.action;
}
}
return null;
}
+11
View File
@@ -0,0 +1,11 @@
import type { AgentInfo } from '../types.js';
/**
* 构建 Agent
* 主模式,拥有完整权限执行编码任务
*/
export const buildAgent: Omit<AgentInfo, 'name'> = {
description: '构建模式,拥有完整权限执行编码任务',
mode: 'primary',
maxSteps: 30,
};
+86
View File
@@ -0,0 +1,86 @@
import type { AgentInfo } from '../types.js';
/**
* 代码审查 Agent
* 检查代码质量、安全性和最佳实践
*/
export const codeReviewerAgent: Omit<AgentInfo, 'name'> = {
description: '代码审查专家,检查代码质量、安全性和最佳实践',
mode: 'subagent',
prompt: `你是一个资深代码审查专家。你的任务是全面分析代码质量。
审查重点:
1. **代码质量**
- 可读性和命名规范
- 代码结构和组织
- 重复代码和可复用性
- 注释和文档
2. **潜在 Bug**
- 边界条件处理
- 空值/undefined 检查
- 类型安全问题
- 异步错误处理
3. **安全漏洞**
- SQL 注入
- XSS 跨站脚本
- 敏感信息泄露
- 权限控制问题
4. **性能问题**
- 不必要的计算
- 内存泄漏风险
- N+1 查询问题
- 大数据处理
5. **最佳实践**
- 设计模式应用
- SOLID 原则
- 错误处理策略
- 测试覆盖建议
输出格式:
- 问题分为 Critical(严重)、Warning(警告)、Info(建议)三个等级
- 每个问题说明:位置、描述、建议修复方案
- 最后给出总体评分和改进建议`,
tools: {
enabled: [
'read_file',
'list_directory',
'search_files',
'grep_content',
'git_status',
'git_diff',
],
noTask: true,
},
permission: {
file: {
read: 'allow',
write: 'deny',
edit: 'deny',
delete: 'deny',
},
bash: {
enabled: true,
rules: [
{ pattern: 'npm run lint*', action: 'allow' },
{ pattern: 'npm run test*', action: 'allow' },
{ pattern: 'npx eslint *', action: 'allow' },
{ pattern: 'npx tsc --noEmit*', action: 'allow' },
{ pattern: 'tsc --noEmit*', action: 'allow' },
],
default: 'deny',
},
git: {
read: 'allow',
write: 'deny',
dangerous: 'deny',
},
},
model: {
temperature: 0.3, // 低温度,更精确的分析
},
maxSteps: 10,
};
+49
View File
@@ -0,0 +1,49 @@
import type { AgentInfo } from '../types.js';
/**
* 探索 Agent
* 快速探索代码库,搜索文件和代码结构(只读)
*/
export const exploreAgent: Omit<AgentInfo, 'name'> = {
description: '快速探索代码库,搜索文件和代码结构(只读)',
mode: 'subagent',
prompt: `你是一个代码探索专家。你的任务是快速搜索和理解代码库结构。
规则:
- 只做搜索和读取操作,禁止修改任何文件
- 使用 search_files、grep_content、read_file、list_directory 工具来探索代码
- 提供清晰、结构化的分析结果
- 关注代码结构、依赖关系和关键实现
输出格式:
- 使用 Markdown 格式组织信息
- 列出关键文件和它们的作用
- 总结代码结构和设计模式`,
tools: {
enabled: [
'read_file',
'list_directory',
'search_files',
'grep_content',
'get_file_info',
],
noTask: true,
},
permission: {
file: {
read: 'allow',
write: 'deny',
edit: 'deny',
delete: 'deny',
},
bash: {
enabled: false,
},
git: {
read: 'allow',
write: 'deny',
dangerous: 'deny',
},
},
maxSteps: 20,
};
+14
View File
@@ -0,0 +1,14 @@
import type { AgentInfo } from '../types.js';
/**
* 通用 Agent
* 适合复杂的多步骤任务、代码搜索和问题研究
*/
export const generalAgent: Omit<AgentInfo, 'name'> = {
description: '通用 Agent,适合复杂的多步骤任务、代码搜索和问题研究',
mode: 'subagent',
tools: {
noTask: true, // 禁止嵌套调用 Task
},
maxSteps: 15,
};
+33
View File
@@ -0,0 +1,33 @@
import type { AgentInfo } from '../types.js';
import { generalAgent } from './general.js';
import { exploreAgent } from './explore.js';
import { codeReviewerAgent } from './code-reviewer.js';
import { buildAgent } from './build.js';
import { planAgent } from './plan.js';
/**
* 预设 Agent 集合
*/
export const presetAgents: Record<string, Omit<AgentInfo, 'name'>> = {
general: generalAgent,
explore: exploreAgent,
'code-reviewer': codeReviewerAgent,
build: buildAgent,
plan: planAgent,
};
/**
* 获取所有预设 Agent 名称
*/
export function getPresetAgentNames(): string[] {
return Object.keys(presetAgents);
}
/**
* 检查是否为预设 Agent
*/
export function isPresetAgent(name: string): boolean {
return name in presetAgents;
}
export { generalAgent, exploreAgent, codeReviewerAgent, buildAgent, planAgent };
+82
View File
@@ -0,0 +1,82 @@
import type { AgentInfo } from '../types.js';
/**
* 计划 Agent
* 主模式,设计实现方案(不执行修改)
*/
export const planAgent: Omit<AgentInfo, 'name'> = {
description: '计划模式,设计实现方案(不执行修改)',
mode: 'primary',
prompt: `你是一个软件架构师。你的任务是设计实现方案,而不是直接执行。
工作流程:
1. 理解需求:分析用户的需求和目标
2. 调研现状:阅读相关代码,了解现有架构
3. 设计方案:提出详细的实现计划
4. 评估风险:识别潜在问题和挑战
规则:
- 可以读取代码来了解现状
- 只输出计划,不要实际执行修改
- 提供具体、可操作的步骤
输出格式:
## 需求分析
[对需求的理解]
## 现状分析
[相关代码的结构和设计]
## 实现方案
### 步骤 1: [标题]
- 目标: ...
- 涉及文件: ...
- 具体修改: ...
### 步骤 2: [标题]
...
## 风险评估
- [风险1]: [应对方案]
- [风险2]: [应对方案]
## 测试计划
- [测试项1]
- [测试项2]`,
tools: {
disabled: [
'write_file',
'edit_file',
'delete_file',
'move_file',
'copy_file',
'create_directory',
'bash',
'git_add',
'git_commit',
'git_push',
'git_checkout',
'git_stash',
],
},
permission: {
file: {
read: 'allow',
write: 'deny',
edit: 'deny',
delete: 'deny',
},
bash: {
enabled: false,
},
git: {
read: 'allow',
write: 'deny',
dangerous: 'deny',
},
},
model: {
temperature: 0.5,
},
maxSteps: 15,
};
+154
View File
@@ -0,0 +1,154 @@
import type { AgentInfo, AgentConfigFile, AgentMode } from './types.js';
import { presetAgents } from './presets/index.js';
import { loadAgentConfig } from './config-loader.js';
import { mergePermissions, SYSTEM_DEFAULT_PERMISSION } from './permission-merger.js';
/**
* Agent 注册表
* 管理所有 Agent 的注册、查询和配置合并
*/
export class AgentRegistry {
private agents: Map<string, AgentInfo> = new Map();
private globalConfig: AgentConfigFile['defaults'] | null = null;
private userConfig: AgentConfigFile | null = null;
private initialized = false;
/**
* 初始化 - 加载预设和用户配置
*/
async init(workdir: string): Promise<void> {
if (this.initialized) return;
// 1. 注册预设 Agent
for (const [name, agentConfig] of Object.entries(presetAgents)) {
this.agents.set(name, { ...agentConfig, name });
}
// 2. 加载用户配置
this.userConfig = await loadAgentConfig(workdir);
if (this.userConfig) {
this.globalConfig = this.userConfig.defaults ?? null;
// 注册用户自定义 Agent
if (this.userConfig.agents) {
for (const [name, config] of Object.entries(this.userConfig.agents)) {
this.agents.set(name, { ...config, name });
}
}
}
this.initialized = true;
}
/**
* 获取 Agent(应用权限合并)
*/
get(name: string): AgentInfo | undefined {
const agent = this.agents.get(name);
if (!agent) return undefined;
return this.applyGlobalConfig(agent);
}
/**
* 列出所有 Agent
*/
list(mode?: AgentMode): AgentInfo[] {
return [...this.agents.values()]
.filter((a) => !mode || a.mode === mode || a.mode === 'all')
.map((a) => this.applyGlobalConfig(a));
}
/**
* 列出可作为子 Agent 的 Agent(供 Task 工具使用)
*/
listSubagents(): AgentInfo[] {
return this.list().filter((a) => a.mode !== 'primary');
}
/**
* 动态注册 Agent(运行时)
*/
register(agent: AgentInfo): void {
this.agents.set(agent.name, agent);
}
/**
* 移除 Agent
*/
remove(name: string): boolean {
return this.agents.delete(name);
}
/**
* 检查 Agent 是否存在
*/
has(name: string): boolean {
return this.agents.has(name);
}
/**
* 获取 Agent 数量
*/
get size(): number {
return this.agents.size;
}
/**
* 获取所有 Agent 名称
*/
getNames(): string[] {
return [...this.agents.keys()];
}
/**
* 获取全局配置
*/
getGlobalConfig(): AgentConfigFile['defaults'] | null {
return this.globalConfig;
}
/**
* 应用全局配置到 Agent
*/
private applyGlobalConfig(agent: AgentInfo): AgentInfo {
// 合并 maxSteps
const maxSteps = agent.maxSteps ?? this.globalConfig?.maxSteps ?? 10;
// 合并模型配置
const model = {
...this.globalConfig?.model,
...agent.model,
};
// 合并权限配置
const permission = mergePermissions(
SYSTEM_DEFAULT_PERMISSION,
this.globalConfig?.permission,
agent.permission
);
return {
...agent,
maxSteps,
model: Object.keys(model).length > 0 ? model : undefined,
permission,
};
}
/**
* 生成 Task 工具的 Agent 描述(用于工具 description
*/
generateSubagentDescription(): string {
const subagents = this.listSubagents();
if (subagents.length === 0) {
return '当前没有可用的子 Agent';
}
const descriptions = subagents.map((a) => `- ${a.name}: ${a.description}`);
return `可用的 Agent:\n${descriptions.join('\n')}`;
}
}
// 导出单例
export const agentRegistry = new AgentRegistry();
+164
View File
@@ -0,0 +1,164 @@
import type { ProviderType } from '../types/index.js';
/**
* Agent 模式
* - primary: 主 Agent,用户直接使用
* - subagent: 子 Agent,由 Task 工具调用
* - all: 两种方式都可以
*/
export type AgentMode = 'primary' | 'subagent' | 'all';
/**
* 权限动作
*/
export type PermissionAction = 'allow' | 'deny' | 'ask';
/**
* 权限规则(支持通配符模式)
*/
export interface PermissionRule {
pattern: string;
action: PermissionAction;
}
/**
* Agent Bash 权限配置
*/
export interface AgentBashPermission {
/** 是否启用 bash */
enabled?: boolean;
/** 命令规则,按顺序匹配(支持通配符如 "git diff*", "rm -rf*" */
rules?: PermissionRule[];
/** 默认策略 */
default?: PermissionAction;
}
/**
* Agent 文件权限配置
*/
export interface AgentFilePermission {
read?: PermissionAction;
write?: PermissionAction;
edit?: PermissionAction;
delete?: PermissionAction;
/** 敏感路径规则 */
sensitivePaths?: PermissionRule[];
}
/**
* Agent Git 权限配置
*/
export interface AgentGitPermission {
/** 读操作(status, diff, log 等) */
read?: PermissionAction;
/** 写操作(add, commit, push 等) */
write?: PermissionAction;
/** 危险操作(force push, reset --hard 等) */
dangerous?: PermissionAction;
}
/**
* Agent 完整权限配置
*/
export interface AgentPermission {
file?: AgentFilePermission;
bash?: AgentBashPermission;
web?: PermissionAction;
git?: AgentGitPermission;
}
/**
* Agent 模型配置
*/
export interface AgentModelConfig {
/** Provider 类型 */
provider?: ProviderType;
/** 模型名称 */
model?: string;
/** 温度参数 */
temperature?: number;
/** Top P 参数 */
topP?: number;
/** 最大输出 tokens */
maxTokens?: number;
}
/**
* Agent 工具配置
*/
export interface AgentToolConfig {
/** 禁用的工具列表 */
disabled?: string[];
/** 启用的工具列表(如果设置,则只启用这些) */
enabled?: string[];
/** 禁止嵌套 Task */
noTask?: boolean;
}
/**
* Agent 定义
*/
export interface AgentInfo {
/** Agent 名称 */
name: string;
/** Agent 描述 */
description: string;
/** Agent 模式 */
mode: AgentMode;
/** 自定义 System Prompt */
prompt?: string;
/** 模型配置 */
model?: AgentModelConfig;
/** 工具配置 */
tools?: AgentToolConfig;
/** 权限配置 */
permission?: AgentPermission;
/** 最大执行步数 */
maxSteps?: number;
}
/**
* Agent 配置文件格式(用户自定义)
*/
export interface AgentConfigFile {
/** 全局默认配置 */
defaults?: {
maxSteps?: number;
model?: AgentModelConfig;
permission?: AgentPermission;
};
/** Agent 定义 */
agents?: Record<string, Omit<AgentInfo, 'name'>>;
}
/**
* Agent 执行上下文
*/
export interface AgentExecutionContext {
/** 父会话 ID */
parentSessionId?: string;
/** 工作目录 */
workdir: string;
/** 回调:输出流 */
onStream?: (text: string) => void;
/** 回调:工具调用 */
onToolCall?: (toolName: string, params: Record<string, unknown>) => void;
/** 回调:工具结果 */
onToolResult?: (toolName: string, result: unknown) => void;
}
/**
* Agent 执行结果
*/
export interface AgentExecutionResult {
/** 是否成功 */
success: boolean;
/** 输出文本 */
text: string;
/** 执行步数 */
steps: number;
/** 会话 ID */
sessionId: string;
/** 错误信息 */
error?: string;
}
+9 -1
View File
@@ -4,9 +4,10 @@ import { Command } from 'commander';
import { Agent } from './core/agent.js'; import { Agent } from './core/agent.js';
import { TerminalUI } from './ui/terminal.js'; import { TerminalUI } from './ui/terminal.js';
import { loadConfig, initConfig } from './utils/config.js'; import { loadConfig, initConfig } from './utils/config.js';
import { toolRegistry, todoManager } from './tools/index.js'; import { toolRegistry, todoManager, initTaskContext, updateTaskDescription } from './tools/index.js';
import { getPermissionManager, promptPermission } from './permission/index.js'; import { getPermissionManager, promptPermission } from './permission/index.js';
import { SessionManager } from './session/index.js'; import { SessionManager } from './session/index.js';
import { agentRegistry } from './agent/index.js';
import { initLSP, shutdownLSP } from './lsp/index.js'; import { initLSP, shutdownLSP } from './lsp/index.js';
import { import {
printServerList, printServerList,
@@ -119,6 +120,13 @@ program.action(async () => {
// 初始化 todoManager(让 todo 工具可以访问会话) // 初始化 todoManager(让 todo 工具可以访问会话)
todoManager.setSessionManager(sessionManager); todoManager.setSessionManager(sessionManager);
// 初始化 Agent 注册表(加载预设和用户配置)
await agentRegistry.init(process.cwd());
// 初始化 Task 工具上下文
initTaskContext(config, sessionManager);
updateTaskDescription();
// 显示会话恢复信息 // 显示会话恢复信息
const session = sessionManager.getSession(); const session = sessionManager.getSession();
if (session && session.messages.length > 0) { if (session && session.messages.length > 0) {
+37
View File
@@ -145,6 +145,43 @@ export class SessionManager {
return this.currentSession; return this.currentSession;
} }
/**
* 创建子会话(用于 Task 工具)
* @param parentId 父会话 ID
* @param agentName 关联的 Agent 名称
* @param title 会话标题
*/
createChildSession(parentId: string, agentName: string, title?: string): SessionData {
const workdir = this.currentSession?.workdir || process.cwd();
const childSession: SessionData = {
id: this.storage.generateSessionId(),
parentId,
agentName,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
workdir,
title: title || `子任务 (@${agentName})`,
messages: [],
discoveredTools: [],
todos: [],
};
return childSession;
}
/**
* 保存子会话
*/
async saveChildSession(session: SessionData): Promise<void> {
await this.storage.saveSession(session);
}
/**
* 获取当前会话 ID
*/
getSessionId(): string | undefined {
return this.currentSession?.id;
}
/** /**
* 恢复指定会话 * 恢复指定会话
*/ */
+10
View File
@@ -145,6 +145,16 @@ export class SessionStorage {
} }
} }
/**
* 保存指定会话(用于子会话)
*/
async saveSession(session: SessionData): Promise<void> {
await this.ensureDir();
session.updatedAt = new Date().toISOString();
const filePath = path.join(this.sessionsDir, `${session.id}.json`);
await fs.writeFile(filePath, JSON.stringify(session, null, 2), 'utf-8');
}
/** /**
* 删除指定会话 * 删除指定会话
*/ */
+4
View File
@@ -22,6 +22,10 @@ export interface Todo {
export interface SessionData { export interface SessionData {
/** 会话 ID */ /** 会话 ID */
id: string; id: string;
/** 父会话 ID(子会话时存在) */
parentId?: string;
/** 关联的 Agent 名称(子会话时存在) */
agentName?: string;
/** 创建时间 */ /** 创建时间 */
createdAt: string; createdAt: string;
/** 最后更新时间 */ /** 最后更新时间 */
+5
View File
@@ -8,6 +8,9 @@ import { bashTool } from './shell/index.js';
import { toolSearchTool } from './tool-search.js'; import { toolSearchTool } from './tool-search.js';
import { todoReadTool, todoWriteTool } from './todo/index.js'; import { todoReadTool, todoWriteTool } from './todo/index.js';
// Task 工具(Agent 子任务)
import { taskTool } from './task/index.js';
// 文件系统工具 // 文件系统工具
import { import {
readFileTool, readFileTool,
@@ -47,6 +50,7 @@ const allToolsWithMetadata: ToolWithMetadata[] = [
bashTool, bashTool,
todoReadTool, todoReadTool,
todoWriteTool, todoWriteTool,
taskTool,
// 文件系统工具 (deferLoading: true) // 文件系统工具 (deferLoading: true)
readFileTool, readFileTool,
@@ -85,6 +89,7 @@ toolRegistry.registerAll(allToolsWithMetadata);
export { toolRegistry } from './registry.js'; export { toolRegistry } from './registry.js';
export { toolSearchTool } from './tool-search.js'; export { toolSearchTool } from './tool-search.js';
export { todoManager } from './todo/index.js'; export { todoManager } from './todo/index.js';
export { initTaskContext, updateTaskDescription } from './task/index.js';
export type { ToolWithMetadata, ToolMetadata, ToolCategory, ToolSearchResult } from './types.js'; export type { ToolWithMetadata, ToolMetadata, ToolCategory, ToolSearchResult } from './types.js';
// 兼容旧代码:导出所有工具数组(基础 Tool 类型) // 兼容旧代码:导出所有工具数组(基础 Tool 类型)
+1
View File
@@ -0,0 +1 @@
export { taskTool, initTaskContext, updateTaskDescription } from './task.js';
+171
View File
@@ -0,0 +1,171 @@
import type { ToolWithMetadata } from '../types.js';
import type { AgentConfig } from '../../types/index.js';
import { agentRegistry, AgentExecutor } from '../../agent/index.js';
import { toolRegistry } from '../registry.js';
import { SessionManager } from '../../session/index.js';
// Task 工具上下文(运行时注入)
let taskContext: {
baseConfig: AgentConfig;
sessionManager: SessionManager;
} | null = null;
/**
* 初始化 Task 工具上下文
*/
export function initTaskContext(
baseConfig: AgentConfig,
sessionManager: SessionManager
): void {
taskContext = { baseConfig, sessionManager };
}
/**
* 获取 Task 工具动态描述
*/
function getTaskDescription(): string {
const subagents = agentRegistry.listSubagents();
if (subagents.length === 0) {
return '执行子任务(当前没有可用的子 Agent)';
}
const agentList = subagents
.map((a) => `- ${a.name}: ${a.description}`)
.join('\n');
return `执行子任务,委派给专门的 Agent 处理。
可用的 Agent:
${agentList}
使用示例:
- 使用 explore Agent 搜索代码: task({ subagent_type: "explore", prompt: "找到所有 API 路由定义" })
- 使用 code-reviewer Agent 审查: task({ subagent_type: "code-reviewer", prompt: "审查 src/auth 目录的代码" })
- 使用 general Agent 执行复杂任务: task({ subagent_type: "general", prompt: "分析并重构这个函数" })`;
}
/**
* Task 工具
* 用于创建子任务,委派给指定的 Agent 处理
*/
export const taskTool: ToolWithMetadata = {
name: 'task',
description: getTaskDescription(),
parameters: {
description: {
type: 'string',
description: '任务简短描述(3-5 个词,用于标识任务)',
required: true,
},
prompt: {
type: 'string',
description: '详细的任务说明,包括目标、范围和期望输出',
required: true,
},
subagent_type: {
type: 'string',
description: '子 Agent 类型,可选: general, explore, code-reviewer',
required: true,
},
},
metadata: {
name: 'task',
category: 'agent',
description: '执行子任务,委派给专门的 Agent',
keywords: ['task', 'agent', 'subagent', '子任务', '委派', '探索', '审查'],
deferLoading: false, // 核心工具,始终加载
},
async execute(params) {
const { description, prompt, subagent_type } = params as {
description: string;
prompt: string;
subagent_type: string;
};
// 检查上下文是否已初始化
if (!taskContext) {
return {
success: false,
output: '',
error: 'Task 工具未初始化,请确保正确设置上下文',
};
}
const { baseConfig, sessionManager } = taskContext;
// 1. 获取 Agent 配置
const agent = agentRegistry.get(subagent_type);
if (!agent) {
const availableAgents = agentRegistry.listSubagents().map((a) => a.name);
return {
success: false,
output: '',
error: `未找到 Agent: ${subagent_type}。可用的 Agent: ${availableAgents.join(', ')}`,
};
}
// 检查是否为 primary 模式
if (agent.mode === 'primary') {
return {
success: false,
output: '',
error: `Agent "${subagent_type}" 是 primary 模式,不能作为子任务调用`,
};
}
// 2. 创建子会话
const parentSessionId = sessionManager.getSessionId() || 'standalone';
const childSession = sessionManager.createChildSession(
parentSessionId,
agent.name,
`${description} (@${agent.name})`
);
// 3. 创建执行器
const executor = new AgentExecutor(agent, baseConfig, toolRegistry);
// 4. 执行任务
const result = await executor.execute(prompt, {
parentSessionId,
workdir: process.cwd(),
onStream: undefined, // 子任务不使用流式输出
});
// 5. 保存子会话
childSession.messages = [
{ role: 'user', content: prompt },
{ role: 'assistant', content: result.text },
];
await sessionManager.saveChildSession(childSession);
if (result.success) {
return {
success: true,
output: result.text,
metadata: {
agent: agent.name,
sessionId: childSession.id,
steps: result.steps,
},
};
} else {
return {
success: false,
output: '',
error: result.error || '子任务执行失败',
metadata: {
agent: agent.name,
sessionId: childSession.id,
steps: result.steps,
},
};
}
},
};
/**
* 更新 Task 工具描述(Agent 注册表初始化后调用)
*/
export function updateTaskDescription(): void {
(taskTool as { description: string }).description = getTaskDescription();
}
+1 -1
View File
@@ -1,7 +1,7 @@
import type { ToolParameter, ToolResult } from '../types/index.js'; import type { ToolParameter, ToolResult } from '../types/index.js';
// 工具类别 // 工具类别
export type ToolCategory = 'core' | 'filesystem' | 'shell' | 'git' | 'web' | 'database'; export type ToolCategory = 'core' | 'filesystem' | 'shell' | 'git' | 'web' | 'database' | 'agent';
// 工具元数据 // 工具元数据
export interface ToolMetadata { export interface ToolMetadata {