/**
 * Claude Code query abstraction.
 *
 * Sends a prompt to an interactive Claude Code session running in a tmux pane
 * using `tmux send-keys -l --` (literal text) + `C-m` (raw carriage return).
 * Verifies delivery by polling `capture-pane` and retries if needed.
 *
 * This is NOT headless — Claude runs as a full interactive TUI in the pane.
 * We're automating keyboard input and reading pane output.
 *
 * Reliability hardened from oh-my-codex's sendToWorker implementation:
 * - Pre-send readiness wait with exponential backoff
 * - CLI-specific submit plan (Claude: 1 C-m per round)
 * - Per-round capture verification (6 rounds)
 * - Adaptive retry with C-u clear + retype
 * - Post-submit active-task detection
 * - File-based idle detection via session JSONL watching
 */
import { type SessionMessage, type SDKUserMessage, type Options as SDKOptions } from "@anthropic-ai/claude-agent-sdk";
/**
 * Remove a pane from the initialized map and signal the currently-blocked
 * Stop hook that the session is over, so Claude stops promptly instead of
 * waiting out the hook's safety timeout.
 *
 * Called by the runtime when a Claude stage is being torn down. Idempotent.
 */
export declare function clearClaudeSession(paneId: string): Promise<void>;
export interface ClaudeSessionOptions {
    /** tmux pane ID where Claude should be started */
    paneId: string;
    /** CLI flags to pass to the `claude` command (default: ["--allow-dangerously-skip-permissions", "--dangerously-skip-permissions"]) */
    chatFlags?: string[];
    /** Timeout in ms waiting for Claude TUI to be ready (default: 30s) */
    readyTimeoutMs?: number;
}
/**
 * Initialize per-pane Claude state. Does NOT spawn the `claude` CLI — the
 * pane is left as a bare shell. The CLI is spawned lazily on the first
 * `claudeQuery()` call, with the prompt baked into the spawn command:
 *
 *     claude [chatFlags] --session-id <UUID> 'Read the prompt in <tmpfile>'
 *
 * Pre-generating the session UUID here lets the first query pass it to the
 * CLI, so we know the JSONL filename up front and can skip discovery polling.
 *
 * Must be called before any `claudeQuery()` calls targeting the same pane.
 *
 * @example
 * ```typescript
 * import { createClaudeSession, claudeQuery } from "@bastani/atomic/workflows";
 *
 * await createClaudeSession({ paneId: ctx.paneId });
 * await claudeQuery({ paneId: ctx.paneId, prompt: "Describe this project" });
 * ```
 *
 * @example
 * ```typescript
 * // With custom flags
 * await createClaudeSession({
 *   paneId: ctx.paneId,
 *   chatFlags: ["--model", "opus", "--dangerously-skip-permissions"],
 * });
 * ```
 */
export declare function createClaudeSession(options: ClaudeSessionOptions): Promise<string>;
/**
 * Returns true when the most recent assistant message in the transcript
 * ended with `stop_reason: "tool_use"` — i.e. the agent stopped the current
 * API response to call a tool but has not yet produced its post-tool answer.
 *
 * Claude Code's Stop hook fires each time Claude "finishes responding",
 * which includes intermediate tool-use responses in a multi-step agent
 * loop (not just the final `end_turn`). If we return from `waitForIdle`
 * on the first Stop event, we capture the transcript mid-loop — the
 * final assistant text block is still being generated and won't be on
 * disk yet, so `inbox.md` drops the actual answer.
 *
 * We keep watching until we see an assistant message with a terminal
 * stop_reason (`end_turn`, `max_tokens`, `stop_sequence`, `refusal`),
 * which is the real end of the turn.
 *
 * Exported as `_isMidAgentLoop` for unit testing.
 */
