fix(v2): cancel/pause/resume recurring tasks via series_id

Recurring tasks spawn a new messages_in row per occurrence. Cancel
only matched the completed row the agent remembered, leaving the
live next occurrence running. Tag every row in a recurrence chain
with the originating task's id (series_id) so cancel/pause/resume
can reach any live row in the series. Cancel also clears recurrence
to prevent the sweep from cloning a cancelled task. Kind-aware id
prefix on recurrences (task- instead of msg-) keeps list_tasks output
consistent across occurrences.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
exe.dev user
2026-04-16 15:51:37 +00:00
committed by Daniel
parent 419d04fee0
commit 8ef30ad289
5 changed files with 245 additions and 17 deletions

View File

@@ -159,12 +159,13 @@ async function handleRecurrence(inDb: Database.Database, session: Session): Prom
const { CronExpressionParser } = await import('cron-parser');
const interval = CronExpressionParser.parse(msg.recurrence);
const nextRun = interval.next().toISOString();
const newId = `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
const prefix = msg.kind === 'task' ? 'task' : 'msg';
const newId = `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
insertRecurrence(inDb, msg, newId, nextRun);
clearRecurrence(inDb, msg.id);
log.info('Inserted next recurrence', { originalId: msg.id, newId, nextRun });
log.info('Inserted next recurrence', { originalId: msg.id, newId, seriesId: msg.series_id, nextRun });
} catch (err) {
log.error('Failed to compute next recurrence', { messageId: msg.id, recurrence: msg.recurrence, err });
}