diff --git a/src/tests/websocket/listen-client-protocol.test.ts b/src/tests/websocket/listen-client-protocol.test.ts index 9006b05..0f76a2a 100644 --- a/src/tests/websocket/listen-client-protocol.test.ts +++ b/src/tests/websocket/listen-client-protocol.test.ts @@ -194,6 +194,39 @@ describe("listen-client parseServerMessage", () => { }); }); +describe("listen-client permission mode scope keys", () => { + test("falls back from legacy default key and migrates to agent-scoped key", () => { + const listener = __listenClientTestUtils.createListenerRuntime(); + + // Simulate a pre-existing/legacy persisted entry without agent binding. + listener.permissionModeByConversation.set( + "agent:__unknown__::conversation:default", + { + mode: "acceptEdits", + planFilePath: null, + modeBeforePlan: null, + }, + ); + + const status = __listenClientTestUtils.buildDeviceStatus(listener, { + agent_id: "agent-123", + conversation_id: "default", + }); + + expect(status.current_permission_mode).toBe("acceptEdits"); + expect( + listener.permissionModeByConversation.has( + "agent:agent-123::conversation:default", + ), + ).toBe(true); + expect( + listener.permissionModeByConversation.has( + "agent:__unknown__::conversation:default", + ), + ).toBe(false); + }); +}); + describe("listen-client approval resolver wiring", () => { test("resolves matching pending resolver", async () => { const runtime = __listenClientTestUtils.createRuntime(); diff --git a/src/websocket/listener/permissionMode.ts b/src/websocket/listener/permissionMode.ts index 7fddda3..03462e8 100644 --- a/src/websocket/listener/permissionMode.ts +++ b/src/websocket/listener/permissionMode.ts @@ -37,13 +37,36 @@ export function getConversationPermissionModeState( conversationId?: string | null, ): ConversationPermissionModeState { const scopeKey = getPermissionModeScopeKey(agentId, conversationId); - return ( - runtime.permissionModeByConversation.get(scopeKey) ?? { - mode: globalPermissionMode.getMode(), - planFilePath: null, - modeBeforePlan: null, + const normalizedConversationId = normalizeConversationId(conversationId); + + const direct = runtime.permissionModeByConversation.get(scopeKey); + if (direct) { + return direct; + } + + // Backward/interop fallback for default-conversation entries that were + // keyed without an agent id (agent:__unknown__). If we find one while a + // concrete agent id is available, migrate it to the canonical key. + if (normalizedConversationId === "default") { + const legacyDefaultKey = getPermissionModeScopeKey(null, "default"); + const legacyDefault = + runtime.permissionModeByConversation.get(legacyDefaultKey); + if (legacyDefault) { + if (normalizeCwdAgentId(agentId)) { + runtime.permissionModeByConversation.set(scopeKey, { + ...legacyDefault, + }); + runtime.permissionModeByConversation.delete(legacyDefaultKey); + } + return legacyDefault; } - ); + } + + return { + mode: globalPermissionMode.getMode(), + planFilePath: null, + modeBeforePlan: null, + }; } export function setConversationPermissionModeState(