/**
 * SessionRefresh — orchestrates an agent-initiated session respawn.
 *
 * When an agent installs a new MCP server or skill mid-session, Claude Code
 * only attaches the new tools at session start. The agent triggers this class
 * to kill its current tmux session and respawn it with `claude --resume <uuid>`,
 * which loads the freshly installed tools while preserving full conversation
 * state.
 *
 * The respawn lifecycle is owned end-to-end here:
 *   detect    — sanity-check session, topic binding, resume UUID exist
 *   attempt   — apply rate guard, persist resume UUID, kill tmux, respawn
 *   verify    — new session is registered for the topic
 *   finalize  — return structured result
 *
 * Rate guard is a structural rate-counter (allowed-detector category per
 * docs/signal-vs-authority.md "safety guards on irreversible actions"
 * carve-out) — prevents infinite-respawn loops. Not a judgment call.
 *
 * Scope: Telegram-bound AND Slack-bound sessions (TOPIC-PROFILE-SPEC §10.5 —
 * Slack parity is an explicit prerequisite, not a v2 follow-up). Binding
 * resolution checks Telegram first (in-memory map, then a fresh disk read of
 * the persisted topic-session registry — so a binding registered after this
 * process loaded the registry, e.g. on a --no-telegram server, is still
 * recoverable), then the Slack channel/thread registry via the optional
 * `slack` dep. Slack respawns go through the optional `slackRespawner`
 * callback; when a session is Slack-bound but no slackRespawner is wired the
 * refusal is the structured `slack_respawner_unwired` (so callers can degrade
 * to CONTINUATION-on-next-message, disclosed honestly, per §10.5). Genuinely
 * unbound sessions (iMessage, headless) return
 * { ok: false, code: 'not_telegram_bound' } (code kept for back-compat).
 */