export declare function _isMidAgentLoop(messages: SessionMessage[]): boolean;
/**
 * Watch `~/.atomic/claude-hil/` for this session's marker file and fire
 * `onHIL(true|false)` on create/unlink. Returns when `signal` is aborted.
 *
 * The marker is written by the `_claude-ask-hook enter` subcommand from
 * Claude Code's `PreToolUse` hook (matched on `AskUserQuestion`) and removed
 * by `_claude-ask-hook exit` from `PostToolUse` / `PostToolUseFailure`. That
 * makes the signal deterministic and independent of Claude Code's batched
 * JSONL flush timing, which used to hide the HIL window entirely when
 * tool_use and tool_result landed in the same file write.
 *
 * @internal Exported for tests.
 */
export declare function watchHILMarker(claudeSessionId: string, onHIL: (waiting: boolean) => void, signal: AbortSignal): Promise<void>;
/**
 * Path of the directory where the claude-stop-hook writes marker files.
 * Each Claude turn creates `~/.atomic/claude-stop/<session_id>` which
 * triggers the `fs.watch` event in `waitForIdle`.
 *
 * @internal Exported for unit tests.
 */
export declare function markerDir(): string;
/**
 * Return the marker file path for a given Claude session ID.
 *
 * @internal Exported for unit tests.
 */
export declare function markerPath(claudeSessionId: string): string;
/**
 * Directory where the workflow runtime writes queued follow-up prompts that
 * `atomic _claude-stop-hook` picks up and feeds back to Claude as
 * `{decision:"block", reason:<prompt>}`. @internal Exported for unit tests.
 */
export declare function queueDir(): string;
/** Return the queue file path for a given Claude session ID. @internal */
export declare function queuePath(claudeSessionId: string): string;
/**
 * Directory where the runtime writes session-release signals. When the Stop
 * hook sees `~/.atomic/claude-release/<session_id>` it exits 0 without
 * emitting a block decision — the signal used by `clearClaudeSession` to
 * tell Claude it's safe to actually stop. @internal Exported for unit tests.
 */
export declare function releaseDir(): string;
/** Return the release file path for a given Claude session ID. @internal */
export declare function releasePath(claudeSessionId: string): string;
/**
 * Signal the Stop hook's blocking wait that this session is done. Called
 * during session teardown so the final hook invocation exits 0 promptly.
 * Safe to call more than once.
 */
export declare function releaseClaudeSession(claudeSessionId: string): Promise<void>;
/**
 * Wait for the Claude session to become idle using `fs.watch` on the
 * `~/.atomic/claude-stop/` marker directory.
 *
 * When Claude finishes a turn, the `atomic _claude-stop-hook` Stop hook writes
 * `~/.atomic/claude-stop/<session_id>`. The write triggers an OS-native
 * `fs.watch` event on the parent directory — far more reliable than polling
 * tmux pane glyphs, which vary between Claude Code versions.
 *
 * This function is strictly about *idle detection*. HIL is detected separately
 * by {@link watchHILMarker}; the Stop hook does not fire while
 * `AskUserQuestion` is pending (the agent loop blocks on deferred tools), so
 * mixing the two would silently miss the HIL window.
 *
 * Algorithm:
 * 1. Attach the directory watcher, then check for the marker file on disk —
 *    this closes the race where the Stop hook fires between prompt submission
 *    and watcher attach.
 * 2. On any event, re-check the marker file on disk (we intentionally do NOT
 *    filter by `event.filename`, because on Linux a write can deliver multiple
 *    events with varying filenames and editor tools may race us).
 * 3. Read the session transcript via `getSessionMessages` and slice messages
 *    from `transcriptBeforeCount`.
 * 4. Clean up the `fs.watch` watcher on any exit path via AbortController.
 *
 * @param claudeSessionId       - Claude's session UUID (used to identify marker file)
 * @param transcriptBeforeCount - number of messages in transcript before this turn
 */
/**
 * @internal Exported for unit tests.
 */
