/** Submission vocabulary and the startup input gate for the terminal runner. * * Pure over the handles they are given: the queue mutations and the session * guard are decisions the runner applies, and the gate only orders deliveries, * so every branch is testable without composing an Agent. * * @module @deepseek-ai/dsh-code/submissions */ import { type ContentBlock } from '@deepseek-ai/dsh-llm'; import type { Agent, AgentStatus, Inbox } from '@deepseek-ai/dsh-agent'; import type { UserMessage } from '@deepseek-ai/dsh-session'; import type { QueueMutation } from '../ui/ui-contract.ts'; /** One composer submission waiting behind the startup delivery. */ export interface QueuedSubmission { readonly text: string; /** `steer` inserts into the running turn; `followup` waits for the next one. */ readonly mode: 'followup' | 'steer'; readonly images: readonly ContentBlock[]; } /** What one requested queue mutation did; the runner maps it to one notice. */ export type QueueMutationOutcome = 'removed' | 'edited' | 'steered' | 'unavailable' | 'empty' | 'steerUnavailable'; /** * Replace one queued message's text while keeping its attachments. A queue * edit rewrites what the user typed, not what they attached: image and file * blocks ride through in delivery order (text first, then attachments, the * shape {@link deliverLine} submits). Dropping them here would silently strip * an attachment the user already confirmed, so this is the edit's single * definition and the panel's read-only marker only mirrors it. */ export declare function queueEditContent(content: readonly ContentBlock[], text: string): ContentBlock[]; /** * Apply one terminal queue mutation to the live inbox. The decision and the * inbox change are pure over the supplied handles so every branch is testable * without an agent; steering itself is injected because it wakes the driver * rather than mutating the inbox. The durable inbox splices remain the UI's * single source of truth — this helper never reports a state the inbox did not * actually reach. * @param inbox - the live agent inbox (pending lists plus its mutators). * @param status - the agent's lifecycle status; steering needs `running`. * @param messageId - identity of the queued message to mutate. * @param action - the requested mutation. * @param steer - submits the removed message as next-step steering. * @returns the outcome the caller reports. */ export declare function applyQueueMutation(inbox: Pick, status: AgentStatus, messageId: string, action: QueueMutation, steer: (message: UserMessage) => void): QueueMutationOutcome; /** * Cancel the active turn while keeping the next-turn queue, then wake the * driver again so the preserved messages actually run. `cancel` clears * pending work by default and never wakes the driver on its own, so the queue * is captured first and re-submitted afterwards: a waking submission latches * the wake while the aborted activity converges to idle, which is what turns * "preserved" into "sent next" instead of "parked forever". Next-step * steering is deliberately dropped — it belonged to the cancelled turn. * @param agent - the live agent handle. * @returns how many queued messages were preserved across the abort. */ export declare function cancelPreservingQueue(agent: Pick): number; /** * Whether a tagged submission still belongs to the active session. Attachment * prepares resolve on the microtask timeline, while a queued session switch * remounts the app asynchronously — the composing instance's unmount cleanup * runs too late to abort, so the delivery itself carries the composing * session's full id and the runner drops it here when the world moved on. * An untagged (synchronous) or pending-session ('') submission always passes. */ export declare function submissionBelongsToSession(origin: string | undefined, activeSessionId: string | undefined): boolean; /** * Order-preserving gate for composer input while the startup prompt/images * are still preparing. Anything submitted before the startup delivery settles * queues and flushes afterwards in submit order, so the initial request can * never be overtaken by typing that raced a slow image preparation. The flush * also runs when the startup delivery fails: user input is never stranded. */ export declare class StartupInputGate { private readonly deliver; private readonly queued; private pending; constructor(deliver: (submission: QueuedSubmission) => void); /** Submit one line: delivered now while idle, queued behind the startup delivery otherwise. */ submit(submission: QueuedSubmission): void; /** * Run the startup delivery — the callback receives the direct-delivery sink * for the startup prompt itself — then flush everything that queued behind * it, in order, even when the callback rejects. */ run(startup: (deliver: (submission: QueuedSubmission) => void) => Promise): Promise; }