feat: serve 模式默认启用权限问询(acceptEdits)并修复回复链路
- 默认权限模式从 undefined 改为 acceptEdits(工作目录内读写自动放行) - 始终传递 --permission-prompt-tool stdio 给子进程 - 修复 updatedInput 为 undefined 导致子进程 Zod 解析失败 - always 回复附带 updatedPermissions + decisionClassification - 新增 permission_replied/question_replied SSE 事件通知 UI 撤销弹窗 - hook_callback 控制请求自动回复防止子进程阻塞 - PendingPermission 保存 permission_suggestions 供 always 回复使用
This commit is contained in:
parent
e37e9d3ad5
commit
9c3441331c
|
|
@ -21,16 +21,22 @@ export function createPermissionRoutes(sessionManager: SessionManager): Hono {
|
|||
interrupt?: boolean
|
||||
}>()
|
||||
|
||||
const isAlways = body.behavior === 'always'
|
||||
|
||||
const mappedBehavior: 'allow' | 'deny' =
|
||||
body.behavior === 'reject' ? 'deny' :
|
||||
body.behavior === 'once' || body.behavior === 'always' ? 'allow' :
|
||||
body.behavior === 'deny' ? 'deny' : 'allow'
|
||||
|
||||
const updatedPermissions = body.updated_permissions
|
||||
?? (isAlways ? found.perm.suggestions : undefined)
|
||||
|
||||
found.handle.replyPermission(requestId, mappedBehavior, {
|
||||
updatedInput: body.updated_input,
|
||||
updatedPermissions: body.updated_permissions,
|
||||
updatedInput: body.updated_input ?? {},
|
||||
updatedPermissions,
|
||||
message: body.message,
|
||||
interrupt: body.interrupt,
|
||||
decisionClassification: isAlways ? 'user_permanent' : mappedBehavior === 'allow' ? 'user_temporary' : undefined,
|
||||
})
|
||||
|
||||
return c.json({ resolved: true })
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export type PendingPermission = {
|
|||
input: Record<string, unknown>
|
||||
title: string
|
||||
description: string
|
||||
suggestions: Record<string, unknown>[]
|
||||
}
|
||||
|
||||
export type PendingQuestion = {
|
||||
|
|
@ -211,7 +212,7 @@ export class SessionHandle {
|
|||
this.cwd = opts.cwd
|
||||
this.eventBus = opts.eventBus
|
||||
this._model = opts.model
|
||||
this._permissionMode = opts.permissionMode
|
||||
this._permissionMode = opts.permissionMode ?? 'acceptEdits'
|
||||
this.verbose = opts.verbose ?? false
|
||||
}
|
||||
|
||||
|
|
@ -235,14 +236,10 @@ export class SessionHandle {
|
|||
'--session-id',
|
||||
this.sessionId,
|
||||
...(this.opts.model ? ['--model', this.opts.model] : []),
|
||||
...(this.opts.permissionMode
|
||||
? [
|
||||
'--permission-mode',
|
||||
this.opts.permissionMode,
|
||||
'--permission-prompt-tool',
|
||||
'stdio',
|
||||
]
|
||||
: []),
|
||||
'--permission-mode',
|
||||
this._permissionMode,
|
||||
'--permission-prompt-tool',
|
||||
'stdio',
|
||||
...(this.opts.resumeSessionId
|
||||
? ['--resume', this.opts.resumeSessionId]
|
||||
: []),
|
||||
|
|
@ -495,6 +492,8 @@ export class SessionHandle {
|
|||
input: (request.input as Record<string, unknown>) ?? {},
|
||||
title: `${request.tool_name}: ${JSON.stringify(request.input).slice(0, 80)}`,
|
||||
description: `Execute ${request.tool_name}`,
|
||||
suggestions:
|
||||
(request.permission_suggestions as Record<string, unknown>[]) ?? [],
|
||||
}
|
||||
this.pendingPermissions.set(requestId, perm)
|
||||
this.emitEvent('control_request', { request_id: requestId, request })
|
||||
|
|
@ -512,6 +511,38 @@ export class SessionHandle {
|
|||
this.emitEvent('control_request', { request_id: requestId, request })
|
||||
} else if (request?.subtype === 'hook_callback') {
|
||||
this.emitEvent('control_request', { request_id: requestId, request })
|
||||
const callbackId = request.callback_id as string | undefined
|
||||
if (!callbackId || callbackId === 'AUTO_APPROVE_CALLBACK_ID') {
|
||||
this.writeStdin(jsonStringify({
|
||||
type: 'control_response',
|
||||
response: {
|
||||
subtype: 'success',
|
||||
request_id: requestId,
|
||||
response: {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
permissionDecision: 'allow',
|
||||
permissionDecisionReason: 'Auto-approved by serve',
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
this.writeStdin(jsonStringify({
|
||||
type: 'control_response',
|
||||
response: {
|
||||
subtype: 'success',
|
||||
request_id: requestId,
|
||||
response: {
|
||||
hookSpecificOutput: {
|
||||
hookEventName: 'PreToolUse',
|
||||
permissionDecision: 'ask',
|
||||
permissionDecisionReason: 'Forwarding to can_use_tool',
|
||||
},
|
||||
},
|
||||
},
|
||||
}))
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
|
@ -669,6 +700,7 @@ export class SessionHandle {
|
|||
updatedPermissions?: Record<string, unknown>[]
|
||||
message?: string
|
||||
interrupt?: boolean
|
||||
decisionClassification?: string
|
||||
},
|
||||
): void {
|
||||
const response = {
|
||||
|
|
@ -680,10 +712,13 @@ export class SessionHandle {
|
|||
behavior === 'allow'
|
||||
? {
|
||||
behavior: 'allow',
|
||||
updatedInput: opts?.updatedInput,
|
||||
updatedInput: opts?.updatedInput ?? {},
|
||||
...(opts?.updatedPermissions
|
||||
? { updatedPermissions: opts.updatedPermissions }
|
||||
: {}),
|
||||
...(opts?.decisionClassification
|
||||
? { decisionClassification: opts.decisionClassification }
|
||||
: {}),
|
||||
}
|
||||
: {
|
||||
behavior: 'deny',
|
||||
|
|
@ -694,6 +729,10 @@ export class SessionHandle {
|
|||
}
|
||||
this.writeStdin(jsonStringify(response))
|
||||
this.pendingPermissions.delete(requestId)
|
||||
this.emitEvent('permission_replied', {
|
||||
request_id: requestId,
|
||||
behavior,
|
||||
})
|
||||
}
|
||||
|
||||
replyQuestion(
|
||||
|
|
@ -714,6 +753,10 @@ export class SessionHandle {
|
|||
}
|
||||
this.writeStdin(jsonStringify(response))
|
||||
this.pendingQuestions.delete(requestId)
|
||||
this.emitEvent('question_replied', {
|
||||
request_id: requestId,
|
||||
action,
|
||||
})
|
||||
}
|
||||
|
||||
kill(): void {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user