import type { StructuredLogger } from './logger.js'; import { CLIAgentResponse } from './types/brutalist.js'; import { ModelResolver } from './model-resolver.js'; import { classifyRouting, isRoutedClient } from './cli-adapters/routing.js'; export { classifyRouting, isRoutedClient }; import type { MetricsRegistry } from './metrics/index.js'; /** * Sanitize a caller-supplied client id into a stable attribution key. * * CONTRACT — the CORE transform (trim → slice(0,80) → replace * /[^a-zA-Z0-9._:-]/g with '-') MUST stay byte-for-byte identical to * packages/github-action/src/index.ts's sanitizeClientId. The end-to-end * attribution depends on it: the action sanitizes the id into both * knownClientIds AND BRUTALIST_CLAUDE_CLIENTS[].id, the mcp-server (here) * re-sanitizes and emits it, and the orchestrator clamps the emitted id * against the known set. Any divergence in the CORE transform silently * breaks clientId attribution. The two implementations intentionally * differ ONLY in the empty-result fallback (mcp-server → 'client', * action → 'custom-claude'); a characterization test in BOTH packages * pins the shared transform table so drift breaks a test. */ export declare function sanitizeClientId(id: string): string; /** * Cap on custom Claude clients accepted from the BRUTALIST_CLAUDE_CLIENTS env * array — parity with the roast `clients[]` schema cap (tool-config.ts * `.max(16)`) and the GitHub Action's MAX_CUSTOM_CLAUDE_CLIENTS. Guards against * a runaway operator-supplied array spawning an unbounded number of processes. */ export declare const MAX_CLAUDE_CLIENTS = 16; export declare function parseDefaultClientsFromEnv(log: StructuredLogger): CLIClientSpec[]; /** * Resolve a raw client spec into a normalized spec the claude adapter can * consume branchlessly. Stamps routingMode + the auth-inheritance, * small-fast-model, and config-dir decisions ONCE, so every consumer (the * adapter env overlay, the pre-flight probe, and config-dir provisioning) * shares a single source of truth. Non-claude providers pass through * untouched; the function is idempotent for them. */ export declare function normalizeClaudeClient(c: CLIClientSpec, procEnv: NodeJS.ProcessEnv, log: StructuredLogger): CLIClientSpec; export type BrutalistPromptType = 'code' | 'codebase' | 'architecture' | 'idea' | 'research' | 'data' | 'security' | 'product' | 'infrastructure' | 'debate' | 'dependencies' | 'fileStructure' | 'gitHistory' | 'testCoverage' | 'design' | 'legal'; export declare const CLAUDE_ALIASES: readonly ["opus", "sonnet", "haiku"]; export interface CLIAgentOptions { workingDirectory?: string; timeout?: number; clis?: ('claude' | 'codex' | 'agy')[]; clients?: CLIClientSpec[]; activeClient?: CLIClientSpec; analysisType?: BrutalistPromptType; models?: { claude?: string; codex?: string; agy?: string; }; onStreamingEvent?: (event: StreamingEvent) => void; progressToken?: string | number; onProgress?: (progress: number, total: number | undefined, message: string) => void; sessionId?: string; requestId?: string; debateMode?: boolean; mcpServers?: string[]; /** Resolved wall-clock budget threaded to adapters by the orchestrator. */ effectiveTimeoutMs?: number; /** * Optional scoped logger threaded into provider.buildCommand / decodeOutput. * When present, adapters emit via this logger (narrowed with forOperation) * instead of the root logger import. Absent → fall back to root logger. * Pattern A per phase.md: preserves stateless adapter singletons in * cli-adapters/index.ts. */ log?: StructuredLogger; } export interface CLIClientSpec { id: string; provider: 'claude' | 'codex' | 'agy'; model?: string; smallFastModel?: string; baseUrl?: string; authToken?: string; authTokenEnv?: string; configDir?: string; env?: Record; includeProcessAuth?: boolean; /** * Tool/sandbox containment for a Claude-provider client. 'hardened' * is a backward-compatible label: for routed clients it suppresses * caller-requested MCP servers, but is not a shell/network sandbox. Bash, * WebFetch, and WebSearch remain available in both modes. 'standard' * restores requested MCP for a trusted endpoint. */ containment?: 'hardened' | 'standard'; workingDirectory?: string; timeout?: number; mcpServers?: string[]; routingMode?: 'native' | 'routed'; inheritNativeAuth?: boolean; resolvedAuthToken?: string; resolvedSmallFastModel?: string; resolvedConfigDir?: string; } /** * Constructor-deps bag for CLIAgentOrchestrator. * * All fields optional — characterization tests construct * `new CLIAgentOrchestrator()` with no args. In production the * composition root passes the full set; in tests, instrumentation is a * no-op and `this.log` falls back to the root logger via emitLog(). */ export interface CLIAgentOrchestratorDeps { modelResolver?: ModelResolver; metrics?: MetricsRegistry; log?: StructuredLogger; } export interface StreamingEvent { type: 'agent_start' | 'agent_progress' | 'agent_complete' | 'agent_error'; agent: 'claude' | 'codex' | 'agy' | 'system'; content?: string; timestamp: number; sessionId?: string; metadata?: Record; } export interface CLIContext { availableCLIs: ('claude' | 'codex' | 'agy')[]; } export declare class CLIAgentOrchestrator { private defaultTimeout; private defaultWorkingDir; private cliContext; private cliContextCached; private cliContextCacheTime; private readonly CLI_CACHE_TTL; private runningCLIs; private readonly MAX_CONCURRENT_CLIS; readonly modelResolver: ModelResolver; private readonly metrics?; private readonly log?; private streamingBuffers; private readonly STREAMING_FLUSH_INTERVAL; private readonly MAX_CHUNK_SIZE; private readonly HEARTBEAT_INTERVAL; private lastHeartbeat; /** * Accepts a deps bag OR a bare `ModelResolver` (legacy positional form) * OR nothing (characterization-test harnesses). The `instanceof ModelResolver` * branch preserves the pre-observability signature. */ constructor(deps?: CLIAgentOrchestratorDeps | ModelResolver); /** * Return the injected scoped logger if present, otherwise the root * logger singleton. Keeps un-injected (test) instances working while * scoping production emissions with `module='cli-orchestrator'`. */ private emitLog; /** * Heuristic for classifying a spawnAsync error as a timeout. * Centralized so all outcome paths share the same detection logic. * * Matches any of: * - execError.code === 'ETIMEDOUT' (Node's timeout code on some paths) * - execError.killed === true (child_process kill after SIGTERM/SIGKILL * escalation when the timeout timer fired — see spawnAsync timer block) * - execError.message matching /timed out|timeout/i (spawnAsync rejects * with "Command timed out after ..." on timer expiry) */ private isTimeoutError; private parseNDJSON; private decodeClaudeStreamJson; private extractCodexAgentMessage; private emitThrottledStreamingEvent; private buildCLICommand; detectCLIContext(): Promise; selectSingleCLI(preferredCLI?: 'claude' | 'codex' | 'agy', analysisType?: BrutalistPromptType): 'claude' | 'codex' | 'agy'; private _executeCLI; executeClaudeCode(userPrompt: string, systemPromptSpec: string, options?: CLIAgentOptions): Promise; executeCodex(userPrompt: string, systemPromptSpec: string, options?: CLIAgentOptions): Promise; executeSingleCLI(cli: 'claude' | 'codex' | 'agy', userPrompt: string, systemPromptSpec: string, options?: CLIAgentOptions): Promise; private waitForAvailableSlot; executeCLIAgents(cliAgents: string[], systemPrompt: string, userPrompt: string, options?: CLIAgentOptions): Promise; executeCLIAgent(agent: string, systemPrompt: string, userPrompt: string, options?: CLIAgentOptions): Promise; executeBrutalistAnalysis(analysisType: BrutalistPromptType, primaryContent: string, systemPromptSpec: string, context?: string, options?: CLIAgentOptions): Promise; /** * Render the per-critic failure blocks shared by the all-failed and * partial-failure synthesis paths. Emits the canonical * BRUTALIST_CLI_BEGIN / optional BRUTALIST_CLI_CLIENT / END markers so a * failure is attributed to its named client — a lone GLM failure must not * read as bare CLAUDE (D4) — and shows "glm (CLAUDE)" only when the client * id differs from the provider (no redundant "claude (CLAUDE)"). */ private renderFailedCriticBlocks; synthesizeBrutalistFeedback(responses: CLIAgentResponse[], analysisType: string): string; private buildPromptParts; /** * Assemble the full user prompt with the context appended VERBATIM (no * per-critic fit). The reference assembly (also exercised by the prompt tests); * the live fan-out builds per-critic prompts via fitUserPrompt instead. */ private constructUserPrompt; /** * Build one critic's final prompt, fitting the supplementary context to THAT * critic's fidelity window (N5). A large `context` (e.g. a diff) otherwise * overflows a small critic (agy ~135k, claude ~200k without [1m]); each critic * keeps the head that fits its own window and reads the target for the rest * (critics are agentic). * * The fit is SKIPPED whenever the orchestrator/Action is driving — signalled by * BRUTALIST_FORCE_CLIS (an isolated per-participant stream) OR an injected diff * (BRUTALIST_PR_DIFF_FILE / BRUTALIST_PR_DIFF, set for every action run * INCLUDING the collapsed all-critics pass, which does NOT set FORCE_CLIS). * That layer already sized the diff per-participant; re-fitting here with a * possibly-different headroom (the action honors context-headroom-pct; the root * hardcodes 15) would double-trim the smallest critic and silently drop hunks. * * NOTE: abstract domains (idea/research/data/…) also carry the payload in * primaryContent (untrimmed, inside specificPrompt), so this fit only * de-duplicates their redundant Context: copy — it neither loses their data nor * prevents their overflow. N5 targets the repo-grounded (diff/filesystem) case. */ private fitUserPrompt; } //# sourceMappingURL=cli-agents.d.ts.map