/** * AgentSession - Core abstraction for agent lifecycle and session management. * * This class is shared by interactive, print, ACP, and SDK-hosted session callers. * It encapsulates: * - Agent state access * - Event subscription with automatic session persistence * - Model and thinking level management * - Compaction (manual and auto) * - Bash execution * - Session switching and branching * * Modes use this class and add their own I/O layer on top. */ import { type Agent, type AgentContext, type AgentEvent, type AgentLoopConfig, type AgentMessage, type AgentState, type AgentTool, type MidRunMaintenanceOutcome, type RunSettlementProof, type StablePrefixSnapshot, ThinkingLevel } from "@gajae-code/agent-core"; import type { AttemptScope } from "@gajae-code/agent-core/attempt-scope"; import { type CompactionResult, type EmergencyCompactionSample } from "@gajae-code/agent-core/compaction"; import type { AssistantMessage, AttemptScopeRef, Effort, ImageContent, Message, MessageAttribution, Model, ProviderSessionState, ServiceTier, SimpleStreamOptions, TextContent, ToolCall, ToolChoice, Usage, UsageReport } from "@gajae-code/ai/core"; import { type BtwTextExchange } from "./btw-contract"; export interface ForkContextSeedMetadata { sourceSessionId: string; parentMessageCount: number; includedMessages: number; skippedMessages: number; approximateTokens: number; maxMessages: number; maxTokens: number; skippedReasons: Record; } export interface PurgeQueuedCustomMessagesResult { agentSteering: number; agentFollowUp: number; pendingNextTurn: number; displaySteering: number; displayFollowUp: number; totalExecutable: number; } export type AbortOutcome = { kind: "settled"; } | { kind: "timeout"; } | { kind: "error"; cause: unknown; }; export type CancelAndSubmitOutcome = { kind: "submitted"; } | { kind: "refused"; reason: "duplicate" | "compaction"; } | { kind: "rolled_back"; outcome: Extract; }; export interface ForkContextSeed { messages: Message[]; agentMessages: AgentMessage[]; metadata: ForkContextSeedMetadata; appendOnlyPrefixSnapshot?: StablePrefixSnapshot; } export interface ForkContextSeedOptions { maxMessages: number; maxTokens: number; preserveLatestUser?: boolean; signal?: AbortSignal; } import type { AuthCredentialSelector } from "@gajae-code/ai/core"; import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../async"; import type { Rule } from "../capability/rule"; import { type ModelRegistry } from "../config/model-registry"; import { type ResolvedModelRoleValue, type ScopedModelSelection } from "../config/model-resolver"; import { type PromptTemplate } from "../config/prompt-templates"; import type { Settings, SkillsSettings } from "../config/settings"; import { RawSseDebugBuffer } from "../debug/raw-sse-buffer"; import { type PythonResult } from "../eval/py/executor"; import { type BashArtifactSaveResult, type BashResult } from "../exec/bash-executor"; import type { TtsrManager } from "../export/ttsr"; import type { LoadedCustomCommand } from "../extensibility/custom-commands"; import type { CustomTool } from "../extensibility/custom-tools/types"; import type { ExtensionRunner } from "../extensibility/extensions"; import { type CompactOptions, type ContextUsage, type ExtensionTranscriptEntry } from "../extensibility/extensions/types"; import type { GjcRuntimeSnapshotProvider } from "../extensibility/gjc-plugins/runtime-quarantine"; import type { SessionSwitchEvent } from "../extensibility/shared-events"; import { type Skill, type SkillWarning } from "../extensibility/skills"; import { type FileSlashCommand } from "../extensibility/slash-commands"; import { type MemoryGuardClaimsLease } from "../gjc-runtime/memory-guard-owner-claims"; import { GoalRuntime } from "../goals/runtime"; import type { Goal, GoalModeState } from "../goals/state"; import type { HindsightSessionState } from "../hindsight/state"; import type { MemoryBackend } from "../memory-backend/types"; import { type WorkflowGateEmitter } from "../modes/shared/agent-wire/workflow-gate-broker"; import type { PlanModeState } from "../plan-mode/state"; import { type AgentRegistry } from "../registry/agent-registry"; import type { LazyService } from "../runtime/lazy-service"; import type { NetworkPrewarmRuntime } from "../runtime/network-prewarm-service"; import type { WorkspaceTreeRuntime } from "../runtime/workspace-tree-service"; import { MCPManager } from "../runtime-mcp/manager"; import type { NotificationSessionController } from "../sdk/bus/session-control"; import type { SecretObfuscator } from "../secrets/obfuscator"; import { type DiscoverableTool, type DiscoverableToolSearchIndex } from "../tool-discovery/tool-index"; import type { AskAnswerSource, ToolSession } from "../tools"; import type { CheckpointState } from "../tools/checkpoint"; import { type TodoItem, type TodoPhase } from "../tools/todo-write"; import { type WorkspaceTree } from "../workspace-tree"; import { type DefaultModelSelectionResult } from "./default-model-selection"; import { type ConfiguredFallbackChain, type FallbackChainRuntimeState } from "./fallback-chain-controller"; export { DefaultModelSelectionRecoveryError } from "./default-model-selection"; import type { ClientBridge, ClientBridgePermissionOption, ClientBridgePermissionOutcome, ClientBridgePermissionToolCall } from "./client-bridge"; import { type ContributionPrepOptions, type ContributionPrepResult } from "./contribution-prep"; import type { MemoryGuardRestoreResult } from "./memory-guard-checkpoint-participant"; import { type CustomMessage } from "./messages"; import type { BranchSummaryEntry, CompactionEntry, NewSessionOptions, PreparedNewSession, RecoveryHydrationContext, RecoveryHydrationPromotionFence, SessionContext, SessionManagerCloseOutcome, SessionMemoryStats } from "./session-manager"; import { SessionManager } from "./session-manager"; import { ToolChoiceQueue } from "./tool-choice-queue"; import { YieldQueue } from "./yield-queue"; /** * Classify an async-result delivery against terminal-abort ownership: * - "ordinary": no owned-completion envelope — deliver as before. * - "fresh": an exact registered owned-completion the owning scope's gate * authorizes as a fresh-turn resume (scope:"turn", policy enabled). * - "drop": a recognized owned-completion the gate denies — scope:"owned" * (policy disabled, stopped work must never call followUp/prompt), a * forged/unregistered tuple, or an envelope whose terminal scope no longer * exists. Dropped entries never reach the agent (AC 36 zero final calls * from stopped work), even if a delivery races the settlement purge. */ export declare function ownedCompletionResumeAction(message: AgentMessage): "ordinary" | "fresh" | "drop"; /** Session-specific events that extend the core AgentEvent */ export type AutoCompactionContinuationSkipReason = "auto_continue_disabled_non_resumable_tail"; export type AgentSessionEvent = AgentEvent | { type: "auto_compaction_start"; reason: "threshold" | "overflow" | "idle"; action: "context-full" | "handoff"; } | { type: "auto_compaction_end"; action: "context-full" | "handoff"; result: CompactionResult | undefined; aborted: boolean; willRetry: boolean; errorMessage?: string; /** True when compaction was skipped for a benign reason (no model, no candidates, nothing to compact). */ skipped?: boolean; continuationSkipReason?: AutoCompactionContinuationSkipReason; } | { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string; unbounded?: boolean; } | { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string; } | { type: "model_fallback_switched"; eventId: string; from: string; to: string; reason: string; role: string; scope: string; activeIndex: number; chainLength: number; attemptsUsed: number; } | { type: "ttsr_triggered"; rules: Rule[]; } | { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number; } | { type: "todo_auto_clear"; } | { type: "irc_message"; message: CustomMessage; } | { type: "subagent_steer_message"; message: CustomMessage; } | { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string; } | { type: "thinking_level_changed"; thinkingLevel: ThinkingLevel | undefined; } | { type: "goal_updated"; goal: Goal | null; state?: GoalModeState; }; /** Listener function for agent session events */ export type AgentSessionEventListener = (event: AgentSessionEvent) => void; export type AsyncJobSnapshotItem = Pick; export interface AsyncJobSnapshot { running: AsyncJobSnapshotItem[]; recent: AsyncJobSnapshotItem[]; delivery: AsyncJobDeliveryState; } export interface RetainedMemorySample { tuiChatChildren?: number; tuiCachedRenderBytes?: number; } export interface AgentSessionConfig { agent: Agent; sessionManager: SessionManager; settings: Settings; /** The session's REQUESTED effective agent directory, independent of the * global Settings singleton (which may be reused across sessions). */ agentDir?: string; /** Lazy memory backend service; omitted callers receive a session-local default. */ memoryBackend?: LazyService; /** Lazy workspace-tree service; omitted callers retain the legacy direct-scan path. */ workspaceTreeService?: LazyService; /** Lazy model-host prewarm service used for first-request latency diagnostics. */ networkPrewarmService?: LazyService; /** Shared Gate-A-eligible notification session controller, when this host supports it. */ notificationSessionController?: NotificationSessionController; /** Models to cycle through with Alt+N (from --models flag) */ scopedModels?: ScopedModelSelection[]; /** Initial session thinking selector. */ thinkingLevel?: ThinkingLevel; /** Prompt templates for expansion */ promptTemplates?: PromptTemplate[]; /** File-based slash commands for expansion */ slashCommands?: FileSlashCommand[]; /** Extension runner (created in main.ts with wrapped tools) */ extensionRunner?: ExtensionRunner; /** Override first-party worker integration dispatch for embedded hosts and deterministic lifecycle tests. */ workerIntegrationRequest?: (signal: AbortSignal) => Promise; /** Bound terminal worker-integration settlement for embedded hosts and deterministic lifecycle tests. */ workerIntegrationTimeoutMs?: number; /** Loaded skills (already discovered by SDK) */ skills?: Skill[]; /** Skill loading warnings (already captured by SDK) */ skillWarnings?: SkillWarning[]; /** Custom commands (TypeScript slash commands) */ customCommands?: LoadedCustomCommand[]; skillsSettings?: SkillsSettings; /** Model registry for API key resolution and model discovery */ modelRegistry: ModelRegistry; /** Task recursion depth for nested sessions. Top-level sessions use 0. */ taskDepth?: number; /** Controls whether workflow gates are published to the process-wide endpoint registry. */ workflowGatePublication?: "endpoint" | "local"; /** Tool registry for LSP and settings */ toolRegistry?: Map; /** * Tool objects this session's builder constructed from built-in descriptors, captured * before any extension, MCP, or dynamically registered tool could claim their registry * names. Absent means no provenance was proven and every tool reports as `custom`. */ builtinToolIdentities?: ReadonlySet; /** Tool-session factory context used to lazily attach workflow-gate-only tools. */ workflowGateToolSession?: ToolSession; /** Current session pre-LLM message transform pipeline */ transformContext?: (messages: AgentMessage[], signal?: AbortSignal, scope?: AttemptScopeRef) => AgentMessage[] | Promise; /** Provider payload hook used by the active session request path */ onPayload?: SimpleStreamOptions["onPayload"]; /** Provider response hook used by the active session request path */ onResponse?: SimpleStreamOptions["onResponse"]; /** Raw SSE hook used by the active session request path */ onSseEvent?: SimpleStreamOptions["onSseEvent"]; /** Per-session raw SSE diagnostic buffer */ rawSseDebugBuffer?: RawSseDebugBuffer; /** Current session message-to-LLM conversion pipeline */ convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise; /** System prompt builder that can consider tool availability. Returns ordered provider-facing blocks. */ rebuildSystemPrompt?: (toolNames: string[], tools: Map, candidateModel?: Model) => Promise<{ systemPrompt: string[]; }>; /** Initial workspace tree snapshot used for the first volatile per-turn context message. */ workspaceTree?: WorkspaceTree; /** Called after a lazy first-turn scan publishes the resolved tree to the stable prompt builder. */ onWorkspaceTreeReady?: (tree: WorkspaceTree) => void | Promise; /** Rebuild the SSH tool from current capability discovery results. */ reloadSshTool?: () => Promise; requestedToolNames?: ReadonlySet; /** Optional per-session allowlist for tools exposed through search_tool_bm25. */ discoverableToolAllowedNames?: readonly string[]; /** Optional accessor for live MCP server instructions, injected as untrusted user-role request data. */ getMcpServerInstructions?: () => Map | undefined; /** Enable hidden-by-default MCP tool discovery for this session. */ mcpDiscoveryEnabled?: boolean; /** Effective discovery mode normalized by the session factory. */ discoveryMode?: "off" | "mcp-only" | "all"; /** MCP tool names to activate for the current session when discovery mode is enabled. */ initialSelectedMCPToolNames?: string[]; /** Keep persisted MCP names until a deferred exact catalog becomes available. */ preserveUnavailableInitialMCPToolSelection?: boolean; /** Built-in discoverable tool names restored for the current all-discovery session. */ initialSelectedDiscoveredBuiltinToolNames?: string[]; /** Discoverable built-ins active for configured or explicit reasons independently of persisted discovery selection. */ initialBaselineDiscoveredBuiltinToolNames?: string[]; /** Whether an MCP selection was explicitly supplied to the constructor, including an empty selection. */ initialMCPToolSelectionIsExplicit?: boolean; /** Whether a discoverable built-in selection was explicitly supplied to the constructor, including an empty selection. */ initialDiscoveredBuiltinToolSelectionIsExplicit?: boolean; /** Whether constructor-provided MCP selections should be persisted immediately. */ persistInitialMCPToolSelection?: boolean; /** Whether constructor-provided discovered-built-in selections should be persisted immediately. */ persistInitialDiscoveredBuiltinToolSelection?: boolean; /** Explicit MCP authority to write for a new session; distinct from active fallback tools. */ initialPersistedMCPToolNames?: string[]; /** Explicit discovered built-in authority to write for a new session; distinct from active fallback tools. */ initialPersistedDiscoveredBuiltinToolNames?: string[]; /** Immutable predecessor authority while a recovery host is read-only. */ recoveryHydrationContext?: RecoveryHydrationContext; /** MCP server names whose tools should seed discovery-mode sessions whenever those servers are connected. */ defaultSelectedMCPServerNames?: string[]; /** MCP tool names that should seed brand-new sessions created from this AgentSession. */ defaultSelectedMCPToolNames?: string[]; /** MCP capabilities that are always active and never part of persisted user selection. */ mandatoryMCPToolNames?: string[]; /** TTSR manager for time-traveling stream rules */ ttsrManager?: TtsrManager; /** Secret obfuscator for deobfuscating streaming edit content */ obfuscator?: SecretObfuscator; /** Logical owner for retained Python kernels created by this session. */ evalKernelOwnerId?: string; /** * AsyncJobManager that this session installed as the process-global instance. * Only set for top-level sessions; subagents inherit the parent's manager and * **MUST NOT** dispose it on their own teardown. */ ownedAsyncJobManager?: AsyncJobManager; /** False when a child borrows its parent's manager and must not dispose it. */ disposeAsyncJobManager?: boolean; /** Cheap TUI retained-memory counters; absent for headless sessions. */ retainedMemorySampler?: () => RetainedMemorySample; /** * MCPManager whose lifecycle this session owns (top-level sessions that * connected plugin-bundle MCP servers). Only the owned manager is * disconnected on dispose; subagents and callers that merely observe the * process-global manager **MUST NOT** dispose it on their own teardown. */ ownedMcpManager?: MCPManager; /** Optional startup dependency that must settle before the first provider turn. */ startupTurnBarrier?: Promise; /** Optional fork-context seed used to initialize a child session before its first prompt. */ forkContextSeed?: ForkContextSeed; /** Optional provider state override. Fork-context children should omit this by default. */ providerSessionState?: Map; /** Agent identity (registry id like "0-Main" or "3-Alice") used for IRC routing. */ agentId?: string; /** Shared agent registry (for forwarding IRC observations to the main session UI). */ agentRegistry?: AgentRegistry; /** * Override the provider-facing session ID for all API requests from this session. * When absent, `sessionManager.getSessionId()` is used. Needed when benchmark or * SDK callers issue probes / prewarming with an explicit `--provider-session-id` * so that credential sticky selection is consistent with the session's streaming calls. */ providerSessionId?: string; /** Optional auth-selection identity, distinct from logical/canonical and provider-cache identity. */ credentialSessionId?: string; /** Opaque credential-store authority fingerprint for durable numeric session pins. */ credentialStoreIdentity?: string; /** Optional provider-facing cache identity, distinct from logical session identity. */ providerCacheSessionId?: string; /** Explicit provider affinity whose persisted transcript path scopes async ownership. */ asyncJobProviderSessionId?: string; } export interface AgentSessionMemoryGuardRestoreInput extends Omit { staged: Extract; claimsLease: MemoryGuardClaimsLease; claimsStateDir: string; } export interface AgentMemoryGuardPromotionFence extends RecoveryHydrationPromotionFence { readonly claimsLease: MemoryGuardClaimsLease; } export type AgentMemoryGuardRestoreResult = { kind: "staged"; session: AgentSession; promotionFence: AgentMemoryGuardPromotionFence; } | { kind: "blocked"; reason: "transcript-mismatch" | "hydration-context-mismatch" | "claim-mismatch" | "agent-messages-mismatch"; }; type MidRunMaintenanceLifecycle = Parameters>[1]; /** Options for AgentSession.prompt() */ export interface PromptOptions { /** Whether to expand file-based prompt templates (default: true) */ expandPromptTemplates?: boolean; /** Image attachments */ images?: ImageContent[]; /** When streaming, how to queue the message: "steer" (interrupt) or "followUp" (wait). */ streamingBehavior?: "steer" | "followUp"; /** When set to "sequential", this follow-up is delivered one prompt at a time even if followUpMode is "all". */ followUpQueuePolicy?: "respect-mode" | "sequential"; /** Optional tool choice override for the next LLM call. */ toolChoice?: ToolChoice; /** Send as developer/system message instead of user. Providers that support it use the developer role; others fall back to user. */ synthetic?: boolean; /** Explicit billing/initiator attribution for the prompt. Defaults to user prompts as `user` and synthetic prompts as `agent`. */ attribution?: MessageAttribution; /** Skip pre-send compaction checks for this prompt (internal use for maintenance flows). */ skipCompactionCheck?: boolean; /** * Invoked after all prompt preflight checks pass and immediately before agent execution begins. * Cancellation before this callback rejects the prompt. * Prefer `onPreflightAcceptCommit` for async durable acceptance (#3031/#3032). */ onPreflightAccepted?: () => void; /** * Awaitable durable-accept fence. Called after preflight and immediately before * agent execution begins. SDK bus installs a closure that fsyncs acceptance. */ onPreflightAcceptCommit?: () => void | Promise; /** Skill-only: prepared metadata before the durable fence (path/lineCount/cleanedArgs). */ onSkillPrepared?: (meta: { name: string; path: string; lineCount?: number; cleanedArgs?: string; }) => void; /** Optional invocation-scoped cancellation fence used before an accepted skill starts execution. */ preflightSignal?: AbortSignal; } /** Result from a handoff operation. */ export interface HandoffResult { document: string; savedPath?: string; } export interface SessionHandoffOptions { autoTriggered?: boolean; signal?: AbortSignal; } /** Result from cycleModel() */ export interface ModelCycleResult { model: Model; thinkingLevel: ThinkingLevel | undefined; /** Whether cycling through scoped models (--models flag) or all available */ isScoped: boolean; } export type ModelChangeCause = "user-selection" | "profile-activation" | "fallback-switch" | "restore" | "rollback" | "startup-override" | "temporary-operation"; export type TemporaryModelReason = "plan-mode" | "context-promotion" | "temporary-cycle" | "profile-preview" | "extension-temporary" | "other"; /** Opaque handle for a non-destructive temporary provider-session scope. */ export interface TemporaryProviderSessionScope { readonly reason: TemporaryModelReason; } /** Result from cycleRoleModels() */ export interface RoleModelCycleResult { model: Model; thinkingLevel: ThinkingLevel | undefined; role: string; } /** Session statistics for /session command */ export interface SessionStats { sessionFile: string | undefined; sessionId: string; userMessages: number; assistantMessages: number; toolCalls: number; toolResults: number; totalMessages: number; tokens: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number; }; premiumRequests: number; cost: number; costBreakdown?: Usage["cost"]; sessionMemory: SessionMemoryStats; } export type EphemeralTurnPurpose = "btw" | "background"; interface EphemeralTurnBaseArgs { promptText: string; onTextDelta?: (delta: string) => void; signal?: AbortSignal; } export interface BtwRoleTextMessage { role: "user" | "assistant"; text: string; } export interface BtwConversationScope { model: Model; systemPrompt: string[]; messages: BtwRoleTextMessage[]; thinkingLevel: ThinkingLevel; hideThinkingSummary: boolean; serviceTier: ServiceTier | undefined; credentialSessionId: string; providerAffinitySessionId: string; sideSessionId: string; } export interface BtwTurnCapture { question: string; scope: BtwConversationScope | undefined; } export type EphemeralTurnArgs = (Omit & { purpose: "btw"; turn: BtwTurnCapture; contextExchanges?: readonly BtwTextExchange[]; }) | (EphemeralTurnBaseArgs & { purpose?: "background"; /** Internal caller-supplied, non-persistent context such as the IRC roster. */ prependMessages?: AgentMessage[]; /** Revalidates optional caller context after asynchronous boundaries. */ prependMessagesValid?: () => boolean; /** * An existing IRC roster claim owned by the caller. `undefined` makes this * turn claim and commit its own roster candidate; `null` opts out. */ ircRosterClaim?: IrcRosterClaim | null; }); interface EphemeralTurnResult { replyText: string; assistantMessage: AssistantMessage; } export interface AgentBashArtifactStore { saveArtifact(content: string, toolType: string): Promise; getArtifactPath(id: string): Promise; } export declare function saveAgentBashOriginalArtifact(store: AgentBashArtifactStore, originalText: string): Promise; type IrcRosterClaim = { token: symbol; signature: string; epoch: number; message: CustomMessage; }; export type QueuedMessageEditMode = "steer" | "followUp"; export interface QueuedMessageEditEntry { id: string; text: string; mode: QueuedMessageEditMode; label: string; } /** A custom message contributed at the before-agent-start point. */ export type BeforeAgentStartInternalMessage = Pick; /** * Internal (first-party, non-user-hook) contributor invoked at the active * before-agent-start point alongside the extension runner. Returns an optional * custom message to append to the prompt context. Errors are nonfatal. */ export type BeforeAgentStartContributor = (event: { prompt: string; images?: ImageContent[]; sessionId: string | undefined; }) => Promise; export declare class WorkerIntegrationRequestScheduler { #private; readonly request: (signal: AbortSignal) => Promise; readonly timeoutMs: number; constructor(request: (signal: AbortSignal) => Promise, timeoutMs?: number); enqueue(): void; flush(): Promise; } export type StreamingEditParsedToolCall = { toolCall: ToolCall; path: string; resolvedPath: string; diff?: string; op?: string; rename?: string; }; export type StreamingEditParsedCacheEntry = { version: string; parsed: StreamingEditParsedToolCall | undefined; }; export declare function getStreamingEditToolCallForEvent(event: AgentEvent, cache: Map, resolvePath: (filePath: string) => string | undefined): StreamingEditParsedToolCall | undefined; /** Test-only counters for AgentSession event fan-out hot-path assertions. */ export declare const __agentSessionPerfCounters: { listenerSnapshotRebuilds: number; messageUpdateExtensionQueues: number; reset(): void; }; export declare function buildContextInjectionSignature(kind: string, parts: readonly string[]): string; export declare class StreamingEditFileCache { #private; get(path: string): string | undefined; set(path: string, content: string): void; delete(path: string): void; clear(): void; has(path: string): boolean; get totalBytes(): number; } export interface DefaultFallbackRuntimeState { chain: ConfiguredFallbackChain; controller: FallbackChainRuntimeState; exhaustedLastTurn: boolean; } export declare class AgentSession { #private; readonly agent: Agent; sessionManager: SessionManager; readonly settings: Settings; /** * The session's effective agent directory: its REQUESTED directory when * provided, else the global Settings singleton's (which may be reused * across sessions and therefore not the requesting session's own). */ getSessionAgentDir(): string; readonly memoryBackend: LazyService; readonly notificationSessionController: NotificationSessionController | undefined; readonly taskDepth: number; readonly yieldQueue: YieldQueue; readonly configWarnings: string[]; readonly streamingEditDebugCounters: { guardRuns: number; processedChars: number; checkedRemovedLines: number; fullChecks: number; nonEditDeterminations: number; }; readonly rawSseDebugBuffer: RawSseDebugBuffer; extendStartupTurnBarrier(barrier: Promise): void; constructor(config: AgentSessionConfig); /** * Resolves when constructor-time workflow-gate tool restoration (ask * registration plus durable active-workflow attachment) has settled. The * SDK factory awaits this so a resumed canonical workflow session is * returned with `ask` already resident. */ get workflowGateToolRestoration(): Promise; /** Model registry for API key resolution and model discovery */ get modelRegistry(): ModelRegistry; /** Advance the tool-choice queue and return the next directive for the upcoming LLM call. */ nextToolChoice(): ToolChoice | undefined; /** * Force the next model call to target a specific active tool, then terminate * the agent loop. Pushes a two-step sequence [forced, "none"] so the model * calls exactly the forced tool once and then cannot call another. */ setForcedToolChoice(toolName: string): void; /** The tool-choice queue: forces forthcoming tool invocations and carries handlers. */ get toolChoiceQueue(): ToolChoiceQueue; /** Current skill prompt executing in this session, if any. */ getActiveSkillState(): { skill: string; session_id?: string; } | undefined; /** * Live prompt marker or restored durable workflow — the effective state the * cwd-local mutation guard must honor after resume. */ getEffectiveActiveWorkflowSkillState(): { skill: string; sessionId: string; } | undefined; /** Replace the session-owned MCP manager after a cwd rescope. */ replaceOwnedMcpManager(next: MCPManager | undefined): Promise; /** Swap named custom/project tools after a cwd rescope. */ replaceNamedCustomTools(previousNames: readonly string[], nextTools: CustomTool[]): Promise; /** Best-effort accessor for the active skill's `current_phase` field from * its persisted mode-state file. Used by the `skill` tool to enforce the * terminal-phase chain guard. Returns undefined when no active skill is * recorded or the mode-state file is missing/unreadable; callers should * treat undefined as a non-terminal phase (refuses to chain). */ getActiveSkillPhase(): string | undefined; /** Provider-facing ask metadata must expose only the active deep-interview phase. */ getDeepInterviewAskStage(): "topology" | "post-topology" | undefined; /** Peek the in-flight directive's invocation handler for use by the resolve tool. */ peekQueueInvoker(): ((input: unknown) => Promise | unknown) | undefined; peekStandingResolveHandler(): ((input: unknown) => Promise | unknown) | undefined; setStandingResolveHandler(handler: ((input: unknown) => Promise | unknown) | null): void; setSdkPlanModeHandler(handler: ((on: boolean) => Promise) | null): void; /** Provider-scoped mutable state store for transport/session caches. */ get providerSessionState(): Map; /** Suspend provider state without closing it while a temporary model is active. */ beginTemporaryProviderSessionScope(reason: TemporaryModelReason): TemporaryProviderSessionScope; /** Restore a temporary scope, unwinding any auto-owned scopes above it. */ restoreTemporaryProviderSessionScope(token: TemporaryProviderSessionScope): Promise; /** Promote a temporary scope. The suspended provider state is permanently closed. */ commitTemporaryProviderSessionScope(token: TemporaryProviderSessionScope): boolean; buildForkContextSeed(options: ForkContextSeedOptions): Promise; getHindsightSessionState(): HindsightSessionState | undefined; setHindsightSessionState(state: HindsightSessionState | undefined): HindsightSessionState | undefined; /** TTSR manager for time-traveling stream rules */ get ttsrManager(): TtsrManager | undefined; /** Whether a TTSR abort is pending (stream was aborted to inject rules) */ get isTtsrAbortPending(): boolean; /** Whether the plan-mode → compaction transition's expected internal abort is * pending. Consumed by `#handleAgentEvent` to stamp `SILENT_ABORT_MARKER` * on the next aborted assistant message_end; cleared unconditionally by * `InteractiveMode.#approvePlan`'s `finally` block. */ get isPlanCompactAbortPending(): boolean; /** Arm the silent-abort marker for the next aborted assistant message_end. * Caller MUST clear via `clearPlanCompactAbortPending()` in a `finally` * to guarantee no leak. */ markPlanCompactAbortPending(): void; /** Unconditionally clear the silent-abort flag. Idempotent: safe when the * flag was never set OR was already consumed by `#handleAgentEvent`. */ clearPlanCompactAbortPending(): void; /** Register a compact display string for a custom message that the caller is * about to dispatch via `promptCustomMessage` / `sendCustomMessage`. * Returns a stable tag the caller MUST embed in * `CustomMessage.details.__pendingDisplayTag` so the agent-side * `message_start` handler can remove the matching display entry when the * queued message is consumed. * * Does NOT push to the agent's steering/followUp queue — that happens * separately inside `sendCustomMessage`. */ enqueueCustomMessageDisplay(text: string, mode: "steer" | "followUp"): string; getAgentId(): string | undefined; get isDisposed(): boolean; registerToolSessionCleanup(cleanup: () => Promise | void): () => void; registerToolSessionTransitionCleanup(cleanup: () => Promise | void): () => void; getAsyncJobSnapshot(options?: { recentLimit?: number; }): AsyncJobSnapshot | null; /** * Emit a UI-only notice to the session. Surfaces in interactive mode as a * `showWarning` / `showError` / `showStatus` line; non-interactive modes * receive the event through the normal subscribe stream. * * Notices are NOT added to agent state and never reach the LLM — use this * for out-of-band conditions the user should see but the model shouldn't * react to (e.g. background queue flush failures). */ emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void; /** Await all transformations queued by externally emitted tool results. */ awaitPendingContextTransformations(): Promise; /** * Subscribe to agent events. * Session persistence is handled internally (saves messages on message_end). * Multiple listeners can be added. Returns unsubscribe function for this listener. */ subscribe(listener: AgentSessionEventListener): () => void; /** * Remove all listeners, flush pending writes, and disconnect from agent. * Call this when completely done with the session. */ dispose(): Promise; /** * Strict writer close for ACP session delete. On the first attempt it flushes * pending writes, then returns the certainty-aware close outcome so the caller * can block destructive mutation on a non-`closed` result. When the manager * retains a retryable writer (a prior `close_failed_retryable`), the flush is * NOT repeated: the underlying writer rejects flushes while in the retryable * state, and `SessionManager.closeStrict()` owns the flush/close sequencing so * a second call can return `closed` once the OS close lands. */ closeWriterStrict(): Promise; /** * Bounded, best-effort teardown of the subprocess-spawning resources this session * owns: the browser tool's headless/spawned Chrome and the Python eval kernel + JS VM * contexts. Unlike {@link dispose}, this touches only child processes and is time-boxed, * so a top-level `SIGINT`/`SIGTERM`/`SIGHUP` handler can run it without hanging — without * it, an external kill bypasses `dispose()` and orphans Chrome/Python to PID 1 (#698). * * Idempotent: every step is a no-op once the graceful {@link dispose} path has released * the resources. Never throws; per-step failures are logged and the whole run is capped * at `timeoutMs` so a wedged subprocess can't stall process exit. */ disposeChildSubprocesses(timeoutMs?: number): Promise; /** Full agent state */ get state(): AgentState; /** Current model (may be undefined if not yet selected) */ get model(): Model | undefined; /** Current thinking level */ get thinkingLevel(): ThinkingLevel | undefined; get serviceTier(): ServiceTier | undefined; /** Whether agent is currently streaming a response */ get isStreaming(): boolean; /** Wait until streaming and session settlement work are fully settled. */ /** * Wait until streaming and session settlement work are fully settled. * * The internal-only `ignoreSelectionFenceGeneration` is used exclusively by * `setDefaultModelSelection`'s mid-selection drain: the selection must not * wait on work parked behind its own (or a later) fence — its agent run, * recovery, and deferrals — because that work is waiting on the selection * itself (#4519). External callers observe everything. */ waitForIdle(ignoreSelectionFenceGeneration?: number): Promise; /** * Deterministically await every in-flight agent-event handler (including the * synchronous canonical persistence each one performs) plus the durable * transcript flush, without waiting for a prompt/turn lifecycle. * * This is the explicit signal replacement for real-time settle sleeps after * externally emitted events (`agent.emitExternalEvent`): the dispatcher * increments `#agentEventHandlersInFlight` synchronously before this call can * observe it, and each handler decrements it in its `finally`, so the * settlement promise below resolves exactly when the last handler — and * therefore its `sessionManager.appendMessage`/`_persist` work — has finished. * `flush()` then queues deterministically behind the SessionManager persist * queue, so every seeded entry is canonical AND on disk before the caller * proceeds. */ awaitSessionSettlement(): Promise; drainAsyncJobDeliveriesForAcp(options?: { timeoutMs?: number; }): Promise; /** * Owner-scoped async-delivery snapshot used by the strict ACP delete * quiescence barrier to PROVE quiescence after a best-effort drain. Unlike * {@link drainAsyncJobDeliveriesForAcp}'s boolean return (which conflates * "nothing to drain" with "timed out"), this reads the live state directly so * any remaining queued/delivering work is observable and can block mutation. */ getAsyncDeliveryStateForAcp(): { queued: number; delivering: boolean; }; /** Most recent assistant message in agent state. */ getLastAssistantMessage(): AssistantMessage | undefined; /** Current effective system prompt blocks (includes any per-turn extension modifications) */ get systemPrompt(): string[]; /** Current retry attempt (0 if not retrying) */ get retryAttempt(): number; /** * Get the names of currently active tools. * Returns the names of tools currently set on the agent. */ getActiveToolNames(): string[]; /** Whether the edit tool is registered in this session. */ get hasEditTool(): boolean; /** * Get a tool by name from the registry. */ getToolByName(name: string): AgentTool | undefined; /** Get a registered tool with the same guards used for model-facing execution. */ getToolForExecution(name: string): AgentTool | undefined; /** * Register a UI/control-plane request handler for a currently foregrounded * managed bash execution. This is intentionally narrower than generic * process/job control: unsupported tool types simply do not register a * handler, so Ctrl+B-style folding fails closed instead of aborting or * shell-suspending arbitrary work. */ registerForegroundBashBackgroundRequestHandler(handler: () => void): () => void; /** * Returns whether a managed foreground bash call is currently backgroundable. * UI key handlers use this to avoid consuming normal editor shortcuts when * no fold target exists. */ hasForegroundBashBackgroundRequestHandler(): boolean; /** Set the SDK permission policy used by guarded ACP tool execution. */ setSdkPermissionMode(mode: "prompt" | "allow" | "deny"): void; /** Current SDK permission policy for guarded ACP tool execution. */ get sdkPermissionMode(): "prompt" | "allow" | "deny"; /** Register or clear the SDK reverse permission provider for this session. */ setSdkPermissionProvider(provider: ((toolCall: ClientBridgePermissionToolCall, options: ClientBridgePermissionOption[], signal?: AbortSignal) => Promise) | undefined): void; /** * Ask the active managed foreground bash call to return as a background job. * Returns false when no supported foreground tool is currently backgroundable. */ requestForegroundBashBackground(): boolean; /** * Get all configured tool names (built-in via --tools or default, plus custom tools). */ getAllToolNames(): string[]; getSelectedMCPToolNames(): string[]; isToolDiscoveryEnabled(): boolean; getDiscoverableTools(filter?: { source?: DiscoverableTool["source"]; }): DiscoverableTool[]; getDiscoverableToolSearchIndex(): DiscoverableToolSearchIndex; getSelectedDiscoveredToolNames(): string[]; activateDiscoveredTools(toolNames: string[]): Promise; /** * Reload the SSH tool from disk-backed capability discovery and make the * refreshed definition visible to the next model call without restarting. */ refreshSshTool(options?: { activateIfAvailable?: boolean; }): Promise; /** * Set active tools by name. * Only tools in the registry can be enabled. Unknown tool names are ignored. * Also rebuilds the system prompt to reflect the new tool set. * Changes take effect before the next model call. */ setActiveToolsByName(toolNames: string[]): Promise; /** Rebuild the base system prompt using the current active tool set. */ refreshBaseSystemPrompt(): Promise; /** * Replace MCP tools in the registry and recompute the visible MCP tool set immediately. * This allows /mcp add/remove/reauth to take effect without restarting the session. */ refreshMCPTools(mcpTools: CustomTool[]): Promise; /** * Refresh plugin sub-skill tools after workflow/sub-skill activation or phase changes. */ refreshGjcSubskillTools(): Promise; /** Whether auto-compaction is currently running */ get isCompacting(): boolean; /** * Whether idle-flush tasks, auto-continuations, or other short-lived * post-prompt work are pending. True in the brief window after * `session.prompt()` returns but before a scheduled background delivery * (e.g. an async-job result) has finished its own streaming turn. * Loop-mode and similar auto-submit paths should treat this as a block * to avoid racing against the delivery turn. */ get hasPostPromptWork(): boolean; /** Stable resource ownership identifier for the active prompt run. */ get activePromptHandle(): string | undefined; /** All messages including custom types like BashExecutionMessage */ get messages(): AgentMessage[]; get transcriptPromptGeneration(): number; static restoreFromMemoryGuardCheckpoint(input: AgentSessionMemoryGuardRestoreInput): Promise; /** The immutable recovery authority, present only before external ownership promotion. */ get recoveryHydrationContext(): RecoveryHydrationContext | undefined; /** Enables normal session mutations after the owner has published its durable fence and writer lease. */ promoteRecoveryHydrationAfterOwnershipReadyFence(fence: AgentMemoryGuardPromotionFence): Promise; /** Main startup calls this exactly once, after a strict open returned `kind: "opened"`. */ continuePersistedHistory(): Promise; buildDisplaySessionContext(): SessionContext; /** Build display context from an unpublished successor without changing active session state. @internal */ buildPreparedDisplaySessionContext(prepared: PreparedNewSession): SessionContext; /** Convert session messages using the same pre-LLM pipeline as the active session. */ convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal, scope?: AttemptScope): Promise; /** Apply session-level stream hooks to a direct side request. */ prepareSimpleStreamOptions(options: SimpleStreamOptions, provider?: string, scope?: AttemptScope): SimpleStreamOptions; /** Current steering mode */ get steeringMode(): "all" | "one-at-a-time"; /** Current follow-up mode */ get followUpMode(): "all" | "one-at-a-time"; /** Current interrupt mode */ get interruptMode(): "immediate" | "wait"; /** Current session file path, or undefined if sessions are disabled */ get sessionFile(): string | undefined; /** Current session ID */ get sessionId(): string; /** Credential selection identity; defaults to the provider-facing session identity. */ get credentialSessionId(): string; /** Pin one OAuth credential for this session scope and persist the minimal intent. */ setCredentialPin(provider: string, selector: AuthCredentialSelector): Promise; /** Mask persistent/global selection for this session and restore AUTO ranking. */ setCredentialAuto(provider: string): Promise; /** Current session display name, if set */ get sessionName(): string | undefined; /** Scoped models for cycling (from --models flag) */ get scopedModels(): ReadonlyArray; /** Prompt templates */ getPlanModeState(): PlanModeState | undefined; /** Live SDK configuration values exposed through the session query surface. */ getSdkConfigItems(): Record; setPlanModeState(state: PlanModeState | undefined): void; invokeSkill(name: string, args?: string, options?: Pick): Promise<{ name: string; path: string; args?: string; lineCount?: number; }>; setSdkPlanMode(on: boolean): Promise; operateGoal(op: "create" | "get" | "resume" | "pause" | "complete" | "drop", objective?: string): Promise; getTranscript(): ExtensionTranscriptEntry[]; getTranscriptBody(entryId: string): string | undefined; getGoalModeState(): GoalModeState | undefined; setGoalModeState(state: GoalModeState | undefined): void; getWorkflowGateEmitter(): WorkflowGateEmitter | undefined; getAskAnswerSource(): AskAnswerSource | undefined; setWorkflowGateEmitter(emitter: WorkflowGateEmitter | undefined): void; get goalRuntime(): GoalRuntime; markPlanReferenceSent(): void; setPlanReferencePath(path: string): void; get clientBridge(): ClientBridge | undefined; setClientBridge(bridge: ClientBridge | undefined): void; getCheckpointState(): CheckpointState | undefined; setCheckpointState(state: CheckpointState | undefined): void; /** * Inject the plan mode context message into the conversation history. */ sendPlanModeContext(options?: { deliverAs?: "steer" | "followUp" | "nextTurn"; }): Promise; sendGoalModeContext(options?: { deliverAs?: "steer" | "followUp" | "nextTurn"; }): Promise; resolveRoleModel(role: string): Model | undefined; /** * Resolve a role to its model AND thinking level. * Unlike resolveRoleModel(), this preserves the thinking level suffix * from role configuration (e.g., "anthropic/Anthropic model-sonnet-4-5:xhigh"). */ resolveRoleModelWithThinking(role: string): ResolvedModelRoleValue; get promptTemplates(): ReadonlyArray; /** Replace file-based slash commands used for prompt expansion. */ setSlashCommands(slashCommands: FileSlashCommand[]): void; /** Custom commands (TypeScript slash commands and MCP prompts) */ get customCommands(): ReadonlyArray; /** Update the MCP prompt commands list. Called when server prompts are (re)loaded. */ setMCPPromptCommands(commands: LoadedCustomCommand[]): void; /** * Send a prompt to the agent. * - Handles extension commands (registered via pi.registerCommand) immediately, even during streaming * - Expands file-based prompt templates by default * - During streaming, queues via steer() or followUp() based on streamingBehavior option * - Validates model and API key before sending (when not streaming) * @throws Error if streaming and no streamingBehavior specified * @throws Error if no model selected or no API key available (when not streaming) */ prompt(text: string, options?: PromptOptions): Promise; promptCustomMessage(message: Pick, "customType" | "content" | "display" | "details" | "attribution">, options?: Pick): Promise; /** * Queue a steering message to interrupt the agent mid-run. */ steer(text: string, images?: ImageContent[]): Promise; /** * Queue a follow-up message to process after the agent would otherwise stop. */ followUp(text: string, images?: ImageContent[], options?: Pick): Promise; queueDeferredMessage(message: CustomMessage): void; queueDeferredMessageForTests(message: CustomMessage, triggerTurn?: boolean): void; /** Read-only test seam for the hidden next-turn context queue. */ getPendingNextTurnMessagesForTests(): readonly CustomMessage[]; /** Test-only abort outcome override; undefined retains the production abort race. */ setCancelAndSubmitAbortOutcomeProviderForTests(provider: (() => Promise) | undefined): void; /** * Send a custom message to the session. Creates a CustomMessageEntry. * * Handles three cases: * - Streaming: queue as steer/follow-up or store for next turn * - Not streaming + triggerTurn: appends to state/session, starts new turn unless the client cannot own it * - Not streaming + no trigger: appends to state/session, no turn */ sendCustomMessage(message: Pick, "customType" | "content" | "display" | "details" | "attribution">, options?: { triggerTurn?: boolean; deliverAs?: "steer" | "followUp" | "nextTurn"; followUpQueuePolicy?: "respect-mode" | "sequential"; /** INTERNAL trusted option: origin of a hidden next-turn message — * "turn" (produced by the current turn's continuation machinery, * purged by a terminal abort) or "external" (background producers, * preserved). Extension sendMessage callers are classified * "external" at the trusted bridge and never supply this bit * (review thread P2). */ origin?: "turn" | "external"; }): Promise; /** Remove undelivered queued custom messages matching `predicate` from executable queues and tagged display mirrors. */ purgeQueuedCustomMessages(predicate: (message: CustomMessage) => boolean): PurgeQueuedCustomMessagesResult; /** * Send a user message to the agent. * When deliverAs is set, queue the message instead of starting a new turn. * * @param content User message content (string or content array) * @param options.deliverAs Delivery mode: "steer" or "followUp" */ sendUserMessage(content: string | (TextContent | ImageContent)[], options?: { deliverAs?: "steer" | "followUp"; /** Preserve a busy SDK dispatch as queued work across an admission fence. */ queuedAtDispatch?: boolean; onPreflightAccepted?: () => void; onPreflightAcceptCommit?: () => void | Promise; /** Fired when a queued submission (steering or follow-up) is promoted to its own run (SDK ownership correlation). */ onQueuedPromoted?: (promotion: { startsOwnRun?: boolean; removed?: boolean; }) => void; /** Internal dispatch disposition used before actual queue consumption. */ onDispatchDisposition?: (promotion: { startsOwnRun: boolean; }) => void; preflightSignal?: AbortSignal; sdkRunToken?: string; }): Promise; /** * Clear queued messages and return them. * Useful for restoring to editor when user aborts. */ clearQueue(): { steering: string[]; followUp: string[]; }; /** Number of pending messages (includes steering, follow-up, and next-turn messages) */ get queuedMessageCount(): number; /** * Number of pending messages a user-facing drain can actually deliver back: * exactly the steering and follow-up queues that `clearQueue()`, * `popLastQueuedMessage()`, and `getQueuedMessageEntries()` operate on. * * Hidden next-turn context is deliberately excluded. Those entries are * authored by the agent (e.g. a `todo_write` failure reminder queued with * `deliverAs: "nextTurn"` and no `triggerTurn`), are never returned by the * drain handlers, and deliberately survive turn completion — so a UI gate * that counted them would report permanently pending work that no key press * can clear, locking the user out of their own input (#4741). */ get drainableQueuedMessageCount(): number; /** Typed pending-message counts per queue (steering, follow-up, next-turn). */ get pendingMessageCounts(): { steering: number; followUp: number; nextTurn: number; }; /** Whether the agent has queued steering messages that a `user_interrupt` * abort would resume into (steer-on-interrupt). Drives the Esc-on-steer UX: * the first Esc consumes the steer and auto-continues, a second Esc aborts. */ get hasQueuedSteering(): boolean; /** Get pending messages (read-only). Returns the public text-only view; * internal `{text, tag?}` records are mapped to `.text` so callers * (`updatePendingMessagesDisplay`, `restoreQueuedMessagesToEditor`) see * the unchanged historical shape. */ getQueuedMessages(): { steering: readonly string[]; followUp: readonly string[]; }; getQueuedMessageEntries(): QueuedMessageEditEntry[]; removeQueuedMessageForEditing(id: string): string | undefined; moveQueuedMessageForEditing(id: string, direction: "up" | "down"): boolean; /** * Pop the newest queued message across steering and follow-up queues. * Used by dequeue keybinding to restore messages to editor one at a time. * Returns the popped entry's `.text`; the tag (if any) dies with the * record — no orphan state can outlive the queue entry. */ popLastQueuedMessage(): string | undefined; get skillsSettings(): SkillsSettings | undefined; /** Skills loaded by SDK (always includes bundled GJC workflow defaults unless explicitly overridden by SDK callers) */ get skills(): readonly Skill[]; /** * Install the skill set discovered at a newly rescoped cwd (`move_session`). * Project-scoped skills belong to the directory they were discovered in, so * they must not survive a move out of it. */ replaceSkills(skills: Skill[]): Promise; /** * Retire the cached workspace tree after a rescope so the next turn re-scans * at the new cwd instead of re-presenting the abandoned launcher root. */ retireWorkspaceTreeForRescope(): void; /** Skill loading warnings captured by SDK */ get skillWarnings(): readonly SkillWarning[]; getTodoPhases(): TodoPhase[]; setTodoPhases(phases: TodoPhase[]): void; applyCompactionPostAppendForTests(compactionEntryId: string, firstKeptEntryId: string, fromExtension?: boolean): Promise; /** Read-only test seam for active mid-run EventStream drain barriers. */ get activeMidRunBarrierCountForTests(): number; /** Read-only test seam for active mid-run maintenance invocations. */ get activeMidRunMaintenanceCountForTests(): number; /** Test seam: drive the cooperative mid-run maintenance checkpoint directly. */ runMidRunMaintenanceForTests(context: AgentContext, lifecycle?: MidRunMaintenanceLifecycle): Promise; /** Test seam: estimate mid-run context tokens for a given context view. */ estimateMidRunContextTokensForTests(messages: readonly AgentMessage[]): number; /** Abort current operation and preserve the established void/rethrow contract. */ abort(options?: { goalReason?: "interrupted" | "internal"; timeoutMs?: number; cause?: "user_interrupt" | "new_session" | "session_switch" | "compaction" | "handoff" | "tool_abort" | "internal"; silent?: boolean; }): Promise; /** * Private terminal-abort seam: read the CURRENT turn's attempt epoch WITHOUT * interrupting it. Used to write the durable initial terminal marker BEFORE * any fence/stop effect (plan ordered step 4). Only the epoch is exposed — * never the opaque lineage handle — so no private origin metadata leaves the * session. Fails closed (undefined) when no active turn lineage exists. */ /** * Private terminal-abort seam: cancel a PENDING (not-yet-started) prompt * preflight. Aborting the preflight controller fires the captured admission * signal so #throwIfPromptPreflightCancelled throws and the pending prompt * never starts even if its SDK waiter was already settled; the controller is * reset for the next admission. No run handle exists for a preflight prompt. */ cancelPendingPreflightForTerminalAbort(): void; /** * Capture the steering admission snapshot at abort ADMISSION: the host * invokes this before its durable marker transaction, so client steering * admitted while the abort is in flight classifies as post-snapshot and is * preserved instead of being purged at the later abortPromptAndWait * (review thread P1). Snapshots are stored PER-ADMISSION in a per-turn * FIFO: the settlement of each admission consumes its own captured * sequence, so a later overlapping abort of the same turn can never * overwrite an earlier admission's snapshot and purge an already-accepted * steer (review thread P1). */ captureTerminalAbortSteeringSnapshot(): number | undefined; /** * Discard a captured snapshot whose admission never settles (a durable * same-key replay, no-effect, conflict, or marker failure): the entry is * keyed to the CURRENT turn, which is where its admission captured it. * Removing by token keeps overlapping admissions' entries independent. */ /** * Rebind a captured snapshot to the CURRENT turn: the owner-mismatch * fall-through may terminalize the aborting requester's own turn that won * the race, while the token was captured under the other connection's * turn. The entry moves to the current turn's FIFO (retaining the original * admission sequence) so the settlement's purge classifies steering * admitted since admission as post-snapshot (review thread P1). */ rebindTerminalAbortSteeringSnapshot(token: number): void; discardTerminalAbortSteeringSnapshot(token: number): void; getTerminalTurnEpoch(): number | undefined; abortPromptAndWait(handle: string, options: { graceMs: number; terminal?: { scope: "turn" | "owned"; expectedEpoch?: number; steeringSnapshotToken?: number; }; }): Promise; /** Atomically interrupt the active run and make text the next prompt. */ cancelAndSubmit(text: string, options?: { queuedEntryId?: string; }): Promise; /** * Start a new session, optionally with initial messages and parent tracking. * Clears all messages and starts a new session. * Listeners are preserved and will continue receiving events. * @param options - Optional initial messages and parent session path * @returns true if completed, false if cancelled by hook */ newSession(options?: NewSessionOptions): Promise; /** * Clear active conversational/model context while preserving the current * session identity and durable history trail. */ clearContext(): Promise; /** * Set a display name for the current session. */ setSessionName(name: string, source?: "auto" | "user"): Promise; /** * Fork the current session, creating a new session file with the exact same state. * Copies all entries and artifacts to the new session. * Unlike newSession(), this preserves all messages in the agent state. * @returns true if completed, false if cancelled by hook or not persisting */ fork(): Promise; /** * Set model directly. * Validates API key, saves to session and settings. * @throws Error if no API key available for the model */ setModel(model: Model, role?: string, options?: { selector?: string; thinkingLevel?: ThinkingLevel; cause?: ModelChangeCause; onMutationStarted?: () => void; }): Promise; setActiveModelProfile(name: string | undefined): void; getActiveModelProfile(): string | undefined; /** * Re-apply vendor-separated delegation after a profile activation changed the * role layer. `eagerTasks` is resolved at prompt build time, but under * `tools.discoveryMode: all` the `task` tool is hidden until something * activates it, and the delegation directive stays behind its * `has tools "task"` guard. Activating a vendor-separated profile mid-session * must therefore reach the live tool set, not just the next session. */ syncEagerDelegation(): Promise; /** Record runtime override keys installed by a profile activation. */ noteProfileInstalledOverrides(modelRoles: readonly string[], agentModelOverrides: readonly string[], preProfileModel: Model | undefined): void; /** Drop the recorded profile-installed override keys after materialization. */ clearProfileInstalledOverrides(): void; /** Current profile-installed override keys, for deriving the activation base. */ getProfileInstalledOverrideKeys(): { modelRoles: readonly string[]; agentModelOverrides: readonly string[]; }; /** * Activate a complete model profile through a nonvisual session control. * Session-scoped only: does not persist `modelProfile.default`. */ activateModelProfileForControl(profileName: string): Promise; /** * Activate a model profile from a control surface. * * Control selections are session-scoped unless the caller explicitly opts * into persistence. The transaction canonicalizes the profile, performs * credential preflight, and serializes against other session admissions. * Unknown/registry profile failures are surfaced as SDK `invalid_input` so * the ACP adapter maps them to invalid params. */ /** Persist effective roles only when the active profile is a durable default. */ materializeActiveDefaultModelProfileAssignment(model: Model): boolean; setDefaultModelProfileForControl(profileName: string, options?: { persistDefault?: boolean; thinkingLevelOverride?: ThinkingLevel; onBeforeActivation?: () => void; onAfterActivation?: () => void; }): Promise<{ changed: boolean; id: string; }>; /** * Drop a session-only profile marker and the runtime role overrides its * activation installed. Exposed for the extension `setModel` seam so a * concrete pick clears session-only profiles without materializing them * globally. A stale persisted default that no longer matches the dropped * marker is superseded by the concrete selection and removed. */ clearSessionOnlyModelProfileState(): void; /** * Run a control-surface mutation inside the session admission boundary so * SDK `config.patch` and other host mutations serialize against synthetic * profile activation and default-model selection. */ withSdkControlMutation(body: () => Promise): Promise; /** Return the persisted configured fallback selectors for a model role. */ getConfiguredModelChain(role: string): readonly string[] | undefined; /** Return the persisted configured chain with its durable ownership metadata. */ getConfiguredModelChainState(role: string): { entries: readonly string[]; origin: string; identity?: string; explicitHead: boolean; } | undefined; /** Persist the configured fallback selectors for a model role. */ setConfiguredModelChain(role: string, entries: readonly string[], origin: string, identity?: string, explicitHead?: boolean): void; /** * Replace only the in-memory default fallback controller. Used when startup * falls through an unavailable persisted chain to the global default, which * must not mutate the persisted configured intent. */ setDefaultFallbackRuntimeModel(selector: string): void; /** * Seed default fallback state after guarded auth-aware model resolution skips chain entries. * The configured chain's role, origin, and identity are retained by the controller. */ seedDefaultFallbackResolution(activeIndex: number, skips: Array<{ selector: string; reason: string; }>): void; getDefaultFallbackRuntimeState(): DefaultFallbackRuntimeState; restoreDefaultFallbackRuntimeState(state: DefaultFallbackRuntimeState): void; /** * The model selector ("provider/id") that resume restores as the session * default — the latest session-log `model_change` with role="default". * Model-profile activation snapshots this before mutating the session so a * failed-activation rollback can restore the pre-activation resume default * instead of promoting a transient runtime model to the resume default. */ getSessionDefaultModelSelector(): string | undefined; /** * Resolve the model that `modelRoles.default` currently points to, independent * of whatever model this session's log last recorded. Used by the TUI resume * flow's `session.resumeModelBehavior: "ask"` prompt to offer the currently * configured default as an alternative to the session's saved model. */ resolveConfiguredDefaultModel(): Model | undefined; /** * Record or clear the session resume default ("provider/id") without touching * the live runtime model. An undefined selector appends an explicit clear * marker so rollback preserves the absence of a prior default during replay. * Never writes global settings. */ recordResumeDefaultModel(selector: string | undefined): void; /** * Set model temporarily (for this session only). * Validates API key, saves to session log but NOT to settings. * * The change is recorded in the session log as `role: "temporary"` by * default, which means it is NOT restored as the session default on resume — * transient retry/fallback/context-promotion/plan switches must not clobber * the user's explicit pick (issue #849). Callers that intentionally own the * session resume default may opt into `persistAsSessionDefault: true` without * changing global settings. * @throws Error if no API key available for the model */ setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel, options?: { persistAsSessionDefault?: boolean; cause?: ModelChangeCause; reason?: TemporaryModelReason; providerSessionScope?: TemporaryProviderSessionScope; signal?: AbortSignal; }): Promise; /** Restore the exact live-model state captured before a failed selector transaction. */ restoreModelSelectionForRollback(model: Model | undefined, thinkingLevel: ThinkingLevel | undefined): Promise; /** Set a durable per-session model from a control surface without exposing credential errors. */ setModelTemporaryForControl(model: Model, expectedSessionId?: string, thinkingLevel?: ThinkingLevel): Promise; setDefaultModelSelection(model: Model, thinkingLevel: ThinkingLevel | undefined, options?: { /** Run inside the selection admission before the durable mutation. */ onBeforeMutation?: () => void; /** Run inside the selection admission after the durable mutation. */ onAfterMutation?: () => void; }): Promise; /** * Cycle to next/previous model. * Uses scoped models (from --models flag) if available, otherwise all available models. * @param direction - "forward" (default) or "backward" * @returns The new model info, or undefined if only one model available */ cycleModel(direction?: "forward" | "backward"): Promise; /** Number of configured role-model candidates that can be cycled. */ getRoleModelCycleCandidateCount(roleOrder?: readonly string[]): number; /** * Cycle through configured role models in a fixed order. * Skips missing roles. * @param roleOrder - Order of roles to cycle through (e.g., ["default"]) * @param options - Optional settings: `temporary` to not persist to settings */ cycleRoleModels(roleOrder: readonly string[], options?: { temporary?: boolean; }): Promise; /** * Get all available models with valid API keys. */ getAvailableModels(): Model[]; setThinkingLevel(level: ThinkingLevel | undefined, persist?: boolean): void; /** * Set thinking level from a control surface. Global changes commit before affecting live state. */ setThinkingLevelForControl(level: ThinkingLevel, persist: boolean): Promise; getThinkingScopeForControl(): "session" | "global config"; getThinkingVisibility(): "visible" | "hidden"; setThinkingVisibility(visibility: "visible" | "hidden", persist?: boolean): void; /** * Set thinking visibility from a control surface. Global changes commit before affecting live state. */ setThinkingVisibilityForControl(visibility: "visible" | "hidden", persist: boolean): Promise; /** * Cycle to next thinking level. * @returns New level, or undefined if model doesn't support thinking */ cycleThinkingLevel(): ThinkingLevel | undefined; /** * True when *any* fast-mode-granting service tier is configured, regardless * of whether the active model's provider actually realizes it. Used by the * toggle (`/fast on|off`) so re-toggling a scoped tier (`openai-only`, * `Anthropic model-only`) doesn't silently broaden it to unscoped `priority`. * * For "is fast mode actually applied to the next request?" use * {@link isFastModeActive} instead — that one respects the model's provider. */ isFastModeEnabled(): boolean; /** * True when the configured tier is realized as a fast-mode field on the * provider's wire protocol. Providers that silently drop unscoped priority * intent return false so UI indicators match the request that is sent. */ isFastForProvider(provider?: string, supportsServiceTier?: boolean): boolean; /** * Wire-effective fast-mode predicate for task-tool subagent roles, evaluated * against `task.serviceTier` rather than the main session tier. */ isFastForSubagentProvider(provider?: string, supportsServiceTier?: boolean): boolean; /** * True when the configured `serviceTier` resolves to `"priority"` for the * *currently selected model's provider* AND fast mode was not auto-disabled * for that provider this session. This is the current-model EFFECTIVE * predicate (what the next request actually does); use {@link isFastForProvider} * for pure configured intent (e.g. subagent/`modelRoles` display rows). */ isFastModeActive(): boolean; setServiceTier(serviceTier: ServiceTier | undefined): void; setFastMode(enabled: boolean): void; toggleFastMode(): boolean; /** * Get available thinking levels for current model. */ getAvailableThinkingLevels(): ReadonlyArray; /** * Runtime evidence published for the current GJC bundle activation * generation, set once by `createAgentSession` after every producer has run. * Undefined until that publication happens, so consumers report runtime * status as unavailable rather than falsely clear. */ gjcRuntimeSnapshot?: GjcRuntimeSnapshotProvider; /** Activation generation a published snapshot must match to be merged. */ gjcActivationGeneration?: number; /** * Set steering mode. * Saves to settings. */ setSteeringMode(mode: "all" | "one-at-a-time"): void; /** * Set follow-up mode. * Saves to settings. */ setFollowUpMode(mode: "all" | "one-at-a-time"): void; /** * Set interrupt mode. * Saves to settings. */ setInterruptMode(mode: "immediate" | "wait"): void; /** * Manually compact the session context. * Aborts current agent operation first. * @param customInstructions Optional instructions for the compaction summary * @param options Optional callbacks for completion/error handling */ compact(customInstructions?: string, options?: CompactOptions): Promise; /** * Cancel in-progress context maintenance (manual compaction, auto-compaction, or auto-handoff). */ abortCompaction(): void; /** Trigger idle compaction through the auto-compaction flow (with UI events). */ runIdleCompaction(): Promise; /** * Cancel in-progress branch summarization. */ abortBranchSummary(): void; /** * Cancel in-progress handoff generation. */ abortHandoff(): void; /** * Check if handoff generation is in progress. */ get isGeneratingHandoff(): boolean; /** * Generate a handoff document with a oneshot LLM call, then start a new session with it. * * @param customInstructions Optional focus for the handoff document * @param options Handoff execution options * @returns The handoff document text, or undefined if cancelled/failed */ handoff(customInstructions?: string, options?: SessionHandoffOptions): Promise; prepareContributionPrep(options?: ContributionPrepOptions): Promise; /** Test seam: override the emergency-compaction resource sampler so tests never read real RSS. */ setResourceSampler(sampler: () => EmergencyCompactionSample): void; setRetainedMemorySampler(sampler: (() => RetainedMemorySample) | undefined): void; /** * Toggle auto-compaction setting. */ setAutoCompactionEnabled(enabled: boolean): void; /** Whether auto-compaction is enabled */ get autoCompactionEnabled(): boolean; /** * Cancel in-progress retry. */ abortRetry(): void; /** * Skip the current retry backoff and re-attempt immediately. Distinct from * abortRetry(), which cancels the retry and returns to idle. No-op when no * retry backoff is active. */ retryNow(): void; /** Whether auto-retry is currently in progress */ get isRetrying(): boolean; /** Whether auto-retry is enabled */ get autoRetryEnabled(): boolean; /** * Toggle auto-retry setting. */ setAutoRetryEnabled(enabled: boolean): void; /** * Manually retry the last failed assistant turn, or resume an interrupted tail * left by a non-graceful process exit after the user/custom/tool-result message * was persisted but before the agent emitted a terminal assistant response. * Removes failed/aborted/unresolved tool-use assistant tails before * re-attempting with a fresh retry budget. * @returns true if retry/resume was initiated, false if no retryable tail exists or agent is busy */ retry(): Promise; /** * Execute a bash command. * Adds result to agent context and session. * @param command The bash command to execute * @param onChunk Optional streaming callback for output * @param options.excludeFromContext If true, command output won't be sent to LLM (!! prefix) * @param options.onPersisted Called once the execution's message is in session state * (immediately when idle, at the post-turn flush while streaming) */ executeBash(command: string, onChunk?: (chunk: string) => void, options?: { excludeFromContext?: boolean; onPersisted?: () => void; }): Promise; /** * Record a bash execution result in session history. * Used by executeBash and by extensions that handle bash execution themselves. */ recordBashResult(command: string, result: BashResult, options?: { excludeFromContext?: boolean; onPersisted?: () => void; }): void; /** * Cancel running bash command. */ abortBash(): void; /** Whether a bash command is currently running */ get isBashRunning(): boolean; /** Whether there are pending bash messages waiting to be flushed */ get hasPendingBashMessages(): boolean; /** * Execute Python code in the shared kernel. * Uses the same kernel session as eval's Python backend, allowing collaborative editing. * @param code The Python code to execute * @param onChunk Optional streaming callback for output * @param options.excludeFromContext If true, execution won't be sent to LLM ($$ prefix) * @param options.onPersisted Called once the execution's message is in session state * (immediately when idle, at the post-turn flush while streaming) */ executePython(code: string, onChunk?: (chunk: string) => void, options?: { excludeFromContext?: boolean; onPersisted?: () => void; }): Promise; assertEvalExecutionAllowed(): void; /** * Track Python work started outside AgentSession.executePython so dispose can await and abort it too. */ trackEvalExecution(execution: Promise, abortController: AbortController): Promise; /** * Record a Python execution result in session history. */ recordPythonResult(code: string, result: PythonResult, options?: { excludeFromContext?: boolean; onPersisted?: () => void; }): void; /** * Cancel running Python execution. */ abortEval(): void; /** Whether a Python execution is currently running */ get isEvalRunning(): boolean; /** Whether there are pending Python messages waiting to be flushed */ get hasPendingPythonMessages(): boolean; /** * Generate an ephemeral reply to a background message (e.g. an IRC ping from * another agent) using this session's current model + system prompt + history. * * The reply is computed via a side-channel `streamSimple` call (analogous to * `/btw`) so it never blocks on the recipient's in-flight tool calls. After * the reply is generated, both the incoming question and the auto-reply are * queued for injection into the recipient's persisted history so the model * sees the exchange on its next turn. Injection happens immediately when the * session is idle, otherwise it is deferred until streaming ends. */ respondAsBackground(args: { from: string; message: string; awaitReply?: boolean; signal?: AbortSignal; }): Promise<{ replyText: string | null; }>; /** * Emit an IRC relay observation event on this session for UI rendering only. * Does not persist the record to history. Public so other sessions can forward. */ emitIrcRelayObservation(record: CustomMessage): void; emitSubagentSteerObservation(args: { from: string; to: string; body: string; timestamp?: number; }): void; emitSubagentSteerRelayObservation(record: CustomMessage): void; createBtwConversationScope(instruction: string): BtwConversationScope; /** * Run a single ephemeral side-channel turn without modifying session history. * Background turns retain IRC/session behavior. `/btw` turns require a * pre-frozen, visible-text-only scope and bypass extension context transforms, * provider observability hooks, and session persistence surfaces. */ runEphemeralTurn(args: EphemeralTurnArgs): Promise; /** * Reload the current session from disk. * * Intended for extension commands and headless modes to re-read the current session * file and re-emit session_switch hooks. */ reload(): Promise; /** * Switch to a different session file. * Aborts current operation, loads messages, restores model/thinking. * Listeners are preserved and will continue receiving events. * @returns true if switch completed, false if cancelled by hook */ switchSession(sessionPath: string, options?: { transition?: SessionSwitchEvent["transition"]; onTransitionMutationStarted?: () => void; }): Promise; /** * Create a branch from a specific entry. * Emits before_branch/branch session events to hooks. * * @param entryId ID of the entry to branch from * @returns Object with: * - selectedText: The text of the selected user message (for editor pre-fill) * - cancelled: True if a hook cancelled the branch */ branch(entryId: string): Promise<{ selectedText: string; cancelled: boolean; }>; /** * Navigate to a different node in the session tree. * Unlike branch() which creates a new session file, this stays in the same file. * * @param targetId The entry ID to navigate to * @param options.summarize Whether user wants to summarize abandoned branch * @param options.customInstructions Custom instructions for summarizer * @returns Result with editorText (if user message) and cancelled status */ navigateTree(targetId: string, options?: { summarize?: boolean; customInstructions?: string; }): Promise<{ editorText?: string; cancelled: boolean; aborted?: boolean; summaryEntry?: BranchSummaryEntry; /** Raw session context built during navigation — pass to renderInitialMessages to skip a second O(N) walk. */ sessionContext?: SessionContext; }>; /** * Get all user messages from session for branch selector. */ getUserMessagesForBranching(): Array<{ entryId: string; text: string; }>; /** * Get session statistics. */ getSessionStats(): SessionStats; /** * Get current context usage statistics. * Uses the last assistant message's usage data when available, * otherwise estimates tokens for all messages. */ getContextUsage(): ContextUsage | undefined; getContextUsageObservabilityForTests(): { estimateCount: number; }; fetchUsageReports(signal?: AbortSignal): Promise; fetchUsageReportsForControl(): Promise; /** * Export session to HTML. * @param outputPath Optional output path (defaults to session directory) * @returns Path to exported file */ exportToHtml(outputPath?: string): Promise; /** * Get text content of last assistant message. * Useful for /copy command. * @returns Text content, or undefined if no assistant message exists */ getLastAssistantText(): string | undefined; hasCopyCandidateAssistantMessage(): boolean; /** * Get text content of the most recent visible handoff message. * Fresh handoff sessions store the handoff context as a custom message, not * an assistant message, so callers that copy the "last" message can use this * as a fallback before the new session has an assistant response. */ getLastVisibleHandoffText(): string | undefined; /** * Format the entire session as plain text for clipboard export. * Includes user messages, assistant text, thinking blocks, tool calls, and tool results. */ formatSessionAsText(): string; /** * Format the conversation as compact context for subagents. * Includes only user messages and assistant text responses. * Excludes: system prompt, tool definitions, tool calls/results, thinking blocks. */ formatCompactContext(): string; /** * Check if extensions have handlers for a specific event type. */ hasExtensionHandlers(eventType: string): boolean; /** * Register a first-party internal before-agent-start contributor. Returns an * unregister function. This is NOT user-facing hook discovery; it is an * in-core seam invoked alongside the extension runner. */ registerBeforeAgentStartContributor(contributor: BeforeAgentStartContributor): () => void; /** * Get the extension runner (for setting UI context and error handlers). */ get extensionRunner(): ExtensionRunner | undefined; }