import { type SlackRefreshBinding, type SlackRespawner } from './slackRefreshBinding.js';
import type { SessionManager } from './SessionManager.js';
import type { StateManager } from './StateManager.js';
import type { TelegramAdapter } from '../messaging/TelegramAdapter.js';
import type { TopicResumeMap } from './TopicResumeMap.js';
export declare function ensureResumeTranscriptInConfigHome(uuid: string, targetConfigHome: string): boolean;
export interface SessionRefreshDeps {
    sessionManager: SessionManager;
    state: StateManager;
    telegram: TelegramAdapter | null;
    topicResumeMap: TopicResumeMap | null;
    /**
     * Respawn callback. Wired by server bootstrap to call
     * respawnSessionForTopic with the right closure (sessionManager,
     * telegram, topicMemory, etc). Kept as a callback to keep this class
     * decoupled from the server-internal respawn helper.
     *
     * IMPORTANT: respawnSessionForTopic does NOT kill the old tmux session
     * — it only spawns a new one and re-registers the topic mapping.
     * SessionRefresh.refreshSession is responsible for killing the old
     * session via sessionManager.killSession BEFORE calling the respawner,
     * which also triggers the beforeSessionKill listener that persists
     * the resume UUID.
     *
     * Resolves to the new tmux session name on success.
     */
    respawner: (sessionName: string, topicId: number, followUpPrompt: string | undefined, 
    /** P1.3 account swap (optional, additive): launch the respawn under this
     *  account's config home + record the account id. Omitted = unchanged. */
    accountSwap?: {
        configHome?: string;
        accountId?: string;
    }) => Promise<string>;
    /**
     * Slack binding resolution (§10.5, optional + additive). SlackAdapter
     * satisfies this structurally; null/omitted = Telegram-only behavior,
     * byte-for-byte as before. Consulted only after BOTH Telegram lookups miss
     * (Telegram-bound resolution takes precedence, matching today's order).
     */
    slack?: SlackRefreshBinding | null;
    /**
     * Slack respawner callback (§10.5, optional + additive). Wired by server
     * bootstrap to mirror the Slack message-handler spawn path (resume read +
     * spawnInteractiveSession + registerChannelSession) — see SlackRespawner
     * docs. Same kill contract as `respawner`: SessionRefresh kills first.
     */
    slackRespawner?: SlackRespawner | null;
    /** Rate-guard config. Defaults: 5 refreshes per 10-minute rolling window. */
    rateLimit?: {
        maxPerWindow: number;
        windowMs: number;
    };
    /** Injectable clock for tests. Defaults to Date.now. */
    clock?: () => number;
}
export type RefreshResult = {
    ok: true;
    newSessionName: string;
    /** Telegram topic id, or (Slack) the stable NEGATIVE synthetic id —
     *  same hash the rest of the system uses to bridge Slack channels into
     *  numeric topic space. Kept required for back-compat consumers. */
    topicId: number;
    /** §10.5 platform tag. Absent = Telegram (pre-Slack result shape is
     *  preserved byte-for-byte for existing consumers/tests). */
    platform?: 'slack';
    /** §10.5 conversation key (`slack:<channel>[:<thread>]`). Slack only. */
    conversationKey?: string;
} | {
    ok: false;
    code: RefreshFailureCode;
    message: string;
};
export type RefreshFailureCode = 'rate_limited' | 'session_not_found' | 'not_telegram_bound' | 'no_telegram_adapter' | 'slack_respawner_unwired' | 'refresh_in_progress';
export interface RefreshOptions {
    sessionName: string;
    followUpPrompt?: string;
    reason?: string;
    /**
     * Fresh respawn: do NOT `--resume` the old session. After the kill (which
     * fires beforeSessionKill → TopicResumeMap saves the Claude UUID), clear that
     * resume entry so the respawner spawns a brand-new session with no
     * conversation carried over. Used by ContextWedgeSentinel: the old
     * transcript's latest assistant turn is corrupted (thinking-block 400), so
     * resuming it would immediately re-wedge. Default false (continuity-preserving
     * resume, the original self-refresh behavior).
     */
    fresh?: boolean;
    /**
     * Subscription & Auth Standard P1.3 (quota-aware account swap): when set, the
     * respawned session is launched/resumed under THIS account's config home
     * (CLAUDE_CONFIG_DIR) instead of the parent's, and `accountId` is recorded on
     * the new session record. Both optional + additive — when unset, refresh
     * behaviour is byte-for-byte unchanged (the resume UUID is account-agnostic,
     * so conversation continuity is preserved across the swap). Only meaningful
     * for claude-code sessions.
     */
    configHome?: string;
    accountId?: string;
}
export declare class SessionRefresh {
    private readonly deps;
    private readonly maxPerWindow;
    private readonly windowMs;
    private readonly clock;
    private readonly recentRefreshes;
    /** In-flight refresh guard — prevents the race where a second call
     *  fires before the first's kill+spawn completes, which would spawn
     *  two parallel sessions for the same topic. */
    private readonly inFlight;
    constructor(deps: SessionRefreshDeps);
    /**
     * Refresh a session: kill its tmux session (which fires beforeSessionKill
     * so the existing listener persists the Claude UUID via TopicResumeMap),
     * then respawn via the injected respawner which spawns a fresh tmux that
     * runs `claude --resume <uuid>` — picking up newly installed MCPs/skills
     * while preserving the full conversation.
     *
     * Returns a structured result; never throws on the expected failure modes
     * (rate-limit, session-not-found, non-Telegram-bound). Throws only on
     * unexpected internal errors from the respawner callback.
     */
    refreshSession(opts: RefreshOptions): Promise<RefreshResult>;
    /**
     * Returns true if a fresh refresh is allowed under the rolling window cap.
     * Pure read of the recent-refresh ledger; does not mutate.
     */
    private checkRateLimit;
    /** Append a timestamp to the rolling window for this session. */
    private recordRefresh;
    /**
     * Structured log when the rate guard blocks a request. Logged at warn
     * level so over-blocks are detectable in operations (per
     * docs/signal-vs-authority.md "Authorities must log their decisions").
     */
    private logRateLimit;
}
//# sourceMappingURL=SessionRefresh.d.ts.map