(initial: boolean)
| 179 | let isOwner = false |
| 180 | |
| 181 | async function load(initial: boolean) { |
| 182 | const next = await readCronTasks(dir) |
| 183 | if (stopped) return |
| 184 | tasks = next |
| 185 | |
| 186 | // Only surface missed tasks on initial load. Chokidar-triggered |
| 187 | // reloads leave overdue tasks to check() (which anchors from createdAt |
| 188 | // and fires immediately). This avoids a misleading "missed while Claude |
| 189 | // was not running" prompt for tasks that became overdue mid-session. |
| 190 | // |
| 191 | // Recurring tasks are NOT surfaced or deleted — check() handles them |
| 192 | // correctly (fires on first tick, reschedules forward). Only one-shot |
| 193 | // missed tasks need user input (run once now, or discard forever). |
| 194 | if (!initial) return |
| 195 | |
| 196 | const now = Date.now() |
| 197 | const missed = findMissedTasks(next, now).filter( |
| 198 | t => !t.recurring && !missedAsked.has(t.id) && (!filter || filter(t)), |
| 199 | ) |
| 200 | if (missed.length > 0) { |
| 201 | for (const t of missed) { |
| 202 | missedAsked.add(t.id) |
| 203 | // Prevent check() from re-firing the raw prompt while the async |
| 204 | // removeCronTasks + chokidar reload chain is in progress. |
| 205 | nextFireAt.set(t.id, Infinity) |
| 206 | } |
| 207 | logEvent('tengu_scheduled_task_missed', { |
| 208 | count: missed.length, |
| 209 | taskIds: missed |
| 210 | .map(t => t.id) |
| 211 | .join( |
| 212 | ',', |
| 213 | ) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS, |
| 214 | }) |
| 215 | if (onMissed) { |
| 216 | onMissed(missed) |
| 217 | } else { |
| 218 | onFire(buildMissedTaskNotification(missed)) |
| 219 | } |
| 220 | void removeCronTasks( |
| 221 | missed.map(t => t.id), |
| 222 | dir, |
| 223 | ).catch(e => |
| 224 | logForDebugging(`[ScheduledTasks] failed to remove missed tasks: ${e}`), |
| 225 | ) |
| 226 | logForDebugging( |
| 227 | `[ScheduledTasks] surfaced ${missed.length} missed one-shot task(s)`, |
| 228 | ) |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | function check() { |
| 233 | if (isKilled?.()) return |
no test coverage detected