import type { CanonClient } from './client.js'; import type { SessionRule } from './approval-types.js'; import type { RuntimeCardV1, RuntimeCardNativeMetadata, RuntimeInputAnswers, RuntimeInputChoice, RuntimeInputKind, RuntimeInputNativeMetadata, RuntimeInputQuestion } from './runtime-cards.js'; /** * The five runtime request families. `contact` is non-interactive (no pending * node / no manager loop); the other four flow through {@link RuntimeRequestManager}. */ export type RuntimeRequestKind = 'contact' | 'approval' | 'input' | 'plan' | 'card'; export interface RuntimeRequestContext { agentId: string; ownerId: string; } export type RuntimeResolveReason = 'replied' | 'timeout' | 'aborted'; /** Normalized outcome of a single consume poll. */ export type RuntimeConsumeOutcome = { state: 'pending'; } | { state: 'resolved'; result: Result; } | { state: 'timeout'; }; /** Outcome message the host emits after a request settles (via `onOutcome`). */ export interface RuntimeOutcomeMessage { text: string; metadata: Record; } /** * Everything the descriptor's create step surfaced to the host, handed to * `onCreated`. Hosts that must sequence side effects on the server response * (turn blocks, streaming state, display-vs-poll control flow) read these * instead of re-deriving them. */ export interface RuntimeCreateResult { requestId: string; messageId?: string; /** Card family: whether the created card is interactive (a response is pending). */ interactive?: boolean; expiresAt?: number; } /** * How the built-in input/card descriptors pick `responseUserId` when the payload * does not set it explicitly. An explicit `payload.responseUserId` always wins. * - `'owner'` — fall back to `ctx.ownerId` (omit when that is empty). * - `'infer'` — omit, so the backend targets the owner if present else the sole * other member (correct for agent→user DMs where the owner is not a member). */ export type ResponderPolicy = 'owner' | 'infer'; export interface RuntimeRequestOptions { /** Caller-chosen request id. When omitted the descriptor generates one. */ requestId?: string; /** Absolute deadline (epoch ms). Overrides `timeoutMs`. */ expiresAt?: number; timeoutMs?: number; pollMs?: number; signal?: AbortSignal; /** Responder fallback for the built-in input/card create (see {@link ResponderPolicy}). */ responderPolicy?: ResponderPolicy; /** Host side effects immediately after the server request is created (turn block, typing). */ onCreated?: (created: RuntimeCreateResult) => void | Promise; /** Host emits the durable outcome message after the request settles. */ onOutcome?: (outcome: RuntimeOutcomeMessage) => void | Promise; } /** Error used to reject an in-flight request that is explicitly cancelled. */ export declare class RuntimeRequestCancelledError extends Error { constructor(message?: string); } export interface RuntimeRequestCallInput { client: CanonClient; ctx: RuntimeRequestContext; conversationId: string; requestId: string; payload: Payload; /** Resolved absolute deadline (epoch ms). */ expiresAt: number; state: State | undefined; } export interface RuntimeRequestPreflightInput { client: CanonClient; ctx: RuntimeRequestContext; conversationId: string; payload: Payload; options: RuntimeRequestOptions; } export interface RuntimeRequestPushInput { conversationId: string; message: { senderId: string; metadata?: Record; }; ctx: RuntimeRequestContext; } export interface RuntimeRequestPushMatch { requestId: string; } /** * Everything that varies per request family. The engine ({@link RuntimeRequestManager}) * owns the pending map, timers, poll/notify/abort loop and cancellation; the * descriptor supplies only the family-specific pieces. */ export interface KindDescriptor { readonly kind: RuntimeRequestKind; /** Fresh request id when the caller supplies none. */ generateId(payload: Payload): string; /** Deadline for this request (epoch ms). Falls back to options / default when omitted. */ resolveExpiry?(input: { payload: Payload; options: RuntimeRequestOptions; }): number | undefined; /** Poll cadence (ms). Defaults to the manager default. */ pollMs?(payload: Payload): number; /** * Optional immediate resolution BEFORE any server request is created (e.g. the * approval session-rule fast path). Returning a result skips create + poll. */ preflight?(input: RuntimeRequestPreflightInput): Result | null; /** * Create the server-side request; returns the effective id + optional per-request * state, plus any create-response fields the host needs back (surfaced via * {@link RuntimeCreateResult} to `onCreated`). */ create(input: RuntimeRequestCallInput & { options: RuntimeRequestOptions; }): Promise<{ requestId: string; expiresAt?: number; state?: State; messageId?: string; interactive?: boolean; }>; /** Poll the server once and interpret the response. */ consume(input: RuntimeRequestCallInput): Promise>; /** Best-effort cancellation (abort / explicit cancel / dispose). */ cancel?(input: RuntimeRequestCallInput): Promise; /** Result produced when the local deadline fires. */ timeoutResult(input: RuntimeRequestCallInput): Result; /** * Finalization side effects + optional result override. Runs on `replied` / * `timeout` (never `aborted`). Approval uses this to store the accepted session * rule and emit its own outcome messages; returning a result lets it narrow the * accepted rule into the returned value. */ finalize?(input: RuntimeRequestCallInput & { result: Result; reason: 'replied' | 'timeout'; }): Result | void; /** Outcome message for the host to emit via `onOutcome` (input/card families). */ buildOutcome?(input: RuntimeRequestCallInput & { result: Result; reason: 'replied' | 'timeout'; }): RuntimeOutcomeMessage | null; /** Map an inbound push message to a pending request that should poll now. */ matchPush?(input: RuntimeRequestPushInput): RuntimeRequestPushMatch | null; } /** * Platform-agnostic engine for Canon's interactive runtime requests. * * Generalizes the former `ApprovalManager`: it owns the pending map, deadline * timers, the poll-with-abort loop that races an inbound push wakeup, and * cancellation — all keyed by {@link KindDescriptor}. Approval, runtime input, * runtime card, and plan families register over the same primitive; the former * hand-written await-loops collapse into `request()`. * * Session-rule storage lives here because the approval descriptor's preflight / * finalize read and write it; other families ignore it. */ export declare class RuntimeRequestManager { protected client: CanonClient; protected ctx: RuntimeRequestContext; private descriptors; private pending; private rules; constructor(client: CanonClient, ctx: RuntimeRequestContext); /** Register (or override) the descriptor for a kind. */ register(kind: RuntimeRequestKind, descriptor: KindDescriptor): void; /** Built-in input, card, and plan descriptors. Subclasses add approval policy. */ protected registerBuiltins(): void; get pendingCount(): number; /** * Run one interactive runtime request end to end: * preflight → create → onCreated → register pending → poll (racing push wakeup * and abort) → post-deadline consume → finalize → onOutcome → resolve. */ request(kind: RuntimeRequestKind, conversationId: string, payload: Payload, options?: RuntimeRequestOptions): Promise; /** Push short-circuit: wake the pending request's poll to consume immediately. */ notify(kind: RuntimeRequestKind, conversationId: string, requestId: string): void; /** * Feed an inbound message to the manager. Returns true when a matching pending * request was woken to consume canonical state. Receipt metadata never settles * the request directly. Fails closed on unknown / mismatched ids. */ handleMessage(conversationId: string, message: { senderId: string; metadata?: Record; }): boolean; /** Cancel a specific pending request (rejects its promise, best-effort server cancel). */ cancel(kind: RuntimeRequestKind, conversationId: string, requestId: string): Promise; checkSessionRules(toolName: string, conversationId?: string): SessionRule | null; addSessionRule(conversationId: string, rule: SessionRule): void; getSessionRules(): SessionRule[]; clearSessionRules(): void; dispose(): void; protected pruneExpiredRules(): void; /** Sleep that resolves on timeout, on `notify()` wakeup, or when the request settles. */ private sleep; } /** Interactive runtime input (`AskUserQuestion`, secret prompts, choices). */ export interface RuntimeInputRequestPayload { kind: RuntimeInputKind; title?: string; prompt?: string; choices?: RuntimeInputChoice[]; questions?: RuntimeInputQuestion[]; secretName?: string; native?: RuntimeInputNativeMetadata; sensitive?: boolean; responseUserId?: string; turnId?: string; } export type RuntimeInputRequestResult = { status: 'submitted'; inputId: string; value: string; answers?: RuntimeInputAnswers; } | { status: 'cancelled'; inputId: string; } | { status: 'timeout'; inputId: string; }; export declare const runtimeInputDescriptor: KindDescriptor; /** Interactive runtime card (canon.card.v1 with an actions block). */ export interface RuntimeCardRequestPayload { card: RuntimeCardV1; responseUserId?: string; runtimeId?: string; turnId?: string; native?: RuntimeCardNativeMetadata; } export type RuntimeCardRequestResult = { status: 'submitted'; cardId: string; actionId?: string; values?: Record; respondedBy?: string; } | { status: 'cancelled'; cardId: string; respondedBy?: string; } | { status: 'timeout'; cardId: string; }; export declare const runtimeCardDescriptor: KindDescriptor; /** Blocking plan review produced by coding runtimes. */ export interface RuntimePlanRequestPayload { title?: string; summary?: string; body?: string; allowedPrompts?: ReadonlyArray<{ tool: string; prompt: string; }>; responseUserId?: string; turnId?: string; } export type RuntimePlanRequestResult = { status: 'approve' | 'revise' | 'reject'; planId: string; feedback?: string; grantedPrompts?: Array<{ tool: string; prompt: string; }>; receiptId?: string; } | { status: 'cancelled'; planId: string; } | { status: 'timeout'; planId: string; }; export declare const runtimePlanDescriptor: KindDescriptor;