export interface AgentConfig { timeout?: number; // Default: 300000 (5 minutes) // Per-group model/effort overrides (take precedence over global env vars) codexModel?: string; codexEffort?: string; claudeModel?: string; claudeEffort?: string; claudeThinking?: 'adaptive' | 'enabled' | 'disabled'; claudeThinkingBudget?: number; } export type AgentType = 'claude-code' | 'codex'; export type RoomMode = 'single' | 'tribunal'; /** Phase of agent output as emitted by the runner. */ export type AgentOutputPhase = | 'progress' | 'final' | 'tool-activity' | 'intermediate'; /** Phase as visible in the UI (mapped from AgentOutputPhase). */ export type VisiblePhase = 'silent' | 'progress' | 'final'; export type AgentVisibility = 'public' | 'silent'; export type PairedRoomRole = 'owner' | 'reviewer' | 'arbiter'; export type PairedTaskStatus = | 'active' | 'review_ready' | 'in_review' | 'merge_ready' | 'completed' | 'arbiter_requested' | 'in_arbitration'; export type ArbiterVerdict = 'proceed' | 'revise' | 'reset' | 'escalate'; export type PairedWorkspaceRole = 'owner' | 'reviewer'; export type PairedWorkspaceStatus = 'ready' | 'stale'; export interface RoomRoleContext { serviceId: string; role: PairedRoomRole; ownerServiceId: string; reviewerServiceId: string; ownerAgentType?: AgentType; reviewerAgentType?: AgentType | null; failoverOwner: boolean; arbiterServiceId?: string; arbiterAgentType?: AgentType | null; } export interface PairedProject { chat_jid: string; group_folder: string; canonical_work_dir: string; created_at: string; updated_at: string; } export interface PairedTask { id: string; chat_jid: string; group_folder: string; owner_service_id: string; reviewer_service_id: string; owner_agent_type?: AgentType | null; reviewer_agent_type?: AgentType | null; arbiter_agent_type?: AgentType | null; title: string | null; source_ref: string | null; plan_notes: string | null; review_requested_at: string | null; round_trip_count: number; status: PairedTaskStatus; arbiter_verdict: string | null; arbiter_requested_at: string | null; completion_reason: string | null; created_at: string; updated_at: string; } export interface PairedTurnOutput { id: number; task_id: string; turn_number: number; role: PairedRoomRole; output_text: string; created_at: string; } export interface PairedWorkspace { id: string; task_id: string; role: PairedWorkspaceRole; workspace_dir: string; snapshot_source_dir: string | null; snapshot_ref: string | null; status: PairedWorkspaceStatus; snapshot_refreshed_at: string | null; created_at: string; updated_at: string; } export type AgentResponsePolicy = 'normal' | 'silent-unless-addressed'; export type StructuredAgentOutput = | { visibility: 'public'; text: string; } | { visibility: 'silent'; }; export function normalizeAgentOutputPhase( phase?: AgentOutputPhase, ): AgentOutputPhase { return phase ?? 'final'; } export function toVisiblePhase(phase: AgentOutputPhase): VisiblePhase { switch (phase) { case 'intermediate': case 'tool-activity': return 'silent'; case 'progress': return 'progress'; case 'final': return 'final'; default: { const exhaustive: never = phase; throw new Error(`Unknown agent output phase: ${exhaustive}`); } } } export interface RegisteredGroup { name: string; folder: string; trigger: string; added_at: string; agentConfig?: AgentConfig; requiresTrigger?: boolean; // Default: true for groups, false for solo chats isMain?: boolean; // True for the main control group (no trigger, elevated privileges) agentType?: AgentType; workDir?: string; // Working directory for the agent (defaults to group folder) } export interface NewMessage { id: string; chat_jid: string; sender: string; sender_name: string; content: string; timestamp: string; seq?: number; is_from_me?: boolean; is_bot_message?: boolean; } export interface ScheduledTask { id: string; group_folder: string; chat_jid: string; agent_type: AgentType | null; ci_provider?: 'github' | null; ci_metadata?: string | null; max_duration_ms?: number | null; status_message_id: string | null; status_started_at: string | null; prompt: string; schedule_type: 'cron' | 'interval' | 'once'; schedule_value: string; context_mode: 'group' | 'isolated'; next_run: string | null; last_run: string | null; last_result: string | null; status: 'active' | 'paused' | 'completed'; suspended_until?: string | null; created_at: string; } export interface TaskRunLog { task_id: string; run_at: string; duration_ms: number; status: 'success' | 'error'; result: string | null; error: string | null; } // --- Channel abstraction --- export interface ChannelMeta { name: string; position: number; category: string; categoryPosition: number; } export interface Channel { name: string; connect(): Promise; sendMessage(jid: string, text: string): Promise; isConnected(): boolean; ownsJid(jid: string): boolean; // Optional: whether a stored inbound message was authored by this channel's own bot/user. isOwnMessage?(msg: NewMessage): boolean; disconnect(): Promise; // Optional: typing indicator. Channels that support it implement it. setTyping?(jid: string, isTyping: boolean): Promise; // Optional: edit/delete messages (used by status dashboard). editMessage?(jid: string, messageId: string, text: string): Promise; sendAndTrack?(jid: string, text: string): Promise; // Optional: sync group/chat names from the platform. syncGroups?(force: boolean): Promise; // Optional: get channel metadata (position, category) for ordering. getChannelMeta?(jids: string[]): Promise>; // Optional: delete all messages in a channel (used for dashboard cleanup). purgeChannel?(jid: string): Promise; } // Callback type that channels use to deliver inbound messages export type OnInboundMessage = (chatJid: string, message: NewMessage) => void; // Callback for chat metadata discovery. // name is optional — channels that deliver names inline (Telegram) pass it here; // channels that sync names separately (via syncGroups) omit it. export type OnChatMetadata = ( chatJid: string, timestamp: string, name?: string, channel?: string, isGroup?: boolean, ) => void;