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",
|
"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",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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 { 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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 恢复指定会话
|
* 恢复指定会话
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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 {
|
export interface SessionData {
|
||||||
/** 会话 ID */
|
/** 会话 ID */
|
||||||
id: string;
|
id: string;
|
||||||
|
/** 父会话 ID(子会话时存在) */
|
||||||
|
parentId?: string;
|
||||||
|
/** 关联的 Agent 名称(子会话时存在) */
|
||||||
|
agentName?: string;
|
||||||
/** 创建时间 */
|
/** 创建时间 */
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** 最后更新时间 */
|
/** 最后更新时间 */
|
||||||
|
|||||||
@@ -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 类型)
|
||||||
|
|||||||
@@ -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';
|
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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user