From 8c5fd91d0570841557f6a898a41fd736ab5869e0 Mon Sep 17 00:00:00 2001 From: AI Briefing Date: Mon, 16 Mar 2026 18:55:10 +0800 Subject: [PATCH 1/6] fix: route auto-approve through CodePilot Handle permission prompts in CodePilot instead of relying on Claude Code bypass mode so auto-approve still works in environments where bypassPermissions is rejected. --- src/hooks/useSSEStream.ts | 2 ++ src/lib/bridge/permission-broker.ts | 10 ++++++- src/lib/claude-client.ts | 46 +++++++++++++++++++++-------- src/types/index.ts | 1 + 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/hooks/useSSEStream.ts b/src/hooks/useSSEStream.ts index 14c39fc3..c4bb33e4 100644 --- a/src/hooks/useSSEStream.ts +++ b/src/hooks/useSSEStream.ts @@ -10,6 +10,7 @@ interface ToolUseInfo { interface ToolResultInfo { tool_use_id: string; content: string; + is_error?: boolean; } export interface SSECallbacks { @@ -73,6 +74,7 @@ function handleSSEEvent( callbacks.onToolResult({ tool_use_id: resultData.tool_use_id, content: resultData.content, + is_error: resultData.is_error, }); } catch { // skip malformed tool_result data diff --git a/src/lib/bridge/permission-broker.ts b/src/lib/bridge/permission-broker.ts index 395a1245..bf2056c7 100644 --- a/src/lib/bridge/permission-broker.ts +++ b/src/lib/bridge/permission-broker.ts @@ -13,7 +13,7 @@ import type { PermissionUpdate } from '@anthropic-ai/claude-agent-sdk'; import type { ChannelAddress, OutboundMessage } from './types'; import type { BaseChannelAdapter } from './channel-adapter'; import { deliver } from './delivery-layer'; -import { insertPermissionLink, getPermissionLink, markPermissionLinkResolved, getSession, getDb } from '../db'; +import { insertPermissionLink, getPermissionLink, markPermissionLinkResolved, getSession, getDb, getSetting } from '../db'; import { resolvePendingPermission } from '../permission-registry'; import { escapeHtml } from './adapters/telegram-utils'; @@ -36,6 +36,14 @@ export async function forwardPermissionRequest( suggestions?: unknown[], replyToMessageId?: string, ): Promise { + // Check if auto-approval is enabled globally — auto-approve without IM notification + const globalAutoApprove = getSetting('dangerously_skip_permissions') === 'true'; + if (globalAutoApprove) { + console.log(`[bridge] Auto-approved permission ${permissionRequestId} (tool=${toolName}) due to global auto-approval setting`); + resolvePendingPermission(permissionRequestId, { behavior: 'allow' }); + return; + } + // Check if this session uses full_access permission profile — auto-approve without IM notification if (sessionId) { const session = getSession(sessionId); diff --git a/src/lib/claude-client.ts b/src/lib/claude-client.ts index a0c21db4..d8089497 100644 --- a/src/lib/claude-client.ts +++ b/src/lib/claude-client.ts @@ -19,6 +19,7 @@ import { registerPendingPermission } from './permission-registry'; import { registerConversation, unregisterConversation } from './conversation-registry'; import { captureCapabilities, setCachedPlugins } from './agent-sdk-capabilities'; import { getSetting, updateSdkSessionId, createPermissionRequest } from './db'; +// Auto-approval is now handled at the CodePilot level, not via SDK bypassPermissions import { resolveForClaudeCode, toClaudeCodeEnv } from './provider-resolver'; import { findClaudeBinary, findGitBash, getExpandedPath, invalidateClaudePathCache } from './platform'; import { notifyPermissionRequest, notifyGeneric } from './telegram-bot'; @@ -315,14 +316,26 @@ export async function generateTextViaSdk(params: { const queryOptions: Options = { cwd: os.homedir(), abortController, - permissionMode: 'bypassPermissions', - allowDangerouslySkipPermissions: true, + permissionMode: 'acceptEdits', env: sanitizeEnv(sdkEnv), settingSources: resolved.settingSources as Options['settingSources'], systemPrompt: params.system, maxTurns: 1, }; + // Add auto-approval handler for this simple query + const globalAutoApprove = getSetting('dangerously_skip_permissions') === 'true'; + if (globalAutoApprove) { + queryOptions.canUseTool = async (toolName, _input, opts) => { + console.log(`[claude-client] Auto-approved ${toolName} (auto-approval enabled)`); + return { + behavior: 'allow', + updatedInput: _input, + ...(opts.suggestions ? { updatedPermissions: opts.suggestions } : {}), + }; + }; + } + if (params.model) { queryOptions.model = params.model; } @@ -443,17 +456,15 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream { const permissionRequestId = `perm-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; + // If auto-approval is enabled, approve immediately without user interaction + if (shouldAutoApprove) { + console.log(`[claude-client] Auto-approved ${toolName} (auto-approval enabled)`); + return { + behavior: 'allow', + updatedInput: input, + ...(opts.suggestions ? { updatedPermissions: opts.suggestions } : {}), + }; + } + const permEvent: PermissionRequestEvent = { permissionRequestId, toolName, @@ -856,6 +873,9 @@ export function streamClaude(options: ClaudeStreamOptions): ReadableStream Date: Tue, 17 Mar 2026 17:09:46 +0800 Subject: [PATCH 2/6] chore: improve stop script to kill port 3000 processes --- start.sh | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100755 start.sh diff --git a/start.sh b/start.sh new file mode 100755 index 00000000..441ce0ea --- /dev/null +++ b/start.sh @@ -0,0 +1,183 @@ +#!/bin/bash + +# CodePilot 后台启动脚本 + +APP_DIR="$(cd "$(dirname "$0")" && pwd)" +LOG_FILE="$APP_DIR/app.log" +PID_FILE="$APP_DIR/app.pid" + +# 显示菜单 +show_menu() { + clear + echo "========================================" + echo " CodePilot 管理菜单" + echo "========================================" + echo "" + + # 检查状态 + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then + echo " 当前状态: 运行中 (PID: $(cat "$PID_FILE"))" + else + echo " 当前状态: 未运行" + fi + + echo "" + echo " [1] 启动服务" + echo " [2] 停止服务" + echo " [3] 重启服务" + echo " [4] 查看实时日志" + echo " [5] 查看状态" + echo " [6] 查看日志 (最后50行)" + echo " [0] 退出" + echo "" + echo "========================================" +} + +# 启动服务 +do_start() { + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then + echo "服务已在运行 (PID: $(cat "$PID_FILE"))" + return + fi + + cd "$APP_DIR" || exit 1 + nohup npm run start > "$LOG_FILE" 2>&1 & + echo $! > "$PID_FILE" + echo "启动成功,PID: $!" +} + +# 停止服务 +do_stop() { + # 先尝试停止 PID 文件中的进程 + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if kill -0 "$PID" 2>/dev/null; then + kill "$PID" 2>/dev/null + sleep 1 + # 如果进程还在,强制杀掉 + if kill -0 "$PID" 2>/dev/null; then + kill -9 "$PID" 2>/dev/null + fi + echo "已停止 (PID: $PID)" + else + echo "进程不存在" + fi + rm -f "$PID_FILE" + else + echo "服务未运行" + fi + + # 强制清理占用端口 3000 的进程 + if ss -tlnp 2>/dev/null | grep -q ":3000 "; then + echo "清理残留进程..." + fuser -k 3000/tcp 2>/dev/null + sleep 1 + # 再次强制清理 + lsof -ti:3000 | xargs -r kill -9 2>/dev/null + fi +} + +# 查看状态 +do_status() { + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then + echo "运行中 (PID: $(cat "$PID_FILE"))" + else + echo "未运行" + fi +} + +# 查看日志 +do_log() { + tail -f "$LOG_FILE" +} + +# 菜单模式 +menu_mode() { + while true; do + show_menu + read -p "请选择操作 [0-6]: " choice + + case $choice in + 1) + echo "" + do_start + read -p "按回车键继续..." + ;; + 2) + echo "" + do_stop + read -p "按回车键继续..." + ;; + 3) + echo "" + do_stop + sleep 1 + do_start + read -p "按回车键继续..." + ;; + 4) + echo "" + echo "按 Ctrl+C 退出日志查看" + do_log + ;; + 5) + echo "" + do_status + read -p "按回车键继续..." + ;; + 6) + echo "" + if [ -f "$LOG_FILE" ]; then + tail -n 50 "$LOG_FILE" + else + echo "暂无日志文件" + fi + read -p "按回车键继续..." + ;; + 0) + echo "" + echo "再见!" + exit 0 + ;; + *) + echo "" + echo "无效选项" + read -p "按回车键继续..." + ;; + esac + done +} + +# 命令行模式 +case "${1:-menu}" in + start) + do_start + ;; + stop) + do_stop + ;; + restart) + do_stop + sleep 1 + do_start + ;; + status) + do_status + ;; + log) + do_log + ;; + menu) + menu_mode + ;; + *) + echo "用法: $0 {start|stop|restart|status|log|menu}" + echo "" + echo " start - 后台启动服务" + echo " stop - 停止服务" + echo " restart- 重启服务" + echo " status - 查看运行状态" + echo " log - 查看实时日志" + echo " menu - 显示交互菜单(默认)" + ;; +esac From 3ea2c9ebde870217e8f028d2197ad2942eab7b79 Mon Sep 17 00:00:00 2001 From: HNGM-HP <542869290@qq.com> Date: Sat, 21 Mar 2026 11:53:25 +0800 Subject: [PATCH 3/6] fix: improve stop script to kill all related processes --- start.sh | 55 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/start.sh b/start.sh index 441ce0ea..03df74f9 100755 --- a/start.sh +++ b/start.sh @@ -48,32 +48,59 @@ do_start() { # 停止服务 do_stop() { - # 先尝试停止 PID 文件中的进程 + local stopped=0 + + # 1. 先尝试停止 PID 文件中的进程及其子进程 if [ -f "$PID_FILE" ]; then PID=$(cat "$PID_FILE") if kill -0 "$PID" 2>/dev/null; then - kill "$PID" 2>/dev/null - sleep 1 + # 杀掉整个进程组(包括子进程) + kill -TERM -"$PID" 2>/dev/null || kill -TERM "$PID" 2>/dev/null + sleep 2 # 如果进程还在,强制杀掉 if kill -0 "$PID" 2>/dev/null; then - kill -9 "$PID" 2>/dev/null + kill -9 -"$PID" 2>/dev/null || kill -9 "$PID" 2>/dev/null fi echo "已停止 (PID: $PID)" - else - echo "进程不存在" + stopped=1 fi rm -f "$PID_FILE" - else - echo "服务未运行" fi - # 强制清理占用端口 3000 的进程 - if ss -tlnp 2>/dev/null | grep -q ":3000 "; then - echo "清理残留进程..." - fuser -k 3000/tcp 2>/dev/null + # 2. 强制清理占用端口 3000 的所有进程 + local port_pids=$(lsof -ti:3000 2>/dev/null) + if [ -n "$port_pids" ]; then + echo "清理端口 3000 残留进程: $port_pids" + echo "$port_pids" | xargs kill -9 2>/dev/null + sleep 1 + stopped=1 + fi + + # 3. 清理所有 node/next 相关进程(当前目录下启动的) + local node_pids=$(pgrep -f "node.*$APP_DIR" 2>/dev/null) + if [ -n "$node_pids" ]; then + echo "清理 Node 进程: $node_pids" + echo "$node_pids" | xargs kill -9 2>/dev/null + stopped=1 + fi + + # 4. 再次检查端口 + if lsof -i:3000 >/dev/null 2>&1; then + echo "仍有进程占用端口 3000,强制清理..." + fuser -k -9 3000/tcp 2>/dev/null sleep 1 - # 再次强制清理 - lsof -ti:3000 | xargs -r kill -9 2>/dev/null + fi + + if [ "$stopped" -eq 0 ]; then + echo "服务未运行" + fi + + # 最终确认 + if lsof -i:3000 >/dev/null 2>&1; then + echo "警告: 端口 3000 仍被占用" + lsof -i:3000 + else + echo "端口 3000 已释放" fi } From cc8fd848a2078b75d89792627292165995780522 Mon Sep 17 00:00:00 2001 From: HNGM-HP <542869290@qq.com> Date: Sat, 21 Mar 2026 12:01:18 +0800 Subject: [PATCH 4/6] fix: use ss instead of lsof for reliable port detection --- start.sh | 105 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 82 insertions(+), 23 deletions(-) diff --git a/start.sh b/start.sh index 03df74f9..b5fdf1eb 100755 --- a/start.sh +++ b/start.sh @@ -14,9 +14,14 @@ show_menu() { echo "========================================" echo "" - # 检查状态 + # 检查状态(使用 ss 更可靠) + local port_info=$(ss -tlnp 2>/dev/null | grep ":3000 ") + local port_pid=$(echo "$port_info" | sed -n 's/.*pid=\([0-9]*\).*/\1/p') + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then echo " 当前状态: 运行中 (PID: $(cat "$PID_FILE"))" + elif [ -n "$port_pid" ]; then + echo " 当前状态: 端口被占用 (PID: $port_pid) - 异常" else echo " 当前状态: 未运行" fi @@ -35,15 +40,44 @@ show_menu() { # 启动服务 do_start() { + # 先检查端口是否已被占用 + local port_info=$(ss -tlnp 2>/dev/null | grep ":3000 ") + if [ -n "$port_info" ]; then + local port_pid=$(echo "$port_info" | sed -n 's/.*pid=\([0-9]*\).*/\1/p') + echo "端口 3000 已被占用 (PID: $port_pid)" + echo "请先运行 ./start.sh stop 停止服务" + return 1 + fi + if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then echo "服务已在运行 (PID: $(cat "$PID_FILE"))" - return + return 0 fi cd "$APP_DIR" || exit 1 + + # 启动服务并等待端口监听 nohup npm run start > "$LOG_FILE" 2>&1 & - echo $! > "$PID_FILE" - echo "启动成功,PID: $!" + local npm_pid=$! + echo $npm_pid > "$PID_FILE" + + echo "正在启动服务..." + + # 等待端口监听(最多等待 30 秒,使用 ss 检测) + local waited=0 + while [ $waited -lt 30 ]; do + sleep 1 + waited=$((waited + 1)) + local check_info=$(ss -tlnp 2>/dev/null | grep ":3000 ") + if [ -n "$check_info" ]; then + local server_pid=$(echo "$check_info" | sed -n 's/.*pid=\([0-9]*\).*/\1/p') + echo "启动成功 (PID: $server_pid)" + return 0 + fi + done + + echo "启动超时,请检查日志: $LOG_FILE" + return 1 } # 停止服务 @@ -67,28 +101,24 @@ do_stop() { rm -f "$PID_FILE" fi - # 2. 强制清理占用端口 3000 的所有进程 - local port_pids=$(lsof -ti:3000 2>/dev/null) + # 2. 强制清理占用端口 3000 的所有进程(使用 ss 更可靠) + local port_pids=$(ss -tlnp 2>/dev/null | grep ":3000 " | sed -n 's/.*pid=\([0-9]*\).*/\1/p' | sort -u) if [ -n "$port_pids" ]; then echo "清理端口 3000 残留进程: $port_pids" - echo "$port_pids" | xargs kill -9 2>/dev/null + for pid in $port_pids; do + kill -9 "$pid" 2>/dev/null && echo " 已杀掉 PID: $pid" + done sleep 1 stopped=1 fi - # 3. 清理所有 node/next 相关进程(当前目录下启动的) - local node_pids=$(pgrep -f "node.*$APP_DIR" 2>/dev/null) - if [ -n "$node_pids" ]; then - echo "清理 Node 进程: $node_pids" - echo "$node_pids" | xargs kill -9 2>/dev/null - stopped=1 - fi - - # 4. 再次检查端口 - if lsof -i:3000 >/dev/null 2>&1; then - echo "仍有进程占用端口 3000,强制清理..." - fuser -k -9 3000/tcp 2>/dev/null + # 3. 备用方式:使用 lsof + local lsof_pids=$(lsof -ti:3000 2>/dev/null) + if [ -n "$lsof_pids" ]; then + echo "清理残留进程 (lsof): $lsof_pids" + echo "$lsof_pids" | xargs kill -9 2>/dev/null sleep 1 + stopped=1 fi if [ "$stopped" -eq 0 ]; then @@ -96,9 +126,9 @@ do_stop() { fi # 最终确认 - if lsof -i:3000 >/dev/null 2>&1; then + if ss -tlnp 2>/dev/null | grep -q ":3000 "; then echo "警告: 端口 3000 仍被占用" - lsof -i:3000 + ss -tlnp | grep ":3000 " else echo "端口 3000 已释放" fi @@ -106,10 +136,39 @@ do_stop() { # 查看状态 do_status() { + local has_pid=0 + local has_port=0 + + # 检查 PID 文件 if [ -f "$PID_FILE" ] && kill -0 "$(cat "$PID_FILE")" 2>/dev/null; then - echo "运行中 (PID: $(cat "$PID_FILE"))" + has_pid=1 + echo "PID 文件: 运行中 (PID: $(cat "$PID_FILE"))" + else + echo "PID 文件: 未运行" + [ -f "$PID_FILE" ] && rm -f "$PID_FILE" + fi + + # 检查端口占用(使用 ss 更可靠) + local port_info=$(ss -tlnp 2>/dev/null | grep ":3000 ") + if [ -n "$port_info" ]; then + has_port=1 + local port_pid=$(echo "$port_info" | sed -n 's/.*pid=\([0-9]*\).*/\1/p') + echo "端口 3000: 被占用 (PID: $port_pid)" + else + echo "端口 3000: 空闲" + fi + + # 综合状态 + echo "" + if [ "$has_pid" -eq 1 ] && [ "$has_port" -eq 1 ]; then + echo ">>> 服务正常运行" + elif [ "$has_pid" -eq 0 ] && [ "$has_port" -eq 1 ]; then + echo ">>> 异常: 端口被占用但 PID 文件不存在" + echo ">>> 建议: 运行 ./start.sh stop 清理残留进程" + elif [ "$has_pid" -eq 1 ] && [ "$has_port" -eq 0 ]; then + echo ">>> 异常: PID 存在但端口未监听" else - echo "未运行" + echo ">>> 服务未运行" fi } From c653c7e241d7dbc1d35369878cef2afe094aa18f Mon Sep 17 00:00:00 2001 From: HNGM-HP <542869290@qq.com> Date: Fri, 27 Mar 2026 13:29:43 +0800 Subject: [PATCH 5/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E5=89=8D=E5=AD=98=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + package-lock.json | 106 +++++++++++++++++- src/__tests__/unit/claude-permissions.test.ts | 60 ++++++++++ src/lib/claude-permissions.ts | 45 ++++++++ 4 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/__tests__/unit/claude-permissions.test.ts create mode 100644 src/lib/claude-permissions.ts diff --git a/.gitignore b/.gitignore index 5c6e4eef..c75b266c 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,6 @@ apps/site/node_modules/ # claude code session data .claude/ +app.log +app.pid +AGENTS.* \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index ed584417..77f315c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2854,6 +2854,72 @@ "node": ">= 10.0.0" } }, + "node_modules/@electron/windows-sign": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@electron/windows-sign/-/windows-sign-1.2.2.tgz", + "integrity": "sha512-dfZeox66AvdPtb2lD8OsIIQh12Tp0GNCRUDfBHIKGpbmopZto2/A8nSpYYLoedPIHpqkeblZ/k8OV0Gy7PYuyQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "peer": true, + "dependencies": { + "cross-dirname": "^0.1.0", + "debug": "^4.3.4", + "fs-extra": "^11.1.1", + "minimist": "^1.2.8", + "postject": "^1.0.0-alpha.6" + }, + "bin": { + "electron-windows-sign": "bin/electron-windows-sign.js" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/fs-extra": { + "version": "11.3.4", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", + "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/@electron/windows-sign/node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/@electron/windows-sign/node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -11845,6 +11911,15 @@ "buffer": "^5.1.0" } }, + "node_modules/cross-dirname": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/cross-dirname/-/cross-dirname-0.1.0.tgz", + "integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -14988,7 +15063,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -21241,6 +21315,36 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/postject/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/powershell-utils": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/powershell-utils/-/powershell-utils-0.1.0.tgz", diff --git a/src/__tests__/unit/claude-permissions.test.ts b/src/__tests__/unit/claude-permissions.test.ts new file mode 100644 index 00000000..40cdc325 --- /dev/null +++ b/src/__tests__/unit/claude-permissions.test.ts @@ -0,0 +1,60 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; + +import { + DANGEROUSLY_SKIP_PERMISSIONS_UNSUPPORTED_CODE, + getDangerouslySkipPermissionsSupport, + isDangerouslySkipPermissionsSupported, +} from '../../lib/claude-permissions'; + +describe('claude-permissions', () => { + it('allows auto-approve for non-root users on linux', () => { + const result = getDangerouslySkipPermissionsSupport({ + platform: 'linux', + uid: 1000, + env: {} as NodeJS.ProcessEnv, + }); + + assert.equal(result.supported, true); + assert.equal( + isDangerouslySkipPermissionsSupported({ + platform: 'linux', + uid: 1000, + env: {} as NodeJS.ProcessEnv, + }), + true, + ); + }); + + it('blocks auto-approve for unsandboxed root on linux', () => { + const result = getDangerouslySkipPermissionsSupport({ + platform: 'linux', + uid: 0, + env: {} as NodeJS.ProcessEnv, + }); + + assert.equal(result.supported, false); + assert.equal(result.reasonCode, DANGEROUSLY_SKIP_PERMISSIONS_UNSUPPORTED_CODE); + assert.match(result.reason || '', /root\/sudo/i); + }); + + it('allows auto-approve for sandboxed root on linux', () => { + const result = getDangerouslySkipPermissionsSupport({ + platform: 'linux', + uid: 0, + env: { IS_SANDBOX: '1' } as unknown as NodeJS.ProcessEnv, + }); + + assert.equal(result.supported, true); + }); + + it('allows auto-approve on windows', () => { + const result = getDangerouslySkipPermissionsSupport({ + platform: 'win32', + uid: 0, + env: {} as NodeJS.ProcessEnv, + }); + + assert.equal(result.supported, true); + }); +}); diff --git a/src/lib/claude-permissions.ts b/src/lib/claude-permissions.ts new file mode 100644 index 00000000..0a186b27 --- /dev/null +++ b/src/lib/claude-permissions.ts @@ -0,0 +1,45 @@ +export const DANGEROUSLY_SKIP_PERMISSIONS_UNSUPPORTED_CODE = 'DANGEROUSLY_SKIP_PERMISSIONS_ROOT_UNSUPPORTED' as const; + +export interface DangerousSkipPermissionsSupport { + supported: boolean; + reasonCode?: typeof DANGEROUSLY_SKIP_PERMISSIONS_UNSUPPORTED_CODE; + reason?: string; +} + +interface DangerousSkipPermissionsSupportOptions { + platform?: NodeJS.Platform; + uid?: number; + env?: NodeJS.ProcessEnv; +} + +export function getDangerouslySkipPermissionsSupport( + options: DangerousSkipPermissionsSupportOptions = {}, +): DangerousSkipPermissionsSupport { + const platform = options.platform ?? process.platform; + const env = options.env ?? process.env; + const uid = options.uid ?? (typeof process.getuid === 'function' ? process.getuid() : undefined); + + if (platform === 'win32') { + return { supported: true }; + } + + if (uid !== 0) { + return { supported: true }; + } + + if (env.IS_SANDBOX === '1' || env.CLAUDE_CODE_BUBBLEWRAP === '1') { + return { supported: true }; + } + + return { + supported: false, + reasonCode: DANGEROUSLY_SKIP_PERMISSIONS_UNSUPPORTED_CODE, + reason: 'Auto-approve is unavailable when CodePilot runs as root/sudo. Run the app as a regular user to enable it.', + }; +} + +export function isDangerouslySkipPermissionsSupported( + options: DangerousSkipPermissionsSupportOptions = {}, +): boolean { + return getDangerouslySkipPermissionsSupport(options).supported; +} From 9ce7e6042ec8e97da2aceb6cd6cb3e9e0668e68e Mon Sep 17 00:00:00 2001 From: HNGM-HP <542869290@qq.com> Date: Fri, 27 Mar 2026 13:36:54 +0800 Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=A7=BB=E5=8A=A8?= =?UTF-8?q?=E7=AB=AFweb=EF=BC=8C=E4=BC=9A=E8=AF=9D=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E7=9A=84BUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/layout/AppShell.tsx | 7 ++++ src/components/layout/ChatListPanel.tsx | 43 +++++++++++++++++++------ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/components/layout/AppShell.tsx b/src/components/layout/AppShell.tsx index df76a2d7..0f00421f 100644 --- a/src/components/layout/AppShell.tsx +++ b/src/components/layout/AppShell.tsx @@ -104,6 +104,13 @@ export function AppShell({ children }: { children: React.ReactNode }) { }, []); /* eslint-enable react-hooks/set-state-in-effect */ + // Listen for mobile close events + useEffect(() => { + const handler = () => setChatListOpenRaw(false); + window.addEventListener("chatlist-close", handler); + return () => window.removeEventListener("chatlist-close", handler); + }, []); + // Panel width state with localStorage persistence const [chatListWidth, setChatListWidth] = useState(240); diff --git a/src/components/layout/ChatListPanel.tsx b/src/components/layout/ChatListPanel.tsx index 08ca1c58..f8bb084d 100644 --- a/src/components/layout/ChatListPanel.tsx +++ b/src/components/layout/ChatListPanel.tsx @@ -7,6 +7,7 @@ import { FileArrowDown, Plus, FolderOpen, + X, } from "@/components/ui/icon"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -385,14 +386,37 @@ export function ChatListPanel({ open, width }: ChatListPanelProps) { if (!open) return null; return ( - + ); }