Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/users/features/scheduled-tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 1 addition & 4 deletions integration-tests/cli/acp-cron.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ function setupAcpCronTest(rig: TestRig) {
stdio: ['pipe', 'pipe', 'pipe'],
env: {
...process.env,
QWEN_CODE_ENABLE_CRON: '1',
},
},
);
Expand Down Expand Up @@ -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,
Expand Down
45 changes: 21 additions & 24 deletions integration-tests/cli/cron-tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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".',
Expand All @@ -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'),
Expand All @@ -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.',
Expand All @@ -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".',
Expand All @@ -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.');
Expand Down
18 changes: 7 additions & 11 deletions integration-tests/cli/tool-search.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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.
Expand Down Expand Up @@ -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 <functions>...</functions>
// block; we check the ARGS the model sent (a keyword query, not select:)
Expand Down Expand Up @@ -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". ' +
Expand Down
1 change: 0 additions & 1 deletion integration-tests/interactive/cron-interactive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion integration-tests/interactive/interactive-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
* });
* ```
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/config/settingsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions packages/vscode-ide-companion/schemas/settings.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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 `● <label>` line below the tool group. Requires a fast model to be configured; runs in parallel with the next API call so latency is hidden. Currently affects interactive CLI rendering only — SDK / non-interactive emission of the `tool_use_summary` message is not yet wired (the message factory is exported for a follow-up PR). Can be overridden with QWEN_CODE_EMIT_TOOL_USE_SUMMARIES=0 or =1.",
Expand Down
Loading