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
@@ -79,6 +79,7 @@ export class AgnesAdapter extends BaseAdapter {
...this.config.headers,
},
body: JSON.stringify(body),
signal: AbortSignal.timeout(this.config.timeoutMs ?? 300_000),
});
if (!response.ok || !response.body) {
@@ -174,6 +174,7 @@ export class DeepSeekAdapter extends BaseAdapter {
const effortMap: Record<string, string> = {
low: 'high', medium: 'high', high: 'high', xhigh: 'max', max: 'max',
};
// DeepSeek API 仅支持 high / max 两档,low/medium 映射为 high
body.reasoning_effort = effortMap[request.params.thinkingEffort ?? 'high'] ?? 'high';
}
+7 -2
View File
@@ -347,6 +347,7 @@ export class OllamaAdapter extends BaseAdapter {
request.systemPrompt.roleDefinition,
request.systemPrompt.outputConstraints,
request.systemPrompt.safetyGuidelines,
request.systemPrompt.dynamicReminders,
].filter(Boolean).join('\n\n'),
},
...request.messages.filter((m) => m.role !== 'system').map((m) => {
@@ -376,6 +377,10 @@ export class OllamaAdapter extends BaseAdapter {
function: { name: tc.name, arguments: JSON.stringify(tc.args) },
}));
}
// 推理内容回传(保持多轮推理链完整)
if (m.role === 'assistant' && m.reasoningContent) {
(msg as Record<string, unknown>).reasoning_content = m.reasoningContent;
}
return msg;
}),
];
@@ -467,8 +472,8 @@ function mapOllamaDoneReason(
switch (reason) {
case 'stop': return MetonaFinishReason.STOP;
case 'length': return MetonaFinishReason.LENGTH;
case 'load': return MetonaFinishReason.ERROR;
case 'unload': return MetonaFinishReason.ERROR;
case 'load': return MetonaFinishReason.STOP; // 冷启动加载完成,非错误
case 'unload': return MetonaFinishReason.STOP;
default: return MetonaFinishReason.STOP;
}
}
@@ -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(),
};