diff --git a/docs/users/features/scheduled-tasks.md b/docs/users/features/scheduled-tasks.md index ed884e7e1b..15020e6da7 100644 --- a/docs/users/features/scheduled-tasks.md +++ b/docs/users/features/scheduled-tasks.md @@ -6,7 +6,7 @@ Scheduled tasks let Qwen Code re-run a prompt automatically on an interval. Use Tasks are session-scoped: they live in the current Qwen Code process and are gone when you exit. Nothing is written to disk. -> **Note:** Scheduled tasks are an experimental feature. Enable them with `experimental.cron: true` in your [settings](../configuration/settings.md), or set `QWEN_CODE_ENABLE_CRON=1` in your environment. +> **Tip:** Scheduled tasks are enabled by default. To disable them, set `experimental.cron: false` in your [settings](../configuration/settings.md), or set `QWEN_CODE_DISABLE_CRON=1` in your environment. ## Schedule a recurring prompt with /loop diff --git a/integration-tests/cli/acp-cron.test.ts b/integration-tests/cli/acp-cron.test.ts index 4b0a8c3cf3..8eacb2bc3c 100644 --- a/integration-tests/cli/acp-cron.test.ts +++ b/integration-tests/cli/acp-cron.test.ts @@ -87,7 +87,6 @@ function setupAcpCronTest(rig: TestRig) { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, - QWEN_CODE_ENABLE_CRON: '1', }, }, ); @@ -302,9 +301,7 @@ async function initSession( 'cron job fires and streams results via sessionUpdate after prompt returns', async () => { const rig = new TestRig(); - rig.setup('acp-cron-e2e', { - settings: { experimental: { cron: true } }, - }); + rig.setup('acp-cron-e2e'); const { sendRequest, diff --git a/integration-tests/cli/cron-tools.test.ts b/integration-tests/cli/cron-tools.test.ts index 9493f55744..950a61a647 100644 --- a/integration-tests/cli/cron-tools.test.ts +++ b/integration-tests/cli/cron-tools.test.ts @@ -22,15 +22,12 @@ describe('cron-tools', () => { if (rig) { await rig.cleanup(); } - // Clean up env vars - delete process.env['QWEN_CODE_ENABLE_CRON']; + delete process.env['QWEN_CODE_DISABLE_CRON']; }); - it('should have cron tools registered when enabled via settings', async () => { + it('should have cron tools registered by default', async () => { rig = new TestRig(); - await rig.setup('cron-tools-registered', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('cron-tools-registered'); const result = await rig.run( 'Do you have access to tools called cron_create, cron_list, and cron_delete? Reply with just "yes" or "no".', @@ -43,31 +40,37 @@ describe('cron-tools', () => { // Env vars set in the test process are not forwarded into Docker containers, // so this test cannot pass in sandbox mode. (IS_SANDBOX ? it.skip : it)( - 'should have cron tools registered when enabled via env var', + 'should NOT have cron tools when disabled via env var', async () => { rig = new TestRig(); - await rig.setup('cron-tools-env-var'); + await rig.setup('cron-tools-disabled-env-var'); - process.env['QWEN_CODE_ENABLE_CRON'] = '1'; + process.env['QWEN_CODE_DISABLE_CRON'] = '1'; const result = await rig.run( - 'Do you have access to tools called cron_create, cron_list, and cron_delete? Reply with just "yes" or "no".', + 'Try to create a cron job with cron_create using cron "*/5 * * * *", prompt "disabled test", and recurring true. If you cannot call that tool, say so briefly.', ); - validateModelOutput(result, null, 'cron tools via env var'); - expect(result.toLowerCase()).toContain('yes'); + validateModelOutput(result, null, 'cron disabled via env var'); + const toolLogs = rig.readToolLogs(); + expect( + toolLogs.some((log) => log.toolRequest.name === 'cron_create'), + 'cron_create should not be callable when cron is disabled', + ).toBe(false); }, ); - it('should NOT have cron tools by default', async () => { + it('should NOT have cron tools when disabled via settings', async () => { rig = new TestRig(); - await rig.setup('cron-tools-disabled-by-default'); + await rig.setup('cron-tools-disabled-via-settings', { + settings: { experimental: { cron: false } }, + }); const result = await rig.run( 'Try to create a cron job with cron_create using cron "*/5 * * * *", prompt "disabled test", and recurring true. If you cannot call that tool, say so briefly.', ); - validateModelOutput(result, null, 'cron disabled by default'); + validateModelOutput(result, null, 'cron disabled via settings'); const toolLogs = rig.readToolLogs(); expect( toolLogs.some((log) => log.toolRequest.name === 'cron_create'), @@ -77,9 +80,7 @@ describe('cron-tools', () => { it('should create, list, and delete a cron job in a single turn', async () => { rig = new TestRig(); - await rig.setup('cron-create-list-delete', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('cron-create-list-delete'); const result = await rig.run( 'Call cron_create with cron "*/5 * * * *", prompt "test ping", recurring true. Then call cron_list. Then delete that job using cron_delete. Then call cron_list again. How many jobs remain? Reply with just the number.', @@ -106,9 +107,7 @@ describe('cron-tools', () => { it('should create a one-shot (non-recurring) job', async () => { rig = new TestRig(); - await rig.setup('cron-one-shot', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('cron-one-shot'); const result = await rig.run( 'Do these steps: (1) Call cron_create with cron "*/5 * * * *", prompt "one-shot test", recurring false. (2) Call cron_list. Is the job marked as recurring or one-shot? Remember the answer. (3) Delete all cron jobs. Reply with just "recurring" or "one-shot".', @@ -132,9 +131,7 @@ describe('cron-tools', () => { it('should exit normally in -p mode when no cron jobs are created', async () => { rig = new TestRig(); - await rig.setup('cron-no-jobs-exit', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('cron-no-jobs-exit'); // A normal -p call without cron should still exit quickly const result = await rig.run('What is 2+2? Reply with just the number.'); diff --git a/integration-tests/cli/tool-search.test.ts b/integration-tests/cli/tool-search.test.ts index ce811882f4..5b01d12937 100644 --- a/integration-tests/cli/tool-search.test.ts +++ b/integration-tests/cli/tool-search.test.ts @@ -13,8 +13,8 @@ * invoke them in the same session. * * Cron tools (cron_create, cron_list, cron_delete) are convenient deferred - * targets: deterministic, side-effect-free in -p mode, and gated behind the - * `experimental.cron` setting so we control when they're registered. + * targets: deterministic, side-effect-free in -p mode, and enabled by default + * (can be disabled via `experimental.cron: false`). */ import { describe, it, expect, afterEach } from 'vitest'; @@ -33,9 +33,7 @@ describe('tool-search / deferred tools', () => { it('reveals a deferred tool via select: and lets the model invoke it', async () => { rig = new TestRig(); - await rig.setup('tool-search-select-then-invoke', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('tool-search-select-then-invoke'); // Force the model down the select: path so the assertion isn't dependent // on whether the model spontaneously chose keyword search vs. select. @@ -74,9 +72,7 @@ describe('tool-search / deferred tools', () => { it('finds deferred tools via keyword search', async () => { rig = new TestRig(); - await rig.setup('tool-search-keyword', { - settings: { experimental: { cron: true } }, - }); + await rig.setup('tool-search-keyword'); // The tool_search response is a synthetic ... // block; we check the ARGS the model sent (a keyword query, not select:) @@ -116,9 +112,9 @@ describe('tool-search / deferred tools', () => { it('does not register deferred tools when their feature flag is off', async () => { rig = new TestRig(); - // No experimental.cron setting → cron_* tools must not be registered at - // all (deferred or otherwise). tool_search has nothing to surface. - await rig.setup('tool-search-no-cron'); + await rig.setup('tool-search-no-cron', { + settings: { experimental: { cron: false } }, + }); const result = await rig.run( 'Call tool_search with query "select:cron_list". ' + diff --git a/integration-tests/interactive/cron-interactive.test.ts b/integration-tests/interactive/cron-interactive.test.ts index d4894e6079..9e9f982c37 100644 --- a/integration-tests/interactive/cron-interactive.test.ts +++ b/integration-tests/interactive/cron-interactive.test.ts @@ -26,7 +26,6 @@ function makeEnv(): NodeJS.ProcessEnv { delete env['NO_COLOR']; return { ...env, - QWEN_CODE_ENABLE_CRON: '1', FORCE_COLOR: '1', TERM: 'xterm-256color', NODE_NO_WARNINGS: '1', diff --git a/integration-tests/interactive/interactive-session.ts b/integration-tests/interactive/interactive-session.ts index 0065cebeae..e329b91230 100644 --- a/integration-tests/interactive/interactive-session.ts +++ b/integration-tests/interactive/interactive-session.ts @@ -76,7 +76,7 @@ export class InteractiveSession { * @example * ```ts * const session = await InteractiveSession.start({ - * env: { QWEN_CODE_ENABLE_CRON: '1' }, + * env: { QWEN_CODE_DISABLE_CRON: '1' }, * args: ['--approval-mode', 'yolo'], * }); * ``` diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index cdeff7c9c0..d56683fa38 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -1876,7 +1876,7 @@ export async function loadCliConfig( maxWallTimeSeconds: resolveMaxWallTimeSeconds(argv, settings), maxToolCalls: resolveMaxToolCalls(argv, settings), experimentalZedIntegration: argv.acp || argv.experimentalAcp || false, - cronEnabled: settings.experimental?.cron ?? false, + cronEnabled: settings.experimental?.cron ?? true, computerUseEnabled: settings.tools?.computerUse?.enabled ?? true, emitToolUseSummaries: settings.experimental?.emitToolUseSummaries ?? true, listExtensions: argv.listExtensions || false, diff --git a/packages/cli/src/config/settingsSchema.ts b/packages/cli/src/config/settingsSchema.ts index eb51ad19c9..1b8a5be9fc 100644 --- a/packages/cli/src/config/settingsSchema.ts +++ b/packages/cli/src/config/settingsSchema.ts @@ -2501,9 +2501,9 @@ const SETTINGS_SCHEMA = { label: 'Enable Cron/Loop Tools', category: 'Experimental', requiresRestart: true, - default: false, + default: true, description: - 'Enable in-session cron/loop tools (experimental). When enabled, the model can create recurring prompts using cron_create, cron_list, and cron_delete tools. Can also be enabled via QWEN_CODE_ENABLE_CRON=1 environment variable.', + 'Enable in-session cron/loop tools. When enabled, the model can create recurring prompts using cron_create, cron_list, and cron_delete tools. Can be disabled via QWEN_CODE_DISABLE_CRON=1 environment variable.', showInDialog: true, }, emitToolUseSummaries: { diff --git a/packages/core/src/config/config.ts b/packages/core/src/config/config.ts index 0b507054d3..4005014709 100644 --- a/packages/core/src/config/config.ts +++ b/packages/core/src/config/config.ts @@ -1143,7 +1143,7 @@ export class Config { private readonly cliVersion?: string; private runtimeStatusEnabled = false; private readonly experimentalZedIntegration: boolean = false; - private readonly cronEnabled: boolean = false; + private readonly cronEnabled: boolean = true; private readonly forkSubagentEnabled: boolean = false; private workflowsEnabled = false; private readonly computerUseEnabled: boolean = true; @@ -1328,7 +1328,7 @@ export class Config { this.sessionTokenLimit = params.sessionTokenLimit ?? -1; this.experimentalZedIntegration = params.experimentalZedIntegration ?? false; - this.cronEnabled = params.cronEnabled ?? false; + this.cronEnabled = params.cronEnabled ?? true; this.forkSubagentEnabled = params.forkSubagentEnabled ?? false; this.workflowsEnabled = params.workflowsEnabled ?? false; this.computerUseEnabled = params.computerUseEnabled ?? true; @@ -3312,8 +3312,7 @@ export class Config { } isCronEnabled(): boolean { - // Cron is experimental and opt-in: enabled via settings or env var - if (process.env['QWEN_CODE_ENABLE_CRON'] === '1') return true; + if (process.env['QWEN_CODE_DISABLE_CRON'] === '1') return false; return this.cronEnabled; } diff --git a/packages/vscode-ide-companion/schemas/settings.schema.json b/packages/vscode-ide-companion/schemas/settings.schema.json index f95e9307a4..a9d0d0bbe2 100644 --- a/packages/vscode-ide-companion/schemas/settings.schema.json +++ b/packages/vscode-ide-companion/schemas/settings.schema.json @@ -2534,9 +2534,9 @@ "type": "object", "properties": { "cron": { - "description": "Enable in-session cron/loop tools (experimental). When enabled, the model can create recurring prompts using cron_create, cron_list, and cron_delete tools. Can also be enabled via QWEN_CODE_ENABLE_CRON=1 environment variable.", + "description": "Enable in-session cron/loop tools. When enabled, the model can create recurring prompts using cron_create, cron_list, and cron_delete tools. Can be disabled via QWEN_CODE_DISABLE_CRON=1 environment variable.", "type": "boolean", - "default": false + "default": true }, "emitToolUseSummaries": { "description": "Generate a short LLM-based label after each tool batch completes. In compact mode the label replaces the generic `Tool × N` header; in full mode it appears as a dim `●