mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-01-16 22:09:13 +00:00
Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0681c71894 | ||
|
|
155c4b9728 | ||
|
|
57ca2823b3 | ||
|
|
620341eeae | ||
|
|
c6c33233c5 | ||
|
|
106b69e5c0 | ||
|
|
6afe0f8c29 | ||
|
|
0b3be1a82c | ||
|
|
8af43e3ac3 | ||
|
|
886f914fb3 | ||
|
|
90365af2f8 | ||
|
|
cbef5ffd89 | ||
|
|
63406b4ba4 | ||
|
|
52db3a766d | ||
|
|
5e80e80387 | ||
|
|
985f65f8fa | ||
|
|
9b9c5fadd5 | ||
|
|
372c67cad4 | ||
|
|
af3864b5de | ||
|
|
1e3791f30a | ||
|
|
9bf626d051 | ||
|
|
6f33d92b2c | ||
|
|
a35af6550f | ||
|
|
d6607e134e | ||
|
|
9024a41723 | ||
|
|
bde056b62e | ||
|
|
ff5ea3c6d7 | ||
|
|
97497457a8 |
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
12
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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': '模型统计(技术细节)',
|
||||||
|
|||||||
@@ -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: {} });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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.
|
[](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion)
|
||||||
|
[](https://marketplace.visualstudio.com/items?itemName=qwenlm.qwen-code-vscode-ide-companion)
|
||||||
|
[](https://open-vsx.org/extension/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)
|
||||||
|
|||||||
@@ -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": {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user