import { expandHome } from './working-dir.js';
import type { CliId } from '../adapters/cli/types.js';
import type { BackendType } from '../adapters/backend/types.js';
import type { LarkAttachment, LarkMention, ScheduledTask } from '../types.js';
import type { MessageResource } from '../im/lark/message-parser.js';
import type { ResolvedSender } from '../im/lark/identity-cache.js';
import type { DaemonSession } from './types.js';
import { type Locale } from '../i18n/index.js';
export { expandHome };
export declare function getSessionWorkingDir(ds?: DaemonSession): string;
export declare function getProjectScanDir(ds?: DaemonSession): string;
/**
 * Return all directories to scan for projects (supports multi-dir WORKING_DIR).
 * Each configured workingDir is used as the scan root AS-IS — scanProjects
 * recurses downward from it. See getProjectScanDir for why we no longer climb
 * to the parent directory.
 */
export declare function getProjectScanDirs(ds?: DaemonSession): string[];
export declare function getAttachmentsDir(messageId: string): string;
export declare function downloadResources(larkAppId: string, messageId: string, resources: MessageResource[]): Promise<{
    attachments: LarkAttachment[];
    needLogin: boolean;
}>;
/** Get bots actually present in the chat (excludes current bot).
 *  Calls Lark OpenAPI to list chat members, then cross-references with
 *  registered bots to enrich with cliId. Falls back to empty on API error. */
export declare function getAvailableBots(currentAppId: string, chatId: string): Promise<Array<{
    name: string;
    displayName: string;
    openId: string;
}>>;
/**
 * Render a `<sender>` tag for prompt injection. Caller resolves the sender
 * (open_id + type + optional name) via `resolveSender(...)` in identity-cache.
 * Returns empty string when no sender data is available so the prompt stays
 * clean for synthetic flows (scheduled tasks, no-op spawns).
 */
export declare function renderSenderTag(sender?: ResolvedSender): string;
/**
 * cursor-agent's model tends to copy the inlined `<sender open_id="ou_xxx"
 * name="高鹏" />` verbatim into its reply — it reads `open_id:name` as the
 * `--mention <open_id:name>` form and leaks `ou_xxx:高鹏` into the `botmux
 * send` body / opening line. Other CLIs haven't shown this, so the guard is
 * scoped to cursor only (claude-code et al. that set injectsSessionContext
 * never see this inline tag anyway). Returns '' for every other CLI and when
 * there is no sender tag to misread.
 */
export declare function renderCursorSenderNote(cliId: CliId | undefined, hasSender: boolean, locale?: Locale): string;
/**
 * Render a buffered follow-up's sender attribution for daemon's pending-repo
 * branch (handleThreadReply), where a cross-user follow-up's `<sender>` tag is
 * prepended OUTSIDE the builder and later folds into the opening
 * `<user_message>`. Pair the tag with the cursor anti-echo note so a folded-in
 * foreign sender gets the same protection the builder gives its own top-level
 * `<sender>`; otherwise an inline `ou_xxx:name` reaches cursor with no adjacent
 * note (the builder's note only covers `ds.pendingSender`'s top-level tag, and
 * may be absent entirely when pendingSender is undefined). Returns '' when
 * there is no sender to attribute.
 */
export declare function renderBufferedSenderBlock(sender: ResolvedSender | undefined, cliId: CliId | undefined, locale?: Locale): string;
export declare function formatAttachmentsHint(attachments?: LarkAttachment[], locale?: Locale): string;
export declare function buildNewTopicPrompt(userMessage: string, sessionId: string, cliId: CliId, cliPathOverride?: string, attachments?: LarkAttachment[], mentions?: LarkMention[], availableBots?: Array<{
    name: string;
    displayName: string;
    openId: string;
}>, followUps?: string[], botIdentity?: {
    name?: string;
    openId?: string;
}, locale?: Locale, sender?: ResolvedSender, opts?: {
    larkAppId?: string;
    chatId?: string;
}): string;
/**
 * Build the content for a follow-up message (thread reply to an active session).
 * Mirrors buildNewTopicPrompt structure but for subsequent messages.
 * Session ID is omitted for adopt mode and CLIs with injectsSessionContext.
 */
export declare function buildFollowUpContent(content: string, sessionId: string, opts?: {
    attachments?: LarkAttachment[];
    mentions?: LarkMention[];
    isAdoptMode?: boolean;
    cliId?: CliId;
    cliPathOverride?: string;
    locale?: Locale;
    sender?: ResolvedSender;
    larkAppId?: string;
    chatId?: string;
}): string;
/**
 * Build raw input content for adopt-bridge mode.
 *
 * Bridge mode injects the user's text into the existing CLI exactly as the
 * local user would type it: NO `<session_id>`, NO `<botmux_reminder>`, NO
 * Skills hint. The model is intentionally unaware of botmux — the daemon
 * harvests final output via the transcript watcher and forwards it to Lark
 * out-of-band.
 *
 * Attachments and @mentions are surfaced as plain prose so the user's intent
 * carries over, but the format avoids any wording that would prompt the
 * model to call `botmux send` / route through botmux tooling.
 */
