Compare commits

..

28 Commits

Author SHA1 Message Date
tanzhenxin
0681c71894 Merge pull request #1490 from QwenLM/fix/mcp-server-remove
fix: unable to remove MCP server when only one element exists
2026-01-16 17:22:42 +08:00
tanzhenxin
155c4b9728 Merge pull request #1508 from PJ-568/main
fix: mistranslation of token
2026-01-16 15:50:00 +08:00
tanzhenxin
57ca2823b3 Merge pull request #1497 from Antovex/feat/settings-for-experimental-skills
feat(cli): add settings support for experimental skills
2026-01-16 15:49:34 +08:00
tanzhenxin
620341eeae remove -x alias and fix whitespace issue
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-01-16 15:28:33 +08:00
PJ568
c6c33233c5 fix: mistranslation of token 2026-01-15 18:16:31 +08:00
Antarin Ghosal
106b69e5c0 docs: update experimental skills configuration in skills.md 2026-01-15 15:02:14 +05:30
Antarin Ghosal
6afe0f8c29 docs: update setting name in configuration docs 2026-01-15 14:59:52 +05:30
Antarin Ghosal
0b3be1a82c fix: update settings path to tools.experimental.skills 2026-01-15 14:58:31 +05:30
Antarin Ghosal
8af43e3ac3 refactor: nest skills under tools.experimental 2026-01-15 14:57:02 +05:30
tanzhenxin
886f914fb3 Merge pull request #1496 from QwenLM/fix/vscode-run
fix(vscode-ide-companion): simplify ELECTRON_RUN_AS_NODE detection and improve README
2026-01-15 09:00:11 +08:00
tanzhenxin
90365af2f8 Merge pull request #1499 from QwenLM/fix/1498
fix: include --acp flag in tool exclusion check
2026-01-15 08:56:58 +08:00
yiliang114
cbef5ffd89 fix: include --acp flag in tool exclusion check
Fixed #1498

The tool exclusion logic only checked --experimental-acp but not --acp,
causing edit, write_file, and run_shell_command to be incorrectly
excluded when VS Code extension uses --acp flag in ACP mode.
2026-01-14 22:49:04 +08:00
Antarin Ghosal
63406b4ba4 Update command options for skills feature
Fixed a typo
2026-01-14 19:13:35 +05:30
Antarin Ghosal
52db3a766d feat(cli): add settings support for experimental skills
- Add tools.experimentalSkills setting in settingsSchema
- Read default from settings in config. ts
- Add --skills as shorter alias for --experimental-skills
- Update documentation for new setting

Fixes #1493
2026-01-14 18:49:17 +05:30
yiliang114
5e80e80387 fix(vscode-ide-companion): simplify ELECTRON_RUN_AS_NODE detection and improve README
- Bump version to 0.7.1
- Simplify macOS/Linux terminal launch by always using ELECTRON_RUN_AS_NODE=1
  (all VSCode-like IDEs are Electron-based)
- Update README with marketplace badges, cleaner docs structure
- Fix broken markdown table row

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 21:10:19 +08:00
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
LaZzyMan
6f33d92b2c fix: can not remove the mcp server when there is only one element 2026-01-14 16:27:45 +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
yiliang114
bde056b62e Merge branch 'main' of https://github.com/QwenLM/qwen-code into fix/vscode-run 2026-01-14 13:11:58 +08:00
pomelo
ff5ea3c6d7 Merge pull request #1485 from QwenLM/fix-docs
fix: docs
2026-01-14 10:31:55 +08:00
yiliang114
97497457a8 Merge branch 'main' of https://github.com/QwenLM/qwen-code into fix/vscode-run 2026-01-13 14:21:26 +08:00
21 changed files with 236 additions and 178 deletions

View File

