feat: v0.3.17 修复多项问题(sandbox/全局配置层/继承选项/配置刷新/附件提示/content_filter 错误处理)

- fix: sandbox: true → false(ESM preload 不兼容 sandbox 导致 window.metona 不可用,选择文件夹按钮无反应)
- feat: 全局配置层(GlobalConfigService)— LLM/Agent 等配置跨工作空间共享,切换工作空间后不再丢失
  - 读取时工作空间 DB 空值或默认值回退到全局 JSON
  - 写入时双写(DB + 全局 JSON)
  - 首次启动迁移旧配置到全局层
- fix: 工作空间继承选项显示条件放宽 + push 前校验避免覆盖目标已有文件(SOUL.md/agent.db)
- fix: config:changed IPC 广播 + 前端监听,修改 Agent 配置后详情栏 maxIterations 实时刷新
- feat: 上传图片/文件时在 System Prompt 注入附件提示,避免 AI 在工作空间查找用户上传的文件
- feat: content_filter 错误识别(ContentFilterError)+ 友好提示,替代原始 JSON 错误体透传
- fix: JSDoc 注释中 llm.* / agent.* / onboarding.completed 中的 */ 被解析为注释结束符
This commit is contained in:
2026-07-22 13:12:15 +08:00
parent 9af9984418
commit 3e5ddea72d
12 changed files with 506 additions and 32 deletions
+41 -1
View File
@@ -163,6 +163,29 @@ export function registerAllIPCHandlers(
log.warn('[AGENT] Memory retrieval failed, proceeding without memories:', err);
}
// 附件提示注入:用户直接上传的文件/图片,避免 LLM 误以为需要在工作空间查找
// 解决场景:用户上传图片后,AI 先在工作空间找图片,找不到才意识到是用户上传的
const attachments = (userMessage as MetonaMessage & { attachments?: Array<{ name: string; type: string }> }).attachments;
if (Array.isArray(attachments) && attachments.length > 0) {
const attachmentList = attachments.map((att, i) => {
const typeLabel = att.type === 'image' ? 'image' : att.type === 'text' ? 'text file' : 'file';
const note = att.type === 'image'
? 'visible via vision capability, do NOT search in workspace'
: att.type === 'text'
? 'content already inlined in the user message, do NOT search in workspace'
: 'uploaded directly by user, do NOT search in workspace';
return `${i + 1}. [${typeLabel}] ${att.name}${note}`;
}).join('\n');
const attachmentBlock = `## User Attachments (Direct Upload)\nThe following files were uploaded directly by the user to this conversation. They are inline attachments, NOT workspace files:\n${attachmentList}`;
systemPrompt.dynamicReminders = systemPrompt.dynamicReminders
? `${systemPrompt.dynamicReminders}\n\n---\n\n${attachmentBlock}`
: attachmentBlock;
log.debug(`[AGENT] Injected ${attachments.length} attachment hints into system prompt`);
}
// 监听 Agent Loop 事件
// F8: 主进程 text_delta 节流合并
// 问题:主进程 1:1 转发所有流式事件,text_delta 频率 30-50 次/秒,
@@ -817,6 +840,11 @@ export function registerAllIPCHandlers(
log.info(`[CONFIG] Log level updated to ${level}`);
}
// 广播配置变更事件,前端监听后更新 store(如 maxIterations 显示)
if (!mainWindow.isDestroyed()) {
mainWindow.webContents.send('config:changed', { key, value });
}
// 工作空间路径变更时写入独立文件(下次启动生效)
if (key === 'workspace.path') {
try {
@@ -978,6 +1006,13 @@ export function registerAllIPCHandlers(
log.info(`[CONFIG] Log level updated to ${logLevelValue}`);
}
// 第四步半:广播所有配置变更事件,前端监听后更新 store
if (!mainWindow.isDestroyed()) {
for (const { key, value } of entries) {
mainWindow.webContents.send('config:changed', { key, value });
}
}
// 第五步:工作空间路径写入独立文件(下次启动生效)
if (workspacePathValue) {
try {
@@ -1061,7 +1096,7 @@ export function registerAllIPCHandlers(
// ===== 工作空间校验与继承 =====
// 校验工作空间路径:检测路径有效性 + 4 个必需文件状态
// 校验工作空间路径:检测路径有效性 + 必需文件状态 + 数据库是否存在
ipcMain.handle('workspace:check', async (_event, targetPath: string) => {
if (!targetPath || typeof targetPath !== 'string') {
return { valid: false, reason: '路径不能为空' };
@@ -1080,6 +1115,7 @@ export function registerAllIPCHandlers(
exists: false,
missingFiles: ['SOUL.md', 'MEMORY.md'],
isNewWorkspace: true,
dbExists: false,
reason: '目录不存在,将在切换后自动创建',
};
}
@@ -1101,12 +1137,16 @@ export function registerAllIPCHandlers(
if (!existsSync(join(resolvedPath, f))) missingFiles.push(f);
}
// 校验 4: 检测 .metona/agent.db 是否存在(用于判断是否显示"继承数据库"选项)
const dbExists = existsSync(join(resolvedPath, '.metona', 'agent.db'));
return {
valid: true,
path: resolvedPath,
exists: true,
missingFiles,
isNewWorkspace: missingFiles.length === requiredFiles.length,
dbExists,
};
});