export declare function buildBridgeInputContent(content: string, opts?: {
    attachments?: LarkAttachment[];
    mentions?: LarkMention[];
    selfMention?: {
        name?: string | null;
        openId?: string | null;
    };
    locale?: Locale;
}): string;
/**
 * Build the prompt that gets piped into a freshly-spawned CLI when an existing
 * (non-bridge) session re-forks its worker. Hits the `worker=null` re-fork
 * branch in handleThreadReply: resume after /close, daemon-restart + new
 * message, and any other path that lands a new turn without a live worker.
 *
 * Without wrapping, the worker would queue the user's raw text as the initial
 * prompt — the CLI sees no `<user_message>` / `<botmux_reminder>` envelope
 * and answers in its own terminal instead of calling `botmux send`.  This
 * helper centralises the wrap so both daemon.ts and tests agree on the shape.
 *
 * Adopt-bridge sessions go through `buildBridgeInputContent` instead — see
 * the buildBridgeInputContent docstring for why bridge prompts intentionally
 * skip botmux routing tags.
 */
export declare function buildReforkPrompt(ds: DaemonSession, content: string, opts?: {
    attachments?: LarkAttachment[];
    mentions?: LarkMention[];
    cliId?: CliId;
    cliPathOverride?: string;
    selfMention?: {
        name?: string | null;
        openId?: string | null;
    };
    locale?: Locale;
    sender?: ResolvedSender;
}): string;
/**
 * Copy current streaming-card fields from `ds` into the persisted Session and save.
 * Lets the existing card be PATCHed on next screen_update after a daemon restart,
 * instead of a fresh card being POSTed.
 */
export declare function persistStreamCardState(ds: DaemonSession): void;
export declare function rememberLastCliInput(ds: DaemonSession, userPrompt: string, cliInput: string): void;
/**
 * Whether daemon restore should eagerly re-fork a worker to re-attach a
 * surviving backing pane. True for every persistent backend (tmux/herdr/zellij);
 * the pty backend has nothing to re-attach to, so it stays lazy.
 *
 * Eager re-attach is what makes a session actually come back after a restart —
 * otherwise a killed worker leaves the session dead until its next message, and
 * a pane whose CLI died in the meantime never gets healed, so the transcript
 * fallback can't fire. The old `BOTMUX_QUIET_RESTART` gate that suppressed this
 * (to avoid re-pushing cards on dev restarts) is gone: restored sessions now
 * carry `suppressRecoveryCard`, so the recovery re-fork stays silent in the
 * Lark thread without having to skip recovery altogether.
 */
export declare function shouldAutoForkOnRestore(backendType: BackendType): boolean;
/**
 * Re-fork the given restored sessions to re-attach their surviving panes, but
 * staggered to avoid a thundering-herd CPU/IO spike when many sessions survive a
 * restart: spawn `batchSize` workers, wait `delayMs`, repeat.
 *
 * Sessions whose worker is already live are skipped — a real message can arrive
 * (the Lark dispatcher is up before restore finishes) and lazily fork the worker
 * during one of our `delayMs` pauses; re-forking it here would kill that live
 * worker mid-turn via `forkWorker`'s double-fork guard.
 */
export declare function staggeredRecoveryFork(sessions: readonly DaemonSession[], fork: (ds: DaemonSession) => void, batchSize?: number, delayMs?: number): Promise<void>;
export declare function restoreActiveSessions(activeSessions: Map<string, DaemonSession>): Promise<void>;
/**
 * Resolve a session's live web-terminal worker port, WAKING the worker on demand
 * if needed.
 *
 * A session can be active with no live worker — a pty session that resumes
 * lazily, or a persistent-backend session whose staggered restart re-fork
 * hasn't reached it yet (or whose worker died since). The terminal
 * reverse-proxy, however, needs the worker's HTTP port to serve `/s/{id}`, so a
 * surviving-but-worker-less session would otherwise 502 ("session not running")
 * even though its tmux/zellij pane is alive. This bridges that gap: if the
 * session is active and its persistent backing pane still exists, re-fork the
 * worker to re-attach (empty prompt = no new turn, same as restart reattach) and
 * wait for it to report its port.
 *
 * Returns the port, or undefined when there's nothing serveable (no live worker
 * possible: not active, non-persistent backend, or the pane is gone). The
 * `forkWorker` double-fork guard plus its synchronous `ds.worker` assignment make
 * concurrent calls (the terminal's HTML GET + WS upgrade arrive together) safe —
 * only the first forks; the rest just await the same `ds.workerPort`.
 */
export declare function ensureTerminalWorkerPort(ds: DaemonSession): Promise<number | undefined>;
/**
 * Reactivate a single closed session — used by the "▶️ 恢复会话" card button
 * and the `botmux resume <id>` CLI command. Mirrors the per-session branch
 * of `restoreActiveSessions` but operates on one record by id and without
 * killing stale pids (the `/close` flow that produced this closed record
 * already killed them).
 *
 * Returns `{ ok: true, ds }` on success; structured error otherwise so callers
 * (HTTP IPC, card handler) can surface a precise message.
 *
 *   - 'not_found'        — sessionId doesn't exist in any session file
 *   - 'not_closed'       — session is still active or in some other state
 *   - 'anchor_occupied'  — another active session already owns this anchor
 *                          (e.g. user kept typing after /close, auto-creating
 *                          a fresh thread session); refuse rather than clobber
 *   - 'adopt_unsupported' — adopt sessions are torn down by /close and have
 *                          no resume semantics
 */
export declare function resumeSession(sessionId: string, activeSessions: Map<string, DaemonSession>): Promise<{
    ok: true;
    ds: DaemonSession;
} | {
    ok: false;
    error: 'not_found' | 'not_closed' | 'anchor_occupied' | 'adopt_unsupported';
    activeSessionId?: string;
}>;
export declare function executeScheduledTask(task: ScheduledTask, activeSessions: Map<string, DaemonSession>, refreshCliVersion: (...args: any[]) => boolean): Promise<void>;
//# sourceMappingURL=session-manager.d.ts.map