Compare commits

..

11 Commits

Author SHA1 Message Date
Mingholy
985f65f8fa Merge pull request #1494 from QwenLM/chore/v0.7.1
chore: bump version to 0.7.1
2026-01-14 18:29:59 +08:00
Mingholy
9b9c5fadd5 Merge pull request #1492 from QwenLM/mingholy/fix/loggingContentGenerator-timing-issue
Fix timing issue in LoggingContentGenerator initialization
2026-01-14 18:09:26 +08:00
Mingholy
372c67cad4 Merge pull request #1489 from QwenLM/fix/slow-quit
Reduce slow quit by trimming skills watchers
2026-01-14 18:07:37 +08:00
mingholy.lmh
af3864b5de chore: bump version to 0.7.1
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-01-14 18:02:43 +08:00
mingholy.lmh
1e3791f30a fix: ci issue 2026-01-14 17:51:00 +08:00
mingholy.lmh
9bf626d051 refactor: streamline initialization of LoggingContentGenerator and update auth type retrieval 2026-01-14 16:44:51 +08:00
mingholy.lmh
a35af6550f fix: timing issue of initialize loggingContentGenerator 2026-01-14 16:17:35 +08:00
tanzhenxin
d6607e134e update 2026-01-14 15:40:53 +08:00
tanzhenxin
9024a41723 Conditional skill manager initialization with improved file watching
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-01-14 15:22:49 +08:00
pomelo
ff5ea3c6d7 Merge pull request #1485 from QwenLM/fix-docs
fix: docs
2026-01-14 10:31:55 +08:00
pomelo-nwu
0faaac8fa4 fix: docs 2026-01-14 10:30:03 +08:00
14 changed files with 126 additions and 107 deletions

View File

@@ -22,13 +22,7 @@
### Installation
1. Install Qwen Code CLI:
```bash
npm install -g qwen-code
```
2. Download and install the extension from the [Visual Studio Code Extension Marketplace](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion).
Download and install the extension from the [Visual Studio Code Extension Marketplace](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion).
## Troubleshooting

View File

