import type { AgentDriver } from "@skaile/workspaces/bridge"; import type { CompactionConfig } from "@skaile/workspaces/core"; import type { CompactionAttemptEvent, CompactionTrigger, SnapshotEvent, TokenUsage } from "@skaile/workspaces/types"; /** * How long to wait for an aborted compaction turn to actually unwind before * retrying. Only the claude-sdk driver's `abort()` waits for its query; the * codex and subprocess drivers return first and reject the next `prompt()` as * "turn already in progress". Sized just above that driver's own 3s grace. */ export declare const ABORT_SETTLE_GRACE_MS = 5000; /** * Prompt tokens actually occupying the context window on the last turn. * * Anthropic reports fresh input separately from cache reads/writes: on a cached * conversation `inputTokens` is ~2-10 while the real context sits in * `cacheReadTokens`, which is why reading `inputTokens` alone made the compaction * threshold unreachable. OpenAI's `inputTokens` already *includes* cache reads, so * `cacheReadTokens` is a subset there — taking the max of the two rather than the * sum keeps that driver from double-counting. */ export declare function contextTokens(usage: TokenUsage | null | undefined): number; /** * Context window the fill is measured against. * * A 1M alias is floored at 1M: the SDK reports the base 200k window for it, so * those sessions compacted far too early. A floor, never an override — the * non-alias branch is unchanged, and a nonsensical `0` must not disable it. */ export declare function resolveContextWindow(driverWindow: number | null, model: string | null): number; /** * Detect whether the captured response from a compaction `prompt()` call is * actually a rate-limit error message returned as text (instead of as a * thrown error). Persisting such a "summary" would corrupt context * restoration on the next wake — the snapshot prompt would inject the error * text as if it were the conversation summary. * * Conservative: requires both a strong signal phrase AND a short response. * Real summaries are routinely 2k+ chars; rate-limit relays are short. * * Issue: I-29. */ export declare function detectRateLimitInSummary(text: string): boolean; /** * Validation of a compaction output's structural shape, run BEFORE we persist * it as a `success`. Cheap synchronous checks only — no LLM calls. * * Failed validation does NOT throw; it returns a structured verdict so the * orchestrator can record a `failed` Compaction row with the right errorCode. * * @param text The captured summary text from the driver. * @param tokensBefore Estimated context size before the call — the whole prompt, cached * tokens included (from {@link contextTokens}), not `usage.inputTokens`. * @param tokensAfter Output token count of the summary (from driver or chars/4 heuristic). * @returns `{ ok: true }` on pass, otherwise `{ ok: false, errorCode }` with one of: * `"empty"` | `"too_short"` | `"poor_ratio"` | `"rate_limit"`. * * Spec: `_devlog/specs/2026-05-05-session-resume-restart-design.md` § Validation at write time. * @docLink packages/runner/capabilities#validate-compaction-output */ export declare function validateCompactionOutput(text: string, tokensBefore: number, tokensAfter: number): { ok: true; } | { ok: false; errorCode: string; }; /** * Options for the {@link CompactionOrchestrator}. * * @docLink packages/runner/dev-guide#compaction */ export interface CompactionOrchestratorOptions { driver: AgentDriver; config?: CompactionConfig; skillDirectives?: string[]; mcpDirectives?: string[]; agentCompactionPrompt?: string; onCompactionStart?: (trigger: CompactionTrigger) => void; onSnapshot?: (snapshot: SnapshotEvent) => void; /** * Emitted on EVERY compaction attempt, success or failure. Phase 1 sink for * the new `Compaction` table observability surface; the host's serve.ts wires * this to forward events on the agent transport. */ onCompactionAttempt?: (event: CompactionAttemptEvent) => void; onCompactionEnd?: () => void; /** * Fires once the compaction call has fully settled and the driver is free. * * Distinct from {@link onCompactionEnd}, which fires earlier on the success * path — before `resetSession()` and the snapshot injection — so that the * injection turn's events are not suppressed. A host holding user turns off * the driver has to wait for this one instead, or it releases them straight * into the injection. * * Guaranteed on every exit, including a throw. */ onCompactionSettled?: () => void; onLog?: (line: string) => void; /** * Phase 3 (resume cascade): supplies the current capability registry * signature at compaction time so it lands on the * {@link CompactionAttemptEvent}. The wake selector compares this against * the runner's current signature to decide tier-1 native SDK resume * eligibility. * * Returns `null` when the host cannot compute a signature (legacy boot * path, registry not yet bootstrapped); the orchestrator records `null` * in that case and the resulting compaction is tier-1 ineligible. */ getCurrentCapabilitySignature?: () => string | null; } /** * Orchestrates context window compaction for a running agent session. * * Monitors token usage against the configured thresholds and triggers * compaction via snapshots when the strategy fires. * * @docLink packages/runner/dev-guide#compaction */ export declare class CompactionOrchestrator { private readonly driver; private readonly strategy; private readonly config; private readonly opts; private _isCompacting; /** Whether {@link CompactionOrchestratorOptions.onCompactionStart} has fired for the current call. */ private _startFired; /** Whether {@link CompactionOrchestratorOptions.onCompactionEnd} has fired for the current call. */ private _endFired; private lastCompactionSeq; private lastCompactionTime; /** * Summary of the last successful compaction, carried into the next one. * Per-instance, in step with `lastCompactionSeq`: a driver rebuild resets * both, so the first compaction after one is not treated as a re-compaction. */ private lastSummary; private currentSeq; private messagesSinceCompaction; constructor(opts: CompactionOrchestratorOptions); /** * How full the context window is. Prefers the driver's per-call figure; the * turn-cumulative `getTokenUsage()` overstates it by 10-40x on a multi-tool * turn, so it is only the fallback for drivers that report no per-call size. * The fallback cannot resurrect that overstatement: a driver clears both * readings together, so a null per-call figure implies no cumulative one. * `?.` guards duck-typed hosts that predate the accessor, not `AgentDriver`. */ private contextFill; trackMessage(seq: number): void; shouldCompact(): boolean; compact(trigger: CompactionTrigger, focus?: string): Promise; /** * Fire {@link CompactionOrchestratorOptions.onCompactionEnd} once per * compaction. The host stops suppressing forwarded events here, so a second * call would emit a stray idle status over the injection turn that follows * it on the success path. */ private _endCompaction; /** * One compaction attempt, plus at most one narrowed retry. * * @param retryOf Set only by the retry path. It suppresses a second * `onCompactionStart` (the retry continues the same compaction phase), * guarantees the retry can never itself retry, and carries the first * attempt's context reading forward. */ private _doCompact; /** * The previous summary as it goes into a re-compaction prompt, capped so the * prompt cannot grow without bound. Truncating loses context the model is * being asked to restate, so it is logged when it happens. */ private _carriedSummary; /** * Start the anti-thrash cooldown after a failed attempt. * * `TokenThresholdStrategy` skips its cooldown gate entirely while * `lastCompactionTime` is null, and only success used to set it — so any * failing session re-ran a full-context compaction on every single turn. */ private _beginCooldown; /** * Whether a failed attempt earns the one narrowed retry. * * Only automatic (threshold) compactions retry: a `manual` or `hibernate` * caller owns the decision to try again, and a caller-supplied `focus` would * be discarded by the retry's own directive. */ private _shouldRetry; /** * Drive the compaction `prompt()` call, capturing summary text from the * driver's raw bridge-level events and any thrown error. The listener is * always removed in `finally`. * * `elapsedMs` is the wall-clock cost of the `prompt()` call. It is the only * thing that separates a genuinely empty model response from a turn that was * settled by something other than the model, which resolves in milliseconds. * * The call is bounded by {@link compactionTimeoutMs}; on expiry the turn is * aborted and `timedOut` is returned so the caller can record a classified * `timeout` rather than wait out the driver's much longer stall watchdog. */ private _runCompactionPrompt; /** * Record a `failed` attempt for a thrown `prompt()` error: classify the * error, map it to an error code, log the raw text, and emit the attempt * event. Signalling compaction end is the caller's job — a retry continues * the same phase. * * @param timedOut Set when the bound in {@link _runCompactionPrompt} expired. * The synthetic message carries none of the classifier's keywords, so * round-tripping it would report the opaque `unknown` this bound replaces. * @returns The error code recorded, so the caller can decide about a retry. */ private _emitThrownFailure; /** * Record a `failed` attempt for output that passed `prompt()` but failed * structural validation. The rejected text is persisted so the next * occurrence is diagnosable from the row alone; signalling compaction end is * the caller's job. */ private _emitValidationFailure; /** * Success path: emit the attempt + snapshot events, update internal state, * and (unless hibernating) reset the driver and inject the snapshot. */ private _completeCompaction; } //# sourceMappingURL=orchestrator.d.ts.map