/**
 * File-based state management.
 *
 * All state is stored as JSON files — no database dependency.
 * This is intentional: agent infrastructure should be portable
 * and not require running a DB server.
 */
import type { Session, JobState, ActivityEvent } from './types.js';
export declare class StateManager {
    private stateDir;
    private _readOnly;
    private _sessionPoolActive;
    private _machineId;
    private readonly _now;
    /** Memoized full session list — the fix for the systemic CPU hot-loop where
     *  `listSessions` re-read + re-parsed EVERY session file from disk on EVERY call,
     *  and is called each tick by the reaper + sentinels via `listRunningSessions`
     *  (O(sessions × pollers × tick-rate) disk reads). Invalidated on any session
     *  write so spawns/terminations remain instantly visible. */
    private _sessionsCache;
    private _sessionsCacheAt;
    constructor(stateDir: string, opts?: {
        now?: () => number;
    });
    /** The root the state tree hangs off (files live under `<baseDir>/state/...`).
     *  Exposed so sibling stores (e.g. PendingInjectStore) can colocate without
     *  re-plumbing the path through their own config. */
    get baseDir(): string;
    /** Drop the listSessions cache so the next read reflects a just-written change. */
    private invalidateSessionsCache;
    /**
     * Set the machine ID for this StateManager instance.
     * When set, all activity events are automatically stamped with the originating machineId
     * (Phase 4D — Gap 6: machine-prefixed state).
     */
    setMachineId(machineId: string): void;
    /** Get the configured machine ID (null if not set). */
    get machineId(): string | null;
    /** Whether this StateManager is in read-only mode (standby machine). */
    get readOnly(): boolean;
    /**
     * Set read-only mode. When true, all write operations throw.
     * Used on standby machines to prevent accidental state forks.
     */
    setReadOnly(readOnly: boolean): void;
    /**
     * Whether the active-active session pool is enabled for this machine. When true, a
     * read-only standby is still permitted to write the PER-SESSION state of sessions it
     * legitimately OWNS (the pool's CAS ownership guarantees a single owner per session,
     * so a per-session file write can't fork shared cluster state). Shared-cluster writes
     * (set/delete/saveJobState/appendEvent) stay blocked on a standby regardless.
     * Default false → a pure one-awake standby remains fully read-only (unchanged).
     */
    setSessionPoolActive(active: boolean): void;
    /**
     * Guard that throws in read-only mode. `sessionScoped` marks a write that targets a
     * single owned session's own file (state/sessions/<id>.json) — permitted on a
     * read-only standby ONLY when the session pool is active (bug #9: a moved session's
     * owner-side resume must persist on the standby that now owns it; the pool path that
     * triggers it only fires for CAS-confirmed owned sessions). Shared-state writes pass
     * no opts and stay blocked.
     */
    private guardWrite;
    /**
     * PUBLIC guard entrypoint for coherence-journal appends (COHERENCE-JOURNAL-SPEC §3.1).
     *
     * The journal writer (`CoherenceJournal`) does its own file I/O — it is not a
     * StateManager method — so the read-only-standby decision is centralized HERE and the
     * flusher calls this before each append batch. Two deliberate properties:
     *
     * 1. Journal writes are permitted on a read-only standby INDEPENDENT of
     *    `_sessionPoolActive`. The `sessionScoped` exception above only opens when the
     *    pool is live; reusing it would silently disable the journal on exactly the
     *    quiet-standby topology the EXO artifact-stranding incident happened on. A
     *    standby observing its own placement/lifecycle events cannot fork shared state:
     *    journal streams are single-producer-per-machine by construction.
     *
     * 2. The permission is allowlisted to precisely the canonicalized journal prefix —
     *    own streams + meta/lock sidecars (`state/coherence-journal/`) and replica
     *    appends (`state/coherence-journal/peers/`, inside the first). A path escaping
     *    the prefix throws even when NOT read-only: the journal dir is the only thing
     *    this entrypoint will ever bless, so a bug constructing a stray path fails
     *    loudly here instead of writing somewhere surprising.
     */
    guardJournalWrite(targetPath: string): void;
    /** Validate a key/ID contains only safe characters to prevent path traversal. */
    private validateKey;
    getSession(sessionId: string): Session | null;
    saveSession(session: Session): void;
    /**
     * Close every OTHER record still marked live for the same tmux session name
     * (ghost-record supersession). Each ghost is closed through saveSession
     * itself so the journal funnel records its completed transition — the
     * reentry terminates because a 'completed' save never enters this path.
     * Best-effort: a failure here must never endanger the spawn being saved.
     */
    private supersedeStaleLiveRecords;
    /** Best-effort read of an existing session record's status (funnel diff input). */
    private peekSessionStatus;
    /** Injected at server startup; absent (undefined) = journaling disabled, zero-cost no-op. */
    private coherenceJournal?;
    /** Wire the coherence journal (COHERENCE-JOURNAL-SPEC §3.3). Server startup only. */
    setCoherenceJournal(journal: import('./CoherenceJournal.js').CoherenceJournal | undefined): void;
    /**
     * The wired journal handle, for callers that already hold the StateManager
     * (e.g. routes threading the §3.3 stop-funnel emits) — saves a parallel
     * plumbing path through every context object.
     */
    getCoherenceJournal(): import('./CoherenceJournal.js').CoherenceJournal | undefined;
    /**
     * Map a saveSession status diff onto journal lifecycle events. NEVER throws
     * into saveSession (the journal must not endanger the observed operation).
     * 'reaped' is NOT derived here — the reaper emits it explicitly alongside its
     * reap-log append (with reapLogRef), per the spec.
     */
    private recordLifecycleTransition;
    listSessions(filter?: {
        status?: Session['status'];
    }): Session[];
    /**
     * Read every session file (readdir + readFileSync + JSON.parse), memoized for
     * SESSIONS_CACHE_TTL_MS. THIS is the hot path: the reaper and every sentinel call
     * `listRunningSessions` → `listSessions` on each tick, so without the cache the
     * server re-read + re-parsed the FULL session directory many times per second
     * (the systemic ~30%-CPU `readFileUtf8` hot-loop). Writes invalidate the cache, so
     * a freshly spawned or removed session is visible on the very next call.
     */
    private readAllSessionsCached;
    removeSession(sessionId: string): boolean;
    getJobState(slug: string): JobState | null;
    saveJobState(state: JobState): void;
    appendEvent(event: ActivityEvent): void;
    queryEvents(options: {
        since?: Date;
        type?: string;
        limit?: number;
    }): ActivityEvent[];
    get<T>(key: string): T | null;
    set<T>(key: string, value: T): void;
    delete(key: string): boolean;
    /**
     * Write a file atomically — write to .tmp then rename.
     * Prevents corruption from power loss or disk-full mid-write.
     */
    private atomicWrite;
}
//# sourceMappingURL=StateManager.d.ts.map