diff --git a/web/frontend/src/components/chat/chat-page.tsx b/web/frontend/src/components/chat/chat-page.tsx
index 3ad811dae..aa479d776 100644
--- a/web/frontend/src/components/chat/chat-page.tsx
+++ b/web/frontend/src/components/chat/chat-page.tsx
@@ -25,6 +25,7 @@ import type { ConnectionState } from "@/store/chat"
import type { ChatAttachment } from "@/store/chat"
import { showAssistantDetailsAtom } from "@/store/chat"
import type { GatewayState } from "@/store/gateway"
+import { PermissionPrompt } from "@/components/permission/PermissionPrompt"
const MAX_IMAGE_SIZE_BYTES = 7 * 1024 * 1024
const MAX_IMAGE_SIZE_LABEL = "7 MB"
@@ -115,6 +116,11 @@ export function ChatPage() {
const [showAssistantDetails, setShowAssistantDetails] = useAtom(
showAssistantDetailsAtom,
)
+ const [permissionRequest, setPermissionRequest] = useState<{
+ toolName: string
+ path: string
+ originalCommand: string
+ } | null>(null)
const {
messages,
@@ -191,6 +197,45 @@ export function ChatPage() {
}
}
+ // Check for permission requests in messages
+ useEffect(() => {
+ if (permissionRequest) return // Already showing a permission prompt
+
+ // Check the last few messages for request_permission tool calls
+ for (let i = messages.length - 1; i >= Math.max(0, messages.length - 5); i--) {
+ const msg = messages[i]
+ if (msg.role !== "assistant") continue
+ if (!msg.toolCalls) continue
+
+ const permCall = msg.toolCalls.find(
+ (tc) => tc.function?.name === "request_permission"
+ )
+ if (!permCall || !permCall.function) continue
+
+ try {
+ const args = JSON.parse(permCall.function.arguments ?? "{}")
+ if (args.path && args.command) {
+ setPermissionRequest({
+ toolName: "request_permission",
+ path: args.path,
+ originalCommand: args.command,
+ })
+ return
+ }
+ } catch {
+ // Ignore parse errors
+ }
+ }
+ }, [messages, permissionRequest])
+
+ const handlePermissionGranted = () => {
+ setPermissionRequest(null)
+ }
+
+ const handlePermissionDenied = () => {
+ setPermissionRequest(null)
+ }
+
const handleAddImages = () => {
if (!canInput) return
fileInputRef.current?.click()
@@ -324,6 +369,18 @@ export function ChatPage() {
/>
)}
+ {permissionRequest && (
+
+ )}
+
{messages.map((msg) => {
if (
!showAssistantDetails &&
diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json
index fa08e1195..0a378c115 100644
--- a/web/frontend/src/i18n/locales/en.json
+++ b/web/frontend/src/i18n/locales/en.json
@@ -49,7 +49,7 @@
"websocketConnecting": "Connecting to chat service... Please wait.",
"websocketDisconnected": "Unable to chat: WebSocket connection is disconnected. Check network and gateway status, then refresh the page or restart Launcher.",
"websocketError": "Unable to chat: WebSocket connection failed. Check network and gateway status, then retry.",
- "noDefaultModel": "Unable to chat: No default model is selected. Set a default model on the Models page."
+ "noDefaultModel": "Unable to chat: No default model is selected. Set a default model on the Models page.",
"permissionRequiredTitle": "Permission Required",
"permissionRequiredMessage": "PicoClaw wants to use {{tool}} to access {{path}} which is outside your workspace.",
"commandToExecute": "Command to execute:",
@@ -58,8 +58,8 @@
"permissionSession": "Allow for session",
"permissionGrantedOnce": "Permission granted for one-time access to {{path}}",
"permissionGrantedSession": "Permission granted for session access to {{path}}",
- "permissionDenied": "Permission denied for {{path}}",
- "permissionError": "An error occurred while handling the permission request.",
+ "permissionDenied": "Permission denied for {{path}}",
+ "permissionError": "An error occurred while handling the permission request."
},
"newChat": "New Chat",
"notConnected": "Gateway is not running. Start it to chat.",
diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json
index 5b41f4f78..59fda8636 100644
--- a/web/frontend/src/i18n/locales/zh.json
+++ b/web/frontend/src/i18n/locales/zh.json
@@ -49,7 +49,7 @@
"websocketConnecting": "正在连接聊天服务,请稍候。",
"websocketDisconnected": "无法对话:WebSocket 连接已断开。请检查网络与服务状态,然后刷新页面或重启 Launcher。",
"websocketError": "无法对话:WebSocket 连接失败。请检查网络与服务状态后重试。",
- "noDefaultModel": "无法对话:尚未设置默认模型。请前往模型页面设置默认模型。"
+ "noDefaultModel": "无法对话:尚未设置默认模型。请前往模型页面设置默认模型。",
"permissionRequiredTitle": "需要权限",
"permissionRequiredMessage": "PicoClaw 想要使用 {{tool}} 访问 {{path}},但该路径位于工作区之外。",
"commandToExecute": "要执行的命令:",
@@ -58,8 +58,8 @@
"permissionSession": "允许整个会话",
"permissionGrantedOnce": "已授予对 {{path}} 的一次性访问权限",
"permissionGrantedSession": "已授予对 {{path}} 的会话访问权限",
- "permissionDenied": "已拒绝对 {{path}} 的访问权限",
- "permissionError": "处理权限请求时发生错误。",
+ "permissionDenied": "已拒绝对 {{path}} 的访问权限",
+ "permissionError": "处理权限请求时发生错误。"
},
"newChat": "新建对话",
"notConnected": "服务未运行,请先启动以进行对话。",