(directory, client, sessionID, options = {})
| 1235 | // active-run guard, recover instead of waiting for another manual command. |
| 1236 | if ((live.type === "busy" || live.type === "retry") && options.recoverStaleActive !== false && staleActiveRun(sessionID)) { |
| 1237 | sessionStatuses.set(sessionID, "idle") |
| 1238 | sessionStatusSeenAt.set(sessionID, now()) |
| 1239 | return "idle" |
| 1240 | } |
| 1241 | sessionStatuses.set(sessionID, live.type) |
| 1242 | sessionStatusSeenAt.set(sessionID, now()) |
| 1243 | return live.type |
| 1244 | } |
| 1245 | |
| 1246 | const fallback = activeRuns.has(sessionID) && !staleActiveRun(sessionID) ? "busy" : "idle" |
| 1247 | sessionStatuses.set(sessionID, fallback) |
| 1248 | sessionStatusSeenAt.set(sessionID, now()) |
| 1249 | return fallback |
| 1250 | } |
| 1251 | |
| 1252 | async function sessionIsIdle(client, sessionID, directory, options = {}) { |
| 1253 | return await sessionStatusType(client, sessionID, directory, options) === "idle" |
| 1254 | } |
| 1255 | |
| 1256 | function scheduleIdleWork(directory, client, sessionID) { |
| 1257 | const previous = idleTimers.get(sessionID) |
| 1258 | if (previous) clearTimeout(previous) |
| 1259 | const timer = setTimeout(() => { |
| 1260 | idleTimers.delete(sessionID) |
| 1261 | Promise.resolve() |
| 1262 | .then(async () => { |
| 1263 | if (!await sessionIsIdle(client, sessionID, directory)) { |
| 1264 | await scheduleDueWork(directory, client, sessionID, BUSY_RETRY_MS) |
| 1265 | return |
| 1266 | } |
| 1267 | await finalizeActiveRun(directory, client, sessionID) |
| 1268 | if (!await sessionIsIdle(client, sessionID, directory)) { |
| 1269 | await scheduleDueWork(directory, client, sessionID, BUSY_RETRY_MS) |
| 1270 | return |
| 1271 | } |
| 1272 | await maybeRunDueJobs(directory, client, sessionID) |
| 1273 | }) |
| 1274 | .catch((error) => { |
| 1275 | toast(client, `Loop idle handler failed: ${sdkErrorMessage(error)}`, "error").catch(() => {}) |
| 1276 | appendLoopLog(directory, "idle-error", { sessionID, error: sdkErrorMessage(error) }).catch(() => {}) |
| 1277 | }) |
| 1278 | }, IDLE_DEBOUNCE_MS) |
| 1279 | idleTimers.set(sessionID, timer) |
| 1280 | } |
| 1281 | |
| 1282 | function jobDueAt(job, current = now()) { |
| 1283 | if (isGoalJob(job) && ["completed", "blocked", "cleared"].includes(job.goalStatus)) return Infinity |
| 1284 | if (!job.enabled || job.paused) return Infinity |
| 1285 | if (job.maxRuns > 0 && (job.runCount || 0) >= job.maxRuns) return Infinity |
| 1286 | if (job.watchPaths?.length) return Infinity |
| 1287 | const created = Date.parse(job.createdAt || new Date().toISOString()) |
| 1288 | if (job.maxRuntimeMs > 0 && current - created >= job.maxRuntimeMs) return current |
| 1289 | if (job.intervalMs === 0) return current |
| 1290 | if (!job.lastRunAt) return current |
| 1291 | return job.lastRunAt + (job.intervalMs || 0) |
| 1292 | } |
| 1293 | |
| 1294 | function nextDueDelay(state) { |
no test coverage detected