/**
 * agent_bridge — long-running daemon (WAB.7 + WAB-SUB.2 lazy api client, 0.5.106).
 *
 * Authoritative spec: `docs/tasks/T-warm-agent-chat-bridge.md` WAB.7 +
 * WAB-SUB.2 §"daemon mode-aware lazy client". Architecture:
 * `docs/tasks/WAB.1-architecture.md` decisions (a)-(g).
 *
 * Wires every WAB.2-WAB.6 component (event bus, transport bridge, session
 * manager, batch coordinator + dispatcher, pack-bound tool dispatcher)
 * into a single long-running process driven by `start()` + `shutdown()`.
 * Owns the daemon-wide singletons:
 *   - one Anthropic SDK client — LAZILY constructed only when the
 *     resolved pack binding is api-mode (WAB-SUB.2 cap on subscription
 *     daemons paying for an unused SDK client + unused ANTHROPIC_API_KEY)
 *   - one PID lock at `~/.opensquid/agent-bridge[-<scope>].lock`
 *   - one PID file at `~/.opensquid/agent-bridge[-<scope>].pid`
 *     (CAT.5.1 — scope = umbrellaId → projectUuid → legacy unscoped, so the
 *      `loop` umbrella + the `general` session run concurrently)
 *   - one inbox watcher per project (the bridge is project-scoped)
 *   - one materialized MCP config at
 *     `~/.opensquid/agent-bridge/mcp-config.json` (subscription mode only)
 *
 * Mode-aware hard-fails (WAB-SUB.2):
 *   - api mode    → missing ANTHROPIC_API_KEY throws "Mode 'api' requires
 *                   ANTHROPIC_API_KEY ..." with setup-chat hint.
 *   - subscription mode → key is NOT required; missing key is silently
 *                         skipped (subscription auth flows through Claude
 *                         Code's own state).
 *   - all modes   → missing pack root / project UUID / PID lock held →
 *                   hard-fail with structured message
 *                   (`feedback_opensquid_runtime_failure_handling`).
 *
 * Shutdown order (spec WAB.7): dispatcher → transport → sessionManager →
 * release lock → delete PID file. The dispatcher's shutdown internally
 * drains the BatchCoordinator + awaits in-flight turns, so listing it
 * first stops new turns AND waits for the current one to finish before
 * we tear down the transport. Idempotent — a second call is a no-op.
 *
 * `@anthropic-ai/sdk` is an OPTIONAL peer dep — dynamic-imported only in
 * the api-mode branch so subscription-mode daemons don't even attempt the
 * import. A subscription pack with NO @anthropic-ai/sdk installed boots
 * cleanly — that's the whole point of mode dispatch.
 *
 * Imports from: node:fs/promises, node:os, node:path, proper-lockfile,
 *   ../paths.js, ../../models/load_config.js, ../../packs/loader.js,
 *   ../../rag/backend_factory.js, ./dispatcher.js, ./event_bus.js,
 *   ./mcp_config.js, ./pack_binding.js, ./session_manager.js,
 *   ./session_persistence.js, ./transport_bridge.js, ./types.js.
 * Imported by: ./cli.ts (the agent-bridge CLI), test sibling.
 */
import type { ModelAliasConfig } from '../../models/types.js';
import type { RagBackend } from '../../rag/types.js';
import type { SecretResolver } from '../../secrets/types.js';
import type { AnthropicMessageClient } from './agent_loop.js';
import { type BuildChatToolDispatcherResult } from './pack_binding.js';
import type { SessionKey } from './types.js';
/** Inputs that select the per-instance scope (umbrella wins over project). */
export interface AgentBridgeScopeKey {
    umbrellaId?: string;
    projectUuid?: string;
}
/**
 * Derive the side-file scope suffix. `umbrellaId` (CAT.5/6) wins; else
 * `projectUuid` (legacy per-project); else `''` (the legacy single-instance
 * back-compat path). Pure + total.
 */
