import { EventEmitter } from 'node:events';
import { Readable, Writable } from 'node:stream';
import { createServer } from 'node:http';

type TraceEventType = "session_start" | "user_message" | "llm_request" | "llm_response" | "tool_call" | "permission_decision" | "tool_result" | "code_mode_call" | "compaction" | "guardrail" | "completion_check" | "subagent" | "turn_end" | "outcome" | "session_end";
/** Which surface created the session. */
type TraceSessionMode = "tui" | "print" | "sdk" | "server" | "eval" | "worker";
interface SessionStartPayload {
    autopilot_version: string;
    /** git HEAD of the running Autopilot checkout, or null when not a checkout. */
    harness_commit: string | null;
    cwd: string;
    repo_remote?: string | null;
    repo_head?: string | null;
    model: string;
    mode: TraceSessionMode;
    /** KimiConfig with credential-bearing keys replaced by "<redacted>". */
    config: Record<string, unknown>;
    tool_profile: string;
    /** Active model profile (#999), absent when the model uses the default behavior. */
    model_profile?: string;
    /** sha256 of the JSON array of ToolDef sent to the model. */
    tool_schemas_sha: string;
    /** Blob sha of that same JSON array. */
    tools_blob: string;
    system_prompt_sha: string;
}
type UserMessageSource = "typed" | "deny_feedback" | "continuation" | "eval" | "sdk" | "print" | "server";
interface UserMessagePayload {
    content_blob: string;
    source: UserMessageSource;
    /** Number of attachments (images, files) on the message. */
    attachments: number;
}
interface LlmRequestPayload {
    request_id: string;
    attempt: number;
    model: string;
    /** Request body minus `messages` and `tools`. */
    params: Record<string, unknown>;
    /** Blob sha of the exact serialized body sent over the wire. */
    request_blob: string;
    messages_sha: string;
    tools_sha: string;
    estimated_tokens: number;
    /** Bytes of the serialized body (uploaded in full on every call). */
    body_bytes?: number;
}
interface TraceToolCall {
    id: string;
    name: string;
    arguments: string;
}
interface TraceUsage {
    prompt_tokens: number;
    completion_tokens: number;
    cached_tokens?: number;
    cost?: number;
}
interface LlmResponsePayload {
    request_id: string;
    text_blob: string;
    reasoning_blob?: string;
    reasoning_details?: unknown[];
    tool_calls: TraceToolCall[];
    finish_reason: string | null;
    usage?: TraceUsage;
    generation_id?: string;
    latency_ms: number;
    /** Request start → response headers: includes uploading the body, so slow uploads show up here. */
    headers_ms?: number;
    /** Request start → first streamed token. */
    first_token_ms?: number;
    aborted: boolean;
    error?: string;
}
interface ToolCallPayload {
    tool_call_id: string;
    name: string;
    args_blob: string;
    /** The parsed arguments, inline when their JSON is ≤ 2 KB. */
    args_inline?: Record<string, unknown>;
    needs_permission: boolean;
    code_mode_redirected: boolean;
}
type PermissionDecision$1 = "allow" | "allow_session" | "deny" | "auto";
interface PermissionDecisionPayload {
    tool_call_id: string;
    decision: PermissionDecision$1;
    feedback_text?: string;
    latency_ms: number;
    /** True when the user was actually prompted; false for automatic decisions.
     *  Auto decisions record `latency_ms: 0`, so without this flag a trace cannot
     *  tell "no prompt" apart from "answered instantly" (#968). */
    prompted?: boolean;
}
interface ToolResultPayload {
    tool_call_id: string;
    name: string;
    ok: boolean;
    error_code?: string;
    raw_blob: string;
    raw_bytes: number;
    reduced_blob: string;
    reduced_bytes: number;
    artifact_id?: string;
    exit_code?: number;
    timed_out?: boolean;
    duration_ms: number;
}
interface CodeModeCallPayload {
    parent_tool_call_id: string;
    name: string;
    args_blob: string;
    raw_blob: string;
    ok: boolean;
    duration_ms: number;
}
type CompactionMethod = "compiled" | "llm" | "preflight";
interface CompactionPayload {
    method: CompactionMethod;
    tokens_before: number;
    tokens_after: number;
    turns_removed: number;
    artifacts_archived?: number;
    summary_blob?: string;
}
type GuardrailKind = "loop_recovery" | "loop_stopped" | "limit_ceiling" | "code_mode_redirect_cap"
/** `maxToolIterations` reached and the counter reset automatically. */
 | "limit_reset"
/** `maxToolIterations` reached with `toolLimitBehavior: "stop"`. */
 | "limit_stopped"
/** A PR was opened this turn but its CI was never verified. */
 | "pr_ci_nudge";
interface GuardrailPayload {
    kind: GuardrailKind;
    message: string;
}
interface CompletionCheckPayload {
    probability: number;
    nudged: boolean;
}
interface SubagentPayload {
    worker_id: string;
    mode: string;
    child_session_id?: string;
    status: string;
    cost_usd?: number;
    duration_ms?: number;
}
type TurnEndReason = "complete" | "aborted" | "error" | "guardrail" | "needs_input";
interface TurnEndPayload {
    reason: TurnEndReason;
    /** Tool names in call order. */
    tools_used: string[];
    cost_usd?: number;
    duration_ms: number;
    final_text_blob?: string;
}
type VerificationCategory = "test" | "lint" | "typecheck" | "build";
type NextMessageClassification = "continue" | "correction" | "new_task" | "question" | "other";
type RevertHow = "clear" | "restore_checkpoint" | "git_checkout";
/** Outcome signals a dataset builder can filter or weight on (plan §3.1). */
type OutcomePayload = {
    kind: "git_delta";
    turn_id: string;
    files_changed: number;
    insertions: number;
    deletions: number;
    /** Blob of the raw `git diff --numstat` output after the turn. */
    numstat_blob: string;
    /** Blob of the patch, when it is at most 512 KB. */
    patch_blob?: string;
} | {
    kind: "verification";
    turn_id: string;
    tool_call_id: string;
    command: string;
    category: VerificationCategory;
    exit_code: number;
} | {
    kind: "rating";
    turn_id: string;
    value: "good" | "bad";
    reason?: string;
} | {
    kind: "next_message";
    turn_id: string;
    classification: NextMessageClassification;
    classifier: "regex";
} | {
    kind: "reverted";
    turn_id: string;
    how: RevertHow;
} | {
    kind: "test_verdict";
    task_id: string;
    passed: boolean;
    command: string;
    exit_code: number;
    duration_ms: number;
    log_blob: string;
};
type SessionEndReason = "exit" | "clear" | "restore_checkpoint" | "crash" | "eval_done";
interface SessionEndPayload {
    reason: SessionEndReason;
    total_cost_usd?: number;
}
interface TracePayloads {
    session_start: SessionStartPayload;
    user_message: UserMessagePayload;
    llm_request: LlmRequestPayload;
    llm_response: LlmResponsePayload;
    tool_call: ToolCallPayload;
    permission_decision: PermissionDecisionPayload;
    tool_result: ToolResultPayload;
    code_mode_call: CodeModeCallPayload;
    compaction: CompactionPayload;
    guardrail: GuardrailPayload;
    completion_check: CompletionCheckPayload;
    subagent: SubagentPayload;
    turn_end: TurnEndPayload;
    outcome: OutcomePayload;
    session_end: SessionEndPayload;
}
type PayloadFor<T extends TraceEventType> = TracePayloads[T];

/**
 * Append-only session trace recorder.
 *
 * `createRecorder({ enabled, sessionId, root })` returns a recorder that is
 * a no-op unless `enabled` is true. When enabled it appends one JSON line
 * per event to `<sessionTraceDir>/trace.jsonl` with `appendFileSync` and
 * stores large bodies in a content-addressed blob store (blobs.ts).
 *
 * Guarantees:
 *   - Never throws. The first disk error is logged once via
 *     `logger.warn("trace:write_failed")` and the recorder disables itself
 *     for the rest of the session.
 *   - Nothing is created on disk until the first event or blob, so a
 *     disabled recorder (or an enabled one that never emits) leaves no trace
 *     directory behind.
 *   - Every string that hits disk goes through `redactLight` first.
 *   - A session stops recording (with one warning) once it has written
 *     `RETENTION.traceMaxSessionBytes`.
 *   - Reopening a session that already has a trace.jsonl (resume,
 *     `--continue`, checkpoint restore) appends: `seq` continues from the
 *     number of lines already on disk.
 */

