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