mirror of
https://github.com/wu736139669/hapi.git
synced 2026-11-07 01:21:37 +00:00
* fix(codex): drop safe-yolo from Codex permission modes Every Codex session runs on the native shared runtime (runCodex is runSharedCodex), which rejects safe-yolo. The mode stayed in CODEX_PERMISSION_MODES and six filters hid it again in the web picker, composer, hub permission-mode route, hapi set-mode and both native apps. API callers that validate against the flavor catalog (e.g. a spawn with permissionMode: 'safe-yolo') still passed it through and only failed inside the runner. Remove it from the Codex catalog (shared, iOS, Android, fixture) and delete the filters. autoReview is the native replacement. Safe Yolo stays for Gemini, Kimi and Copilot. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(ios): Codex option list now has four modes Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
191 lines
7.9 KiB
TypeScript
191 lines
7.9 KiB
TypeScript
import { describe, expect, it, test } from 'bun:test'
|
|
import {
|
|
AGENT_FLAVORS,
|
|
AgentFlavorSchema,
|
|
CREATABLE_AGENT_FLAVORS,
|
|
getPermissionModeLabel,
|
|
getPermissionModeOptionsForFlavor,
|
|
getPermissionModeTone,
|
|
getPermissionModesForFlavor,
|
|
isPermissionModeAllowedForFlavor,
|
|
isSteeringSupportedForFlavor,
|
|
isSteeringSupportedForSession,
|
|
} from './modes'
|
|
|
|
describe('Gemini CLI sunset (read-only, not creatable)', () => {
|
|
test('gemini stays a valid flavor so existing stored sessions still validate/load', () => {
|
|
expect(AGENT_FLAVORS).toContain('gemini')
|
|
expect(AgentFlavorSchema.safeParse('gemini').success).toBe(true)
|
|
})
|
|
|
|
test('gemini is excluded from creatable flavors (not offered for new sessions)', () => {
|
|
expect(CREATABLE_AGENT_FLAVORS).not.toContain('gemini')
|
|
})
|
|
|
|
test('all other flavors remain creatable', () => {
|
|
for (const flavor of AGENT_FLAVORS) {
|
|
if (flavor === 'gemini') continue
|
|
expect(CREATABLE_AGENT_FLAVORS).toContain(flavor)
|
|
}
|
|
})
|
|
})
|
|
|
|
describe('getPermissionModesForFlavor', () => {
|
|
test("returns the conservative Grok modes", () => {
|
|
expect(getPermissionModesForFlavor('grok')).toEqual([
|
|
'default',
|
|
'auto',
|
|
'plan',
|
|
'bypassPermissions'
|
|
])
|
|
})
|
|
|
|
test("returns no HAPI mode selector for DSH's server-owned permission policy", () => {
|
|
expect(getPermissionModesForFlavor('dsh')).toEqual([])
|
|
})
|
|
|
|
test("returns [] for flavor 'pi' (RPC mode has no runtime permission switching)", () => {
|
|
expect(getPermissionModesForFlavor('pi')).toEqual([])
|
|
})
|
|
|
|
test("returns [] for pi and does not fall back to Claude modes", () => {
|
|
// Ensure Pi is opt-in empty, not silently inheriting Claude defaults.
|
|
expect(getPermissionModesForFlavor('pi')).not.toEqual(getPermissionModesForFlavor('claude'))
|
|
expect(getPermissionModesForFlavor('pi')).not.toEqual(getPermissionModesForFlavor(null))
|
|
})
|
|
|
|
test("unknown flavors fall back to Claude modes, not Pi's empty list", () => {
|
|
expect(getPermissionModesForFlavor(null)).not.toEqual([])
|
|
expect(getPermissionModesForFlavor(undefined)).not.toEqual([])
|
|
expect(getPermissionModesForFlavor('PI')).not.toEqual([])
|
|
expect(getPermissionModesForFlavor('Pi')).not.toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('getPermissionModeOptionsForFlavor', () => {
|
|
test("returns [] for pi (no permission options offered)", () => {
|
|
expect(getPermissionModeOptionsForFlavor('pi')).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('isPermissionModeAllowedForFlavor', () => {
|
|
test("allows only the supported Grok modes", () => {
|
|
expect(isPermissionModeAllowedForFlavor('default', 'grok')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('plan', 'grok')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('bypassPermissions', 'grok')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('acceptEdits', 'grok')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'grok')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('yolo', 'grok')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('read-only', 'dsh')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('plan', 'dsh')).toBe(false)
|
|
})
|
|
|
|
test("no mode is allowed for pi", () => {
|
|
expect(isPermissionModeAllowedForFlavor('yolo', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('default', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('plan', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('acceptEdits', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('bypassPermissions', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('read-only', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('safe-yolo', 'pi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('ask', 'pi')).toBe(false)
|
|
})
|
|
|
|
test("cursor includes autoReview", () => {
|
|
expect(getPermissionModesForFlavor('cursor')).toContain('autoReview')
|
|
expect(getPermissionModeLabel('autoReview')).toBe('Auto')
|
|
expect(getPermissionModeTone('autoReview')).toBe('warning')
|
|
expect(isPermissionModeAllowedForFlavor('autoReview', 'cursor')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('autoReview', 'claude')).toBe(false)
|
|
})
|
|
|
|
test("codex offers autoReview at launch and mid-session", () => {
|
|
expect(getPermissionModesForFlavor('codex')).toEqual(['default', 'read-only', 'autoReview', 'yolo'])
|
|
expect(isPermissionModeAllowedForFlavor('autoReview', 'codex')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('autoReview', 'gemini')).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('getPermissionModeLabel', () => {
|
|
test("yolo label is 'Yolo'", () => {
|
|
expect(getPermissionModeLabel('yolo')).toBe('Yolo')
|
|
})
|
|
|
|
test("default label is 'Default'", () => {
|
|
expect(getPermissionModeLabel('default')).toBe('Default')
|
|
})
|
|
})
|
|
|
|
describe('getPermissionModeTone', () => {
|
|
test("yolo tone is danger", () => {
|
|
expect(getPermissionModeTone('yolo')).toBe('danger')
|
|
})
|
|
|
|
test("default tone is neutral", () => {
|
|
expect(getPermissionModeTone('default')).toBe('neutral')
|
|
})
|
|
})
|
|
|
|
describe('claude auto permission mode', () => {
|
|
it('is allowed for claude only', () => {
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'claude')).toBe(true)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'codex')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'gemini')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'cursor')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'opencode')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'kimi')).toBe(false)
|
|
expect(isPermissionModeAllowedForFlavor('auto', 'pi')).toBe(false)
|
|
})
|
|
|
|
it('has a label and tone', () => {
|
|
expect(getPermissionModeLabel('auto')).toBe('Auto')
|
|
expect(getPermissionModeTone('auto')).toBe('warning')
|
|
})
|
|
})
|
|
|
|
describe('isSteeringSupportedForFlavor', () => {
|
|
it('supports codex, cursor and pi', () => {
|
|
expect(isSteeringSupportedForFlavor('codex')).toBe(true)
|
|
expect(isSteeringSupportedForFlavor('cursor')).toBe(true)
|
|
expect(isSteeringSupportedForFlavor('pi')).toBe(true)
|
|
expect(isSteeringSupportedForFlavor('claude')).toBe(false)
|
|
expect(isSteeringSupportedForFlavor('opencode')).toBe(false)
|
|
expect(isSteeringSupportedForFlavor(undefined)).toBe(false)
|
|
expect(isSteeringSupportedForFlavor(null)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('isSteeringSupportedForSession', () => {
|
|
it('supports codex and pi sessions', () => {
|
|
expect(isSteeringSupportedForSession({ flavor: 'codex' })).toBe(true)
|
|
expect(isSteeringSupportedForSession({ flavor: 'pi' })).toBe(true)
|
|
})
|
|
|
|
it('supports Cursor ACP sessions', () => {
|
|
expect(isSteeringSupportedForSession({
|
|
flavor: 'cursor',
|
|
cursorSessionProtocol: 'acp',
|
|
cursorSessionId: 'sess-1',
|
|
})).toBe(true)
|
|
expect(isSteeringSupportedForSession({ flavor: 'cursor' })).toBe(true)
|
|
})
|
|
|
|
it('rejects legacy Cursor stream-json sessions', () => {
|
|
expect(isSteeringSupportedForSession({
|
|
flavor: 'cursor',
|
|
cursorSessionProtocol: 'stream-json',
|
|
cursorSessionId: 'legacy-1',
|
|
})).toBe(false)
|
|
expect(isSteeringSupportedForSession({
|
|
flavor: 'cursor',
|
|
cursorSessionId: 'legacy-without-protocol',
|
|
})).toBe(false)
|
|
})
|
|
|
|
it('rejects non-steerable flavors', () => {
|
|
expect(isSteeringSupportedForSession({ flavor: 'claude' })).toBe(false)
|
|
expect(isSteeringSupportedForSession(null)).toBe(false)
|
|
})
|
|
})
|