/** * `session_wait`: one blocking first-party MCP call that replaces the * `sleep 55` plus three or four `session_events`/`session_get` probes an agent * otherwise runs per loop while it waits for a child or peer session. * * The durable `session_events` table is the only authority. The NATS event bus * is live fanout only, so every wake (bus, deadline, or the initial call) is * followed by a database read and the returned rows are always the exact * durable events, never the bus payload. Subscriptions are opened before the * first read so an event committed between the read and the subscription * cannot be missed. * * Timing facts: the built-in `opengeni` MCP server entry carries no * `timeoutMs`, so the worker's MCP client uses the SDK default request timeout * of 60 s and a longer call would surface as a `-32001` tool error. The wait is * therefore capped at {@link SESSION_WAIT_MAX_SECONDS} both in the tool schema * and inside {@link waitForSessionChanges}. The API's `Bun.serve` * `idleTimeout` is 255 s, far above that cap. * * Cancellation: the API serves one transport per POST, so the worker's MCP * `notifications/cancelled` never reaches this handler; instead the route binds * the HTTP request's own abort to `transport.close()` (`request-abort.ts`), * which aborts `extra.signal`. A worker that drops the call on Steer/Pause * therefore ends the server-side wait promptly; the deadline bounds it anyway. * * The live bus is best-effort: a failed subscription degrades the wait to the * durable pre-check plus the deadline re-check and is reported as * `liveFanout: false` rather than failing the tool. */ import type { SessionEvent, SessionEventSemanticClass, SessionEventType } from "@opengeni/contracts"; export declare const SESSION_WAIT_MAX_TARGETS = 16; export declare const SESSION_WAIT_MAX_SECONDS = 50; export declare const SESSION_WAIT_DEFAULT_SECONDS = 45; /** Newest-first durable rows read per target on every database check. */ export declare const SESSION_WAIT_EVENTS_PER_TARGET = 20; /** * The durable event types that matter to a waiter: turn lifecycle, completed * agent messages, blocking failures, goal facts, and session status/control * changes. Raw deltas, tool receipts, sandbox/machine diagnostics, and PTY * noise never wake a waiter; `session_events` remains the drill-down for them. */ export declare const SESSION_WAIT_EVENT_TYPES: readonly ["turn.started", "turn.completed", "turn.failed", "turn.cancelled", "turn.superseded", "turn.capacity_waiting", "agent.message.completed", "session.status.changed", "session.requiresAction", "session.humanInput.requested", "session.control.paused", "session.control.resumed", "session.wait.started", "session.wait.finished", "tool.auth_needed", "session.command.finished", "credential.auth_needed", "rig.setup.failed", "goal.set", "goal.updated", "goal.progress", "goal.rewrite.proposed", "goal.rewrite.rejected", "goal.completed", "goal.paused", "goal.resumed", "goal.cleared", "goal.continuation"]; /** * Settlement and blocking events that make a child result usable. * Goal facts are deliberately absent: an agent can complete its durable goal * before it emits the final assistant message and settles the turn. Completed * agent messages are also absent because commentary messages use the same * event type; an ordinary result-bearing `turn.completed` carries the * authoritative final output. */ export declare const SESSION_WAIT_COMPLETION_EVENT_TYPES: readonly ["turn.completed", "turn.failed", "turn.cancelled", "turn.superseded", "turn.capacity_waiting", "session.requiresAction", "session.humanInput.requested", "session.control.paused", "tool.auth_needed", "credential.auth_needed", "rig.setup.failed", "goal.paused"]; /** * A completed turn is result-bearing only when it carries the ordinary final * output. Segment-limit and maintenance turns settle one execution segment * while the session still has work to do, so they must not release a parent. */ export declare function sessionWaitCompletionEventMatches(event: SessionEvent): boolean; /** The self-session event that announces a newly pending machine input. */ export declare const SESSION_WAIT_OWN_PENDING_EVENT_TYPE = "system.update.pending"; /** * Whether a pending own machine input of this kind ends the wait. Every * pre-existing kind and `child_requires_action` are `immediate`; deferred * child notices are reported but do not end the wait by themselves. */ export declare function ownPendingKindWakes(kind: string): boolean; export declare function sessionWaitSemanticClass(type: SessionEventType): SessionEventSemanticClass; export type SessionWaitTarget = { sessionId: string; afterSequence: number; }; export type SessionWaitEventSummary = { id: string; sequence: number; type: SessionEventType; occurredAt: string; turnId: string | null; turnGeneration: number | null; status: string; text: string | null; failure: { error: string | null; code: string | null; retryable: boolean | null; recovery: string | null; } | null; result?: unknown; }; export type SessionWaitTargetResult = { sessionId: string; afterSequence: number; /** Cursor for the next `session_wait`/`session_events after=` call. */ latestSequence: number; hasMore: boolean; events: SessionWaitEventSummary[]; }; export type SessionWaitResult = { changed: SessionWaitTargetResult[]; ownPendingUpdates: number; ownPendingUpdateKinds: string[]; /** * Pending own inputs whose kind wakes the session by itself (every * pre-existing kind plus `child_requires_action`). Only these end the wait; * `deferred` child notices (resolution, pause, capacity wait, progress) are * reported but keep the wait on the targets. */ ownPendingImmediateUpdates: number; ownPendingDeferredUpdateKinds: string[]; waitedMs: number; timedOut: boolean; aborted: boolean; /** False when at least one live-fanout subscription failed; the wait then relied on the deadline re-check. */ liveFanout: boolean; truncated: boolean; bytes: number; maxBytes: number; }; export type SessionWaitTargetRead = { events: readonly SessionEvent[]; hasMore: boolean; }; export type SessionWaitSource = { /** Durable forward read of matching events strictly after the target cursor. */ readTargetEvents: (target: SessionWaitTarget) => Promise; /** Kinds of the caller's own pending machine inputs; null disables self tracking. */ readOwnPendingUpdateKinds: (() => Promise) | null; /** Live fanout subscription for one session; the returned function unsubscribes. */ subscribe: (sessionId: string, onEvents: (events: SessionEvent[]) => void | Promise) => Promise<() => void>; /** * Re-run target authorization for the sessions about to be returned after a * wait (parity with SSE re-authorization). Throws to refuse the result. */ reauthorizeTargets?: ((sessionIds: readonly string[]) => Promise) | undefined; }; export type SessionWaitInput = { targets: readonly SessionWaitTarget[]; ownSessionId: string | null; maxWaitMs: number; /** Target events that end the wait. Defaults to the ordinary activity set. */ targetEventTypes?: readonly SessionEventType[] | undefined; /** Optional payload-aware refinement applied after the event-type filter. */ targetEventMatches?: ((event: SessionEvent) => boolean) | undefined; source: SessionWaitSource; signal?: AbortSignal | undefined; now?: (() => number) | undefined; maxBytes?: number | undefined; }; export declare function waitForSessionChanges(input: SessionWaitInput): Promise; /** One bounded, result-bearing summary of a durable event for a waiter. */ export declare function summarizeSessionWaitEvent(event: SessionEvent, textChars?: number): SessionWaitEventSummary; /** * Keep the whole model-visible result at or below the `session_events` MCP * envelope. Text is tightened first; only then are newest rows dropped from the * largest target, which keeps each target's `latestSequence` an exact cursor * for the rows actually delivered and marks the remainder as `hasMore`. */ export declare function boundSessionWaitResult(input: Omit, maxBytes?: number): SessionWaitResult;