import { ComponentLogger } from '../core/logger.service.js'; import { SessionCommandHelper } from '../session/index.js'; import { RuntimeType } from '../../constants.js'; import { type CredentialGuardFiles } from './credential-guard.service.js'; /** * Result of MCP configuration operation. * Returned by ensureMcpConfig to indicate what was configured. */ export interface McpConfigResult { /** Whether the config was written successfully */ success: boolean; /** Number of new servers added */ addedServers: number; /** Total servers in the final config */ totalServers: number; /** Names of servers in the final config */ serverNames: string[]; /** Error message if success is false */ error?: string; } /** * Runtime configuration interface */ export interface RuntimeConfig { displayName: string; initScript: string; welcomeMessage: string; timeout: number; description: string; } /** * Abstract base class for AI runtime services that handles tmux session initialization, * detection, and interaction patterns for different AI CLI tools. * * Uses Template Method pattern for maximum code reuse while allowing runtime-specific customization. */ export declare abstract class RuntimeAgentService { protected logger: ComponentLogger; protected sessionHelper: SessionCommandHelper; protected projectRoot: string; protected runtimeConfig: RuntimeConfig | null; private detectionInProgress; private detectionResults; constructor(sessionHelper: SessionCommandHelper, projectRoot: string); protected abstract getRuntimeType(): RuntimeType; protected abstract detectRuntimeSpecific(sessionName: string): Promise; protected abstract getRuntimeReadyPatterns(): string[]; protected abstract getRuntimeErrorPatterns(): string[]; protected abstract getRuntimeExitPatterns(): RegExp[]; /** * Get patterns that indicate this runtime has exited. * Used by RuntimeExitMonitorService to detect when the CLI process exits. * * @returns Array of RegExp patterns that match runtime exit output */ getExitPatterns(): RegExp[]; /** * Apply the control-plane guard to the launch commands (Request 72c9427a). * * For Claude Code, writes the per-session settings file (deny rules plus a * PreToolUse Bash hook) and appends `--settings `. Every runtime logs * one line saying whether it is guarded, so the coverage is visible at * launch. If the files cannot be written, the session still launches, but * unguarded and with an ERROR. Blocking agent start on the guard would turn * a disk error into an outage. * * @param sessionName - PTY session name * @param commands - Launch commands built so far * @param targetPath - Agent working directory, whose `.claude/agents` is protected * @returns The commands, with `--settings` appended when the guard applies */ protected applyControlPlaneGuard(sessionName: string, commands: string[], targetPath?: string): Promise; /** * Write the credential guard's paths file and wrappers * (specs/2026-10-04-agent-credential-isolation.md, layer 2). * * @param sessionName - PTY session name (for logs) * @returns The files, or null when the guard is off or could not be written */ protected prepareCredentialGuardFiles(sessionName: string): CredentialGuardFiles | null; /** * Attach the credential guard for runtimes other than Claude Code. * * - Codex: a session PreToolUse hook via `-c`, plus the flag that lets a * session hook run without a trust prompt (skipped, with a WARN, on a * Codex that does not know the flag — passing it would stop Codex starting). * - Gemini CLI: `GEMINI_CLI_SYSTEM_SETTINGS_PATH=` with a BeforeTool hook. * - Antigravity: Crewly's entry in agy's global hooks file (agy has no * per-process hooks path); the script ignores calls from the owner's own agy. * - Anything else (OpenCode): unguarded, logged. * * Never blocks a launch: on any failure the session starts unguarded with a WARN. * * @param sessionName - PTY session name * @param commands - Launch commands * @returns The commands, changed for Codex and Gemini CLI */ protected applyCredentialGuardNonClaude(sessionName: string, commands: string[]): Promise; /** * Codex part of the credential guard, applied last so the `codex resume` * rewrite has already happened: a session PreToolUse hook via `-c`, plus * the flag that lets it run without a trust prompt. On a Codex that does * not list that flag the session starts unguarded with a WARN (passing an * unknown flag would stop Codex starting). * * @param sessionName - PTY session name * @param commands - Codex launch commands * @returns The commands with the hook arguments added when supported */ protected applyCodexCredentialGuard(sessionName: string, commands: string[]): Promise; /** * Launch Codex with `--no-daemon` so its shell commands run in this * agent's own process and carry this agent's identity env. * * Without it every Codex TUI on the machine attaches to one shared * `codex app-server` daemon, started by whichever Codex session came * first (usually the orchestrator), and every agent's skills then ran as * that session (see codex-daemon.utils). A Codex too old to know the flag * launches as before, with a WARN, rather than failing to start. * * @param sessionName - PTY session name * @param commands - Codex launch commands built so far * @returns The commands, with `--no-daemon` added when supported */ protected keepCodexOffSharedDaemon(sessionName: string, commands: string[]): Promise; /** * Template method for executing runtime initialization script. * Most logic is shared, only runtime-specific parts are delegated to abstract methods. * * @param sessionName - PTY session name * @param targetPath - Working directory for the session * @param runtimeFlags - Optional CLI flags to inject after the runtime binary (skill flags, --model/-m …) * @param promptFilePath - Optional path to a prompt file; for non-Claude-Code runtimes, * appends --append-system-prompt-file flag * @param agentName - Optional agent name for Claude Code --agent flag (#207) * @param resumeSessionId - Conversation to resume for runtimes whose resume is a * subcommand rather than a flag (Codex: `codex … ` → `codex resume … `) */ executeRuntimeInitScript(sessionName: string, targetPath?: string, runtimeFlags?: string[], promptFilePath?: string, agentName?: string, resumeSessionId?: string): Promise; /** * Template method for detecting if runtime is running. * Handles caching and concurrent access, delegates actual detection to concrete classes. */ detectRuntimeWithCommand(sessionName: string, forceRefresh?: boolean): Promise; /** * One-question dialogs this runtime can raise at start-up that have a * single right answer for an agent. Matched against the screen with all * whitespace removed (TUIs drop spaces). Override per runtime. * * @returns Known prompts with the keys that answer them */ protected getKnownPrompts(): readonly KnownRuntimePrompt[]; /** * If the screen shows one of {@link getKnownPrompts}, answer it. * Called from every wait loop (start-up and before the kickoff), so an * agent never sits on a question nobody is there to answer — the owner * found Kai on steamfun-ops and Nova here stuck on codex's "Working * directory · resume" picker (2026-09-26). * * @param sessionName - Session * @param screen - Captured screen text * @returns True when a prompt was answered */ answerKnownPrompt(sessionName: string, screen: string): Promise; /** * Simplified method for waiting for runtime to be ready. * Checks at regular intervals until timeout, looking for ready patterns in the terminal output. */ waitForRuntimeReady(sessionName: string, timeout: number, checkInterval?: number): Promise; /** * Whether a captured screen shows the runtime idle at its input prompt — * i.e. it will accept typed input *right now*. * * This is deliberately stricter than `waitForRuntimeReady()`: that method * matches banner text (`OpenAI Codex`, `model:` …) which is already on * screen while the TUI is still booting, so an instruction typed at that * point is swallowed. Registration gates on this predicate instead. * * Base rule: a prompt line for this runtime is visible in the last * `RUNTIME_INPUT_READY_PATTERNS.TAIL_LINES` non-empty lines, and no * spinner / busy status bar / runtime-specific "not ready" marker is * present in that tail. Concrete runtimes extend the marker list via * {@link getNotReadyMarkers}. * * @param screen - Captured terminal screen (ANSI is stripped defensively) * @returns true when the runtime is idle at its prompt */ isReadyForInput(screen: string): boolean; /** * Runtime-specific substrings (lower-case, whitespace-collapsed) whose * presence in the screen tail means the TUI is not yet accepting input * even though a prompt glyph may already be painted. Override per runtime. * * @returns Markers to treat as "not ready" */ protected getNotReadyMarkers(): readonly string[]; /** * Hook called after the runtime is ready but before prompts are sent. * Override in concrete classes for runtime-specific post-initialization steps * (e.g., Gemini CLI directory allowlist additions). * * Default implementation is a no-op. * * @param sessionName - PTY session name * @param targetProjectPath - Optional target project path for the agent (where MCP configs should be written). * Falls back to this.projectRoot if not provided. */ postInitialize(sessionName: string, targetProjectPath?: string, additionalAllowlistPaths?: string[], browserAutomationOverride?: boolean): Promise; /** * Clear cached detection results for a session */ clearDetectionCache(sessionName: string): void; /** * Get runtime configuration */ getRuntimeConfiguration(): RuntimeConfig | null; /** * Ensure MCP server configuration exists at the given config file path. * * Reads `enableBrowserAutomation` from settings, builds the required MCP servers * list, reads any existing config at `configFilePath`, merge-only adds missing * servers, and writes the result back via `atomicWriteJson`. * * Parent directories of `configFilePath` are created automatically with * `fs.mkdir({ recursive: true })`. * * Preserves any existing user-configured MCP servers (never overwrites). * Errors are non-fatal and logged as warnings. * * @param configFilePath - Absolute path to the MCP config JSON file * (e.g., `/project/.mcp.json` or `/project/.gemini/settings.json`) * @param projectPath - Project directory path, used only for log context * @param browserAutomationOverride - Per-agent override for browser automation. * When provided, takes precedence over global settings. * `undefined` means use global setting. */ protected ensureMcpConfig(configFilePath: string, projectPath: string, browserAutomationOverride?: boolean): Promise; /** * Verify MCP config file exists and contains expected servers. * * Reads the config file back after write and checks that the expected * server names are present. Non-fatal — logs warnings on failure. * * @param configFilePath - Absolute path to the MCP config JSON file * @param expectedServers - Server names expected to be present (e.g. ['playwright']) * @returns True if all expected servers are present, false otherwise */ protected verifyMcpConfig(configFilePath: string, expectedServers: string[]): Promise; /** * Check if the Crewly Pro addon is installed. * * Pro addon provides its own Crewly in Chrome bridge, so Playwright MCP * should not be injected when it is present. * * @returns True if crewly-pro addon manifest exists */ protected isProAddonInstalled(): boolean; /** * Initialize runtime configuration from config file */ private initializeRuntimeConfig; /** * Get runtime configuration with fallback */ protected getRuntimeConfig(): RuntimeConfig; /** * Load initialization script commands from file */ protected loadInitScript(scriptName: string): Promise; /** * Send shell commands to session */ protected sendShellCommandsToSession(sessionName: string, commands: string[], targetPath?: string): Promise; } /** * Shell line that puts the backend's own Node directory and the Crewly user * npm prefix first on PATH, after the login shell's rc files ran. * * @param nodeBinDir - Directory of the Node binary Crewly runs on * @returns `export PATH=...` line (single-quoted dirs, `$PATH` kept) */ export declare function runtimePathExport(nodeBinDir?: string): string; /** A start-up dialog with one right answer for an unattended agent. */ export interface KnownRuntimePrompt { /** Stable id for logs */ id: string; /** All must match the whitespace-stripped screen */ match: readonly RegExp[]; /** Keys that answer it ('Enter' or a key name the session helper knows) */ keys: readonly string[]; } //# sourceMappingURL=runtime-agent.service.abstract.d.ts.map