/** * Persistent Claude CLI Process - Keeps Claude alive for multi-turn conversations * * WHY THIS EXISTS: * - Previous approach spawned new Claude CLI process for each message * - Each spawn required ~20K system prompt to be sent every time * - Result: Slow responses (16-30 seconds per message) * * NEW ARCHITECTURE: * - Keep Claude process alive using stream-json input/output * - Send messages via stdin, receive responses via stdout * - Session memory preserved in Claude's context * - System prompt sent only once at process start * * STREAM-JSON PROTOCOL: * Input (stdin): * User message: {"type":"user","message":{"role":"user","content":"..."}} * Tool result: {"type":"user","message":{"role":"user","content":[{"type":"tool_result","tool_use_id":"xxx","content":"...","is_error":false}]}} * * Output (stdout): * Init: {"type":"system","subtype":"init",...} * Assistant: {"type":"assistant","message":{...}} * Tool use: Content block with type="tool_use" in assistant message * Result: {"type":"result","subtype":"success",...} */ import { EventEmitter } from 'events'; import type { TokenUsageRecord, PromptCallbacks, ToolUseBlock } from './types.js'; export interface PersistentProcessOptions { sessionId: string; model?: string; systemPrompt?: string; mcpConfigPath?: string; /** * Skip permission prompts for tool execution * * @warning SECURITY RISK: Bypasses all permission checks. * Only enable in trusted environments where agent actions are pre-approved. */ dangerouslySkipPermissions?: boolean; useGatewayTools?: boolean; /** Timeout for each request in ms (default: 120000) */ requestTimeout?: number; /** Idle timeout for pooled persistent processes in ms (default: session_ms) */ idleTimeoutMs?: number; /** Cleanup interval for pooled persistent processes in ms (default: session_cleanup_ms) */ cleanupIntervalMs?: number; /** Maximum time to keep a process waiting for host-side tool results (default: max(4 * idleTimeoutMs, 30m)) */ pendingToolUseTimeoutMs?: number; /** Environment variables to pass to the Claude CLI process */ env?: Record; /** Structurally allowed tools (--allowedTools CLI flag) */ allowedTools?: string[]; /** Structurally disallowed tools (--disallowedTools CLI flag) */ disallowedTools?: string[]; /** Override built-in tool set (--tools CLI flag). Use "" to disable all tools. */ tools?: string; /** Override plugin directory (--plugin-dir CLI flag). Use empty dir to disable plugins. */ pluginDir?: string; /** Optional callback for recording token usage */ onTokenUsage?: (record: TokenUsageRecord) => void; /** Channel key for token usage tracking */ channelKey?: string; /** Agent ID for token usage tracking */ agentId?: string; /** Effort level for Claude 4.6 adaptive thinking */ effort?: 'low' | 'medium' | 'high' | 'max'; } export interface PersistentProcessAcquireResult { process: PersistentClaudeProcess; created: boolean; } export type { ToolUseBlock } from './types.js'; export interface ContentBlock { type: 'text' | 'tool_use' | 'tool_result'; text?: string; id?: string; name?: string; input?: Record; tool_use_id?: string; content?: string | Array<{ type: string; text?: string; }>; } export interface StreamMessage { type: 'system' | 'assistant' | 'result' | 'error' | 'user'; subtype?: string; message?: { role: string; content: ContentBlock[]; model?: string; id?: string; usage?: { input_tokens: number; output_tokens: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number; }; }; result?: string; session_id?: string; total_cost_usd?: number; usage?: { input_tokens: number; output_tokens: number; cache_read_input_tokens?: number; cache_creation_input_tokens?: number; }; duration_ms?: number; num_turns?: number; is_error?: boolean; error?: string; } export interface PromptResult { response: string; usage: { input_tokens: number; output_tokens: number; cache_creation_input_tokens?: number; cache_read_input_tokens?: number; }; session_id: string; cost_usd?: number; toolUseBlocks?: ToolUseBlock[]; hasToolUse?: boolean; duration_ms?: number; } export type { PromptCallbacks }; type ProcessState = 'idle' | 'busy' | 'starting' | 'dead'; /** * PersistentClaudeProcess - Manages a single long-lived Claude CLI process * * Features: * - Keeps Claude process alive for multiple messages * - Uses stream-json for bidirectional communication * - Handles tool execution via Gateway Tools * - Auto-restarts on process death */ export declare class PersistentClaudeProcess extends EventEmitter { private process; private options; private state; private outputBuffer; private currentCallbacks; private currentResolve; private currentReject; private requestTimeoutHandle; private toolUseBlocks; private awaitingToolResults; private pendingToolUseStartedAt; private accumulatedText; private startPromise; private onTokenUsage?; /** * Resolve the effective request timeout in ms. * 0 = unlimited (no timeout). Returns the configured or default value. */ private _getRequestTimeoutMs; constructor(options: PersistentProcessOptions); /** * Start the Claude CLI process * * Note: The CLI only emits the 'init' event after receiving the first user message. * So we don't wait for init here - we just start the process and let it run. * The first sendMessage() call will handle init as part of its response flow. */ start(): Promise; private doStart; /** * Build CLI arguments for stream-json mode */ private buildArgs; /** * Send a user message to Claude */ sendMessage(content: string, callbacks?: PromptCallbacks): Promise; /** * Send a tool result back to Claude */ sendToolResult(toolUseId: string, result: string, isError?: boolean, callbacks?: PromptCallbacks): Promise; /** * Send multiple tool results back to Claude in a single message. * Required when Claude requests multiple tools in one turn. */ sendToolResults(results: Array<{ tool_use_id: string; content: string; is_error: boolean; }>, callbacks?: PromptCallbacks): Promise; /** * Handle stdout data */ private handleStdout; /** * Process a parsed event from stdout */ private processEvent; /** * Handle stderr data */ private handleStderr; /** * Handle process close */ private handleClose; /** * Handle process error */ private handleError; /** * Handle request timeout */ private handleTimeout; /** * Clear request timeout */ private clearRequestTimeout; /** * Reset request state */ private resetRequestState; /** * Stop the process */ stop(): void; /** * Check if process is alive */ isAlive(): boolean; /** * Check if process is ready for new messages */ isReady(): boolean; /** * True while Claude has requested tools and the host has not sent tool_result yet. * The process is technically idle during host-side tool execution, but it must not * be reclaimed because the next prompt depends on this live Claude context. */ hasPendingToolUse(): boolean; getPendingToolUseStartedAt(): number | null; /** * Get current state */ getState(): ProcessState; /** * Get session ID */ getSessionId(): string; } export declare function formatClaudeArgsForLog(args: readonly string[]): string[]; /** * PersistentProcessPool - Manages multiple persistent Claude processes * * Features: * - One process per channel/session * - Automatic process lifecycle management * - Process reuse for multi-turn conversations */ export declare class PersistentProcessPool { private processes; private defaultOptions; private idleTimeoutMs; private cleanupIntervalMs; private pendingToolUseTimeoutMs; private cleanupTimer; constructor(defaultOptions?: Partial); private resolveIdleTimeoutMs; private resolveCleanupIntervalMs; private resolvePendingToolUseTimeoutMs; private startCleanupTimer; /** * Get or create a process for a channel */ getProcess(channelKey: string, options?: Partial): Promise; /** * Get or create a process for a channel and report whether this call created it. */ getProcessWithStatus(channelKey: string, options?: Partial): Promise; /** * Stop idle ready processes that have outlived the configured idle timeout. */ cleanupIdleProcesses(now?: number): number; /** * Stop a specific process */ stopProcess(channelKey: string): void; /** * Stop all processes */ stopAll(): void; /** * Get number of active processes */ getActiveCount(): number; /** * Get all channel keys with active processes */ getActiveChannels(): string[]; /** * Get states of all active processes * @returns Map of channelKey → ProcessState */ getProcessStates(): Map; } //# sourceMappingURL=persistent-cli-process.d.ts.map