fix: 全面修复审计问题并优化系统提示词

**崩溃/挂死修复 (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 (状态由右键菜单展示)
This commit is contained in:
thzxx
2026-06-30 23:17:38 +08:00
parent ad0cbc8d47
commit 97a3d3d53b
30 changed files with 458 additions and 308 deletions
+124 -39
View File
@@ -2,12 +2,18 @@
* 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;
@@ -30,11 +36,18 @@ interface SubAgentHandle {
export class TaskOrchestrator extends EventEmitter {
private activeSubAgents = new Map<string, SubAgentHandle>();
constructor(
private agentLoopEngine: AgentLoopEngine,
private toolRegistry?: ToolRegistry,
) {
super();
}
/**
* 委派子任务
*
* 创建一个子任务句柄,通过事件驱动的方式执行
* 实际执行逻辑由上层 Agent Loop 决定
* 创建一个独立的 SubAgent 执行上下文,调用 AgentLoopEngine 完成任务
* 支持限制可用工具列表(白名单)
*/
async delegate(params: {
taskId?: string;
@@ -48,52 +61,124 @@ export class TaskOrchestrator extends EventEmitter {
this.emit('taskDelegated', { taskId, description: params.description, parentSessionId: params.parentSessionId });
// 返回一个可被上层消费的 Promise
return new Promise<SubAgentResult>((resolve) => {
const handle: SubAgentHandle = {
taskId,
description: params.description,
status: 'pending',
abort: () => {
handle.status = 'error';
this.activeSubAgents.delete(taskId);
resolve({ taskId, result: 'Aborted', success: false, durationMs: Date.now() - startMs });
},
onComplete: (callback) => {
if (handle.result) callback(handle.result);
},
onError: (_callback) => {},
getStatus: () => ({ taskId, status: handle.status, description: handle.description }),
};
this.activeSubAgents.set(taskId, handle);
// 立即标记为运行中
handle.status = 'running';
this.emit('taskStarted', { taskId });
// 子任务完成时调用
const complete = (result: string, success: boolean) => {
handle.status = success ? 'completed' : 'error';
handle.result = { taskId, result, success, durationMs: Date.now() - startMs };
const handle: SubAgentHandle = {
taskId,
description: params.description,
status: 'running',
abort: () => {
handle.status = 'error';
this.activeSubAgents.delete(taskId);
this.emit('taskCompleted', handle.result);
resolve(handle.result);
},
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(),
};
// 暴露完成方法给调用者
(handle as unknown as Record<string, unknown>).complete = complete;
});
// 构建 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) {
const complete = (handle as unknown as Record<string, unknown>).complete as ((result: string, success: boolean) => void) | undefined;
complete?.(result, success);
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);
}
}