/** * CortexAgent: production-grade wrapper for pi-agent-core's Agent. * * Composes ContextManager, EventBridge, BudgetGuard, system prompt assembly, * and lifecycle management into a single orchestrator class. * * This is the primary public API of the @animus-labs/cortex package. * * Lifecycle: CREATED -> ACTIVE -> DESTROYED * - CREATED: After construction. Slots can be set, but no loops have run. * - ACTIVE: After first prompt(). The agent is running or idle between prompts. * - DESTROYED: After destroy(). All resources released. prompt() throws. * * References: * - cortex-architecture.md * - system-prompt.md * - model-tiers.md * - cross-platform-considerations.md */ import { ContextManager } from './context-manager.js'; import type { AgentContext, AgentMessage, AgentStateAccessor } from './context-manager.js'; import { EventBridge } from './event-bridge.js'; import type { PiEventSource } from './event-bridge.js'; import { BudgetGuard } from './budget-guard.js'; import { McpClientManager } from './mcp-client.js'; import { CompactionManager } from './compaction/index.js'; import type { ObservationalMemoryState, ObservationEvent, ReflectionEvent } from './compaction/observational/types.js'; import { SubAgentManager } from './sub-agent-manager.js'; import { SkillRegistry } from './skill-registry.js'; import type { CortexModel } from './model-wrapper.js'; import type { CortexTool } from './tool-contract.js'; import type { CortexAgentConfig, CortexLifecycleState, CortexUsage, SessionUsage, ClassifiedError, AgentTextOutput, CompactionResult, CompactionTarget, CompactionDegradedInfo, CompactionExhaustedInfo, McpTransportConfig, McpConnectionState, McpToolCallProgress, LoadedSkill, SubAgentSpawnConfig, SubAgentSnapshot, ThinkingLevel, ModelThinkingCapabilities } from './types.js'; /** * Minimal Agent interface matching pi-agent-core's Agent class. * Defined here to avoid a hard runtime dependency; the real Agent is * passed at construction time. */ export interface PiAgent extends AgentStateAccessor, PiEventSource { prompt(input: string, options?: { update?: (event: unknown) => void; signal?: AbortSignal; }): Promise; abort(): void; waitForIdle(): Promise; reset(): void; /** * Inject a steering message into the running agentic loop. * Pi applies steering after the current assistant turn and tool batch finish. * Only effective while a prompt() call is in progress. */ steer(message: { role: string; content: string; }): void; /** * Context transformation hook installed by Cortex. */ transformContext?: (messages: unknown[]) => Promise; } /** * Minimal Model interface matching pi-ai's Model type. * Only the fields we need for provider validation and utility model resolution. */ export interface PiModel { provider: string; name: string; contextWindow?: number; [key: string]: unknown; } /** * Minimum context window floor in tokens. * Below this, the system prompt alone may not fit, breaking the agent. */ export declare const MINIMUM_CONTEXT_WINDOW = 16384; /** * Operational reminder appended to tool results when working tags are enabled. * Exported so consumers (e.g., cortex-code TUI) can strip it from display text. */ export declare const TOOL_RESULT_WORKING_TAGS_REMINDER = "[Do not narrate. If analyzing these results, use tags. Only text outside tags is shown to the user.]"; type CacheRetention = 'none' | 'short' | 'long'; interface DirectCompletionOptions { cacheRetention?: CacheRetention; /** * Optional abort signal to cancel an in-flight completion. When the signal * fires, the call rejects with an `AbortError` (an Error whose `name` is * `'AbortError'`) so callers can distinguish caller-initiated cancellation * from a genuine failure. Applies to `directComplete`, `structuredComplete`, * and `utilityComplete`. */ signal?: AbortSignal; } export declare class CortexAgent { private static readonly globalTrackedPids; private static exitHandlerInstalled; private readonly agent; private readonly contextManager; private readonly eventBridge; private readonly budgetGuard; private readonly config; private readonly logger; private readonly promptDiagnostics; private workingTagsEnabled; private readonly workingDirectory; private readonly envOverrides; private lifecycleState; private currentBasePrompt; private currentSystemPrompt; private _cacheRetention; private _activePromptCacheRetention; private _sessionId; private primaryModel; private primaryPiModel; private resolvedUtilityModel; private resolvedUtilityPiModel; private utilityModelManualOverride; private readonly registeredTools; private readonly toolRuntime; private currentPiTools; private readonly _deferredToolsEnabled; private readonly _deferMcp; private readonly _deferredAlwaysLoad; private readonly deferredToolRegistry; private readonly persistResult?; private readonly toolCategories?; private readonly toolResultThresholds?; private readonly compactionManager; private _contextWindowLimit; private loopCompleteHandlers; private errorHandlers; private beforeCompactionHandlers; private compactionErrorHandlers; private compactionDegradedHandlers; private compactionExhaustedHandlers; private turnCompleteHandlers; private subAgentSpawnedHandlers; private subAgentCompletedHandlers; private subAgentFailedHandlers; private backgroundResultDeliveryHandlers; private pendingBackgroundResults; private eventUnsubscribers; private abortController; private _isPrompting; private readonly trackedPids; private readonly mcpClientManager; private readonly subAgentManager; private readonly skillRegistry; private loadSkillTool; private skillBuffer; private _prePromptMessageCount; private _cacheBreakpointIndices; private _lastDirectUsage; private _sessionUsage; /** * Create a CortexAgent. Prefer CortexAgent.create(). * * @param agent - A pi-agent-core Agent instance * @param config - CortexAgent configuration * @throws Error if the utility model violates the same-provider constraint */ private constructor(); /** * Send a prompt to the agent and run the agentic loop. * * Transitions from CREATED to ACTIVE on first call. * Catches errors, classifies them, and emits onError. * * @param input - The prompt text * @returns The agent's response (opaque, from pi-agent-core) * @throws Error if the agent has been destroyed */ prompt(input: string, options?: DirectCompletionOptions): Promise; /** * Inject a steering message into the running agentic loop. * Queues the message for pi-agent-core to inject after the current * assistant turn and any current tool batch finish. * Only effective while a prompt() call is in progress. * * No-op if the agent is not currently running a prompt. * * @param message - The message content to inject */ steer(message: string): void; private buildDirectCompletionOptions; /** * Make a direct LLM completion call using the primary model. * NOT an agentic tool-use loop. Used for structured output phases * like THOUGHT and REFLECT where a single LLM response is needed * without tool execution. * * Dynamically imports pi-ai's complete() function. If pi-ai is not * installed, throws a clear error. * * @param context - System prompt and messages for the completion * @returns The response text from the LLM * @throws Error if pi-ai is not installed or the call fails */ directComplete(context: { systemPrompt: string; messages: unknown[]; }, options?: DirectCompletionOptions): Promise; /** * Make a structured output LLM call using the tool-call-as-structured-output pattern. * * Defines a tool whose input_schema matches the desired output structure, * passes it via pi-ai's complete() with tools, and extracts the tool call * arguments as the structured result. This works across all providers that * support tool use (Anthropic, OpenAI, Google, Mistral, etc.) without * needing provider-specific structured output parameters. * * @param context - System prompt and messages (accepts pi-ai native message format) * @param schema - Tool schema defining the structured output shape (TypeBox or JSON Schema) * @param toolName - Name for the virtual tool (default: 'structured_output') * @param toolDescription - Description for the virtual tool * @returns The parsed tool call arguments, or null if the model didn't call the tool */ structuredComplete(context: { systemPrompt: string; messages: unknown[]; }, schema: unknown, toolName?: string, toolDescription?: string, options?: DirectCompletionOptions): Promise | null>; /** * Extract tool call arguments from a pi-ai AssistantMessage response. */ /** * Check if a pi-ai result represents a silent error. * * Pi-ai's stream wrapper catches errors and resolves the promise with an * output object that has stopReason 'error' and errorMessage set, instead * of throwing. This means callers never see the error unless they check. * This method surfaces those silent errors as thrown exceptions so they * propagate properly (e.g., to retry logic). */ private checkForSilentError; /** * Surface a caller-aborted completion as a throwable `AbortError`. * * Pi-ai resolves (it does not throw) with stopReason 'aborted' when the * supplied AbortSignal fires mid-flight. We also check the signal directly * to cover the race where abortion lands just after a result resolved: the * caller signalled they no longer want this completion, so we discard it. * Throwing an Error named 'AbortError' lets callers distinguish caller * cancellation from genuine failure via the standard `err.name` idiom, and * must be checked before `checkForSilentError` so an abort is never * misreported as an LLM error. */ private throwIfAborted; private extractToolCallArgs; private static loadAgentClass; private static buildPiAgentConfig; private static normalizePermissionDecision; /** * Extract safe, identifying fields from tool args for logging. * Returns paths, commands, and patterns without content or results. */ private static summarizeToolArgs; private static buildPermissionReason; private static wireManagedPiAgent; private static createManagedAgent; /** * Create a CortexAgent with a pi-agent-core Agent constructed internally. * * This eliminates the consumer's need to import pi-agent-core directly. * The factory dynamically imports pi-agent-core and pi-ai, resolves the * model, creates the internal Agent, and returns a fully configured * CortexAgent. * * @param config - CortexAgent configuration (model, tools, options) * @returns A new CortexAgent wrapping an internally-created pi-agent-core Agent * @throws Error if pi-agent-core or pi-ai is not installed */ static create(config: CortexAgentConfig & { /** * Additional consumer-provided tools to register alongside the built-in tools. * Built-in tools (Read, Write, Edit, Glob, Grep, Bash, WebFetch, TaskOutput) * are registered automatically. Tools passed here must use Cortex's * execute(params, context?) contract. Wrap raw pi-agent-core tools with * fromPiAgentTool() before passing them to CortexAgent.create(). */ tools?: CortexTool[]; /** @deprecated Use initialBasePrompt instead. */ systemPrompt?: string; }): Promise; /** * Get the ContextManager for slot and ephemeral context management. */ getContextManager(): ContextManager; /** * Compose a system prompt from the application/base prompt plus * Cortex operational sections. * * Base prompt content comes FIRST (identity, persona, domain instructions). * Cortex appends operational rules AFTER (system rules, tool guidance, * safety, environment info). * * @param basePrompt - The application/base prompt content * @returns The assembled system prompt */ composeSystemPrompt(basePrompt: string): string; /** * @deprecated Use composeSystemPrompt() for pure composition or * setBasePrompt() to update the live agent state. */ buildSystemPrompt(basePrompt: string): string; /** * Set the application/base prompt and update the live agent state. * * Preserves conversation history. Non-destructive. */ setBasePrompt(basePrompt: string): string; /** * @deprecated Use setBasePrompt(). */ rebuildSystemPrompt(newBasePrompt: string): void; /** * Get the current application/base prompt. */ getBasePrompt(): string; /** * Get the current assembled system prompt. */ getCurrentSystemPrompt(): string; /** * Get the Cortex operational system prompt sections as structured data. * Useful for context snapshot / inspector tooling. */ getSystemPromptSections(): Array<{ name: string; content: string; }>; private applySystemPrompt; private refreshPromptState; private hasConfiguredSystemPrompt; /** * Get conversation history, excluding the slot region. * * Returns messages from position slotCount through the end of the array. * The consumer snapshots this to their storage. * * @returns Conversation history messages (everything after slots) */ getConversationHistory(): AgentMessage[]; /** * Restore conversation history after the slot region. * * Splices saved messages into the array starting at position slotCount, * replacing any existing conversation history. * * @param messages - Previously saved conversation history */ restoreConversationHistory(messages: AgentMessage[]): void; /** * Get the primary model. */ getModel(): CortexModel; /** * Get the resolved utility model. */ getUtilityModel(): CortexModel; /** * Peek at the utility model that auto-resolution would produce for the * current primary model, without applying it or clearing a manual override. * * For providers Cortex cannot enumerate (e.g. Ollama and custom * OpenAI-compatible endpoints), this returns the primary model itself, * mirroring the runtime fallback in resolveUtilityModels(). Consumers use * this to label an "Auto" choice in a UI with the model that will actually * run, instead of re-deriving it from a model list that the agent never sees. */ getAutoResolvedUtilityModel(): CortexModel; /** * Hot-swap the primary model without restarting the agent. * Used when the user changes their provider/model in settings. * * @param model - The new CortexModel to use */ setModel(model: CortexModel): void; /** * Explicitly set the utility model, overriding auto-resolution. * The utility model must be from the same provider as the primary model. * After calling this, setModel() will NOT auto-resolve the utility model. * Call resetUtilityModel() to restore auto-resolution. * * @param model - The CortexModel to use as the utility model */ setUtilityModel(model: CortexModel): void; /** * Reset the utility model to auto-resolution based on the primary model's provider. * Clears any manual override set by setUtilityModel(). */ resetUtilityModel(): void; /** * Whether the utility model has been manually overridden. */ isUtilityModelOverridden(): boolean; /** * Change the thinking/reasoning effort level. * Maps Cortex's "max" to pi-agent-core's "xhigh" internally. * * @param level - The consumer-facing thinking level */ setThinkingLevel(level: ThinkingLevel): void; /** * Get the current thinking/reasoning effort level. * Maps pi-agent-core's "xhigh" back to Cortex's "max". * * @returns The current consumer-facing thinking level, or 'medium' if not set */ getThinkingLevel(): ThinkingLevel; get isWorkingTagsEnabled(): boolean; setWorkingTagsEnabled(enabled: boolean): void; /** * Get the thinking capabilities of the current primary model. * Uses pi-ai model metadata to expose the exact supported thinking levels. * * @returns Capabilities object describing thinking support */ getModelThinkingCapabilities(): Promise; /** * Clamp a requested thinking level to the current model's supported levels. * Pi may clamp upward before clamping downward, so callers should surface * clamping to users when latency or cost could change. */ clampThinkingLevel(level: ThinkingLevel): Promise; /** * Set the cache retention policy for the agentic loop. * Used by the consumer to switch between short/long cache based on * tick interval and provider. Managed agents pass this through the pi-ai * stream options for each provider request. */ setCacheRetention(value: 'none' | 'short' | 'long'): void; /** * Get the current cache retention policy. * Returns null if not yet resolved (pi-ai will use its own default). */ getCacheRetention(): 'none' | 'short' | 'long' | null; /** * Set the stable cache/session key forwarded to the provider as its * prompt_cache_key. Use a value stable across calls that share a prefix. * Pass null to clear (the provider then generates its own per-request key). */ setSessionId(value: string | null): void; /** * Get the current cache/session key, or null if unset. */ getSessionId(): string | null; /** * Process a tool result through the result-persistence interceptor. * Delegates to the shared `processToolResult` helper, supplying instance * state (persistResult callback, tool categories, threshold overrides). */ private applyToolResultPersistence; /** * Pi 0.74 snapshots the agent state when prompt() starts. When ToolSearch * loads deferred tools mid-run, keep the active loop context in sync so the * next automatic provider call sees the newly loaded schemas. */ private syncActiveLoopTools; /** * Update the agent's tool set by adapting Cortex's canonical in-process * tool contract to pi-agent-core's raw execute signature. * * When deferred tools are enabled, this also partitions the union of * registered + MCP tools into a "loaded" set (sent to the API) and a * "deferred" set (announced by name in the `_available_tools` slot). */ refreshTools(): void; /** * Register an additional consumer-provided tool at runtime. * Useful for dynamic tool management (e.g., enabling a tool after agent * creation based on user permission changes). */ addConsumerTool(tool: CortexTool): void; /** * Remove a consumer-provided tool by name at runtime. * Built-in tools cannot be removed. */ removeConsumerTool(toolName: string): void; /** * Partition candidate tools into "loaded" (sent on every turn) and * "deferred" (announced by name in the `_available_tools` slot). * * A tool is deferred when: * - It is not in the consumer's `alwaysLoad` allowlist, AND * - Its `alwaysLoad` field is not true, AND * - It has not been discovered via ToolSearch this session, AND * - Either `tool.shouldDefer === true` OR * (`tool.isMcp === true` AND `_deferMcp` is true) */ private partitionDeferredTools; private shouldDeferTool; /** * Update the `_available_tools` slot if its content has actually changed. * Skipping no-op writes preserves the prompt cache: identical bytes mean * the cached prefix stays valid for the next API call. */ private updateAvailableToolsSlot; /** * Make a utility completion call using the utility model. * Convenience wrapper for internal operations (WebFetch summarization, * safety classification, etc.). * * Analogous to directComplete() but uses the utility model (smaller, cheaper) * instead of the primary model. Dynamically imports pi-ai's complete() function. * * @param context - System prompt and messages for the completion * @returns The response text from the LLM * @throws Error if pi-ai is not installed or the call fails */ utilityComplete(context: { systemPrompt: string; messages: Array<{ role: string; content: string; }>; }, options?: DirectCompletionOptions): Promise; /** * Abort the current agentic loop without destroying the agent. * The agent remains usable for subsequent prompts. */ abort(): Promise; /** * Ordered cleanup of all resources. * Called by the consumer when the agent is no longer needed. * * Steps: * 1. Abort any in-progress agentic loop * 2. Wait for idle (with timeout) * 3. Cancel all sub-agents (stub, wired in Phase 4) * 4. Emit onLoopComplete for final checkpoint (best-effort) * 5. Close all MCP client connections (kills stdio subprocesses, closes HTTP) * 6. Clear skill buffer (stub, wired in Phase 4) * 7. Unsubscribe all event listeners * 8. Clear agent state * 9. Mark as destroyed * * @param timeoutMs - Maximum time to wait for cleanup (default: 8000ms) */ destroy(timeoutMs?: number): Promise; /** * Whether the agent is currently running an agentic loop. */ get isRunning(): boolean; /** * Get the current lifecycle state. */ get state(): CortexLifecycleState; /** * The number of messages in agent.state.messages before the current * prompt() call. Used by the cache breakpoint system to distinguish * "old history" (cacheable) from "new tick content" (ephemeral). */ get prePromptMessageCount(): number; /** * Register a handler for when the full agentic loop completes. * Maps to pi-agent-core's agent_end event. * The consumer uses this to trigger conversation history checkpoints. */ onLoopComplete(handler: () => void): void; /** * Register a handler for classified errors during the agentic loop. */ onError(handler: (error: ClassifiedError) => void): void; /** * Register a handler called before compaction starts. * Handler is awaited. The consumer should flush critical state * (e.g., observational memory) before history is compacted. * * NOT called during mid-loop emergency truncation (Layer 3). */ onBeforeCompaction(handler: (target: CompactionTarget) => Promise): void; /** * Register a handler called after compaction completes. * The consumer uses this to re-seed messages from messages.db, * update internal state, or perform other post-compaction work. */ onPostCompaction(handler: (result: CompactionResult) => void): void; /** * Register a handler for compaction errors. */ onCompactionError(handler: (error: Error) => void): void; /** * Register a handler called when Layer 2 compaction failed and Layer 3 * (emergency truncation) was used as fallback. The session continues * but context quality is degraded. */ onCompactionDegraded(handler: (info: CompactionDegradedInfo) => void): void; /** * Register a handler called when all compaction layers have failed. * The consumer should take recovery action (e.g., pause heartbeat, * abort the session, or notify the user). */ onCompactionExhausted(handler: (info: CompactionExhaustedInfo) => void): void; /** * Register a handler for turn completion with parsed working tag output. */ onTurnComplete(handler: (output: AgentTextOutput) => void): void; /** * Register a handler for sub-agent spawn events. */ onSubAgentSpawned(handler: (taskId: string, instructions: string, background: boolean) => void): void; /** * Register a handler for sub-agent completion events. */ onSubAgentCompleted(handler: (taskId: string, result: string, status: string, usage: unknown) => void): void; /** * Register a handler for sub-agent failure events. */ onSubAgentFailed(handler: (taskId: string, error: string) => void): void; /** * Register a handler that fires when background sub-agent results are about * to be delivered to the parent agent, restarting its agentic loop. * Consumers can use this to update UI state (show spinners, etc.). */ onBackgroundResultDelivery(handler: (taskIds: string[]) => void): void; /** * Get the EventBridge for direct event access. * Consumers that need raw event data (for logging) can subscribe directly. */ getEventBridge(): EventBridge; /** * Get the BudgetGuard for inspecting turn/cost state. */ getBudgetGuard(): BudgetGuard; /** * Get the usage data from the most recent directComplete() or * structuredComplete() call. Returns null if no usage was available * or no call has been made yet. * * This is the primary mechanism for consumers (like the backend pipeline) * to capture per-phase usage for persistence. The value is reset to null * at the start of each directComplete/structuredComplete call. */ getLastDirectUsage(): CortexUsage | null; /** * Get accumulated session usage (cost, turns, token breakdown). * * Unlike BudgetGuard (which resets per agentic loop), this accumulates * across the entire session lifetime. Consumers can persist this value * and restore it via restoreSessionUsage() after loading a saved session. */ getSessionUsage(): SessionUsage; /** * Restore session usage from consumer-provided data. * * Call this after restoreConversationHistory() when resuming a saved session. * Values are added to any usage already accumulated (in case turns ran * before the restore call). */ restoreSessionUsage(usage: SessionUsage): void; /** * Update the post-hoc current-context token count from LLM usage data. * Called by the consumer after each LLM call with the input_tokens * from AssistantMessage.usage. */ updateCurrentContextTokenCount(inputTokens: number): void; /** * Get the post-hoc current-context token count from the most recent parent turn. */ get currentContextTokenCount(): number; /** * Estimate the current context tokens Cortex would send on the next parent LLM call. * * This is a heuristic estimate of the transformed context snapshot built from: * - the current system prompt * - slots and conversation history * - ephemeral context * - background task state * - loaded skills * * The estimate is compared against the most recent post-hoc parent turn usage * and the larger value is returned. This matches the compaction manager's * internal decision logic. */ estimateCurrentContextTokens(): number; /** * Set the context window size (from model metadata). * If a contextWindowLimit is set, the effective value will be * min(limit, contextWindow) with a floor of MINIMUM_CONTEXT_WINDOW. */ setContextWindow(contextWindow: number): void; /** * Set a user-configured limit on the context window. * The effective context window becomes min(limit, model.contextWindow) * with a floor of MINIMUM_CONTEXT_WINDOW (16K tokens). * Pass null to remove the limit and use the model's full context window. */ setContextWindowLimit(limit: number | null): void; /** * Get the raw user-configured context window limit (null = no limit). */ get contextWindowLimit(): number | null; /** * Get the effective context window after applying the limit and floor. */ get effectiveContextWindow(): number; /** * Get the model's actual context window (unaffected by consumer limits). */ get modelContextWindow(): number; /** * Recompute and apply the effective context window from the model * and the user-configured limit. */ private _updateEffectiveContextWindow; /** * Signal how recently the user last interacted. * Used by the compaction system to adjust thresholds: * - Recent interaction: use normal thresholds * - No interaction for a while: compact more aggressively * * The backend calls this during GATHER when a message-triggered tick fires * (set to Date.now()). For interval ticks, it is not called, so the * timestamp ages naturally. */ setLastInteractionTime(timestamp: number): void; /** * Cap a tool result at insertion time. If the result exceeds * maxResultTokens, truncates to head+tail bookend format. * Call this when tool results enter conversation history. */ capToolResult(content: string): string; /** * Get the observational memory state for session persistence. * Returns null if not using the observational strategy. */ getObservationalMemoryState(): ObservationalMemoryState | null; /** * Restore observational memory state from a previous session. * Must be called after restoreConversationHistory(). */ restoreObservationalMemoryState(state: ObservationalMemoryState): void; /** * Force a synchronous observation cycle. * Useful after critical user corrections. */ triggerObservation(): Promise; /** * Register a handler for observation events. * Fires when messages are compressed into observations. */ onObservation(handler: (event: ObservationEvent) => void): void; /** * Register a handler for reflection events. * Fires when the reflector condenses observations. */ onReflection(handler: (event: ReflectionEvent) => void): void; /** * Run end-of-tick compaction check. Call after EXECUTE completes, * before the next tick starts. Returns the CompactionResult if * Layer 2 compaction ran, null otherwise. */ checkAndRunCompaction(): Promise; /** * Get the CompactionManager for advanced use. */ getCompactionManager(): CompactionManager; /** * Get the configured environment variable overrides. * Consumers use this when creating built-in tools (e.g., BashToolConfig.envOverrides) * to ensure all subprocess environments include these overrides. */ getEnvOverrides(): Record | undefined; /** * Get the McpClientManager for managing MCP server connections. * Consumers use this to connect/disconnect plugin tool servers * and to retrieve discovered tools. */ getMcpClientManager(): McpClientManager; /** * Connect to an MCP server and discover its tools. * Convenience wrapper around mcpClientManager.connect(). * * @param serverName - Unique name for this server (used for tool namespacing) * @param config - Transport configuration (stdio or http) */ connectMcpServer(serverName: string, config: McpTransportConfig): Promise; /** * Disconnect from an MCP server and remove its tools. * Convenience wrapper around mcpClientManager.disconnect(). * * @param serverName - The server name to disconnect */ disconnectMcpServer(serverName: string): Promise; /** * Snapshot of every MCP server this agent is currently connected to (or * attempting to reconnect to). The shape is deliberately read-only: use * {@link connectMcpServer} / {@link disconnectMcpServer} to mutate. The * consumer (`cortex-code`'s hot-reload watcher) uses this to compute the * diff between desired (config files) and current state between turns. */ getMcpServerStates(): McpConnectionState[]; /** * Register a callback fired when MCP tool servers emit * `notifications/progress` during a long-running `tools/call`. Consumers * wire this to whatever UI affordance they have for "still waiting…". */ setMcpToolCallProgressHandler(handler: ((progress: McpToolCallProgress) => void) | undefined): void; /** * Get all tools from all sources: built-in tools registered on the * pi-agent-core Agent, plus MCP-wrapped tools from connected servers. * * Returns only the MCP-wrapped tools. Built-in tools are registered * directly on the Agent and are not included here. */ getMcpTools(): CortexTool[]; /** * Get the composed transformContext hook for the pi-agent-core Agent. * * Composes five steps in order: * 0. Tier 1 insertion-time cap (mutates source messages) * 1. Insert ephemeral + skill buffer at the boundary position * (after old history, before new tick content) for cache optimization * 2. Message sanitization * 3. Compaction (all three layers: microcompaction, summarization, failsafe) * 4. Compute API message indices for cache breakpoints BP2 and BP3 * * Cache breakpoint strategy: * Anthropic allows 4 cache_control breakpoints. Pi-ai sets up to 3 * (system prompt, last tool definition, last user message). The * onPayload hook strips the tool breakpoint and adds BP2 (after last * slot) and BP3 (old history boundary), keeping the total at 4. * * By inserting ephemeral at the boundary instead of the end, the * conversation history prefix becomes stable across ticks, enabling * cache reads on ~128K of tokens instead of only ~5.5K. * * The hook is async because Layer 2 compaction may require an LLM call * for summarization. Pi-agent-core's transformContext supports async hooks. * * @returns An async transformContext function for the Agent constructor */ getTransformContextHook(): (context: AgentContext) => Promise; private buildAgentContextSnapshot; private buildInjectedAndSanitizedContextSnapshot; /** * Compute API message indices for cache breakpoints BP2 and BP3. * * Walks the transformed message array and counts how messages will appear * in the final Anthropic API params after convertMessages processes them. * convertMessages skips empty user messages and merges consecutive * toolResult messages into single user messages. * * BP2: placed after the last slot message. Slots are stable across the * entire session lifetime, so everything up to BP2 is always cached. * BP3: placed at the old history boundary (before injected ephemeral/skills). * Old history is stable across ticks within the same session, so this * is a "ratcheting" breakpoint that advances as history grows. * * @param messages - The transformed message array (after injection + sanitization) * @param slotCount - Number of slot messages at the start of the array * @returns API indices for BP2 and BP3, or -1 if not applicable */ private computeCacheBreakpointIndices; /** * Create built-in tool instances, excluding any in the disabled set. */ private createBuiltinTools; /** * Normalize registered tools so this agent owns fresh mutable state and * everything stored internally uses Cortex's canonical tool contract. */ private normalizeRegisteredTools; private resolveModels; /** * Resolve the utility model from the public CortexModel boundary. * If 'default' or undefined, look up the provider default and preserve * the raw provider-specific fields from the primary pi-ai model. */ private inferDefaultUtilityModel; private resolveUtilityModels; /** * Build the Environment section of the system prompt. * Dynamically generated from the actual runtime environment. */ private buildEnvironmentSection; /** * Detect the current shell. */ private detectShell; /** * Wire internal event handlers to the EventBridge. * Maps bridge events to consumer-registered callbacks. */ private wireInternalEvents; /** * Extract text from a turn_end event's raw data. */ private extractTurnTextFromEvent; /** * Extract input token count from a turn_end event's raw data. * * Pi-agent-core's turn_end event carries the AssistantMessage which * includes usage.input from pi-ai. This is the total input token count * for that LLM call (an assignment, not a delta). * * Follows the same multi-pattern extraction approach as BudgetGuard's * extractCost to handle variations in pi-agent-core event structure. */ private extractInputTokens; /** * Compute total input tokens from a pi-ai Usage object. * With prefix caching, tokens shift between input/cacheRead/cacheWrite. * The real context size is `input + cacheRead + cacheWrite`. * Falls back to `totalTokens - output` if individual fields are missing. */ private computeTotalInput; /** * Check if the agent was aborted (user or system cancellation). * Only returns true for actual abort/cancel signals, not arbitrary errors. */ private isAborted; /** * Check if the agent is currently idle (not running a loop). * Tracked via a boolean flag set at prompt() entry and cleared in its finally block. */ private isIdle; /** * Extract text content from a pi-ai AssistantMessage response. * * Pi-ai's complete() returns an AssistantMessage with either: * - A string `content` field * - A `content` array with typed parts (text, thinking, toolCall) */ private extractTextFromAssistantMessage; /** * Extract a summary of tool calls from a child agent's conversation history. * Scans for toolResult messages and builds a name + duration list. */ private extractToolCallSummary; /** * Extract usage data from a pi-ai AssistantMessage response. * * The AssistantMessage.usage field has the structure: * { input, output, cacheRead, cacheWrite, totalTokens, * cost: { input, output, cacheRead, cacheWrite, total } } * * Returns null if usage data is not present or not in the expected format. */ private extractUsageFromAssistantMessage; /** * Perform ordered cleanup. */ private orderedCleanup; /** * Force-kill all tracked subprocesses. * Synchronous, last-resort fallback for unclean exits. */ private forceKillAll; /** * Set up process exit handler for orphaned subprocess cleanup (Level 3 safety net). */ private setupExitHandler; private static handleProcessExit; private trackPid; private untrackPid; /** * Get the SkillRegistry for add/remove/query operations. */ getSkillRegistry(): SkillRegistry; /** * Pre-load a skill into the ephemeral context for the current loop. * Same path as the load_skill tool, but triggered by the consumer. * No LLM turn is consumed. */ loadSkill(name: string, args?: string): Promise; /** * Clear the skill buffer. The consumer should call this at the start * of each tick (before pre-loading skills for the new loop). * Cortex cannot auto-clear because it has no concept of tick boundaries, * and clearing at prompt() start would wipe consumer pre-loaded skills. */ clearSkillBuffer(): void; /** * Get the current skill buffer contents. */ getSkillBuffer(): LoadedSkill[]; /** * Set consumer-provided variables for ${VAR} substitution in skills. * Merged with Cortex built-ins (SKILL_DIR, ARGUMENTS). * Consumer variables take precedence on collision. * Call this each tick during GATHER to update runtime values. */ setPreprocessorVariables(variables: Record): void; /** * Set consumer-provided context that will be passed to skill scripts. * Merged with Cortex built-in fields (skillDir, args, scriptArgs). * Consumer fields take precedence on collision. * Call this each tick during GATHER to update runtime values. */ setScriptContext(context: Record): void; /** * Get the SubAgentManager for direct sub-agent tracking. */ getSubAgentManager(): SubAgentManager; /** * Spawn a background sub-agent and return its task ID immediately. * Used by consumers that manage delegated work outside the SubAgent tool. */ spawnBackgroundSubAgent(params: Omit): Promise<{ taskId: string; }>; /** * Snapshot of all currently running sub-agents, including live cost and * activity. Read-only; safe to call from anywhere (e.g. budget accounting * or status surfaces). Returns an empty array when none are running. */ getActiveSubAgents(): SubAgentSnapshot[]; /** * Rebuild the load_skill tool's description with the current available * skills summary. Called automatically when skills are added/removed * via the registry's onChange callback. */ private rebuildLoadSkillDescription; private buildAvailableSkillsSummary; /** * Push a loaded skill to the buffer with deduplication. * If the same skill is loaded twice, the second replaces the first. */ private pushToSkillBuffer; /** * Skill injection is now handled inline in getTransformContextHook() * at the boundary position for cache optimization. This method is * retained as a no-op for backward compatibility. * @deprecated Skill injection moved to getTransformContextHook() boundary insertion */ private injectSkillBuffer; /** * Wire the sub-agent manager's lifecycle hooks to CortexAgent event handlers. */ private wireSubAgentHooks; /** * Build a short summary of tool args for background state display. */ private summarizeToolArgs; /** * Build a block describing running sub-agents and * background bash processes. Returns null if nothing is running. * Called from transformContext before each LLM call. */ private buildBackgroundTaskState; /** * Spawn a foreground sub-agent and block until completion. * Used by the SubAgent tool. */ private spawnForegroundSubAgentInternal; /** * Spawn a background sub-agent and return the task ID immediately. */ private spawnBackgroundSubAgentInternal; /** * Handle a background task (sub-agent or Bash command) completing. If the * agent is currently prompting, queue it for delivery after the current * loop. If the agent is idle, deliver immediately by restarting the loop. * * Shared by background sub-agents (resolved promise) and backgrounded Bash * commands (process `close` callback), so both wake the loop the same way. */ private deliverOrQueueBackgroundCompletion; /** * Drain all pending background completions by restarting the agentic loop * with a combined message. Called at the end of prompt(). Completions that * were already observed in the meantime (Bash poll/kill) are skipped, and if * nothing remains to deliver the loop is not restarted. */ private drainPendingBackgroundResults; /** * Format a pending completion into the message delivered to the loop, or * null if there is nothing to deliver. Marks Bash tasks as notified so the * same completion is never delivered twice. */ private formatPendingCompletion; private formatBashCompletion; private formatBackgroundResult; private fireBackgroundResultDeliveryHandlers; private createChildAgent; private resolveChildPromptSeed; /** * Run a sub-agent to completion. Handles result delivery to the manager. */ private runSubAgent; /** * Build child agent config from parent config and spawn params. * Budget guards can be tightened, not loosened. */ private buildChildAgentConfig; /** * Build the tool set for a child agent. * SubAgent and load_skill are always excluded from child agents. */ private buildChildToolSet; /** * Generate a unique task ID for sub-agents. */ private generateTaskId; } export {}; //# sourceMappingURL=cortex-agent.d.ts.map