/** DTOs for agent registration + scoped/revocable bridge tokens (US-007). */ export interface RegisterAgentDto { /** Agent alias slug, e.g. "pi0". Combined with host into the agent slug. */ alias: string; /** Host slug, e.g. "web1". Combined with alias into the agent slug. */ host: string; display_name?: string; } export interface RevokeAgentDto { agent_user_id: string; } export interface RegisterAgentResponse { agent_user_id: string; bridge_token: string; } export interface AgentSummary { agent_user_id: string; alias: string; host: string; display_name?: string; created_at: string; active_token_count: number; } const SLUG_RE = /^[a-z0-9][a-z0-9_.-]*$/; /** Combined agent slug, e.g. alias="pi0", host="web1" -> "pi0-web1". */ export function agentSlug(alias: string, host: string): string { return `${alias}-${host}`; } const assertSlug = (value: unknown, field: string): void => { if (typeof value !== 'string' || value.length === 0 || !SLUG_RE.test(value)) { throw new Error(`${field} must match [a-z0-9][a-z0-9_.-]* (lowercase, non-empty)`); } }; export function validateRegisterAgent(input: unknown): asserts input is RegisterAgentDto { const o = input as Partial | null | undefined; if (!o || typeof o !== 'object') throw new Error('payload must be an object'); assertSlug(o.alias, 'alias'); assertSlug(o.host, 'host'); if (o.display_name !== undefined) { if (typeof o.display_name !== 'string' || o.display_name.length === 0) { throw new Error('display_name must be a non-empty string'); } if (o.display_name.length > 100) { throw new Error('display_name must be at most 100 chars'); } } } export function validateRevokeAgent(input: unknown): asserts input is RevokeAgentDto { const o = input as Partial | null | undefined; if (!o || typeof o !== 'object') throw new Error('payload must be an object'); if (typeof o.agent_user_id !== 'string' || !o.agent_user_id.startsWith('@')) { throw new Error('agent_user_id must be a Matrix user id'); } }