diff --git a/local_agent/src/connection.ts b/local_agent/src/connection.ts index 8d6c6b8..38d95b6 100644 --- a/local_agent/src/connection.ts +++ b/local_agent/src/connection.ts @@ -51,11 +51,13 @@ export function connect(url: string, token: string): void { currentUrl = url; currentToken = token; shouldReconnect = true; + resetSessionApprovalState(); doConnect(); } export function disconnect(): void { shouldReconnect = false; + resetSessionApprovalState(); if (reconnectTimer) { clearTimeout(reconnectTimer); reconnectTimer = null; @@ -157,10 +159,23 @@ const pendingConfirms: Map(); // 本次会话缓存的 sudo 密码(连接断开后清空,绝不写盘) let cachedSudoPassword: string | null = null; +function resetSessionApprovalState(): void { + sessionAlwaysApprove = false; + alwaysApproveChatSessionIds.clear(); + cachedSudoPassword = null; +} + +function getChatSessionId(request: ToolRequest): string | null { + const sessionId = request.args?.__chat_session_id; + return typeof sessionId === "string" && sessionId.length > 0 ? sessionId : null; +} + /** 处理服务端发来的确认响应 */ export function handleConfirmResponse(id: string, approved: boolean, always?: boolean, password?: string): void { const pending = pendingConfirms.get(id); @@ -207,11 +222,13 @@ function needsSudo(command: string): boolean { async function handleRequest(request: ToolRequest): Promise { const command = typeof request.args.command === "string" ? request.args.command : ""; const isSudoCommand = request.tool === "run_command" && needsSudo(command); + const chatSessionId = getChatSessionId(request); + const isSessionApproved = !!chatSessionId && alwaysApproveChatSessionIds.has(chatSessionId); + const isApproved = autoApprove || sessionAlwaysApprove || isSessionApproved; - // Check if dangerous — skip confirmation if autoApprove or sessionAlwaysApprove is on + // Check if dangerous — skip confirmation when current request is already approved. if ( - !autoApprove && - !sessionAlwaysApprove && + !isApproved && request.tool === "run_command" && isDangerous(command) ) { @@ -222,7 +239,12 @@ async function handleRequest(request: ToolRequest): Promise { const result = await requestWebConfirmation(request, requirePassword); if (result.action === "always") { // 「始终允许」:本次会话后续所有命令都跳过确认 - sessionAlwaysApprove = true; + if (chatSessionId) { + alwaysApproveChatSessionIds.add(chatSessionId); + } else { + // 兼容未携带 __chat_session_id 的旧请求:仍在当前连接会话内放行。 + sessionAlwaysApprove = true; + } if (result.password) { cachedSudoPassword = result.password; } diff --git a/root_agent/tools/local_agent.py b/root_agent/tools/local_agent.py index 6e35403..5147f79 100644 --- a/root_agent/tools/local_agent.py +++ b/root_agent/tools/local_agent.py @@ -113,8 +113,11 @@ async def local_list_files( async def _call(tool_context: ToolContext, tool: str, args: dict, device: str = "") -> str: """实际调用 local_agent 的内部方法。""" user_id = tool_context.state.get("__user_id") + chat_session_id = tool_context.state.get("__chat_session_id") if not user_id: return "错误:无法确定用户身份" + if isinstance(chat_session_id, str) and chat_session_id: + args = {**args, "__chat_session_id": chat_session_id} from server.routers.local_agent import ( _connections,