mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Add 'auto' as a first-class HAPI permission mode for claude-flavored sessions, enforced by Claude's classifier rather than emulated in canCallTool. Includes mode configuration, CLI respawn on auto transitions, plan-exit targeting, API extensions, and documentation updates.
This commit is contained in:
@@ -145,7 +145,7 @@ Before commit/push/PR: use the **`pre-push-review`** skill (`~/.cursor/skills/pr
|
||||
- **RPC**: CLI registers handlers (`rpc-register`), hub routes requests via `rpcGateway.ts`
|
||||
- **Versioned updates**: CLI sends `update-metadata`/`update-state` with version; hub rejects stale
|
||||
- **Session modes**: `local` (terminal) vs `remote` (web-controlled); switchable mid-session
|
||||
- **Permission modes**: `default`, `acceptEdits`, `bypassPermissions`, `plan`
|
||||
- **Permission modes**: `default`, `acceptEdits`, `auto`, `bypassPermissions`, `plan`
|
||||
- **Namespaces**: Multi-user isolation via `CLI_API_TOKEN:<namespace>` suffix
|
||||
|
||||
## Adding new web features — consider an FUE
|
||||
|
||||
@@ -153,8 +153,11 @@ export async function runClaude(options: StartOptions = {}): Promise<void> {
|
||||
setControlledByUser(session, startingMode);
|
||||
|
||||
// Import MessageQueue2 and create message queue
|
||||
// 'plan' and 'auto' are enforced inside Claude itself (not emulated via
|
||||
// canCallTool like acceptEdits/bypassPermissions), so switching to/from
|
||||
// them must start a new process with the matching --permission-mode flag.
|
||||
const messageQueue = new MessageQueue2<EnhancedMode>(mode => hashObject({
|
||||
isPlan: mode.permissionMode === 'plan',
|
||||
agentEnforcedMode: mode.permissionMode === 'plan' || mode.permissionMode === 'auto' ? mode.permissionMode : null,
|
||||
model: mode.model,
|
||||
effort: mode.effort,
|
||||
fallbackModel: mode.fallbackModel,
|
||||
|
||||
@@ -32,7 +32,7 @@ interface PermissionResponse {
|
||||
receivedAt?: number;
|
||||
}
|
||||
|
||||
const PLAN_EXIT_MODES: PermissionMode[] = ['default', 'acceptEdits', 'bypassPermissions'];
|
||||
const PLAN_EXIT_MODES: PermissionMode[] = ['default', 'acceptEdits', 'auto', 'bypassPermissions'];
|
||||
|
||||
function isAskUserQuestionToolName(toolName: string): boolean {
|
||||
return toolName === 'AskUserQuestion' || toolName === 'ask_user_question';
|
||||
|
||||
@@ -72,7 +72,7 @@ describe('buildCliArgs', () => {
|
||||
})
|
||||
|
||||
it('validates all known permission modes', () => {
|
||||
for (const mode of ['default', 'acceptEdits', 'bypassPermissions', 'plan', 'ask', 'read-only', 'safe-yolo', 'yolo']) {
|
||||
for (const mode of ['default', 'acceptEdits', 'auto', 'bypassPermissions', 'plan', 'ask', 'read-only', 'safe-yolo', 'yolo']) {
|
||||
const args = buildCliArgs('claude', {
|
||||
directory: '/tmp',
|
||||
permissionMode: mode,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { describe, expect, it } from 'bun:test'
|
||||
import {
|
||||
getPermissionModeLabel,
|
||||
getPermissionModeTone,
|
||||
isPermissionModeAllowedForFlavor
|
||||
} from './modes'
|
||||
|
||||
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)
|
||||
})
|
||||
|
||||
it('has a label and tone', () => {
|
||||
expect(getPermissionModeLabel('auto')).toBe('Auto')
|
||||
expect(getPermissionModeTone('auto')).toBe('warning')
|
||||
})
|
||||
})
|
||||
+4
-1
@@ -11,7 +11,7 @@ export const AGENT_FLAVORS = ['claude', 'codex', 'cursor', 'gemini', 'kimi', 'op
|
||||
export type AgentFlavor = typeof AGENT_FLAVORS[number]
|
||||
export const AgentFlavorSchema = z.enum(AGENT_FLAVORS)
|
||||
|
||||
export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan'] as const
|
||||
export const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'auto', 'bypassPermissions', 'plan'] as const
|
||||
export type ClaudePermissionMode = typeof CLAUDE_PERMISSION_MODES[number]
|
||||
|
||||
export const CODEX_PERMISSION_MODES = ['default', 'read-only', 'safe-yolo', 'yolo'] as const
|
||||
@@ -35,6 +35,7 @@ export type CursorPermissionMode = typeof CURSOR_PERMISSION_MODES[number]
|
||||
export const PERMISSION_MODES = [
|
||||
'default',
|
||||
'acceptEdits',
|
||||
'auto',
|
||||
'bypassPermissions',
|
||||
'plan',
|
||||
'ask',
|
||||
@@ -49,6 +50,7 @@ export type PermissionMode = typeof PERMISSION_MODES[number]
|
||||
export const PERMISSION_MODE_LABELS: Record<PermissionMode, string> = {
|
||||
default: 'Default',
|
||||
acceptEdits: 'Accept Edits',
|
||||
auto: 'Auto',
|
||||
plan: 'Plan Mode',
|
||||
ask: 'Ask Mode',
|
||||
debug: 'Debug Mode',
|
||||
@@ -63,6 +65,7 @@ export type PermissionModeTone = 'neutral' | 'info' | 'warning' | 'danger'
|
||||
export const PERMISSION_MODE_TONES: Record<PermissionMode, PermissionModeTone> = {
|
||||
default: 'neutral',
|
||||
acceptEdits: 'warning',
|
||||
auto: 'warning',
|
||||
plan: 'info',
|
||||
ask: 'info',
|
||||
debug: 'info',
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ See `src/router.tsx` for route definitions.
|
||||
|
||||
- Message thread with infinite scroll.
|
||||
- Composer for sending messages.
|
||||
- Permission mode toggle (default/acceptEdits/bypassPermissions/plan).
|
||||
- Permission mode toggle (default/acceptEdits/auto/bypassPermissions/plan).
|
||||
- Model selection (default/sonnet/sonnet[1m]/opus/opus[1m]).
|
||||
- Session abort and mode switch controls.
|
||||
- Context size display.
|
||||
|
||||
@@ -521,8 +521,8 @@ export class ApiClient {
|
||||
async approvePermission(
|
||||
sessionId: string,
|
||||
requestId: string,
|
||||
modeOrOptions?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | {
|
||||
mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
|
||||
modeOrOptions?: 'default' | 'acceptEdits' | 'auto' | 'bypassPermissions' | 'plan' | {
|
||||
mode?: 'default' | 'acceptEdits' | 'auto' | 'bypassPermissions' | 'plan'
|
||||
allowTools?: string[]
|
||||
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort'
|
||||
answers?: Record<string, string[]> | Record<string, { answers: string[] }>
|
||||
|
||||
Reference in New Issue
Block a user