export declare function agentBridgeScope(key?: AgentBridgeScopeKey): string;
export declare const agentBridgeLockPath: (key?: AgentBridgeScopeKey) => string;
export declare const agentBridgePidPath: (key?: AgentBridgeScopeKey) => string;
export declare const agentBridgeLogPath: (key?: AgentBridgeScopeKey) => string;
export interface AgentBridgeDaemonOptions {
    /**
     * The project whose inbox + sessions this daemon serves. REQUIRED for the
     * normal project-scoped path. EMPTY (`''`) is permitted ONLY in project-less
     * mode (T-CHAT-AS-TERMINAL CAT.6 — `umbrellaId === 'general'` or
     * `projectLess: true`), where the umbrella id is the scope identity instead.
     */
    projectUuid: string;
    /**
     * Owning umbrella id (T-CHAT-AS-TERMINAL CAT.5). When set, the daemon runs
     * UMBRELLA-keyed: the transport watches the umbrella inbox, the dispatcher's
     * arbitration reads the umbrella lease (ownership-aware against the headless
     * id `headless-<umbrellaId>`), and a {@link HeadlessLeaseManager} holds the
     * lease while no human session is live (standing down when one appears).
     * Omitted ⇒ legacy per-project keying (no headless lease handoff).
     *
     * When `umbrellaId === 'general'` the daemon is automatically PROJECT-LESS
     * (CAT.6): the umbrella id is the scope identity and `projectUuid` is not
     * required (the general session has no project by design).
     */
    umbrellaId?: string;
    /**
     * Project-LESS mode (T-CHAT-AS-TERMINAL CAT.6). When true, the daemon runs
     * with NO real project: session-state + tool scoping key off the umbrella id
     * (the scope identity) instead of a project uuid, and the `projectUuid`
     * field is not required (defaults to `''`). Implied when
     * `umbrellaId === 'general'`. The normal project-scoped path is unchanged:
     * omit this (or leave it false) and `projectUuid` stays required.
     */
    projectLess?: boolean;
    /** Required — absolute path to the pack folder (contains manifest.yaml + chat_agent.yaml). */
    packRoot: string;
    /**
     * Anthropic API key — defaults to process.env.ANTHROPIC_API_KEY. Only
     * REQUIRED when the resolved pack binding is api-mode; subscription
     * mode skips the check entirely.
     */
    anthropicApiKey?: string;
    /** Override the daemon-home base (tests). Defaults to OPENSQUID_HOME(). */
    daemonHome?: string;
    /** Override the models config (tests). Defaults to `loadModelsConfig()`. */
    modelsConfig?: Record<string, ModelAliasConfig>;
    /** Override the RAG backend (tests). Defaults to libsql-qwen3+lexical fallback. */
    ragBackend?: RagBackend;
    /** Override the secret resolver (tests). Defaults to an env+dotenv backend
     *  rooted at `~/.loop/.env`. The pack_binding's api-mode strategy uses
     *  this to read ANTHROPIC_API_KEY (and analogous keys). */
    secrets?: SecretResolver;
    /** Inject an Anthropic client (tests). Defaults to dynamic-import + construct.
     *  In api-mode only — subscription mode never touches this. */
    anthropicClient?: AnthropicMessageClient;
    /**
     * Optional MCP config path override for subscription mode. Defaults to
     * `OPENSQUID_AGENT_BRIDGE_MCP_CONFIG` env → materialize the daemon's
     * default config at `<daemonHome>/agent-bridge/mcp-config.json`. Tests
     * that want to skip the I/O can pass a stub path here.
     */
    mcpConfigPath?: string;
    /** Structured warn sink. Defaults to stderr write. */
    onWarn?: (message: string) => void;
    /** Reply hook — fired when the chat agent emits a final reply. Defaults to
     *  no-op (production callers wire this to the legacy chat-daemon RPC; for
     *  WAB.7 the on-disk inbox + outbox plumbing lives upstream). */
    onReply?: (key: SessionKey, replyText: string, projectUuid: string) => void;
}
export declare class AgentBridgeDaemon {
    private readonly opts;
    private readonly warn;
    private state;
    private release;
    private transport;
    private dispatcher;
    private sessionManager;
    private bus;
    private headlessLease;
    private signalHandlers;
    private bindingResult;
    constructor(opts: AgentBridgeDaemonOptions);
    /** Acquire PID lock, wire components, install signal handlers, start
     *  watching the inbox. Atomic — any failure rolls back acquired resources. */
    start(): Promise<void>;
    /**
     * Build the dispatcher's mode-aware agentLoopOptions. Lazy api-client
     * construction lives here so subscription-mode daemons never touch
     * `@anthropic-ai/sdk` (nor require ANTHROPIC_API_KEY). Subscription mode
     * resolves the MCP config path so the spawned claude can reach
     * opensquid's MCP servers.
     *
     * Throws on api-mode start if no API key + no injected client. Throws on
     * subscription-mode start if MCP config materialization fails. Both
     * happen during the daemon's start() try/catch which rolls back the
     * lock + binding.
     */
    private buildAgentLoopOptions;
    /** Tear down in spec order. Idempotent — second call is a no-op so signal
     *  handlers + the CLI stop verb can both invoke without coordination. */
    shutdown(): Promise<void>;
    /** This daemon's scope key (umbrella → project) for side-file naming. */
    private scopeKey;
    /** Test/admin: expose the live binding result for assertions. */
    bindingFor(): BuildChatToolDispatcherResult | null;
    /**
     * CAT.6 — true when the daemon runs project-less (no real project; the
     * umbrella id is the scope identity). Project-less is signalled either by the
     * reserved `general` umbrella or an explicit `projectLess: true` opt. Pure
     * function of the options — used by `start()`'s guard + scope-key derivation.
     */
    private isProjectLess;
    /** Shared teardown — used by both shutdown() and start()'s rollback path.
     *  Order: dispatcher (drops batch timers + awaits in-flight turn) →
     *  headlessLease (stop heartbeat + release our hold) → transport (closes
     *  watcher + drains tail reads) → sessionManager (drains LRU, fires
     *  onEvict('shutdown')) → release lock → pidfile. */
    private teardown;
    private installSignalHandlers;
    private uninstallSignalHandlers;
}
/** packRoot: explicit env → `~/.opensquid/packs/default`. Existence checked
 *  at daemon start (loadPack throws on missing manifest). */
export declare function resolvePackRootFromEnv(env?: NodeJS.ProcessEnv): string;
/**
 * CAT.6 packRoot for the project-less general session: the built-in `general`
 * pack (`packs/builtin/general/`). `OPENSQUID_GENERAL_PACK_ROOT` wins for test
 * isolation / an operator override; otherwise it resolves under the built-in
 * scope root (`resolveBuiltinScopeRoot()`).
 */
export declare function resolveGeneralPackRoot(env?: NodeJS.ProcessEnv): string;
//# sourceMappingURL=daemon.d.ts.map