import { Capability } from '../capabilities.js'; import type { AgentAdapter, AgentSpawnConfig, AgentProcess, Backend, ContinuationSink } from '../types.js'; import { CancelledError } from './defaults.js'; import { CortexAgentContext } from './spawn-args.js'; import { type TmuxExec } from './tmux-control.js'; import { extractAskUserQuestions, getCurrentPlanFilePath, mergeSubstantialOutput } from './event-parser.js'; interface ClaudeSessionOptions { needsResume: boolean; model?: string | null; isUserInitiated?: boolean; callbackSource?: string | null; scheduleTaskId?: string | null; sessionKey?: string | null; claudeAgent?: string | null; systemPrompt?: string | null; appendSystemPrompt?: string | null; outputStyle?: string | null; tools?: string | null; pluginDirs?: string[] | null; anthropicBaseUrl?: string; extraEnv?: Record; /** Extra CLI options from profile (e.g. {"--thinking": "xhigh"}). */ extraOption?: Record; /** Cortex execution context surfaced to the MCP server child as CORTEX_THREAD_ID/PROFILE/PROJECT/SESSION_NAME env vars. * Captured at spawn time; later turns on the same session reuse the original snapshot. */ context?: CortexAgentContext; } declare class ClaudeSession { private proc; private rl; sessionId: string; private channel; private sessionKey; /** Model name requested via --model CLI arg (used as fallback for cost_record). */ modelName: string | null; private isUserInitiated; private callbackSource; private scheduleTaskId; private claudeAgent; private systemPrompt; private appendSystemPrompt; private outputStyle; private tools; private pluginDirs; private anthropicBaseUrl; private extraEnv; private extraOption; private context; private currentTurn; /** Tracks in-flight background tasks (run_in_background) for this session. */ private bgTracker; /** Set by orchestration to receive spontaneous background-task continuation turns. */ private continuationSink; private alive; private needsResume; private idleTimer; private turnIdleTimer; private maxTimer; private stderr; private cumulativeCostUsd; /** Captured from result event's modelUsage key for cost_record. */ lastModelName: string | null; /** Captured from result event's usage for cost_record (per-turn, non-cumulative). */ lastTokenUsage: { input: number; output: number; cacheCreation: number; cacheRead: number; } | null; constructor(channel: string, sessionId: string, options: ClaudeSessionOptions); private toSpawnOptions; private handleProcessClose; private spawnProcess; private createTurnStreams; private registerTurn; /** Register/replace the continuation sink. Persists across normal turns; lives as long * as the pooled session, until close()/kill(). */ setContinuationSink(sink: ContinuationSink): void; clearContinuationSink(): void; /** Open a synthetic turn to capture the spontaneous continuation the CLI emits after a * background task finishes. Its assistant text / result are routed to continuationSink * (not to a send() promise — there is no caller awaiting it). */ private openContinuationTurn; private writeTurnStdin; private startTurnIdleTimer; sendMessage(userMessage: string, options: { files?: any[]; callbackSource?: string | null; scheduleTaskId?: string | null; isUserInitiated?: boolean; onProgress?: ((progress: any) => void) | null; onAssistantMessage?: ((text: string) => void) | null; onToolUse?: ((name: string, input: any) => void) | null; onCompact?: ((info: { trigger: string; preTokens?: number; }) => void) | null; }): Promise; private bumpTurnIdleTimer; private handleResultEvent; private handleAssistantEvent; private handleLine; private closeTurnLogs; private resetIdleTimer; close(): void; kill(): boolean; isAlive(): boolean; } export declare function closeSession(channel: string, sessionKey?: string): void; /** Close all sessions whose key starts with the given prefix (used by Thread cleanup). */ export declare function closeSessionsByPrefix(prefix: string): void; export declare function closeAllSessions(): void; export interface RunClaudeOptions { channel: string; sessionId?: string | null; files?: any[]; callbackSource?: string | null; scheduleTaskId?: string | null; model?: string | null; isUserInitiated?: boolean; onProgress?: any; onAssistantMessage?: any; onToolUse?: ((name: string, input: any) => void) | null; sessionKey?: string | null; claudeAgent?: string | null; systemPrompt?: string | null; outputStyle?: string | null; tools?: string | null; pluginDirs?: string[] | null; anthropicBaseUrl?: string; } export declare function runClaude(userMessage: string, opts: RunClaudeOptions): { promise: Promise; kill(): boolean; sessionId: string; }; /** Pure dispatch: select claude adapter mode from an AgentSpawnConfig. * Defaults to 'print' for missing or unrecognized values (conservative — never silently * flips a session into the experimental TUI path). */ export declare function selectClaudeMode(config: AgentSpawnConfig): 'print' | 'tui'; /** Test hook: mirror of ClaudeSession.toSpawnOptions() for the AgentSpawnConfig entry point. * Must stay in sync with ClaudeSession constructor + toSpawnOptions — both paths derive * ClaudeSpawnOptions through deriveClaudeSpawnOptions(), so any field added to that helper * is covered here without divergence. */ declare function computeSpawnArgsForConfig(config: AgentSpawnConfig): string[]; export declare class ClaudeAdapter implements AgentAdapter { readonly backend: Backend; readonly capabilities: Set; spawn(config: AgentSpawnConfig): AgentProcess; /** * DR-0012 TUI-mode dispatch. Returns an AgentProcess whose send() pushes ALL NormalizedEvents * (including derived ones — ask_user_question, plan_*, cost_record, turn_complete) via * ClaudeTuiSession's onEvent stream, then resolves with the TuiAgentResult cast to AgentResult. * * Sessions are pooled in `tuiSessions` by sessionKey; multi-turn reuses the same tmux session. * kill() forwards to ClaudeTuiSession.kill() which tears down the tmux session. */ private spawnTui; close(sessionKey: string): Promise; kill(sessionKey: string): boolean; listSessions(): string[]; } /** * DR-0012 §3.6 startup hook — sweep orphan tmux sessions matching the cortex-claude- prefix. * * Rationale: tmux sessions are independent of agent-server's process lifetime, but the in-memory * `tuiSessions` Map is not. After an agent-server restart we have no record of channel/sessionKey * → tmux mapping (it was never persisted), so we cannot re-adopt existing tmux sessions into the * pool. The honest choice is to kill them at startup; otherwise they accumulate forever and a * later session reusing the same sessionId would conflict with `tmux new-session -s ` * (which fails on duplicate). Logs the killed names so operators can investigate if needed. * * Full re-adoption (preserving an in-flight TUI session across restart) requires persisting * sessionKey + cwd + needsResume metadata to disk — deferred as a follow-up. * * Override `exec` in tests so we don't touch the real tmux server. */ export declare function recoverTuiOrphans(exec?: TmuxExec): { found: string[]; killed: string[]; }; /** Construct a ClaudeSession WITHOUT spawning the `claude` child process, for unit * testing handleLine / continuation routing. Initializes only the fields the line * handlers touch. Callers should stub createTurnStreams to avoid log file I/O and * register cleanup via t.after(() => session.close()) to clear the idle timer. */ declare function makeSessionForTest(): ClaudeSession; export declare const _test: { extractAskUserQuestions: typeof extractAskUserQuestions; mergeSubstantialOutput: typeof mergeSubstantialOutput; computeSpawnArgs: typeof computeSpawnArgsForConfig; makeSessionForTest: typeof makeSessionForTest; }; export { getCurrentPlanFilePath }; export { CancelledError };