import type { ChatMessage, Mode, ProviderId, TokenUsage, ToolResult } from "../../types.js"; import { type CompactResult } from "../../agent/context-manager.js"; import { type ContextUsageSnapshot } from "../../llm/token-usage.js"; import type { ContextAttemptReference, ContextSnapshotV1 } from "../../llm/context-snapshot.js"; import { type PartialUsageSnapshot } from "./session-context-usage.js"; import type { PreviousTurnSignal } from "../../agent/continue-orient.js"; import { type SessionWorkspace } from "../../store/session-workspace.js"; import type { TranscriptItem as ClassicTranscriptItem } from "../ports/transcript-item.js"; import { type AnyAppEvent, type SessionId, type TurnId } from "../events/app-event.js"; import { type Clock, type IdFactory } from "../events/sequencer.js"; import { OutputSpool } from "../events/event-buffer.js"; import type { AgentPort } from "../ports/agent-port.js"; import type { JobsPort } from "../ports/jobs-port.js"; import type { InteractiveSessionsPort } from "../ports/interactive-sessions-port.js"; import type { PersistencePort } from "../ports/persistence-port.js"; import type { ConfirmationPort } from "../ports/confirm-port.js"; import type { SecretPort } from "../ports/secret-port.js"; import { type TurnResult } from "./turn-controller.js"; import { type Disposable } from "./disposable.js"; import { type TurnDisplayOptions } from "./session-prompt-queue.js"; import { type ResponderRuntimeState } from "./session-responder.js"; import { type SessionUsageReport } from "./session-usage-ledger.js"; export interface SessionState { readonly sessionId: SessionId; readonly mode: Mode; readonly provider: ProviderId | undefined; readonly model: string | undefined; readonly running: boolean; readonly compacting: boolean; readonly historyLength: number; readonly queued: readonly string[]; readonly responder: ResponderRuntimeState; readonly title: string | undefined; /** Canonical versioned context measurement for runtime consumers. */ readonly contextSnapshot: ContextSnapshotV1 | undefined; /** Six-field legacy projection retained for existing renderers. */ readonly contextUsage: ContextUsageSnapshot | undefined; readonly contextChip: string | undefined; } export type NoticeLevel = "info" | "warn"; export interface SessionControllerDeps { readonly agent: AgentPort; readonly persistence: PersistencePort; readonly jobs?: JobsPort | undefined; readonly interactiveSessions?: InteractiveSessionsPort | undefined; readonly emit: (event: AnyAppEvent) => void; readonly sessionId?: string | undefined; readonly provider?: ProviderId | undefined; readonly model?: string | undefined; readonly mode?: Mode | undefined; readonly confirm?: ConfirmationPort | undefined; readonly requestSecret?: SecretPort["request"] | undefined; readonly idFactory?: IdFactory | undefined; readonly clock?: Clock | undefined; readonly mintTurnId?: (() => TurnId) | undefined; readonly getTranscriptSnapshot?: (() => ClassicTranscriptItem[] | undefined) | undefined; /** When true, never persist sessions or generate AI titles (CLI --no-history). */ readonly noHistory?: boolean | undefined; readonly notifyResponderDelivery?: ((summary: string) => void) | undefined; readonly titleCompleter?: ((messages: ChatMessage[]) => Promise) | undefined; } export type TurnEndListener = (result: TurnResult) => void; export type SessionStateListener = () => void; export declare class SessionController implements Disposable { private readonly deps; readonly spool: OutputSpool; private sessionIdValue; private readonly sequencer; private readonly turn; private policy; private readonly disposables; private readonly turnEndListeners; private readonly stateListeners; private history; private readonly prompts; private readonly responder; private provider; private model; private mode; private compactingFlag; private readonly activeCompactions; private compactAbort; /** Display name written into history.db. */ private sessionTitle; private readonly namer; private lastMainRequestSnapshot; /** Throttle mid-turn history autosaves so abort/crash still keep tools on disk. */ private lastAutosaveAt; private autosaveInFlight; /** Orders autosave, terminal, compaction, title, switch, and shutdown writes. */ private readonly persistence; private static readonly AUTOSAVE_MIN_MS; /** Last known versioned context measurement for the session. */ private contextSnapshot; /** Avoid applying the same manual compaction in both commit and event paths. */ private lastContextCompactionId; private readonly contextLimits; private readonly usageLedger; /** Bumped by reset/history load/dispose so late callbacks can be ignored. */ private lifecycleGeneration; private lastTurnResult; private restoredPreviousTurn; private activeTurnGeneration; private readonly projectContext; constructor(deps: SessionControllerDeps); /** Current per-session scratch/output workspace (always bound while live). */ get workspace(): SessionWorkspace | undefined; get sessionId(): SessionId; getState(): SessionState; private contextUsageProjection; private get contextLimitTokens(); private get usageTarget(); private contextTimestamp; private setContextSnapshot; private resolveContextSnapshot; /** Next-request occupancy, only when a request-scoped measurement exists. */ private requestScopedContextTokens; recordTokenUsage(usage: TokenUsage, model?: string, provider?: ProviderId, attempt?: ContextAttemptReference): void; usageReport(): SessionUsageReport; noteContextCompacted(afterTokens?: number, scope?: "message-history" | "assembled-request", compactionId?: string): void; noteContextEstimate(estimatedTokens: number): void; private refreshEstimatedContext; get messages(): readonly ChatMessage[]; /** Subscribe to transient UI state such as running/queue status. */ subscribe(listener: SessionStateListener): () => void; setProvider(provider: ProviderId | undefined): void; setContextLimitTokens(limit: number | undefined): void; setModel(model: string | undefined): void; setMode(mode: Mode): void; /** * Replace model history (and optionally rebind the session id so later * autosaves update the resumed history row). */ loadHistory(messages: readonly ChatMessage[], options?: { sessionId?: string; title?: string | undefined; /** * Restored from history so the footer matches the live session count. * Accepts partial snapshots (contextLimit optional). */ contextUsage?: ContextUsageSnapshot | PartialUsageSnapshot | undefined; /** Loaded durable revision; resume advances to a fresh writer generation. */ persistenceRevision?: number | undefined; /** Last turn outcome or interrupted in-flight checkpoint from history. */ previousTurn?: PreviousTurnSignal | undefined; /** Per-session scratch/output folder (restored with history). */ workspaceFolder?: string | undefined; workspaceCode?: string | undefined; }): void; notice(level: NoticeLevel, text: string): void; allowTool(name: string): void; disallowTool(name: string): void; allowedTools(): readonly string[]; /** * Clear conversation state. When `mintNewId` is true (for `/new`/`/clean`), * remint the session id so subsequent autosave does not overwrite the prior * history row. */ reset(options?: { mintNewId?: boolean; }): void; /** Roll back history after a rejected plan-implement compaction. */ restoreMessages(messages: readonly ChatMessage[], contextSnapshot?: ContextSnapshotV1 | ContextUsageSnapshot | undefined): void; compact(sessionTranscript?: string, keepRecent?: number, signal?: AbortSignal, options?: { purpose?: "default" | "plan-implement" | undefined; /** Default true: emit compacted event + persist. */ persist?: boolean | undefined; }): Promise; private settlePersistedResponderResults; private continuationCheckpoint; canResumeFromHistory(): boolean; persistNow(name?: string): Promise; estimateContext(): { messages: number; tokens: number; }; cancelAll(): Promise; private fenceInteractiveOwner; enqueue(prompt: string, opts?: TurnDisplayOptions): void; queued(): readonly string[]; removeQueued(index: number): void; takeQueued(index: number): string | undefined; editQueued(index: number, text: string): void; reorderQueued(fromIndex: number, toIndex: number): void; sendQueuedNow(index: number): void; abort(reason?: string): void; continueQueue(): Promise; /** In-memory plan-approval flag consumed by the agent gate (CORE-005). */ setPlanApproved(value: boolean): void; isPlanApproved(): boolean; /** Fires after every turn settles (completed/aborted/error), including drain. */ onTurnEnd(listener: TurnEndListener): () => void; submit(prompt: string, opts?: TurnDisplayOptions): Promise; drain(): Promise; private runTurn; private readonly loopRecoveryAttempts; private maybeScheduleLoopGuardRecovery; /** * Best-effort mid-turn autosave (throttled). Called from onMessages so a * long agent run still lands tools/messages on disk before abort/crash. */ private scheduleAutosave; /** Fence the previous lifecycle: running turn, compaction and title. */ private beginLifecycleGeneration; dispose(): void; private notifyState; private observeEmit; }