interface TraceRecorder {
    readonly enabled: boolean;
    /** Session id this recorder writes under. */
    readonly sessionId: string;
    /** Absolute session trace directory, or null when disabled. */
    readonly dir: string | null;
    /**
     * Append one event. `turnId` defaults to the ambient turn id from the
     * structured logger (`getLogTurnId()`), or null when there is none.
     */
    emit<T extends TraceEventType>(type: T, payload: PayloadFor<T>, turnId?: string | null): void;
    /** Store `content` (redacted) in the blob store. Returns its sha, or null when disabled. */
    blob(content: string): string | null;
    /** Resolve once every pending write is on disk. Writes are synchronous, so this is immediate. */
    flush(): Promise<void>;
}

type Mode = "edit" | "plan" | "auto";

type Role = "system" | "user" | "assistant" | "tool";
interface ToolCall {
    id: string;
    type: "function";
    function: {
        name: string;
        arguments: string;
    };
    /**
     * Provider-specific data attached to the call that must be sent back with
     * it — Gemini's `{ google: { thought_signature } }`. Kept only for the
     * provider that produced it (see stripReasoningEcho).
     */
    extra_content?: Record<string, unknown>;
}
interface TextContentPart {
    type: "text";
    text: string;
}
interface ImageContentPart {
    type: "image_url";
    image_url: {
        url: string;
    };
}
type ContentPart = TextContentPart | ImageContentPart;
/**
 * One structured reasoning block as OpenRouter reports it
 * (`reasoning.text` / `reasoning.summary` / `reasoning.encrypted` / …).
 * Opaque to us: stored as received and echoed back unchanged.
 */
interface ReasoningDetail {
    type: string;
    index?: number;
    id?: string | null;
    format?: string;
    text?: string | null;
    summary?: string;
    data?: string;
    signature?: string | null;
    [key: string]: unknown;
}
interface ChatMessage {
    role: Role;
    content: string | ContentPart[] | null;
    reasoning_content?: string | null;
    /** Structured reasoning to echo back to OpenRouter (Claude, GPT-5, Gemini). */
    reasoning_details?: ReasoningDetail[];
    tool_calls?: ToolCall[];
    tool_call_id?: string;
    name?: string;
}

declare class LspConnection extends EventEmitter {
    private child;
    private nextId;
    private pending;
    private buffer;
    private closed;
    private readonly requestTimeoutMs;
    constructor(requestTimeoutMs?: number);
    start(command: string[], env?: Record<string, string>, spawnTimeoutMs?: number): Promise<void>;
    request(method: string, params?: unknown, signal?: AbortSignal): Promise<unknown>;
    notify(method: string, params?: unknown): void;
    kill(): void;
    private send;
    private processBuffer;
}

/** Position in a document (0-based). */
interface Position {
    line: number;
    character: number;
}
/** Range in a document. */
interface Range {
    start: Position;
    end: Position;
}
/** Location (file + range). */
interface Location {
    uri: string;
    range: Range;
}
/** Hover result. */
interface Hover {
    contents: string | {
        language: string;
        value: string;
    } | Array<string | {
        language: string;
        value: string;
    }>;
    range?: Range;
}
/** Document symbol (simplified). */
interface DocumentSymbol {
    name: string;
    detail?: string;
    kind: number;
    range: Range;
    selectionRange: Range;
    children?: DocumentSymbol[];
}
/** Workspace symbol (simplified). */
interface WorkspaceSymbol {
    name: string;
    kind: number;
    location: Location;
    containerName?: string;
}
/** Diagnostic (simplified). */
interface Diagnostic {
    range: Range;
    severity?: number;
    code?: string | number;
    source?: string;
    message: string;
}
/** Text edit for workspace edits. */
interface TextEdit {
    range: Range;
    newText: string;
}
/** Workspace edit result. */
interface WorkspaceEdit {
    changes?: Record<string, TextEdit[]>;
    documentChanges?: unknown[];
}
/** Code action (simplified). */
interface CodeAction {
    title: string;
    kind?: string;
    edit?: WorkspaceEdit;
    command?: unknown;
    diagnostics?: Diagnostic[];
}

declare class LspClient {
    private connection;
    private rootUri;
    private serverCapabilities;
    private openDocuments;
    private diagnosticsCache;
    private initialized;
    private initializing;
    constructor(connection: LspConnection, rootUri: string);
    initialize(signal?: AbortSignal): Promise<void>;
    shutdown(signal?: AbortSignal): Promise<void>;
    getCapabilities(): Record<string, unknown>;
    didOpen(path: string, languageId: string, content: string): void;
    didClose(path: string): void;
    didChange(path: string, content: string): void;
    getDiagnostics(path: string): Diagnostic[];
    hover(path: string, position: Position, signal?: AbortSignal): Promise<Hover | null>;
    definition(path: string, position: Position, signal?: AbortSignal): Promise<Location | Location[] | null>;
    references(path: string, position: Position, signal?: AbortSignal): Promise<Location[] | null>;
    documentSymbols(path: string, signal?: AbortSignal): Promise<DocumentSymbol[] | null>;
    workspaceSymbol(query: string, signal?: AbortSignal): Promise<WorkspaceSymbol[] | null>;
    rename(path: string, position: Position, newName: string, signal?: AbortSignal): Promise<WorkspaceEdit | null>;
    codeAction(path: string, range: {
        start: Position;
        end: Position;
    }, signal?: AbortSignal): Promise<CodeAction[] | null>;
    implementation(path: string, position: Position, signal?: AbortSignal): Promise<Location | Location[] | null>;
    typeDefinition(path: string, position: Position, signal?: AbortSignal): Promise<Location | Location[] | null>;
    listOpenDocuments(): string[];
}

/**
 * Provider presets: everything Autopilot needs to know about an LLM provider
 * in one row — where to send requests, which key to use, which models to
 * default to, which request-shape quirks apply, and which OpenRouter-only
 * features it can support.
 *
 * Every model call (chat, side calls, embeddings), the catalog, cost
 * tracking, onboarding and feature gating read the *active* provider
 * (see resolve.ts). Nothing outside src/providers/ and the key-management
 * surfaces should branch on OpenRouter directly; that rule is enforced by
 * src/providers/openrouter-guard.test.ts.
 *
 * Model ids, base URLs and env names were checked against models.dev
 * (https://models.dev/api.json) on 2026-10-20.
 */
declare const PROVIDER_IDS: readonly ["openrouter", "requesty", "custom", "openai", "deepseek", "moonshot", "kimi-code", "anthropic", "gemini", "minimax", "copilot", "xai", "mistral", "groq", "cerebras", "together", "fireworks", "deepinfra", "vercel", "cloudflare", "azure", "chatgpt", "vertex", "bedrock", "zai", "zai-coding", "ollama", "lmstudio"];
type ProviderId = (typeof PROVIDER_IDS)[number];

/**
 * Resolve the active provider from config and environment. This is the only
 * place that decides where model calls go; llm-auth.ts turns the result into
 * request credentials.
 *
 * Precedence:
 *   1. AUTOPILOT_PROVIDER env (an explicit override; with AUTOPILOT_BASE_URL also
 *      set, that URL replaces the preset's base URL, e.g. a gateway in front of it)
 *   2. KIMIFLARE_BASE_URL env (a host app embedding Autopilot owns routing)
 *   3. `provider` in config
 *   4. legacy inference for configs written before `provider` existed:
 *      `baseUrl` → custom, OpenRouter key → openrouter, Requesty key → requesty
 *   5. openrouter (onboarding will ask for a key)
 */

