Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"telemetry": "node scripts/telemetry.js",
"check:lockfile": "node scripts/check-lockfile.js",
"check:desktop-isolation": "node scripts/check-desktop-isolation.js",
"check:serve-fast-path-bundle": "npm run build -- --cli-only && cross-env DEV=true npm run bundle && node scripts/check-serve-fast-path-bundle.js",
"desktop-openwork-sync": "bun run scripts/desktop-openwork-sync.ts",
"clean": "node scripts/clean.js",
"pre-commit": "node scripts/pre-commit.js"
Expand Down
76 changes: 76 additions & 0 deletions packages/cli/src/acp-integration/acpAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ vi.mock('@qwen-code/qwen-code-core', () => ({
getAutoMemoryRoot: vi.fn(
(projectRoot: string) => `${projectRoot}/.qwen/memory`,
),
getUserAutoMemoryRoot: vi.fn(() => '/tmp/user-memory'),
QwenOAuth2Event: {},
qwenOAuth2Events: { on: vi.fn(), off: vi.fn() },
MCPDiscoveryState: {
Expand Down Expand Up @@ -347,6 +348,8 @@ vi.mock('@qwen-code/qwen-code-core', () => ({
SessionService: vi.fn(),
Storage: {
getGlobalQwenDir: vi.fn(() => '/tmp/qwen-global-test'),
getGlobalTempDir: vi.fn(() => '/tmp/qwen-global-temp'),
getUserExtensionsDir: vi.fn(() => '/tmp/qwen-extensions'),
},
parseRule: vi.fn((raw: string) => {
const trimmed = raw.trim();
Expand Down Expand Up @@ -597,6 +600,7 @@ import {
} from '../config/permission-settings.js';
import { loadCliConfig } from '../config/config.js';
import { createLoadedSettingsAdapter } from '../config/loadedSettingsAdapter.js';
import { AcpFileSystemService } from './service/filesystem.js';
import { Session, buildAvailableCommandsSnapshot } from './session/Session.js';
import {
SERVE_STATUS_EXT_METHODS,
Expand Down Expand Up @@ -1215,6 +1219,78 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
await agentPromise;
});

it('configures ACP file system fallback roots for read_file allowed local roots', async () => {
const fsCapabilities = { readTextFile: true, writeTextFile: true };
const fallbackFileSystem = {};
const innerConfig = {
...makeInnerConfig(),
getTargetDir: vi.fn().mockReturnValue('/project'),
getSessionId: vi.fn().mockReturnValue('session-with-fs'),
getFileSystemService: vi.fn().mockReturnValue(fallbackFileSystem),
setFileSystemService: vi.fn(),
storage: {
getProjectTempDir: vi.fn().mockReturnValue('/project/.qwen/tmp'),
getProjectDir: vi.fn().mockReturnValue('/project'),
getUserSkillsDirs: vi.fn().mockReturnValue(['/home/test/.qwen/skills']),
},
};
vi.mocked(loadSettings).mockReturnValue(makeSessionSettings());
vi.mocked(loadCliConfig).mockResolvedValue(
innerConfig as unknown as Config,
);
vi.mocked(Session).mockImplementation(
() =>
({
getId: vi.fn().mockReturnValue('session-with-fs'),
getConfig: vi.fn().mockReturnValue(innerConfig),
sendAvailableCommandsUpdate: vi.fn().mockResolvedValue(undefined),
replayHistory: vi.fn().mockResolvedValue(undefined),
installRewriter: vi.fn(),
startCronScheduler: vi.fn(),
dispose: vi.fn(),
}) as unknown as InstanceType<typeof Session>,
);

const agentPromise = runAcpAgent(
mockConfig,
makeSessionSettings(),
mockArgv,
);
await vi.waitFor(() => expect(capturedAgentFactory).toBeDefined());

const fakeConn = {
get closed() {
return mockConnectionState.promise;
},
} as AgentSideConnectionLike;
const agent = capturedAgentFactory!(fakeConn) as AgentLike;

await agent.initialize({ clientCapabilities: { fs: fsCapabilities } });
await agent.newSession({ cwd: '/project', mcpServers: [] });

expect(AcpFileSystemService).toHaveBeenCalledWith(
fakeConn,
'session-with-fs',
fsCapabilities,
fallbackFileSystem,
{
localReadRoots: [
'/project/.qwen/tmp',
'/project/subagents',
'/tmp/qwen-global-temp',
'/project/.qwen/memory',
'/tmp/user-memory',
'/home/test/.qwen/skills',
'/tmp/qwen-extensions',
],
},
);
expect(innerConfig.setFileSystemService).toHaveBeenCalled();

mockConnectionState.resolve();
await agentPromise;
});

it('does not return discontinued qwen-oauth as the only ACP auth option', async () => {
vi.mocked(buildAuthMethods).mockReturnValue([
{
Expand Down
14 changes: 14 additions & 0 deletions packages/cli/src/acp-integration/acpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
findProviderById,
getAllGeminiMdFilenames,
getAutoMemoryRoot,
getUserAutoMemoryRoot,
Comment thread
doudouOUC marked this conversation as resolved.
Resolved
getDefaultBaseUrlForProtocol,
getDefaultModelIds,
getScopedEnvContents,
Expand Down Expand Up @@ -7627,6 +7628,19 @@ class QwenAgent implements Agent {
config.getSessionId(),
this.clientCapabilities.fs,
config.getFileSystemService(),
{
// SYNC: Mirrors ReadFileTool's default allowed local roots, including
// auto-memory roots, so ACP-local read fallback follows the same policy.
localReadRoots: [
Comment thread
doudouOUC marked this conversation as resolved.
Resolved
config.storage.getProjectTempDir(),
path.join(config.storage.getProjectDir(), 'subagents'),
Storage.getGlobalTempDir(),
Comment thread
doudouOUC marked this conversation as resolved.
Resolved
getAutoMemoryRoot(config.getTargetDir()),
getUserAutoMemoryRoot(),
...config.storage.getUserSkillsDirs(),
Storage.getUserExtensionsDir(),
],
},
);
config.setFileSystemService(acpFileSystemService);
}
Expand Down
Loading
Loading