mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-28 11:41:04 +00:00
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:
parent
99e5a9fbfd
commit
439a1a46e2
9 changed files with 75 additions and 32 deletions
|
|
@ -14,13 +14,15 @@ describe('cron-tools', () => {
|
|||
if (rig) {
|
||||
await rig.cleanup();
|
||||
}
|
||||
// Clean up env var if set by disable test
|
||||
delete process.env['QWEN_CODE_DISABLE_CRON'];
|
||||
// Clean up env vars
|
||||
delete process.env['QWEN_CODE_ENABLE_CRON'];
|
||||
});
|
||||
|
||||
it('should have cron tools registered', async () => {
|
||||
it('should have cron tools registered when enabled via settings', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-tools-registered');
|
||||
await rig.setup('cron-tools-registered', {
|
||||
settings: { experimental: { cron: true } },
|
||||
});
|
||||
|
||||
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".',
|
||||
|
|
@ -30,9 +32,37 @@ describe('cron-tools', () => {
|
|||
expect(result.toLowerCase()).toContain('yes');
|
||||
});
|
||||
|
||||
it('should have cron tools registered when enabled via env var', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-tools-env-var');
|
||||
|
||||
process.env['QWEN_CODE_ENABLE_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".',
|
||||
);
|
||||
|
||||
validateModelOutput(result, null, 'cron tools via env var');
|
||||
expect(result.toLowerCase()).toContain('yes');
|
||||
});
|
||||
|
||||
it('should NOT have cron tools by default', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-tools-disabled-by-default');
|
||||
|
||||
const result = await rig.run(
|
||||
'Do you have access to a tool called cron_create? Reply with just "yes" or "no".',
|
||||
);
|
||||
|
||||
validateModelOutput(result, null, 'cron disabled by default');
|
||||
expect(result.toLowerCase()).toContain('no');
|
||||
});
|
||||
|
||||
it('should create, list, and delete a cron job in a single turn', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-create-list-delete');
|
||||
await rig.setup('cron-create-list-delete', {
|
||||
settings: { experimental: { cron: true } },
|
||||
});
|
||||
|
||||
const result = await rig.run(
|
||||
'Call cron_create with cron_expression "*/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.',
|
||||
|
|
@ -59,7 +89,9 @@ describe('cron-tools', () => {
|
|||
|
||||
it('should create a one-shot (non-recurring) job', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-one-shot');
|
||||
await rig.setup('cron-one-shot', {
|
||||
settings: { experimental: { cron: true } },
|
||||
});
|
||||
|
||||
const result = await rig.run(
|
||||
'Do these steps: (1) Call cron_create with cron_expression "*/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".',
|
||||
|
|
@ -81,23 +113,11 @@ describe('cron-tools', () => {
|
|||
validateModelOutput(result, 'one-shot', 'cron one-shot');
|
||||
});
|
||||
|
||||
it('should not have cron tools when QWEN_CODE_DISABLE_CRON=1', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-disable-flag');
|
||||
|
||||
process.env['QWEN_CODE_DISABLE_CRON'] = '1';
|
||||
|
||||
const result = await rig.run(
|
||||
'Do you have access to a tool called cron_create? Reply with just "yes" or "no".',
|
||||
);
|
||||
|
||||
validateModelOutput(result, null, 'cron disable flag');
|
||||
expect(result.toLowerCase()).toContain('no');
|
||||
});
|
||||
|
||||
it('should exit normally in -p mode when no cron jobs are created', async () => {
|
||||
rig = new TestRig();
|
||||
await rig.setup('cron-no-jobs-exit');
|
||||
await rig.setup('cron-no-jobs-exit', {
|
||||
settings: { experimental: { cron: true } },
|
||||
});
|
||||
|
||||
// A normal -p call without cron should still exit quickly
|
||||
const result = await rig.run('What is 2+2? Reply with just the number.');
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -370,6 +370,7 @@ export interface ConfigParameters {
|
|||
maxSessionTurns?: number;
|
||||
sessionTokenLimit?: number;
|
||||
experimentalZedIntegration?: boolean;
|
||||
cronEnabled?: boolean;
|
||||
listExtensions?: boolean;
|
||||
overrideExtensions?: string[];
|
||||
allowedMcpServers?: string[];
|
||||
|
|
@ -557,6 +558,7 @@ export class Config {
|
|||
|
||||
private readonly cliVersion?: string;
|
||||
private readonly experimentalZedIntegration: boolean = false;
|
||||
private readonly cronEnabled: boolean = false;
|
||||
private readonly chatRecordingEnabled: boolean;
|
||||
private readonly loadMemoryFromIncludeDirectories: boolean = false;
|
||||
private readonly importFormat: 'tree' | 'flat';
|
||||
|
|
@ -680,6 +682,7 @@ export class Config {
|
|||
this.sessionTokenLimit = params.sessionTokenLimit ?? -1;
|
||||
this.experimentalZedIntegration =
|
||||
params.experimentalZedIntegration ?? false;
|
||||
this.cronEnabled = params.cronEnabled ?? false;
|
||||
this.listExtensions = params.listExtensions ?? false;
|
||||
this.overrideExtensions = params.overrideExtensions;
|
||||
this.noBrowser = params.noBrowser ?? false;
|
||||
|
|
@ -1687,8 +1690,10 @@ export class Config {
|
|||
return this.cronScheduler;
|
||||
}
|
||||
|
||||
isCronDisabled(): boolean {
|
||||
return process.env['QWEN_CODE_DISABLE_CRON'] === '1';
|
||||
isCronEnabled(): boolean {
|
||||
// Cron is experimental and opt-in: enabled via settings or env var
|
||||
if (process.env['QWEN_CODE_ENABLE_CRON'] === '1') return true;
|
||||
return this.cronEnabled;
|
||||
}
|
||||
|
||||
getEnableRecursiveFileSearch(): boolean {
|
||||
|
|
@ -2211,7 +2216,7 @@ export class Config {
|
|||
}
|
||||
|
||||
// Register cron tools unless disabled
|
||||
if (!this.isCronDisabled()) {
|
||||
if (this.isCronEnabled()) {
|
||||
await registerCoreTool(CronCreateTool, this);
|
||||
await registerCoreTool(CronListTool, this);
|
||||
await registerCoreTool(CronDeleteTool, this);
|
||||
|
|
|
|||
|
|
@ -1445,9 +1445,15 @@
|
|||
}
|
||||
},
|
||||
"experimental": {
|
||||
"description": "Setting to enable experimental features",
|
||||
"description": "Settings to enable experimental features.",
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
"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.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"$version": {
|
||||
"type": "number",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue