From 9c3441331cdc04fcd9e32422f0eb737963eb5c4f Mon Sep 17 00:00:00 2001 From: DoSun Date: Sat, 9 May 2026 11:10:13 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20serve=20=E6=A8=A1=E5=BC=8F=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E5=90=AF=E7=94=A8=E6=9D=83=E9=99=90=E9=97=AE=E8=AF=A2?= =?UTF-8?q?=EF=BC=88acceptEdits=EF=BC=89=E5=B9=B6=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E5=9B=9E=E5=A4=8D=E9=93=BE=E8=B7=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 默认权限模式从 undefined 改为 acceptEdits(工作目录内读写自动放行) - 始终传递 --permission-prompt-tool stdio 给子进程 - 修复 updatedInput 为 undefined 导致子进程 Zod 解析失败 - always 回复附带 updatedPermissions + decisionClassification - 新增 permission_replied/question_replied SSE 事件通知 UI 撤销弹窗 - hook_callback 控制请求自动回复防止子进程阻塞 - PendingPermission 保存 permission_suggestions 供 always 回复使用 --- src/server/routes/permission.ts | 10 ++++-- src/server/sessionHandle.ts | 63 +++++++++++++++++++++++++++------ 2 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/server/routes/permission.ts b/src/server/routes/permission.ts index 47168c893..5f36b35fa 100644 --- a/src/server/routes/permission.ts +++ b/src/server/routes/permission.ts @@ -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 }) diff --git a/src/server/sessionHandle.ts b/src/server/sessionHandle.ts index 638115db5..2ded16da4 100644 --- a/src/server/sessionHandle.ts +++ b/src/server/sessionHandle.ts @@ -37,6 +37,7 @@ export type PendingPermission = { input: Record title: string description: string + suggestions: Record[] } 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) ?? {}, title: `${request.tool_name}: ${JSON.stringify(request.input).slice(0, 80)}`, description: `Execute ${request.tool_name}`, + suggestions: + (request.permission_suggestions as Record[]) ?? [], } 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[] 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 {