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:
+28
-15
@@ -7,6 +7,10 @@
|
||||
import { create } from 'zustand';
|
||||
import { useSessionStore } from './session-store';
|
||||
|
||||
// L-1: 消息 ID 防碰撞计数器
|
||||
let _msgIdCounter = 0;
|
||||
export const genMsgId = (role: string) => `msg_${Date.now()}_${role}_${_msgIdCounter++}`;
|
||||
|
||||
// ===== 消息类型 =====
|
||||
|
||||
export type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
|
||||
@@ -85,7 +89,9 @@ interface AgentState {
|
||||
|
||||
// 流式
|
||||
isStreaming: boolean;
|
||||
streamingContent: string;
|
||||
|
||||
// Run 标识(用于过滤旧流事件)
|
||||
currentRunId: string | null;
|
||||
|
||||
// Provider
|
||||
provider: string;
|
||||
@@ -101,10 +107,10 @@ interface AgentState {
|
||||
updateLastAssistantMessage: (delta: string) => void;
|
||||
setAgentStatus: (status: AgentStatus) => void;
|
||||
setStreaming: (streaming: boolean) => void;
|
||||
appendStreamingContent: (delta: string) => void;
|
||||
clearStreamingContent: () => void;
|
||||
setCurrentRunId: (runId: string | null) => void;
|
||||
addTraceStep: (step: TraceStep) => void;
|
||||
updateTraceStep: (iteration: number, updates: Partial<TraceStep>) => void;
|
||||
updateTraceStepById: (id: string, updates: Partial<TraceStep>) => void;
|
||||
updateLastTraceStep: (updates: Partial<TraceStep>) => void;
|
||||
updateTokenUsage: (usage: Partial<TokenUsage>) => void;
|
||||
setProvider: (provider: string, model: string) => void;
|
||||
@@ -125,7 +131,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
||||
traceSteps: [],
|
||||
isStreaming: false,
|
||||
streamingContent: '',
|
||||
currentRunId: null,
|
||||
provider: '',
|
||||
model: '',
|
||||
contextWindow: 0,
|
||||
@@ -133,7 +139,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
// ===== Actions =====
|
||||
|
||||
setCurrentSession: (id) => {
|
||||
set({ currentSessionId: id, messages: [], traceSteps: [], currentIteration: 0, tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 } });
|
||||
set({ currentSessionId: id, messages: [], traceSteps: [], currentIteration: 0, tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 }, agentStatus: 'idle', isStreaming: false, currentRunId: null });
|
||||
|
||||
// 从数据库加载该会话的消息
|
||||
if (id && window.metona?.sessions?.getMessages) {
|
||||
@@ -208,7 +214,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
console.error('[AgentStore]', 'Failed to create session:', err);
|
||||
set({ agentStatus: 'error', isStreaming: false });
|
||||
get().addMessage({
|
||||
id: `msg_${Date.now()}_error`,
|
||||
id: genMsgId('error'),
|
||||
role: 'system',
|
||||
content: `错误: 无法创建会话 — ${(err as Error).message}`,
|
||||
timestamp: Date.now(),
|
||||
@@ -218,7 +224,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
}
|
||||
|
||||
const userMessage: ChatMessage = {
|
||||
id: `msg_${Date.now()}_user`,
|
||||
id: genMsgId('user'),
|
||||
role: 'user',
|
||||
content, // 用户可见内容(纯文本)
|
||||
timestamp: Date.now(),
|
||||
@@ -229,7 +235,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
messages: [...s.messages, userMessage],
|
||||
agentStatus: 'thinking',
|
||||
isStreaming: true,
|
||||
streamingContent: '',
|
||||
currentRunId: null, // 将在首个 streamEvent 中由 runId 设置
|
||||
currentIteration: 0,
|
||||
}));
|
||||
|
||||
@@ -283,7 +289,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
console.error('[AgentStore]', 'IPC sendMessage failed:', err);
|
||||
set({ agentStatus: 'error', isStreaming: false });
|
||||
get().addMessage({
|
||||
id: `msg_${Date.now()}_error`,
|
||||
id: genMsgId('error'),
|
||||
role: 'system',
|
||||
content: `错误: 消息发送失败 — ${(err as Error).message}`,
|
||||
timestamp: Date.now(),
|
||||
@@ -302,7 +308,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
messages[lastIdx] = { ...lastMsg, content: lastMsg.content + delta };
|
||||
} else {
|
||||
messages.push({
|
||||
id: `msg_${Date.now()}_assistant`,
|
||||
id: genMsgId('assistant'),
|
||||
role: 'assistant',
|
||||
content: delta,
|
||||
timestamp: Date.now(),
|
||||
@@ -317,10 +323,7 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
|
||||
setStreaming: (streaming) => set({ isStreaming: streaming }),
|
||||
|
||||
appendStreamingContent: (delta) =>
|
||||
set((s) => ({ streamingContent: s.streamingContent + delta })),
|
||||
|
||||
clearStreamingContent: () => set({ streamingContent: '' }),
|
||||
setCurrentRunId: (runId) => set({ currentRunId: runId }),
|
||||
|
||||
addTraceStep: (step) =>
|
||||
set((s) => ({ traceSteps: [...s.traceSteps, step] })),
|
||||
@@ -332,6 +335,14 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
),
|
||||
})),
|
||||
|
||||
// L-2: 按 ID 精确匹配 traceStep(避免 iteration 碰撞)
|
||||
updateTraceStepById: (id, updates) =>
|
||||
set((s) => ({
|
||||
traceSteps: s.traceSteps.map((t) =>
|
||||
t.id === id ? { ...t, ...updates } : t,
|
||||
),
|
||||
})),
|
||||
|
||||
updateLastTraceStep: (updates) =>
|
||||
set((s) => {
|
||||
if (s.traceSteps.length === 0) return s;
|
||||
@@ -376,11 +387,13 @@ export const useAgentStore = create<AgentState>((set, get) => ({
|
||||
tokenUsage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },
|
||||
traceSteps: [],
|
||||
isStreaming: false,
|
||||
streamingContent: '',
|
||||
currentRunId: null,
|
||||
}),
|
||||
|
||||
abort: () => {
|
||||
const sessionId = get().currentSessionId;
|
||||
// 不清除 currentRunId — 保留用于过滤旧 run 的延迟终止事件(DONE/TERMINATED)
|
||||
// 旧 run 的 DONE 到达时会匹配 currentRunId,处理后清除;新 run 的 INIT 会覆盖
|
||||
set({ agentStatus: 'idle', isStreaming: false });
|
||||
if (sessionId && window.metona?.agent?.abortSession) {
|
||||
window.metona.agent.abortSession(sessionId).catch((err) => { console.error('[AgentStore]', err); });
|
||||
|
||||
Reference in New Issue
Block a user