diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index 4b7a96fd8..6ed4b02de 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -235,6 +235,7 @@ If you are experiencing performance issues with file searching (e.g., with `@` c | Setting | Type | Description | Default | Notes | | ------------------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `tools.sandbox` | boolean or string | Sandbox execution environment (can be a boolean or a path string). | `undefined` | | +| `tools.sandboxImage` | string | Sandbox image URI used by Docker/Podman when `--sandbox-image` and `QWEN_SANDBOX_IMAGE` are not set. | `undefined` | | | `tools.shell.enableInteractiveShell` | boolean | Use `node-pty` for an interactive shell experience. Fallback to `child_process` still applies. | `false` | | | `tools.core` | array of strings | **Deprecated.** Will be removed in next version. Use `permissions.allow` + `permissions.deny` instead. Restricts built-in tools to an allowlist. All tools not in the list are disabled. | `undefined` | | | `tools.exclude` | array of strings | **Deprecated.** Use `permissions.deny` instead. Tool names to exclude from discovery. Automatically migrated to the `permissions` format on first load. | `undefined` | | @@ -442,6 +443,7 @@ Here is an example of a `settings.json` file with the nested structure, new as o "tools": { "approvalMode": "yolo", "sandbox": "docker", + "sandboxImage": "ghcr.io/qwenlm/qwen-code:0.14.1", "discoveryCommand": "bin/get_tools", "callCommand": "bin/call_tool", "exclude": ["write_file"] @@ -505,28 +507,32 @@ For authentication-related variables (like `OPENAI_*`) and the recommended `.qwe ### Environment Variables Table -| Variable | Description | Notes | -| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `QWEN_TELEMETRY_ENABLED` | Set to `true` or `1` to enable telemetry. Any other value is treated as disabling it. | Overrides the `telemetry.enabled` setting. | -| `QWEN_TELEMETRY_TARGET` | Sets the telemetry target (`local` or `gcp`). | Overrides the `telemetry.target` setting. | -| `QWEN_TELEMETRY_OTLP_ENDPOINT` | Sets the OTLP endpoint for telemetry. | Overrides the `telemetry.otlpEndpoint` setting. | -| `QWEN_TELEMETRY_OTLP_PROTOCOL` | Sets the OTLP protocol (`grpc` or `http`). | Overrides the `telemetry.otlpProtocol` setting. | -| `QWEN_TELEMETRY_LOG_PROMPTS` | Set to `true` or `1` to enable or disable logging of user prompts. Any other value is treated as disabling it. | Overrides the `telemetry.logPrompts` setting. | -| `QWEN_TELEMETRY_OUTFILE` | Sets the file path to write telemetry to when the target is `local`. | Overrides the `telemetry.outfile` setting. | -| `QWEN_TELEMETRY_USE_COLLECTOR` | Set to `true` or `1` to enable or disable using an external OTLP collector. Any other value is treated as disabling it. | Overrides the `telemetry.useCollector` setting. | -| `QWEN_SANDBOX` | Alternative to the `sandbox` setting in `settings.json`. | Accepts `true`, `false`, `docker`, `podman`, or a custom command string. | -| `SEATBELT_PROFILE` | (macOS specific) Switches the Seatbelt (`sandbox-exec`) profile on macOS. | `permissive-open`: (Default) Restricts writes to the project folder (and a few other folders, see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other operations. `strict`: Uses a strict profile that declines operations by default. ``: Uses a custom profile. To define a custom profile, create a file named `sandbox-macos-.sb` in your project's `.qwen/` directory (e.g., `my-project/.qwen/sandbox-macos-custom.sb`). | -| `DEBUG` or `DEBUG_MODE` | (often used by underlying libraries or the CLI itself) Set to `true` or `1` to enable verbose debug logging, which can be helpful for troubleshooting. | **Note:** These variables are automatically excluded from project `.env` files by default to prevent interference with the CLI behavior. Use `.qwen/.env` files if you need to set these for Qwen Code specifically. | -| `NO_COLOR` | Set to any value to disable all color output in the CLI. | | -| `CLI_TITLE` | Set to a string to customize the title of the CLI. | | -| `CODE_ASSIST_ENDPOINT` | Specifies the endpoint for the code assist server. | This is useful for development and testing. | +| Variable | Description | Notes | +| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `QWEN_TELEMETRY_ENABLED` | Set to `true` or `1` to enable telemetry. Any other value is treated as disabling it. | Overrides the `telemetry.enabled` setting. | +| `QWEN_TELEMETRY_TARGET` | Sets the telemetry target (`local` or `gcp`). | Overrides the `telemetry.target` setting. | +| `QWEN_TELEMETRY_OTLP_ENDPOINT` | Sets the OTLP endpoint for telemetry. | Overrides the `telemetry.otlpEndpoint` setting. | +| `QWEN_TELEMETRY_OTLP_PROTOCOL` | Sets the OTLP protocol (`grpc` or `http`). | Overrides the `telemetry.otlpProtocol` setting. | +| `QWEN_TELEMETRY_LOG_PROMPTS` | Set to `true` or `1` to enable or disable logging of user prompts. Any other value is treated as disabling it. | Overrides the `telemetry.logPrompts` setting. | +| `QWEN_TELEMETRY_OUTFILE` | Sets the file path to write telemetry to when the target is `local`. | Overrides the `telemetry.outfile` setting. | +| `QWEN_TELEMETRY_USE_COLLECTOR` | Set to `true` or `1` to enable or disable using an external OTLP collector. Any other value is treated as disabling it. | Overrides the `telemetry.useCollector` setting. | +| `QWEN_SANDBOX` | Alternative to the `sandbox` setting in `settings.json`. | Accepts `true`, `false`, `docker`, `podman`, or a custom command string. | +| `QWEN_SANDBOX_IMAGE` | Overrides sandbox image selection for Docker/Podman. | Takes precedence over `tools.sandboxImage`. | +| `SEATBELT_PROFILE` | (macOS specific) Switches the Seatbelt (`sandbox-exec`) profile on macOS. | `permissive-open`: (Default) Restricts writes to the project folder (and a few other folders, see `packages/cli/src/utils/sandbox-macos-permissive-open.sb`) but allows other operations. `strict`: Uses a strict profile that declines operations by default. ``: Uses a custom profile. To define a custom profile, create a file named `sandbox-macos-.sb` in your project's `.qwen/` directory (e.g., `my-project/.qwen/sandbox-macos-custom.sb`). | +| `DEBUG` or `DEBUG_MODE` | (often used by underlying libraries or the CLI itself) Set to `true` or `1` to enable verbose debug logging, which can be helpful for troubleshooting. | **Note:** These variables are automatically excluded from project `.env` files by default to prevent interference with the CLI behavior. Use `.qwen/.env` files if you need to set these for Qwen Code specifically. | +| `NO_COLOR` | Set to any value to disable all color output in the CLI. | | +| `CLI_TITLE` | Set to a string to customize the title of the CLI. | | +| `CODE_ASSIST_ENDPOINT` | Specifies the endpoint for the code assist server. | This is useful for development and testing. | | `QWEN_CODE_MAX_OUTPUT_TOKENS` | Overrides the default maximum output tokens per response. When not set, Qwen Code uses an adaptive strategy: starts with 8K tokens and automatically retries with 64K if the response is truncated. Set this to a specific value (e.g., `16000`) to use a fixed limit instead. | Takes precedence over the capped default (8K) but is overridden by `samplingParams.max_tokens` in settings. Disables automatic escalation when set. Example: `export QWEN_CODE_MAX_OUTPUT_TOKENS=16000` | -| `TAVILY_API_KEY` | Your API key for the Tavily web search service. | Used to enable the `web_search` tool functionality. Example: `export TAVILY_API_KEY="tvly-your-api-key-here"` | +| `TAVILY_API_KEY` | Your API key for the Tavily web search service. | Used to enable the `web_search` tool functionality. Example: `export TAVILY_API_KEY="tvly-your-api-key-here"` | ## Command-Line Arguments Arguments passed directly when running the CLI can override other configurations for that specific session. +For sandbox image selection, precedence is: +`--sandbox-image` > `QWEN_SANDBOX_IMAGE` > `tools.sandboxImage` > built-in default image. + ### Command-Line Arguments Table | Argument | Alias | Description | Possible Values | Notes | diff --git a/docs/users/features/sandbox.md b/docs/users/features/sandbox.md index ba5e477e0..c9a367f37 100644 --- a/docs/users/features/sandbox.md +++ b/docs/users/features/sandbox.md @@ -103,8 +103,16 @@ qwen -p "run the test suite" - **CLI flag**: `--sandbox-image ` - **Environment variable**: `QWEN_SANDBOX_IMAGE=` +- **Settings file**: `tools.sandboxImage` in your `settings.json` (e.g., `{"tools": {"sandboxImage": "ghcr.io/qwenlm/qwen-code:0.14.1"}}`) -If you don’t set either, Qwen Code uses the default image configured in the CLI package (for example `ghcr.io/qwenlm/qwen-code:`). +Priority order (highest to lowest): + +1. `--sandbox-image` +2. `QWEN_SANDBOX_IMAGE` +3. `tools.sandboxImage` +4. Built-in default image from the CLI package (for example `ghcr.io/qwenlm/qwen-code:`) + +`settings.env.QWEN_SANDBOX_IMAGE` also works as a generic env injection mechanism, but `tools.sandboxImage` is the preferred persistent setting. ### macOS Seatbelt profiles diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index d1b2bd3f8..311ba7a47 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -111,10 +111,21 @@ vi.mock('open', () => ({ vi.mock('read-package-up', () => ({ readPackageUp: vi.fn(() => - Promise.resolve({ packageJson: { version: 'test-version' } }), + Promise.resolve({ + packageJson: { + version: 'test-version', + config: { sandboxImageUri: 'pkg-default-image' }, + }, + }), ), })); +vi.mock('command-exists', () => ({ + default: { + sync: vi.fn(() => true), + }, +})); + vi.mock('@qwen-code/qwen-code-core', async (importOriginal) => { const actualServer = await importOriginal(); const SkillManagerMock = vi.fn(); @@ -2441,6 +2452,83 @@ describe('Telemetry configuration via environment variables', () => { }); }); +describe('sandbox image resolution precedence', () => { + const originalArgv = process.argv; + + beforeEach(() => { + vi.resetAllMocks(); + vi.mocked(os.homedir).mockReturnValue('/mock/home/user'); + vi.stubEnv('GEMINI_API_KEY', 'test-api-key'); + delete process.env['QWEN_SANDBOX_IMAGE']; + }); + + afterEach(() => { + process.argv = originalArgv; + vi.unstubAllEnvs(); + vi.restoreAllMocks(); + delete process.env['QWEN_SANDBOX_IMAGE']; + }); + + it('uses --sandbox-image over env and settings', async () => { + vi.stubEnv('QWEN_SANDBOX_IMAGE', 'env-image'); + process.argv = [ + 'node', + 'script.js', + '--sandbox', + '--sandbox-image', + 'cli-image', + ]; + const argv = await parseArguments(); + const settings: Settings = { + tools: { + sandbox: true, + sandboxImage: 'settings-image', + }, + }; + const config = await loadCliConfig(settings, argv, undefined, []); + expect(config.getSandbox()?.image).toBe('cli-image'); + }); + + it('uses QWEN_SANDBOX_IMAGE over tools.sandboxImage', async () => { + vi.stubEnv('QWEN_SANDBOX_IMAGE', 'env-image'); + process.argv = ['node', 'script.js', '--sandbox']; + const argv = await parseArguments(); + const settings: Settings = { + tools: { + sandbox: true, + sandboxImage: 'settings-image', + }, + }; + const config = await loadCliConfig(settings, argv, undefined, []); + expect(config.getSandbox()?.image).toBe('env-image'); + }); + + it('uses tools.sandboxImage when cli and env are absent', async () => { + process.argv = ['node', 'script.js', '--sandbox']; + const argv = await parseArguments(); + const settings: Settings = { + tools: { + sandbox: true, + sandboxImage: 'settings-image', + }, + }; + const config = await loadCliConfig(settings, argv, undefined, []); + expect(config.getSandbox()?.image).toBe('settings-image'); + }); + + it('falls back to package default image when no explicit source is provided', async () => { + process.argv = ['node', 'script.js', '--sandbox']; + const argv = await parseArguments(); + const settings: Settings = { + tools: { + sandbox: true, + }, + }; + const config = await loadCliConfig(settings, argv, undefined, []); + expect(config.getSandbox()?.image).toBe('pkg-default-image'); + }); +}); + describe('loadCliConfig runtimeOutputDir', () => { const originalArgv = process.argv; const originalRuntimeEnv = process.env['QWEN_RUNTIME_DIR']; diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 2b64685f7..c80ea8be6 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -514,7 +514,7 @@ export async function parseArguments(): Promise { }) .deprecateOption( 'sandbox-image', - 'Use the "tools.sandbox" setting in settings.json instead. This flag will be removed in a future version.', + 'Use the "tools.sandboxImage" setting in settings.json instead. This flag will be removed in a future version.', ) .deprecateOption( 'checkpointing', diff --git a/packages/cli/src/config/sandboxConfig.ts b/packages/cli/src/config/sandboxConfig.ts index f98e528fb..e0159ff53 100644 --- a/packages/cli/src/config/sandboxConfig.ts +++ b/packages/cli/src/config/sandboxConfig.ts @@ -99,6 +99,7 @@ export async function loadSandboxConfig( const image = argv.sandboxImage ?? process.env['QWEN_SANDBOX_IMAGE'] ?? + settings.tools?.sandboxImage ?? packageJson?.config?.sandboxImageUri; return command && image ? { command, image } : undefined; diff --git a/packages/cli/src/config/settingsSchema.test.ts b/packages/cli/src/config/settingsSchema.test.ts index c4ad800e2..252db02c0 100644 --- a/packages/cli/src/config/settingsSchema.test.ts +++ b/packages/cli/src/config/settingsSchema.test.ts @@ -110,6 +110,16 @@ describe('SettingsSchema', () => { ).toBeDefined(); }); + it('should have sandboxImage setting under tools', () => { + expect(getSettingsSchema().tools.properties.sandboxImage).toBeDefined(); + expect(getSettingsSchema().tools.properties.sandboxImage.type).toBe( + 'string', + ); + expect(getSettingsSchema().tools.properties.sandboxImage.default).toBe( + undefined, + ); + }); + it('should have unique categories', () => { const categories = new Set(); diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 824710266..a9a4d951b 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1014,6 +1014,16 @@ const SETTINGS_SCHEMA = { 'Sandbox execution environment (can be a boolean or a path string).', showInDialog: false, }, + sandboxImage: { + type: 'string', + label: 'Sandbox Image', + category: 'Tools', + requiresRestart: true, + default: undefined as string | undefined, + description: + 'Sandbox image URI used by Docker/Podman when --sandbox-image and QWEN_SANDBOX_IMAGE are not set.', + showInDialog: false, + }, shell: { type: 'object', label: 'Shell', diff --git a/packages/vscode-ide-companion/schemas/settings.schema.json b/packages/vscode-ide-companion/schemas/settings.schema.json index 3c4782f27..ecd103b71 100644 --- a/packages/vscode-ide-companion/schemas/settings.schema.json +++ b/packages/vscode-ide-companion/schemas/settings.schema.json @@ -438,6 +438,10 @@ "type": "object", "additionalProperties": true }, + "sandboxImage": { + "description": "Sandbox image URI used by Docker/Podman when --sandbox-image and QWEN_SANDBOX_IMAGE are not set.", + "type": "string" + }, "shell": { "description": "Settings for shell execution.", "type": "object",