feat: v0.7.0 四阶段全量迭代 — 修复面收口 · 安全纵深 · 架构还债 · 能力演进
P1 修复面收口: v0.6.3 截断自愈推全量(Anthropic/Ollama/非流式/引擎兜底); SSE 上游错误帧检测进重试通道; clearMessages 摘要游标根治; truncateResult 内联图片白名单统一; 前端四 bug(确认弹窗锁死/MemoryViewer/ Virtuoso Footer/abort 尾部过滤) + reasoning 缓冲跨迭代污染; 托盘通知过滤与新建会话死链接线 P2 安全纵深: MCP 审批闭环(ConfirmationHook×PolicyEngine 联动+重名拒注册); SSRF 收敛 ssrf-guard 共享模块 (web_fetch 双通道校验+重定向终态复检); Electron 加固(preload CJS 化→sandbox:true/CSP/权限白名单/will-navigate); run_command cmd.exe 白名单通道元字符守门; diff_viewer 10MB 预检; Anthropic thinking 预算下限; Agnes 思考显式关闭 P3 架构还债: OpenAICompatibleAdapter 中间基类收敛四家样板; 错误分类单轨化(删 mapError/getFetchSignal, 超时显式 ETIMEDOUT); PRAGMA user_version 迁移版本化; 死代码清理专项(cn.ts/SHORTCUTS/ContextMenu 分支/ getWindowState/modifiedArgs/sandbox 空壳); i18next 引入; a11y 第一轮; SearXNG 页批量草稿模型统一 P4 能力演进: Ollama pull 可取消/capabilities 探测/num_ctx 实测缓存; UpdateService feed 比对式自动更新 (app:updateCheck IPC + StatusBar 入口); MiMo providerOptions(web_search 服务端工具/strict JSON); web_fetch extract_mode=markdown(turndown); network.proxyUrl 全局代理(Chromium sessions+undici dispatcher) 测试: 264 → 507 用例(Electron ABI 全绿零跳过), 覆盖引擎压缩管线/重试竞速/MEMORY.md 闸门/file_editor 五操作/ filesystem 七工具实体夹具/git 真实仓库/SSE 错误帧/全线截断自愈/Provider 请求形态矩阵/SSRF 表测/钩子分级矩阵/ OutputValidator 全量/SLO 指标/MCP 安全纯函数/task_manager 链路/渲染层纯域/i18n 桥契约
This commit is contained in:
@@ -26,15 +26,7 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useUIStore, type ThemeMode } from '@renderer/stores/ui-store';
|
||||
import { useAgentStore } from '@renderer/stores/agent-store';
|
||||
|
||||
const PROVIDER_LABELS: Record<string, string> = {
|
||||
deepseek: 'DeepSeek',
|
||||
agnes: 'Agnes',
|
||||
mimo: 'MiMo',
|
||||
ollama: 'Ollama',
|
||||
openai: 'OpenAI',
|
||||
anthropic: 'Anthropic',
|
||||
};
|
||||
import { PROVIDER_LABELS } from '@renderer/lib/constants';
|
||||
|
||||
export function Header(): React.JSX.Element {
|
||||
const sidebarVisible = useUIStore((s) => s.sidebarVisible);
|
||||
|
||||
@@ -179,18 +179,13 @@ export function Sidebar(): React.JSX.Element {
|
||||
return; // 不创建本地假会话
|
||||
}
|
||||
}
|
||||
const newSession: Session = {
|
||||
id: `s_${Date.now()}`,
|
||||
title: '新会话',
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now(),
|
||||
messageCount: 0,
|
||||
pinned: false,
|
||||
archived: false,
|
||||
};
|
||||
useSessionStore.getState().addSession(newSession);
|
||||
setCurrentSession(newSession.id);
|
||||
loadSessionMessages(newSession.id);
|
||||
// v0.6.4 修复: 与 M-9 注释对齐 —— IPC 桥不可用(window.metona.sessions.create
|
||||
// 不存在)时同样不构造本地假会话。假会话重启后即蒸发,且会污染 session 列表状态。
|
||||
// 直接提示用户(正常构建下 preload 必然提供该 API,此分支仅在桥接损坏时触达)。
|
||||
console.error('[Sidebar] window.metona.sessions.create is unavailable — IPC bridge broken');
|
||||
import('@metona-team/metona-toast')
|
||||
.then((mod) => mod.default.error('IPC 桥不可用:无法创建会话'))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -517,6 +512,7 @@ function SessionItem({
|
||||
<IconButton
|
||||
className="delete-btn"
|
||||
size="small"
|
||||
aria-label={`删除会话 ${session.title}`}
|
||||
onClick={handleDelete}
|
||||
sx={{
|
||||
opacity: 0,
|
||||
|
||||
@@ -22,6 +22,7 @@ export function StatusBar(): React.JSX.Element {
|
||||
const [version, setVersion] = useState(
|
||||
typeof __APP_VERSION__ !== 'undefined' && __APP_VERSION__ ? `v${__APP_VERSION__}` : 'dev',
|
||||
);
|
||||
const [checkingUpdate, setCheckingUpdate] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
// M-28 修复: 添加 cancelled 标志防止组件卸载后 setState
|
||||
@@ -82,7 +83,52 @@ export function StatusBar(): React.JSX.Element {
|
||||
|
||||
{/* 右侧:版本 + 设置 */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}>
|
||||
<Typography variant="caption" sx={{ color: 'text.disabled', fontSize: 10 }}>{version}</Typography>
|
||||
{/* v0.6.4 P4-2: 版本号可点击 → 手动检查更新(feed 比对式) */}
|
||||
<Tooltip title={`点击检查更新(当前 ${version})`}>
|
||||
<Typography
|
||||
variant="caption"
|
||||
component="button"
|
||||
onClick={() => {
|
||||
if (checkingUpdate) return;
|
||||
setCheckingUpdate(true);
|
||||
window.metona?.app
|
||||
?.updateCheck()
|
||||
.then((result) => {
|
||||
if (result.status === 'available') {
|
||||
import('@metona-team/metona-toast')
|
||||
.then((mod) =>
|
||||
mod.default.info(`发现新版本 ${result.latestVersion},点击此通知或设置中打开下载页`, {
|
||||
onClick: result.downloadUrl
|
||||
? () =>
|
||||
void window.metona?.app?.openExternal(result.downloadUrl as string)
|
||||
: undefined,
|
||||
}),
|
||||
)
|
||||
.catch(() => {});
|
||||
} else if (result.status === 'up-to-date') {
|
||||
import('@metona-team/metona-toast')
|
||||
.then((mod) => mod.default.success('已是最新版本'))
|
||||
.catch(() => {});
|
||||
} else {
|
||||
import('@metona-team/metona-toast')
|
||||
.then((mod) => mod.default.warning(result.message))
|
||||
.catch(() => {});
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error('[StatusBar] update check failed:', err))
|
||||
.finally(() => setCheckingUpdate(false));
|
||||
}}
|
||||
sx={{
|
||||
color: checkingUpdate ? 'warning.main' : 'text.disabled',
|
||||
fontSize: 10,
|
||||
cursor: 'pointer',
|
||||
border: 0, p: 0, bgcolor: 'transparent', lineHeight: 1,
|
||||
'&:hover': { color: 'primary.main' },
|
||||
}}
|
||||
>
|
||||
{checkingUpdate ? '检查中…' : version}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
<Tooltip title="设置 (Ctrl+,)">
|
||||
<IconButton size="small" onClick={openSettings} sx={{ color: 'text.secondary', width: 28, height: 28 }}>
|
||||
<Settings size={14} />
|
||||
|
||||
Reference in New Issue
Block a user