From a4ffc6eb247fa8dca97a41a86d50bba2ddb1d60f Mon Sep 17 00:00:00 2001 From: tanzhenxin Date: Fri, 6 Feb 2026 13:04:08 +0800 Subject: [PATCH] feat: promote Agent Skills from experimental to stable Co-authored-by: Qwen-Coder --- docs/developers/sdk-java.md | 1 - docs/users/configuration/settings.md | 9 +- docs/users/features/_meta.ts | 2 +- docs/users/features/commands.md | 2 +- docs/users/features/headless.md | 27 +- docs/users/features/skills.md | 62 +--- packages/cli/src/config/config.test.ts | 308 +++++++++--------- packages/cli/src/config/config.ts | 16 +- packages/cli/src/config/settings.ts | 22 -- packages/cli/src/config/settingsSchema.ts | 10 - packages/cli/src/gemini.test.tsx | 1 - packages/cli/src/gemini.tsx | 2 +- packages/cli/src/i18n/locales/de.js | 1 - packages/cli/src/i18n/locales/en.js | 1 - packages/cli/src/i18n/locales/pt.js | 1 - packages/cli/src/i18n/locales/ru.js | 1 - packages/cli/src/i18n/locales/zh.js | 1 - .../cli/src/services/BuiltinCommandLoader.ts | 2 +- packages/core/src/config/config.ts | 19 +- packages/sdk-java/client/README.md | 2 +- .../alibaba/acp/sdk/session/SessionTest.java | 6 +- packages/sdk-java/qwencode/QWEN.md | 1 - packages/sdk-java/qwencode/README.md | 1 - .../code/cli/transport/TransportOptions.java | 24 -- .../process/TransportOptionsAdapter.java | 4 - .../src/transport/ProcessTransport.ts | 1 - .../src/services/qwenConnectionHandler.ts | 2 +- packages/zed-extension/extension.toml | 8 +- 28 files changed, 196 insertions(+), 341 deletions(-) diff --git a/docs/developers/sdk-java.md b/docs/developers/sdk-java.md index 0b16e60a5..f8ec528d4 100644 --- a/docs/developers/sdk-java.md +++ b/docs/developers/sdk-java.md @@ -245,7 +245,6 @@ The `TransportOptions` class allows configuration of how the SDK communicates wi - `allowedTools`: List of tools that are pre-approved for use without additional confirmation - `authType`: Authentication type to use for the session - `includePartialMessages`: Enables receiving partial messages during streaming responses -- `skillsEnable`: Enables or disables skills functionality for the session - `turnTimeout`: Timeout for a complete turn of conversation - `messageTimeout`: Timeout for individual messages within a turn - `resumeSessionId`: ID of a previous session to resume diff --git a/docs/users/configuration/settings.md b/docs/users/configuration/settings.md index 6acd0e819..0094f411d 100644 --- a/docs/users/configuration/settings.md +++ b/docs/users/configuration/settings.md @@ -43,7 +43,7 @@ Qwen Code uses JSON settings files for persistent configuration. There are four In addition to a project settings file, a project's `.qwen` directory can contain other project-specific files related to Qwen Code's operation, such as: - [Custom sandbox profiles](../features/sandbox) (e.g. `.qwen/sandbox-macos-custom.sb`, `.qwen/sandbox.Dockerfile`). -- [Agent Skills](../features/skills) (experimental) under `.qwen/skills/` (each Skill is a directory containing a `SKILL.md`). +- [Agent Skills](../features/skills) under `.qwen/skills/` (each Skill is a directory containing a `SKILL.md`). ### Configuration migration @@ -359,12 +359,6 @@ LSP server configuration is done through `.lsp.json` files in your project root > > **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`. @@ -534,7 +528,6 @@ Arguments passed directly when running the CLI can override other configurations | `--telemetry-log-prompts` | | Enables logging of prompts for telemetry. | | See [telemetry](../../developers/development/telemetry) for more information. | | `--checkpointing` | | Enables [checkpointing](../features/checkpointing). | | | | `--acp` | | Enables ACP mode (Agent Client Protocol). Useful for IDE/editor integrations like [Zed](../integration-zed). | | Stable. Replaces the deprecated `--experimental-acp` flag. | -| `--experimental-skills` | | Enables experimental [Agent Skills](../features/skills) (registers the `skill` tool and loads Skills from `.qwen/skills/` and `~/.qwen/skills/`). | | Experimental. | | `--experimental-lsp` | | Enables experimental [LSP (Language Server Protocol)](../features/lsp) feature for code intelligence (go-to-definition, find references, diagnostics, etc.). | | Experimental. Requires language servers to be installed. | | `--extensions` | `-e` | Specifies a list of extensions to use for the session. | Extension names | If not provided, all available extensions are used. Use the special term `qwen -e none` to disable all extensions. Example: `qwen -e my-extension -e my-other-extension` | | `--list-extensions` | `-l` | Lists all available extensions and exits. | | | diff --git a/docs/users/features/_meta.ts b/docs/users/features/_meta.ts index 0155b3ba4..f5218e85f 100644 --- a/docs/users/features/_meta.ts +++ b/docs/users/features/_meta.ts @@ -1,7 +1,7 @@ export default { commands: 'Commands', 'sub-agents': 'SubAgents', - skills: 'Skills (Experimental)', + skills: 'Skills', headless: 'Headless Mode', checkpointing: { display: 'hidden', diff --git a/docs/users/features/commands.md b/docs/users/features/commands.md index 1ad7ea1d7..9a3b2c051 100644 --- a/docs/users/features/commands.md +++ b/docs/users/features/commands.md @@ -59,7 +59,7 @@ Commands for managing AI tools and models. | ---------------- | --------------------------------------------- | --------------------------------------------- | | `/mcp` | List configured MCP servers and tools | `/mcp`, `/mcp desc` | | `/tools` | Display currently available tool list | `/tools`, `/tools desc` | -| `/skills` | List and run available skills (experimental) | `/skills`, `/skills ` | +| `/skills` | List and run available skills | `/skills`, `/skills ` | | `/approval-mode` | Change approval mode for tool usage | `/approval-mode --project` | | →`plan` | Analysis only, no execution | Secure review | | →`default` | Require approval for edits | Daily use | diff --git a/docs/users/features/headless.md b/docs/users/features/headless.md index 139b3bbba..203e08a2d 100644 --- a/docs/users/features/headless.md +++ b/docs/users/features/headless.md @@ -189,20 +189,19 @@ qwen -p "Write code" --output-format stream-json --include-partial-messages | jq Key command-line options for headless usage: -| Option | Description | Example | -| ---------------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------ | -| `--prompt`, `-p` | Run in headless mode | `qwen -p "query"` | -| `--output-format`, `-o` | Specify output format (text, json, stream-json) | `qwen -p "query" --output-format json` | -| `--input-format` | Specify input format (text, stream-json) | `qwen --input-format text --output-format stream-json` | -| `--include-partial-messages` | Include partial messages in stream-json output | `qwen -p "query" --output-format stream-json --include-partial-messages` | -| `--debug`, `-d` | Enable debug mode | `qwen -p "query" --debug` | -| `--all-files`, `-a` | Include all files in context | `qwen -p "query" --all-files` | -| `--include-directories` | Include additional directories | `qwen -p "query" --include-directories src,docs` | -| `--yolo`, `-y` | Auto-approve all actions | `qwen -p "query" --yolo` | -| `--approval-mode` | Set approval mode | `qwen -p "query" --approval-mode auto_edit` | -| `--continue` | Resume the most recent session for this project | `qwen --continue -p "Pick up where we left off"` | -| `--resume [sessionId]` | Resume a specific session (or choose interactively) | `qwen --resume 123e... -p "Finish the refactor"` | -| `--experimental-skills` | Enable experimental Skills (registers the `skill` tool) | `qwen --experimental-skills -p "What Skills are available?"` | +| Option | Description | Example | +| ---------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------ | +| `--prompt`, `-p` | Run in headless mode | `qwen -p "query"` | +| `--output-format`, `-o` | Specify output format (text, json, stream-json) | `qwen -p "query" --output-format json` | +| `--input-format` | Specify input format (text, stream-json) | `qwen --input-format text --output-format stream-json` | +| `--include-partial-messages` | Include partial messages in stream-json output | `qwen -p "query" --output-format stream-json --include-partial-messages` | +| `--debug`, `-d` | Enable debug mode | `qwen -p "query" --debug` | +| `--all-files`, `-a` | Include all files in context | `qwen -p "query" --all-files` | +| `--include-directories` | Include additional directories | `qwen -p "query" --include-directories src,docs` | +| `--yolo`, `-y` | Auto-approve all actions | `qwen -p "query" --yolo` | +| `--approval-mode` | Set approval mode | `qwen -p "query" --approval-mode auto_edit` | +| `--continue` | Resume the most recent session for this project | `qwen --continue -p "Pick up where we left off"` | +| `--resume [sessionId]` | Resume a specific session (or choose interactively) | `qwen --resume 123e... -p "Finish the refactor"` | For complete details on all available configuration options, settings files, and environment variables, see the [Configuration Guide](../configuration/settings). diff --git a/docs/users/features/skills.md b/docs/users/features/skills.md index d5b1be9e6..952b00846 100644 --- a/docs/users/features/skills.md +++ b/docs/users/features/skills.md @@ -1,39 +1,12 @@ -# Agent Skills (Experimental) +# Agent Skills -> Create, manage, and share Skills to extend Qwen Code’s capabilities. +> Create, manage, and share Skills to extend Qwen Code's capabilities. -This guide shows you how to create, use, and manage Agent Skills in **Qwen Code**. Skills are modular capabilities that extend the model’s effectiveness through organized folders containing instructions (and optionally scripts/resources). - -> [!note] -> -> Skills are currently **experimental** and must be enabled with `--experimental-skills`. +This guide shows you how to create, use, and manage Agent Skills in **Qwen Code**. Skills are modular capabilities that extend the model's effectiveness through organized folders containing instructions (and optionally scripts/resources). ## Prerequisites - Qwen Code (recent version) - -## 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? @@ -42,7 +15,7 @@ Agent Skills package expertise into discoverable capabilities. Each Skill consis ### How Skills are invoked -Skills are **model-invoked** — the model autonomously decides when to use them based on your request and the Skill’s description. This is different from slash commands, which are **user-invoked** (you explicitly type `/command`). +Skills are **model-invoked** — the model autonomously decides when to use them based on your request and the Skill's description. This is different from slash commands, which are **user-invoked** (you explicitly type `/command`). If you want to invoke a Skill explicitly, use the `/skills` slash command: @@ -50,7 +23,7 @@ If you want to invoke a Skill explicitly, use the `/skills` slash command: /skills ``` -The `/skills` command is only available when you run with `--experimental-skills`. Use autocomplete to browse available Skills and descriptions. +Use autocomplete to browse available Skills and descriptions. ### Benefits @@ -74,7 +47,7 @@ mkdir -p ~/.qwen/skills/my-skill-name Use personal Skills for: - Your individual workflows and preferences -- Experimental Skills you’re developing +- Skills you're developing - Personal productivity helpers ### Project Skills @@ -153,7 +126,7 @@ python scripts/helper.py input.txt ## View available Skills -When `--experimental-skills` is enabled, Qwen Code discovers Skills from: +Qwen Code discovers Skills from: - Personal Skills: `~/.qwen/skills/` - Project Skills: `.qwen/skills/` @@ -163,10 +136,7 @@ When `--experimental-skills` is enabled, Qwen Code discovers Skills from: Extensions can provide custom skills that become available when the extension is enabled. These skills are stored in the extension's `skills/` directory and follow the same format as personal and project skills. -Extension skills are automatically discovered and loaded when: - -- The extension is installed and enabled -- The `--experimental-skills` flag is enabled +Extension skills are automatically discovered and loaded when the extension is installed and enabled. To see which extensions provide skills, check the extension's `qwen-extension.json` file for a `skills` field. @@ -185,7 +155,7 @@ ls ~/.qwen/skills/ # List project Skills (if in a project directory) ls .qwen/skills/ -# View a specific Skill’s content +# View a specific Skill's content cat ~/.qwen/skills/my-skill/SKILL.md ``` @@ -193,17 +163,17 @@ cat ~/.qwen/skills/my-skill/SKILL.md After creating a Skill, test it by asking questions that match your description. -Example: if your description mentions “PDF files”: +Example: if your description mentions "PDF files": ```text Can you help me extract text from this PDF? ``` -The model autonomously decides to use your Skill if it matches the request — you don’t need to explicitly invoke it. +The model autonomously decides to use your Skill if it matches the request — you don't need to explicitly invoke it. ## Debug a Skill -If Qwen Code doesn’t use your Skill, check these common issues: +If Qwen Code doesn't use your Skill, check these common issues: ### Make the description specific @@ -251,7 +221,7 @@ Ensure: Run Qwen Code with debug mode to see Skill loading errors: ```bash -qwen --experimental-skills --debug +qwen --debug ``` ## Share Skills with your team @@ -260,7 +230,7 @@ You can share Skills through project repositories: 1. Add the Skill under `.qwen/skills/` 2. Commit and push -3. Teammates pull the changes and run with `--experimental-skills` +3. Teammates pull the changes ```bash git add .qwen/skills/ @@ -301,8 +271,8 @@ git commit -m "Remove unused Skill" One Skill should address one capability: -- Focused: “PDF form filling”, “Excel analysis”, “Git commit messages” -- Too broad: “Document processing” (split into smaller Skills) +- Focused: "PDF form filling", "Excel analysis", "Git commit messages" +- Too broad: "Document processing" (split into smaller Skills) ### Write clear descriptions diff --git a/packages/cli/src/config/config.test.ts b/packages/cli/src/config/config.test.ts index 7159cd284..d1d5a1af7 100644 --- a/packages/cli/src/config/config.test.ts +++ b/packages/cli/src/config/config.test.ts @@ -169,9 +169,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -197,9 +195,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -212,7 +208,7 @@ describe('parseArguments', () => { it('should allow --prompt without --prompt-interactive', async () => { process.argv = ['node', 'script.js', '--prompt', 'test prompt']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.prompt).toBe('test prompt'); expect(argv.promptInteractive).toBeUndefined(); }); @@ -224,33 +220,33 @@ describe('parseArguments', () => { '--prompt-interactive', 'interactive prompt', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.promptInteractive).toBe('interactive prompt'); expect(argv.prompt).toBeUndefined(); }); it('should allow -i flag as alias for --prompt-interactive', async () => { process.argv = ['node', 'script.js', '-i', 'interactive prompt']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.promptInteractive).toBe('interactive prompt'); expect(argv.prompt).toBeUndefined(); }); it('should allow -r flag as alias for --resume', async () => { process.argv = ['node', 'script.js', '-r', 'session-123']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.resume).toBe('session-123'); }); it('should allow -c flag as alias for --continue', async () => { process.argv = ['node', 'script.js', '-c']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.continue).toBe(true); }); it('should convert positional query argument to prompt by default', async () => { process.argv = ['node', 'script.js', 'Hi Gemini']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe('Hi Gemini'); expect(argv.prompt).toBe('Hi Gemini'); expect(argv.promptInteractive).toBeUndefined(); @@ -258,7 +254,7 @@ describe('parseArguments', () => { it('should map @path to prompt (one-shot) when it starts with @', async () => { process.argv = ['node', 'script.js', '@path ./file.md']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe('@path ./file.md'); expect(argv.prompt).toBe('@path ./file.md'); expect(argv.promptInteractive).toBeUndefined(); @@ -274,7 +270,7 @@ describe('parseArguments', () => { '--model', 'gemini-1.5-pro', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe('@path ./file.md'); expect(argv.prompt).toBe('@path ./file.md'); // Should map to one-shot expect(argv.promptInteractive).toBeUndefined(); @@ -284,7 +280,7 @@ describe('parseArguments', () => { it('maps unquoted positional @path + arg to prompt (one-shot)', async () => { // Simulate: gemini @path ./file.md process.argv = ['node', 'script.js', '@path', './file.md']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); // After normalization, query is a single string expect(argv.query).toBe('@path ./file.md'); // And it's mapped to one-shot prompt when no -p/-i flags are set @@ -302,7 +298,7 @@ describe('parseArguments', () => { '@path', './file2.md', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); // After normalization, all arguments are joined with spaces expect(argv.query).toBe('@path ./file1.md @path ./file2.md'); // And it's mapped to one-shot prompt @@ -320,7 +316,7 @@ describe('parseArguments', () => { './file2.md', 'additional text', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); // After normalization, all arguments are joined with spaces expect(argv.query).toBe( '@path ./file1.md @path ./file2.md additional text', @@ -342,7 +338,7 @@ describe('parseArguments', () => { '--debug', '--telemetry', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe('@path ./file.md'); expect(argv.prompt).toBe('@path ./file.md'); // Should map to one-shot expect(argv.promptInteractive).toBeUndefined(); @@ -362,7 +358,7 @@ describe('parseArguments', () => { for (const testQuery of testCases) { process.argv = ['node', 'script.js', testQuery]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe(testQuery); expect(argv.prompt).toBe(testQuery); expect(argv.promptInteractive).toBeUndefined(); @@ -372,7 +368,7 @@ describe('parseArguments', () => { it('should handle @command with leading whitespace', async () => { // Test that trim() + routing handles leading whitespace correctly process.argv = ['node', 'script.js', ' @path ./file.md']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe(' @path ./file.md'); expect(argv.prompt).toBe(' @path ./file.md'); expect(argv.promptInteractive).toBeUndefined(); @@ -392,9 +388,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -413,9 +407,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -434,9 +426,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -458,7 +448,7 @@ describe('parseArguments', () => { '--include-partial-messages', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.outputFormat).toBe('stream-json'); expect(argv.inputFormat).toBe('stream-json'); @@ -467,14 +457,14 @@ describe('parseArguments', () => { it('should allow --approval-mode without --yolo', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'auto-edit']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.approvalMode).toBe('auto-edit'); expect(argv.yolo).toBe(false); }); it('should allow --yolo without --approval-mode', async () => { process.argv = ['node', 'script.js', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.yolo).toBe(true); expect(argv.approvalMode).toBeUndefined(); }); @@ -487,9 +477,7 @@ describe('parseArguments', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining('Invalid values:'), @@ -505,7 +493,7 @@ describe('parseArguments', () => { '--allowed-tools', 'read_file,ShellTool(git status)', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.allowedTools).toEqual(['read_file', 'ShellTool(git status)']); }); @@ -516,13 +504,13 @@ describe('parseArguments', () => { '--allowed-mcp-server-names', 'server1,server2', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.allowedMcpServerNames).toEqual(['server1', 'server2']); }); it('should support comma-separated values for --extensions', async () => { process.argv = ['node', 'script.js', '--extensions', 'ext1,ext2']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.extensions).toEqual(['ext1', 'ext2']); }); }); @@ -556,7 +544,7 @@ describe('loadCliConfig', () => { 'stream-json', '--include-partial-messages', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); @@ -567,7 +555,7 @@ describe('loadCliConfig', () => { it('should initialize native LSP service when enabled', async () => { process.argv = ['node', 'script.js', '--experimental-lsp']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); @@ -609,7 +597,7 @@ describe('loadCliConfig', () => { it(`should leave proxy to empty by default`, async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getProxy()).toBeFalsy(); @@ -650,7 +638,7 @@ describe('loadCliConfig', () => { it(`should set proxy to ${expected} according to environment variable [${input.env_name}]`, async () => { vi.stubEnv(input.env_name, input.proxy_url); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getProxy()).toBe(expected); @@ -659,7 +647,7 @@ describe('loadCliConfig', () => { it('should set proxy when --proxy flag is present', async () => { process.argv = ['node', 'script.js', '--proxy', 'http://localhost:7890']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getProxy()).toBe('http://localhost:7890'); @@ -668,7 +656,7 @@ describe('loadCliConfig', () => { it('should prioritize CLI flag over environment variable for proxy (CLI http://localhost:7890, environment variable http://localhost:7891)', async () => { vi.stubEnv('http_proxy', 'http://localhost:7891'); process.argv = ['node', 'script.js', '--proxy', 'http://localhost:7890']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getProxy()).toBe('http://localhost:7890'); @@ -693,7 +681,7 @@ describe('loadCliConfig telemetry', () => { it('should set telemetry to false by default when no flag or setting is present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(false); @@ -701,7 +689,7 @@ describe('loadCliConfig telemetry', () => { it('should set telemetry to true when --telemetry flag is present', async () => { process.argv = ['node', 'script.js', '--telemetry']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(true); @@ -709,7 +697,7 @@ describe('loadCliConfig telemetry', () => { it('should set telemetry to false when --no-telemetry flag is present', async () => { process.argv = ['node', 'script.js', '--no-telemetry']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(false); @@ -717,7 +705,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry value from settings if CLI flag is not present (settings true)', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(true); @@ -725,7 +713,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry value from settings if CLI flag is not present (settings false)', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: false } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(false); @@ -733,7 +721,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --telemetry CLI flag (true) over settings (false)', async () => { process.argv = ['node', 'script.js', '--telemetry']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: false } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(true); @@ -741,7 +729,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --no-telemetry CLI flag (false) over settings (true)', async () => { process.argv = ['node', 'script.js', '--no-telemetry']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryEnabled()).toBe(false); @@ -749,7 +737,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry OTLP endpoint from settings if CLI flag is not present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpEndpoint: 'http://settings.example.com' }, }; @@ -766,7 +754,7 @@ describe('loadCliConfig telemetry', () => { '--telemetry-otlp-endpoint', 'http://cli.example.com', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpEndpoint: 'http://settings.example.com' }, }; @@ -776,7 +764,7 @@ describe('loadCliConfig telemetry', () => { it('should use default endpoint if no OTLP endpoint is provided via CLI or settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryOtlpEndpoint()).toBe('http://localhost:4317'); @@ -784,7 +772,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry target from settings if CLI flag is not present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { target: ServerConfig.DEFAULT_TELEMETRY_TARGET }, }; @@ -796,7 +784,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --telemetry-target CLI flag over settings', async () => { process.argv = ['node', 'script.js', '--telemetry-target', 'gcp']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { target: ServerConfig.DEFAULT_TELEMETRY_TARGET }, }; @@ -806,7 +794,7 @@ describe('loadCliConfig telemetry', () => { it('should use default target if no target is provided via CLI or settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryTarget()).toBe( @@ -816,7 +804,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry log prompts from settings if CLI flag is not present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { logPrompts: false } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryLogPromptsEnabled()).toBe(false); @@ -824,7 +812,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --telemetry-log-prompts CLI flag (true) over settings (false)', async () => { process.argv = ['node', 'script.js', '--telemetry-log-prompts']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { logPrompts: false } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryLogPromptsEnabled()).toBe(true); @@ -832,7 +820,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --no-telemetry-log-prompts CLI flag (false) over settings (true)', async () => { process.argv = ['node', 'script.js', '--no-telemetry-log-prompts']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { logPrompts: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryLogPromptsEnabled()).toBe(false); @@ -840,7 +828,7 @@ describe('loadCliConfig telemetry', () => { it('should use default log prompts (true) if no value is provided via CLI or settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryLogPromptsEnabled()).toBe(true); @@ -848,7 +836,7 @@ describe('loadCliConfig telemetry', () => { it('should use telemetry OTLP protocol from settings if CLI flag is not present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpProtocol: 'http' }, }; @@ -858,7 +846,7 @@ describe('loadCliConfig telemetry', () => { it('should prioritize --telemetry-otlp-protocol CLI flag over settings', async () => { process.argv = ['node', 'script.js', '--telemetry-otlp-protocol', 'http']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpProtocol: 'grpc' }, }; @@ -868,7 +856,7 @@ describe('loadCliConfig telemetry', () => { it('should use default protocol if no OTLP protocol is provided via CLI or settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv); expect(config.getTelemetryOtlpProtocol()).toBe('grpc'); @@ -887,9 +875,7 @@ describe('loadCliConfig telemetry', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining('Invalid values:'), @@ -915,7 +901,7 @@ describe('mergeExcludeTools', () => { process.stdin.isTTY = true; const settings: Settings = {}; process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getExcludeTools()).toEqual([]); }); @@ -924,14 +910,14 @@ describe('mergeExcludeTools', () => { process.stdin.isTTY = false; const settings: Settings = {}; process.argv = ['node', 'script.js', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getExcludeTools()).toEqual(defaultExcludes); }); it('should handle settings with excludeTools but no extensions', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { exclude: ['tool1', 'tool2'] } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getExcludeTools()).toEqual( @@ -958,7 +944,7 @@ describe('Approval mode tool exclusion logic', () => { it('should exclude all interactive tools in non-interactive mode with default approval mode', async () => { process.argv = ['node', 'script.js', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -977,7 +963,7 @@ describe('Approval mode tool exclusion logic', () => { '-p', 'test', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -996,7 +982,7 @@ describe('Approval mode tool exclusion logic', () => { '-p', 'test', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1009,7 +995,7 @@ describe('Approval mode tool exclusion logic', () => { it('should not exclude a tool explicitly allowed in tools.allowed', async () => { process.argv = ['node', 'script.js', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { allowed: [ShellTool.Name], @@ -1026,7 +1012,7 @@ describe('Approval mode tool exclusion logic', () => { it('should not exclude a tool explicitly allowed in tools.core', async () => { process.argv = ['node', 'script.js', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { core: [ShellTool.Name], @@ -1050,7 +1036,7 @@ describe('Approval mode tool exclusion logic', () => { '-p', 'test', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1070,7 +1056,7 @@ describe('Approval mode tool exclusion logic', () => { '-p', 'test', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1083,7 +1069,7 @@ describe('Approval mode tool exclusion logic', () => { it('should exclude no interactive tools in non-interactive mode with legacy yolo flag', async () => { process.argv = ['node', 'script.js', '--yolo', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1108,7 +1094,7 @@ describe('Approval mode tool exclusion logic', () => { for (const testCase of testCases) { process.argv = testCase.args; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1129,7 +1115,7 @@ describe('Approval mode tool exclusion logic', () => { '-p', 'test', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { exclude: ['custom_tool'] } }; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1183,7 +1169,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should allow all MCP servers if the flag is not provided', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(baseSettings, argv, undefined, []); expect(config.getMcpServers()).toEqual(baseSettings.mcpServers); }); @@ -1195,7 +1181,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { '--allowed-mcp-server-names', 'server1', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(baseSettings, argv, undefined, []); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, @@ -1211,7 +1197,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { '--allowed-mcp-server-names', 'server3', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(baseSettings, argv, undefined, []); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, @@ -1228,7 +1214,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { '--allowed-mcp-server-names', 'server4', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(baseSettings, argv, undefined, []); expect(config.getMcpServers()).toEqual({ server1: { url: 'http://localhost:8080' }, @@ -1237,14 +1223,14 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should allow no MCP servers if the flag is provided but empty', async () => { process.argv = ['node', 'script.js', '--allowed-mcp-server-names', '']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(baseSettings, argv, undefined, []); expect(config.getMcpServers()).toEqual({}); }); it('should read allowMCPServers from settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ...baseSettings, mcp: { allowed: ['server1', 'server2'] }, @@ -1258,7 +1244,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should read excludeMCPServers from settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ...baseSettings, mcp: { excluded: ['server1', 'server2'] }, @@ -1271,7 +1257,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { it('should override allowMCPServers with excludeMCPServers if overlapping', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ...baseSettings, mcp: { @@ -1292,7 +1278,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { '--allowed-mcp-server-names', 'server1', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ...baseSettings, mcp: { @@ -1315,7 +1301,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { '--allowed-mcp-server-names', 'server3', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ...baseSettings, mcp: { @@ -1334,7 +1320,7 @@ describe('loadCliConfig with allowed-mcp-server-names', () => { describe('loadCliConfig model selection', () => { it.skip('selects a model from settings.json if provided', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { model: { @@ -1351,7 +1337,7 @@ describe('loadCliConfig model selection', () => { it.skip('uses the default gemini model if nothing is set', async () => { process.argv = ['node', 'script.js']; // No model set. - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { // No model set. @@ -1373,7 +1359,7 @@ describe('loadCliConfig model selection', () => { '--model', 'qwen3-coder-plus', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { model: { @@ -1397,7 +1383,7 @@ describe('loadCliConfig model selection', () => { '--model', 'qwen3-coder-plus', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { // No model provided via settings. @@ -1435,14 +1421,14 @@ describe('loadCliConfig folderTrust', () => { }, }, }; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getFolderTrust()).toBe(false); }); it('should be true when folderTrust is true', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { security: { folderTrust: { @@ -1456,7 +1442,7 @@ describe('loadCliConfig folderTrust', () => { it('should be false by default', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getFolderTrust()).toBe(false); @@ -1489,7 +1475,7 @@ describe('loadCliConfig with includeDirectories', () => { '--include-directories', `${path.resolve(path.sep, 'cli', 'path1')},${path.join(mockCwd, 'cli', 'path2')}`, ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { context: { includeDirectories: [ @@ -1534,7 +1520,7 @@ describe('loadCliConfig chatCompression', () => { it('should pass chatCompression settings to the core config', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { model: { chatCompression: { @@ -1550,7 +1536,7 @@ describe('loadCliConfig chatCompression', () => { it('should have undefined chatCompression if not in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getChatCompression()).toBeUndefined(); @@ -1574,7 +1560,7 @@ describe('loadCliConfig useRipgrep', () => { it('should be true by default when useRipgrep is not set in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseRipgrep()).toBe(true); @@ -1582,7 +1568,7 @@ describe('loadCliConfig useRipgrep', () => { it('should be false when useRipgrep is set to false in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { useRipgrep: false } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseRipgrep()).toBe(false); @@ -1590,7 +1576,7 @@ describe('loadCliConfig useRipgrep', () => { it('should be true when useRipgrep is explicitly set to true in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { useRipgrep: true } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseRipgrep()).toBe(true); @@ -1614,7 +1600,7 @@ describe('loadCliConfig useBuiltinRipgrep', () => { it('should be true by default when useBuiltinRipgrep is not set in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseBuiltinRipgrep()).toBe(true); @@ -1622,7 +1608,7 @@ describe('loadCliConfig useBuiltinRipgrep', () => { it('should be false when useBuiltinRipgrep is set to false in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { useBuiltinRipgrep: false } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseBuiltinRipgrep()).toBe(false); @@ -1630,7 +1616,7 @@ describe('loadCliConfig useBuiltinRipgrep', () => { it('should be true when useBuiltinRipgrep is explicitly set to true in settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { useBuiltinRipgrep: true } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getUseBuiltinRipgrep()).toBe(true); @@ -1654,7 +1640,7 @@ describe('screenReader configuration', () => { it('should use screenReader value from settings if CLI flag is not present (settings true)', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ui: { accessibility: { screenReader: true } }, }; @@ -1664,7 +1650,7 @@ describe('screenReader configuration', () => { it('should use screenReader value from settings if CLI flag is not present (settings false)', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ui: { accessibility: { screenReader: false } }, }; @@ -1674,7 +1660,7 @@ describe('screenReader configuration', () => { it('should prioritize --screen-reader CLI flag (true) over settings (false)', async () => { process.argv = ['node', 'script.js', '--screen-reader']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { ui: { accessibility: { screenReader: false } }, }; @@ -1684,7 +1670,7 @@ describe('screenReader configuration', () => { it('should be false by default when no flag or setting is present', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = {}; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getScreenReader()).toBe(false); @@ -1716,7 +1702,7 @@ describe('loadCliConfig tool exclusions', () => { it('should not exclude interactive tools in interactive mode without YOLO', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); @@ -1726,7 +1712,7 @@ describe('loadCliConfig tool exclusions', () => { it('should not exclude interactive tools in interactive mode with YOLO', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); @@ -1736,7 +1722,7 @@ describe('loadCliConfig tool exclusions', () => { it('should exclude interactive tools in non-interactive mode without YOLO', async () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '-p', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getExcludeTools()).toContain('run_shell_command'); expect(config.getExcludeTools()).toContain('edit'); @@ -1746,7 +1732,7 @@ describe('loadCliConfig tool exclusions', () => { it('should not exclude interactive tools in non-interactive mode with YOLO', async () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '-p', 'test', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getExcludeTools()).not.toContain('run_shell_command'); expect(config.getExcludeTools()).not.toContain('replace'); @@ -1775,7 +1761,7 @@ describe('loadCliConfig interactive', () => { it('should be interactive if isTTY and no prompt', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(true); }); @@ -1783,7 +1769,7 @@ describe('loadCliConfig interactive', () => { it('should be interactive if prompt-interactive is set', async () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js', '--prompt-interactive', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(true); }); @@ -1791,7 +1777,7 @@ describe('loadCliConfig interactive', () => { it('should not be interactive if not isTTY and no prompt', async () => { process.stdin.isTTY = false; process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(false); }); @@ -1799,7 +1785,7 @@ describe('loadCliConfig interactive', () => { it('should not be interactive if prompt is set', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--prompt', 'test']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(false); }); @@ -1807,7 +1793,7 @@ describe('loadCliConfig interactive', () => { it('should not be interactive if positional prompt words are provided with other flags', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro', 'Hello']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(false); }); @@ -1822,7 +1808,7 @@ describe('loadCliConfig interactive', () => { '--yolo', 'Hello world', ]; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(false); // Verify the question is preserved for one-shot execution @@ -1833,7 +1819,7 @@ describe('loadCliConfig interactive', () => { it('should be interactive if no positional prompt words are provided with flags', async () => { process.stdin.isTTY = true; process.argv = ['node', 'script.js', '--model', 'gemini-1.5-pro']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.isInteractive()).toBe(true); }); @@ -1861,56 +1847,56 @@ describe('loadCliConfig approval mode', () => { it('should default to DEFAULT approval mode when no flags are set', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should set PLAN approval mode when --approval-mode=plan', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'plan']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); }); it('should set YOLO approval mode when --yolo flag is used', async () => { process.argv = ['node', 'script.js', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); it('should set YOLO approval mode when -y flag is used', async () => { process.argv = ['node', 'script.js', '-y']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); it('should set DEFAULT approval mode when --approval-mode=default', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'default']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should set AUTO_EDIT approval mode when --approval-mode=auto-edit', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'auto-edit']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.AUTO_EDIT); }); it('should set YOLO approval mode when --approval-mode=yolo', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); it('should use approval mode from settings when CLI flags are not provided', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); // Using string value to test normalization const settings = { tools: { approvalMode: 'plan' } } as unknown as Settings; const config = await loadCliConfig(settings, argv, undefined, []); @@ -1919,7 +1905,7 @@ describe('loadCliConfig approval mode', () => { it('should normalize approval mode values from settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { tools: { approvalMode: ServerConfig.ApprovalMode.AUTO_EDIT }, }; @@ -1929,7 +1915,7 @@ describe('loadCliConfig approval mode', () => { it('should throw when approval mode in settings is invalid', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings = { tools: { approvalMode: 'invalid_mode' }, } as unknown as Settings; @@ -1942,7 +1928,7 @@ describe('loadCliConfig approval mode', () => { // Note: This test documents the intended behavior, but in practice the validation // prevents both flags from being used together process.argv = ['node', 'script.js', '--approval-mode', 'default']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); // Manually set yolo to true to simulate what would happen if validation didn't prevent it argv.yolo = true; const config = await loadCliConfig({}, argv, undefined, []); @@ -1951,7 +1937,7 @@ describe('loadCliConfig approval mode', () => { it('should fall back to --yolo behavior when --approval-mode is not set', async () => { process.argv = ['node', 'script.js', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.YOLO); }); @@ -1967,35 +1953,35 @@ describe('loadCliConfig approval mode', () => { it('should override --approval-mode=yolo to DEFAULT', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should override --approval-mode=auto-edit to DEFAULT', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'auto-edit']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should override --yolo flag to DEFAULT', async () => { process.argv = ['node', 'script.js', '--yolo']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should remain DEFAULT when --approval-mode=default', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'default']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.DEFAULT); }); it('should allow PLAN approval mode in untrusted folders', async () => { process.argv = ['node', 'script.js', '--approval-mode', 'plan']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getApprovalMode()).toBe(ServerConfig.ApprovalMode.PLAN); }); @@ -2085,14 +2071,14 @@ describe('loadCliConfig fileFiltering', () => { describe('Output format', () => { it('should default to TEXT', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getOutputFormat()).toBe(OutputFormat.TEXT); }); it('should use the format from settings', async () => { process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { output: { format: OutputFormat.JSON } }, argv, @@ -2104,7 +2090,7 @@ describe('Output format', () => { it('should prioritize the format from argv', async () => { process.argv = ['node', 'script.js', '--output-format', 'json']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { output: { format: OutputFormat.JSON } }, argv, @@ -2120,9 +2106,7 @@ describe('Output format', () => { throw new Error('process.exit called'); }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining('Invalid values:'), ); @@ -2152,9 +2136,7 @@ describe('parseArguments with positional prompt', () => { }); mockWriteStderrLine.mockClear(); - await expect(parseArguments({} as Settings)).rejects.toThrow( - 'process.exit called', - ); + await expect(parseArguments()).rejects.toThrow('process.exit called'); expect(mockWriteStderrLine).toHaveBeenCalledWith( expect.stringContaining( @@ -2167,7 +2149,7 @@ describe('parseArguments with positional prompt', () => { it('should correctly parse a positional prompt to query field', async () => { process.argv = ['node', 'script.js', 'positional', 'prompt']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.query).toBe('positional prompt'); // Since no explicit prompt flags are set and query doesn't start with @, should map to prompt (one-shot) expect(argv.prompt).toBe('positional prompt'); @@ -2176,7 +2158,7 @@ describe('parseArguments with positional prompt', () => { it('should correctly parse a prompt from the --prompt flag', async () => { process.argv = ['node', 'script.js', '--prompt', 'test prompt']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); expect(argv.prompt).toBe('test prompt'); }); }); @@ -2185,7 +2167,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_ENABLED over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_ENABLED', 'true'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: false } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getTelemetryEnabled()).toBe(true); @@ -2194,7 +2176,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_TARGET over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_TARGET', 'gcp'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { target: 'local' }, } as unknown as Settings; @@ -2205,7 +2187,7 @@ describe('Telemetry configuration via environment variables', () => { it('should throw when GEMINI_TELEMETRY_TARGET is invalid', async () => { vi.stubEnv('GEMINI_TELEMETRY_TARGET', 'bogus'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { target: 'gcp' }, } as unknown as Settings; @@ -2219,7 +2201,7 @@ describe('Telemetry configuration via environment variables', () => { vi.stubEnv('OTEL_EXPORTER_OTLP_ENDPOINT', 'http://default.env.com'); vi.stubEnv('GEMINI_TELEMETRY_OTLP_ENDPOINT', 'http://gemini.env.com'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpEndpoint: 'http://settings.com' }, }; @@ -2230,7 +2212,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_OTLP_PROTOCOL over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_OTLP_PROTOCOL', 'http'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { otlpProtocol: 'grpc' } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getTelemetryOtlpProtocol()).toBe('http'); @@ -2239,7 +2221,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_LOG_PROMPTS over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_LOG_PROMPTS', 'false'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { logPrompts: true } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getTelemetryLogPromptsEnabled()).toBe(false); @@ -2248,7 +2230,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_OUTFILE over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_OUTFILE', '/gemini/env/telemetry.log'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { outfile: '/settings/telemetry.log' }, }; @@ -2259,7 +2241,7 @@ describe('Telemetry configuration via environment variables', () => { it('should prioritize GEMINI_TELEMETRY_USE_COLLECTOR over settings', async () => { vi.stubEnv('GEMINI_TELEMETRY_USE_COLLECTOR', 'true'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { useCollector: false } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getTelemetryUseCollector()).toBe(true); @@ -2268,7 +2250,7 @@ describe('Telemetry configuration via environment variables', () => { it('should use settings value when GEMINI_TELEMETRY_ENABLED is not set', async () => { vi.stubEnv('GEMINI_TELEMETRY_ENABLED', undefined); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { enabled: true } }; const config = await loadCliConfig(settings, argv, undefined, []); expect(config.getTelemetryEnabled()).toBe(true); @@ -2277,7 +2259,7 @@ describe('Telemetry configuration via environment variables', () => { it('should use settings value when GEMINI_TELEMETRY_TARGET is not set', async () => { vi.stubEnv('GEMINI_TELEMETRY_TARGET', undefined); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const settings: Settings = { telemetry: { target: 'local' }, } as unknown as Settings; @@ -2288,7 +2270,7 @@ describe('Telemetry configuration via environment variables', () => { it("should treat GEMINI_TELEMETRY_ENABLED='1' as true", async () => { vi.stubEnv('GEMINI_TELEMETRY_ENABLED', '1'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getTelemetryEnabled()).toBe(true); }); @@ -2296,7 +2278,7 @@ describe('Telemetry configuration via environment variables', () => { it("should treat GEMINI_TELEMETRY_ENABLED='0' as false", async () => { vi.stubEnv('GEMINI_TELEMETRY_ENABLED', '0'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { telemetry: { enabled: true } }, argv, @@ -2309,7 +2291,7 @@ describe('Telemetry configuration via environment variables', () => { it("should treat GEMINI_TELEMETRY_LOG_PROMPTS='1' as true", async () => { vi.stubEnv('GEMINI_TELEMETRY_LOG_PROMPTS', '1'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig({}, argv, undefined, []); expect(config.getTelemetryLogPromptsEnabled()).toBe(true); }); @@ -2317,7 +2299,7 @@ describe('Telemetry configuration via environment variables', () => { it("should treat GEMINI_TELEMETRY_LOG_PROMPTS='false' as false", async () => { vi.stubEnv('GEMINI_TELEMETRY_LOG_PROMPTS', 'false'); process.argv = ['node', 'script.js']; - const argv = await parseArguments({} as Settings); + const argv = await parseArguments(); const config = await loadCliConfig( { telemetry: { logPrompts: true } }, argv, diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index 88117ed6e..4a3a18895 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -110,7 +110,6 @@ export interface CliArgs { allowedTools: string[] | undefined; acp: boolean | undefined; experimentalAcp: boolean | undefined; - experimentalSkills: boolean | undefined; experimentalLsp: boolean | undefined; extensions: string[] | undefined; listExtensions: boolean | undefined; @@ -160,7 +159,7 @@ function normalizeOutputFormat( return OutputFormat.TEXT; } -export async function parseArguments(settings: Settings): Promise { +export async function parseArguments(): Promise { let rawArgv = hideBin(process.argv); // hack: if the first argument is the CLI entry point, remove it @@ -313,15 +312,9 @@ export async function parseArguments(settings: Settings): Promise { }) .option('experimental-skills', { type: 'boolean', - description: 'Enable experimental Skills feature', - default: (() => { - const legacySkills = ( - settings as Settings & { - tools?: { experimental?: { skills?: boolean } }; - } - ).tools?.experimental?.skills; - return settings.experimental?.skills ?? legacySkills ?? false; - })(), + description: + 'Deprecated: Skills are now enabled by default. This flag is ignored.', + hidden: true, }) .option('experimental-lsp', { type: 'boolean', @@ -959,7 +952,6 @@ export async function loadCliConfig( maxSessionTurns: argv.maxSessionTurns ?? settings.model?.maxSessionTurns ?? -1, experimentalZedIntegration: argv.acp || argv.experimentalAcp || false, - experimentalSkills: argv.experimentalSkills || false, listExtensions: argv.listExtensions || false, overrideExtensions: overrideExtensions || argv.extensions, noBrowser: !!process.env['NO_BROWSER'], diff --git a/packages/cli/src/config/settings.ts b/packages/cli/src/config/settings.ts index f69178570..614086a44 100644 --- a/packages/cli/src/config/settings.ts +++ b/packages/cli/src/config/settings.ts @@ -1133,28 +1133,6 @@ export function loadSettings( ); } -export function migrateDeprecatedSettings( - loadedSettings: LoadedSettings, -): void { - const processScope = (scope: SettingScope) => { - const settings = loadedSettings.forScope(scope).settings; - const legacySkills = ( - settings as Settings & { - tools?: { experimental?: { skills?: boolean } }; - } - ).tools?.experimental?.skills; - if ( - legacySkills !== undefined && - settings.experimental?.skills === undefined - ) { - loadedSettings.setValue(scope, 'experimental.skills', legacySkills); - } - }; - - processScope(SettingScope.User); - processScope(SettingScope.Workspace); -} - export function saveSettings(settingsFile: SettingsFile): void { try { // Ensure the directory exists diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index 52bfe0ecd..6a7170a45 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -1150,16 +1150,6 @@ const SETTINGS_SCHEMA = { description: 'Setting to enable experimental features', showInDialog: false, properties: { - skills: { - type: 'boolean', - label: 'Experimental: 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, - }, visionModelPreview: { type: 'boolean', label: 'Vision Model Preview', diff --git a/packages/cli/src/gemini.test.tsx b/packages/cli/src/gemini.test.tsx index f3a788e72..039f0bef3 100644 --- a/packages/cli/src/gemini.test.tsx +++ b/packages/cli/src/gemini.test.tsx @@ -470,7 +470,6 @@ describe('gemini.tsx main function kitty protocol', () => { allowedTools: undefined, acp: undefined, experimentalAcp: undefined, - experimentalSkills: undefined, extensions: undefined, listExtensions: undefined, openaiLogging: undefined, diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 459082872..c2f75f06d 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -211,7 +211,7 @@ export async function main() { const settings = loadSettings(); await cleanupCheckpoints(); - let argv = await parseArguments(settings.merged); + let argv = await parseArguments(); // Check for invalid input combinations early to prevent crashes if (argv.promptInteractive && !process.stdin.isTTY) { diff --git a/packages/cli/src/i18n/locales/de.js b/packages/cli/src/i18n/locales/de.js index 772e40a89..cf5d523a7 100644 --- a/packages/cli/src/i18n/locales/de.js +++ b/packages/cli/src/i18n/locales/de.js @@ -335,7 +335,6 @@ export default { 'Folder Trust': 'Ordnervertrauen', 'Vision Model Preview': 'Vision-Modell-Vorschau', 'Tool Schema Compliance': 'Werkzeug-Schema-Konformität', - 'Experimental: Skills': 'Experimentell: Fähigkeiten', // Settings enum options 'Auto (detect from system)': 'Automatisch (vom System erkennen)', Text: 'Text', diff --git a/packages/cli/src/i18n/locales/en.js b/packages/cli/src/i18n/locales/en.js index be76025b7..e5236c1e3 100644 --- a/packages/cli/src/i18n/locales/en.js +++ b/packages/cli/src/i18n/locales/en.js @@ -351,7 +351,6 @@ export default { 'Folder Trust': 'Folder Trust', 'Vision Model Preview': 'Vision Model Preview', 'Tool Schema Compliance': 'Tool Schema Compliance', - 'Experimental: Skills': 'Experimental: Skills', // Settings enum options 'Auto (detect from system)': 'Auto (detect from system)', Text: 'Text', diff --git a/packages/cli/src/i18n/locales/pt.js b/packages/cli/src/i18n/locales/pt.js index 62e81def1..83a491126 100644 --- a/packages/cli/src/i18n/locales/pt.js +++ b/packages/cli/src/i18n/locales/pt.js @@ -365,7 +365,6 @@ export default { 'Folder Trust': 'Confiança de Pasta', 'Vision Model Preview': 'Visualização de Modelo de Visão', 'Tool Schema Compliance': 'Conformidade de Esquema de Ferramenta', - 'Experimental: Skills': 'Experimental: Habilidades', // Settings enum options 'Auto (detect from system)': 'Automático (detectar do sistema)', diff --git a/packages/cli/src/i18n/locales/ru.js b/packages/cli/src/i18n/locales/ru.js index 46778ecac..8b484aac1 100644 --- a/packages/cli/src/i18n/locales/ru.js +++ b/packages/cli/src/i18n/locales/ru.js @@ -355,7 +355,6 @@ export default { 'Folder Trust': 'Доверие к папке', 'Vision Model Preview': 'Визуальная модель (предпросмотр)', 'Tool Schema Compliance': 'Соответствие схеме инструмента', - 'Experimental: Skills': 'Экспериментальное: Навыки', // Варианты перечислений настроек 'Auto (detect from system)': 'Авто (определить из системы)', Text: 'Текст', diff --git a/packages/cli/src/i18n/locales/zh.js b/packages/cli/src/i18n/locales/zh.js index d488f9028..713c6ffee 100644 --- a/packages/cli/src/i18n/locales/zh.js +++ b/packages/cli/src/i18n/locales/zh.js @@ -340,7 +340,6 @@ export default { 'Folder Trust': '文件夹信任', 'Vision Model Preview': '视觉模型预览', 'Tool Schema Compliance': '工具 Schema 兼容性', - 'Experimental: Skills': '实验性: 技能', // Settings enum options 'Auto (detect from system)': '自动(从系统检测)', Text: '文本', diff --git a/packages/cli/src/services/BuiltinCommandLoader.ts b/packages/cli/src/services/BuiltinCommandLoader.ts index 8e2237766..5d2cd05ec 100644 --- a/packages/cli/src/services/BuiltinCommandLoader.ts +++ b/packages/cli/src/services/BuiltinCommandLoader.ts @@ -81,7 +81,7 @@ export class BuiltinCommandLoader implements ICommandLoader { quitCommand, restoreCommand(this.config), resumeCommand, - ...(this.config?.getExperimentalSkills?.() ? [skillsCommand] : []), + skillsCommand, statsCommand, summaryCommand, themeCommand, diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index eff0e7034..4be942147 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -324,7 +324,6 @@ export interface ConfigParameters { outputLanguageFilePath?: string; maxSessionTurns?: number; sessionTokenLimit?: number; - experimentalSkills?: boolean; experimentalZedIntegration?: boolean; listExtensions?: boolean; overrideExtensions?: string[]; @@ -488,7 +487,6 @@ export class Config { | undefined; private readonly cliVersion?: string; private readonly experimentalZedIntegration: boolean = false; - private readonly experimentalSkills: boolean = false; private readonly chatRecordingEnabled: boolean; private readonly loadMemoryFromIncludeDirectories: boolean = false; private readonly importFormat: 'tree' | 'flat'; @@ -592,7 +590,6 @@ export class Config { this.sessionTokenLimit = params.sessionTokenLimit ?? -1; this.experimentalZedIntegration = params.experimentalZedIntegration ?? false; - this.experimentalSkills = params.experimentalSkills ?? false; this.listExtensions = params.listExtensions ?? false; this.overrideExtensions = params.overrideExtensions; this.noBrowser = params.noBrowser ?? false; @@ -699,11 +696,9 @@ export class Config { this.debugLogger.debug('Extension manager initialized'); this.subagentManager = new SubagentManager(this); - if (this.getExperimentalSkills()) { - this.skillManager = new SkillManager(this); - await this.skillManager.startWatching(); - this.debugLogger.debug('Skill manager initialized'); - } + this.skillManager = new SkillManager(this); + await this.skillManager.startWatching(); + this.debugLogger.debug('Skill manager initialized'); // Load session subagents if they were provided before initialization if (this.sessionSubagents.length > 0) { @@ -1358,10 +1353,6 @@ export class Config { return this.experimentalZedIntegration; } - getExperimentalSkills(): boolean { - return this.experimentalSkills; - } - getListExtensions(): boolean { return this.listExtensions; } @@ -1673,9 +1664,7 @@ export class Config { }; registerCoreTool(TaskTool, this); - if (this.getExperimentalSkills()) { - registerCoreTool(SkillTool, this); - } + registerCoreTool(SkillTool, this); registerCoreTool(LSTool, this); registerCoreTool(ReadFileTool, this); diff --git a/packages/sdk-java/client/README.md b/packages/sdk-java/client/README.md index 3a1b6ef45..7bfc390bf 100644 --- a/packages/sdk-java/client/README.md +++ b/packages/sdk-java/client/README.md @@ -58,7 +58,7 @@ The following is a simple example showing how to use the ACP SDK to create a cli public void testSession() throws AgentInitializeException, SessionNewException, IOException { // Create an ACP client with a process transport AcpClient acpClient = new AcpClient( - new ProcessTransport(new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "--experimental-skills", "-y"}))); + new ProcessTransport(new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "-y"}))); try { // Send a prompt to the agent diff --git a/packages/sdk-java/client/src/test/java/com/alibaba/acp/sdk/session/SessionTest.java b/packages/sdk-java/client/src/test/java/com/alibaba/acp/sdk/session/SessionTest.java index 1ce1ad926..81972c018 100644 --- a/packages/sdk-java/client/src/test/java/com/alibaba/acp/sdk/session/SessionTest.java +++ b/packages/sdk-java/client/src/test/java/com/alibaba/acp/sdk/session/SessionTest.java @@ -45,7 +45,7 @@ class SessionTest { private static final Logger logger = LoggerFactory.getLogger(SessionTest.class); @Test public void testSession() throws AgentInitializeException, SessionNewException, IOException { - AcpClient acpClient = new AcpClient(new ProcessTransport(new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "--experimental-skills", "-y"}))); + AcpClient acpClient = new AcpClient(new ProcessTransport(new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "-y"}))); try { acpClient.sendPrompt(Collections.singletonList(new TextContent("你是谁")), new AgentEventConsumer().setContentEventConsumer(new ContentEventSimpleConsumer(){ @Override @@ -86,7 +86,7 @@ class SessionTest { @Test void test() throws SessionNewException, AgentInitializeException, IOException { Transport transport = new ProcessTransport( - new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "--experimental-skills", "-y"})); + new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "-y"})); AcpClient acpClient = new AcpClient(transport, new InitializeRequestParams().setClientCapabilities( new ClientCapabilities() .setTerminal(true) @@ -98,7 +98,7 @@ class SessionTest { @Test void testPermission() throws AgentInitializeException, SessionNewException, IOException { Transport transport = new ProcessTransport( - new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp", "--experimental-skills"})); + new ProcessTransportOptions().setCommandArgs(new String[] {"qwen", "--acp"})); AcpClient acpClient = new AcpClient(transport, new InitializeRequestParams().setClientCapabilities( new ClientCapabilities() .setTerminal(false) diff --git a/packages/sdk-java/qwencode/QWEN.md b/packages/sdk-java/qwencode/QWEN.md index 4fedee46f..80bda7ab8 100644 --- a/packages/sdk-java/qwencode/QWEN.md +++ b/packages/sdk-java/qwencode/QWEN.md @@ -153,7 +153,6 @@ The `TransportOptions` class allows configuration of how the SDK communicates wi - `allowedTools`: List of tools that are pre-approved for use without additional confirmation - `authType`: Authentication type to use for the session - `includePartialMessages`: Enables receiving partial messages during streaming responses -- `skillsEnable`: Enables or disables skills functionality for the session - `turnTimeout`: Timeout for a complete turn of conversation - `messageTimeout`: Timeout for individual messages within a turn - `resumeSessionId`: ID of a previous session to resume diff --git a/packages/sdk-java/qwencode/README.md b/packages/sdk-java/qwencode/README.md index 02516c9c3..755e03d8a 100644 --- a/packages/sdk-java/qwencode/README.md +++ b/packages/sdk-java/qwencode/README.md @@ -245,7 +245,6 @@ The `TransportOptions` class allows configuration of how the SDK communicates wi - `allowedTools`: List of tools that are pre-approved for use without additional confirmation - `authType`: Authentication type to use for the session - `includePartialMessages`: Enables receiving partial messages during streaming responses -- `skillsEnable`: Enables or disables skills functionality for the session - `turnTimeout`: Timeout for a complete turn of conversation - `messageTimeout`: Timeout for individual messages within a turn - `resumeSessionId`: ID of a previous session to resume diff --git a/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java b/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java index 5f72e1c0b..1acf3bb61 100644 --- a/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java +++ b/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/TransportOptions.java @@ -57,10 +57,6 @@ public class TransportOptions implements Cloneable { * Whether to include partial messages in responses. */ private Boolean includePartialMessages; - /** - * Whether to enable skills. - */ - private Boolean skillsEnable; /** * Timeout for individual turns. */ @@ -298,26 +294,6 @@ public class TransportOptions implements Cloneable { return this; } - /** - * Gets whether skills are enabled. - * - * @return Whether skills are enabled - */ - public Boolean getSkillsEnable() { - return skillsEnable; - } - - /** - * Sets whether skills are enabled. - * - * @param skillsEnable Whether skills are enabled - * @return This instance for method chaining - */ - public TransportOptions setSkillsEnable(Boolean skillsEnable) { - this.skillsEnable = skillsEnable; - return this; - } - /** * Gets the turn timeout. * diff --git a/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java b/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java index fe8f21691..2488e9d71 100644 --- a/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java +++ b/packages/sdk-java/qwencode/src/main/java/com/alibaba/qwen/code/cli/transport/process/TransportOptionsAdapter.java @@ -107,10 +107,6 @@ class TransportOptionsAdapter { args.add("--include-partial-messages"); } - if (transportOptions.getSkillsEnable() != null && transportOptions.getSkillsEnable()) { - args.add("--experimental-skills"); - } - if (StringUtils.isNotBlank(transportOptions.getResumeSessionId())) { args.add("--resume"); args.add(transportOptions.getResumeSessionId()); diff --git a/packages/sdk-typescript/src/transport/ProcessTransport.ts b/packages/sdk-typescript/src/transport/ProcessTransport.ts index 374c9e1a9..2b621d434 100644 --- a/packages/sdk-typescript/src/transport/ProcessTransport.ts +++ b/packages/sdk-typescript/src/transport/ProcessTransport.ts @@ -226,7 +226,6 @@ export class ProcessTransport implements Transport { '--output-format', 'stream-json', '--channel=SDK', - '--experimental-skills', ]; if (this.options.model) { diff --git a/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts index af9140905..9b4a188c8 100644 --- a/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts +++ b/packages/vscode-ide-companion/src/services/qwenConnectionHandler.ts @@ -55,7 +55,7 @@ export class QwenConnectionHandler { let availableModels: ModelInfo[] | undefined; // Build extra CLI arguments (only essential parameters) - const extraArgs: string[] = ['--experimental-skills']; + const extraArgs: string[] = []; await connection.connect(cliEntryPath!, workingDir, extraArgs); diff --git a/packages/zed-extension/extension.toml b/packages/zed-extension/extension.toml index 367e1e166..a81174ac4 100644 --- a/packages/zed-extension/extension.toml +++ b/packages/zed-extension/extension.toml @@ -13,20 +13,20 @@ icon = "qwen-code.svg" [agent_servers.qwen-code.targets.darwin-aarch64] archive = "https://registry.npmjs.org/@qwen-code/qwen-code/-/qwen-code-0.8.0.tgz" cmd = "node" -args = ["./package/cli.js", "--acp", "--experimental-skills"] +args = ["./package/cli.js", "--acp"] [agent_servers.qwen-code.targets.darwin-x86_64] archive = "https://registry.npmjs.org/@qwen-code/qwen-code/-/qwen-code-0.8.0.tgz" cmd = "node" -args = ["./package/cli.js", "--acp", "--experimental-skills"] +args = ["./package/cli.js", "--acp"] [agent_servers.qwen-code.targets.linux-x86_64] archive = "https://registry.npmjs.org/@qwen-code/qwen-code/-/qwen-code-0.8.0.tgz" cmd = "node" -args = ["./package/cli.js", "--acp", "--experimental-skills"] +args = ["./package/cli.js", "--acp"] [agent_servers.qwen-code.targets.windows-x86_64] archive = "https://registry.npmjs.org/@qwen-code/qwen-code/-/qwen-code-0.8.0.tgz" cmd = "node" -args = ["./package/cli.js", "--acp", "--experimental-skills"] +args = ["./package/cli.js", "--acp"]