mirror of
https://github.com/claude-code-best/claude-code.git
synced 2026-06-22 16:25:51 +00:00
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>
This commit is contained in:
204
src/utils/permissions/__tests__/getNextPermissionMode.test.ts
Normal file
204
src/utils/permissions/__tests__/getNextPermissionMode.test.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* Tests for src/utils/permissions/getNextPermissionMode.ts
|
||||
*
|
||||
* Covers the unified permission mode cycling logic:
|
||||
* default → acceptEdits → plan → auto → bypassPermissions → default
|
||||
*
|
||||
* After the "open auto/bypass to all users" change, there is no USER_TYPE
|
||||
* distinction — all users share the same cycle order.
|
||||
*/
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import type { ToolPermissionContext } from '../../Tool.js'
|
||||
import type { PermissionMode } from '../PermissionMode.js'
|
||||
|
||||
// Inline getNextPermissionMode to avoid importing the heavy permissionSetup
|
||||
// dependency chain (growthbook, settings, etc.).
|
||||
// The function under test is small and pure enough to copy for testing.
|
||||
import { getNextPermissionMode } from '../getNextPermissionMode.js'
|
||||
|
||||
// ─── helpers ──────────────────────────────────────────────────────────────────
|
||||
|
||||
function makeContext(
|
||||
mode: PermissionMode,
|
||||
overrides: Partial<ToolPermissionContext> = {},
|
||||
): ToolPermissionContext {
|
||||
return {
|
||||
mode,
|
||||
additionalWorkingDirectories: new Map(),
|
||||
alwaysAllowRules: {},
|
||||
alwaysDenyRules: {},
|
||||
alwaysAskRules: {},
|
||||
isBypassPermissionsModeAvailable: true,
|
||||
...overrides,
|
||||
}
|
||||
}
|
||||
|
||||
// ─── tests ────────────────────────────────────────────────────────────────────
|
||||
|
||||
describe('getNextPermissionMode', () => {
|
||||
// ── Full cycle ──────────────────────────────────────────────────────────
|
||||
|
||||
describe('unified cycle order', () => {
|
||||
test('default → acceptEdits', () => {
|
||||
expect(getNextPermissionMode(makeContext('default'))).toBe('acceptEdits')
|
||||
})
|
||||
|
||||
test('acceptEdits → plan', () => {
|
||||
expect(getNextPermissionMode(makeContext('acceptEdits'))).toBe('plan')
|
||||
})
|
||||
|
||||
test('plan → auto', () => {
|
||||
expect(getNextPermissionMode(makeContext('plan'))).toBe('auto')
|
||||
})
|
||||
|
||||
test('auto → bypassPermissions (when bypass available)', () => {
|
||||
expect(getNextPermissionMode(makeContext('auto'))).toBe('bypassPermissions')
|
||||
})
|
||||
|
||||
test('bypassPermissions → default', () => {
|
||||
expect(getNextPermissionMode(makeContext('bypassPermissions'))).toBe('default')
|
||||
})
|
||||
|
||||
test('full cycle completes back to default', () => {
|
||||
const cycle: PermissionMode[] = []
|
||||
let ctx = makeContext('default')
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const next = getNextPermissionMode(ctx)
|
||||
cycle.push(next)
|
||||
ctx = makeContext(next)
|
||||
}
|
||||
expect(cycle).toEqual([
|
||||
'acceptEdits',
|
||||
'plan',
|
||||
'auto',
|
||||
'bypassPermissions',
|
||||
'default',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
// ── auto → default when bypass unavailable ─────────────────────────────
|
||||
|
||||
describe('auto mode with bypass unavailable', () => {
|
||||
test('auto → default when isBypassPermissionsModeAvailable is false', () => {
|
||||
const ctx = makeContext('auto', {
|
||||
isBypassPermissionsModeAvailable: false,
|
||||
})
|
||||
expect(getNextPermissionMode(ctx)).toBe('default')
|
||||
})
|
||||
})
|
||||
|
||||
// ── dontAsk mode ────────────────────────────────────────────────────────
|
||||
|
||||
describe('dontAsk mode', () => {
|
||||
test('dontAsk → default', () => {
|
||||
expect(getNextPermissionMode(makeContext('dontAsk'))).toBe('default')
|
||||
})
|
||||
})
|
||||
|
||||
// ── USER_TYPE independence ──────────────────────────────────────────────
|
||||
|
||||
describe('no USER_TYPE distinction', () => {
|
||||
test('cycle order is the same regardless of USER_TYPE', () => {
|
||||
// Save original
|
||||
const originalUserType = process.env.USER_TYPE
|
||||
|
||||
// Test with no USER_TYPE
|
||||
delete process.env.USER_TYPE
|
||||
const cycleNoType: PermissionMode[] = []
|
||||
let ctx = makeContext('default')
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const next = getNextPermissionMode(ctx)
|
||||
cycleNoType.push(next)
|
||||
ctx = makeContext(next)
|
||||
}
|
||||
|
||||
// Test with USER_TYPE=ant
|
||||
process.env.USER_TYPE = 'ant'
|
||||
const cycleAnt: PermissionMode[] = []
|
||||
ctx = makeContext('default')
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const next = getNextPermissionMode(ctx)
|
||||
cycleAnt.push(next)
|
||||
ctx = makeContext(next)
|
||||
}
|
||||
|
||||
// Restore
|
||||
if (originalUserType !== undefined) {
|
||||
process.env.USER_TYPE = originalUserType
|
||||
} else {
|
||||
delete process.env.USER_TYPE
|
||||
}
|
||||
|
||||
// Both should produce the same cycle
|
||||
expect(cycleNoType).toEqual(cycleAnt)
|
||||
expect(cycleNoType).toEqual([
|
||||
'acceptEdits',
|
||||
'plan',
|
||||
'auto',
|
||||
'bypassPermissions',
|
||||
'default',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
// ── teamContext parameter ───────────────────────────────────────────────
|
||||
|
||||
describe('teamContext parameter', () => {
|
||||
test('does not affect cycle when provided', () => {
|
||||
const ctx = makeContext('default')
|
||||
const teamCtx = { leadAgentId: 'agent-123' }
|
||||
expect(getNextPermissionMode(ctx, teamCtx)).toBe('acceptEdits')
|
||||
})
|
||||
|
||||
test('does not affect cycle for plan mode', () => {
|
||||
const ctx = makeContext('plan')
|
||||
const teamCtx = { leadAgentId: 'agent-456' }
|
||||
expect(getNextPermissionMode(ctx, teamCtx)).toBe('auto')
|
||||
})
|
||||
})
|
||||
|
||||
// ── cycle stability (no infinite loops) ─────────────────────────────────
|
||||
|
||||
describe('cycle stability', () => {
|
||||
test('all modes return to default within 6 steps', () => {
|
||||
const modes: PermissionMode[] = [
|
||||
'default',
|
||||
'acceptEdits',
|
||||
'plan',
|
||||
'auto',
|
||||
'bypassPermissions',
|
||||
'dontAsk',
|
||||
]
|
||||
for (const startMode of modes) {
|
||||
let current = startMode
|
||||
let returnedToDefault = false
|
||||
for (let i = 0; i < 6; i++) {
|
||||
current = getNextPermissionMode(makeContext(current))
|
||||
if (current === 'default') {
|
||||
returnedToDefault = true
|
||||
break
|
||||
}
|
||||
}
|
||||
expect(returnedToDefault).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
test('cycling 100 times never produces an invalid mode', () => {
|
||||
const validModes = new Set<string>([
|
||||
'default',
|
||||
'acceptEdits',
|
||||
'plan',
|
||||
'auto',
|
||||
'bypassPermissions',
|
||||
'dontAsk',
|
||||
])
|
||||
let ctx = makeContext('default')
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const next = getNextPermissionMode(ctx)
|
||||
expect(validModes.has(next)).toBe(true)
|
||||
ctx = makeContext(next)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user