Files
metona-ai-desktop/electron/harness/orchestration/orchestrator.ts
T
thzxx 6f08759f63 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正确填充
2026-07-06 22:48:33 +08:00

285 lines
9.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Task Orchestrator — 任务编排器
*
* 支持父子委派模式:主 Agent 委派子任务给 SubAgent。
* 每个 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 { 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 { PreToolHook } from '../hooks/pre-tool';
import type { PostToolHook } from '../hooks/post-tool';
import log from 'electron-log';
export interface SubAgentResult {
taskId: string;
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;
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 mainEngine: AgentLoopEngine,
private toolRegistry?: ToolRegistry,
private preToolHooks: PreToolHook[] = [],
private postToolHooks: PostToolHook[] = [],
private defaultConfig?: Partial<AgentLoopConfig>,
) {
super();
}
/**
* 委派子任务
*
* 创建一个独立的 AgentLoopEngine 实例执行子任务。
* SubAgent 不共享主 Agent 的引擎状态,安全隔离。
*/
async delegate(params: {
taskId?: string;
description: string;
parentSessionId: string;
maxIterations?: number;
tools?: string[];
}): Promise<SubAgentResult> {
const taskId = params.taskId ?? `sub_${nanoid(8)}`;
const startMs = Date.now();
// ===== 递归深度检查 =====
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);
},
getStatus: () => ({ taskId, status: handle.status, description: handle.description, depth: handle.depth }),
};
this.activeSubAgents.set(taskId, handle);
this.emit('taskStarted', { taskId, depth });
try {
// 构建用户消息
const userMessage: MetonaMessage = {
role: 'user',
content: params.description,
timestamp: Date.now(),
};
// 构建 System Prompt(子 Agent 专用)
const systemPrompt = this.buildSubAgentPrompt(params.description, depth);
// 运行 Agent Loop(同步等待完成)
const output = await subEngine.runStream(
userMessage,
taskId,
[], // 子 Agent 无历史
systemPrompt,
);
const durationMs = Date.now() - startMs;
const success = output.terminationReason === 'completed';
const result: SubAgentResult = {
taskId,
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}" (depth=${depth}) ${success ? 'completed' : 'failed'} in ${durationMs}ms, ${output.iterations.length} iterations`);
return result;
} catch (error) {
const durationMs = Date.now() - startMs;
const errMsg = (error as Error).message;
const result: SubAgentResult = {
taskId,
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}" (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, iterations: 0 };
this.activeSubAgents.delete(taskId);
this.emit('taskCompleted', handle.result);
}
}
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();
}
}