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:
@@ -211,11 +211,53 @@ function AgentSettings() {
|
||||
}
|
||||
|
||||
function ToolsSettings() {
|
||||
const [tools, setTools] = useState<Array<{ name: string; description: string; riskLevel: string; enabled: boolean }>>([]);
|
||||
useEffect(() => { if (window.metona?.tools?.list) window.metona.tools.list().then((l) => setTools((l as MetonaToolInfo[]).map((t) => ({ name: t.name, description: t.description, riskLevel: t.riskLevel, enabled: t.enabled })))).catch((err) => { console.error('[SettingsModal]', err); }); }, []);
|
||||
const handleToggle = (name: string, enabled: boolean) => { setTools((p) => p.map((t) => t.name === name ? { ...t, enabled } : t)); window.metona?.tools?.toggle(name, enabled).catch((err) => { console.error('[SettingsModal]', err); }); };
|
||||
const [tools, setTools] = useState<Array<{ name: string; description: string; riskLevel: string; requiresPermission: boolean; enabled: boolean }>>([]);
|
||||
const [autoExecList, setAutoExecList] = useState<string[]>([]);
|
||||
|
||||
const loadTools = () => {
|
||||
if (window.metona?.tools?.list) {
|
||||
window.metona.tools.list().then((l) => setTools((l as MetonaToolInfo[]).map((t) => ({
|
||||
name: t.name, description: t.description, riskLevel: t.riskLevel,
|
||||
requiresPermission: t.requiresPermission, enabled: t.enabled,
|
||||
})))).catch((err) => { console.error('[SettingsModal]', err); });
|
||||
}
|
||||
};
|
||||
const loadAutoExec = () => {
|
||||
if (window.metona?.tool?.getAutoExecuteList) {
|
||||
window.metona.tool.getAutoExecuteList().then((r) => {
|
||||
if (r.success) setAutoExecList(r.data);
|
||||
}).catch((err) => { console.error('[SettingsModal]', err); });
|
||||
}
|
||||
};
|
||||
useEffect(() => { loadTools(); loadAutoExec(); }, []);
|
||||
|
||||
const handleToggle = (name: string, enabled: boolean) => {
|
||||
setTools((p) => p.map((t) => t.name === name ? { ...t, enabled } : t));
|
||||
window.metona?.tools?.toggle(name, enabled).catch((err) => { console.error('[SettingsModal]', err); });
|
||||
};
|
||||
|
||||
// 设置/取消自动执行
|
||||
const handleSetAutoExec = async (name: string, enabled: boolean) => {
|
||||
if (!window.metona?.tool?.setAutoExecute) return;
|
||||
const r = await window.metona.tool.setAutoExecute(name, enabled);
|
||||
if (r.success) {
|
||||
setAutoExecList((p) => enabled ? [...p, name] : p.filter((n) => n !== name));
|
||||
}
|
||||
};
|
||||
|
||||
const riskColors: Record<string, 'success' | 'info' | 'warning' | 'error'> = { safe: 'success', low: 'info', medium: 'warning', high: 'error' };
|
||||
|
||||
// 需要确认的工具(high/critical 或 requiresPermission)
|
||||
const needsConfirmTools = tools.filter((t) =>
|
||||
t.riskLevel === 'high' || t.riskLevel === 'critical' || t.requiresPermission,
|
||||
);
|
||||
// 需要确认但未设为自动执行的工具
|
||||
const pendingConfirmTools = needsConfirmTools.filter((t) => !autoExecList.includes(t.name));
|
||||
// 已设为自动执行的工具详情
|
||||
const autoExecToolDetails = autoExecList
|
||||
.map((name) => tools.find((t) => t.name === name))
|
||||
.filter((t): t is NonNullable<typeof t> => t !== undefined);
|
||||
|
||||
return (
|
||||
<Stack spacing={2}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>工具管理</Typography>
|
||||
@@ -231,6 +273,56 @@ function ToolsSettings() {
|
||||
</Stack>
|
||||
</Stack>
|
||||
))}
|
||||
|
||||
<Divider sx={{ my: 1 }} />
|
||||
|
||||
{/* ===== 自动执行工具管理 ===== */}
|
||||
<Stack direction="row" sx={{ alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>自动执行工具</Typography>
|
||||
<Chip label={`${autoExecList.length} 个`} size="small" color={autoExecList.length > 0 ? 'success' : 'default'} variant="outlined" sx={{ height: 18, fontSize: 10 }} />
|
||||
</Stack>
|
||||
<Typography variant="caption" sx={{ color: 'text.secondary', lineHeight: 1.5 }}>
|
||||
已设为自动执行的工具将跳过用户确认步骤,直接执行。此设置跨会话持久化。
|
||||
</Typography>
|
||||
|
||||
{/* 已自动执行的工具列表 */}
|
||||
{autoExecToolDetails.length === 0 ? (
|
||||
<Typography variant="caption" sx={{ textAlign: 'center', py: 2, color: 'text.disabled', fontStyle: 'italic' }}>
|
||||
暂无自动执行工具
|
||||
</Typography>
|
||||
) : (
|
||||
autoExecToolDetails.map((t) => (
|
||||
<Stack key={t.name} direction="row" sx={{ py: 1, px: 1.5, borderRadius: 1.5, bgcolor: 'success.main', opacity: 0.8, justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', minWidth: 0, flex: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}>{t.name}</Typography>
|
||||
<Chip label="自动" size="small" color="success" sx={{ height: 16, fontSize: 9 }} />
|
||||
</Stack>
|
||||
<Button size="small" color="warning" variant="outlined" sx={{ fontSize: 10, minWidth: 70 }} onClick={() => handleSetAutoExec(t.name, false)}>
|
||||
取消自动
|
||||
</Button>
|
||||
</Stack>
|
||||
))
|
||||
)}
|
||||
|
||||
{/* 可设为自动执行的工具(需要确认但未设置) */}
|
||||
{pendingConfirmTools.length > 0 && (
|
||||
<>
|
||||
<Typography variant="caption" sx={{ fontWeight: 600, color: 'text.secondary', mt: 1 }}>
|
||||
可设为自动执行(当前需要确认)
|
||||
</Typography>
|
||||
{pendingConfirmTools.map((t) => (
|
||||
<Stack key={t.name} direction="row" sx={{ py: 1, px: 1.5, borderRadius: 1.5, bgcolor: 'secondary.main', border: '1px dashed', borderColor: 'divider', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||
<Stack direction="row" spacing={1} sx={{ alignItems: 'center', minWidth: 0, flex: 1 }}>
|
||||
<Typography variant="body2" sx={{ fontFamily: 'monospace', fontSize: 12 }}>{t.name}</Typography>
|
||||
<Chip label={t.riskLevel.toUpperCase()} size="small" color={riskColors[t.riskLevel]} variant="outlined" sx={{ height: 16, fontSize: 9 }} />
|
||||
</Stack>
|
||||
<Button size="small" color="success" variant="contained" sx={{ fontSize: 10, minWidth: 70 }} onClick={() => handleSetAutoExec(t.name, true)}>
|
||||
设为自动
|
||||
</Button>
|
||||
</Stack>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user