feat: add permission decision options, tool allowlisting, and refactored chat UI components

This commit is contained in:
weishu
2025-12-17 07:57:37 +08:00
parent c2b886896b
commit 0fa05dc205
17 changed files with 2689 additions and 86 deletions
+21 -7
View File
@@ -37,13 +37,18 @@ export const AgentStateSchema = z.object({
tool: z.string(),
arguments: z.unknown(),
createdAt: z.number().nullish()
})).nullish(),
}).passthrough()).nullish(),
completedRequests: z.record(z.string(), z.object({
tool: z.string(),
arguments: z.unknown(),
createdAt: z.number().nullish(),
completedAt: z.number().nullish(),
status: z.enum(['canceled', 'denied', 'approved']),
mode: z.string().nullish()
})).nullish()
reason: z.string().optional(),
mode: z.string().optional(),
decision: z.enum(['approved', 'approved_for_session', 'denied', 'abort']).optional(),
allowTools: z.array(z.string()).optional()
}).passthrough()).nullish()
}).passthrough()
export type AgentState = z.infer<typeof AgentStateSchema>
@@ -558,19 +563,28 @@ export class SyncEngine {
async approvePermission(
sessionId: string,
requestId: string,
mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan'
mode?: 'default' | 'acceptEdits' | 'bypassPermissions' | 'plan',
allowTools?: string[],
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort'
): Promise<void> {
await this.sessionRpc(sessionId, 'permission', {
id: requestId,
approved: true,
mode
mode,
allowTools,
decision
})
}
async denyPermission(sessionId: string, requestId: string): Promise<void> {
async denyPermission(
sessionId: string,
requestId: string,
decision?: 'approved' | 'approved_for_session' | 'denied' | 'abort'
): Promise<void> {
await this.sessionRpc(sessionId, 'permission', {
id: requestId,
approved: false
approved: false,
decision
})
}
+20 -4
View File
@@ -4,8 +4,16 @@ import type { SyncEngine } from '../../sync/syncEngine'
import type { WebAppEnv } from '../middleware/auth'
import { requireSessionFromParam, requireSyncEngine } from './guards'
const decisionSchema = z.enum(['approved', 'approved_for_session', 'denied', 'abort'])
const approveBodySchema = z.object({
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions']).optional()
mode: z.enum(['default', 'acceptEdits', 'bypassPermissions', 'plan']).optional(),
allowTools: z.array(z.string()).optional(),
decision: decisionSchema.optional()
})
const denyBodySchema = z.object({
decision: decisionSchema.optional()
})
export function createPermissionsRoutes(getSyncEngine: () => SyncEngine | null): Hono<WebAppEnv> {
@@ -26,7 +34,7 @@ export function createPermissionsRoutes(getSyncEngine: () => SyncEngine | null):
const { sessionId, session } = sessionResult
const json = await c.req.json().catch(() => null)
const parsed = approveBodySchema.safeParse(json)
const parsed = approveBodySchema.safeParse(json ?? {})
if (!parsed.success) {
return c.json({ error: 'Invalid body' }, 400)
}
@@ -37,7 +45,9 @@ export function createPermissionsRoutes(getSyncEngine: () => SyncEngine | null):
}
const mode = parsed.data.mode
await engine.approvePermission(sessionId, requestId, mode)
const allowTools = parsed.data.allowTools
const decision = parsed.data.decision
await engine.approvePermission(sessionId, requestId, mode, allowTools, decision)
return c.json({ ok: true })
})
@@ -60,7 +70,13 @@ export function createPermissionsRoutes(getSyncEngine: () => SyncEngine | null):
return c.json({ error: 'Request not found' }, 404)
}
await engine.denyPermission(sessionId, requestId)
const json = await c.req.json().catch(() => null)
const parsed = denyBodySchema.safeParse(json ?? {})
if (!parsed.success) {
return c.json({ error: 'Invalid body' }, 400)
}
await engine.denyPermission(sessionId, requestId, parsed.data.decision)
return c.json({ ok: true })
})