/** Per-provider settings persisted under `providers.<id>` in config.json. */
interface ProviderSettings {
    apiKey?: string;
    /** Override the preset's base URL (e.g. Ollama on another host, a regional endpoint). */
    baseUrl?: string;
    /** Last chat model used with this provider; restored when switching back. */
    model?: string;
    /** OAuth sign-ins (ChatGPT): refresh token, access-token expiry (epoch ms), issued client id, host id. */
    refreshToken?: string;
    expiresAt?: number;
    clientId?: string;
    hostId?: string;
    /** Google Vertex: project and location (also from GOOGLE_VERTEX_PROJECT / GOOGLE_VERTEX_LOCATION). */
    project?: string;
    location?: string;
    /** Amazon Bedrock region (also from AWS_REGION / AWS_DEFAULT_REGION). */
    region?: string;
}

/**
 * Custom OpenAI-compatible endpoint routing.
 *
 * When `KIMIFLARE_BASE_URL` is set (or `baseUrl` in config), every model call
 * — chat and embeddings — is sent to `<baseUrl>/chat/completions` (or
 * `/embeddings`) with `Authorization: Bearer <KIMIFLARE_API_KEY>` instead of
 * OpenRouter, and no OpenRouter key is needed.
 *
 * This is how a host application (e.g. an agents platform running kimiflare
 * inside a container) points the CLI at its own gateway/broker instead of
 * handing the process a raw provider key. The broker terminates the bearer it
 * issued and forwards upstream with credentials that never enter this process.
 *
 * Precedence (per field): env var > config field. Model ids pass through to
 * the body unchanged — no OpenRouter id-shape validation.
 */

interface CustomEndpoint {
    /** OpenAI-compatible base URL. `/chat/completions` is appended unless the URL already ends with it. */
    baseUrl: string;
    /** Bearer sent as `Authorization` to `baseUrl`. Omitted entirely when unset (unauthenticated local gateways). */
    apiKey?: string;
    /** The provider preset this endpoint belongs to (its quirks, defaults and name). Unset means a host-app gateway. */
    providerId?: ProviderId;
}

/**
 * The one place that turns config into model-call credentials.
 *
 * Every `runKimi` / `fetchEmbeddings` call site spreads `llmAuthFromConfig(cfg)`
 * into its options instead of picking individual credential fields, so the
 * main loop, side-calls (summaries, memory, decomposition, …), embeddings,
 * print/emit/serve/SDK entry points all authenticate the same way — and all
 * of them follow the active provider (src/providers/resolve.ts). Only the
 * active provider's credentials are returned: with provider "openai", the
 * OpenRouter key is never handed to a call site even when it is configured.
 */

interface LlmAuth {
    /** The active provider. Absent only on hand-built auth objects (tests); treated as inferred from the keys. */
    providerId?: ProviderId;
    /** The user's OpenRouter key; set only when OpenRouter is the active provider. */
    openrouterApiKey?: string;
    /** The user's Requesty key; set only when Requesty is the active provider. */
    requestyApiKey?: string;
    /** OpenAI-compatible endpoint for every other provider (presets and host-app gateways). */
    customEndpoint?: CustomEndpoint;
    /** Extra OpenRouter routing preferences (config: openrouterProvider). OpenRouter only. */
    openrouterRouting?: OpenRouterProviderPrefs;
}

/**
 * OpenRouter `provider` routing object (subset we pass through). See
 * https://openrouter.ai/docs/guides/routing/provider-selection. Setting
 * `order` or `sort` disables sticky routing (prompt-cache locality) and
 * load balancing, so they're opt-in only.
 */
interface OpenRouterProviderPrefs {
    order?: string[];
    only?: string[];
    ignore?: string[];
    allow_fallbacks?: boolean;
    require_parameters?: boolean;
    data_collection?: "allow" | "deny";
    zdr?: boolean;
    quantizations?: string[];
    sort?: "price" | "throughput" | "latency";
    max_price?: Record<string, number>;
}

type SubagentPolicy = "off" | "suggest" | "auto";

