import { type AgentTool } from "@kenkaiiii/gg-agent"; import { type Message, type MessageProvenance, type Provider, type ThinkingLevel } from "@kenkaiiii/gg-ai"; import { EventBus } from "./event-bus.js"; import { SlashCommandRegistry } from "./slash-commands.js"; import { type BranchInfo, type KenTurnPayload, type AutopilotMarkerPayload, type AppMarkerPayload, type RunOutcome, type TurnMetricPayload } from "./session-manager.js"; import type { BackgroundProcess } from "./process-manager.js"; import type { MCPElicitHandler } from "./mcp/index.js"; import { type ImportForeignTranscriptResult } from "./foreign-session-import.js"; import { type EnhanceResult } from "../utils/prompt-enhancer.js"; /** A chat attachment (image / video / other file) prepared for the model. The * raw base64 `data` rides native blocks; `path` (when persisted to disk) lets * the agent's tools open the file directly. */ export interface SessionAttachment { kind: "image" | "video" | "file"; mediaType: string; data: string; name: string; path?: string; } export interface AgentSessionOptions { provider: Provider; model: string; cwd: string; baseUrl?: string; /** Replaces the whole system prompt — nothing else is rendered. */ systemPrompt?: string; /** * A sub-agent definition's body, COMPOSED with the standard scaffolding * (Tools, project context, return contract, Environment) instead of replacing * it — see `buildSubAgentSystemPrompt`. * * Prefer this over `systemPrompt` for delegated children: a bare replacement * leaves the child with no Tools section and no Environment facts, which is * precisely how a sub-agent ends up misusing tools it was never told it had. * Ignored when `systemPrompt` is set. */ agentPrompt?: string; /** Whether `agentPrompt` composition includes project instruction files. Default `"project"`. */ agentContext?: "project" | "none"; /** Synchronous volatile prompt suffix, refreshed immediately before every run. */ getSystemPromptTail?: () => string; sessionId?: string; continueRecent?: boolean; maxTokens?: number; maxTurns?: number; /** * How many times the turn budget may be extended when the agent is still * making progress. Defaults to the agent-loop default (2); sub-agents pass a * stricter cap because a child's extensions multiply against the parent's * own budget. */ maxTurnExtensions?: number; thinkingLevel?: ThinkingLevel; signal?: AbortSignal; /** Prefix used for provider prompt-cache routing keys. */ promptCacheKeyPrefix?: string; /** * Explicit prompt-cache routing key. When set, overrides the * `${promptCacheKeyPrefix}:${sessionId}` default so spawned sub-agents can * inherit a stable parent-scoped key — without this, each sub-agent process * generates a fresh sessionId and starts with a cold cache. */ promptCacheKey?: string; /** * If true, this session does NOT create a `.jsonl` session file or persist * any messages. Used by subagent spawns (`--json` mode) so their transcripts * don't leak into `ggcoder continue` for the parent project. Subagent runs * are one-shot, NDJSON-streamed to the parent over stdout, and have no * resumable identity. */ transient?: boolean; /** * If true, `initialize()` returns WITHOUT waiting for MCP servers to connect — * the connection runs in the background and tools are appended when ready. * Hosts whose readiness is gated on `initialize()` (the gg-app sidecar, which * can't emit its listening handshake until init resolves) set this so a slow * or hanging stdio MCP server (e.g. a first-run `npx -y …` download) can't * delay the session from becoming usable. Default (false) keeps the CLI's * connect-before-ready behavior so MCP tools are present on the first turn. */ backgroundMcpConnect?: boolean; /** * Handler for a server-initiated MCP `elicitation/create` — a request for user * input in the middle of a tool call. Hosts that can render a form (the * gg-app sidecar) supply this; without it the session declares no elicitation * capability and servers fall back to their no-input behavior. */ onMcpElicit?: MCPElicitHandler; /** * If true, an over-context restored session is NOT compacted inline during * `loadExistingSession()` — the existing pre-run auto-compaction in * `runLoop()` handles it on the first prompt instead (with proper * compaction_start/_end events). The inline load compaction makes a summary * LLM call with a 30s timeout, and hosts whose readiness is gated on * `initialize()` (the gg-app sidecar: waitForReady blocks the whole webview) * would freeze the UI for that entire call. Default (false) keeps the * compact-on-load behavior for CLI resume/`ggcoder continue`. */ deferLoadCompaction?: boolean; /** * Plan-mode callbacks. When provided, the `enter_plan`/`exit_plan` tools are * registered and the session manages plan-mode restrictions + system-prompt * rebuilds. Hosts (e.g. the gg-app sidecar) use these to surface plan-mode * UI. Omitted by callers that don't want plan mode (CLI wires its own). */ onEnterPlan?: (reason?: string) => void | Promise; onExitPlan?: (planPath: string) => Promise; /** * If provided, the session's tool set is filtered to ONLY these tool names * after `createTools()` runs, and the system prompt's Tools section lists only * them. Used by read-only advisory sessions (e.g. the Ken mentor agent) to * register a safe subset — excluded mutating tools (write/edit/bash/…) are * never registered, so a hallucinated call can't change the repo. Default * (undefined) = all tools, preserving every existing caller's behavior. */ allowedTools?: string[]; /** * MCP server names whose tools are allowed in an allow-listed session. Only * meaningful alongside `allowedTools`. With it set, the session connects ONLY * these named MCP servers (not the full configured set) and every tool they * expose (`mcp____*`) passes the allow-list. The Ken mentor agent uses * this to get `kencode-search` for real-code research while still being barred * from every mutating tool. Empty/undefined → an allow-listed session skips * MCP entirely (its dynamic tool names could never match a fixed allow-list). */ allowedMcpServers?: string[]; /** * Force 1-h prompt-cache TTL + pre-warm regardless of the user's global * `speedProfile` setting. Bursty read-only advisory sessions (the Ken * mentor + autopilot reviewer) call the same static system prompt on a * schedule that routinely exceeds the default 5-min cache window — a * dropped cache there resends the whole cached prefix at full price right * when it matters most, independent of whatever the user picked for the * main build session. Default (undefined) = follow `speedProfile`. */ forceLongCacheRetention?: boolean; /** Hidden persistent subagent workers omit the async orchestration tool suite. */ subagentWorker?: boolean; /** Session storage root override. Chat agents use a dedicated namespace. */ sessionRootDir?: string; /** Register GG Coder built-in/prompt/custom slash commands. Defaults to true. */ coderSlashCommands?: boolean; /** Enable loop-break, re-grounding, and Ideal review hooks. Defaults to true. */ selfCorrectionHooks?: boolean; /** Load project skills/agents and create local .gg directories. Defaults to true. */ projectCustomization?: boolean; /** Register global + bundled subagents without loading project customization. */ globalSubagents?: boolean; /** Load GG Coder extensions. Defaults to true. */ loadExtensions?: boolean; /** Inject GG Coder's model-specific subagent orchestration prompt. Defaults to true. */ orchestrationPrompt?: boolean; /** Host-provided tools appended to this session only (for example, chat delegation). */ additionalTools?: AgentTool[]; } /** Resolve the per-result cap passed to the agent loop for the active transport. */ export declare function resolveSessionToolResultCharLimit(model: string, provider: Provider, accountId?: string): number; /** * Aggregate budget for ALL tool results produced in one assistant turn. * Individual results are already capped, but wide parallel fan-outs (GPT-5.6's * signature behavior) were observed injecting 100k+ uncached tokens in a single * turn. ~15% of the context window in chars (1 token ≈ 3.5 chars), floored at * 100KB so small windows still fit two full-size reads, ceilinged at 240KB so * 1M-context models don't waive the budget entirely. */ export declare function resolveSessionTurnToolResultCharLimit(model: string, provider: Provider, accountId?: string): number; export interface AgentSessionState { provider: Provider; model: string; cwd: string; sessionId: string; sessionPath: string; messageCount: number; planMode: boolean; /** accountId from the most recently resolved credentials, if any — lets * callers compute the transport-specific context window (e.g. OpenAI Codex * OAuth) without re-resolving credentials. */ accountId?: string; } export declare class AgentSession { readonly eventBus: EventBus; readonly slashCommands: SlashCommandRegistry; private settingsManager; private authStorage; private sessionManager; private extensionLoader; private messages; private kenTurns; private autopilotMarkers; private appMarkers; private turnMetrics; private tools; /** Rebuilds the read tool for a new model (video byte cap is baked in at * creation). Called from switchModel so video-capable models get the * read-tool's native-video path after a mid-session model change. */ private rebuildReadTool; private skills; private cacheKeyLogged; private hookStats; private hookText; private hookConsecutiveFailures; private hookRepeatedNoProgressCalls; private hookProgressTracker; private hookCycleDetector; private hookCyclicPattern; private hookFileEditCounts; private hookToolCalls; private idealReviewPhase; /** Runtime-only suppression while Ken owns verification in autopilot mode. */ private idealReviewSuppressed; /** Mirror of the last `hook_armed` value broadcast this run, so the event * fires only on a real edge. */ private idealReviewArmed; /** Cached test-drift probe, keyed by the size of the edited-file set. Drift * depends only on WHICH files were edited and that set only grows, so this * keeps the arming check off the filesystem on most tool results — the probe * is several sync existsSync calls per edited file. */ private idealDriftProbe; private readonly reviewCoverage; /** Coverage follow-ups spent this run, capped by MAX_REVIEW_COVERAGE_INJECTIONS. */ private reviewCoverageInjected; /** 0 = none; 1 = first nudge sent; 2 = final stop-and-report injected. */ private loopBreakInjected; private regroundingInjected; /** * The environment as the cached system prompt currently describes it. * Re-recorded on every prompt build, so a rebuild (e.g. `/add-dir`) needs no * delta; anything that changes WITHOUT one is caught by the hook below. */ private renderedEnvironment; /** Wall-clock start of the current run; scopes the background-process gate. */ private runStartedAt; /** Gate injections spent this run, capped by MAX_PROCESS_GATE_INJECTIONS. */ private processGateInjected; /** Verification gate: code edited this run, nothing proved it since. */ private readonly verificationGate; /** Mirror of the last verification `hook_armed` value, so the event fires * only on a real edge. */ private verificationArmed; private compactionOccurred; private lastCompactionCompacted; private compactionRetryAfter; /** A restored oversized checkpoint must be canonicalized before its first prompt is persisted. */ private deferredCompactionPending; /** Latest provider count, anchored to the assistant response it measured. */ private providerContext; private originalRequest; private userQueue; private queueSeq; private processManager?; private lspManager?; private subAgentManager?; /** * Out-of-band push notifications (finished children, background-process * progress). Producers enqueue; `getHookSteeringMessages` drains into the * live turn so the agent never has to spend a turn asking. */ private readonly notifications; private managerAbortSignal?; private readonly managerAbortHandler; private mcpManager?; /** Deferred MCP tools awaiting discovery via tool_search. */ private mcpCatalog?; /** Resolved prompt-injection byte budgets (contextLimits setting). */ private contextLimits; /** * Built-in tools held in the catalog instead of the live toolset. Their names * still render as one-line hints in the prompt's Tools section, so the model * can discover and promote them; a promoted name drops out of this list. */ private deferredBuiltinToolNames; /** Live (connected) MCP tools by name — the reconcile target for cached stubs. */ private liveMcpTools; /** Server name for each cached-only tool, so a stub knows what to wait on. */ private cachedMcpToolServers; private readonly mcpCatalogCache; private provider; private model; private cwd; /** accountId from the most recently resolved credentials — cached so sync * callers (e.g. the app-sidecar's context-window footer stat) can reflect * transport-specific windows (e.g. OpenAI Codex OAuth's smaller window) * without re-resolving credentials on every poll. */ private lastAccountId?; private baseUrl?; private maxTokens; private thinkingLevel?; private customSystemPrompt?; /** Sub-agent definition body composed into the standard prompt scaffolding. */ private agentPrompt?; /** Stable prompt prefix retained separately from the volatile uncached tail. */ private baseSystemPrompt; /** Shared with the tool layer so plan-mode restrictions read live state. */ private planModeRef; /** Path of the approved plan currently being implemented, or undefined. When * set, the system prompt carries the `[DONE:n]` progress contract so the * model emits step-completion markers the UI's plan-progress widget reads. */ private approvedPlanPath?; /** Extra workspace roots added with `/add-dir` (resolved, de-duplicated). */ private additionalRoots; private sessionId; private checkpointGeneration; /** Stable identity shared by compaction and approved-plan checkpoint files. */ private conversationId; /** Original user-authored prompt, retained when internal messages replace history. */ private sessionPreview; /** Runtime conversation identity for provider transport headers. Transient * children need one even though they intentionally have no persisted session. */ private readonly transportSessionId; private sessionPath; private lastPersistedIndex; /** * The array `agentLoop` is currently mutating, while a run is in flight. * * Normally identical to `this.messages`, but a mid-loop compaction rebinds * `this.messages` to the compacted result while the loop keeps appending to * its own array. Step-boundary flushes must follow the loop's array or they * would silently persist nothing for the rest of the run. */ private activeLoopMessages; /** * Number of non-system messages guaranteed to be in the session file — the * anchor base for transcript markers (Ken turns, autopilot verdicts, app * markers). `this.messages` can run ahead of the file: the agent loop * appends assistant/tool/steering messages in place but they are only * persisted when the run SUCCEEDS, so after a failed run the in-memory list * carries an unpersisted tail. Markers anchored against that tail point past * their real position on resume — the row (notably an error) renders lower * in the transcript than it happened, bunching at the bottom, or gets * dropped as out-of-range. Anchoring to the persisted prefix keeps resume * placement 1:1 with where the row appeared live. */ private persistedTranscriptCount; /** Current leaf entry ID in the session DAG — used to chain parentIds for branching. */ private currentLeafId; private opts; constructor(options: AgentSessionOptions); /** * Derive the output-token cap for a model. Follows the active model's * `maxOutputTokens` so a session booted on a large-output model (e.g. Kimi's * 256K) doesn't carry that cap to a smaller one (e.g. Opus's 128K) after a * model switch — that mismatch surfaces from the provider as * `max_tokens: 262144 > 128000, which is the maximum allowed …`. An explicit * `maxTokens` override is honored but clamped to the model's ceiling. */ private resolveMaxTokens; initialize(): Promise; /** * Whether a tool name is permitted for this session. With no `allowedTools` * everything passes (default behavior). Otherwise a tool is allowed when its * name is in `allowedTools`, OR it's an MCP tool (`mcp____`) * whose `` is in `allowedMcpServers`. The MCP-prefix rule lets a * whitelisted research server (e.g. kencode-search) expose all its tools * without hard-coding each one, while every other tool stays blocked. */ private isToolAllowed; /** * Connect all configured MCP servers and append their tools to `this.tools`. * Resolves the GLM api key first (Z.AI's bundled servers need it). Never * throws — a failed connect is logged and skipped — so it is safe to either * `await` (CLI: tools ready before the first turn) or fire-and-forget * (sidecar: `backgroundMcpConnect`, so a slow stdio server can't stall * startup). Tools are pushed onto the live array the agent loop reads each * turn, so background-connected servers become available on the next prompt. */ private connectMcpServers; /** Persist `cwd` as a trusted project for project-scope MCP. Called by the * sidecar's `/mcp/add` handler when a user adds a project-scope server via * the MCP modal — the explicit add is itself the trust signal. The next * session load connects its `.gg/mcp.json` servers. */ trustProject(cwd: string): Promise; /** * Route freshly connected MCP tools: deferred into the tool_search catalog * (default — keeps ~8k tokens of schema out of every cache-miss turn) or * pushed eagerly when the user opted out. * Allow-listed sessions (Ken) always get the eager path — their fixed tool * expectations predate the catalog, and tool_search isn't allow-listed. * Promotion pushes onto the live `this.tools` array the running agent loop * re-reads every turn, so promoted tools are callable on the next step. */ private addMcpTools; /** * Register `tool_search` once. Promotion of a cached-only entry waits for its * server so the model is told immediately when that capability turns out to * be unreachable, instead of promoting a tool that fails on first call. * * The catalog is created on demand rather than required up front: deferred * built-in tools populate it with zero MCP servers connected, so gating * registration on an existing catalog would leave those tools unreachable. */ private ensureToolSearchTool; /** Append tools, replacing any same-named entry (cached stub → live tool). */ private replaceOrPushTools; /** Swap already-promoted cached stubs for their live equivalents, in place. */ private replaceLivePromotedTools; /** * Publish cached tool definitions into the deferred catalog so `tool_search` * answers correctly on turn 1. A cached stub carries the real name, one-line * description and input schema; calling it waits for the live connection and * then dispatches against the real client, or returns a clear error when that * server ultimately failed. Live tools replace stubs on connect. */ private seedMcpCatalogFromCache; /** Catalog-only registration for cached stubs — never marks them live. */ private addCachedMcpTools; private buildCachedMcpTool; /** * Resolve a `/name [args]` input to the prompt template it expands into, or * null when it isn't a prompt-template command for THIS session (an ordinary * message, a registry/action command, or any slash input on a non-coder * agent). Shared by {@link prompt} and {@link willExpandPromptTemplate} so * callers can't drift from the expansion that actually happens. */ private resolveSlashInput; /** * Whether {@link prompt} would expand this input into a template body and * persist it as a user message. Hosts use it to record the typed `/name` for * transcript restore — gating on anything looser risks tagging an unrelated * message when the command turns out NOT to expand. */ willExpandPromptTemplate(content: string): Promise; /** * Process user input. Handles slash commands or runs agent loop. */ prompt(content: string, provenance?: MessageProvenance, options?: { disableTools?: boolean; }): Promise; /** * Prompt with multimodal attachments (images / videos) alongside optional * text. Images and videos become native content blocks the model can see; * non-media files are surfaced as a text note with their saved path so the * agent can open them with its tools. Slash-command parsing is skipped — * attachments are always a direct conversational turn. */ promptWithAttachments(text: string, attachments: SessionAttachment[]): Promise; /** * Build the native content blocks (text + image/video notes + file notes) for * a user message with attachments. Shared by {@link promptWithAttachments} and * the mid-run steering drain so queued media is delivered identically. */ private buildAttachmentParts; /** * Reset per-run self-correction hook state. Mirrors the TUI's run_start * resets so each run evaluates the hooks from a clean slate. `originalRequest` * is the verbatim user ask, pinned for post-compaction re-grounding. */ private resetHookState; /** * Fold one agent event into the hook stat accumulators. Pure bookkeeping — * the same signals the TUI's useAgentLoop collects, so the loop-break and * ideal-review decisions match across the CLI and the app. */ private trackHookEvent; /** * Append every message added since the last flush to the session file. * * Safe to call mid-run: `agentLoop` mutates its message array in place, so * the slice from `lastPersistedIndex` is always exactly the new tail. * Transient sessions (subagent spawns) have no session file and * `persistMessage` no-ops for them. * * Persistence failures must never take down a live run — the post-loop flush * retries the same range. */ private flushPendingMessages; /** * Mid-loop steering hook: delivers pushed notifications and queued user * steering, fires the loop-breaker when the agent looks stuck, then * post-compaction re-grounding. At most one loop-break/re-grounding per run. * Mirrors the TUI's getSteeringMessages ordering. */ private getHookSteeringMessages; /** * Turn-budget extension gate. The loop consults this instead of stopping * mid-task when it exhausts `maxTurns`. Grant ONLY on evidence of progress — * handing more turns to a spinning agent just buys it more tokens to spin * with — so reuse the same stuck signals that drive the loop-breaker. */ private shouldExtendTurnBudget; /** * Would the stop AFTER the current turn inject the Ideal review? Same inputs * as the pre-stop gate below, evaluated early so clients know a candidate * final answer is a review draft BEFORE it streams. * * The turn count is looked ahead by one on purpose. `hookStats.turns` only * advances at `turn_end`, so while the model is writing the draft the counter * still reads the PREVIOUS turn; the real gate sees one more. Without the * lookahead a run sitting on score 3 crosses to 4 on the draft's own * `turn_end` — after the text already streamed — which is precisely the * appear-then-vanish flash. Over-arming by one turn point costs only live * token streaming on a final answer that then shows whole; under-arming costs * the flash, so this errs toward arming. */ private wouldInjectIdealReview; /** Would a stop right now inject the verification gate? Same conditions as * the pre-stop branch below, so arming and injection cannot disagree. */ private wouldInjectVerification; /** Broadcast pre-final hook arming on change. Both edges matter: armed=false * after the hook fires is what lets a client stream the REVIEWED final * answer live again. * * Callable before `initialize()`: the sidecar sets Ken's review suppression * on a freshly constructed session, and every arming predicate below reads * settings that `initialize()` has not loaded yet. Nothing can be armed * before the session can run a turn, and the first `tool_result`/`turn_end` * recomputes both edges — so skipping is the correct answer, not a patch. */ private refreshHookArming; private refreshIdealReviewArmed; /** * Pre-stop Ideal review phase machine. Once review starts, completion is * blocked until harness-owned post-injection reads cover every changed file. */ private getHookFollowUpMessages; private reviewLspEvidence; private withReviewLspEvidence; /** Auto-compact if needed, run agent loop with auth retry, and persist messages. */ private runLoop; switchModel(provider: string, model: string): Promise; /** * Record the switch as its own trailing message at the point it happened, * rather than by rewriting the system prompt. Two reasons: the system prompt * is the cached prefix and rewriting it costs a full cache write, and a * resumed transcript can only attribute output to the right model if the * switch sits in message order. * * Skipped before the conversation starts (nothing to disambiguate) and while * an assistant turn has unresolved tool calls, where inserting a user message * would break tool_use/tool_result pairing. */ private appendModelSwitchNote; private adoptCompactionCheckpoint; /** Canonicalize a deferred restore before a new prompt can fork stale history. */ private adoptDeferredCheckpointBeforePrompt; private persistCompactionCheckpoint; compact(existingCredentials?: { accessToken: string; accountId?: string; projectId?: string; baseUrl?: string; }, mode?: "manual" | "automatic" | "forced"): Promise; newSession(preserveConversation?: boolean): Promise; loadSession(sessionPath: string): Promise; /** * Create a branch at a specific point in the conversation. * Rewinds the message history to the given entry and sets the leaf * so new messages fork from that point. * * @param stepsBack Number of messages to rewind (default: 2 — backs up past last assistant + tool) */ branch(stepsBack?: number): Promise<{ branchedFrom: number; messagesKept: number; }>; /** * List all branches in the current session. */ listBranches(): Promise; getState(): AgentSessionState; /** * Tokens currently in context and the window they are measured against. * * Uses the same accounting as the compaction decision: authoritative provider * usage when we have it (it includes the system prompt and tool schemas), * plus a local estimate of anything appended after that sample. A client * reading this right after a compaction sees the drop, because compaction * clears the retained provider sample along with the messages it measured. * * `costUsd` is present only when EVERY recorded turn has an authoritative * price; a partial sum would read as a full session cost and understate it. */ getContextUsage(): { used: number; size: number; costUsd?: number; }; getPlanMode(): boolean; /** * Suppress only the pre-final Ideal self-review for this live session. * Autopilot uses this while Ken independently owns verification; loop-break * and post-compaction re-grounding remain active. */ setIdealReviewSuppressed(suppressed: boolean): void; /** Queue a user message (optionally with attachments) to be injected mid-run * as steering. Returns the new queue length. No-op semantics are the caller's * concern. */ queueMessage(text: string, attachments?: SessionAttachment[]): number; /** Pending queued messages (id + text), oldest first, for client display. */ listQueuedMessages(): Array<{ id: string; text: string; }>; /** Cancel one pending message by id. Returns true if it was still queued. * A false return is the normal race rather than an error: the message drained * into the run between the client rendering the cancel affordance and the * click arriving. */ cancelQueuedMessage(id: string): boolean; /** Number of messages currently queued. */ getQueuedCount(): number; /** Remove and return the oldest queued message (text + attachments), or null. * Used by the sidecar to run a message that queued while autopilot was * reviewing (no run in flight to steer it into) — unlike {@link drainQueue}, * attachments survive so queued media isn't silently dropped. */ takeNextQueuedMessage(): { text: string; attachments: SessionAttachment[]; } | null; /** Clear the queue, returning the combined text (to restore to the composer). * Queued attachments are dropped on cancel — the composer only restores text. */ drainQueue(): string; /** Snapshot of background processes (bash run_in_background), newest-state. */ listBackgroundProcesses(): BackgroundProcess[]; /** Stop a background process by id. Returns a human-readable status string. */ killBackgroundProcess(id: string): Promise; /** Replace a host-owned system prompt in place without resetting conversation history. */ setCustomSystemPrompt(systemPrompt: string, promptCacheKeyPrefix?: string): void; /** * Toggle plan mode: flips the shared ref (so tools enforce read-only * restrictions) and rebuilds the system prompt in place so the model is told * about the mode change on its next turn. No-op when a custom system prompt * is in force (the host owns the prompt then). */ setPlanMode(active: boolean): Promise; /** * Bake an approved plan into the system prompt so the model is told to emit * `[DONE:n]` markers as it completes each step (the contract the UI's * plan-progress widget reads). Pass `undefined` to clear it. No-op when a * custom system prompt is in force (the host owns the prompt then). */ setApprovedPlan(approvedPlanPath: string | undefined): Promise; /** Extra workspace roots added with `/add-dir`, in the order added. */ getAdditionalRoots(): string[]; /** * Add another workspace root. Tools already accept absolute paths, so this * only widens the write guard and tells the model the root exists. Rebuilding * the system prompt costs one cache-miss turn — the alternative (an uncached * suffix) would drift from the tool behaviour it describes. * * @returns the resolved root, or an error message for the user. */ addDirectory(dir: string): Promise<{ ok: true; root: string; } | { ok: false; error: string; }>; /** Remove an exact root previously added with `/add-dir`. */ removeDirectory(dir: string): Promise<{ ok: true; root: string; } | { ok: false; error: string; }>; /** * Names to advertise as available-on-demand. A tool the model already * promoted lives in `this.tools` and carries its own schema, so it drops out * of the index rather than being listed twice. */ private deferredToolNamesForPrompt; /** * The environment about to be rendered into a prompt, remembered as the * truth the model has been told. Pairs with the env-delta hook: whatever the * prompt states, the model is only corrected when reality moves away from it. */ private recordRenderedEnvironment; /** Environment facts that vary per session rather than per host. */ private promptEnvironment; /** * Build the stable system-prompt prefix for the current tool set and state. * * Three modes, in precedence order: a full replacement (`systemPrompt`), a * composed sub-agent prompt (`agentPrompt` — agent body plus Tools, project * context, return contract and Environment), or the standard prompt. */ private buildBasePrompt; /** Rebuild messages[0] from current plan-mode + approved-plan state. */ private rebuildSystemPromptInPlace; /** * Compose the system message: stable prefix, then everything volatile behind * the `` marker that providers split on for cache control. * * Anything that varies with the live model/provider/thinking level belongs in * the tail. Putting it in the prefix means every model switch rewrites cached * bytes and the next turn pays a full cache write instead of a read. */ private withSystemPromptTail; /** * Sol/Terra async-orchestration guidance for the CURRENT model and thinking * level. Per-switch volatile by definition, so it is rendered as a tail part * rather than spliced into the cached prefix. */ private orchestrationPolicyTail; /** * Re-render the uncached tail of messages[0] in place. The cached prefix is * byte-identical afterwards, so this is safe to call on every model / * thinking-level change. */ private refreshSystemPromptTail; getMessages(): Message[]; getTurnMetrics(): TurnMetricPayload[]; private persistTurnMetric; private rePersistTurnMetrics; /** Ken Kai (mentor) turns recorded against this session, in record order. Used * by the host to interleave Ken's advisory exchanges back into the transcript * on resume. Never part of the LLM message history. */ getKenTurns(): KenTurnPayload[]; /** Autopilot verdict markers recorded against this session, in record order. * Used by the host to interleave the auto-review loop's markers back into * the transcript on resume, mirroring `getKenTurns`. */ getAutopilotMarkers(): AutopilotMarkerPayload[]; /** Non-system messages that are actually on disk. Transcript markers anchor * against this (not the in-memory list, which can run ahead after a failed * run), so hosts computing marker-derived values must use the same base. */ getPersistedTranscriptCount(): number; /** * Rebase every transcript anchor (Ken turns, autopilot verdicts, app markers) * onto a freshly compacted message list. Called right after `this.messages` * is replaced and before the markers are re-persisted into the continuation * file, so the new file carries positions that match its own transcript. */ private remapMarkerAnchors; /** * Record one Ken Kai (mentor agent) turn against this build session: the * user's question + Ken's reply. Kept in memory for the live transcript and * persisted as a `custom` entry (parentId null, so it's never on the message * DAG and never seen by the LLM, and can't race the build session's leaf while * Ken runs concurrently). `afterMessageCount` anchors it among the messages so * the host can interleave it chronologically. No-op persistence for transient * sessions (kept in memory only). Best-effort: a write failure is swallowed by * appendEntry's own handling. */ persistKenTurn(question: string, reply: string): Promise; /** Re-append the in-memory Ken turns to the current session file. Called after * a continuation/compaction file is created so Ken's advisory history isn't * lost when the session is rewritten (those rewrites only re-persist * messages). Each turn keeps its original `afterMessageCount` anchor. */ private rePersistKenTurns; /** * Record one autopilot verdict marker (prompted / done / human / capped) * against this build session. Kept in memory for the live transcript and * persisted as a `custom` entry (parentId null, same as Ken turns) so a * resumed session renders the exact same Ken bubble the live run showed * instead of dropping the marker or falling back to a raw verdict string. * No-op persistence for transient sessions (kept in memory only). */ persistAutopilotMarker(phase: AutopilotMarkerPayload["phase"], extra?: { reason?: string; body?: string; }): Promise; /** Re-append the in-memory autopilot markers to the current session file. * Mirrors `rePersistKenTurns` — called after a continuation/compaction file * is created so the auto-review history survives the rewrite. */ private rePersistAutopilotMarkers; /** App transcript markers recorded against this session, in record order. * Used by the host to interleave display-only rows (plan banner, task * header, errors, user-bubble hints) back into the transcript on resume. */ getAppMarkers(): AppMarkerPayload[]; /** * Record one app transcript marker (display-only row) against this session. * Same treatment as autopilot markers: kept in memory for the live * transcript, persisted as a `custom` entry (parentId null, never on the * message DAG) so a resumed session shows the identical row. `anchorOffset` * shifts the recorded `afterMessageCount` — pass +1 for a marker that should * attach to the user message about to be pushed by the imminent prompt. * No-op persistence for transient sessions. */ persistAppMarker(kind: AppMarkerPayload["kind"], data: Record, anchorOffset?: number): Promise; /** * Open the run journal for one `RunLifecycle` generation. * * Never throws and never blocks the run: a session with no file (transient * children) writes nothing, and a write failure just means the run isn't * journalled — which is strictly better than failing the run over it. */ persistRunStarted(generation: number): Promise; /** Close the run journal. Its absence is what marks a run as crashed. */ persistRunFinished(generation: number, outcome: RunOutcome): Promise; /** Re-append the in-memory app markers to the current session file. Mirrors * `rePersistKenTurns` — called after a continuation/compaction file is * created so display-only rows survive the rewrite. */ private rePersistAppMarkers; /** * Rewrite a draft prompt into a tighter, terminology-correct version using * the ACTIVE provider/model. A stateless one-off LLM call (no agent loop, no * tools, no session mutation) — safe to run even mid-run. Returns the plain * enhanced text plus typed segments marking each corrected term. Errors throw * so the caller can surface them. */ enhancePrompt(text: string): Promise; /** Current reasoning/thinking level, or undefined when thinking is off. */ getThinkingLevel(): ThinkingLevel | undefined; /** Set the reasoning/thinking level (undefined turns thinking off). Takes * effect on the next prompt, since the in-flight loop reads it at start. */ setThinkingLevel(level: ThinkingLevel | undefined): void; /** Replace the abort signal (e.g. after cancellation). */ setSignal(signal: AbortSignal): void; private bindManagerCancellation; /** True when speedProfile is "optimized" (1-h cache TTL + pre-warm), or the * session was constructed with `forceLongCacheRetention` (Ken sessions). */ private isSpeedOptimized; /** * Ordered auth-storage keys the current (provider, model) pair tries, first * match wins. Almost always just the provider id; Xiaomi models can prefer * one endpoint and fall back to another the user configured instead (e.g. * `mimo-v2.5-pro` prefers the Token Plan, falls back to API Credits; the * API-only `mimo-v2.5-pro-ultraspeed` has no fallback). */ private currentAuthStorageKeys; private getPromptCacheKey; /** Stable cache-routing key for downstream sub-agent processes. */ getCurrentCacheKey(): string | undefined; dispose(): Promise; private setSessionPath; private createNewSession; private loadExistingSession; /** * Surface runs that died mid-flight, without resuming them. * * Deliberately NOT auto-resumed: the dead run's tools already wrote files, * ran commands and made commits. Replaying it would duplicate those effects. * The user gets a transcript row and decides. * * Each detected run is also closed as `aborted`, so reopening the session * reports it once rather than on every load. */ private recordInterruptedRuns; private prepareDynamicContext; private persistMessage; private createSlashCommandContext; /** * Import a Claude Code / Codex / Cursor transcript as a resumable GG Coder * session in this session's sessions directory. Never throws — a bad path or * an unrecognized format comes back as `{ ok: false, error }` so both the CLI * and the desktop app can show it verbatim. */ importForeignTranscript(filePath: string, opts?: { cwd?: string; }): Promise; } //# sourceMappingURL=agent-session.d.ts.map