Merge pull request #2731 from QwenLM/feat/in-session-cron-loops

feat(cron): add in-session loop scheduling with cron tools
This commit is contained in:
tanzhenxin 2026-04-01 16:18:46 +08:00 committed by GitHub
commit 76d64c9464
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
60 changed files with 3110 additions and 41 deletions

View file

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

View file

@ -1236,7 +1236,10 @@ export const useGeminiStream = (
}
// Check image format support for non-continuations
if (submitType === SendMessageType.UserQuery) {
if (
submitType === SendMessageType.UserQuery ||
submitType === SendMessageType.Cron
) {
const formatCheck = checkImageFormatsSupport(queryToSend);
if (formatCheck.hasUnsupportedFormats) {
addItem(
@ -1253,7 +1256,10 @@ export const useGeminiStream = (
lastPromptRef.current = finalQueryToSend;
lastPromptErroredRef.current = false;
if (submitType === SendMessageType.UserQuery) {
if (
submitType === SendMessageType.UserQuery ||
submitType === SendMessageType.Cron
) {
// trigger new prompt event for session stats in CLI
startNewPrompt();
@ -1698,6 +1704,38 @@ export const useGeminiStream = (
storage,
]);
// ─── Cron scheduler integration ─────────────────────────
const cronQueueRef = useRef<string[]>([]);
const [cronTrigger, setCronTrigger] = useState(0);
// Start the scheduler on mount, stop on unmount
useEffect(() => {
if (!config.isCronEnabled()) return;
const scheduler = config.getCronScheduler();
scheduler.start((job: { prompt: string }) => {
cronQueueRef.current.push(job.prompt);
setCronTrigger((n) => n + 1);
});
return () => {
const summary = scheduler.getExitSummary();
scheduler.stop();
if (summary) {
process.stderr.write(summary + '\n');
}
};
}, [config]);
// When idle, drain the cron queue one prompt at a time
useEffect(() => {
if (
streamingState === StreamingState.Idle &&
cronQueueRef.current.length > 0
) {
const prompt = cronQueueRef.current.shift()!;
submitQuery(prompt, SendMessageType.Cron);
}
}, [streamingState, submitQuery, cronTrigger]);
return {
streamingState,
submitQuery,