feat(cron): make cron tools opt-in via experimental settings

Change cron/loop tools from opt-out to opt-in. Cron tools are now
disabled by default and can be enabled via:
- settings.json: { "experimental": { "cron": true } }
- Environment variable: QWEN_CODE_ENABLE_CRON=1

This ensures experimental features are explicitly enabled by users
who want to try them.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
tanzhenxin 2026-03-29 02:25:28 +00:00
parent 99e5a9fbfd
commit 439a1a46e2
9 changed files with 75 additions and 32 deletions

View file

@ -1089,6 +1089,7 @@ export async function loadCliConfig(
maxSessionTurns:
argv.maxSessionTurns ?? settings.model?.maxSessionTurns ?? -1,
experimentalZedIntegration: argv.acp || argv.experimentalAcp || false,
cronEnabled: settings.experimental?.cron ?? false,
listExtensions: argv.listExtensions || false,
overrideExtensions: overrideExtensions || argv.extensions,
noBrowser: !!process.env['NO_BROWSER'],

View file

@ -1594,9 +1594,20 @@ const SETTINGS_SCHEMA = {
category: 'Experimental',
requiresRestart: true,
default: {},
description: 'Setting to enable experimental features',
description: 'Settings to enable experimental features.',
showInDialog: false,
properties: {},
properties: {
cron: {
type: 'boolean',
label: 'Enable Cron/Loop Tools',
category: 'Experimental',
requiresRestart: true,
default: false,
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.',
showInDialog: true,
},
},
},
} as const satisfies SettingsSchema;

View file

@ -144,7 +144,7 @@ describe('runNonInteractive', () => {
}),
getExperimentalZedIntegration: vi.fn().mockReturnValue(false),
isInteractive: vi.fn().mockReturnValue(false),
isCronDisabled: vi.fn().mockReturnValue(true),
isCronEnabled: vi.fn().mockReturnValue(false),
getCronScheduler: vi.fn().mockReturnValue(null),
} as unknown as Config;

View file

@ -372,7 +372,7 @@ export async function runNonInteractive(
currentMessages = [{ role: 'user', parts: toolResponseParts }];
} else {
// No more tool calls — check if cron jobs are keeping us alive
const scheduler = config.isCronDisabled()
const scheduler = !config.isCronEnabled()
? null
: config.getCronScheduler();
if (scheduler && scheduler.size > 0) {

View file

@ -204,7 +204,7 @@ describe('useGeminiStream', () => {
.mockReturnValue(contentGeneratorConfig),
getMaxSessionTurns: vi.fn(() => 50),
getArenaAgentClient: vi.fn(() => null),
isCronDisabled: vi.fn(() => true),
isCronEnabled: vi.fn(() => false),
getCronScheduler: vi.fn(() => null),
} as unknown as Config;
mockOnDebugMessage = vi.fn();

View file

@ -1643,7 +1643,7 @@ export const useGeminiStream = (
// Start the scheduler on mount, stop on unmount
useEffect(() => {
if (config.isCronDisabled()) return;
if (!config.isCronEnabled()) return;
const scheduler = config.getCronScheduler();
scheduler.start((job: { prompt: string }) => {
cronQueueRef.current.push(job.prompt);