type ReasoningEffort = "low" | "medium" | "high";
interface McpServerConfig {
    type: "local" | "remote";
    command?: string[];
    url?: string;
    env?: Record<string, string>;
    headers?: Record<string, string>;
    enabled?: boolean;
    /** Per-call timeout in milliseconds for tool invocations on this server. Default: 60000. */
    timeoutMs?: number;
}
interface LspServerConfig {
    command: string[];
    env?: Record<string, string>;
    enabled?: boolean;
    rootPatterns?: string[];
    /** Per-request timeout in milliseconds for LSP calls. Default: 10000. */
    timeoutMs?: number;
    /** Max auto-restart attempts after a crash. Default: 3. Set 0 to disable. */
    maxRestartAttempts?: number;
}
/** Permission rule: allow, deny, or ask (default). */
type PermissionRule = "allow" | "deny" | "ask";
/** Per-tool permission rules keyed by glob pattern. */
type PermissionRules = Record<string, PermissionRule>;
interface TraceConfig {
    enabled: boolean;
    root?: string;
}
interface KimiConfig {
    /**
     * The active LLM provider (env: AUTOPILOT_PROVIDER). Unset on configs
     * written before multi-provider support: the provider is then inferred
     * from which credential is present (see src/providers/resolve.ts).
     */
    provider?: ProviderId;
    /** Per-provider keys, base URL overrides and last-used models, keyed by provider id. */
    providers?: Partial<Record<ProviderId, ProviderSettings>>;
    /**
     * The user's own OpenRouter API key (`sk-or-…`; env: OPENROUTER_API_KEY or
     * KIMIFLARE_OPENROUTER_KEY, which win over the file). Every model call —
     * chat, memory embeddings, plumbing side-calls — is billed to this key.
     * Bring-your-own only: kimiflare never pays for or proxies model calls.
     */
    openrouterApiKey?: string;
    /**
     * Optional Requesty API key (env: REQUESTY_API_KEY, which wins over the
     * file). Used only when no OpenRouter key and no custom endpoint are
     * configured: model calls then go to Requesty (https://requesty.ai). Set
     * REQUESTY_BASE_URL to pick a region, e.g. https://router.eu.requesty.ai/v1.
     */
    requestyApiKey?: string;
    /** OpenRouter model id, e.g. "moonshotai/kimi-k2.6". */
    model: string;
    /**
     * Optional OpenRouter provider-routing preferences, merged into every
     * request's `provider` object (kimiflare always sends
     * `require_parameters: true`). E.g. `{ "ignore": ["SomeProvider"] }` or
     * `{ "quantizations": ["fp8", "bf16"] }`. Avoid `order`/`sort` unless you
     * need them: they disable OpenRouter's sticky routing, which is what keeps
     * the prompt cache warm. See https://openrouter.ai/docs/guides/routing/provider-selection
     */
    openrouterProvider?: OpenRouterProviderPrefs;
    /**
     * Custom OpenAI-compatible endpoint base URL (env: KIMIFLARE_BASE_URL).
     * For a host application that embeds kimiflare and owns its own broker:
     * when set, ALL model calls go to `<baseUrl>/chat/completions` instead of
     * OpenRouter and no OpenRouter key is needed. See src/agent/custom-endpoint.ts.
     */
    baseUrl?: string;
    /**
     * Bearer sent as `Authorization` to `baseUrl` (env: KIMIFLARE_API_KEY).
     * Only used when `baseUrl` is set; omitted from the request entirely when
     * unset (for unauthenticated local gateways).
     */
    apiKey?: string;
    /**
     * Cloudflare account id + API token (env: CLOUDFLARE_ACCOUNT_ID /
     * CLOUDFLARE_API_TOKEN). Not used for model calls. Remote session
     * deployment uses these credentials to provision infrastructure.
     */
    accountId?: string;
    apiToken?: string;
    reasoningEffort?: ReasoningEffort;
    /** Harness subagent routing: off, auto (default; coordinator assesses substantial tasks), or suggest. Worker calls remain permission-gated and budget-limited. */
    subagentPolicy?: SubagentPolicy;
    /** Set only when the user explicitly chose a policy; used to migrate the former implicit suggest default. */
    subagentPolicyExplicit?: boolean;
    /**
     * Feature flag for the plan / edit / auto permission modes. Off by default:
     * every session runs in auto (tools run without per-call prompts) and the
     * mode UI (Shift+Tab, /mode, /plan, /edit, /auto, the mode badge) is hidden.
     * Turn on with `/settings modes on` to get the mode system back, starting
     * in edit.
     */
    modesEnabled?: boolean;
    coauthor?: boolean;
    coauthorName?: string;
    coauthorEmail?: string;
    mcpServers?: Record<string, McpServerConfig>;
    cacheStablePrompts?: boolean;
    /** Enable compiled context (token-optimized state packet + artifact store). */
    compiledContext?: boolean;
    /** Number of recent user turns to retain image content; older images are dropped. */
    imageHistoryTurns?: number;
    /** Attached images are downscaled to this long edge (px) and re-encoded
     *  before upload. 0 sends images unchanged. Default 1568. */
    imageMaxEdge?: number;
    /** Email the operator about unattended runs (needs RESEND_API_KEY). Env:
     *  AUTOPILOT_NOTIFY_EMAIL_TO / AUTOPILOT_NOTIFY_EMAIL_FROM override. */
    runNotifications?: {
        email?: {
            to?: string | string[];
            from?: string;
            events?: string[];
        };
    };
    /** Cap on unattended runs doing work at once (`autopilot serve`). Default 4; env AUTOPILOT_MAX_CONCURRENT_RUNS. */
    maxConcurrentRuns?: number;
    /** JPEG quality (1–100) for re-encoded images. Default 75. */
    imageQuality?: number;
    /** Hard safety ceiling on tool iterations per turn, across automatic
     *  200-call counter resets. Defaults to 1000. */
    maxTotalToolIterations?: number;
    /** Enable local structured memory (SQLite + embeddings). */
    memoryEnabled?: boolean;
    /** Path to memory database. Defaults to .kimiflare/memory.db in repo root, or ~/.local/share/kimiflare/memory.db. */
    memoryDbPath?: string;
    /** Max age of memories in days before cleanup. Default: 90. */
    memoryMaxAgeDays?: number;
    /** Max memories per repo. Default: 1000. */
    memoryMaxEntries?: number;
    /** Embedding model for memory and skill vectors. Default: the embedding provider's preset model. */
    memoryEmbeddingModel?: string;
    /**
     * Provider for memory/skill embeddings when it should differ from the chat
     * provider (env: AUTOPILOT_EMBEDDING_PROVIDER) — e.g. keep an OpenAI key
     * for embeddings while chatting with Anthropic or DeepSeek, which have no
     * embeddings API. Unset: the active provider.
     */
    embeddingProvider?: ProviderId;
    /** Model for internal plumbing tasks (memory verification, hypothetical queries). Default: DEFAULT_PLUMBING_MODEL. */
    plumbingModel?: string;
    /** Model for auto-extracting high-signal edit events. Default: DEFAULT_PLUMBING_MODEL. */
    memoryExtractionModel?: string;
    /** Enable Code Mode: present tools as a TypeScript API and execute generated code in a sandbox. */
    codeMode?: boolean;
    /** End-of-turn "did the agent finish?" check via Jev (OpenRouter only). Default true. */
    completionCheck?: boolean;
    /** Enable LSP integration. Default: false. */
    lspEnabled?: boolean;
    /** LSP server configurations. */
    lspServers?: Record<string, LspServerConfig>;
    /** Enable cost attribution by task type. Default: true. */
    costAttribution?: boolean;
    /** Enable @ file mention picker in chat input. Default: true. */
    filePicker?: boolean;
    /** URL of the remote orchestrator Worker. */
    remoteWorkerUrl?: string;
    /** Shared secret for authenticating with the remote Worker. */
    remoteAuthSecret?: string;
    /** Configurable TTL for remote sessions in minutes (default: 30). */
    remoteTtlMinutes?: number;
    /** Max input token budget per remote job (default: 5_000_000). */
    remoteMaxInputTokens?: number;
    /** GitHub OAuth token for remote PR creation. */
    githubOAuthToken?: string;
    /** GitHub refresh token (if available). */
    githubRefreshToken?: string;
    /** GitHub token expiry timestamp. */
    githubTokenExpiry?: number;
    /** Default GitHub repo for remote sessions (owner/repo). */
    githubRepo?: string;
    /** Shell override for the bash tool. "auto" (default) detects the platform, or specify "bash", "cmd", "powershell", or an absolute path. */
    shell?: string;
    /** Max workers to spawn in parallel (default: 3). */
    workerMaxParallel?: number;
    /** Timeout per worker in milliseconds (default: 600000 = 10 min). */
    workerTimeoutMs?: number;
    /** Turn count at which KimiFlare suggests /fresh in auto/edit mode. 0 = disabled. Default: 30. */
    autoFreshSuggestionTurns?: number;
    /** If true, automatically execute /fresh when the threshold is hit instead of just suggesting it. Default: false. */
    autoFreshEnabled?: boolean;
    /** Estimated in-memory token threshold to trigger aggressive auto-compaction between turns. Default: 500_000. */
    autoCompactTokenThreshold?: number;
    /** Optional absolute cap for the budget-relative compaction thresholds (cost
     *  control). Off by default: thresholds scale with the model's context window. */
    autoCompactCapTokens?: number;
    /** Estimated in-memory token threshold to trigger auto-fresh when compaction cannot reduce below this. Default: 2_000_000. */
    autoFreshTokenThreshold?: number;
    /** Permission rules for headless/CI mode. Keys are tool names (e.g. "bash", "write").
     *  Values are glob-pattern → rule mappings. Patterns are matched against the
     *  target path (for file tools) or command string (for bash). */
    permissions?: Record<string, PermissionRules>;
    /** Prefer creating pull requests over pushing directly to the default branch.
     *  Injected into the system prompt. Default: true. */
    preferPullRequests?: boolean;
    /** Re-inject the live `tasks_set` plan on every request so it survives
     *  compaction and the model does not re-plan. Default: true. */
    planReminder?: boolean;
    /** Allow the bash tool to run `git push` directly to the repository's default
     *  branch. When false (default), such pushes are blocked and the model is
     *  directed to open a PR instead. */
    allowDirectPush?: boolean;
    /** Append a compact diagnostics block to successful edit/write results.
     *  Uses the language server when one is running, else a fast syntax check.
     *  Default: true. */
    postEditDiagnostics?: boolean;
    /** Include warnings (not just errors) in the post-edit diagnostics block.
     *  Default: false. */
    postEditDiagnosticsWarnings?: boolean;
    /**
     * Local session tracing (docs/plans/trace-and-dataset-dev-plan.md). Off by
     * default; writes `trace.jsonl` + blobs per session under `root`
     * (default `${XDG_DATA_HOME:-~/.local/share}/kimiflare/traces`).
     * Env: KIMIFLARE_TRACE=1|0, KIMIFLARE_TRACE_DIR=<path>.
     */
    trace?: TraceConfig;
}
/** OpenRouter's default chat model (each provider's defaults live in src/providers/presets.ts). */
declare const DEFAULT_MODEL: string;
declare const DEFAULT_REASONING_EFFORT: ReasoningEffort;
declare function loadConfig(): Promise<KimiConfig | null>;
declare function saveConfig(cfg: KimiConfig): Promise<string>;

interface LspServerStatus {
    id: string;
    rootUri: string;
    state: "starting" | "running" | "crashed";
    pid?: number;
    toolCount: number;
    restartAttempts: number;
}
interface LspManagerHooks {
    /** Sleep for the given number of ms. Overridable for tests. */
    sleep?: (ms: number) => Promise<void>;
    /** Called when an unexpected exit triggers (or fails to trigger) a restart. */
    onRestart?: (info: {
        id: string;
        rootPath: string;
        attempt: number;
        delayMs: number;
    }) => void;
    onRestartGaveUp?: (info: {
        id: string;
        rootPath: string;
        attempts: number;
        reason: string;
    }) => void;
    /** Connection factory override for tests. */
    connectionFactory?: (timeoutMs: number) => LspConnection;
    /** Client factory override for tests. */
    clientFactory?: (connection: LspConnection, rootUri: string) => LspClient;
}
declare class LspManager {
    private servers;
    private readonly hooks;
    constructor(hooks?: LspManagerHooks);
    private key;
    private makeConnection;
    private makeClient;
    startServer(id: string, config: LspServerConfig, rootPath: string): Promise<void>;
    private wireExitHandler;
    private scheduleRestart;
    stopServer(id: string, rootPath: string): Promise<void>;
    stopAll(): Promise<void>;
    getClient(id: string, rootPath: string): LspClient | undefined;
    /** Find the first running client for a given server ID, regardless of root. */
    findClient(id: string): LspClient | undefined;
    /** Auto-detect which server ID to use for a given file path. */
    resolveClientForPath(filePath: string): {
        id: string;
        client: LspClient;
    } | undefined;
    listActive(): LspServerStatus[];
    notifyChange(path: string, content: string): void;
    private estimateToolCount;
}

