feat(permission): 实现 WebSocket 权限确认机制
重构权限系统,将终端 UI 代码从 core 模块移除,实现基于 WebSocket 的权限确认流程: Core 模块清理: - 删除 permission/prompt.ts 和 file-prompt.ts(终端交互) - 删除 diff.ts 中的 chalk 渲染函数 - 删除 config.ts 中的 inquirer 交互 - 移除 chalk 依赖 Server 权限处理: - 新增 permission/handler.ts,实现 WebSocket 权限请求/响应 - 更新 agent/adapter.ts 设置权限回调 - 更新 ws.ts 处理 permission_response 消息 Web 权限组件: - 新增 PermissionDialog 组件,显示权限请求详情和 Diff - 更新 useChat hook 管理权限状态 - 更新 Chat 页面集成权限弹窗
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { createWebSocket, getMessages, type Message } from '../api/client.js';
|
||||
import type { PermissionRequest } from '../components/PermissionDialog.js';
|
||||
|
||||
interface UseChatOptions {
|
||||
sessionId: string;
|
||||
@@ -19,6 +20,7 @@ interface ChatState {
|
||||
isConnected: boolean;
|
||||
isLoading: boolean;
|
||||
streamingContent: string;
|
||||
permissionRequest: PermissionRequest | null;
|
||||
}
|
||||
|
||||
export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdated }: UseChatOptions) {
|
||||
@@ -27,10 +29,11 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
isConnected: false,
|
||||
isLoading: false,
|
||||
streamingContent: '',
|
||||
permissionRequest: null,
|
||||
});
|
||||
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const reconnectTimeoutRef = useRef<NodeJS.Timeout>();
|
||||
const reconnectTimeoutRef = useRef<ReturnType<typeof setTimeout>>();
|
||||
const reconnectAttemptsRef = useRef(0);
|
||||
const maxReconnectAttempts = 5;
|
||||
// 标记是否正在主动关闭连接(切换 session 时)
|
||||
@@ -144,6 +147,16 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
onSessionUpdatedRef.current?.(message.payload.id, message.payload.name);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'permission_request':
|
||||
// 权限请求
|
||||
if (message.payload) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
permissionRequest: message.payload as PermissionRequest,
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch {
|
||||
// 忽略解析错误
|
||||
@@ -188,6 +201,44 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
setState((prev) => ({ ...prev, isLoading: false, streamingContent: '' }));
|
||||
}, [sessionId]);
|
||||
|
||||
// 发送权限响应
|
||||
const respondToPermission = useCallback(
|
||||
(requestId: string, allow: boolean, remember?: boolean) => {
|
||||
if (!wsRef.current || wsRef.current.readyState !== WebSocket.OPEN) {
|
||||
onErrorRef.current?.(new Error('WebSocket not connected'));
|
||||
return;
|
||||
}
|
||||
|
||||
wsRef.current.send(
|
||||
JSON.stringify({
|
||||
type: 'permission_response',
|
||||
sessionId,
|
||||
payload: { requestId, allow, remember },
|
||||
})
|
||||
);
|
||||
|
||||
// 清除权限请求状态
|
||||
setState((prev) => ({ ...prev, permissionRequest: null }));
|
||||
},
|
||||
[sessionId]
|
||||
);
|
||||
|
||||
// 允许权限请求
|
||||
const allowPermission = useCallback(
|
||||
(requestId: string, remember?: boolean) => {
|
||||
respondToPermission(requestId, true, remember);
|
||||
},
|
||||
[respondToPermission]
|
||||
);
|
||||
|
||||
// 拒绝权限请求
|
||||
const denyPermission = useCallback(
|
||||
(requestId: string, remember?: boolean) => {
|
||||
respondToPermission(requestId, false, remember);
|
||||
},
|
||||
[respondToPermission]
|
||||
);
|
||||
|
||||
// 初始化
|
||||
useEffect(() => {
|
||||
// 重置状态
|
||||
@@ -197,6 +248,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
isConnected: false,
|
||||
isLoading: false,
|
||||
streamingContent: '',
|
||||
permissionRequest: null,
|
||||
});
|
||||
reconnectAttemptsRef.current = 0;
|
||||
|
||||
@@ -225,5 +277,7 @@ export function useChat({ sessionId, onError, onSessionNotFound, onSessionUpdate
|
||||
sendMessage,
|
||||
cancelProcessing,
|
||||
reload: loadMessages,
|
||||
allowPermission,
|
||||
denyPermission,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user