**崩溃/挂死修复 (5):** - 统一 TrayManager.isQuitting 变量,修复 Cmd+Q 无法退出 - useAgentStream 闭包过期快照 → 每次 getState() - Agnes chatStream 添加 AbortSignal.timeout - SSE JSON.parse 添加 try-catch 保护 - Orchestrator setTools 污染 → save/restore 模式 **功能修复 (14):** - 上下文压缩实现 (每5轮 COMPRESSING 状态) - 修复 requestId 硬编码空串 - ConfigService.set() 保留已有 category - MemoryManager 新增 working 类型搜索 - PromptInjectionDefender 补全 sanitize() - Ollama: 补全 dynamicReminders + reasoningContent - openai-format: 所有 assistant 消息保留 reasoningContent - SSE: finish_reason 时提前 flush tool_calls - DeepSeek thinking effort 映射注释 - Ollama done_reason load→stop - RateLimitHook >= 边界修复 - WorkspaceService isValid 首次启动修复 - sessions:archive IPC handler - 托盘/窗口图标路径生产环境修复 **系统提示词优化:** - SOUL.md 存在时不显示兜底身份,原文放最前 - 兜底身份改为中文 (MetonaAI 自身描述) - 用户文本在前,附件内容在后 **文件上传:** - 非图片文件不再 base64 编码,保留 JSON 结构 - 用户文本优先于文件内容 **UI 修复:** - 首页 Logo 路径修复 (public/ + 相对路径) - TokenUsage contextWindow 动态计算 (Provider 感知) - 切换 Provider 同步 contextWindow - 托盘图标始终显示 Logo (状态由右键菜单展示)
196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
/**
|
|
* Task Orchestrator — 任务编排器
|
|
*
|
|
* 支持父子委派模式:主 Agent 委派子任务给 SubAgent。
|
|
* 每个子任务运行一个独立的 AgentLoopEngine 实例。
|
|
*
|
|
* @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 type { ToolRegistry } from '../tools/registry';
|
|
import type { MetonaToolDef } from '../types';
|
|
import log from 'electron-log';
|
|
|
|
export interface SubAgentResult {
|
|
taskId: string;
|
|
result: string;
|
|
success: boolean;
|
|
durationMs: number;
|
|
}
|
|
|
|
interface SubAgentHandle {
|
|
taskId: string;
|
|
description: string;
|
|
status: 'pending' | 'running' | 'completed' | 'error';
|
|
result?: SubAgentResult;
|
|
abort: () => void;
|
|
onComplete: (callback: (result: SubAgentResult) => void) => void;
|
|
onError: (callback: (error: Error) => void) => void;
|
|
getStatus: () => { taskId: string; status: string; description: string };
|
|
}
|
|
|
|
export class TaskOrchestrator extends EventEmitter {
|
|
private activeSubAgents = new Map<string, SubAgentHandle>();
|
|
|
|
constructor(
|
|
private agentLoopEngine: AgentLoopEngine,
|
|
private toolRegistry?: ToolRegistry,
|
|
) {
|
|
super();
|
|
}
|
|
|
|
/**
|
|
* 委派子任务
|
|
*
|
|
* 创建一个独立的 SubAgent 执行上下文,调用 AgentLoopEngine 完成任务。
|
|
* 支持限制可用工具列表(白名单)。
|
|
*/
|
|
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();
|
|
|
|
this.emit('taskDelegated', { taskId, description: params.description, parentSessionId: params.parentSessionId });
|
|
|
|
const handle: SubAgentHandle = {
|
|
taskId,
|
|
description: params.description,
|
|
status: 'running',
|
|
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 }),
|
|
};
|
|
|
|
this.activeSubAgents.set(taskId, handle);
|
|
this.emit('taskStarted', { taskId });
|
|
|
|
try {
|
|
// 构建用户消息
|
|
const userMessage: MetonaMessage = {
|
|
role: 'user',
|
|
content: params.description,
|
|
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);
|
|
}
|
|
|
|
// 运行 Agent Loop(同步等待完成)
|
|
let output;
|
|
try {
|
|
output = await this.agentLoopEngine.runStream(
|
|
userMessage,
|
|
taskId,
|
|
[], // 子 Agent 无历史
|
|
systemPrompt,
|
|
);
|
|
} finally {
|
|
// 恢复主 Agent 的原始工具列表
|
|
this.agentLoopEngine.setTools(savedTools);
|
|
}
|
|
|
|
const durationMs = Date.now() - startMs;
|
|
const success = output.terminationReason === 'completed';
|
|
const result: SubAgentResult = {
|
|
taskId,
|
|
result: output.finalAnswer,
|
|
success,
|
|
durationMs,
|
|
};
|
|
|
|
handle.status = success ? 'completed' : 'error';
|
|
handle.result = result;
|
|
this.activeSubAgents.delete(taskId);
|
|
this.emit('taskCompleted', result);
|
|
|
|
log.info(`[Orchestrator] SubAgent "${taskId}" ${success ? 'completed' : 'failed'} in ${durationMs}ms`);
|
|
|
|
return result;
|
|
} catch (error) {
|
|
const durationMs = Date.now() - startMs;
|
|
const errMsg = (error as Error).message;
|
|
const result: SubAgentResult = {
|
|
taskId,
|
|
result: errMsg,
|
|
success: false,
|
|
durationMs,
|
|
};
|
|
|
|
handle.status = 'error';
|
|
handle.result = result;
|
|
this.activeSubAgents.delete(taskId);
|
|
this.emit('taskError', { taskId, error: errMsg });
|
|
|
|
log.error(`[Orchestrator] SubAgent "${taskId}" error: ${errMsg}`);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 完成子任务(外部触发)
|
|
*/
|
|
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 };
|
|
this.activeSubAgents.delete(taskId);
|
|
this.emit('taskCompleted', handle.result);
|
|
}
|
|
}
|
|
|
|
getActiveAgentsStatus(): Array<{ taskId: string; status: string; description: string }> {
|
|
return Array.from(this.activeSubAgents.values()).map((a) => a.getStatus());
|
|
}
|
|
|
|
abortAll(): void {
|
|
for (const agent of this.activeSubAgents.values()) {
|
|
agent.abort();
|
|
}
|
|
this.activeSubAgents.clear();
|
|
}
|
|
}
|