Compare commits

..

13 Commits

Author SHA1 Message Date
pomelo-nwu
2852f48a4a docs(auth): add Coding Plan documentation
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-01-15 20:15:27 +08:00
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
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
13 changed files with 174 additions and 109 deletions

View File

@@ -5,11 +5,13 @@ Qwen Code supports two authentication methods. Pick the one that matches how you
- **Qwen OAuth (recommended)**: sign in with your `qwen.ai` account in a browser. - **Qwen OAuth (recommended)**: sign in with your `qwen.ai` account in a browser.
- **OpenAI-compatible API**: use an API key (OpenAI or any OpenAI-compatible provider / endpoint). - **OpenAI-compatible API**: use an API key (OpenAI or any OpenAI-compatible provider / endpoint).
![](https://img.alicdn.com/imgextra/i2/O1CN01IxI1bt1sNO543AVTT_!!6000000005754-0-tps-1958-822.jpg)
## Option 1: Qwen OAuth (recommended & free) 👍 ## Option 1: Qwen OAuth (recommended & free) 👍
Use this if you want the simplest setup and youre using Qwen models. Use this if you want the simplest setup and you're using Qwen models.
- **How it works**: on first start, Qwen Code opens a browser login page. After you finish, credentials are cached locally so you usually wont need to log in again. - **How it works**: on first start, Qwen Code opens a browser login page. After you finish, credentials are cached locally so you usually won't need to log in again.
- **Requirements**: a `qwen.ai` account + internet access (at least for the first login). - **Requirements**: a `qwen.ai` account + internet access (at least for the first login).
- **Benefits**: no API key management, automatic credential refresh. - **Benefits**: no API key management, automatic credential refresh.
- **Cost & quota**: free, with a quota of **60 requests/minute** and **2,000 requests/day**. - **Cost & quota**: free, with a quota of **60 requests/minute** and **2,000 requests/day**.
@@ -24,15 +26,54 @@ qwen
Use this if you want to use OpenAI models or any provider that exposes an OpenAI-compatible API (e.g. OpenAI, Azure OpenAI, OpenRouter, ModelScope, Alibaba Cloud Bailian, or a self-hosted compatible endpoint). Use this if you want to use OpenAI models or any provider that exposes an OpenAI-compatible API (e.g. OpenAI, Azure OpenAI, OpenRouter, ModelScope, Alibaba Cloud Bailian, or a self-hosted compatible endpoint).
### Quick start (interactive, recommended for local use) ### Recommended: Coding Plan (subscription-based) 🚀
When you choose the OpenAI-compatible option in the CLI, it will prompt you for: Use this if you want predictable costs with higher usage quotas for the qwen3-coder-plus model.
- **API key** > [!IMPORTANT]
- **Base URL** (default: `https://api.openai.com/v1`) >
- **Model** (default: `gpt-4o`) > Coding Plan is only available for users in China mainland (Beijing region).
> **Note:** the CLI may display the key in plain text for verification. Make sure your terminal is not being recorded or shared. - **How it works**: subscribe to the Coding Plan with a fixed monthly fee, then configure Qwen Code to use the dedicated endpoint and your subscription API key.
- **Requirements**: an active Coding Plan subscription from [Alibaba Cloud Bailian](https://bailian.console.aliyun.com/cn-beijing/?tab=globalset#/efm/coding_plan).
- **Benefits**: higher usage quotas, predictable monthly costs, access to latest qwen3-coder-plus model.
- **Cost & quota**: varies by plan (see table below).
#### Coding Plan Pricing & Quotas
| Feature | Lite Basic Plan | Pro Advanced Plan |
| :------------------ | :-------------------- | :-------------------- |
| **Price** | ¥40/month | ¥200/month |
| **5-Hour Limit** | Up to 1,200 requests | Up to 6,000 requests |
| **Weekly Limit** | Up to 9,000 requests | Up to 45,000 requests |
| **Monthly Limit** | Up to 18,000 requests | Up to 90,000 requests |
| **Supported Model** | qwen3-coder-plus | qwen3-coder-plus |
#### Quick Setup for Coding Plan
When you select the OpenAI-compatible option in the CLI, enter these values:
- **API key**: `sk-sp-xxxxx`
- **Base URL**: `https://coding.dashscope.aliyuncs.com/v1`
- **Model**: `qwen3-coder-plus`
> **Note**: Coding Plan API keys have the format `sk-sp-xxxxx`, which is different from standard Alibaba Cloud API keys.
#### Configure via Environment Variables
Set these environment variables to use Coding Plan:
```bash
export OPENAI_API_KEY="your-coding-plan-api-key" # Format: sk-sp-xxxxx
export OPENAI_BASE_URL="https://coding.dashscope.aliyuncs.com/v1"
export OPENAI_MODEL="qwen3-coder-plus"
```
For more details about Coding Plan, including subscription options and troubleshooting, see the [full Coding Plan documentation](https://bailian.console.aliyun.com/cn-beijing/?tab=doc#/doc/?type=model&url=3005961).
### Other OpenAI-compatible Providers
If you are using other providers (OpenAI, Azure, local LLMs, etc.), use the following configuration methods.
### Configure via command-line arguments ### Configure via command-line arguments

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

@@ -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

@@ -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",