type MemoryCategory = "fact" | "event" | "instruction" | "task" | "preference";
interface Memory {
    id: string;
    content: string;
    embedding: Float32Array;
    category: MemoryCategory;
    sourceSessionId: string;
    repoPath: string;
    createdAt: number;
    accessedAt: number;
    importance: number;
    relatedFiles: string[];
    topicKey: string | null;
    supersededBy: string | null;
    forgotten: boolean;
    vectorized: boolean;
    /**
     * Embedding model that produced `embedding`. Vectors are only compared with
     * vectors from the same model; null for rows with no real vector (plans and
     * other topic-key-only rows, or rows written before models were recorded
     * and not recognisable by size).
     */
    embeddingModel: string | null;
    /** Agent role that created this memory (plan, build, general). */
    agentRole: string | null;
}
interface MemoryQuery {
    text: string;
    embedding?: Float32Array;
    repoPath?: string;
    category?: MemoryCategory;
    limit?: number;
    maxAgeDays?: number;
    /** Filter recalled memories by the agent role that created them. */
    agentRole?: string;
    /** Model that produced `embedding`; only rows from the same model are vector-compared. */
    embeddingModel?: string;
}
interface HybridResult {
    memory: Memory;
    ftsScore: number;
    vectorScore: number;
    exactScore: number;
    topicKeyScore: number;
    combinedScore: number;
}
interface MemoryStats {
    totalCount: number;
    dbSizeBytes: number;
    lastCleanupAt: number | null;
    byCategory: Record<MemoryCategory, number>;
}

interface MemoryManagerOpts extends LlmAuth {
    dbPath: string;
    model?: string;
    plumbingModel?: string;
    extractionModel?: string;
    /** Override for the embedding model; defaults to the embedding provider's preset model. */
    embeddingModel?: string;
    /**
     * Credentials for embeddings when they come from a different provider than
     * chat (config `embeddingProvider`). Defaults to the chat credentials.
     */
    embeddingAuth?: LlmAuth;
    maxAgeDays?: number;
    maxEntries?: number;
    redactSecrets?: boolean;
}
interface LlmOpts extends LlmAuth {
    model: string;
    signal?: AbortSignal;
}
declare class MemoryManager {
    private db;
    private opts;
    constructor(opts: MemoryManagerOpts);
    open(): void;
    close(): void;
    isOpen(): boolean;
    private get llmOpts();
    private get plumbingLlmOpts();
    private get extractionLlmOpts();
    /** Expose extraction LLM opts so the agent loop can pass them to extractors. */
    getExtractionLlmOpts(): LlmOpts;
    private shouldRedact;
    /** Resolve a stable repo identity so subdirectories, worktrees, and clones
     *  share one memory namespace instead of fragmenting by absolute cwd. */
    private repo;
    /**
     * Store a memory with verification, topic-key normalization, hypothetical queries,
     * secret redaction, and supersession.
     */
    remember(content: string, category: MemoryCategory, importance: number, repoPath: string, sessionId: string, signal?: AbortSignal, agentRole?: string, topicKey?: string, relatedFiles?: string[]): Promise<{
        id: string;
        superseded?: string[];
    }>;
    /**
     * Store a plan directly under a deterministic topic key.
     * Skips embedding, verification, and hypothetical queries so it is fast and
     * deterministic. Supersedes any previous plan stored under the same key.
     */
    rememberPlan(plan: string, repoPath: string, sessionId: string, topicKey?: string): Promise<{
        id: string;
        superseded?: string[];
    }>;
    /**
     * Recall the latest memory for an exact topic key.
     * Does not use embeddings; returns null if no matching memory exists.
     */
    getByTopicKey(repoPath: string, topicKey: string): Memory | null;
    /**
     * Count high-signal memories created since the given timestamp.
     * Used for KIMI.md drift detection (Trigger A: session-start check).
     */
    countHighSignalMemoriesSince(repoPath: string, since: number): number;
    /**
     * Get the timestamp of the most recent KIMI.md refresh memory.
     * Returns 0 if none exists.
     */
    getLastKimiMdRefreshTime(repoPath: string): number;
    /**
     * Record that KIMI.md was refreshed. Creates a lightweight memory
     * so drift detection knows when the snapshot was last updated.
     */
    recordKimiMdRefresh(repoPath: string, sessionId: string): Promise<void>;
    /**
     * Recall memories using the full hybrid retrieval pipeline.
     */
    recall(query: MemoryQuery): Promise<HybridResult[]>;
    /**
     * Recall memories created by a specific agent role.
     */
    recallByRole(query: MemoryQuery, agentRole: string): Promise<HybridResult[]>;
    /**
     * Format recalled memories as a compact context block for injection into messages.
     */
    static formatRecalled(results: HybridResult[]): string;
    /**
     * Synthesize recalled memories into a dense prose paragraph.
     * Uses the lightweight plumbing model (Scout) to keep costs low.
     */
    synthesizeRecalled(results: HybridResult[], signal?: AbortSignal): Promise<string>;
    /**
     * Soft-delete a memory by ID.
     */
    forget(id: string): Promise<boolean>;
    /**
     * Embed memories that have no vector yet, or whose vector came from a
     * different embedding model (the provider or model changed). Runs at
     * startup in batches of up to `limit`, so a large database is re-indexed
     * over a few sessions instead of all at once.
     */
    backfill(repoPath: string, limit?: number, signal?: AbortSignal): Promise<number>;
    /** The embedding model in use, or null when the provider has none. */
    get embeddingModel(): string | null;
    /** Vector-recall status for `/memory`: model, provider, and how many rows still need (re-)embedding. */
    embeddingStatus(repoPath: string): {
        model: string | null;
        provider: string;
        current: number;
        stale: number;
    };
    private get embedAuth();
    private embedOne;
    cleanup(repoPath: string): Promise<{
        oldDeleted: number;
        excessDeleted: number;
        duplicatesMerged: number;
    }>;
    getStats(): MemoryStats | null;
    clearRepo(repoPath: string): number;
    private verifyMemory;
    private normalizeTopicKey;
    private generateHypotheticalQueries;
}