@@ -275,6 +275,7 @@ If you are experiencing performance issues with file searching (e.g., with `@` c
| `tools.truncateToolOutputThreshold` | number | Truncate tool output if it is larger than this many characters. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `25000` | Requires restart: Yes | | `tools.truncateToolOutputThreshold` | number | Truncate tool output if it is larger than this many characters. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `25000` | Requires restart: Yes |
| `tools.truncateToolOutputLines` | number | Maximum lines or entries kept when truncating tool output. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `1000` | Requires restart: Yes | | `tools.truncateToolOutputLines` | number | Maximum lines or entries kept when truncating tool output. Applies to Shell, Grep, Glob, ReadFile and ReadManyFiles tools. | `1000` | Requires restart: Yes |
| `tools.autoAccept` | boolean | Controls whether the CLI automatically accepts and executes tool calls that are considered safe (e.g., read-only operations) without explicit user confirmation. If set to `true`, the CLI will bypass the confirmation prompt for tools deemed safe. | `false` | | | `tools.autoAccept` | boolean | Controls whether the CLI automatically accepts and executes tool calls that are considered safe (e.g., read-only operations) without explicit user confirmation. If set to `true`, the CLI will bypass the confirmation prompt for tools deemed safe. | `false` | |
| `tools.experimental.skills` | boolean | Enable experimental Agent Skills feature | `false` | |
#### mcp #### mcp

View File

@@ -11,12 +11,29 @@ This guide shows you how to create, use, and manage Agent Skills in **Qwen Code*
## Prerequisites ## Prerequisites
- Qwen Code (recent version) - Qwen Code (recent version)
- Run with the experimental flag enabled:
## How to enable
### Via CLI flag
```bash ```bash
qwen --experimental-skills qwen --experimental-skills
``` ```
### Via settings.json
Add to your `~/.qwen/settings.json` or project's `.qwen/settings.json`:
```json
{
"tools": {
"experimental": {
"skills": true
}
}
}
```
- Basic familiarity with Qwen Code ([Quickstart](../quickstart.md)) - Basic familiarity with Qwen Code ([Quickstart](../quickstart.md))
## What are Agent Skills? ## What are Agent Skills?

12
package-lock.json generated
View File

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

View File

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

View File

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

View File

@@ -334,7 +334,7 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
.option('experimental-skills', { .option('experimental-skills', {
type: 'boolean', type: 'boolean',
description: 'Enable experimental Skills feature', description: 'Enable experimental Skills feature',
default: false, default: settings.tools?.experimental?.skills ?? false,
}) })
.option('channel', { .option('channel', {
type: 'string', type: 'string',
@@ -874,11 +874,10 @@ export async function loadCliConfig(
} }
}; };
if ( // ACP mode check: must include both --acp (current) and --experimental-acp (deprecated).
!interactive && // Without this check, edit, write_file, run_shell_command would be excluded in ACP mode.
!argv.experimentalAcp && const isAcpMode = argv.acp || argv.experimentalAcp;
inputFormat !== InputFormat.STREAM_JSON if (!interactive && !isAcpMode && inputFormat !== InputFormat.STREAM_JSON) {
) {
switch (approvalMode) { switch (approvalMode) {
case ApprovalMode.PLAN: case ApprovalMode.PLAN:
case ApprovalMode.DEFAULT: case ApprovalMode.DEFAULT:

View File

@@ -981,6 +981,27 @@ const SETTINGS_SCHEMA = {
description: 'The number of lines to keep when truncating tool output.', description: 'The number of lines to keep when truncating tool output.',
showInDialog: true, showInDialog: true,
}, },
experimental: {
type: 'object',
label: 'Experimental',
category: 'Tools',
requiresRestart: true,
default: {},
description: 'Experimental tool features.',
showInDialog: false,
properties: {
skills: {
type: 'boolean',
label: 'Skills',
category: 'Tools',
requiresRestart: true,
default: false,
description:
'Enable experimental Agent Skills feature. When enabled, Qwen Code can use Skills from .qwen/skills/ and ~/.qwen/skills/.',
showInDialog: true,
},
},
},
}, },
}, },

View File

@@ -873,11 +873,11 @@ export default {
'Session Stats': '会话统计', 'Session Stats': '会话统计',
'Model Usage': '模型使用情况', 'Model Usage': '模型使用情况',
Reqs: '请求数', Reqs: '请求数',
'Input Tokens': '输入令牌', 'Input Tokens': '输入 token 数',
'Output Tokens': '输出令牌', 'Output Tokens': '输出 token 数',
'Savings Highlight:': '节省亮点:', 'Savings Highlight:': '节省亮点:',
'of input tokens were served from the cache, reducing costs.': 'of input tokens were served from the cache, reducing costs.':
'的输入令牌来自缓存,降低了成本', '从缓存载入 token ,降低了成本',
'Tip: For a full token breakdown, run `/stats model`.': 'Tip: For a full token breakdown, run `/stats model`.':
'提示:要查看完整的令牌明细,请运行 `/stats model`', '提示:要查看完整的令牌明细,请运行 `/stats model`',
'Model Stats For Nerds': '模型统计(技术细节)', 'Model Stats For Nerds': '模型统计(技术细节)',

View File

@@ -8,7 +8,10 @@ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import * as path from 'node:path'; import * as path from 'node:path';
import * as os from 'node:os'; import * as os from 'node:os';
import { updateSettingsFilePreservingFormat } from './commentJson.js'; import {
updateSettingsFilePreservingFormat,
applyUpdates,
} from './commentJson.js';
describe('commentJson', () => { describe('commentJson', () => {
let tempDir: string; let tempDir: string;
@@ -180,3 +183,18 @@ describe('commentJson', () => {
}); });
}); });
}); });
describe('applyUpdates', () => {
it('should apply updates correctly', () => {
const original = { a: 1, b: { c: 2 } };
const updates = { b: { c: 3 } };
const result = applyUpdates(original, updates);
expect(result).toEqual({ a: 1, b: { c: 3 } });
});
it('should apply updates correctly when empty', () => {
const original = { a: 1, b: { c: 2 } };
const updates = { b: {} };
const result = applyUpdates(original, updates);
expect(result).toEqual({ a: 1, b: {} });
});
});

