Compare commits

..

16 Commits

Author SHA1 Message Date
tanzhenxin
dc067697dc fix(cli): relocate skills setting to experimental namespace 2026-01-19 13:44:39 +08:00
tanzhenxin
baf848a4d9 Merge pull request #1528 from liqiongyu/fix/1512-skills-crlf
fix(core): parse skills frontmatter with CRLF/BOM
2026-01-19 11:03:17 +08:00
tanzhenxin
d0104dc487 Merge pull request #1509 from QwenLM/docs/code-plan
docs(auth): add Coding Plan documentation
2026-01-19 09:54:22 +08:00
liqoingyu
531062aeaf fix(core): parse skills frontmatter with CRLF/BOM 2026-01-18 17:11:41 +08:00
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
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
LaZzyMan
6f33d92b2c fix: can not remove the mcp server when there is only one element 2026-01-14 16:27:45 +08:00
10 changed files with 151 additions and 9 deletions

View File

@@ -311,6 +311,12 @@ If you are experiencing performance issues with file searching (e.g., with `@` c
>
> **Note about advanced.tavilyApiKey:** This is a legacy configuration format. For Qwen OAuth users, DashScope provider is automatically available without any configuration. For other authentication types, configure Tavily or Google providers using the new `webSearch` configuration format.
#### experimental
| Setting | Type | Description | Default |
| --------------------- | ------- | -------------------------------- | ------- |
| `experimental.skills` | boolean | Enable experimental Agent Skills | `false` |
#### mcpServers
Configures connections to one or more Model-Context Protocol (MCP) servers for discovering and using custom tools. Qwen Code attempts to connect to each configured MCP server to discover available tools. If multiple MCP servers expose a tool with the same name, the tool names will be prefixed with the server alias you defined in the configuration (e.g., `serverAlias__actualToolName`) to avoid conflicts. Note that the system might strip certain schema properties from MCP tool definitions for compatibility. At least one of `command`, `url`, or `httpUrl` must be provided. If multiple are specified, the order of precedence is `httpUrl`, then `url`, then `command`.

View File

@@ -11,12 +11,29 @@ This guide shows you how to create, use, and manage Agent Skills in **Qwen Code*
## Prerequisites
- Qwen Code (recent version)
- Run with the experimental flag enabled:
## How to enable
### Via CLI flag
```bash
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))
## What are Agent Skills?

View File

@@ -334,7 +334,14 @@ export async function parseArguments(settings: Settings): Promise<CliArgs> {
.option('experimental-skills', {
type: 'boolean',
description: 'Enable experimental Skills feature',
default: false,
default: (() => {
const legacySkills = (
settings as Settings & {
tools?: { experimental?: { skills?: boolean } };
}
).tools?.experimental?.skills;
return settings.experimental?.skills ?? legacySkills ?? false;
})(),
})
.option('channel', {
type: 'string',

View File

@@ -922,6 +922,21 @@ export function migrateDeprecatedSettings(
loadedSettings.setValue(scope, 'extensions', newExtensionsValue);
}
const legacySkills = (
settings as Settings & {
tools?: { experimental?: { skills?: boolean } };
}
).tools?.experimental?.skills;
if (
legacySkills !== undefined &&
settings.experimental?.skills === undefined
) {
console.log(
`Migrating deprecated tools.experimental.skills setting from ${scope} settings...`,
);
loadedSettings.setValue(scope, 'experimental.skills', legacySkills);
}
};
processScope(SettingScope.User);

View File

@@ -1207,6 +1207,16 @@ const SETTINGS_SCHEMA = {
description: 'Setting to enable experimental features',
showInDialog: false,
properties: {
skills: {
type: 'boolean',
label: 'Skills',
category: 'Experimental',
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,
},
extensionManagement: {
type: 'boolean',
label: 'Extension Management',

View File

@@ -873,11 +873,11 @@ export default {
'Session Stats': '会话统计',
'Model Usage': '模型使用情况',
Reqs: '请求数',
'Input Tokens': '输入令牌',
'Output Tokens': '输出令牌',
'Input Tokens': '输入 token 数',
'Output Tokens': '输出 token 数',
'Savings Highlight:': '节省亮点:',
'of input tokens were served from the cache, reducing costs.':
'的输入令牌来自缓存,降低了成本',
'从缓存载入 token ,降低了成本',
'Tip: For a full token breakdown, run `/stats model`.':
'提示:要查看完整的令牌明细,请运行 `/stats model`',
'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 path from 'node:path';
import * as os from 'node:os';
import { updateSettingsFilePreservingFormat } from './commentJson.js';
import {
updateSettingsFilePreservingFormat,
applyUpdates,
} from './commentJson.js';
describe('commentJson', () => {
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');
}
function applyUpdates(
export function applyUpdates(
current: Record<string, unknown>,
updates: Record<string, unknown>,
): Record<string, unknown> {
@@ -50,6 +50,7 @@ function applyUpdates(
typeof value === 'object' &&
value !== null &&
!Array.isArray(value) &&
Object.keys(value).length > 0 &&
typeof result[key] === 'object' &&
result[key] !== null &&
!Array.isArray(result[key])

View File

@@ -112,6 +112,62 @@ You are a helpful assistant with this skill.
expect(config.filePath).toBe(validSkillConfig.filePath);
});
it('should parse markdown with CRLF line endings', () => {
const markdownCrlf = `---\r
name: test-skill\r
description: A test skill\r
---\r
\r
You are a helpful assistant with this skill.\r
`;
const config = manager.parseSkillContent(
markdownCrlf,
validSkillConfig.filePath,
'project',
);
expect(config.name).toBe('test-skill');
expect(config.description).toBe('A test skill');
expect(config.body).toBe('You are a helpful assistant with this skill.');
});
it('should parse markdown with UTF-8 BOM', () => {
const markdownWithBom = `\uFEFF---
name: test-skill
description: A test skill
---
You are a helpful assistant with this skill.
`;
const config = manager.parseSkillContent(
markdownWithBom,
validSkillConfig.filePath,
'project',
);
expect(config.name).toBe('test-skill');
expect(config.description).toBe('A test skill');
});
it('should parse markdown when body is empty and file ends after frontmatter', () => {
const frontmatterOnly = `---
name: test-skill
description: A test skill
---`;
const config = manager.parseSkillContent(
frontmatterOnly,
validSkillConfig.filePath,
'project',
);
expect(config.name).toBe('test-skill');
expect(config.description).toBe('A test skill');
expect(config.body).toBe('');
});
it('should parse content with allowedTools', () => {
const markdownWithTools = `---
name: test-skill

View File

@@ -307,9 +307,11 @@ export class SkillManager {
level: SkillLevel,
): SkillConfig {
try {
const normalizedContent = normalizeSkillFileContent(content);
// Split frontmatter and content
const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
const match = content.match(frontmatterRegex);
const frontmatterRegex = /^---\n([\s\S]*?)\n---(?:\n|$)([\s\S]*)$/;
const match = normalizedContent.match(frontmatterRegex);
if (!match) {
throw new Error('Invalid format: missing YAML frontmatter');
@@ -556,3 +558,13 @@ export class SkillManager {
}
}
}
function normalizeSkillFileContent(content: string): string {
// Strip UTF-8 BOM to ensure frontmatter starts at the first character.
let normalized = content.replace(/^\uFEFF/, '');
// Normalize line endings so skills authored on Windows (CRLF) parse correctly.
normalized = normalized.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
return normalized;
}