interface PlanOption {
    label: string;
    plan: string;
}
interface ToolContext {
    cwd: string;
    signal?: AbortSignal;
    onTasks?: (tasks: Task[]) => void;
    onPlanOptions?: (options: PlanOption[]) => void;
    coauthor?: {
        name: string;
        email: string;
    };
    memoryManager?: MemoryManager | null;
    sessionId?: string;
    githubToken?: string;
    /** Shell override for the bash tool. If omitted, the tool auto-detects based on platform. */
    shell?: string;
    /**
     * Intent tier classified for this turn, when known. Carried into
     * hook payloads (PreToolUse / PostToolUse) so user hooks can branch
     * on tier — e.g. skip auto-format for light-tier turns, or audit
     * every heavy-tier action. Optional because code-mode sub-calls
     * and SDK consumers may not have a tier.
     */
    intentTier?: "light" | "medium" | "heavy";
    /** Credentials for tools that need to call an LLM (see llm-auth.ts). */
    llmAuth?: LlmAuth;
    /** Model id for tools that need to call an LLM. */
    model?: string;
    /** Optional override for the durable managed-jobs database (primarily for embedding/tests). */
    jobsDbPath?: string;
    /** Run identifier for durable tool-boundary journaling in unattended runs. */
    runId?: string;
    /** Notifies the host that a durable run budget paused execution. */
    onRunBudgetExceeded?: (reason: string) => void;
    /** Optional override for the durable run/event/timer database. */
    runsDbPath?: string;
    /** When false (default), the bash tool blocks `git push` to the repository's
     *  default branch and directs the model to open a PR instead. */
    allowDirectPush?: boolean;
    /** When true (default), the bash tool also blocks commit-creating git
     *  commands (`commit`, `merge`, `rebase`, `cherry-pick`, `revert`) and
     *  `reset --hard` on the default branch, directing the model to branch first. */
    preferPullRequests?: boolean;
    /** Host language server manager, when one is running. Used to append a
     *  diagnostics block to edit/write results. */
    lspManager?: LspManager;
    /** When false, edit/write results omit the post-edit diagnostics block. */
    postEditDiagnostics?: boolean;
    /** Include warnings (not just errors) in the post-edit diagnostics block. */
    postEditDiagnosticsWarnings?: boolean;
    /** Session trace recorder for this call (set by the agent loop). Tools and
     *  the executor emit on it; absent or disabled means no-op. */
    trace?: TraceRecorder;
    /** Turn id stamped on trace events for this call. */
    turnId?: string | null;
    /** Set when the call is an `api.*` invocation from inside an `execute_code`
     *  sandbox: the id of the parent `execute_code` tool call. The executor then
     *  records a `code_mode_call` instead of a top-level tool_call/tool_result. */
    codeModeParentToolCallId?: string;
    /** Set by the executor when this call's ToolSpec.permissionReason asked
     *  for permission and the call was approved: the reason approved. Lets a
     *  tool tell an approved sensitive call from one that only became
     *  sensitive after the check (e.g. a hostname that re-resolved). */
    approvedSensitiveReason?: string;
    /** Per-session sha256 snapshots recorded by read and checked by edit/write. */
    fileReadHashes?: Map<string, string>;
}
interface ToolRender {
    title: string;
    body?: string;
    diff?: {
        path: string;
        before: string;
        after: string;
    };
}
interface RunWaitRequest {
    runId: string;
    timerId: string;
    condition: "time" | "job";
    jobId?: string;
    wakeAt: number;
}
interface ToolOutput {
    content: string;
    rawBytes: number;
    reducedBytes: number;
    /** Internal control signal: persist the wait and end this agent turn. */
    waitRequest?: RunWaitRequest;
    /** Structured facts about the execution (bash: exit code, timeout), for the trace. */
    meta?: ToolOutputMeta;
}
interface ToolOutputMeta {
    exitCode?: number | null;
    timedOut?: boolean;
    /** bash: killed because the turn was aborted. */
    aborted?: boolean;
    /** bash: the raw streams, kept apart for PostToolUse hooks (not traced). */
    stdout?: string;
    stderr?: string;
}
interface ToolSpec<Args = any> {
    name: string;
    description: string;
    parameters: Record<string, unknown>;
    needsPermission: boolean;
    /** For a tool that normally runs without a prompt: a reason this
     *  particular call needs the user's permission (e.g. it reads a
     *  credentials file, or fetches a private network address), or null.
     *  Checked after the arguments are parsed; may be async (e.g. to resolve
     *  a hostname). */
    permissionReason?: (args: Args, ctx: ToolContext) => string | null | Promise<string | null>;
    /** What an approval of a permissionReason call covers, e.g. a URL's
     *  origin. Defaults to the resolved `path` argument, else the whole
     *  arguments. */
    permissionTarget?: (args: Args, ctx: ToolContext) => string;
    /** When true, the tool only reads state and never mutates the workspace.
     *  Read-only tools within a single turn may be executed in parallel. */
    isReadOnly?: boolean;
    /** When true, calls may run concurrently with each other and with
     *  read-only calls even though the tool is not read-only — for work
     *  that is isolated from the workspace (e.g. sandboxed subagents).
     *  Permission prompts for a concurrent batch are serialized and
     *  same-tool calls are approved together (see agent/permission-gate.ts). */
    concurrent?: boolean;
    /** In Code Mode, expose this tool directly next to `execute_code` instead
     *  of inside the sandbox API (for long-running or parallel tools that the
     *  synchronous, time-limited sandbox cannot host). */
    codeModeDirect?: boolean;
    render?: (args: Args) => ToolRender;
    run: (args: Args, ctx: ToolContext) => Promise<string | ToolOutput>;
}
type TaskStatus = "pending" | "in_progress" | "completed";
interface Task {
    id: string;
    title: string;
    status: TaskStatus;
}

/**
 * Whether the user said yes or no.
 *
 * NOTE on naming: the legacy `PermissionDecision` string type
 * (`"allow" | "allow_session" | "deny"`) also lives below as a
 * deprecated back-compat alias. New callers should return the typed
 * `PermissionDecisionResult` shape; the executor normalizes either at
 * the boundary so existing SDK callers keep working unchanged.
 */
type PermissionDecisionKind = "allow" | "deny";
/**
 * How broadly an allow applies. M2.1's enum overloaded this with the
 * decision — `"allow_session"` meant "allow + cache for the session" —
 * which made the new `"pattern"` scope from M6.2 impossible to express.
 *
 *   - `once`    — approved this single call. The executor will ask
 *                 again next time the same tool is requested.
 *   - `session` — approved for the rest of the session (existing
 *                 `"allow_session"` behavior).
 *   - `pattern` — approved because the call matched a pre-configured
 *                 glob in settings.json (M6.2). The executor does NOT
 *                 cache pattern-allow decisions — the pattern itself
 *                 is the durable record and lives in the config file.
 *
 * `scope` is meaningful only when `decision === "allow"`; for `deny`
 * it's ignored, but we keep it required so the shape stays uniform.
 */
type PermissionScope = "once" | "session" | "pattern";
interface PermissionDecisionResult {
    decision: PermissionDecisionKind;
    scope: PermissionScope;
    /** Free-text the user typed when denying ("do X instead"). Recorded on the
     *  trace's `permission_decision`; the text itself reaches the model as the
     *  next user message. */
    feedback?: string;
}
/**
 * Legacy single-string decision. Kept for SDK back-compat — callers can
 * still return `"allow" | "allow_session" | "deny"` and the executor
 * normalizes via `toPermissionResult`. Prefer
 * `PermissionDecisionResult` in new code.
 *
 * @deprecated since M2.2. Use `PermissionDecisionResult` instead.
 */
type PermissionDecisionLegacy = "allow" | "allow_session" | "deny";
/**
 * Union of the new typed result and the legacy string. Both shapes are
 * accepted by `PermissionAsker`; the executor calls
 * `toPermissionResult` to normalize.
 */
type PermissionDecision = PermissionDecisionLegacy | PermissionDecisionResult;
interface PermissionRequest {
    tool: ToolSpec;
    args: Record<string, unknown>;
    sessionKey: string;
    /** Set when a PreToolUse hook answered `permissionDecision: "ask"`: the
     *  prompt is forced (even for tools that skip it) and this is the hook's
     *  `permissionDecisionReason`, if it gave one. */
    hookAskReason?: string;
    /** Set when a tool that normally skips the prompt needs it for this call
     *  (ToolSpec.permissionReason), e.g. reading a credentials file. Hosts
     *  must not auto-allow such a request on the strength of
     *  `tool.needsPermission` being false. */
    sensitiveReason?: string;
    /**
     * Fires the `PermissionRequest` hooks for this call. Hosts call it right
     * before they would prompt the user — after plan mode, `--dangerously-
     * allow-all` and config permission rules have had their say — passing
     * their current mode. A non-null result decides the request without a
     * prompt; null means show the prompt as usual. Absent when no
     * PermissionRequest hook is enabled. Never returns "allow" in plan mode.
     */
    firePermissionHook?: (mode: string) => Promise<PermissionHookResult | null>;
}
/** A PermissionRequest hook's answer: approve or deny without a prompt. */
interface PermissionHookResult {
    decision: "allow" | "deny";
    reason?: string;
}

