diff --git a/packages/cli/src/acp-integration/acpAgent.test.ts b/packages/cli/src/acp-integration/acpAgent.test.ts index 191812d9843..77d11332673 100644 --- a/packages/cli/src/acp-integration/acpAgent.test.ts +++ b/packages/cli/src/acp-integration/acpAgent.test.ts @@ -986,6 +986,7 @@ import { GoalConflictError, GoalInvalidTransitionError, sessionIdContext, + ExtensionManager, } from '@qwen-code/qwen-code-core'; import { ndJsonStream } from '@qwen-code/acp-bridge/ndJsonStream'; import { @@ -2125,6 +2126,7 @@ describe('QwenAgent MCP SSE/HTTP support', () => { getWorkspaceContext: vi.fn().mockReturnValue({}), getDebugMode: vi.fn().mockReturnValue(false), getToolRegistry: vi.fn().mockReturnValue(undefined), + getTargetDir: vi.fn().mockReturnValue(process.cwd()), } as unknown as Config; vi.mocked(loadSettings).mockReturnValue(makeSessionSettings()); @@ -11014,6 +11016,9 @@ describe('QwenAgent MCP SSE/HTTP support', () => { // Shared boot helper for the qwen/settings/* handler tests below. async function bootCoreSettingsAgent(settings: LoadedSettings) { + if (typeof mockConfig.getTargetDir !== 'function') { + mockConfig.getTargetDir = vi.fn().mockReturnValue(process.cwd()); + } vi.mocked(loadSettings).mockReturnValue(settings); const agentPromise = runAcpAgent(mockConfig, settings, mockArgv); await vi.waitFor(() => expect(capturedAgentFactory).toBeDefined()); @@ -11086,6 +11091,127 @@ describe('QwenAgent MCP SSE/HTTP support', () => { await agentPromise; }); + it('qwen/settings/getCore falls back to config target dir when cwd is omitted', async () => { + const targetDir = '/worktree/.qwen'; + mockConfig.getTargetDir = vi.fn().mockReturnValue(targetDir); + const settings = makeCoreSettings(); + const { agent, agentPromise } = await bootCoreSettingsAgent(settings); + + await agent.extMethod('qwen/settings/getCore', {}); + + expect(loadSettings).toHaveBeenCalledWith(targetDir); + + mockConnectionState.resolve(); + await agentPromise; + }); + + it('qwen/settings/getCore prefers explicit cwd over config target dir', async () => { + mockConfig.getTargetDir = vi.fn().mockReturnValue('/worktree/project'); + const settings = makeCoreSettings(); + const { agent, agentPromise } = await bootCoreSettingsAgent(settings); + + await agent.extMethod('qwen/settings/getCore', { cwd: '/explicit/dir' }); + + expect(loadSettings).toHaveBeenCalledWith('/explicit/dir'); + expect(vi.mocked(ExtensionManager)).toHaveBeenCalledWith( + expect.objectContaining({ workspaceDir: '/explicit/dir' }), + ); + + mockConnectionState.resolve(); + await agentPromise; + }); + + it('qwen/settings/getCore uses the session target dir when cwd is omitted', async () => { + const sessionId = '11111111-1111-1111-1111-111111111111'; + const targetDir = '/relocated/worktree'; + const innerConfig = await setupSessionMocks(sessionId); + innerConfig.getTargetDir = vi.fn().mockReturnValue(targetDir); + const settings = makeCoreSettings(); + vi.mocked(loadSettings).mockReturnValue(settings); + const { agent, agentPromise } = await bootAcpAgent(); + + await agent.newSession({ cwd: '/launch/dir', mcpServers: [] }); + await agent.extMethod('qwen/settings/getCore', { sessionId }); + + expect(loadSettings).toHaveBeenLastCalledWith(targetDir); + expect(vi.mocked(ExtensionManager)).toHaveBeenLastCalledWith( + expect.objectContaining({ workspaceDir: targetDir }), + ); + + mockConnectionState.resolve(); + await agentPromise; + }); + + it('qwen/settings/setCoreValue falls back to config target dir when cwd is omitted', async () => { + const targetDir = '/worktree/.qwen'; + mockConfig.getTargetDir = vi.fn().mockReturnValue(targetDir); + const settings = makeCoreSettings(); + const { agent, agentPromise } = await bootCoreSettingsAgent(settings); + + await agent.extMethod('qwen/settings/setCoreValue', { + scope: 'workspace', + key: 'general.outputLanguage', + value: 'Japanese', + }); + + expect(loadSettings).toHaveBeenCalledWith(targetDir); + expect(settings.setValue).toHaveBeenCalledWith( + 'Workspace', + 'general.outputLanguage', + 'Japanese', + ); + + mockConnectionState.resolve(); + await agentPromise; + }); + + it('qwen/settings/setCoreValue prefers explicit cwd over config target dir', async () => { + mockConfig.getTargetDir = vi.fn().mockReturnValue('/worktree/project'); + const settings = makeCoreSettings(); + const { agent, agentPromise } = await bootCoreSettingsAgent(settings); + + await agent.extMethod('qwen/settings/setCoreValue', { + cwd: '/explicit/dir', + scope: 'workspace', + key: 'general.outputLanguage', + value: 'Japanese', + }); + + expect(loadSettings).toHaveBeenCalledWith('/explicit/dir'); + expect(vi.mocked(ExtensionManager)).toHaveBeenCalledWith( + expect.objectContaining({ workspaceDir: '/explicit/dir' }), + ); + + mockConnectionState.resolve(); + await agentPromise; + }); + + it('qwen/settings/setCoreValue uses the session target dir when cwd is omitted', async () => { + const sessionId = '11111111-1111-1111-1111-111111111111'; + const targetDir = '/relocated/worktree'; + const innerConfig = await setupSessionMocks(sessionId); + innerConfig.getTargetDir = vi.fn().mockReturnValue(targetDir); + const settings = makeCoreSettings(); + vi.mocked(loadSettings).mockReturnValue(settings); + const { agent, agentPromise } = await bootAcpAgent(); + + await agent.newSession({ cwd: '/launch/dir', mcpServers: [] }); + await agent.extMethod('qwen/settings/setCoreValue', { + sessionId, + scope: 'workspace', + key: 'general.outputLanguage', + value: 'Japanese', + }); + + expect(loadSettings).toHaveBeenLastCalledWith(targetDir); + expect(vi.mocked(ExtensionManager)).toHaveBeenLastCalledWith( + expect.objectContaining({ workspaceDir: targetDir }), + ); + + mockConnectionState.resolve(); + await agentPromise; + }); + it('qwen/settings/setCoreValue clears model.baseUrl when setting model.name', async () => { const settings = makeCoreSettings(); const { agent, agentPromise } = await bootCoreSettingsAgent(settings); diff --git a/packages/cli/src/acp-integration/acpAgent.ts b/packages/cli/src/acp-integration/acpAgent.ts index c8aad29652c..c80b8223da1 100644 --- a/packages/cli/src/acp-integration/acpAgent.ts +++ b/packages/cli/src/acp-integration/acpAgent.ts @@ -5691,6 +5691,27 @@ class QwenAgent implements Agent { }; } + private resolveCoreSettingsCwd( + params: Record, + requestedCwd: string | undefined, + ): string { + if (requestedCwd) { + return requestedCwd; + } + const sessionId = + typeof params['sessionId'] === 'string' ? params['sessionId'] : undefined; + if (sessionId) { + const sessionTargetDir = this.sessions + .get(sessionId) + ?.getConfig() + .getTargetDir(); + if (sessionTargetDir) { + return sessionTargetDir; + } + } + return this.config.getTargetDir(); + } + private syncLivePermissionManagers( before: PermissionRuleSet, after: PermissionRuleSet, @@ -11138,9 +11159,13 @@ class QwenAgent implements Agent { return { newSessionId, title, displayName: title }; } case 'qwen/settings/getCore': { - const settings = loadSettings(cwd); + const coreSettingsCwd = this.resolveCoreSettingsCwd( + params, + requestedCwd, + ); + const settings = loadSettings(coreSettingsCwd); this.settings = settings; - return this.buildCoreSettings(settings, cwd); + return this.buildCoreSettings(settings, coreSettingsCwd); } case 'qwen/settings/setCoreValue': { const key = params['key']; @@ -11153,7 +11178,11 @@ class QwenAgent implements Agent { 'Unsupported Qwen setting key', ); } - const settings = loadSettings(cwd); + const coreSettingsCwd = this.resolveCoreSettingsCwd( + params, + requestedCwd, + ); + const settings = loadSettings(coreSettingsCwd); const settingKey = key as QwenCoreSettingKey; const normalizedValue = normalizeCoreSettingValue( settingKey, @@ -11183,7 +11212,7 @@ class QwenAgent implements Agent { // `setValue` already persisted to disk and recomputed the in-memory // merged view, so reloading from disk here is redundant I/O. this.settings = settings; - return this.buildCoreSettings(settings, cwd); + return this.buildCoreSettings(settings, coreSettingsCwd); } case 'qwen/settings/setMcpServer': { const name = params['name'];