View File

@@ -38,7 +38,7 @@ export function updateSettingsFilePreservingFormat(
fs.writeFileSync(filePath, updatedContent, 'utf-8'); fs.writeFileSync(filePath, updatedContent, 'utf-8');
} }
function applyUpdates( export function applyUpdates(
current: Record<string, unknown>, current: Record<string, unknown>,
updates: Record<string, unknown>, updates: Record<string, unknown>,
): Record<string, unknown> { ): Record<string, unknown> {
@@ -50,6 +50,7 @@ function applyUpdates(
typeof value === 'object' && typeof value === 'object' &&
value !== null && value !== null &&
!Array.isArray(value) && !Array.isArray(value) &&
Object.keys(value).length > 0 &&
typeof result[key] === 'object' && typeof result[key] === 'object' &&
result[key] !== null && result[key] !== null &&
!Array.isArray(result[key]) !Array.isArray(result[key])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -235,6 +235,7 @@ export class SkillManager {
} }
this.watchStarted = true; this.watchStarted = true;
await this.ensureUserSkillsDir();
await this.refreshCache(); await this.refreshCache();
this.updateWatchersFromCache(); this.updateWatchersFromCache();
} }
@@ -486,29 +487,14 @@ export class SkillManager {
} }
private updateWatchersFromCache(): void { private updateWatchersFromCache(): void {
const desiredPaths = new Set<string>(); const watchTargets = new Set<string>(
(['project', 'user'] as const)
for (const level of ['project', 'user'] as const) { .map((level) => this.getSkillsBaseDir(level))
const baseDir = this.getSkillsBaseDir(level); .filter((baseDir) => fsSync.existsSync(baseDir)),
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);
}
}
}
for (const existingPath of this.watchers.keys()) { for (const existingPath of this.watchers.keys()) {
if (!desiredPaths.has(existingPath)) { if (!watchTargets.has(existingPath)) {
void this.watchers void this.watchers
.get(existingPath) .get(existingPath)
?.close() ?.close()
@@ -522,7 +508,7 @@ export class SkillManager {
} }
} }
for (const watchPath of desiredPaths) { for (const watchPath of watchTargets) {
if (this.watchers.has(watchPath)) { if (this.watchers.has(watchPath)) {
continue; continue;
} }
@@ -557,4 +543,16 @@ export class SkillManager {
void this.refreshCache().then(() => this.updateWatchersFromCache()); void this.refreshCache().then(() => this.updateWatchersFromCache());
}, 150); }, 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 false, // canUpdateOutput
); );
this.skillManager = config.getSkillManager(); this.skillManager = config.getSkillManager()!;
this.skillManager.addChangeListener(() => { this.skillManager.addChangeListener(() => {
void this.refreshSkills(); void this.refreshSkills();
}); });

View File

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

View File

