import { type AftProjectTransport, type BgNudgeRef } from "@cortexkit/aft-bridge"; import type { PluginContext } from "./types.js"; export interface BgCompletion { task_id: string; status: string; exit_code: number | null; command: string; duration_ms?: number; runtime_ms?: number; runtime?: number; /** * Exit-aware preview of stdout+stderr captured at completion (from Rust): * success = short tail (≤600 B), failure = small head + larger tail * (≤2.25 KiB). Full output stays recoverable via bash_status / file pointers. */ output_preview?: string; /** True when the captured tail is shorter than the actual output. */ output_truncated?: boolean; original_tokens?: number; compressed_tokens?: number; tokens_skipped?: boolean; status_reason?: string; live_descendants?: Array<{ pid: number; comm: string; argv0: string; }> | null; live_descendants_omitted?: number; live_descendants_summary?: string; mode?: "pipes" | "pty" | string; output_path?: string; } export interface PatternMatchEntry { task_id: string; session_id: string; watch_id: string; match_text: string; match_offset: number; context: string; once: boolean; reason?: "pattern_match" | "task_exit"; /** Ack the underlying bash completion after this task-exit reminder is delivered. */ ackCompletionOnDelivery?: boolean; } export interface BgLongRunningReminder { task_id: string; session_id: string; command: string; elapsed_ms: number; mode?: "pipes" | "pty" | string; } type SessionBgState = { outstandingTaskIds: Set; pendingCompletions: BgCompletion[]; pendingLongRunning: BgLongRunningReminder[]; pendingPatternMatches: PatternMatchEntry[]; explicitControlTasks: Set; debounceTimer: NodeJS.Timeout | null; firstCompletionAt: number | null; scheduledFireAt: number | null; scheduledCompletionCount: number; retryDelayMs: number | null; wakeRetryAttempts: number; wakeHardStopped: boolean; forcedDrainCompleted: boolean; unknownCompletions: Array<{ completion: BgCompletion; receivedAt: number; }>; /** * Task IDs spawned since the last session idle boundary. Push completions * for these tasks stay pending but do not send an immediate follow-up; * sync bash_watch may still consume them inline in the same turn. */ wakeDeferredTaskIds: Set; /** * Task IDs whose completions were consumed inline by an explicit * `bash_status({ exit: true, ... })` wait. The bash_completed push * frame for these tasks may arrive AFTER the wait poll loop returned; * without this set, the late frame would land in pendingCompletions * and the next drain would deliver a duplicate reminder. We dedupe * at the ingest boundary so pendingCompletions stays a clean source * of truth. Bounded FIFO at CONSUMED_TASKIDS_CAP. */ consumedTaskIds: Set; consumedTaskOrder: string[]; /** * Task IDs whose delivery is IN FLIGHT (removed from pendingCompletions / * pattern queues, delivery not yet resolved). Ingest skips these so a subc * forced drain in the removal→delivery window can't re-accept and double-deliver * an already-departing completion (audit C-#1). Mirrors the OpenCode twin. */ deliveringTaskIds: Set; /** * Task IDs DELIVERED to the agent but whose `bash_ack_completions` has not yet * confirmed (Rust still holds them and, over subc, re-nudges). Ingest skips them * for fresh delivery AND a forced drain RE-ACKs them (the self-terminating close * of the re-nudge loop, C-#3). Removed by DAEMON RECONCILIATION, not a timer: * when a forced drain no longer returns a task as unacknowledged, it is safe to * forget (R2-T3 — a time TTL could evict a still-held task and reopen the * double-deliver). Insertion-ordered for the FIFO OOM backstop cap. */ deliveredAwaitingAckTaskIds: Set; lastSeenAt: number; }; type TextContent = { type: "text"; text: string; textSignature?: string; }; type ImageContent = { type: "image"; data: string; mimeType: string; }; type ContentBlock = TextContent | ImageContent; type SendUserMessageRuntime = { sendUserMessage: (content: string, options?: { deliverAs?: "steer" | "followUp"; }) => void; }; export declare const sessionBgStates: Map; export declare const SESSION_BG_STATE_IDLE_TTL_MS: number; export declare function setActiveSessionId(sessionId: string | undefined): void; export declare function getActiveSessionId(): string | undefined; interface DrainContext { ctx: PluginContext; directory: string; sessionID?: string; /** Complete provenance for a subc bg_events nudge. */ nudgeRef?: BgNudgeRef; /** Cached bridge so one nudge resolves drain and ack through one authority. */ resolvedBridge?: AftProjectTransport; } /** * Mark a bg task's completion as consumed by an explicit bash_status wait. * Removes it from pendingCompletions so the next wake/in-turn drain * doesn't double-notify the agent. */ export declare function consumeBgCompletion(sessionID: string | undefined, taskId: string): void; export declare function markBgCompletionDelivered(drainContext: DrainContext, taskId: string): Promise; /** * Pre-mark a task as expected to be consumed inline before the wait loop * starts polling. See OpenCode `markTaskWaiting` for full design notes. */ export declare function markTaskWaiting(sessionID: string | undefined, taskId: string): void; /** * Remove a task from the consumed set when the wait loop returned without * seeing terminal status. Without this, future push frames would be * permanently suppressed. */ export declare function unmarkTaskWaiting(sessionID: string | undefined, taskId: string): void; export declare function trackBgTask(sessionID: string | undefined, taskId: string): void; export declare function markExplicitControl(sessionID: string | undefined, taskId: string, trackOutstanding?: boolean): void; export declare function unmarkExplicitControl(sessionID: string | undefined, taskId: string): void; export declare function handlePushedPatternMatch(drainContext: DrainContext & { runtime: SendUserMessageRuntime; }, frame: PatternMatchEntry): Promise; export declare function ingestBgCompletions(sessionID: string | undefined, completions: unknown): BgCompletion[]; export declare function handlePushedBgCompletion(drainContext: DrainContext & { runtime: SendUserMessageRuntime; }, completion: unknown): Promise; export declare function handlePushedBgLongRunning(drainContext: DrainContext & { runtime: SendUserMessageRuntime; }, reminder: BgLongRunningReminder): Promise; export declare function appendToolResultBgCompletions(drainContext: DrainContext, content: ContentBlock[]): Promise; export declare function handleTurnEndBgCompletions(drainContext: DrainContext & { runtime: SendUserMessageRuntime; }): Promise; /** * Subc bg_events wake entrypoint (forced unconditional drain). The subc nudge is * payload-less. The module sends one optimistic nudge when the subscription opens, * then re-nudges while completions or pattern matches remain unacknowledged, so it * always means "drain me now" — even for a task * this process never tracked (prior session / already-cleared outstanding entry), * which the gated drain in {@link triggerWakeIfPending} would skip, leaving the * module to re-arm and nudge forever. The module-side loop lives at * `crates/aft/src/subc/push.rs::{emit_bg_event_wakes,clear_stale_bg_wakes_for_empty_sessions}`: * it re-emits pending wakes until ack clears durable items, so coalescing a duplicate * while this handler is in flight is safe. See the OpenCode twin for the full rationale. */ export declare function handleSubcBgEventsNudge(drainContext: DrainContext & { runtime: SendUserMessageRuntime; }): Promise; export declare function formatSystemReminder(completions: readonly BgCompletion[]): string; export declare function formatLongRunningReminder(reminders: readonly BgLongRunningReminder[]): string; export declare function formatPatternMatchReminder(matches: readonly PatternMatchEntry[]): string; export declare function __setBgNotificationHopTimeoutForTests(timeoutMs: number): void; export declare function __resetBgNotificationStateForTests(): void; export declare function cleanupIdleSessionStates(now?: number): void; export {}; //# sourceMappingURL=bg-notifications.d.ts.map