From 670b118cd64c28a01c0a1ae985279e0807300e2d Mon Sep 17 00:00:00 2001 From: hcyang Date: Mon, 25 May 2026 15:10:37 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix(session):=20=E5=A4=84=E7=90=86=E6=9D=83?= =?UTF-8?q?=E9=99=90=E6=8B=92=E7=BB=9D=E7=8A=B6=E6=80=81=E4=B8=8E=E7=95=8C?= =?UTF-8?q?=E9=9D=A2=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 permission_denied 会话状态表示权限被拒绝 - 添加 denySessionPermission 方法以更新会话状态为拒绝并设置失败原因 - 在权限拒绝时清除提示草稿并调用拒绝权限处理逻辑 - 中断会话时清除提示草稿以防止残留输入 - 会话列表中新增 permission_denied 状态对应的 UI 状态映射为 denied --- src/session.ts | 17 ++++++++++++++++- src/ui/App.tsx | 3 +++ src/ui/SessionList.tsx | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index c81ae4a..4b34f20 100644 --- a/src/session.ts +++ b/src/session.ts @@ -159,7 +159,8 @@ export type SessionStatus = | "waiting_for_user" | "completed" | "interrupted" - | "ask_permission"; + | "ask_permission" + | "permission_denied"; export type ModelUsage = { prompt_tokens: number; @@ -1532,6 +1533,20 @@ ${skillMd} return !this.sessionControllers.has(sessionId); } + /** + * Mark a session's permission as denied by the user. + * Updates the session entry status and failReason so the denial is visible in the session list. + */ + denySessionPermission(sessionId: string, reason?: string): void { + const now = new Date().toISOString(); + this.updateSessionEntry(sessionId, (entry) => ({ + ...entry, + status: "permission_denied", + failReason: reason ?? "Permission denied by user", + updateTime: now, + })); + } + adjustActiveBashTimeout(deltaMs: number): BashTimeoutAdjustment | null { const sessionId = this.activeSessionId; if (!sessionId || !Number.isFinite(deltaMs)) { diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 71e9ca3..ae94fa0 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -669,6 +669,8 @@ function App({ projectRoot, initialPrompt, onRestart }: AppProps): React.ReactEl alwaysAllows: result.alwaysAllows, }); setStatusLine("Permission denied. Add a reply, then press Enter to continue."); + setPromptDraft(null); + sessionManager.denySessionPermission(sessionId); return; } void handlePrompt({ @@ -686,6 +688,7 @@ function App({ projectRoot, initialPrompt, onRestart }: AppProps): React.ReactEl sessionManager.interruptActiveSession(); setActiveStatus("interrupted"); setActiveAskPermissions(undefined); + setPromptDraft(null); refreshSessionsList(); }, [refreshSessionsList, sessionManager]); diff --git a/src/ui/SessionList.tsx b/src/ui/SessionList.tsx index 82ca797..2d83b84 100644 --- a/src/ui/SessionList.tsx +++ b/src/ui/SessionList.tsx @@ -332,6 +332,10 @@ export function formatSessionStatus(status: SessionStatus): string { return "failed"; case "interrupted": return "stopped"; + case "ask_permission": + return "waiting"; + case "permission_denied": + return "denied"; default: return status; } From a1b31c635263d22c486559f2c029242d51e35462 Mon Sep 17 00:00:00 2001 From: Ji Zhang Date: Mon, 25 May 2026 16:15:14 +0800 Subject: [PATCH 2/2] feat(session): Add support for permission_denied status --- src/session.ts | 3 ++- src/tests/session.test.ts | 51 +++++++++++++++++++++++++++++++++++ src/tests/sessionList.test.ts | 2 ++ 3 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/session.ts b/src/session.ts index 4b34f20..a9fc39e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -2730,7 +2730,8 @@ ${skillMd} status === "waiting_for_user" || status === "completed" || status === "interrupted" || - status === "ask_permission" + status === "ask_permission" || + status === "permission_denied" ) { return status; } diff --git a/src/tests/session.test.ts b/src/tests/session.test.ts index e0a863e..95de8e3 100644 --- a/src/tests/session.test.ts +++ b/src/tests/session.test.ts @@ -1313,6 +1313,57 @@ test("activateSession pauses for permission when a tool call requires ask", asyn ); }); +test("SessionManager preserves permission_denied status when sessions are reloaded", async () => { + const workspace = createTempDir("deepcode-permission-denied-workspace-"); + const home = createTempDir("deepcode-permission-denied-home-"); + setHomeDir(home); + + const permissions = { + allow: [], + deny: [], + ask: [], + defaultMode: "askAll" as const, + }; + const manager = createPermissionSessionManager( + workspace, + [ + { + choices: [ + { + message: { + content: "", + tool_calls: [ + { + id: "call-bash", + type: "function", + function: { + name: "bash", + arguments: JSON.stringify({ + command: "rg TODO src", + description: "Search TODO markers", + sideEffects: ["read-in-cwd"], + }), + }, + }, + ], + }, + }, + ], + }, + ], + permissions + ); + + const sessionId = await manager.createSession({ text: "search todos" }); + manager.denySessionPermission(sessionId); + + const reloadedManager = createPermissionSessionManager(workspace, [], permissions); + const reloadedSession = reloadedManager.getSession(sessionId); + + assert.equal(reloadedSession?.status, "permission_denied"); + assert.equal(reloadedSession?.failReason, "Permission denied by user"); +}); + test("replySession applies permission replies, runs pending tools, and stores always allow scopes", async () => { const workspace = createTempDir("deepcode-permission-allow-workspace-"); const home = createTempDir("deepcode-permission-allow-home-"); diff --git a/src/tests/sessionList.test.ts b/src/tests/sessionList.test.ts index 3dfda33..6fe41c7 100644 --- a/src/tests/sessionList.test.ts +++ b/src/tests/sessionList.test.ts @@ -18,6 +18,8 @@ test("formatSessionStatus maps status values to display labels", () => { assert.equal(formatSessionStatus("waiting_for_user"), "waiting"); assert.equal(formatSessionStatus("failed"), "failed"); assert.equal(formatSessionStatus("interrupted"), "stopped"); + assert.equal(formatSessionStatus("ask_permission"), "waiting"); + assert.equal(formatSessionStatus("permission_denied"), "denied"); assert.equal(formatSessionStatus("unknown_status" as any), "unknown_status"); });