@@ -1,6 +1,11 @@
# Qwen Code Companion # Qwen Code Companion
Seamlessly integrate [Qwen Code](https://github.com/QwenLM/qwen-code) into Visual Studio Code with native IDE features and an intuitive interface. This extension bundles everything you need to get started immediately. [![Version](https://img.shields.io/visual-studio-marketplace/v/qwenlm.qwen-code-vscode-ide-companion)](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion)
[![VS Code Installs](https://img.shields.io/visual-studio-marketplace/i/qwenlm.qwen-code-vscode-ide-companion)](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion)
[![Open VSX Downloads](https://img.shields.io/open-vsx/dt/qwenlm/qwen-code-vscode-ide-companion)](https://open-vsx.org/extension/qwenlm/qwen-code-vscode-ide-companion)
[![Rating](https://img.shields.io/visual-studio-marketplace/r/qwenlm.qwen-code-vscode-ide-companion)](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion)
Seamlessly integrate [Qwen Code](https://github.com/QwenLM/qwen-code) into Visual Studio Code with native IDE features and an intuitive chat interface. This extension bundles everything you need — no additional installation required.
## Demo ## Demo
@@ -11,7 +16,7 @@ Seamlessly integrate [Qwen Code](https://github.com/QwenLM/qwen-code) into Visua
## Features ## Features
- **Native IDE experience**: Dedicated Qwen Code sidebar panel accessed via the Qwen icon - **Native IDE experience**: Dedicated Qwen Code Chat panel accessed via the Qwen icon in the editor title bar
- **Native diffing**: Review, edit, and accept changes in VS Code's diff view - **Native diffing**: Review, edit, and accept changes in VS Code's diff view
- **Auto-accept edits mode**: Automatically apply Qwen's changes as they're made - **Auto-accept edits mode**: Automatically apply Qwen's changes as they're made
- **File management**: @-mention files or attach files and images using the system file picker - **File management**: @-mention files or attach files and images using the system file picker
@@ -20,73 +25,46 @@ Seamlessly integrate [Qwen Code](https://github.com/QwenLM/qwen-code) into Visua
## Requirements ## Requirements
- Visual Studio Code 1.85.0 or newer - Visual Studio Code 1.85.0 or newer (also works with Cursor, Windsurf, and other VS Code-based editors)
## Installation ## Quick Start
1. Install from the VS Code Marketplace: https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion 1. **Install** from the [VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion) or [Open VSX Registry](https://open-vsx.org/extension/qwenlm/qwen-code-vscode-ide-companion)
2. Two ways to use 2. **Open the Chat panel** using one of these methods:
- Chat panel: Click the Qwen icon in the Activity Bar, or run `Qwen Code: Open` from the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`). - Click the **Qwen icon** in the top-right corner of the editor
- Terminal session (classic): Run `Qwen Code: Run` to launch a session in the integrated terminal (bundled CLI). - Run `Qwen Code: Open` from the Command Palette (`Cmd+Shift+P` / `Ctrl+Shift+P`)
## Development and Debugging 3. **Start chatting** — Ask Qwen to help with coding tasks, explain code, fix bugs, or write new features
To debug and develop this extension locally: ## Commands
1. **Clone the repository** | Command | Description |
| -------------------------------- | ------------------------------------------------------ |
| `Qwen Code: Open` | Open the Qwen Code Chat panel |
| `Qwen Code: Run` | Launch a classic terminal session with the bundled CLI |
| `Qwen Code: Accept Current Diff` | Accept the currently displayed diff |
| `Qwen Code: Close Diff Editor` | Close/reject the current diff |
```bash ## Feedback & Issues
git clone https://github.com/QwenLM/qwen-code.git
cd qwen-code
```
2. **Install dependencies** - 🐛 [Report bugs](https://github.com/QwenLM/qwen-code/issues/new?template=bug_report.yml&labels=bug,vscode-ide-companion)
- 💡 [Request features](https://github.com/QwenLM/qwen-code/issues/new?template=feature_request.yml&labels=enhancement,vscode-ide-companion)
- 📖 [Documentation](https://qwenlm.github.io/qwen-code-docs/)
- 📋 [Changelog](https://github.com/QwenLM/qwen-code/releases)
```bash ## Contributing
npm install
# or if using pnpm
pnpm install
```
3. **Start debugging** We welcome contributions! See our [Contributing Guide](https://github.com/QwenLM/qwen-code/blob/main/CONTRIBUTING.md) for details on:
```bash - Setting up the development environment
code . # Open the project root in VS Code - Building and debugging the extension locally
``` - Submitting pull requests
- Open the `packages/vscode-ide-companion/src/extension.ts` file
- Open Debug panel (`Ctrl+Shift+D` or `Cmd+Shift+D`)
- Select **"Launch Companion VS Code Extension"** from the debug dropdown
- Press `F5` to launch Extension Development Host
4. **Make changes and reload**
- Edit the source code in the original VS Code window
- To see your changes, reload the Extension Development Host window by:
- Pressing `Ctrl+R` (Windows/Linux) or `Cmd+R` (macOS)
- Or clicking the "Reload" button in the debug toolbar
5. **View logs and debug output**
- Open the Debug Console in the original VS Code window to see extension logs
- In the Extension Development Host window, open Developer Tools with `Help > Toggle Developer Tools` to see webview logs
## Build for Production
To build the extension for distribution:
```bash
npm run compile
# or
pnpm run compile
```
To package the extension as a VSIX file:
```bash
npx vsce package
# or
pnpm vsce package
```
## Terms of Service and Privacy Notice ## Terms of Service and Privacy Notice
By installing this extension, you agree to the [Terms of Service](https://github.com/QwenLM/qwen-code/blob/main/docs/tos-privacy.md). By installing this extension, you agree to the [Terms of Service](https://github.com/QwenLM/qwen-code/blob/main/docs/tos-privacy.md).
## License
[Apache-2.0](https://github.com/QwenLM/qwen-code/blob/main/LICENSE)

View File

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

View File

@@ -314,34 +314,32 @@ export async function activate(context: vscode.ExtensionContext) {
'cli.js', 'cli.js',
).fsPath; ).fsPath;
const execPath = process.execPath; const execPath = process.execPath;
const lowerExecPath = execPath.toLowerCase();
const needsElectronRunAsNode =
lowerExecPath.includes('code') ||
lowerExecPath.includes('electron');
let qwenCmd: string;
const terminalOptions: vscode.TerminalOptions = { const terminalOptions: vscode.TerminalOptions = {
name: `Qwen Code (${selectedFolder.name})`, name: `Qwen Code (${selectedFolder.name})`,
cwd: selectedFolder.uri.fsPath, cwd: selectedFolder.uri.fsPath,
location, location,
}; };
let qwenCmd: string;
if (isWindows) { if (isWindows) {
// Use system Node via cmd.exe; avoid PowerShell parsing issues // On Windows, try multiple strategies to find a Node.js runtime:
// 1. Check if VSCode ships a standalone node.exe alongside Code.exe
// 2. Check VSCode's internal Node.js in resources directory
// 3. Fall back to using Code.exe with ELECTRON_RUN_AS_NODE=1
const quoteCmd = (s: string) => `"${s.replace(/"/g, '""')}"`; const quoteCmd = (s: string) => `"${s.replace(/"/g, '""')}"`;
const cliQuoted = quoteCmd(cliEntry); const cliQuoted = quoteCmd(cliEntry);
// TODO: @yiliang114, temporarily run through node, and later hope to decouple from the local node // TODO: @yiliang114, temporarily run through node, and later hope to decouple from the local node
qwenCmd = `node ${cliQuoted}`; qwenCmd = `node ${cliQuoted}`;
terminalOptions.shellPath = process.env.ComSpec; terminalOptions.shellPath = process.env.ComSpec;
} else { } else {
// macOS/Linux: All VSCode-like IDEs (VSCode, Cursor, Windsurf, etc.)
// are Electron-based, so we always need ELECTRON_RUN_AS_NODE=1
// to run Node.js scripts using the IDE's bundled runtime.
const quotePosix = (s: string) => `"${s.replace(/"/g, '\\"')}"`; const quotePosix = (s: string) => `"${s.replace(/"/g, '\\"')}"`;
const baseCmd = `${quotePosix(execPath)} ${quotePosix(cliEntry)}`; const baseCmd = `${quotePosix(execPath)} ${quotePosix(cliEntry)}`;
if (needsElectronRunAsNode) { qwenCmd = `ELECTRON_RUN_AS_NODE=1 ${baseCmd}`;
// macOS Electron helper needs ELECTRON_RUN_AS_NODE=1;
qwenCmd = `ELECTRON_RUN_AS_NODE=1 ${baseCmd}`;
} else {
qwenCmd = baseCmd;
}
} }
const terminal = vscode.window.createTerminal(terminalOptions); const terminal = vscode.window.createTerminal(terminalOptions);