/**
 * In-process workflow trigger scheduler (plan M3 trigger layer).
 *
 * Architecture note: scheduling lives INSIDE qlogicagent, not the gateway. This mirrors the
 * established precedent — the idle "dream" timer was deliberately moved out of the openclaw gateway
 * into qlogicagent so the Agent owns its own timed lifecycle ("Agent 是唯一状态拥有者"). The gateway
 * remains a pure relay: it terminates external ingress (webhook HTTP, inbound IM) and forwards those
 * events here via `onWebhook` / `onImMessage`; the cron clock and the run decision are the Agent's.
 *
 * The scheduler is the consumer of WorkflowController: on `start()` it loads every active workflow
 * (`listActive`, spec §B4 restart re-registration) and arms a timer per `schedule` trigger; on fire
 * it calls `controller.run(id, { type: "schedule" })`. Webhook / IM / manual triggers are
 * caller-driven (the gateway relay or the agent invokes the matching method). Concurrency,
 * budgeting, and permission gating all live in `controller.run` — the scheduler only decides WHEN.
 *
 * Determinism: the clock (now + timers) is injected, so tests pin time exactly and far-future cron
 * delays are handled by chunked re-arming (Node's setTimeout caps at ~24.8 days).
 */
import type { WorkflowController, RunOutcome } from "./workflow-controller.js";
export type TimerHandle = unknown;
/** Injectable clock + timer surface so the scheduler is fully deterministic under test. */
export interface SchedulerClock {
    now(): number;
    setTimer(fn: () => void, delayMs: number): TimerHandle;
    clearTimer(handle: TimerHandle): void;
}
/** Default clock backed by the real system time and Node timers. */
export declare const systemClock: SchedulerClock;
export interface SchedulerDeps {
    controller: WorkflowController;
    clock?: SchedulerClock;
    /**
     * Surfaces an error from a timer-driven (cron) run, which has no caller to throw back to.
     * Required so a failed unattended run is never silently swallowed (§1.5 fail-loud). Caller-driven
     * triggers (webhook/IM/manual) propagate their errors to the caller instead.
     */
    onError: (err: unknown, workflowId: string) => void;
    onRunStarted?: (workflowId: string, triggerType: string) => void;
    onRunCompleted?: (outcome: RunOutcome, triggerType: string) => void;
    onRunFailed?: (workflowId: string, triggerType: string, err: unknown) => void;
    /**
     * Trigger rate-limit (D32/§B5 防洪): minimum ms between UNATTENDED fires (webhook/IM/cron) of
     * the same workflow. A fire inside the window is DROPPED — loudly, via onTriggerDropped (dead
     * letter), never silently. Manual/intent (attended) fires are exempt. Default 0 (off) — the
     * production assembly (ensureWorkflowRoot) opts in explicitly; rate policy is the host's call.
     */
    minTriggerIntervalMs?: number;
    /** Dead-letter surface for rate-limited drops. Required when minTriggerIntervalMs > 0. */
    onTriggerDropped?: (workflowId: string, triggerType: string, detail: {
        sinceLastMs: number;
        minIntervalMs: number;
    }) => void;
}
export declare class WorkflowScheduler {
    private readonly controller;
    private readonly clock;
    private readonly onError;
    private readonly onRunStarted?;
    private readonly onRunCompleted?;
    private readonly onRunFailed?;
    /** Every registered workflow's trigger, keyed by id (the single source for lookups). */
    private readonly triggers;
    /** Active cron arms keyed by workflow id. */
    private readonly cronArms;
    /** Last unattended fire per workflow (rate-limit window anchor, D32). */
    private readonly lastFireAt;
    private readonly minTriggerIntervalMs;
    private readonly onTriggerDropped?;
    private started;
    constructor(deps: SchedulerDeps);
    /** Load every active workflow and arm its trigger (spec §B4 restart re-registration). */
    start(): Promise<void>;
    /** Stop all timers and forget every registration. Idempotent. */
    stop(): void;
    /**
     * Re-read a workflow and (re-)arm its trigger after a create/patch/setActive change. If the
     * workflow no longer exists or is inactive it is unregistered. Active workflows are armed by
     * their current trigger (manual ⇒ lookup-only, no timer).
     */
    refresh(id: string): Promise<void>;
    /** Drop a workflow's registration and cancel any pending cron timer. */
    unregister(id: string): void;
    /** Workflow ids currently registered (for inspection/tests). */
    registeredIds(): string[];
    /** The epoch ms a scheduled workflow will next fire, or undefined if it has no cron arm. */
    nextFireAt(id: string): number | undefined;
    /**
     * Deliver an external webhook hit. Runs every active workflow whose webhook trigger path equals
     * `path`. Returns the outcomes (empty ⇒ no workflow matched, which the gateway maps to 404).
     * Errors propagate to the caller (the HTTP handler), which is the fail-loud surface here.
     */
    onWebhook(path: string, payload?: Record<string, unknown>): Promise<RunOutcome[]>;
    /**
     * Deliver an inbound IM message. Runs every active workflow whose im-message trigger is on
     * `channel` and whose optional `match` regex tests `text`. The message text + channel are merged
     * into the run payload so downstream nodes can reference `$trigger`.
     */
    onImMessage(channel: string, text: string, payload?: Record<string, unknown>): Promise<RunOutcome[]>;
    /** Manually trigger a single workflow (the agent's `workflow.run`). Errors propagate to caller. */
    triggerManual(id: string, payload?: Record<string, unknown>): Promise<RunOutcome>;
    private matchingIds;
    private registerTrigger;
    private armCron;
    private fireCron;
    /**
     * Unattended-fire rate limit (D32): true ⇒ proceed (and anchor the window); false ⇒ DROPPED,
     * surfaced via onTriggerDropped (dead letter) — never silent.
     */
    private passesRateLimit;
    private runWithLifecycle;
}