export declare function waitForIdle(claudeSessionId: string, transcriptBeforeCount: number): Promise<SessionMessage[]>;
export interface ClaudeQueryOptions {
    /** tmux pane ID where Claude is running */
    paneId: string;
    /** The prompt to send */
    prompt: string;
    /**
     * Called when the agent's human-in-the-loop state changes.
     * `waiting=true`  → AskUserQuestion is pending (agent blocked on user input).
     * `waiting=false` → AskUserQuestion was resolved (agent resumed processing).
     */
    onHIL?: (waiting: boolean) => void;
}
/**
 * Extract text content from assistant messages in a transcript slice.
 *
 * Walks messages from `afterIndex` forward, pulls `TextBlock.text` from each
 * assistant message's content array, and joins them. The `message` payload is
 * `unknown` in the SDK type so we do runtime narrowing.
 *
 * Exported so workflow authors can extract text from `SessionMessage[]`
 * returned by `s.session.query()`.
 */
export declare function extractAssistantText(msgs: ReadonlyArray<{
    type: string;
    message: unknown;
}>, afterIndex: number): string;
/**
 * Send a prompt to a Claude Code interactive session running in a tmux pane.
 *
 * First query and follow-up queries use different delivery channels:
 *
 *   - **First query**: stages the prompt in a tmp file and spawns
 *     `claude --session-id <UUID> 'Read the prompt in <path>'` into the
 *     empty pane. Claude's first action is a Read tool call, which
 *     sidesteps ARG_MAX on the spawn argv.
 *
 *   - **Follow-up query**: writes the prompt to
 *     `~/.atomic/claude-queue/<session_id>`. The Stop hook from the
 *     previous turn is blocked in a poll loop there; it reads the queue
 *     entry and emits `{"decision":"block","reason":<prompt>}` on stdout,
 *     which Claude Code feeds back as the next user message. No tmux
 *     keystrokes, no paste-buffer dance, no pane-state polling — the
 *     whole delivery rides Claude's own continuation API.
 *
 * Both paths converge on `waitForIdle`, which watches the Stop-hook marker
 * file for this session and returns the transcript slice for the turn.
 *
 * @example
 * ```typescript
 * import { claudeQuery } from "@bastani/atomic/workflows";
 *
 * const result = await claudeQuery({
 *   paneId: ctx.paneId,
 *   prompt: "Describe this project",
 * });
 * ctx.log(result.output);
 * ```
 */
export declare function claudeQuery(options: ClaudeQueryOptions): Promise<SessionMessage[]>;
/**
 * Merge two `disallowedTools` lists, preserving caller entries and appending
 * any extras that aren't already present. Exported for unit testing.
 */
export declare function mergeDisallowedTools(existing: string[] | undefined, extras: string[]): string[];
/**
 * Synthetic client wrapper for Claude stages.
 * Auto-starts the Claude CLI in the tmux pane during `start()`.
 */
export declare class ClaudeClientWrapper {
    readonly paneId: string;
    private readonly opts;
    constructor(paneId: string, opts?: {
        chatFlags?: string[];
        readyTimeoutMs?: number;
    });
    /**
     * Start the Claude CLI in the tmux pane. Returns the Claude session UUID
     * so the caller can pass it to `ClaudeSessionWrapper` (and thus expose it
     * as `s.sessionId` to workflows). This is the UUID used by Claude Code to
     * name its JSONL transcript file and to key the Stop-hook marker — workflows
     * pass it to `s.save(s.sessionId)` so the save path reads the correct
     * transcript even when many Claude sessions run in parallel.
     */
    start(): Promise<string>;
    /** Noop — cleanup is handled by the runtime via `clearClaudeSession`. */
    stop(): Promise<void>;
}
/**
 * Synthetic session wrapper for Claude stages.
 * Wraps `claudeQuery()` so users call `s.session.query(prompt)`.
 */
