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:
Generated
+27
@@ -16,6 +16,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"inquirer": "^12.0.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"ora": "^8.1.0",
|
||||
"tree-sitter-bash": "^0.25.1",
|
||||
"vscode-jsonrpc": "^8.2.1",
|
||||
@@ -27,6 +28,7 @@
|
||||
"ai-assist": "dist/index.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^22.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.6.0"
|
||||
@@ -912,6 +914,13 @@
|
||||
"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": {
|
||||
"version": "22.19.2",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
@@ -1540,6 +1555,18 @@
|
||||
"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": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^12.1.0",
|
||||
"inquirer": "^12.0.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"ora": "^8.1.0",
|
||||
"tree-sitter-bash": "^0.25.1",
|
||||
"vscode-jsonrpc": "^8.2.1",
|
||||
@@ -38,6 +39,7 @@
|
||||
"zod": "^4.1.13"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^22.0.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "^5.6.0"
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import type { AgentInfo } from '../types.js';
|
||||
|
||||
/**
|
||||
* 构建 Agent
|
||||
* 主模式,拥有完整权限执行编码任务
|
||||
*/
|
||||
export const buildAgent: Omit<AgentInfo, 'name'> = {
|
||||
description: '构建模式,拥有完整权限执行编码任务',
|
||||
mode: 'primary',
|
||||
maxSteps: 30,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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 };
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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();
|
||||
@@ -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
@@ -4,9 +4,10 @@ import { Command } from 'commander';
|
||||
import { Agent } from './core/agent.js';
|
||||
import { TerminalUI } from './ui/terminal.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 { SessionManager } from './session/index.js';
|
||||
import { agentRegistry } from './agent/index.js';
|
||||
import { initLSP, shutdownLSP } from './lsp/index.js';
|
||||
import {
|
||||
printServerList,
|
||||
@@ -119,6 +120,13 @@ program.action(async () => {
|
||||
// 初始化 todoManager(让 todo 工具可以访问会话)
|
||||
todoManager.setSessionManager(sessionManager);
|
||||
|
||||
// 初始化 Agent 注册表(加载预设和用户配置)
|
||||
await agentRegistry.init(process.cwd());
|
||||
|
||||
// 初始化 Task 工具上下文
|
||||
initTaskContext(config, sessionManager);
|
||||
updateTaskDescription();
|
||||
|
||||
// 显示会话恢复信息
|
||||
const session = sessionManager.getSession();
|
||||
if (session && session.messages.length > 0) {
|
||||
|
||||
@@ -145,6 +145,43 @@ export class SessionManager {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复指定会话
|
||||
*/
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定会话
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,10 @@ export interface Todo {
|
||||
export interface SessionData {
|
||||
/** 会话 ID */
|
||||
id: string;
|
||||
/** 父会话 ID(子会话时存在) */
|
||||
parentId?: string;
|
||||
/** 关联的 Agent 名称(子会话时存在) */
|
||||
agentName?: string;
|
||||
/** 创建时间 */
|
||||
createdAt: string;
|
||||
/** 最后更新时间 */
|
||||
|
||||
@@ -8,6 +8,9 @@ import { bashTool } from './shell/index.js';
|
||||
import { toolSearchTool } from './tool-search.js';
|
||||
import { todoReadTool, todoWriteTool } from './todo/index.js';
|
||||
|
||||
// Task 工具(Agent 子任务)
|
||||
import { taskTool } from './task/index.js';
|
||||
|
||||
// 文件系统工具
|
||||
import {
|
||||
readFileTool,
|
||||
@@ -47,6 +50,7 @@ const allToolsWithMetadata: ToolWithMetadata[] = [
|
||||
bashTool,
|
||||
todoReadTool,
|
||||
todoWriteTool,
|
||||
taskTool,
|
||||
|
||||
// 文件系统工具 (deferLoading: true)
|
||||
readFileTool,
|
||||
@@ -85,6 +89,7 @@ toolRegistry.registerAll(allToolsWithMetadata);
|
||||
export { toolRegistry } from './registry.js';
|
||||
export { toolSearchTool } from './tool-search.js';
|
||||
export { todoManager } from './todo/index.js';
|
||||
export { initTaskContext, updateTaskDescription } from './task/index.js';
|
||||
export type { ToolWithMetadata, ToolMetadata, ToolCategory, ToolSearchResult } from './types.js';
|
||||
|
||||
// 兼容旧代码:导出所有工具数组(基础 Tool 类型)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { taskTool, initTaskContext, updateTaskDescription } from './task.js';
|
||||
@@ -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
@@ -1,7 +1,7 @@
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user