mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
feat: add real-time permission and model mode synchronization close #21
Implements bidirectional sync of permission/model modes between CLI sessions and web app. Adds Codex-specific permission modes (read-only, safe-yolo, yolo) alongside Claude's modes. Web can now control CLI session state via RPC set-session-config handler, while CLI broadcasts state changes through keep-alive payloads. UI controls are flavor-aware, showing appropriate modes for Claude vs Codex vs Gemini. Type centralization in api/types eliminates circular dependencies.
This commit is contained in:
@@ -27,7 +27,7 @@ type SessionSummary = {
|
||||
metadata: SessionSummaryMetadata | null
|
||||
todoProgress: { completed: number; total: number } | null
|
||||
pendingRequestsCount: number
|
||||
modelMode?: 'default' | 'sonnet' | 'opus' | null
|
||||
modelMode?: 'default' | 'sonnet' | 'opus'
|
||||
}
|
||||
|
||||
function toSessionSummary(session: Session): SessionSummary {
|
||||
@@ -55,12 +55,12 @@ function toSessionSummary(session: Session): SessionSummary {
|
||||
metadata,
|
||||
todoProgress,
|
||||
pendingRequestsCount,
|
||||
modelMode: session.modelMode ?? null
|
||||
modelMode: session.modelMode
|
||||
}
|
||||
}
|
||||
|
||||
const permissionModeSchema = z.object({
|
||||
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan'])
|
||||
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan', 'read-only', 'safe-yolo', 'yolo'])
|
||||
})
|
||||
|
||||
const modelModeSchema = z.object({
|
||||
@@ -159,8 +159,26 @@ export function createSessionsRoutes(getSyncEngine: () => SyncEngine | null): Ho
|
||||
return c.json({ error: 'Invalid body' }, 400)
|
||||
}
|
||||
|
||||
await engine.setPermissionMode(sessionResult.sessionId, parsed.data.mode)
|
||||
return c.json({ ok: true })
|
||||
const flavor = sessionResult.session.metadata?.flavor ?? 'claude'
|
||||
const mode = parsed.data.mode
|
||||
const claudeModes = new Set(['default', 'acceptEdits', 'bypassPermissions', 'plan'])
|
||||
const codexModes = new Set(['default', 'read-only', 'safe-yolo', 'yolo'])
|
||||
|
||||
if (flavor === 'gemini') {
|
||||
return c.json({ error: 'Permission mode not supported for Gemini sessions' }, 400)
|
||||
}
|
||||
|
||||
if (flavor === 'codex' ? !codexModes.has(mode) : !claudeModes.has(mode)) {
|
||||
return c.json({ error: 'Invalid permission mode for session flavor' }, 400)
|
||||
}
|
||||
|
||||
try {
|
||||
await engine.applySessionConfig(sessionResult.sessionId, { permissionMode: mode })
|
||||
return c.json({ ok: true })
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to apply permission mode'
|
||||
return c.json({ error: message }, 409)
|
||||
}
|
||||
})
|
||||
|
||||
app.post('/sessions/:id/model', async (c) => {
|
||||
@@ -180,8 +198,18 @@ export function createSessionsRoutes(getSyncEngine: () => SyncEngine | null): Ho
|
||||
return c.json({ error: 'Invalid body' }, 400)
|
||||
}
|
||||
|
||||
await engine.setModelMode(sessionResult.sessionId, parsed.data.model)
|
||||
return c.json({ ok: true })
|
||||
const flavor = sessionResult.session.metadata?.flavor ?? 'claude'
|
||||
if (flavor !== 'claude') {
|
||||
return c.json({ error: 'Model mode is only supported for Claude sessions' }, 400)
|
||||
}
|
||||
|
||||
try {
|
||||
await engine.applySessionConfig(sessionResult.sessionId, { modelMode: parsed.data.model })
|
||||
return c.json({ ok: true })
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to apply model mode'
|
||||
return c.json({ error: message }, 409)
|
||||
}
|
||||
})
|
||||
|
||||
app.get('/sessions/:id/slash-commands', async (c) => {
|
||||
|
||||
Reference in New Issue
Block a user