export declare class ClaudeSessionWrapper {
    readonly paneId: string;
    readonly sessionId: string;
    private readonly onHIL;
    constructor(paneId: string, sessionId: string, onHIL?: (waiting: boolean) => void);
    /**
     * Send a prompt to Claude and wait for the response.
     *
     * The `_options` parameter exists for signature compatibility with
     * {@link HeadlessClaudeSessionWrapper#query} (which forwards SDK options
     * like `agent`, `permissionMode`, etc. to the Agent SDK). In the
     * interactive pane path these options don't apply — we're driving the
     * `claude` CLI binary, not the SDK — so they are silently ignored.
     */
    query(prompt: string, _options?: Partial<SDKOptions>): Promise<SessionMessage[]>;
    /** Noop — for API symmetry with CopilotSession.disconnect(). */
    disconnect(): Promise<void>;
}
/**
 * Headless client wrapper for Claude stages. No tmux pane — noop start/stop.
 * Used when `options.headless` is true in `ctx.stage()`.
 */
export declare class HeadlessClaudeClientWrapper {
    /**
     * Headless Claude stages don't pre-allocate a session — each `query()` call
     * to {@link HeadlessClaudeSessionWrapper} spawns a fresh Agent SDK run that
     * emits its own `session_id`. We still return an empty string here so the
     * method signature matches {@link ClaudeClientWrapper#start}.
     */
    start(): Promise<string>;
    stop(): Promise<void>;
}
/**
 * Resolve the `claude` CLI binary for headless SDK queries.
 *
 * Pins the SDK to the same binary interactive stages already spawn via tmux
 * (`AGENT_CONFIG.claude.cmd` on PATH), bypassing
 * `@anthropic-ai/claude-agent-sdk`'s built-in resolver. That resolver probes
 * optional native packages in a fixed order — on Linux it tries
 * `linux-${arch}-musl` before `linux-${arch}` and returns whichever
 * `require.resolve` finds first — so on a glibc host where both optional
 * packages got installed (Bun installs every optionalDependency by default)
 * it picks the musl binary, which can't exec because its dynamic linker
 * (`/lib/ld-musl-*.so.1`) is absent. The SDK surfaces the resulting ENOENT
 * as a misleading "Claude Code native binary not found" error.
 *
 * `chatCommand` and `workflowCommand` already fail fast when `claude` isn't
 * on PATH (see `isCommandInstalled` in each), so in practice this lookup
 * always succeeds. The throw here is a belt-and-suspenders guard that
 * prefers a clear failure over silently falling back to the SDK's resolver.
 */
export declare function resolveHeadlessClaudeBin(): string;
/**
 * Headless session wrapper for Claude stages. Uses the Agent SDK's `query()`
 * directly instead of tmux pane operations. Implements the same `query()`
 * interface as {@link ClaudeSessionWrapper} so workflow callbacks work
 * identically for headless and interactive stages.
 *
 * The `query()` method accepts the full Agent SDK parameter types —
 * `prompt` can be a plain string or an `AsyncIterable<SDKUserMessage>`
 * for multi-turn streaming, and `options` passes through SDK-level
 * configuration (abort controllers, allowed tools, agents, etc.).
 */
export declare class HeadlessClaudeSessionWrapper {
    readonly paneId = "";
    /**
     * The Claude session UUID of the most recently completed `query()`. Exposed
     * via `s.sessionId` so workflows can pass it to `s.save(s.sessionId)` and
     * have the save path read the correct transcript, even when several headless
     * Claude stages run in parallel (each call gets its own SDK-assigned UUID).
     */
    private _lastSessionId;
    get sessionId(): string;
    query(prompt: string | AsyncIterable<SDKUserMessage>, options?: Partial<SDKOptions>): Promise<SessionMessage[]>;
    disconnect(): Promise<void>;
}
/**
 * Validate a Claude workflow source file for common mistakes.
 *
 * Warns on direct usage of createClaudeSession/claudeQuery — the runtime
 * now handles init/cleanup automatically via s.client and s.session.
 */
export declare const validateClaudeWorkflow: (source: string) => import("../types.ts").ValidationWarning[];
//# sourceMappingURL=claude.d.ts.map