/** * Chat-scoped loop scheduling. * * The scheduler only claims due rows and hands their IDs to Pipeline. Pipeline * owns Agent execution and settles each run after the response is delivered. */ import type Database from "better-sqlite3"; import { type ChatAccessContext } from "./access.js"; export declare const MIN_LOOP_INTERVAL_SECONDS = 60; export declare const DEFAULT_LOOP_DURATION_SECONDS: number; export declare const MAX_LOOP_DURATION_SECONDS: number; export declare const MAX_ACTIVE_LOOPS_PER_CHAT = 8; export declare const MAX_ACTIVE_LOOPS_PER_SCOPE = 3; export declare const LOOP_FAILURE_LIMIT = 3; export type LoopStatus = "active" | "queued" | "running" | "paused" | "completed" | "cancelled"; export interface LoopJob { id: number; chatId: string; threadId: string | null; replyToMsgId: string | null; creatorUserId: string; intervalSeconds: number; prompt: string; maxTimes: number | null; untilTime: string; runCount: number; status: LoopStatus; nextRunAt: string; lastRunAt: string | null; runStartedAt: string | null; lastError: string | null; consecutiveFailures: number; /** 日历表达式触发(与 cron 模式同构):非空时触发由 matchesCron + 分钟级防重决定 */ cronExpr: string | null; timezone: string; description: string | null; createdAt: string; updatedAt: string; } export declare function parseLoopDuration(value: string): number | undefined; export declare function formatLoopInterval(seconds: number): string; export declare function addLoopJob(db: Database.Database, options: { chatId: string; threadId?: string; replyToMsgId?: string; creatorUserId: string; intervalSeconds: number; prompt: string; maxTimes?: number; durationSeconds?: number; /** 绝对截止时间(UTC SQL),覆盖 durationSeconds 推导;用于 --until */ untilTime?: string; /** 覆盖 next_run_at(UTC SQL 时间),用于一次性定时/延迟任务;缺省为 now + interval */ runAt?: string; /** 日历表达式触发(如 "0 9 * * 1"):next_run_at 退化为检查点,触发由表达式匹配决定 */ cronExpr?: string; timezone?: string; description?: string; now?: Date; }): number; export declare function getLoopJob(db: Database.Database, id: number): LoopJob | undefined; export declare function listLoopJobs(db: Database.Database, chatId: string, threadId?: string): LoopJob[]; export declare function cancelLoopJobForAccess(db: Database.Database, id: number, context: ChatAccessContext & { userId?: string; }): LoopJob | undefined; /** Restore rows whose in-memory queue/Agent run disappeared with the process. */ export declare function recoverInterruptedLoopJobs(db: Database.Database, now?: Date): number; /** Atomically claim all currently due loops. */ export declare function claimDueLoopJobs(db: Database.Database, now?: Date): LoopJob[]; /** Atomically start a queued Loop turn. */ export declare function startLoopRun(db: Database.Database, id: number, now?: Date): LoopJob | undefined; export declare function completeLoopRun(db: Database.Database, id: number, result: { success: boolean; error?: string; cancelled?: boolean; now?: Date; }): LoopJob | undefined; /** A queued event discarded by /clear or /stop should remain scheduled. */ export declare function releaseQueuedLoopJob(db: Database.Database, id: number, now?: Date): boolean; export type LoopExecutor = (job: LoopJob) => Promise | void; export declare class LoopScheduler { private readonly db; private readonly executor; private readonly checkIntervalMs; private timer; private ticking; private currentTick; private stopping; constructor(db: Database.Database, executor: LoopExecutor, checkIntervalMs?: number); start(): void; stop(): Promise; tick(now?: Date): Promise; private tickSafely; private scheduleTick; }