Files
claude-code/src/utils/permissions/bypassPermissionsKillswitch.ts
claude-code-best 92f8a92fbb feat: 正式启用 auto mode (#307)
* fix: 修复settings.json内存状态溢出的问题

* fix: 修复auto mode gate check未处理的promise rejection

在 bypassPermissionsKillswitch.ts 的 useKickOffCheckAndDisableAutoModeIfNeeded
中,void fire-and-forget 调用缺少 .catch() 处理,导致 verifyAutoModeGateAccess
失败时产生 unhandled promise rejection。同时移除 permissionSetup.ts 中冗余的
null check。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: 开放 auto mode 和 bypass mode 给所有用户

通过 Shift+Tab 统一循环:default → acceptEdits → plan → auto → bypassPermissions → default

- 移除 USER_TYPE 分支判断,所有用户使用同一循环路径
- isBypassPermissionsModeAvailable 始终为 true
- isAutoModeAvailable 初始化直接为 true
- 移除 AutoModeOptInDialog 确认流程
- 简化 isAutoModeGateEnabled 仅保留快模式熔断器
- 简化 verifyAutoModeGateAccess 仅检查快模式
- 移除 GrowthBook/Statsig 远程门控
- bypass permissions killswitch 改为 no-op
- 新增 24 个测试覆盖循环逻辑和门控不变量

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: 为sideQuery添加Langfuse追踪

sideQuery 绕过了 claude.ts 的主 API 路径,导致所有走 sideQuery 的调用
(auto mode classifier、permission explainer、session search 等)都没有
Langfuse 记录。现在为每次 sideQuery 调用创建独立 trace 并记录 LLM observation,
未配置 Langfuse 时全部 no-op。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: ACP availableModes 补齐 bypassPermissions 并修正测试 import 路径

- ACP agent availableModes 按条件包含 bypassPermissions(非 root/sandbox)
- 顺序对齐 REPL 循环:default → acceptEdits → plan → auto → bypassPermissions
- 新增 2 个测试验证 availableModes 包含 bypassPermissions 及模式切换
- 修正 getNextPermissionMode.test.ts 和 permissionSetup.test.ts 的 import 路径

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-20 10:20:27 +08:00

114 lines
3.5 KiB
TypeScript

import { feature } from 'bun:bundle'
import { useEffect, useRef } from 'react'
import { useNotifications } from 'src/context/notifications.js'
import { toError } from '../../utils/errors.js'
import { logError } from '../../utils/log.js'
import { getIsRemoteMode } from '../../bootstrap/state.js'
import { useAppState, useAppStateStore, useSetAppState } from '../../state/AppState.js'
import type { ToolPermissionContext } from '../../Tool.js'
import {
verifyAutoModeGateAccess,
} from './permissionSetup.js'
/**
* No-op — bypass permissions is always available.
*/
export async function checkAndDisableBypassPermissionsIfNeeded(
_toolPermissionContext: ToolPermissionContext,
_setAppState: (f: (prev: import('../../state/AppState.js').AppState) => import('../../state/AppState.js').AppState) => void,
): Promise<void> {
// Bypass permissions is always available — no gate check needed
}
/**
* Reset stub — kept for interface compatibility.
*/
export function resetBypassPermissionsCheck(): void {
// No-op
}
/**
* No-op hook — bypass permissions is always available.
*/
export function useKickOffCheckAndDisableBypassPermissionsIfNeeded(): void {
// No-op
}
let autoModeCheckRan = false
export async function checkAndDisableAutoModeIfNeeded(
toolPermissionContext: ToolPermissionContext,
setAppState: (f: (prev: import('../../state/AppState.js').AppState) => import('../../state/AppState.js').AppState) => void,
fastMode?: boolean,
): Promise<void> {
if (feature('TRANSCRIPT_CLASSIFIER')) {
if (autoModeCheckRan) {
return
}
autoModeCheckRan = true
const { updateContext, notification } = await verifyAutoModeGateAccess(
toolPermissionContext,
fastMode,
)
setAppState(prev => {
const nextCtx = updateContext(prev.toolPermissionContext)
const newState =
nextCtx === prev.toolPermissionContext
? prev
: { ...prev, toolPermissionContext: nextCtx }
if (!notification) return newState
return {
...newState,
notifications: {
...newState.notifications,
queue: [
...newState.notifications.queue,
{
key: 'auto-mode-gate-notification',
text: notification,
color: 'warning' as const,
priority: 'high' as const,
},
],
},
}
})
}
}
/**
* Reset the run-once flag for checkAndDisableAutoModeIfNeeded.
* Call this after /login so the gate check re-runs with the new org.
*/
export function resetAutoModeGateCheck(): void {
autoModeCheckRan = false
}
export function useKickOffCheckAndDisableAutoModeIfNeeded(): void {
const mainLoopModel = useAppState(s => s.mainLoopModel)
const mainLoopModelForSession = useAppState(s => s.mainLoopModelForSession)
const fastMode = useAppState(s => s.fastMode)
const setAppState = useSetAppState()
const store = useAppStateStore()
const isFirstRunRef = useRef(true)
// Runs on mount (startup check) AND whenever the model or fast mode changes
useEffect(() => {
if (getIsRemoteMode()) return
if (isFirstRunRef.current) {
isFirstRunRef.current = false
} else {
resetAutoModeGateCheck()
}
void checkAndDisableAutoModeIfNeeded(
store.getState().toolPermissionContext,
setAppState,
fastMode,
).catch(error => {
logError(new Error('Auto mode gate check failed', { cause: toError(error) }))
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [mainLoopModel, mainLoopModelForSession, fastMode])
}