v0.1.2: 全项目代码审查修复 + 子代理编排器 + 上下文压缩 + 并行工具执行

后端修复: Ollama adapter 移除死代码/修复超时硬编码/iteration硬编码/pullModel无超时/流异常断开补发DONE; SSE解析器支持非字符串arguments; Sandbox fail-closed安全加固; IPC移除未使用变量

新增功能: TaskOrchestrator子任务编排器; DelegateTaskTool委派工具; Agent Loop并行工具执行; 上下文自动压缩; 记忆检索注入System Prompt

前端修复: useAgentStream text_delta thought累积bug; 工具调用状态正确流转; 修复重复stateChange事件; TraceStep.thought正确填充
This commit is contained in:
thzxx
2026-07-06 22:48:33 +08:00
parent 8cdb93f8bf
commit 6f08759f63
25 changed files with 1044 additions and 675 deletions
+152 -63
View File
@@ -2,17 +2,26 @@
* Task Orchestrator — 任务编排器
*
* 支持父子委派模式:主 Agent 委派子任务给 SubAgent。
* 每个子任务运行一个独立的 AgentLoopEngine 实例。
* 每个 SubAgent 运行在**独立的 AgentLoopEngine 实例**中,避免状态污染
*
* 安全保障:
* 1. 独立引擎实例 — SubAgent 不共享主 Agent 的引擎状态
* 2. 递归深度限制 — 默认最大 3 层,防止无限递归
* 3. 工具白名单隔离 — SubAgent 默认不继承 delegate_task(防止递归)
* 4. 真正的 abort — 通过引擎引用调用 engine.abort()
* 5. 事件隔离 — SubAgent 的流式事件不直接转发到前端,仅通过 orchestrator 事件通知
*
* @see docs/生产级通用 AI Agent 智能体桌面应用:完整设计与构建指南.html — 第五章
*/
import { EventEmitter } from 'events';
import { nanoid } from 'nanoid';
import type { AgentLoopEngine } from '../agent-loop/engine';
import type { MetonaMessage, MetonaSystemPrompt } from '../types';
import { AgentLoopEngine } from '../agent-loop/engine';
import type { AgentLoopConfig } from '../agent-loop/types';
import type { MetonaMessage, MetonaSystemPrompt, MetonaToolDef } from '../types';
import type { ToolRegistry } from '../tools/registry';
import type { MetonaToolDef } from '../types';
import type { PreToolHook } from '../hooks/pre-tool';
import type { PostToolHook } from '../hooks/post-tool';
import log from 'electron-log';
export interface SubAgentResult {
@@ -20,25 +29,34 @@ export interface SubAgentResult {
result: string;
success: boolean;
durationMs: number;
iterations: number;
}
interface SubAgentHandle {
taskId: string;
description: string;
status: 'pending' | 'running' | 'completed' | 'error';
depth: number;
engine?: AgentLoopEngine;
result?: SubAgentResult;
abort: () => void;
onComplete: (callback: (result: SubAgentResult) => void) => void;
onError: (callback: (error: Error) => void) => void;
getStatus: () => { taskId: string; status: string; description: string };
getStatus: () => { taskId: string; status: string; description: string; depth: number };
}
/** 默认递归深度限制 */
const MAX_DELEGATION_DEPTH = 3;
export class TaskOrchestrator extends EventEmitter {
private activeSubAgents = new Map<string, SubAgentHandle>();
/** 追踪每个 session 的当前委派深度 */
private sessionDepth = new Map<string, number>();
constructor(
private agentLoopEngine: AgentLoopEngine,
private mainEngine: AgentLoopEngine,
private toolRegistry?: ToolRegistry,
private preToolHooks: PreToolHook[] = [],
private postToolHooks: PostToolHook[] = [],
private defaultConfig?: Partial<AgentLoopConfig>,
) {
super();
}
@@ -46,8 +64,8 @@ export class TaskOrchestrator extends EventEmitter {
/**
* 委派子任务
*
* 创建一个独立的 SubAgent 执行上下文,调用 AgentLoopEngine 完成任务。
* 支持限制可用工具列表(白名单)
* 创建一个独立的 AgentLoopEngine 实例执行子任务。
* SubAgent 不共享主 Agent 的引擎状态,安全隔离
*/
async delegate(params: {
taskId?: string;
@@ -59,25 +77,60 @@ export class TaskOrchestrator extends EventEmitter {
const taskId = params.taskId ?? `sub_${nanoid(8)}`;
const startMs = Date.now();
this.emit('taskDelegated', { taskId, description: params.description, parentSessionId: params.parentSessionId });
// ===== 递归深度检查 =====
const currentDepth = this.sessionDepth.get(params.parentSessionId) ?? 0;
if (currentDepth >= MAX_DELEGATION_DEPTH) {
log.warn(`[Orchestrator] Delegation depth limit reached (${currentDepth}) for session ${params.parentSessionId}`);
return {
taskId,
result: `SubAgent delegation depth limit reached (${MAX_DELEGATION_DEPTH}). Cannot delegate further.`,
success: false,
durationMs: 0,
iterations: 0,
};
}
const depth = currentDepth + 1;
this.sessionDepth.set(params.parentSessionId, depth);
this.emit('taskDelegated', { taskId, description: params.description, parentSessionId: params.parentSessionId, depth });
// ===== 创建独立的引擎实例 =====
const subEngine = new AgentLoopEngine(
{
maxIterations: params.maxIterations ?? 10,
totalTimeoutMs: 300_000, // 子任务总超时 5 分钟
thinkingEnabled: this.defaultConfig?.thinkingEnabled ?? true,
thinkingEffort: this.defaultConfig?.thinkingEffort ?? 'medium',
contextLength: this.defaultConfig?.contextLength,
contextWindow: this.defaultConfig?.contextWindow ?? 128_000,
},
this.mainEngine.getAdapter(),
this.toolRegistry,
this.preToolHooks,
this.postToolHooks,
);
subEngine.setWorkspacePath(this.mainEngine.getWorkspacePath());
// ===== 工具白名单设置 =====
const allowedTools = this.resolveTools(params.tools);
subEngine.setTools(allowedTools);
const handle: SubAgentHandle = {
taskId,
description: params.description,
status: 'running',
depth,
engine: subEngine,
abort: () => {
subEngine.abort();
handle.status = 'error';
this.activeSubAgents.delete(taskId);
},
onComplete: (callback) => {
if (handle.result) callback(handle.result);
},
onError: (callback) => { /* errors captured by try/catch below */ },
getStatus: () => ({ taskId, status: handle.status, description: handle.description }),
getStatus: () => ({ taskId, status: handle.status, description: handle.description, depth: handle.depth }),
};
this.activeSubAgents.set(taskId, handle);
this.emit('taskStarted', { taskId });
this.emit('taskStarted', { taskId, depth });
try {
// 构建用户消息
@@ -87,49 +140,16 @@ export class TaskOrchestrator extends EventEmitter {
timestamp: Date.now(),
};
// 构建 System Prompt(子 Agent 简化版
const systemPrompt: MetonaSystemPrompt = {
roleDefinition: '你是一个子任务执行 Agent,负责完成被委派的单一任务。',
outputConstraints: '用中文回答,简洁准确地完成任务。',
safetyGuidelines: '不访问工作空间外的文件,不执行危险命令。',
};
// 保存原始工具列表(子任务完成后恢复)
const savedTools = this.agentLoopEngine.getTools?.() ?? [];
// 如果指定了工具白名单,设置受限工具集
if (params.tools && params.tools.length > 0 && this.toolRegistry) {
const allowedTools: MetonaToolDef[] = [];
for (const toolName of params.tools) {
const tool = this.toolRegistry.get(toolName);
if (tool) {
allowedTools.push({
name: tool.definition.name,
description: tool.definition.description,
parameters: tool.definition.parameters,
category: tool.definition.category,
riskLevel: tool.definition.riskLevel,
requiresPermission: tool.definition.requiresPermission,
timeoutMs: tool.definition.timeoutMs,
});
}
}
this.agentLoopEngine.setTools(allowedTools);
}
// 构建 System Prompt(子 Agent 专用
const systemPrompt = this.buildSubAgentPrompt(params.description, depth);
// 运行 Agent Loop(同步等待完成)
let output;
try {
output = await this.agentLoopEngine.runStream(
userMessage,
taskId,
[], // 子 Agent 无历史
systemPrompt,
);
} finally {
// 恢复主 Agent 的原始工具列表
this.agentLoopEngine.setTools(savedTools);
}
const output = await subEngine.runStream(
userMessage,
taskId,
[], // 子 Agent 无历史
systemPrompt,
);
const durationMs = Date.now() - startMs;
const success = output.terminationReason === 'completed';
@@ -138,14 +158,16 @@ export class TaskOrchestrator extends EventEmitter {
result: output.finalAnswer,
success,
durationMs,
iterations: output.iterations.length,
};
handle.status = success ? 'completed' : 'error';
handle.result = result;
this.activeSubAgents.delete(taskId);
this.sessionDepth.set(params.parentSessionId, currentDepth); // 恢复深度
this.emit('taskCompleted', result);
log.info(`[Orchestrator] SubAgent "${taskId}" ${success ? 'completed' : 'failed'} in ${durationMs}ms`);
log.info(`[Orchestrator] SubAgent "${taskId}" (depth=${depth}) ${success ? 'completed' : 'failed'} in ${durationMs}ms, ${output.iterations.length} iterations`);
return result;
} catch (error) {
@@ -156,40 +178,107 @@ export class TaskOrchestrator extends EventEmitter {
result: errMsg,
success: false,
durationMs,
iterations: 0,
};
handle.status = 'error';
handle.result = result;
this.activeSubAgents.delete(taskId);
this.sessionDepth.set(params.parentSessionId, currentDepth); // 恢复深度
this.emit('taskError', { taskId, error: errMsg });
log.error(`[Orchestrator] SubAgent "${taskId}" error: ${errMsg}`);
log.error(`[Orchestrator] SubAgent "${taskId}" (depth=${depth}) error: ${errMsg}`);
return result;
}
}
/**
* 完成子任务(外部触发)
* 解析工具白名单
*
* - 如果指定了 tools,使用白名单(自动排除 delegate_task 防止递归)
* - 如果未指定,使用所有已启用工具(同样排除 delegate_task
*/
private resolveTools(toolNames?: string[]): MetonaToolDef[] {
if (!this.toolRegistry) return [];
// 始终排除 delegate_task 防止递归(除非深度为 1 且显式要求)
const EXCLUDE_TOOLS = new Set(['delegate_task']);
if (toolNames && toolNames.length > 0) {
// 使用白名单模式
const resolved: MetonaToolDef[] = [];
const notFound: string[] = [];
for (const name of toolNames) {
if (EXCLUDE_TOOLS.has(name)) continue; // 静默排除
const tool = this.toolRegistry.get(name);
if (tool) {
resolved.push(tool.definition);
} else {
notFound.push(name);
}
}
if (notFound.length > 0) {
log.warn(`[Orchestrator] Tools not found: ${notFound.join(', ')}`);
}
return resolved;
}
// 未指定白名单 — 使用所有已启用工具(排除 delegate_task
return this.toolRegistry.listTools().filter((t) => !EXCLUDE_TOOLS.has(t.name));
}
/**
* 构建 SubAgent 的 System Prompt
*/
private buildSubAgentPrompt(description: string, depth: number): MetonaSystemPrompt {
return {
roleDefinition: `You are a SubAgent (delegation depth: ${depth}) executing a specific sub-task delegated by the parent Agent.\nYour goal is to complete the assigned task efficiently and return a clear, concise result.\nFocus only on the task at hand. Do not delegate further.`,
outputConstraints: `Complete the task and provide a clear summary of your findings or actions.\nRespond in the same language as the task description.\nKeep your response focused and relevant — the parent Agent will use your result to continue its work.`,
safetyGuidelines: `Do not access files outside the workspace.\nDo not execute dangerous commands.\nIf the task cannot be completed, explain why clearly.`,
};
}
/**
* 中断指定子任务
*/
abortTask(taskId: string): boolean {
const handle = this.activeSubAgents.get(taskId);
if (handle && handle.status === 'running') {
handle.abort();
return true;
}
return false;
}
/**
* 完成子任务(外部触发,保留接口兼容)
*/
completeTask(taskId: string, result: string, success: boolean): void {
const handle = this.activeSubAgents.get(taskId);
if (handle && handle.status === 'running') {
handle.status = success ? 'completed' : 'error';
handle.result = { taskId, result, success, durationMs: 0 };
handle.result = { taskId, result, success, durationMs: 0, iterations: 0 };
this.activeSubAgents.delete(taskId);
this.emit('taskCompleted', handle.result);
}
}
getActiveAgentsStatus(): Array<{ taskId: string; status: string; description: string }> {
getActiveAgentsStatus(): Array<{ taskId: string; status: string; description: string; depth: number }> {
return Array.from(this.activeSubAgents.values()).map((a) => a.getStatus());
}
/**
* 中断所有子任务
*/
abortAll(): void {
for (const agent of this.activeSubAgents.values()) {
agent.abort();
}
this.activeSubAgents.clear();
this.sessionDepth.clear();
}
}