@@ -20,9 +20,9 @@
1. Install Qwen Code CLI:
```bash
npm install -g qwen-code
```
```bash
npm install -g @qwen-code/qwen-code
```
2. Download and install [Zed Editor](https://zed.dev/)

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.7.0",
"version": "0.7.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@qwen-code/qwen-code",
"version": "0.7.0",
"version": "0.7.1",
"workspaces": [
"packages/*"
],
@@ -17310,7 +17310,7 @@
},
"packages/cli": {
"name": "@qwen-code/qwen-code",
"version": "0.7.0",
"version": "0.7.1",
"dependencies": {
"@google/genai": "1.30.0",
"@iarna/toml": "^2.2.5",
@@ -17947,7 +17947,7 @@
},
"packages/core": {
"name": "@qwen-code/qwen-code-core",
"version": "0.7.0",
"version": "0.7.1",
"hasInstallScript": true,
"dependencies": {
"@anthropic-ai/sdk": "^0.36.1",
@@ -21408,7 +21408,7 @@
},
"packages/test-utils": {
"name": "@qwen-code/qwen-code-test-utils",
"version": "0.7.0",
"version": "0.7.1",
"dev": true,
"license": "Apache-2.0",
"devDependencies": {
@@ -21420,7 +21420,7 @@
},
"packages/vscode-ide-companion": {
"name": "qwen-code-vscode-ide-companion",
"version": "0.7.0",
"version": "0.7.1",
"license": "LICENSE",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.25.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.7.0",
"version": "0.7.1",
"engines": {
"node": ">=20.0.0"
},
@@ -13,7 +13,7 @@
"url": "git+https://github.com/QwenLM/qwen-code.git"
},
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.7.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.7.1"
},
"scripts": {
"start": "cross-env node scripts/start.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code",
"version": "0.7.0",
"version": "0.7.1",
"description": "Qwen Code",
"repository": {
"type": "git",
@@ -33,7 +33,7 @@
"dist"
],
"config": {
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.7.0"
"sandboxImageUri": "ghcr.io/qwenlm/qwen-code:0.7.1"
},
"dependencies": {
"@google/genai": "1.30.0",

View File

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

View File

@@ -404,7 +404,7 @@ export class Config {
private toolRegistry!: ToolRegistry;
private promptRegistry!: PromptRegistry;
private subagentManager!: SubagentManager;
private skillManager!: SkillManager;
private skillManager: SkillManager | null = null;
private fileSystemService: FileSystemService;
private contentGeneratorConfig!: ContentGeneratorConfig;
private contentGeneratorConfigSources: ContentGeneratorConfigSources = {};
@@ -672,8 +672,10 @@ export class Config {
}
this.promptRegistry = new PromptRegistry();
this.subagentManager = new SubagentManager(this);
this.skillManager = new SkillManager(this);
await this.skillManager.startWatching();
if (this.getExperimentalSkills()) {
this.skillManager = new SkillManager(this);
await this.skillManager.startWatching();
}
// Load session subagents if they were provided before initialization
if (this.sessionSubagents.length > 0) {
@@ -1439,7 +1441,7 @@ export class Config {
return this.subagentManager;
}
getSkillManager(): SkillManager {
getSkillManager(): SkillManager | null {
return this.skillManager;
}

View File

@@ -270,28 +270,28 @@ export function createContentGeneratorConfig(
}
export async function createContentGenerator(
config: ContentGeneratorConfig,
gcConfig: Config,
generatorConfig: ContentGeneratorConfig,
config: Config,
isInitialAuth?: boolean,
): Promise<ContentGenerator> {
const validation = validateModelConfig(config, false);
const validation = validateModelConfig(generatorConfig, false);
if (!validation.valid) {
throw new Error(validation.errors.map((e) => e.message).join('\n'));
}
if (config.authType === AuthType.USE_OPENAI) {
// Import OpenAIContentGenerator dynamically to avoid circular dependencies
const authType = generatorConfig.authType;
if (!authType) {
throw new Error('ContentGeneratorConfig must have an authType');
}
let baseGenerator: ContentGenerator;
if (authType === AuthType.USE_OPENAI) {
const { createOpenAIContentGenerator } = await import(
'./openaiContentGenerator/index.js'
);
// Always use OpenAIContentGenerator, logging is controlled by enableOpenAILogging flag
const generator = createOpenAIContentGenerator(config, gcConfig);
return new LoggingContentGenerator(generator, gcConfig);
}
if (config.authType === AuthType.QWEN_OAUTH) {
// Import required classes dynamically
baseGenerator = createOpenAIContentGenerator(generatorConfig, config);
} else if (authType === AuthType.QWEN_OAUTH) {
const { getQwenOAuthClient: getQwenOauthClient } = await import(
'../qwen/qwenOAuth2.js'
);
@@ -300,44 +300,38 @@ export async function createContentGenerator(
);
try {
// Get the Qwen OAuth client (now includes integrated token management)
// If this is initial auth, require cached credentials to detect missing credentials
const qwenClient = await getQwenOauthClient(
gcConfig,
config,
isInitialAuth ? { requireCachedCredentials: true } : undefined,
);
// Create the content generator with dynamic token management
const generator = new QwenContentGenerator(qwenClient, config, gcConfig);
return new LoggingContentGenerator(generator, gcConfig);
baseGenerator = new QwenContentGenerator(
qwenClient,
generatorConfig,
config,
);
} catch (error) {
throw new Error(
`${error instanceof Error ? error.message : String(error)}`,
);
}
}
if (config.authType === AuthType.USE_ANTHROPIC) {
} else if (authType === AuthType.USE_ANTHROPIC) {
const { createAnthropicContentGenerator } = await import(
'./anthropicContentGenerator/index.js'
);
const generator = createAnthropicContentGenerator(config, gcConfig);
return new LoggingContentGenerator(generator, gcConfig);
}
if (
config.authType === AuthType.USE_GEMINI ||
config.authType === AuthType.USE_VERTEX_AI
baseGenerator = createAnthropicContentGenerator(generatorConfig, config);
} else if (
authType === AuthType.USE_GEMINI ||
authType === AuthType.USE_VERTEX_AI
) {
const { createGeminiContentGenerator } = await import(
'./geminiContentGenerator/index.js'
);
const generator = createGeminiContentGenerator(config, gcConfig);
return new LoggingContentGenerator(generator, gcConfig);
baseGenerator = createGeminiContentGenerator(generatorConfig, config);
} else {
throw new Error(
`Error creating contentGenerator: Unsupported authType: ${authType}`,
);
}
throw new Error(
`Error creating contentGenerator: Unsupported authType: ${config.authType}`,
);
return new LoggingContentGenerator(baseGenerator, config, generatorConfig);
}

View File

@@ -12,6 +12,7 @@ import type {
import { GenerateContentResponse } from '@google/genai';
import type { Config } from '../../config/config.js';
import type { ContentGenerator } from '../contentGenerator.js';
import { AuthType } from '../contentGenerator.js';
import { LoggingContentGenerator } from './index.js';
import { OpenAIContentConverter } from '../openaiContentGenerator/converter.js';
import {
@@ -50,14 +51,17 @@ const convertGeminiResponseToOpenAISpy = vi
choices: [],
} as OpenAI.Chat.ChatCompletion);
const createConfig = (overrides: Record<string, unknown> = {}): Config =>
({
getContentGeneratorConfig: () => ({
authType: 'openai',
enableOpenAILogging: false,
...overrides,
}),
}) as Config;
const createConfig = (overrides: Record<string, unknown> = {}): Config => {
const configContent = {
authType: 'openai',
enableOpenAILogging: false,
...overrides,
};
return {
getContentGeneratorConfig: () => configContent,
getAuthType: () => configContent.authType as AuthType | undefined,
} as Config;
};
const createWrappedGenerator = (
generateContent: ContentGenerator['generateContent'],
@@ -124,13 +128,17 @@ describe('LoggingContentGenerator', () => {
),
vi.fn(),
);
const generatorConfig = {
model: 'test-model',
authType: AuthType.USE_OPENAI,
enableOpenAILogging: true,
openAILoggingDir: 'logs',
schemaCompliance: 'openapi_30' as const,
};
const generator = new LoggingContentGenerator(
wrapped,
createConfig({
enableOpenAILogging: true,
openAILoggingDir: 'logs',
schemaCompliance: 'openapi_30',
}),
createConfig(),
generatorConfig,
);
const request = {
@@ -225,9 +233,15 @@ describe('LoggingContentGenerator', () => {
vi.fn().mockRejectedValue(error),
vi.fn(),
);
const generatorConfig = {
model: 'test-model',
authType: AuthType.USE_OPENAI,
enableOpenAILogging: true,
};
const generator = new LoggingContentGenerator(
wrapped,
createConfig({ enableOpenAILogging: true }),
createConfig(),
generatorConfig,
);
const request = {
@@ -293,9 +307,15 @@ describe('LoggingContentGenerator', () => {
})(),
),
);
const generatorConfig = {
model: 'test-model',
authType: AuthType.USE_OPENAI,
enableOpenAILogging: true,
};
const generator = new LoggingContentGenerator(
wrapped,
createConfig({ enableOpenAILogging: true }),
createConfig(),
generatorConfig,
);
const request = {
@@ -345,9 +365,15 @@ describe('LoggingContentGenerator', () => {
})(),
),
);
const generatorConfig = {
model: 'test-model',
authType: AuthType.USE_OPENAI,
enableOpenAILogging: true,
};
const generator = new LoggingContentGenerator(
wrapped,
createConfig({ enableOpenAILogging: true }),
createConfig(),
generatorConfig,
);
const request = {

View File

@@ -31,7 +31,10 @@ import {
logApiRequest,
logApiResponse,
} from '../../telemetry/loggers.js';
import type { ContentGenerator } from '../contentGenerator.js';
import type {
ContentGenerator,
ContentGeneratorConfig,
} from '../contentGenerator.js';
import { isStructuredError } from '../../utils/quotaErrorDetection.js';
import { OpenAIContentConverter } from '../openaiContentGenerator/converter.js';
import { OpenAILogger } from '../../utils/openaiLogger.js';
@@ -50,9 +53,11 @@ export class LoggingContentGenerator implements ContentGenerator {
constructor(
private readonly wrapped: ContentGenerator,
private readonly config: Config,
generatorConfig: ContentGeneratorConfig,
) {
const generatorConfig = this.config.getContentGeneratorConfig();
if (generatorConfig?.enableOpenAILogging) {
// Extract fields needed for initialization from passed config
// (config.getContentGeneratorConfig() may not be available yet during refreshAuth)
if (generatorConfig.enableOpenAILogging) {
this.openaiLogger = new OpenAILogger(generatorConfig.openAILoggingDir);
this.schemaCompliance = generatorConfig.schemaCompliance;
}
@@ -89,7 +94,7 @@ export class LoggingContentGenerator implements ContentGenerator {
model,
durationMs,
prompt_id,
this.config.getContentGeneratorConfig()?.authType,
this.config.getAuthType(),
usageMetadata,
responseText,
),
@@ -126,7 +131,7 @@ export class LoggingContentGenerator implements ContentGenerator {
errorMessage,
durationMs,
prompt_id,
this.config.getContentGeneratorConfig()?.authType,
this.config.getAuthType(),
errorType,
errorStatus,
),

View File

@@ -235,6 +235,7 @@ export class SkillManager {
}
this.watchStarted = true;
await this.ensureUserSkillsDir();
await this.refreshCache();
this.updateWatchersFromCache();
}
@@ -486,29 +487,14 @@ export class SkillManager {
}
private updateWatchersFromCache(): void {
const desiredPaths = new Set<string>();
for (const level of ['project', 'user'] as const) {
const baseDir = this.getSkillsBaseDir(level);
const parentDir = path.dirname(baseDir);
if (fsSync.existsSync(parentDir)) {
desiredPaths.add(parentDir);
}
if (fsSync.existsSync(baseDir)) {
desiredPaths.add(baseDir);
}
const levelSkills = this.skillsCache?.get(level) || [];
for (const skill of levelSkills) {
const skillDir = path.dirname(skill.filePath);
if (fsSync.existsSync(skillDir)) {
desiredPaths.add(skillDir);
}
}
}
const watchTargets = new Set<string>(
(['project', 'user'] as const)
.map((level) => this.getSkillsBaseDir(level))
.filter((baseDir) => fsSync.existsSync(baseDir)),
);
for (const existingPath of this.watchers.keys()) {
if (!desiredPaths.has(existingPath)) {
if (!watchTargets.has(existingPath)) {
void this.watchers
.get(existingPath)
?.close()
@@ -522,7 +508,7 @@ export class SkillManager {
}
}
for (const watchPath of desiredPaths) {
for (const watchPath of watchTargets) {
if (this.watchers.has(watchPath)) {
continue;
}
@@ -557,4 +543,16 @@ export class SkillManager {
void this.refreshCache().then(() => this.updateWatchersFromCache());
}, 150);
}
private async ensureUserSkillsDir(): Promise<void> {
const baseDir = this.getSkillsBaseDir('user');
try {
await fs.mkdir(baseDir, { recursive: true });
} catch (error) {
console.warn(
`Failed to create user skills directory at ${baseDir}:`,
error,
);
}
}
}

View File

@@ -53,7 +53,7 @@ export class SkillTool extends BaseDeclarativeTool<SkillParams, ToolResult> {
false, // canUpdateOutput
);
this.skillManager = config.getSkillManager();
this.skillManager = config.getSkillManager()!;
this.skillManager.addChangeListener(() => {
void this.refreshSkills();
});

View File

@@ -1,6 +1,6 @@
{
"name": "@qwen-code/qwen-code-test-utils",
"version": "0.7.0",
"version": "0.7.1",
"private": true,
"main": "src/index.ts",
"license": "Apache-2.0",

View File

@@ -2,7 +2,7 @@
"name": "qwen-code-vscode-ide-companion",
"displayName": "Qwen Code Companion",
"description": "Enable Qwen Code with direct access to your VS Code workspace.",
"version": "0.7.0",
"version": "0.7.1",
"publisher": "qwenlm",
"icon": "assets/icon.png",
"repository": {