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
@@ -40,14 +40,17 @@ export function buildOpenAICompatibleMessages(
.map((m) => {
const msg: Record<string, unknown> = { role: m.role, content: m.content };
// === Assistant 工具调用历史 ===
if (m.role === 'assistant' && m.toolCalls?.length) {
msg.tool_calls = m.toolCalls.map((tc) => ({
id: tc.id,
type: 'function',
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
// 推理内容必须带回上下文(否则 LLM 丢失思考链)
// === Assistant 消息 ===
if (m.role === 'assistant') {
// 工具调用历史
if (m.toolCalls?.length) {
msg.tool_calls = m.toolCalls.map((tc) => ({
id: tc.id,
type: 'function',
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
}
// 推理内容(无论是否有工具调用,都保留 reasoning_content
if (m.reasoningContent) {
msg.reasoning_content = m.reasoningContent;
}
+35 -2
View File
@@ -142,7 +142,7 @@ export async function* parseSSEStream(
}
}
// Token 使用统计
// Token 使用统计 / finish_reason
if (chunk.usage) {
const usage: MetonaTokenUsage = {
inputTokens: chunk.usage.prompt_tokens ?? 0,
@@ -163,6 +163,33 @@ export async function* parseSSEStream(
usage,
};
}
// 非 [DONE] 但 finish_reason 为 tool_calls 时提前 flush 缓冲区
const finishReason = chunk.choices?.[0]?.finish_reason as string | undefined;
if (finishReason === 'tool_calls' || finishReason === 'stop') {
for (const [index, buf] of toolCallsBuffer) {
try {
yield {
type: MetonaStreamEventType.TOOL_CALL_COMPLETE,
requestId,
sessionId,
iteration,
seq: seq++,
timestamp: Date.now(),
toolCall: {
id: `tc_${nanoid(8)}`,
name: buf.name,
args: buf.argsBuffer ? JSON.parse(buf.argsBuffer) : {},
iteration,
timestamp: Date.now(),
},
};
} catch {
// JSON 解析失败,跳过
}
}
toolCallsBuffer.clear();
}
} catch {
// 跳过解析失败的行
}
@@ -195,10 +222,16 @@ export function parseOpenAICompatibleResponse(
reasoningContent: message?.reasoning_content as string | undefined,
toolCalls: rawToolCalls?.map((tc) => {
const fn = tc.function as Record<string, unknown>;
let args: Record<string, unknown> = {};
try {
args = JSON.parse(fn.arguments as string);
} catch {
args = {};
}
return {
id: tc.id as string,
name: fn.name as string,
args: JSON.parse(fn.arguments as string),
args,
iteration: 0,
timestamp: Date.now(),
};