Unify protocol types and modes

This commit is contained in:
weishu
2026-01-03 14:43:59 +08:00
parent bfca058967
commit e2028b6914
20 changed files with 360 additions and 393 deletions
+4 -2
View File
@@ -6,6 +6,8 @@ import type {
MachinePathsExistsResponse,
MachinesResponse,
MessagesResponse,
ModelMode,
PermissionMode,
PushSubscriptionPayload,
PushUnsubscribePayload,
PushVapidPublicKeyResponse,
@@ -253,14 +255,14 @@ export class ApiClient {
})
}
async setPermissionMode(sessionId: string, mode: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo'): Promise<void> {
async setPermissionMode(sessionId: string, mode: PermissionMode): Promise<void> {
await this.request(`/api/sessions/${encodeURIComponent(sessionId)}/permission-mode`, {
method: 'POST',
body: JSON.stringify({ mode })
})
}
async setModelMode(sessionId: string, model: 'default' | 'sonnet' | 'opus'): Promise<void> {
async setModelMode(sessionId: string, model: ModelMode): Promise<void> {
await this.request(`/api/sessions/${encodeURIComponent(sessionId)}/model`, {
method: 'POST',
body: JSON.stringify({ model })
@@ -1,3 +1,4 @@
import { getPermissionModesForFlavor, MODEL_MODE_LABELS, MODEL_MODES, PERMISSION_MODE_LABELS } from '@hapi/protocol'
import { ComposerPrimitive, useAssistantApi, useAssistantState } from '@assistant-ui/react'
import {
type ChangeEvent as ReactChangeEvent,
@@ -26,25 +27,6 @@ export interface TextInputState {
selection: { start: number; end: number }
}
const CLAUDE_PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'bypassPermissions'] as const
const CODEX_PERMISSION_MODES = ['default', 'read-only', 'safe-yolo', 'yolo'] as const
const PERMISSION_MODE_LABELS: Record<string, string> = {
default: 'Default',
acceptEdits: 'Accept Edits',
plan: 'Plan Mode',
bypassPermissions: 'Yolo',
'read-only': 'Read Only',
'safe-yolo': 'Safe Yolo',
yolo: 'Yolo'
}
const MODEL_MODES = ['default', 'sonnet', 'opus'] as const
const MODEL_MODE_LABELS: Record<string, string> = {
default: 'Default',
sonnet: 'Sonnet',
opus: 'Opus'
}
const defaultSuggestionHandler = async (): Promise<Suggestion[]> => []
export function HappyComposer(props: {
@@ -225,15 +207,10 @@ export function HappyComposer(props: {
}
}, [switchDisabled, onSwitchToRemote, haptic])
const permissionModes = useMemo(() => {
if (agentFlavor === 'codex') {
return CODEX_PERMISSION_MODES as readonly PermissionMode[]
}
if (agentFlavor === 'gemini') {
return [] as readonly PermissionMode[]
}
return CLAUDE_PERMISSION_MODES as readonly PermissionMode[]
}, [agentFlavor])
const permissionModes = useMemo(
() => getPermissionModesForFlavor(agentFlavor),
[agentFlavor]
)
const handleKeyDown = useCallback((e: ReactKeyboardEvent<HTMLTextAreaElement>) => {
const key = e.key
+12 -19
View File
@@ -1,17 +1,8 @@
import { PERMISSION_MODE_LABELS } from '@hapi/protocol'
import { useMemo } from 'react'
import type { AgentState, ModelMode, PermissionMode } from '@/types/api'
import { getContextBudgetTokens } from '@/chat/modelConfig'
const PERMISSION_MODE_LABELS: Record<string, string> = {
default: 'Default',
acceptEdits: 'Accept Edits',
plan: 'Plan Mode',
bypassPermissions: 'Yolo',
'read-only': 'Read Only',
'safe-yolo': 'Safe Yolo',
yolo: 'Yolo'
}
// Vibing messages for thinking state
const VIBING_MESSAGES = [
"Accomplishing", "Actioning", "Actualizing", "Baking", "Booping", "Brewing",
@@ -112,9 +103,11 @@ export function StatusBar(props: {
)
const permissionMode = props.permissionMode
const shouldShowPermissionMode = props.agentFlavor !== 'gemini'
const displayPermissionMode = props.agentFlavor !== 'gemini'
&& permissionMode
&& permissionMode !== 'default'
? permissionMode
: null
return (
<div className="flex items-center justify-between px-2 pb-1">
@@ -134,17 +127,17 @@ export function StatusBar(props: {
) : null}
</div>
{shouldShowPermissionMode ? (
{displayPermissionMode ? (
<span className={`text-xs ${
permissionMode === 'acceptEdits' ? 'text-amber-500' :
permissionMode === 'bypassPermissions' ? 'text-red-500' :
permissionMode === 'plan' ? 'text-blue-500' :
permissionMode === 'read-only' ? 'text-amber-500' :
permissionMode === 'safe-yolo' ? 'text-amber-500' :
permissionMode === 'yolo' ? 'text-red-500' :
displayPermissionMode === 'acceptEdits' ? 'text-amber-500' :
displayPermissionMode === 'bypassPermissions' ? 'text-red-500' :
displayPermissionMode === 'plan' ? 'text-blue-500' :
displayPermissionMode === 'read-only' ? 'text-amber-500' :
displayPermissionMode === 'safe-yolo' ? 'text-amber-500' :
displayPermissionMode === 'yolo' ? 'text-red-500' :
'text-[var(--app-hint)]'
}`}>
{PERMISSION_MODE_LABELS[permissionMode]}
{PERMISSION_MODE_LABELS[displayPermissionMode]}
</span>
) : null}
</div>
+2 -19
View File
@@ -3,23 +3,6 @@ import type { ApiClient } from '@/api/client'
import type { ModelMode, PermissionMode } from '@/types/api'
import { queryKeys } from '@/lib/query-keys'
type PermissionModeValue = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo'
type ModelModeValue = 'default' | 'sonnet' | 'opus'
function toPermissionMode(mode: PermissionMode): PermissionModeValue {
if (mode === 'acceptEdits' || mode === 'bypassPermissions' || mode === 'plan' || mode === 'read-only' || mode === 'safe-yolo' || mode === 'yolo') {
return mode
}
return 'default'
}
function toModelMode(mode: ModelMode): ModelModeValue {
if (mode === 'sonnet' || mode === 'opus') {
return mode
}
return 'default'
}
export function useSessionActions(api: ApiClient | null, sessionId: string | null): {
abortSession: () => Promise<void>
archiveSession: () => Promise<void>
@@ -73,7 +56,7 @@ export function useSessionActions(api: ApiClient | null, sessionId: string | nul
if (!api || !sessionId) {
throw new Error('Session unavailable')
}
await api.setPermissionMode(sessionId, toPermissionMode(mode))
await api.setPermissionMode(sessionId, mode)
},
onSuccess: () => void invalidateSession(),
})
@@ -83,7 +66,7 @@ export function useSessionActions(api: ApiClient | null, sessionId: string | nul
if (!api || !sessionId) {
throw new Error('Session unavailable')
}
await api.setModelMode(sessionId, toModelMode(mode))
await api.setModelMode(sessionId, mode)
},
onSuccess: () => void invalidateSession(),
})
+10 -67
View File
@@ -1,13 +1,12 @@
export type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan' | 'read-only' | 'safe-yolo' | 'yolo'
export type ModelMode = 'default' | 'sonnet' | 'opus'
import type {
DecryptedMessage as ProtocolDecryptedMessage,
ModelMode,
Session,
SyncEvent as ProtocolSyncEvent,
WorktreeMetadata
} from '@hapi/protocol/types'
export type WorktreeMetadata = {
basePath: string
branch: string
name: string
worktreePath?: string
createdAt?: number
}
export type { AgentState, ModelMode, PermissionMode, Session, TodoItem, WorktreeMetadata } from '@hapi/protocol/types'
export type SessionMetadataSummary = {
path: string
@@ -22,51 +21,6 @@ export type SessionMetadataSummary = {
worktree?: WorktreeMetadata
}
export type AgentStateRequest = {
tool: string
arguments: unknown
createdAt?: number | null
}
export type AgentStateCompletedRequest = {
tool: string
arguments: unknown
createdAt?: number | null
completedAt?: number | null
status: 'canceled' | 'denied' | 'approved'
reason?: string
mode?: string
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort'
allowTools?: string[]
answers?: Record<string, string[]>
}
export type AgentState = {
controlledByUser?: boolean | null
requests?: Record<string, AgentStateRequest> | null
completedRequests?: Record<string, AgentStateCompletedRequest> | null
}
export type TodoItem = {
content: string
status: 'pending' | 'in_progress' | 'completed'
priority: 'high' | 'medium' | 'low'
id: string
}
export type Session = {
id: string
createdAt: number
updatedAt: number
active: boolean
thinking: boolean
metadata: SessionMetadataSummary | null
agentState: AgentState | null
todos?: TodoItem[]
permissionMode?: PermissionMode
modelMode?: ModelMode
}
export type SessionSummaryMetadata = {
name?: string
path: string
@@ -89,12 +43,7 @@ export type SessionSummary = {
export type MessageStatus = 'sending' | 'sent' | 'failed'
export type DecryptedMessage = {
id: string
seq: number | null
localId: string | null
content: unknown
createdAt: number
export type DecryptedMessage = ProtocolDecryptedMessage & {
status?: MessageStatus
originalText?: string
}
@@ -216,10 +165,4 @@ export type PushVapidPublicKeyResponse = {
publicKey: string
}
export type SyncEvent =
| { type: 'session-added'; sessionId: string; data?: unknown; namespace?: string }
| { type: 'session-updated'; sessionId: string; data?: unknown; namespace?: string }
| { type: 'session-removed'; sessionId: string; namespace?: string }
| { type: 'message-received'; sessionId: string; message: DecryptedMessage; namespace?: string }
| { type: 'machine-updated'; machineId: string; data?: unknown; namespace?: string }
| { type: 'connection-changed'; data?: { status: string }; namespace?: string }
export type SyncEvent = ProtocolSyncEvent