/** * Bounded batch continuation for the canonical Sortie-dogs coordinator. * * The coordinator asset requires a continuation loop: after a terminal unit and its checkpoint it * must compact and resume the same root session on the next independent unit. That policy is only * a policy; without a runtime capability the coordinator has nothing to invoke and silently stops * after the first unit. This module supplies that capability. * * Identity is the whole safety story. Continuation re-prompts a session on the caller's behalf, so * it may only ever target the configured coordinator agent running as a root session. A child * session is never promoted to root and a different coordinator is never adopted; every unresolved * identity fails closed into "no automatic continuation" rather than into a guess. */ import type { ModelTarget } from "./model-routing.js"; /** Plugin tool name the coordinator asset names as the direct continuation capability. */ export declare const CONTINUATION_CAPABILITY = "sortie_compact_and_continue"; /** Fallback marker, used only when the direct capability is unavailable. */ export declare const CONTINUATION_MARKER = ""; /** Terminal marker: compact the batch without resuming it. */ export declare const ROLLOVER_MARKER = ""; /** First token of the synthetic resume prompt, so the coordinator can recognize its own resume. */ export declare const AUTO_CONTINUE_PREFIX = "SORTIE_AUTO_CONTINUE"; export declare const STEP_CONTINUE_PREFIX = "SORTIE_STEP_CONTINUE"; /** First line the rollover summary must emit, mirroring the batch target of three attempts. */ export declare const ROLLOVER_TOKEN = "SORTIE_ROLLOVER_COMPACTED"; export declare const DEFAULT_MAX_AUTO_CONTINUES = 10; /** One centrally configured default for stalled implementation Task recovery. */ export declare const DEFAULT_TASK_WATCHDOG_MILLISECONDS: number; /** * A coordinator that exhausted its step budget reports remaining work instead of continuing. That * report is a continuation request in every respect except the marker, so it is treated as one. * The pattern stays deliberately narrow: a step-exhaustion statement followed by remaining work. */ export declare const STEP_EXHAUSTED_PATTERN: RegExp; declare const DEFAULT_TIMINGS: { /** Compaction is expensive; one rollover per minute per session is the canonical ceiling. */ readonly cooldownMilliseconds: 60000; /** Let the new summary's token state settle before a fresh turn can trigger overflow compaction. */ readonly settleMilliseconds: 1500; /** A delayed retry backs up the immediate rollover kick and the normal session-idle path. */ readonly scheduleMilliseconds: 30000; readonly scheduleAttempts: 2; /** Debounce completed text parts while retaining one-shot hosts that omit session.idle. */ readonly stepRecoveryMilliseconds: 1000; }; export type ContinuationTimings = typeof DEFAULT_TIMINGS; export type ContinuationRejection = "identity-unavailable" | "child-session" | "agent-mismatch" | "capability-unavailable" | "continuation-disabled" | "limit-reached" | "pending-autocontinue" | "fresh-session-required" | "summarize-model-unavailable"; export interface ContinuationIdentity { readonly agent?: string | undefined; readonly parentID?: string | undefined; readonly parentPresent?: boolean | undefined; } export interface ContinuationResolution { /** Queue a compaction of the source session. */ readonly compact: boolean; /** Resume the same root session after that compaction. */ readonly continue: boolean; readonly reason?: ContinuationRejection; } export interface ContinuationResolutionInput { readonly identity: ContinuationIdentity | undefined; readonly configuredAgent: string | undefined; readonly configuredCapability: string | undefined; readonly requestedCapability: string; readonly enabled: boolean; /** Continuations already granted to this session. */ readonly attempts: number; readonly maxAutoContinues: number; readonly pendingAutoContinue: boolean; } /** * The single resolver every continuation path shares: the direct tool, the marker fallback, and the * step-exhausted fallback. Keeping it pure means the identity policy is provable without a host. */ export declare function resolveContinuation(input: ContinuationResolutionInput): ContinuationResolution; /** The subset of the OpenCode SDK client continuation depends on. */ export interface ContinuationClient { readonly session?: { readonly abort?: (request: { path: { id: string; }; query?: { directory?: string; }; }) => Promise; readonly create?: (request: { query?: { directory?: string; }; body?: Record; }) => Promise; readonly delete?: (request: { path: { id: string; }; query?: { directory?: string; }; }) => Promise; readonly get?: (request: { path: { id: string; }; }) => Promise; readonly summarize?: (request: { path: { id: string; }; query?: { directory?: string; }; body: { providerID: string; modelID: string; }; }) => Promise; readonly promptAsync?: (request: { path: { id: string; }; query?: { directory?: string; }; body: { agent: string; parts: ReadonlyArray<{ type: "text"; synthetic?: boolean; text: string; metadata?: Record; }>; }; }) => Promise; }; } export interface ContinuationPolicy { readonly enabled: boolean; readonly agent: string; readonly capability: string; readonly maxAutoContinues: number; /** Absent means the host chooses the compaction model; this package never pins one. */ readonly summarizeModel?: ModelTarget | undefined; } /** * Configuration loads lazily, but the tool must be registered when the plugin is constructed. A * resolver lets the registered capability read the effective policy at call time instead of * freezing whatever was known at construction. */ export type ContinuationPolicySource = ContinuationPolicy | (() => ContinuationPolicy); export interface ContinuationTicketAuthority { issueTicket(sessionID: string, checkpoint: string): Promise<{ readonly issued: boolean; readonly metadata: Record; }>; } /** * The plugin already learns which sessions run the coordinator as a root from its own message hook. * Trusting that observation first keeps continuation working on a host whose session lookup answers * without an agent field, or answers for a different directory, instead of failing silently. */ export type LocalIdentitySource = (sessionID: string) => ContinuationIdentity | undefined; /** Preview-only durable state reference. It enriches continuation but never grants authority. */ export type ContinuationCheckpointSource = (sessionID: string) => Promise; export type RolloverAbort = "identity-unavailable" | "child-session" | "summarize-unavailable" | "summarize-model-unavailable" | "retries-exhausted" | "terminal-identity-rejected" | "compaction-summary-malformed"; export interface ContinuationToolContext { readonly sessionID: string; readonly agent?: string | undefined; } export interface ContinuationTool { readonly name: string; readonly description: string; execute(args: Record, context: ContinuationToolContext): Promise; } export interface ContinuationHooks { readonly tool: ContinuationTool; textComplete(input: { sessionID: string; allowCheckpointContinuation?: boolean; allowStepRecoveryFallback?: boolean; }, output: { text: string; }): Promise; sessionCompacting(input: { sessionID: string; }, output: { context?: string[]; prompt?: string; }): Promise; sessionCompacted(sessionID: string): Promise; compactionAutoContinue(input: { sessionID: string; overflow?: boolean; }, output: { enabled: boolean; }): Promise; observeModel(sessionID: string, model: { providerID: string; modelID: string; }, synthetic?: boolean): void; toolStarted(sessionID: string, tool: string): void; blocksTool(sessionID: string): boolean; sessionIdle(sessionID: string): Promise; stopAutomaticRecovery(sessionID: string, abortSession?: boolean, resumeOnRealUserTurn?: boolean): Promise; recoverStalledTask(sessionID: string, callIDs: readonly string[]): Promise<"recovered" | "identity-rejected" | "capability-unavailable" | "request-rejected">; forgetSession(sessionID: string): void; } export type ContinuationTransitionType = "continuation.queued" | "continuation.compacted" | "continuation.resumed" | "continuation.not_required"; export type ContinuationTransitionReason = "continuation-requested" | "compaction-only" | "summarize-accepted" | "prompt-accepted" | "terminal-checkpoint"; export interface ContinuationTransition { readonly type: ContinuationTransitionType; readonly sessionID: string; readonly epoch: number; readonly reason: ContinuationTransitionReason; readonly attempts: number; readonly resumeAttempts: number; } export type ContinuationTransitionObserver = (transition: ContinuationTransition) => void; /** * Build the continuation runtime. Every hook fails closed: an unreadable session, an absent client, * or a rejected identity leaves the coordinator exactly where it was, with manual continuation * still available to the user. */ export declare function createContinuationHooks(client: ContinuationClient | undefined, directory: string, policySource: ContinuationPolicySource, timings?: ContinuationTimings, localIdentity?: LocalIdentitySource, transitionObserver?: ContinuationTransitionObserver, ticketAuthority?: ContinuationTicketAuthority, checkpointSource?: ContinuationCheckpointSource): ContinuationHooks; export {};