Merge branch 'main' into feat/ide-companion-discovery

This commit is contained in:
tanzhenxin
2025-12-16 14:11:25 +08:00
129 changed files with 4854 additions and 4100 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-core",
"version": "0.5.0",
"version": "0.5.1",
"description": "Qwen Code Core",
"repository": {
"type": "git",

View File

@@ -318,6 +318,7 @@ export interface ConfigParameters {
generationConfig?: Partial<ContentGeneratorConfig>;
cliVersion?: string;
loadMemoryFromIncludeDirectories?: boolean;
chatRecording?: boolean;
// Web search providers
webSearch?: {
provider: Array<{
@@ -457,6 +458,7 @@ export class Config {
| undefined;
private readonly cliVersion?: string;
private readonly experimentalZedIntegration: boolean = false;
private readonly chatRecordingEnabled: boolean;
private readonly loadMemoryFromIncludeDirectories: boolean = false;
private readonly webSearch?: {
provider: Array<{
@@ -572,6 +574,8 @@ export class Config {
._generationConfig as ContentGeneratorConfig;
this.cliVersion = params.cliVersion;
this.chatRecordingEnabled = params.chatRecording ?? true;
this.loadMemoryFromIncludeDirectories =
params.loadMemoryFromIncludeDirectories ?? false;
this.chatCompression = params.chatCompression;
@@ -618,7 +622,9 @@ export class Config {
setGlobalDispatcher(new ProxyAgent(this.getProxy() as string));
}
this.geminiClient = new GeminiClient(this);
this.chatRecordingService = new ChatRecordingService(this);
this.chatRecordingService = this.chatRecordingEnabled
? new ChatRecordingService(this)
: undefined;
}
/**
@@ -738,7 +744,9 @@ export class Config {
startNewSession(sessionId?: string): string {
this.sessionId = sessionId ?? randomUUID();
this.sessionData = undefined;
this.chatRecordingService = new ChatRecordingService(this);
this.chatRecordingService = this.chatRecordingEnabled
? new ChatRecordingService(this)
: undefined;
if (this.initialized) {
logStartSession(this, new StartSessionEvent(this));
}
@@ -1267,7 +1275,10 @@ export class Config {
/**
* Returns the chat recording service.
*/
getChatRecordingService(): ChatRecordingService {
getChatRecordingService(): ChatRecordingService | undefined {
if (!this.chatRecordingEnabled) {
return undefined;
}
if (!this.chatRecordingService) {
this.chatRecordingService = new ChatRecordingService(this);
}

View File

@@ -69,6 +69,8 @@ async function createMockConfig(
targetDir: '.',
debugMode: false,
cwd: process.cwd(),
// Avoid writing any chat recording records from tests (e.g. via tool-call telemetry).
chatRecording: false,
};
const config = new Config(configParams);
await config.initialize();

View File

@@ -198,6 +198,52 @@ describe('GlobTool', () => {
);
});
it('should find files even if workspace path casing differs from glob results (Windows/macOS)', async () => {
// Only relevant for Windows and macOS
if (process.platform !== 'win32' && process.platform !== 'darwin') {
return;
}
let mismatchedRootDir = tempRootDir;
if (process.platform === 'win32') {
// 1. Create a path with mismatched casing for the workspace root
// e.g., if tempRootDir is "C:\Users\...", make it "c:\Users\..."
const drive = path.parse(tempRootDir).root;
if (!drive || !drive.match(/^[A-Z]:\\/)) {
// Skip if we can't determine/manipulate the drive letter easily
return;
}
const lowerDrive = drive.toLowerCase();
mismatchedRootDir = lowerDrive + tempRootDir.substring(drive.length);
} else {
// macOS: change the casing of the path
if (tempRootDir === tempRootDir.toLowerCase()) {
mismatchedRootDir = tempRootDir.toUpperCase();
} else {
mismatchedRootDir = tempRootDir.toLowerCase();
}
}
// 2. Create a new GlobTool instance with this mismatched root
const mismatchedConfig = {
...mockConfig,
getTargetDir: () => mismatchedRootDir,
getWorkspaceContext: () =>
createMockWorkspaceContext(mismatchedRootDir),
} as unknown as Config;
const mismatchedGlobTool = new GlobTool(mismatchedConfig);
// 3. Execute search
const params: GlobToolParams = { pattern: '*.txt' };
const invocation = mismatchedGlobTool.build(params);
const result = await invocation.execute(abortSignal);
expect(result.llmContent).toContain('Found 2 file(s)');
});
it('should return error if path is outside workspace', async () => {
// Bypassing validation to test execute method directly
vi.spyOn(globTool, 'validateToolParams').mockReturnValue(null);

View File

@@ -134,12 +134,21 @@ class GlobToolInvocation extends BaseToolInvocation<
this.getFileFilteringOptions(),
);
const normalizePathForComparison = (p: string) =>
process.platform === 'win32' || process.platform === 'darwin'
? p.toLowerCase()
: p;
const filteredAbsolutePaths = new Set(
filteredPaths.map((p) => path.resolve(this.config.getTargetDir(), p)),
filteredPaths.map((p) =>
normalizePathForComparison(
path.resolve(this.config.getTargetDir(), p),
),
),
);
const filteredEntries = allEntries.filter((entry) =>
filteredAbsolutePaths.has(entry.fullpath()),
filteredAbsolutePaths.has(normalizePathForComparison(entry.fullpath())),
);
if (!filteredEntries || filteredEntries.length === 0) {