/**
* Periodic orchestrator wake scheduler.
*
* After continuous parent-idle time, capability-gated host session APIs may
* receive a static internal wake prompt when incomplete todos remain. Active
* children suppress periodic wakes. Host responses are authoritative; the local
* job board is never consulted. Progress/reservation state is process-global
* so independently created hook instances share one-flight and the two-wake
* no-progress cap.
*
* v2 hosts (hostFlavor 'v2', stamped by the client shim) have no todo/
* children/status surfaces, so the scheduler runs there in a children-driven
* degraded mode: children are enumerated via `session.list({parentID})`
* (event-tracked fallback when the listing is unavailable), the wake
* condition is "children without a terminal outcome" plus stopped-job
* recovery, and the wake prompt is delivered with `delivery: 'queue'`
* (v1 prompt_async queued; v2 steer would hijack an in-flight run). All new
* behavior is behind the host-flavor/capability probe — the v1 code path is
* unchanged.
*/
import type { PluginInput } from '@opencode-ai/plugin';
import type { SessionSelection } from '../../utils/session-selection';
import type { SessionLifecycle } from '../session-lifecycle';
export declare const ORCHESTRATOR_WAKE_TEXT = "\nFinish any incomplete TODOs. Await running agents; if one appears stuck, assess it and cancel/respawn only when justified. Do not respond to this reminder.\n";
export declare const ORCHESTRATOR_STOPPED_JOB_WAKE_TEXT = "\nA background job stopped without a terminal result. Consult the Background Job Board, recover or reroute the work as needed, and do not wait for that job as if it were still running. Do not respond to this reminder.\n";
/** Self-contained terminal delta appended to the stopped-job recovery wake.
* The board snapshot path cannot serve this wake: under the
* `checkpoint-compatible` injection strategy, internal-initiator messages
* (which this wake is) are excluded from creating a board snapshot, so the
* first snapshot the parent sees after the wake reflects a board state from
* BEFORE the job stopped. A wake that says "check the board" with no board
* entry behind it leaves the parent guessing. The delta carries the facts
* of the triggering stop inline: alias, task id, run generation, state, and
* why it stopped. Deduplicated per execution by the caller. */
export declare function formatStoppedJobDelta(record: {
alias: string;
taskID: string;
generation: number;
state: string;
reason: string;
}): string;
/** Children-mode variant (v2 degraded mode): watchdog over background
* children and unreconciled jobs instead of the todo list. */
export declare const ORCHESTRATOR_CHILDREN_WAKE_TEXT = "\nCheck on unfinished background child sessions and unreconciled jobs. Await running agents; if one appears stuck, assess it and cancel/respawn only when justified. Do not respond to this reminder.\n";
/** After this many successful wakes with an unchanged fingerprint, stop. */
export declare const ORCHESTRATOR_WAKE_UNCHANGED_CAP = 2;
/** Max stopped-job deltas queued per parent. Oldest entries are dropped
* when a new distinct stop would exceed the cap, so a busy/waiting parent
* cannot grow an unbounded recovery prompt. */
export declare const STOPPED_RECOVERY_QUEUE_CAP = 32;
/** Max deltas appended to one recovery wake. Remaining entries stay queued
* for the next wake so a failed oversized join cannot wedge the batch. */
export declare const STOPPED_RECOVERY_WAKE_CHUNK = 8;
/** Recovery facts that overflow the bounded detail queue still produce a
* durable, actionable signal. The parent can consult the board for the facts
* whose inline details were coalesced. */
export declare const STOPPED_RECOVERY_OVERFLOW_TEXT = "\nAdditional stopped-job recovery facts were queued beyond the inline detail limit. Consult the Background Job Board for all unreconciled stopped jobs.\n";
/**
* Children-driven mode: a child with `outcome === undefined` counts as
* inactive once its newest update evidence (host `time.updated` or a
* tracked status change) is older than this multiple of the wake interval.
* Bounds wakes when a child crashes mid-run without recording an outcome;
* stopped-job recovery remains the explicit path for such children.
*/
export declare const CHILD_STALENESS_INTERVALS = 3;
type LocalSessionState = {
/** Invalidates local timers/async work for this hook instance. */
generation: symbol;
timer: ReturnType | undefined;
continuousIdle: boolean;
archived: boolean;
};
export type OrchestratorWakeConfig = {
enabled: boolean;
intervalMs: number;
/** Wake-condition source; resolved against host capabilities (see
* `resolveWakeMode`). Optional for callers built before the field
* existed — absent means 'auto'. */
mode?: 'auto' | 'todo' | 'children';
};
export type OrchestratorWakeOptions = {
config: OrchestratorWakeConfig;
shouldManageSession: (sessionID: string) => boolean;
hasInputWait: (sessionID: string) => boolean;
isFallbackInProgress?: (sessionID: string) => boolean;
coordinator?: SessionLifecycle;
/** Revalidate a queued stop immediately before delivering its recovery wake.
* The callback must check both the task generation and that the current
* record is still stopped and terminal-unreconciled. */
isStoppedJobRecoveryCurrent?: (taskID: string, generation: number) => boolean;
/** Resolve the session's CURRENT agent/model selection at send time
* (#1079): a lifecycle wake must continue the parent in the mode the
* session uses now, never a hardcoded `orchestrator`. When absent or
* unresolved, behavior falls back to the historical orchestrator wake. */
resolveSelection?: (sessionID: string) => Promise;
/** True when the parent session has delegated work pending: live
* children or terminal-unreconciled records (#1079). In that state a
* lifecycle wake stays eligible even when the user switched the
* session to a non-orchestrator agent — the wake continues in the
* CURRENT selection instead of forcing `orchestrator`. */
hasPendingDelegatedWork?: (sessionID: string) => boolean;
/** Test seam: override interval without changing config validation. */
intervalMs?: number;
};
/**
* Capability record for the host session surface. The v1 branch keeps
* exactly the historical probe set (get/todo/children/status/promptAsync);
* the v2 branch (hostFlavor 'v2', stamped by the client shim) requires only
* list+promptAsync — `get` is optional enrichment and todo/children/status
* have no v2 equivalent (children-driven degraded mode covers them).
*/
export type WakeSessionApis = {
flavor: 'v1' | 'v2';
hasGet: boolean;
hasTodo: boolean;
hasChildren: boolean;
hasStatus: boolean;
hasList: boolean;
hasPromptAsync: boolean;
/** True when the scheduler can operate against this host surface. */
ready: boolean;
};
export type ResolvedWakeMode = 'todo' | 'children';
/**
* Resolve the configured wake mode against host capabilities: 'auto' uses
* todo-gating on v1 and children-driven degraded mode on v2; an explicit
* 'todo' degrades to children on hosts without the todo API (v2).
*/
export declare function resolveWakeMode(configured: 'auto' | 'todo' | 'children' | undefined, caps: Pick): ResolvedWakeMode;
/** Normalized child view for children-driven wake decisions. */
export type WakeChildInfo = {
id: string;
/** v2 Session.Info.outcome — present only on terminal transition
* (succeeded|failed|interrupted). */
outcome?: string;
/** Workspace directory when the host reports it (scope filter). */
directory?: string;
/** Newest update-evidence timestamp (epoch ms) when known. */
evidenceAt?: number;
};
/** Event-tracked session status (busy-set + parent-active race guard). */
export type TrackedSessionStatus = {
status: 'busy' | 'idle';
at: number;
};
/** Numeric variant of the update-evidence cascade (staleness bound). */
export declare function childUpdateEvidenceMs(child: Record): number | undefined;
/** Map one host child/list entry to the normalized children-mode view. */
export declare function mapWakeChild(child: Record): WakeChildInfo | undefined;
/**
* Active-child determination for children-driven mode: a terminal outcome
* always wins; otherwise the child is active while its newest evidence —
* host update time OR tracked status change (the event busy-set) — is
* fresher than the staleness bound. Children with no evidence at all are
* inactive (cannot be proven active).
*/
export declare function isWakeChildActive(child: WakeChildInfo, tracked: TrackedSessionStatus | undefined, now: number, stalenessMs: number): boolean;
/** Children-mode fingerprint: id + outcome + tracked status + evidence. */
export declare function buildChildrenWakeFingerprint(children: Array, trackedStatuses: ReadonlyMap): string;
export declare function buildOrchestratorWakeFingerprint(todos: Array>, children: Array>, status: Record): string;
export declare function createOrchestratorWakeScheduler(ctx: PluginInput, options: OrchestratorWakeOptions): {
event: (input: {
event: {
type: string;
properties?: unknown;
data?: unknown;
};
}) => Promise;
observeChatMessage: (input: unknown, output: unknown) => void;
triggerStoppedJobRecovery: (sessionID: string, delta?: string, dedupeKey?: string) => void;
/** Clear timers when wait_for_user or fallback begins. */
suppress: (sessionID: string) => void;
/** Test seam */
_test: {
localSessions: Map;
intervalMs: number;
enabled: boolean;
hasRequiredSessionApis: () => boolean;
capabilities: () => WakeSessionApis;
wakeMode: () => ResolvedWakeMode;
lastStatusBySession: Map;
childEvidence: Map;
childSessions: Map>;
};
};
export {};