interface CreateSessionOptions {
    /** Working directory for the agent. Defaults to process.cwd(). */
    cwd?: string;
    /** Override config. If omitted, loads from ~/.config/kimiflare/config.json */
    config?: Partial<KimiConfig>;
    /** Session ID for persistence. If omitted, creates a new session. */
    sessionId?: string;
    /** Which tools to enable. Defaults to ALL_TOOLS. */
    tools?: ToolSpec[];
    /** Enable local structured memory. Defaults to config value. */
    memoryEnabled?: boolean;
    /** Enable LSP integration. Defaults to config value. */
    lspEnabled?: boolean;
    /** Enable cost attribution. Defaults to config value. */
    costAttribution?: boolean;
    /** OpenRouter provider-routing preferences for this session (merged over config.openrouterProvider). */
    provider?: OpenRouterProviderPrefs;
    /** Custom permission handler. Defaults to auto-deny in plan mode, ask callback in edit mode. */
    permissionHandler?: PermissionHandler;
    /** Called when the agent detects KIMI.md drift. */
    onKimiMdStale?: () => void;
    /**
     * M6.1: enable user-configured lifecycle hooks loaded from
     * `~/.config/autopilot/settings.json` + `<cwd>/.autopilot/settings.json`
     * (the legacy kimiflare paths are read too).
     * Default `false` (SDK is a primitive; opt in if you want the
     * TUI's hook behavior).
     */
    enableHooks?: boolean;
    /**
     * End this session if the process exits without `dispose()`: on
     * `beforeExit`, SIGTERM, SIGINT, SIGHUP or an uncaught exception, run
     * SessionEnd hooks and flush OpenTelemetry within a 3 s budget. The SDK
     * never calls `process.exit` and never changes whether the process exits:
     * when the host has its own signal listener the host still decides; when
     * it doesn't, the signal is re-raised after the work so the process dies
     * from it as before. After an uncaught exception or `process.exit()` the
     * work is only started (nothing asynchronous can finish). `dispose()`
     * remains the reliable way to end a session. Default `true`; pass `false`
     * if your app manages its lifecycle and calls `dispose()` itself.
     */
    handleProcessExit?: boolean;
}
interface PromptOptions {
    /** Attach images to the prompt. */
    images?: Array<{
        path: string;
    } | {
        data: string;
        mimeType: string;
    }>;
    /** Override mode for this prompt only. */
    mode?: "plan" | "edit" | "auto";
    /** Override max tool iterations for this prompt only. */
    maxToolIterations?: number;
}
type SessionEvent = {
    type: "session.start";
    sessionId: string;
    cwd: string;
} | {
    type: "session.end";
    reason: "complete" | "aborted" | "error";
    error?: string;
} | {
    type: "message.start";
    messageId: string;
    role: "user" | "assistant";
} | {
    type: "message.delta";
    messageId: string;
    text: string;
} | {
    type: "message.reasoning";
    messageId: string;
    text: string;
} | {
    type: "message.end";
    messageId: string;
} | {
    type: "tool.start";
    toolCallId: string;
    toolName: string;
    args: unknown;
} | {
    type: "tool.result";
    toolCallId: string;
    toolName: string;
    result: string;
    isError: boolean;
} | {
    type: "usage";
    inputTokens: number;
    outputTokens: number;
    reasoningTokens?: number;
    cost?: number;
} | {
    type: "permission.request";
    requestId: string;
    toolName: string;
    args: unknown;
} | {
    type: "permission.resolved";
    requestId: string;
    decision: PermissionDecision;
} | {
    type: "tasks.update";
    tasks: Task[];
} | {
    type: "warning";
    message: string;
}
/** A hook timed out, crashed or couldn't start (#889). Sent once per hook
 *  per session; `count` is how many times it has failed so far. `allowed`
 *  is false when the hook's `failClosed` made the action block. */
 | {
    type: "hook.failed";
    hookId: string;
    event: string;
    failure: "timeout" | "spawn_failed" | "command_not_found" | "nonzero_exit";
    reason: string;
    allowed: boolean;
    failClosed: boolean;
    count: number;
    message: string;
}
/** Older turns were archived so the next request fits the model's input budget. */
 | {
    type: "context.compacted";
    tokensBefore: number;
    tokensAfter: number;
    turnsRemoved: number;
    artifactsArchived: number;
} | {
    type: "status";
    status: "idle" | "streaming" | "tool_executing" | "compacting" | "error";
} | {
    type: "worker.finished";
    workerId: string;
    index: number;
    task: string;
    status: string;
    costUsd?: number;
};
interface SessionUsage {
    totalInputTokens: number;
    totalOutputTokens: number;
    totalCost: number;
    turnCount: number;
}
interface SessionStatus {
    isStreaming: boolean;
    isCompacting: boolean;
    pendingSteer: string[];
    pendingFollowUp: string[];
    currentMode: Mode;
}
type PermissionHandler = (req: PermissionRequest) => Promise<PermissionDecision>;
interface KimiFlareSession {
    readonly sessionId: string;
    readonly cwd: string;
    readonly isStreaming: boolean;
    readonly messages: ChatMessage[];
    /** The session's trace recorder (a no-op unless `config.trace.enabled`). Hosts
     *  such as the eval runner append their own outcome events to it. */
    readonly trace: TraceRecorder;
    /** Run a turn. Called while a turn is streaming, the text is treated as a steer. */
    prompt(text: string, options?: PromptOptions): Promise<void>;
    /** Inject guidance into the running turn: delivered at the next tool
     *  boundary, or before the final answer (the turn continues to address it).
     *  Ignored when no turn is running. */
    steer(text: string): Promise<void>;
    /** Queue a message to run as its own turn after the current (or next)
     *  prompt's turn completes successfully. */
    followUp(text: string): Promise<void>;
    abort(): Promise<void>;
    setModel(modelId: string): void;
    setMode(mode: "plan" | "edit" | "auto"): void;
    setReasoningEffort(level: "low" | "medium" | "high"): void;
    resolvePermission(requestId: string, decision: PermissionDecision): void;
    subscribe(listener: (event: SessionEvent) => void): () => void;
    getUsage(): SessionUsage;
    getStatus(): SessionStatus;
    save(): Promise<void>;
    /** Release resources. `traceEndReason` sets the `session_end` reason recorded on the trace (default "exit"). */
    dispose(options?: {
        traceEndReason?: SessionEndReason;
    }): void;
}

declare function createAgentSession(opts: CreateSessionOptions): Promise<{
    session: KimiFlareSession;
}>;

declare function createDefaultPermissionHandler(options: {
    mode: Mode;
    onRequest?: (req: PermissionRequest) => void;
}): PermissionHandler;

declare function startRpcServer(input?: Readable, output?: Writable, createSession?: typeof createAgentSession): Promise<void>;

/**
 * The start of a user prompt, the same in every host (TUI, print, emit,
 * serve, SDK/RPC): SessionStart (once per session), then the veto-able
 * UserPromptSubmit hook. Observers such as Agent Beacon see the order
 * SessionStart → UserPromptSubmit → tool events → Stop, always with the
 * session id, so hosts must have the id before they call this.
 */

/** Thrown by non-interactive hosts (SDK, RPC) when a UserPromptSubmit hook blocks the prompt. */
declare class PromptBlockedError extends Error {
    readonly reason: string;
    readonly code = "prompt_blocked";
    constructor(reason: string);
}

declare function startAsterCellBridge(port?: number): Promise<ReturnType<typeof createServer>>;
interface AsterCellControlRequest {
    method: "GET" | "POST";
    path: string;
    body?: unknown;
}
declare function executeAsterCellControl(requestId: string): Promise<unknown>;

interface RunWorktree {
    runId: string;
    repositoryRoot: string;
    sourceCwd: string;
    worktreePath: string;
    cwd: string;
    branch: string;
}

