mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-19 23:05:51 +00:00
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>
This commit is contained in:
@@ -1,79 +1,44 @@
|
||||
import { feature } from 'bun:bundle'
|
||||
import { useEffect, useRef } from 'react'
|
||||
import {
|
||||
type AppState,
|
||||
useAppState,
|
||||
useAppStateStore,
|
||||
useSetAppState,
|
||||
} from 'src/state/AppState.js'
|
||||
import type { ToolPermissionContext } from 'src/Tool.js'
|
||||
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 {
|
||||
createDisabledBypassPermissionsContext,
|
||||
shouldDisableBypassPermissions,
|
||||
verifyAutoModeGateAccess,
|
||||
} from './permissionSetup.js'
|
||||
|
||||
let bypassPermissionsCheckRan = false
|
||||
|
||||
/**
|
||||
* No-op — bypass permissions is always available.
|
||||
*/
|
||||
export async function checkAndDisableBypassPermissionsIfNeeded(
|
||||
toolPermissionContext: ToolPermissionContext,
|
||||
setAppState: (f: (prev: AppState) => AppState) => void,
|
||||
_toolPermissionContext: ToolPermissionContext,
|
||||
_setAppState: (f: (prev: import('../../state/AppState.js').AppState) => import('../../state/AppState.js').AppState) => void,
|
||||
): Promise<void> {
|
||||
// Check if bypassPermissions should be disabled based on Statsig gate
|
||||
// Do this only once, before the first query, to ensure we have the latest gate value
|
||||
if (bypassPermissionsCheckRan) {
|
||||
return
|
||||
}
|
||||
bypassPermissionsCheckRan = true
|
||||
|
||||
if (!toolPermissionContext.isBypassPermissionsModeAvailable) {
|
||||
return
|
||||
}
|
||||
|
||||
const shouldDisable = await shouldDisableBypassPermissions()
|
||||
if (!shouldDisable) {
|
||||
return
|
||||
}
|
||||
|
||||
setAppState(prev => {
|
||||
return {
|
||||
...prev,
|
||||
toolPermissionContext: createDisabledBypassPermissionsContext(
|
||||
prev.toolPermissionContext,
|
||||
),
|
||||
}
|
||||
})
|
||||
// Bypass permissions is always available — no gate check needed
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the run-once flag for checkAndDisableBypassPermissionsIfNeeded.
|
||||
* Call this after /login so the gate check re-runs with the new org.
|
||||
* Reset stub — kept for interface compatibility.
|
||||
*/
|
||||
export function resetBypassPermissionsCheck(): void {
|
||||
bypassPermissionsCheckRan = false
|
||||
// No-op
|
||||
}
|
||||
|
||||
/**
|
||||
* No-op hook — bypass permissions is always available.
|
||||
*/
|
||||
export function useKickOffCheckAndDisableBypassPermissionsIfNeeded(): void {
|
||||
const toolPermissionContext = useAppState(s => s.toolPermissionContext)
|
||||
const setAppState = useSetAppState()
|
||||
|
||||
// Run once, when the component mounts
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
void checkAndDisableBypassPermissionsIfNeeded(
|
||||
toolPermissionContext,
|
||||
setAppState,
|
||||
)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
// No-op
|
||||
}
|
||||
|
||||
let autoModeCheckRan = false
|
||||
|
||||
export async function checkAndDisableAutoModeIfNeeded(
|
||||
toolPermissionContext: ToolPermissionContext,
|
||||
setAppState: (f: (prev: AppState) => AppState) => void,
|
||||
setAppState: (f: (prev: import('../../state/AppState.js').AppState) => import('../../state/AppState.js').AppState) => void,
|
||||
fastMode?: boolean,
|
||||
): Promise<void> {
|
||||
if (feature('TRANSCRIPT_CLASSIFIER')) {
|
||||
@@ -87,10 +52,6 @@ export async function checkAndDisableAutoModeIfNeeded(
|
||||
fastMode,
|
||||
)
|
||||
setAppState(prev => {
|
||||
// Apply the transform to CURRENT context, not the stale snapshot we
|
||||
// passed to verifyAutoModeGateAccess. The async GrowthBook await inside
|
||||
// can be outrun by a mid-turn shift-tab; spreading a stale context here
|
||||
// would revert the user's mode change.
|
||||
const nextCtx = updateContext(prev.toolPermissionContext)
|
||||
const newState =
|
||||
nextCtx === prev.toolPermissionContext
|
||||
@@ -133,11 +94,6 @@ export function useKickOffCheckAndDisableAutoModeIfNeeded(): void {
|
||||
const isFirstRunRef = useRef(true)
|
||||
|
||||
// Runs on mount (startup check) AND whenever the model or fast mode changes
|
||||
// (kick-out / carousel-restore). Watching both model fields covers /model,
|
||||
// Cmd+P picker, /config, and bridge onSetModel paths; fastMode covers
|
||||
// /fast on|off for the tengu_auto_mode_config.disableFastMode circuit
|
||||
// breaker. The print.ts headless paths are covered by the sync
|
||||
// isAutoModeGateEnabled() check.
|
||||
useEffect(() => {
|
||||
if (getIsRemoteMode()) return
|
||||
if (isFirstRunRef.current) {
|
||||
@@ -149,7 +105,9 @@ export function useKickOffCheckAndDisableAutoModeIfNeeded(): void {
|
||||
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])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user