feat: 升级至 v0.2.1 — 流式渲染修复、安全增强、工具自动执行
流式渲染修复: - runId 机制防止 abort 后旧流事件污染新 run - run lock 防止并发 run 污染引擎状态 - abort race 提前退出工具执行等待 - TERMINATED 状态通过 stateChange 发射 - tool_call_delta 流式参数拼接 + pending 占位替换 - 首轮卡片创建路径统一,traceStep 按 ID 精确匹配 - compressed 事件转发为 toast 通知 安全增强: - ConfirmationHook 支持持久化自动执行(跨会话) - 设置面板新增自动执行工具管理 UI - SandboxManager 双重安全校验 fail-closed - 审计日志链式哈希防篡改 - PromptInjectionDefender 中文注入标记清理 - scanCode 28 模式 + base64/$() 检测 - validatePath realpathSync 防符号链接逃逸 - code-search 使用 execFile 防命令注入 新增工具: - file_editor、code_search、task_manager、diff_viewer 其他: - Agent Loop 加 PARSING/REFLECTING 状态 + 指数退避重试 - MemoryManager TF-IDF 语义检索 - run_command Windows 中文编码修复(chcp 65001) - 版本号 0.2.0 → 0.2.1
This commit is contained in:
@@ -1,16 +1,25 @@
|
||||
/**
|
||||
* DetailPanel — 右侧详情面板
|
||||
*
|
||||
* 布局策略:TraceViewer flex:1 撑满,TokenUsage/AgentMonitor flexShrink:0 固定。
|
||||
* 通过 Tab 切换显示 Trace / Memory / Tasks。
|
||||
*/
|
||||
|
||||
import { Box } from '@mui/material';
|
||||
import { useState } from 'react';
|
||||
import { Box, Tabs, Tab } from '@mui/material';
|
||||
import { Activity, Brain, ListChecks } from 'lucide-react';
|
||||
import { TraceViewer } from '@renderer/components/trace/TraceViewer';
|
||||
import { TokenUsage } from '@renderer/components/trace/TokenUsage';
|
||||
import { AgentMonitor } from '@renderer/components/layout/AgentMonitor';
|
||||
import { MemoryViewer } from '@renderer/components/memory/MemoryViewer';
|
||||
import { TaskList } from '@renderer/components/tasks/TaskList';
|
||||
import { ErrorBoundary } from '@renderer/components/common/ErrorBoundary';
|
||||
import { LAYOUT } from '@renderer/lib/constants';
|
||||
|
||||
type DetailTab = 'trace' | 'memory' | 'tasks';
|
||||
|
||||
export function DetailPanel(): React.JSX.Element {
|
||||
const [tab, setTab] = useState<DetailTab>('trace');
|
||||
|
||||
return (
|
||||
<Box
|
||||
component="aside"
|
||||
@@ -19,10 +28,42 @@ export function DetailPanel(): React.JSX.Element {
|
||||
bgcolor: 'background.paper', borderLeft: 1, borderColor: 'divider', width: LAYOUT.DETAIL_WIDTH,
|
||||
}}
|
||||
>
|
||||
<Tabs
|
||||
value={tab}
|
||||
onChange={(_, v) => setTab(v as DetailTab)}
|
||||
variant="fullWidth"
|
||||
sx={{
|
||||
minHeight: 32, height: 32, flexShrink: 0,
|
||||
borderBottom: 1, borderColor: 'divider',
|
||||
'& .MuiTab-root': { minHeight: 32, height: 32, fontSize: 11, textTransform: 'none', py: 0, px: 1 },
|
||||
'& .MuiTabs-indicator': { height: 2 },
|
||||
}}
|
||||
>
|
||||
<Tab icon={<Activity size={12} />} iconPosition="start" label="Trace" value="trace" />
|
||||
<Tab icon={<Brain size={12} />} iconPosition="start" label="Memory" value="memory" />
|
||||
<Tab icon={<ListChecks size={12} />} iconPosition="start" label="Tasks" value="tasks" />
|
||||
</Tabs>
|
||||
|
||||
<Box sx={{ p: 1.5, flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
|
||||
<TraceViewer />
|
||||
<TokenUsage />
|
||||
<AgentMonitor />
|
||||
{tab === 'trace' && (
|
||||
<ErrorBoundary fallbackTitle="Trace 渲染失败">
|
||||
<>
|
||||
<TraceViewer />
|
||||
<TokenUsage />
|
||||
<AgentMonitor />
|
||||
</>
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{tab === 'memory' && (
|
||||
<ErrorBoundary fallbackTitle="Memory 渲染失败">
|
||||
<MemoryViewer />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
{tab === 'tasks' && (
|
||||
<ErrorBoundary fallbackTitle="Tasks 渲染失败">
|
||||
<TaskList />
|
||||
</ErrorBoundary>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Box, Typography, Button, IconButton, TextField, Stack, Collapse, List, ListItemButton, ListItemIcon, ListItemText, Badge, Divider, Dialog, DialogTitle, DialogContent, DialogActions } from '@mui/material';
|
||||
import Fuse from 'fuse.js';
|
||||
import { Plus, Search, MessageSquare, Pin, Wrench, Database, ChevronDown, ChevronRight, Trash2 } from 'lucide-react';
|
||||
import { Plus, Search, MessageSquare, Pin, Wrench, ChevronDown, ChevronRight, Trash2 } from 'lucide-react';
|
||||
import { useSessionStore, type Session } from '@renderer/stores/session-store';
|
||||
import { useAgentStore } from '@renderer/stores/agent-store';
|
||||
import { formatRelativeTime } from '@renderer/lib/formatters';
|
||||
@@ -78,7 +78,6 @@ export function Sidebar(): React.JSX.Element {
|
||||
|
||||
<Divider sx={{ mt: 'auto', mb: 1 }} />
|
||||
<ToolManagerPanel />
|
||||
<MemorySearchPanel />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
@@ -169,40 +168,3 @@ function ToolManagerPanel() {
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function MemorySearchPanel() {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [results, setResults] = useState<Array<{ id: string; type: string; content: string; importance: number }>>([]);
|
||||
const [searching, setSearching] = useState(false);
|
||||
const handleSearch = async () => { if (!query.trim() || !window.metona?.memory?.search) return; setSearching(true); try { setResults((await window.metona.memory.search(query, { topK: 5 })) as any); } catch { setResults([]); } setSearching(false); };
|
||||
return (
|
||||
<Box>
|
||||
<ListItemButton onClick={() => setExpanded(!expanded)} dense sx={{ borderRadius: 1, px: 1.5, py: 0.75 }}>
|
||||
<ListItemIcon sx={{ minWidth: 24 }}>{expanded ? <ChevronDown size={12} /> : <ChevronRight size={12} />}<Database size={12} style={{ marginLeft: 4 }} /></ListItemIcon>
|
||||
<ListItemText primary={<Typography variant="body2" sx={{ fontSize: 12 }}>记忆搜索</Typography>} />
|
||||
</ListItemButton>
|
||||
<Collapse in={expanded}>
|
||||
<Stack spacing={1} sx={{ pl: 3, pr: 1, pb: 1 }}>
|
||||
<Stack direction="row" spacing={0.5}>
|
||||
<TextField size="small" value={query} onChange={(e) => setQuery(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') handleSearch(); }} placeholder="搜索记忆..." sx={{ flex: 1, '& .MuiInputBase-root': { fontSize: 11, height: 28 } }} />
|
||||
<Button variant="outlined" size="small" onClick={handleSearch} disabled={searching} sx={{ minWidth: 40, height: 28, fontSize: 10 }}>{searching ? '...' : '搜索'}</Button>
|
||||
</Stack>
|
||||
{results.length > 0 && (
|
||||
<Box sx={{ maxHeight: 200, overflowY: 'auto', display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||
{results.map((r) => (
|
||||
<Box key={r.id} sx={{ px: 1, py: 0.75, borderRadius: 1, bgcolor: 'background.default', fontSize: 10, color: 'text.secondary' }}>
|
||||
<Stack direction="row" spacing={0.5} sx={{ mb: 0.25, alignItems: 'center' }}>
|
||||
<Box sx={{ px: 0.5, borderRadius: 0.5, bgcolor: 'action.hover', color: 'primary.main', fontSize: 9 }}>{r.type}</Box>
|
||||
<Typography variant="caption" sx={{ fontSize: 9 }}>重要度: {r.importance.toFixed(1)}</Typography>
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ fontSize: 10, display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.content.slice(0, 100)}</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Stack>
|
||||
</Collapse>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user