Files
claude-code/src/services/acp/agent/permissionMode.ts
claude-code-best 0f2eec496c feat(acp): bypassPermissions 默认显示,去掉 opt-in 限制
之前 bypassPermissions 需要本地显式 opt-in(ACP_PERMISSION_MODE 环境变量、
CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS 环境变量、或 settings.permissions.defaultMode)
才会出现在 modes 列表里 —— 标准客户端看不到这个 mode,永远没法切换。

去掉 opt-in 后,只要进程级允许(非 root 或 IS_SANDBOX=1)就显示。
- permissionMode: isAcpBypassPermissionModeAvailable 只保留进程级检查,删除
  isAcpBypassLocallyEnabled / isSettingsBypassPermissionMode / isTruthyEnv 等
  只服务于 opt-in 的辅助函数
- createSessionMethod: 调用方去掉 settingsMode 参数
- agent.test: 反转所有依赖 "bypass 需要 opt-in" 的断言

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
2026-06-20 12:38:43 +08:00

103 lines
3.0 KiB
TypeScript

import type { PermissionMode } from '../../../types/permissions.js'
import { resolvePermissionMode } from '../utils.js'
export const permissionModeIds: readonly PermissionMode[] = [
'auto',
'default',
'acceptEdits',
'bypassPermissions',
'dontAsk',
'plan',
]
export function isPermissionMode(modeId: string): modeId is PermissionMode {
return (permissionModeIds as readonly string[]).includes(modeId)
}
export function resolveSessionPermissionMode(
metaMode: unknown,
hasMetaMode: boolean,
settingsMode: unknown,
): PermissionMode {
if (hasMetaMode) {
const metaResolved = resolveRequiredPermissionMode(
metaMode,
'_meta.permissionMode',
)
if (
metaResolved === 'bypassPermissions' &&
!isAcpBypassPermissionModeAvailable()
) {
throw new Error(
'Mode not available: bypassPermissions cannot run as root (start the agent as a non-root user, or set IS_SANDBOX=1).',
)
}
return metaResolved
}
const settingsResolved = resolveConfiguredPermissionMode(settingsMode)
return settingsResolved ?? 'default'
}
function resolveRequiredPermissionMode(
mode: unknown,
source: string,
): PermissionMode {
if (mode === undefined || mode === null) {
throw new Error(`Invalid ${source}: expected a string.`)
}
return resolvePermissionMode(mode, source) as PermissionMode
}
function resolveConfiguredPermissionMode(
mode: unknown,
): PermissionMode | undefined {
if (mode === undefined || mode === null) return undefined
try {
return resolvePermissionMode(
mode,
'permissions.defaultMode',
) as PermissionMode
} catch (err: unknown) {
const reason = err instanceof Error ? err.message : String(err)
console.error(
'[ACP] Invalid permissions.defaultMode, using default:',
reason,
)
return undefined
}
}
export function hasOwnField(
value: Record<string, unknown> | null | undefined,
key: string,
): boolean {
return !!value && Object.hasOwn(value, key)
}
/**
* Whether bypassPermissions is selectable by ACP clients.
*
* The previous implementation required a local opt-in (ACP_PERMISSION_MODE env var,
* CLAUDE_CODE_ACP_ALLOW_BYPASS_PERMISSIONS env var, or settings.permissions.defaultMode).
* That gate made the mode invisible to standard clients unless the operator already
* pre-configured it — defeating the point of exposing it through the ACP mode list.
*
* The only remaining guard is the process-level one: bypass must not silently run
* as root (where every skipped permission check is a privilege boundary crossed),
* unless explicitly marked as a sandbox.
*/
export function isAcpBypassPermissionModeAvailable(): boolean {
return isProcessBypassPermissionModeAvailable()
}
function isProcessBypassPermissionModeAvailable(): boolean {
if (process.env.IS_SANDBOX) return true
if (typeof process.geteuid === 'function') return process.geteuid() !== 0
if (typeof process.getuid === 'function') return process.getuid() !== 0
return true
}