Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"build:sandbox": "node scripts/build_sandbox.js",
"bundle": "npm run generate && node esbuild.config.js && node scripts/copy_bundle_assets.js",
"test": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test --workspaces --if-present --parallel",
"test:ci": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present --parallel && npm run test:scripts",
"test:ci": "cross-env NODE_OPTIONS=\"--max-old-space-size=3072\" npm run test:ci --workspaces --if-present --parallel && npm run test:scripts && npm run check:serve-fast-path-bundle",
"test:scripts": "vitest run --config ./scripts/tests/vitest.config.ts",
"test:e2e": "cross-env VERBOSE=true KEEP_OUTPUT=true npm run test:integration:sandbox:none",
"test:integration:all": "npm run test:integration:sandbox:none && npm run test:integration:sandbox:docker && npm run test:integration:sandbox:podman",
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',
path.join('/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.
getDefaultBaseUrlForProtocol,
getDefaultModelIds,
getScopedEnvContents,
Expand Down Expand Up @@ -7630,6 +7631,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.
Comment thread
doudouOUC marked this conversation as resolved.
config.storage.getProjectTempDir(),
path.join(config.storage.getProjectDir(), 'subagents'),
Storage.getGlobalTempDir(),
Comment thread
doudouOUC marked this conversation as resolved.
Comment thread
doudouOUC marked this conversation as resolved.
getAutoMemoryRoot(config.getTargetDir()),
getUserAutoMemoryRoot(),
...config.storage.getUserSkillsDirs(),
Storage.getUserExtensionsDir(),
],
},
);
config.setFileSystemService(acpFileSystemService);
}
Expand Down
Loading
Loading