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
+8 -4
View File
@@ -37,16 +37,20 @@ export class ConfigService {
}
/**
* 设置配置值
* 设置配置值(保留已有 category
*/
set(key: string, value: unknown): void {
const db = this.getDB();
const jsonValue = typeof value === 'string' ? value : JSON.stringify(value);
// 查询已有 category,避免 INSERT OR REPLACE 覆盖为默认值
const existing = db.prepare('SELECT category FROM app_config WHERE key = ?').get(key) as ConfigRow | undefined;
const category = existing?.category ?? 'general';
db.prepare(`
INSERT OR REPLACE INTO app_config (key, value, updated_at)
VALUES (?, ?, ?)
`).run(key, jsonValue, Date.now());
INSERT OR REPLACE INTO app_config (key, value, category, updated_at)
VALUES (?, ?, ?, ?)
`).run(key, jsonValue, category, Date.now());
log.debug(`Config set: ${key}`);
}
+9 -2
View File
@@ -14,6 +14,7 @@
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
import type { Tool } from '@modelcontextprotocol/sdk/types.js';
import { nanoid } from 'nanoid';
import type Database from 'better-sqlite3';
@@ -170,13 +171,19 @@ export class MCPManager {
command: config.command,
args,
});
} else if (config.transport === 'sse' && config.url) {
// SSE 模式(远程 HTTP
transport = new SSEClientTransport(new URL(config.url));
} else {
throw new Error(`Unsupported transport: ${config.transport}. Only 'stdio' is currently supported.`);
throw new Error(
`Unsupported transport "${config.transport}". ` +
`'stdio' requires 'command', 'sse' requires 'url'.`,
);
}
const client = new Client(
{ name: 'metona-ai-desktop', version: '1.0.0' },
{ capabilities: { tools: {} } },
{ capabilities: {} },
);
await client.connect(transport);
+28 -16
View File
@@ -67,6 +67,13 @@ export class TrayManager {
log.info('System tray initialized');
}
/**
* 标记应用正在退出(窗口关闭时不阻止)
*/
static markQuitting(): void {
TrayManager.isQuitting = true;
}
/**
* 更新托盘状态
*/
@@ -121,36 +128,41 @@ export class TrayManager {
/**
* 获取托盘图标路径
*
* 优先级:process.resourcesPath(打包后)> resourcesPath 参数(开发模式) > __dirname fallback
*/
private getTrayIconPath(): string | null {
// 优先使用 icoWindows),其次 pngmacOS/Linux
const candidates = [
join(this.resourcesPath, 'logo.ico'),
join(this.resourcesPath, 'logo.png'),
join(this.resourcesPath, 'icon.ico'),
join(this.resourcesPath, 'icon.png'),
const searchDirs: string[] = [
join(process.resourcesPath || '', 'app.asar.unpacked', 'assets'),
join(process.resourcesPath || '', 'assets'),
this.resourcesPath,
join(__dirname, '../../assets'),
];
for (const candidate of candidates) {
if (existsSync(candidate)) return candidate;
const candidates = ['logo.ico', 'logo.png', 'icon.ico', 'icon.png'];
for (const dir of searchDirs) {
for (const name of candidates) {
const fullPath = join(dir, name);
if (existsSync(fullPath)) return fullPath;
}
}
return null;
}
/**
* 更新托盘图标(根据状态)
* 更新托盘图标
*
* 始终使用原始 logo 图标(状态已在右键菜单中显示,图标保持品牌识别)。
*/
private updateTrayIcon(status: TrayStatus): void {
private updateTrayIcon(_status: TrayStatus): void {
if (!this.tray) return;
// 基础图标
const iconPath = this.getTrayIconPath();
if (!iconPath) return;
const basePath = this.getTrayIconPath();
if (!basePath) return;
// 对于 thinking/executing 状态,可以使用 overlay 图标
// 当前实现使用基础图标,后续可扩展
const image = nativeImage.createFromPath(iconPath);
const image = nativeImage.createFromPath(basePath);
this.tray.setImage(image.resize({ width: 16, height: 16 }));
}
@@ -203,6 +203,8 @@ export class WindowManager {
const candidates = [
join(__dirname, '../../assets/logo.ico'),
join(__dirname, '../../assets/logo.png'),
join(process.resourcesPath || '', 'app.asar.unpacked', 'assets/logo.ico'),
join(process.resourcesPath || '', 'app.asar.unpacked', 'assets/logo.png'),
join(process.resourcesPath || '', 'assets/logo.ico'),
join(process.resourcesPath || '', 'assets/logo.png'),
];
+8 -7
View File
@@ -97,24 +97,25 @@ export class WorkspaceService {
}
// 验证/创建必需文件
const missingFiles: string[] = [];
const missingOnStart: string[] = [];
for (const fileName of REQUIRED_FILES) {
const filePath = join(this.workspacePath, fileName);
if (!existsSync(filePath)) {
missingFiles.push(fileName);
missingOnStart.push(fileName);
this.createFile(fileName);
}
}
// 加载文件内容
// 加载文件内容(在自动创建之后)
this.loadFiles();
// 校验 MEMORY.md 格式
this.validateMemoryFormat();
const isValid = missingFiles.length === 0;
if (missingFiles.length > 0) {
log.info(`Auto-created missing files: ${missingFiles.join(', ')}`);
// isValid: 所有文件均已就绪(包含刚自动创建的)
const isValid = true;
if (missingOnStart.length > 0) {
log.info(`Auto-created missing files: ${missingOnStart.join(', ')}`);
}
log.info(`Workspace initialized: ${this.workspacePath} (valid: ${isValid})`);
@@ -123,7 +124,7 @@ export class WorkspaceService {
path: this.workspacePath,
files: { ...this.files },
isValid,
missingFiles,
missingFiles: missingOnStart,
};
}