type RunStatus = "queued" | "running" | "waiting" | "needs_input" | "completed" | "failed" | "cancelled" | "interrupted_unknown";
type RunEventType = "state" | "tool_intent" | "tool_result" | "usage" | "checkpoint" | "decision";
type TimerCondition = "time" | "job" | "approval";
type TimerStatus = "scheduled" | "claimed" | "completed" | "failed" | "cancelled";
interface RunRecord {
    id: string;
    sessionId: string | null;
    cwd: string;
    sourceCwd: string | null;
    repositoryRoot: string | null;
    worktreePath: string | null;
    branch: string | null;
    task: string;
    status: RunStatus;
    stopReason: string | null;
    allowedTools: string[];
    maxToolIterations: number | null;
    maxRuntimeMs: number | null;
    maxTotalTokens: number | null;
    maxCostUsd: number | null;
    totalPromptTokens: number;
    totalCompletionTokens: number;
    totalCostUsd: number;
    costUnknownResponses: number;
    createdAt: number;
    updatedAt: number;
    startedAt: number | null;
    finishedAt: number | null;
}
interface RunBudgetUpdate {
    maxToolIterations?: number | null;
    maxRuntimeMs?: number | null;
    maxTotalTokens?: number | null;
    maxCostUsd?: number | null;
}
interface RunEvent {
    sequence: number;
    runId: string;
    type: RunEventType;
    toolCallId: string | null;
    toolName: string | null;
    metadata: Record<string, unknown>;
    createdAt: number;
}
interface RunTimer {
    id: string;
    runId: string;
    condition: TimerCondition;
    jobId: string | null;
    wakeAt: number;
    intervalMs: number;
    status: TimerStatus;
    attempts: number;
    maxAttempts: number;
    leaseUntil: number | null;
    lastError: string | null;
    createdAt: number;
}
/**
 * pending → sending → sent. A rejected send is `retry` (transient) or
 * `failed`; a send whose outcome is unknown (timeout after submission) is
 * `uncertain` and retried only with the same idempotency key. `skipped`:
 * notifications aren't configured.
 */
type NotificationStatus = "pending" | "sending" | "sent" | "retry" | "uncertain" | "failed" | "skipped";
interface RunNotification {
    id: string;
    runId: string;
    kind: RunStatus;
    reason: string | null;
    status: NotificationStatus;
    attempts: number;
    providerMessageId: string | null;
    lastError: string | null;
    createdAt: number;
}
declare class RunStore {
    private readonly db;
    constructor(dbPath?: string);
    close(): void;
    /**
     * A single-use, expiring token that lets an emailed reply decide one paused
     * run (#671 1.6). 80 random bits, base32 — typed only by whoever received
     * the email.
     */
    createDecisionToken(runId: string, ttlMs?: number): string;
    /** The run's open (unused, unexpired) token, or a new one: a retried email carries the same token. */
    openDecisionToken(runId: string): string;
    decisionToken(token: string): {
        token: string;
        runId: string;
        expiresAt: number;
        usedAt: number | null;
    } | null;
    /** Mark a token used; false if it was already used (replay). */
    consumeDecisionToken(token: string, outcome: string): boolean;
    /** Audit trail entry for an emailed decision (accepted or rejected). */
    recordDecision(runId: string, entry: Record<string, unknown>): void;
    /** The operator kill switch: while halted, no run is created, resumed or woken. Survives restarts. */
    halt(reason: string): void;
    unhalt(): void;
    haltState(): {
        reason: string;
        at: number;
    } | null;
    /** Runs doing work right now (an agent turn may be executing). */
    countActiveRuns(): number;
    createRun(input: {
        id?: string;
        task: string;
        cwd: string;
        sessionId?: string;
        worktree?: RunWorktree;
        allowedTools?: string[];
        maxToolIterations?: number | null;
        maxRuntimeMs?: number | null;
        maxTotalTokens?: number | null;
        maxCostUsd?: number | null;
    }): RunRecord;
    getRun(id: string): RunRecord | undefined;
    listRuns(options?: {
        status?: RunStatus;
        limit?: number;
    }): RunRecord[];
    transition(id: string, status: RunStatus, reason?: string): RunRecord;
    /**
     * Resume a paused run and atomically apply any revised limits. A budget
     * pause needs a revised budget; `opts.change` names another fix (e.g. a
     * larger-context model) when the limits stay as they are.
     */
    resumeWithBudgets(id: string, budgets: RunBudgetUpdate, opts?: {
        change?: string;
    }): RunRecord;
    /** Persist a tool intent before execution. Only hashes are retained, never raw arguments. */
    recordToolIntent(runId: string, toolCallId: string, toolName: string, args: unknown): void;
    /** Persist completion metadata after execution without storing tool output text. */
    recordToolResult(runId: string, toolCallId: string, toolName: string, result: {
        ok: boolean;
        content: string;
        errorCode?: string;
    }): void;
    /** Mark that the current run session was durably saved after its last tool results. */
    recordCheckpoint(runId: string): void;
    listEvents(runId: string): RunEvent[];
    countToolIterations(runId: string): number;
    recordUsage(runId: string, usage: {
        promptTokens: number;
        completionTokens: number;
        costUsd?: number;
    }): {
        run: RunRecord;
        budgetExceededReason: string | null;
    };
    /**
     * On daemon startup, requeue runs with a saved checkpoint and no unmatched
     * tool intent. Ambiguous tool actions remain interrupted and are never replayed.
     */
    reconcileInterruptedRuns(now?: number): RunRecord[];
    scheduleTimer(input: {
        runId: string;
        condition: TimerCondition;
        wakeAt: number;
        intervalMs?: number;
        jobId?: string;
        maxAttempts?: number;
    }): RunTimer;
    getTimer(id: string): RunTimer | undefined;
    nextTimerAt(): number | null;
    listFailedTimers(limit?: number): RunTimer[];
    /** Reschedule a successful job-status check without consuming failure retries. */
    rescheduleTimer(id: string, wakeAt: number): RunTimer;
    /** Atomically lease timers due now; expired claims are safely retried. */
    claimDueTimers(now?: number, limit?: number, leaseMs?: number): RunTimer[];
    completeTimer(id: string): RunTimer;
    retryTimer(id: string, error: string, now?: number): RunTimer;
    cancelTimersForRun(runId: string): number;
    cancelTimer(id: string): RunTimer;
    private updateTimer;
    private appendEvent;
    /** Notifications waiting to be sent (pending, or retryable after a failed attempt), oldest first. */
    pendingNotifications(limit?: number): RunNotification[];
    listNotifications(runId: string): RunNotification[];
    /** Record a delivery attempt's outcome. */
    updateNotification(id: string, update: {
        status: NotificationStatus;
        providerMessageId?: string;
        error?: string;
        countAttempt?: boolean;
    }): void;
    /** After a crash, a send that was in flight may or may not have been delivered. */
    recoverInFlightNotifications(): number;
    private assertRunExists;
    private assertRunActive;
}

type JobStatus = "starting" | "running" | "cancelling" | "completed" | "failed" | "timed_out" | "cancelled" | "unknown";

interface RunWakeEvent {
    runId: string;
    timerId: string;
    condition: "time" | "job";
    jobId?: string;
    jobStatus?: JobStatus | "not_found";
}
interface RunWakeSchedulerOptions {
    /** Enqueue/resume the run; must be idempotent by timerId and return promptly. */
    onWake: (event: RunWakeEvent) => void | Promise<void>;
    runsDbPath?: string;
    jobsDbPath?: string;
    leaseMs?: number;
    batchSize?: number;
    onError?: (error: Error, timer?: RunTimer) => void;
}
/**
 * Durable timer dispatcher. It holds no timer state in memory: every wake is
 * claimed from SQLite, so a restart recovers scheduled and expired leases.
 * onWake must be idempotent by timerId because delivery is at-least-once.
 */
declare class RunWakeScheduler {
    private readonly store;
    private readonly options;
    private timer;
    private running;
    private disposed;
    constructor(options: RunWakeSchedulerOptions);
    start(): void;
    stop(): void;
    dispose(): void;
    /** Deterministic entry point for tests and hosts that own their scheduler loop. */
    processDueTimers(now?: number): Promise<number>;
    private tick;
    private scheduleNextTick;
    private processTimer;
    private failRunsForFailedTimers;
}

declare function resolveSdkConfig(opts: CreateSessionOptions): Promise<KimiConfig>;

export { type AsterCellControlRequest, type ChatMessage, type CreateSessionOptions, DEFAULT_MODEL, DEFAULT_REASONING_EFFORT, type KimiConfig, type KimiFlareSession, type PermissionDecision, type PermissionHandler, type PermissionRequest, PromptBlockedError, type PromptOptions, type RunEvent, type RunEventType, type RunRecord, type RunStatus, RunStore, type RunTimer, type RunWakeEvent, RunWakeScheduler, type RunWakeSchedulerOptions, type SessionEvent, type SessionStatus, type SessionUsage, type Task, type TimerCondition, type TimerStatus, type ToolSpec, createAgentSession, createDefaultPermissionHandler, executeAsterCellControl, loadConfig, resolveSdkConfig, saveConfig, startAsterCellBridge, startRpcServer };
