import type { ExecutionEnvironmentMode } from './execution-environment-mode.js'; import type { ResolvedAgentBehaviorPolicy } from './policy.js'; import type { DeliveryIntent, RuntimeCapabilities, TurnLifecycleState } from './turn-protocol.js'; import type { CanonSelfContext } from './self-context.js'; import type { RuntimeCardFieldType, RuntimeCardV1 } from './runtime-cards.js'; import type { ContactRequestLifecyclePhase, ContactRequestRequirements, GroupInviteRequirements, SerializedAgentClientType } from '@canonmsg/backend-contracts'; export type { ContactRequestLifecyclePhase, ContactRequestRequirements, GroupInviteRequirements, } from '@canonmsg/backend-contracts'; export type { ExecutionEnvironmentMode }; export type MediaAttachmentKind = 'image' | 'audio' | 'video' | 'file'; export type VideoProcessingStatus = 'processing' | 'ready' | 'failed'; export type VideoProcessingErrorCode = 'invalid_video' | 'video_limits_exceeded' | 'video_processing_failed'; export interface MediaAttachment { kind: MediaAttachmentKind; url: string; /** Server-issued identity used to retain a finalized resumable upload. */ uploadId?: string; mimeType?: string; fileName?: string; sizeBytes?: number; width?: number; height?: number; durationMs?: number; thumbnailUrl?: string; processingStatus?: VideoProcessingStatus; processingErrorCode?: VideoProcessingErrorCode; } export interface ForwardedFrom { sourceConversationId: string; messageId: string; } /** * Server-serialized contact-card payload. Emitted on messages with * `contentType: 'contact_card'` so agents receive the referenced user's * identity alongside the card. Agents use the referenced `userId` with an * admission-aware reach-out path; if the target's inbound policy blocks cold * contact, they create a contact request and retry after approval. */ export interface ContactCardPayload { userId: string; canonContactId?: string; displayName: string; avatarUrl: string | null; userType: 'human' | 'ai_agent'; clientType?: AgentClientType; about?: string; isActive?: boolean; ownerId?: string; ownerName?: string; lifecycleState?: string; } export type InteractionKind = 'contact' | 'approval' | 'input' | 'plan' | 'card'; export interface InteractionEnvelope { kind: InteractionKind; requestId: string; interactive: boolean; responseUserId?: string; expiresAt?: string; schemaVersion?: string; preview?: { title?: string; blockKinds?: string[]; }; response?: { actionIds?: string[]; actionFields?: Record; questions?: Array<{ id: string; sensitive?: boolean; }>; }; } export interface CanonMessage { id: string; senderId: string; senderName?: string; senderType: 'human' | 'ai_agent'; /** Whether the sender is this agent's owner (server-computed, trusted) */ isOwner: boolean; contentType: 'text' | 'image' | 'audio' | 'video' | 'file' | 'contact_card' | 'interaction'; text: string | null; attachments: MediaAttachment[]; mentions: string[]; reactions?: Record; replyTo: string | null; replyToPosition: number | null; forwarded?: boolean; forwardedFrom?: ForwardedFrom; metadata?: Record; contactCard?: ContactCardPayload; /** Durable canon.card.v1 runtime card content (persists in chat history). */ runtimeCard?: RuntimeCardV1; /** Plaintext, server-authorized interaction envelope (contentType 'interaction'). */ interaction?: InteractionEnvelope; /** Opaque interaction payload (JSON string or ciphertext); never parsed by transport. */ body?: string; status: 'sent' | 'read'; /** REST responses omit this field on non-deleted messages; CanonClient normalizes it to `false`. */ deleted: boolean; createdAt: string; } export interface CanonConversation { id: string; type: 'direct' | 'group'; name: string | null; topic: string | null; memberIds: string[]; isAgentChat: boolean; behavior?: ResolvedAgentBehaviorPolicy; hasUnread?: boolean; lastMessage: { text: string; messageId: string; senderId: string; senderType: 'human' | 'ai_agent'; contentType: CanonMessage['contentType']; timestamp: string; } | null; /** * Live call in this conversation, or null. Optional for tolerance of * pre-rollout serializers; the documented REST-polling call-state contract. */ activeCall?: CanonActiveCallSummary | null; createdAt: string; } /** Opt-in paging for `GET /conversations`. Omit both fields for the full list. */ export interface CanonConversationsPageOptions { /** Page size, 1..100 (server default 50 when the parameter is present). */ limit?: number; /** Cursor from a previous page's `nextBefore`. */ before?: string; } /** * One page of conversations. Pages are cut in conversation-id order (stable * under live traffic); each page's array is still sorted most-recent-first * *within the page*, so concatenating pages is not globally recency-sorted. * `nextBefore` is null when the sweep is exhausted — a short page is not a * reliable end signal, because hidden conversations are filtered after the * page is read. */ export interface CanonConversationsPage { conversations: CanonConversation[]; nextBefore: string | null; } export interface CanonMessagesPage { messages: CanonMessage[]; behavior?: ResolvedAgentBehaviorPolicy; activeSelfContextIdByMessageId?: Record; selfContexts?: CanonSelfContext[]; } export interface CanonRuntimeProvenance { conversation: { id: string; type: CanonConversation['type'] | 'unknown'; memberCount?: number | null; }; sender: { id: string; name?: string | null; type: CanonMessage['senderType']; isOwner: boolean; }; mentionedAgent: boolean; activeSelfContext?: { id: string; type?: CanonSelfContext['type'] | string | null; } | null; } export type CanonContactRequestStatus = 'pending' | 'approved' | 'rejected' | 'expired' | 'cancelled'; export interface CanonContactRequest { id: string; requesterId: string; requesterName: string; requesterAvatarUrl: string | null; targetId: string; targetName: string; targetAvatarUrl: string | null; targetUserType: 'human' | 'ai_agent'; targetOwnerId?: string | null; approverId: string; /** Always null for agent clients; free-form content stays behind the human approval boundary. */ message: string | null; status: CanonContactRequestStatus; /** 'dm' for one-to-one contact request, 'group_invite' for stranger-add. */ kind: 'dm' | 'group_invite'; groupContext?: { conversationId: string; groupName: string | null; }; /** Independent admission/setup gates for group invites and DMs. */ requirements?: ContactRequestRequirements; phase: ContactRequestLifecyclePhase; sourceConversationId?: string; conversationId?: string; createdAt: string | null; resolvedAt?: string | null; expiresAt?: string | null; } export type ContactRequestPayload = CanonContactRequest; export type ContactRequestUpdatedPayload = CanonContactRequest; export type ContactApprovedPayload = CanonContactRequest; export interface ContactRequestListOptions { direction?: 'inbound' | 'outbound'; includeResolved?: boolean; limit?: number; } export interface ContactRequestLifecyclePageOptions { /** `null` establishes a first-start baseline. */ cursor: string | null; limit?: number; } export interface CanonContactRequestListPage { requests: CanonContactRequest[]; /** Durable continuation checkpoint, including when the page is empty. */ nextCursor: string; hasMore: boolean; } /** * Outcome of `CanonClient.addMember` / `agent.addMember`. The REST endpoint * returns 201 on immediate add and 202 when a group invite is waiting for * policy approval, owner session setup, or both. * Both are success from the protocol's perspective — distinct outcomes for * the caller. */ export type AddMemberResult = { status: 'added'; } | { status: 'pending'; requestId: string; requirements: GroupInviteRequirements; }; /** * Successful outcomes of `createContactRequest`. Errors (denied, * malformed input) come back as Firebase `HttpsError`s and are not * represented here. */ export type CreateContactRequestResult = { status: 'open'; } | { status: 'created'; requestId: string; } | { status: 'duplicate'; requestId: string; }; export type ContactSource = 'direct_add' | 'phone_book' | 'contact_request' | 'link' | 'qr' | 'group' | 'open_inbound_message' /** Sentinel value emitted by stream-service when a doc lacks a source field. */ | 'unknown'; /** A single entry from the agent's `users/{agentId}/contacts/` subcollection. */ export interface CanonContact { /** The contact's userId. */ id: string; source: ContactSource; addedAt: string | null; displayNameOverride: string | null; } export type ContactAddedPayload = CanonContact; export type ContactRemovedPayload = { id: string; }; export type ResolvedAdmissionState = 'self' | 'not-found' | 'allowed' | 'request-required' | 'pending-outbound' | 'blocked' | 'inactive' | 'owner-only'; export interface ResolvedAdmissionTargetSummary { id: string; canonContactId?: string; displayName?: string; avatarUrl?: string | null; about?: string; userType?: 'human' | 'ai_agent'; discoverable?: boolean; inboundPolicy?: 'open' | 'approval-required' | 'owner-only'; groupJoinPolicy?: 'open' | 'approval-required' | 'owner-only'; } export interface ResolvedTargetAdmissionPayload { state: ResolvedAdmissionState; canMessage: boolean; canRequestContact: boolean; isContact: boolean; pendingRequestId?: string; blockedByViewer?: boolean; blockedByTarget?: boolean; } export type ResolveAdmissionTargetInput = { targetUserId: string; canonContactId?: never; } | { canonContactId: string; targetUserId?: never; }; export interface CanonResolveAdmissionResult { resolvedTargetUserId?: string | null; target: ResolvedAdmissionTargetSummary | null; admission: ResolvedTargetAdmissionPayload; } export type AgentClientType = SerializedAgentClientType; /** Whether owner-selected runtime descriptor controls are required before session provisioning. */ export type SessionSetupPolicy = 'runtime_descriptor_optional' | 'runtime_descriptor_required'; export interface ResolvedAdmission { discoverable: boolean; inboundPolicy: 'open' | 'approval-required' | 'owner-only'; groupJoinPolicy: 'open' | 'approval-required' | 'owner-only'; } /** Declares what session controls an agent type supports. */ export interface AgentCapabilities { supportsModelSwitch: boolean; supportsPermissionMode: boolean; /** * Whether permissionMode can be changed mid-session. When undefined, * defaults to `supportsPermissionMode` — keep the UI chip non-interactive * for agents whose approval mode is locked at session creation. */ supportsRuntimePermissionMode?: boolean; supportsEffort: boolean; supportsSessionState: boolean; supportsInterrupt: boolean; supportsQueue?: boolean; supportsInterleave?: boolean; /** * Whether the agent can present a plan for approval before executing * (Mac-app "plan mode"). First-class so clients can advertise/query it * instead of inferring it from the descriptor's turnModes shape. */ supportsPlanMode?: boolean; /** Whether the agent asks the user structured questions (AskUserQuestion-style). */ supportsQuestions?: boolean; } export interface ModelOption { value: string; label: string; description?: string; /** This option may only be selected by the agent owner or the agent itself. */ ownerOnly?: boolean; workspaceRootId?: string; workspaceRelativePath?: string; source?: WorkspaceOptionSource; } export type WorkspaceOptionSource = 'default' | 'explicit' | 'discovered'; export interface WorkspaceOption { id: string; label: string; description?: string; workspaceRootId?: string; workspaceRelativePath?: string; source?: WorkspaceOptionSource; } export interface CanonWorkspaceRootMetadata { id: string; label: string; description?: string; defaultRelativePath?: string | null; } export type CanonControlValue = string; export type RuntimeControlValueSource = 'applied' | 'requested' | 'host-default' | 'route-default' | 'unknown'; export interface RuntimeControlState { value: CanonControlValue | null; source: RuntimeControlValueSource; appliedAt?: number; } export type CanonControlAvailability = 'setup' | 'live' | 'setup_and_live'; export type CanonControlLiveBehavior = 'immediate' | 'next_turn' | 'none'; export type CanonControlSelectionPolicy = 'inherit' | 'required_explicit'; export type CanonRuntimeStreamingMode = 'none' | 'status' | 'snapshot' | 'block' | 'delta'; export type CanonRuntimeVoiceMode = 'realtime'; export type CanonRuntimeSurfaceMode = 'host' | 'channel' | 'limited_channel' | 'operator'; export type CanonRuntimeDetailTier = 'primary' | 'detail' | 'diagnostic'; export type CanonRuntimeVisibility = 'conversation' | 'hidden'; export type CanonRuntimePresentationPreset = 'normal' | 'minimal' | 'full'; export type CanonRuntimePresentationField = 'model' | 'permissionMode' | 'effort' | 'workspace' | 'executionMode' | 'contextUsage' | 'cwd' | 'branch' | 'worktreePath' | 'workspaceRoot' | 'workspaceRelativePath' | 'fallbackReason'; export interface CanonRuntimePresentationHint { visibility?: CanonRuntimeVisibility; tier?: CanonRuntimeDetailTier; sensitive?: boolean; } export interface CanonRuntimePresentationPolicy { preset?: CanonRuntimePresentationPreset; fields?: Partial>; } export type CanonRuntimeFactGroup = 'connection' | 'route' | 'runtime' | 'model' | 'session' | 'account' | 'limits'; export type CanonRuntimeInventoryStatus = 'ready' | 'auth_needed' | 'unknown' | 'configured' | 'running' | 'error'; export type CanonRuntimeStatusTone = 'default' | 'success' | 'warning' | 'danger'; export type CanonRuntimeActionAvailability = 'idle' | 'busy' | 'busy_with_queue' | 'waiting_input' | 'always'; export type CanonRuntimeActionPlacement = 'composer_slash' | 'command_palette' | 'session_strip'; export type CanonRuntimeActionCategory = 'plan' | 'turn' | 'session' | 'runtime' | 'details' | 'skill' | 'custom'; export type CanonRuntimeTurnModeScope = 'next_turn' | 'session'; export type CanonRuntimeTurnModeActivation = { kind: 'message_metadata'; value: string; } | { kind: 'control'; controlId: string; value: CanonControlValue; }; export interface CanonRuntimeTurnModeDescriptor { id: string; label: string; description?: string; scope: CanonRuntimeTurnModeScope; default?: boolean; ownerOnly?: boolean; aliases?: ReadonlyArray; activation?: CanonRuntimeTurnModeActivation; } export type CanonRuntimePrimitiveId = 'runtime.status' | 'runtime.reasoning.set' | 'runtime.verbosity.set' | 'runtime.usage' | 'context.compact' | 'session.new' | 'session.reset'; export type CanonRuntimeCommandArgumentKind = 'string' | 'enum' | 'boolean'; export interface CanonRuntimeCommandArgumentChoice { value: string; label: string; description?: string; } export interface CanonRuntimeCommandArgumentDescriptor { id: string; label: string; kind: CanonRuntimeCommandArgumentKind; required?: boolean; captureRemaining?: boolean; choices?: ReadonlyArray; } export type CanonRuntimeActionDispatch = { kind: 'control'; controlId: string; value?: CanonControlValue; } | { kind: 'signal'; signal: 'interrupt' | 'stop_and_drop' | 'new_session'; } | { kind: 'primitive'; primitive: CanonRuntimePrimitiveId; } | { kind: 'text_passthrough'; template: string; } | { kind: 'compose'; text: string; } | { kind: 'open_details'; target?: string; }; export interface CanonRuntimeActionDescriptor { id: string; label: string; description?: string; visibility?: CanonRuntimeVisibility; tier?: CanonRuntimeDetailTier; sensitive?: boolean; primitive?: CanonRuntimePrimitiveId; aliases?: ReadonlyArray; category?: CanonRuntimeActionCategory; placements?: ReadonlyArray; availability?: ReadonlyArray; ownerOnly?: boolean; disabledReason?: string | null; trailingTextBehavior?: 'ignore' | 'send_as_prompt'; args?: ReadonlyArray; dispatch: CanonRuntimeActionDispatch; } export interface CanonRuntimeCommandDescriptor extends CanonRuntimeActionDescriptor { primitive?: CanonRuntimePrimitiveId; args?: ReadonlyArray; } export interface CanonControlDescriptor { id: string; label: string; options?: ReadonlyArray; defaultValue?: CanonControlValue | null; availability: CanonControlAvailability; liveBehavior: CanonControlLiveBehavior; selectionPolicy: CanonControlSelectionPolicy; description?: string; visibility?: CanonRuntimeVisibility; tier?: CanonRuntimeDetailTier; sensitive?: boolean; } export interface CanonRuntimeRichCardCapability { schema: 'canon.card.v1'; lifecycle?: 'blocking_requires_action'; responder?: 'agent_owner'; result?: 'action_or_values'; maxTimeoutMs?: number; blockKinds: ReadonlyArray<'summary' | 'metricGrid' | 'chart' | 'table' | 'list' | 'callout' | 'actions' | 'mediaPreview' | 'details'>; actionFieldTypes?: ReadonlyArray; native?: boolean; } export interface CanonRuntimeCardCapabilities { rich?: CanonRuntimeRichCardCapability; } export interface CanonRuntimeVoiceCapabilities { modes: ReadonlyArray; provider?: string; supportsTranscript?: boolean; supportsAudioOutput?: boolean; supportsBargeIn?: boolean; } export interface CanonRuntimeDescriptor { coreControls: ReadonlyArray; runtimeControls?: ReadonlyArray; commands?: ReadonlyArray; turnModes?: ReadonlyArray; runtimeCards?: CanonRuntimeCardCapabilities; voice?: CanonRuntimeVoiceCapabilities; /** * Optional setup-time local roots advertised by a runtime. These are * metadata only for now; existing session config still selects concrete * workspace IDs until root-relative directory selection lands. */ workspaceRoots?: ReadonlyArray; writableRoots?: ReadonlyArray; supportsInterrupt?: boolean; supportsInputInterrupt?: boolean; /** * Fidelity of live text exposed through Canon's streaming bubble path. * `delta` means token/content deltas, `block` means chunked live previews, * `snapshot` means completed assistant-message snapshots, and `status` * means activity/tool state without live assistant text. */ streamingTextMode?: CanonRuntimeStreamingMode; /** * Runtime-owned presentation policy for built-in Canon detail fields. * Hidden fields are redacted before public/member-readable runtime payloads * are published; clients only render what remains. */ presentation?: CanonRuntimePresentationPolicy; /** * Contact-graph and admission actions this runtime exposes through the * agent SDK. Plugins set this to advertise which tools they will surface * to the LLM (e.g., block-user, request-contact). When omitted, no * admission actions are advertised. */ admissionActions?: import('./turn-protocol.js').HostAdmissionActionCapabilities; } export interface CanonRuntimeExecutionMetadata { resolvedWorkspaceLabel?: string | null; resolvedCwd?: string | null; workspaceRootId?: string | null; workspaceRelativePath?: string | null; executionMode?: ExecutionEnvironmentMode | null; executionBranch?: string | null; worktreePath?: string | null; fallbackReason?: string | null; } export interface CanonRuntimeStatusItem { id: string; label: string; value: string; tone?: CanonRuntimeStatusTone; tier?: CanonRuntimeDetailTier; sensitive?: boolean; visibility?: CanonRuntimeVisibility; source?: RuntimeControlValueSource; } export interface CanonRuntimeFact { id: string; label: string; value: string; group: CanonRuntimeFactGroup; tier?: CanonRuntimeDetailTier; sensitive?: boolean; visibility?: CanonRuntimeVisibility; tone?: 'neutral' | 'good' | 'warning' | 'danger'; copyable?: boolean; updatedAt?: number; } export type CanonRuntimeActivityKind = 'run' | 'tool' | 'command_output' | 'plan' | 'approval' | 'artifact' | 'compaction' | 'status'; export type CanonRuntimeActivityStatus = 'running' | 'completed' | 'failed' | 'blocked' | 'pending'; export interface CanonRuntimeActivityItem { id: string; runId?: string; kind: CanonRuntimeActivityKind; title: string; status: CanonRuntimeActivityStatus; summary?: string; detail?: string; progressText?: string; startedAt?: number; updatedAt: number; endedAt?: number; visibility?: CanonRuntimeVisibility; tier?: CanonRuntimeDetailTier; sensitive?: boolean; actions?: ReadonlyArray; } export type TurnOutputBlockKind = 'text' | 'tool' | 'plan' | 'approval' | 'input' | 'status'; export type TurnOutputBlockStatus = 'running' | 'completed' | 'failed' | 'pending' | 'blocked'; export interface TurnOutputBlock { id: string; turnId: string; kind: TurnOutputBlockKind; status: TurnOutputBlockStatus; sequence: number; title?: string; text?: string; summary?: string; detail?: string; createdAt?: number; updatedAt?: number; } export type CanonUnifiedDiffFileStatus = 'modified' | 'created' | 'deleted' | 'renamed'; export interface CanonUnifiedDiffFile { /** New path of the file (repo-relative where possible). */ path: string; /** Previous path — only meaningful for `renamed` files. */ oldPath?: string; status: CanonUnifiedDiffFileStatus; /** * Unified hunk text (`@@ … @@` headers plus +/-/context lines). Absent when * the hunk text was dropped to honor the payload budget — the file entry * still renders as a path + counts header. */ diff?: string; /** True when this file's hunk text was clipped or dropped for size. */ truncated?: boolean; /** * True when hunk text was withheld because the path or content looked * secret-bearing (.env-style files, private-key blocks). Clients label the * entry instead of rendering an empty diff. */ suppressed?: boolean; additions?: number; deletions?: number; } /** * File-change preview attached to runtime approval requests so Canon clients * can show the full diff before the owner approves — parity with native * habitats (Claude Code TUI, Codex Mac app). Size-disciplined via * {@link truncateUnifiedDiff}; renderers must honor the `truncated` flags. */ export interface CanonUnifiedDiff { files: CanonUnifiedDiffFile[]; /** True when files were dropped or clipped to honor the payload budget. */ truncated?: boolean; } export interface CanonRuntimeInventoryEntry { id: string; label: string; status?: CanonRuntimeInventoryStatus; description?: string; tier?: CanonRuntimeDetailTier; sensitive?: boolean; visibility?: CanonRuntimeVisibility; } export interface CanonRuntimeInventory { id: string; label: string; entries: ReadonlyArray; tier?: CanonRuntimeDetailTier; sensitive?: boolean; visibility?: CanonRuntimeVisibility; } export interface RuntimeInfoPayload { descriptor: CanonRuntimeDescriptor; surfaceMode?: CanonRuntimeSurfaceMode; surfaceLabel?: string; /** * Prominent capability warning surfaced in the session strip (not just the * details panel) — e.g. a degraded transport that silently disables * approval gates. Keep it one sentence with a concrete remedy. */ warning?: string; facts?: ReadonlyArray; statusItems?: ReadonlyArray; inventories?: ReadonlyArray; execution?: CanonRuntimeExecutionMetadata | null; notes?: ReadonlyArray; updatedAt?: number; } /** Capability map keyed by clientType. Add new agent types here. */ export declare const AGENT_CAPABILITIES: Record; /** Trusted agent identity & access context, provided by the server */ export interface AgentContext { agentId: string; canonContactId?: string; displayName?: string; avatarUrl?: string | null; description?: string | null; ownerId: string; ownerName: string; discoverable: boolean; inboundPolicy: 'open' | 'approval-required' | 'owner-only'; groupJoinPolicy: 'open' | 'approval-required' | 'owner-only'; /** Identifies the agent's client platform for UI feature detection */ clientType?: AgentClientType; sessionSetupPolicy?: SessionSetupPolicy; defaultBehavior?: ResolvedAgentBehaviorPolicy; } export interface UpdateAgentProfileOptions { name?: string; avatarUrl?: string | null; description?: string; } export interface CanonTurnDispatch { kind: 'run_turn' | 'observe_only'; reason: string; addressed: boolean; policyVersion?: string; } export interface MessageCreatedPayload { conversationId: string; behavior?: ResolvedAgentBehaviorPolicy; activeSelfContextId?: string | null; selfContexts?: CanonSelfContext[]; provenance?: CanonRuntimeProvenance; /** Server-computed turn dispatch decision. Runtimes should only start turns for `run_turn`. */ turnDispatch?: CanonTurnDispatch; message: { id: string; senderId: string; senderName?: string; senderType?: 'human' | 'ai_agent'; /** Whether the sender is this agent's owner (server-computed, trusted) */ isOwner?: boolean; text?: string; contentType?: 'text' | 'image' | 'audio' | 'video' | 'file' | 'contact_card' | 'interaction'; attachments?: MediaAttachment[]; replyTo?: string; replyToPosition?: number; forwarded?: boolean; forwardedFrom?: ForwardedFrom; mentions?: string[]; reactions?: Record; createdAt?: string; /** Structured metadata for rich UI (approval cards, etc.) */ metadata?: Record; /** Populated when `contentType === 'contact_card'`. */ contactCard?: ContactCardPayload; }; } export interface MessageUpdatedPayload { conversationId: string; messageId: string; actorId?: string; updatedAt?: string; changes: { reactions?: Record; /** Canonical ready attachments after asynchronous media processing. */ attachments?: MediaAttachment[]; [key: string]: unknown; }; } export interface TypingPayload { conversationId: string; userId: string; isTyping: boolean; status?: 'thinking' | 'typing'; timestamp?: number; } export interface PresencePayload { userId: string; online: boolean; } export interface RuntimeUpdatedPayload { conversationId: string; agentId: string; runtime: AgentRuntime | null; runtimeInfo?: RuntimeInfoPayload | null; agentSession: AgentSessionSnapshot | null; } export interface TurnUpdatedPayload { conversationId: string; agentId: string; turn: import('./turn-protocol.js').TurnState | null; } /** * Observe-only notice that the participation gate suppressed a turn this * agent would otherwise have been dispatched (cap reached, mention required, * agent-to-agent disabled). Hosts must not run a turn off this event; it * exists so a benched agent can tell deliberate policy from a dead stream. */ export interface ParticipationSuppressedPayload { conversationId: string; /** The triggering message whose dispatch was suppressed. */ messageId: string; /** Machine code, e.g. 'agent_turn_limit_reached' | 'group_mention_required'. */ reasonCode: string; /** Human-readable form of the same reason. */ reason: string; /** ISO timestamp stamped by stream-service at suppression time. */ suppressedAt: string; } export type RuntimeControlEventKind = 'session' | 'signal' | 'primitive' | 'runtimeInput' | 'runtimeCard' | 'runtimeApproval'; export interface RuntimeControlEventPayload { conversationId: string; agentId: string; kind: RuntimeControlEventKind; requestId?: string; updatedAt?: number; value: Record; } export interface CanonMembershipChange { addedMemberIds: string[]; removedMemberIds: string[]; memberCount: number; } export interface CanonKnownRecentParticipant { id: string; name: string; userType: 'human' | 'ai_agent' | 'unknown'; isOwner: boolean; isSelf: boolean; } export interface CanonGroupContext { memberCount: number; memberIds: string[]; ownerId: string; ownerName: string; ownerPresent: boolean; knownRecentParticipants: CanonKnownRecentParticipant[]; membershipChange?: CanonMembershipChange; } export type CanonGroupContextMode = 'initial' | 'membership_change'; export interface ConversationUpdatedPayload { conversationId: string; changes: Record; membershipChange?: CanonMembershipChange; } export interface VoiceSessionEventPayload { conversationId: string; session: CanonVoiceSession; /** * True when this call targets the receiving agent (runtime_voice aimed at * it). Voice events are conversation-scoped — group and human-mode calls * arrive with targetsMe false; never auto-join on a bare 'started'. * Absent on streams older than the media/video rollout: treat as targeted * (the legacy filter only delivered targeted sessions). */ targetsMe?: boolean; } export type CanonStreamEvent = { type: 'agent.context'; payload: AgentContext; } | { type: 'message.created'; payload: MessageCreatedPayload; } | { type: 'message.updated'; payload: MessageUpdatedPayload; } | { type: 'contact.request'; payload: ContactRequestPayload; } | { type: 'contact.request.updated'; payload: ContactRequestUpdatedPayload; } | { type: 'contact.approved'; payload: ContactApprovedPayload; } | { type: 'contact.added'; payload: ContactAddedPayload; } | { type: 'contact.removed'; payload: ContactRemovedPayload; } | { type: 'typing'; payload: TypingPayload; } | { type: 'presence'; payload: PresencePayload; } | { type: 'runtime.updated'; payload: RuntimeUpdatedPayload; } | { type: 'turn.updated'; payload: TurnUpdatedPayload; } | { type: 'runtime.control'; payload: RuntimeControlEventPayload; } | { type: 'message.deleted'; payload: { conversationId: string; messageId: string; }; } | { type: 'conversation.updated'; payload: ConversationUpdatedPayload; } | { type: 'voice.session.started'; payload: VoiceSessionEventPayload; } | { type: 'voice.session.ended'; payload: VoiceSessionEventPayload; }; export interface SendMessageOptions { messageId?: string; contentType?: 'text' | 'audio' | 'image' | 'video' | 'file' | 'contact_card'; replyTo?: string; replyToPosition?: number; attachments?: MediaAttachment[]; contactCardUserId?: string; mentions?: string[]; selfContextId?: string | null; /** Structured metadata for rich UI (approval cards, etc.) */ metadata?: Record; } export type DirectSessionSelection = { mode: 'new'; } | { mode: 'continue_latest'; } | { mode: 'continue_or_create'; } | { mode: 'specific'; conversationId: string; }; export interface CreateConversationOptions { type: 'direct' | 'group'; targetUserId?: string; memberIds?: string[]; name?: string; /** Required when creating a direct conversation with a first-party coding agent. */ sessionConfig?: SessionConfig | null; /** Direct agent chats can explicitly choose whether to reuse or start a session. */ sessionSelection?: DirectSessionSelection; } export interface CreateConversationResult { conversationId: string; created?: boolean; reused?: boolean; sessionSelection?: DirectSessionSelection['mode']; } export type CanonVoiceSessionMode = 'human' | 'runtime_voice'; export type CanonVoiceSessionStatus = 'active' | 'ended' | 'error'; /** Call media. Absent anywhere in the wire always means 'audio'. */ export type CanonVoiceSessionMedia = 'audio' | 'video'; /** GET /conversations call-state summary for REST-polling consumers. */ export interface CanonActiveCallSummary { sessionId: string; media: CanonVoiceSessionMedia; mode: CanonVoiceSessionMode; startedAt: string | null; } export interface CanonVoiceSessionParticipant { userId: string; userType: 'human' | 'ai_agent' | 'system'; displayName?: string | null; joinedAt?: string | null; leftAt?: string | null; } export interface CanonVoiceSession { id: string; conversationId: string; roomName: string; mode: CanonVoiceSessionMode; media?: CanonVoiceSessionMedia; status: CanonVoiceSessionStatus; createdBy: string; participantIds: string[]; participants?: CanonVoiceSessionParticipant[]; declinedUserIds?: string[]; targetAgentId?: string | null; createdAt?: string | null; updatedAt?: string | null; endedAt?: string | null; error?: string | null; } export interface CanonVoiceSessionToken { sessionId: string; conversationId: string; roomName: string; url: string; token: string; identity: string; mode: CanonVoiceSessionMode; media?: CanonVoiceSessionMedia; targetAgentId?: string | null; expiresAt: string; } export interface CreateVoiceSessionOptions { conversationId: string; targetAgentId?: string | null; /** Requested call media; the server defaults absent/unknown to 'audio'. */ media?: CanonVoiceSessionMedia; } export type CanonMemoStreamStatus = 'live' | 'finalizing' | 'finalized' | 'aborted'; export interface CreateMemoStreamOptions { conversationId: string; /** v1 accepts 'wav' only (byte-append decodable and seekable everywhere). */ container: 'wav'; codec: { mimeType: string; sampleRate: number; channels: number; bitsPerSample: number; }; /** Byte offset of PCM data in the sender's file (RIFF headers precede it). */ dataOffset?: number; /** Pre-allocated durable message id, for the live→durable timeline handoff. */ messageId?: string | null; } export interface CreateMemoStreamResult { streamId: string; bucket: string; } export interface AppendMemoStreamChunkOptions { /** Zero-based, strictly sequential. Retries of the last chunk are acked. */ seq: number; /** Base64 chunk bytes; may be empty when only carrying the final marker. */ data: string; /** Normalized 0..1 amplitude bars for live waveforms. */ peaks?: number[]; /** Running duration of the recording in milliseconds. */ durationMs?: number; /** Marks the recording finished; the stream leaves the live window. */ final?: boolean; } export interface MemoStreamChunkAck { chunkCount: number; totalBytes: number; applied: boolean; } export interface JoinVoiceSessionOptions { conversationId: string; } export type StreamingStatus = 'thinking' | 'streaming' | 'tool' | 'waiting_input'; export interface SetStreamingOptions { conversationId: string; text: string; status: StreamingStatus; messageId: string; turnId?: string | null; blocks?: TurnOutputBlock[]; } export interface SetRuntimeTurnOptions { conversationId: string; state: TurnLifecycleState; turnId?: string | null; queueDepth?: number; currentSpeakerId?: string | null; lastAcceptedIntent?: DeliveryIntent | null; activeMessageIds?: string[]; capabilities?: Partial; openedAt?: number | null; /** * Set when this publish represents real turn progress. Omit for heartbeat or * status-only refreshes so Canon can preserve stale-turn recovery semantics. */ turnUpdatedAt?: number | null; } /** Written by Canon app to /control/{convoId}/{agentId}/session in RTDB */ export interface SessionControl { model?: string; permissionMode?: string; effort?: string; runtimeControlValues?: Record; updatedAt: number; updatedBy: string; } export interface RuntimeControlError { value: string; message: string; updatedAt: number; } /** Runtime-applied state projected into the canonical agent-session snapshot. */ export interface SessionState { lastError?: string; model?: string; permissionMode?: string; effort?: string; runtimeControlValues?: Record; controlState?: Record; runtimeControlErrors?: Record | null; cwd?: string; executionMode?: 'worktree' | 'locked'; executionBranch?: string; worktreePath?: string; executionFallbackReason?: string; clientType?: AgentClientType; /** True when the agent is running under the host wrapper (host.ts) which can apply control signals */ hostMode?: boolean; isActive: boolean; /** Runtimes that cannot report a context window (e.g. Codex) publish totalTokens only. */ contextUsage?: { percentage?: number; totalTokens: number; maxTokens?: number; }; availableModels?: ModelOption[]; updatedAt: number; } export interface SessionConfig { clientType?: AgentClientType; hostMode?: boolean; model?: string; permissionMode?: string; effort?: string; runtimeControlValues?: Record; workspaceId?: string; /** * Explicitly selected execution mode. Sessions created before this field * existed stay `undefined`; UIs must prompt for a value and plugin hosts * fail-closed rather than inferring one. */ executionMode?: ExecutionEnvironmentMode; availableModels?: ModelOption[]; workspaceOptions?: WorkspaceOption[]; availableExecutionModes?: ExecutionEnvironmentMode[]; updatedAt?: number; } export interface PermissionModeOption { value: string; label: string; description?: string; /** Runtime-owned risk marker; Canon enforces owner-only selection generically. */ ownerOnly?: boolean; } export declare const CLAUDE_PERMISSION_MODE_OPTIONS: readonly [{ readonly value: "default"; readonly label: "Default"; }, { readonly value: "acceptEdits"; readonly label: "Auto-edit"; }, { readonly value: "plan"; readonly label: "Plan"; }, { readonly value: "dontAsk"; readonly label: "Don't ask"; readonly ownerOnly: true; }, { readonly value: "bypassPermissions"; readonly label: "Bypass"; readonly ownerOnly: true; }, { readonly value: "auto"; readonly label: "Auto"; readonly ownerOnly: true; }]; export interface AgentRuntime { clientType?: AgentClientType; hostMode?: boolean; defaultModel?: string; defaultPermissionMode?: string; availablePermissionModes?: PermissionModeOption[]; runtimeDescriptor?: CanonRuntimeDescriptor; defaultWorkspaceId?: string; /** * Execution modes the host will accept. The runtime advertises this so the * app can offer matching choices; it is NOT used to auto-populate missing * session-config values. */ availableExecutionModes?: ExecutionEnvironmentMode[]; /** * Reference default surfaced to UI. Treated as advisory only — callers must * still have the user confirm a selection before persisting. */ defaultExecutionMode?: ExecutionEnvironmentMode; availableModels?: ModelOption[]; availableWorkspaces?: WorkspaceOption[]; updatedAt?: number; } export interface AgentSessionSnapshot { conversationId: string; agentId: string; clientType?: AgentClientType; hostMode?: boolean; model?: string; modelOptions?: ModelOption[]; permissionMode?: string; permissionModeOptions?: PermissionModeOption[]; effort?: string; runtimeControlValues?: Record; controlState?: Record; runtimeControlErrors?: Record | null; runtimeDescriptor?: CanonRuntimeDescriptor | null; runtimeInfo?: RuntimeInfoPayload | null; runtimeActivity?: Record | ReadonlyArray | null; workspaceId?: string; workspaceOptions?: WorkspaceOption[]; executionMode?: ExecutionEnvironmentMode; availableExecutionModes?: ExecutionEnvironmentMode[]; executionBranch?: string; resolvedWorkspaceLabel?: string | null; resolvedCwd?: string | null; worktreePath?: string | null; executionFallbackReason?: string | null; turnState?: TurnLifecycleState; turnId?: string | null; turnOpenedAt?: number | null; turnUpdatedAt?: number | null; supportsQueue?: boolean; supportsInputInterrupt?: boolean; queueDepth: number; waitingForInput: boolean; contextUsage?: SessionState['contextUsage']; lastError?: string; lastHeartbeatAt?: number; updatedAt?: number; } export interface RegistrationInput { name: string; description: string; ownerPhone: string; developerInfo?: string; avatarUrl?: string; baseUrl?: string; clientType?: AgentClientType; sessionSetupPolicy?: SessionSetupPolicy; requestedAgentId?: string; localRegistrationId?: string; } export interface RegistrationResult { status: 'approved' | 'rejected' | 'timeout'; apiKey?: string; agentId?: string; agentName?: string; requestId?: string; pollToken?: string; } export interface RegistrationStatus { status: 'pending' | 'approving' | 'approved' | 'rejected'; agentName: string; agentId?: string; apiKey?: string; apiKeyDelivered?: boolean; }