/**
 * WebSocket Hub — event bus for real-time change notifications.
 *
 * Tracks authenticated WebSocket connections and broadcasts entity/repo
 * change events scoped to each client's folder access.
 */
import type { WebSocket } from 'ws';
import type { AuthUser } from '../services/auth-service.js';
/**
 * Identifies an agent that drove a mutation or activity event. Null/undefined =
 * human-driven (the default). `kind: 'internal'` = the in-app web agent, scoped
 * to a user session. `kind: 'external'` = an MCP-connected agent (Cursor, Claude
 * Desktop, etc.), scoped to an api_tokens row. `label` is what the UI renders,
 * already prefixed with the owning user (e.g. "Christian's Cursor").
 *
 * `userId` is the owning user's id (as string, matching the presence shape).
 * The client uses it to color the agent's chip with the same accent as the
 * owning human's avatar — making it obvious at a glance which person's agent
 * is acting. Optional: absent for unauthenticated/system contexts.
 */
export interface AgentIdentity {
    kind: 'internal' | 'external';
    label: string;
    userId?: string;
}
export interface WsChangeEvent {
    type: 'entity_change';
    action: 'created' | 'updated' | 'deleted';
    repo: string;
    entityType: string;
    entityId: string;
    actor: string;
    source: 'api' | 'git-push';
    agent?: AgentIdentity;
    timestamp: string;
}
export interface WsRepoSyncEvent {
    type: 'repo_sync';
    repo: string;
    /** 'git-push' = sync from a remote push. 'api' = a single API call mutated
     *  many entries at once and asks listeners to reload (e.g. transfer-folder). */
    source: 'git-push' | 'api';
    actor: string;
    agent?: AgentIdentity;
    timestamp: string;
}
/** Emitted when a user's edit session closes and changes are committed. */
export interface WsSessionCommitEvent {
    type: 'session_commit';
    repo: string;
    entities: string[];
    actor: string;
    agent?: AgentIdentity;
    timestamp: string;
}
/** Emitted when a sync pipeline run completes for a folder. */
export interface WsSyncCompleteEvent {
    type: 'sync_complete';
    repo: string;
    source: string;
    created: number;
    updated: number;
    deleted: number;
    actor: string;
    timestamp: string;
}
/** Emitted during a sync run to report progress steps. */
export interface WsSyncProgressEvent {
    type: 'sync_progress';
    repo: string;
    message: string;
    actor: string;
    timestamp: string;
}
/** Emitted when a folder is created or deleted. */
export interface WsFolderEvent {
    type: 'folder_created' | 'folder_deleted';
    repo: string;
    actor: string;
    timestamp: string;
}
/** Emitted when a user's access to a folder is granted or revoked. */
export interface WsAccessEvent {
    type: 'access_granted' | 'access_revoked';
    repo: string;
    userId: string;
    actor: string;
    timestamp: string;
}
/** Emitted when a user connects or disconnects (workspace-level online status). */
export interface WsOnlineEvent {
    type: 'online';
    action: 'join' | 'leave';
    user: {
        id: string;
        name: string;
    };
    timestamp: string;
}
/** Emitted when a user enters or leaves an entity view (presence). */
export interface WsPresenceEvent {
    type: 'presence';
    action: 'enter' | 'leave' | 'heartbeat';
    repo: string;
    entityType: string;
    entityId: string;
    user: {
        id: string;
        name: string;
        color?: string;
    };
    timestamp: string;
}
/**
 * Emitted by the server when an agent (internal or external) is actively
 * working in a folder. Mirrors the human-presence pattern: `enter` on tool-call
 * start, `heartbeat` while in flight, `leave` when done. The hub TTL-cleans
 * stale entries so a crashed agent's chip disappears within ~90s.
 *
 * `action` lets the UI render verb-specific copy ("creating", "editing", …);
 * omit for generic "active" presence.
 */
export interface WsAgentActivityEvent {
    type: 'agent_activity';
    action: 'enter' | 'leave' | 'heartbeat';
    repo: string;
    agent: AgentIdentity;
    /** Optional verb hint for the chip label. */
    verb?: 'creating' | 'editing' | 'deleting' | 'reading';
    timestamp: string;
}
/**
 * Polymorphic mention notification. `sourceType` is always 'comment' in
 * Phase 1; the shape stays open so future surfaces can ride the same wire.
 */
export interface WsUserMentionEvent {
    type: 'user_mention';
    userId: number;
    /** Persisted notifications.db row id — used by the client to mark read on click. */
    notificationId: number;
    sourceType: string;
    sourceId: string;
    mentionerName: string;
    preview: string;
    timestamp: string;
    /** Entry coords for deep-linking from the notifications dropdown to the
     *  source comment. Null for sources that don't live on an entry. */
    entryRepo: string | null;
    entryType: string | null;
    entryId: string | null;
    threadId: string | null;
}
/** Emitted when a comment is created, edited, or its lifecycle changes. */
export interface WsCommentEvent {
    type: 'comment_created' | 'comment_updated' | 'comment_resolved' | 'comment_reopened' | 'comment_deleted';
    repo: string;
    entryType: string;
    entryId: string;
    commentId: string;
    threadId: string;
    /** Present on create/update; omitted on lifecycle-only events. */
    comment?: unknown;
    timestamp: string;
}
/** Emitted when the agent starts/stops processing an @agent mention on a comment. */
export interface WsCommentAgentThinkingEvent {
    type: 'comment_agent_thinking';
    repo: string;
    entryType: string;
    entryId: string;
    threadId: string;
    thinking: boolean;
    timestamp: string;
}
export type WsEvent = WsChangeEvent | WsRepoSyncEvent | WsSessionCommitEvent | WsSyncCompleteEvent | WsSyncProgressEvent | WsFolderEvent | WsAgentActivityEvent | WsUserMentionEvent | WsCommentEvent | WsCommentAgentThinkingEvent | WsPresenceEvent | WsOnlineEvent | WsAccessEvent | WsStateClearedEvent;
/** Server-side `/api/clear` or `/api/reset` just wiped this user's chat
 *  sessions / memory / DB state. Tells any open browser tab to drop its
 *  localStorage chat transcript so it doesn't re-hydrate stale messages
 *  on next reload (and re-send them as fallbackHistory). */
interface WsStateClearedEvent {
    type: 'state_cleared';
    target: 'sessions' | 'memory' | 'all';
    timestamp: string;
}
interface AgentActivityEntry {
    agent: AgentIdentity;
    verb?: 'creating' | 'editing' | 'deleting' | 'reading';
    lastSeen: number;
}
export declare class WsHub {
    private clients;
    private heartbeatTimer;
    private presenceMap;
    private agentActivityMap;
    private agentActivityLastBroadcast;
    private onlineMap;
    private serverVersion;
    constructor(serverVersion: string);
    addClient(ws: WebSocket, user: AuthUser | null, folders: string[] | null): void;
    /**
     * Handle a presence message sent by a client.
     * Updates server-side presence state and broadcasts to folder peers.
     */
    handlePresence(action: 'enter' | 'leave' | 'heartbeat', repo: string, entityType: string, entityId: string, user: {
        id: string;
        name: string;
        color?: string;
    }): void;
    /** Return current presence for an entity (for initial state on connect). */
    getPresence(repo: string, entityType: string, entityId: string): Array<{
        id: string;
        name: string;
        color?: string;
    }>;
    /**
     * Record agent activity in a folder. Called by server-side wrappers around
     * tool invocations (orchestrator + MCP). Throttles broadcasts to at most one
     * per (agent, repo) per AGENT_ACTIVITY_THROTTLE_MS so a fast-firing agent
     * doesn't drown the wire — the underlying state still updates so TTL stays
     * accurate, only the broadcast is suppressed.
     *
     * `enter` and `leave` always broadcast; only `heartbeat` is throttled.
     */
    handleAgentActivity(action: 'enter' | 'leave' | 'heartbeat', repo: string, agent: AgentIdentity, verb?: 'creating' | 'editing' | 'deleting' | 'reading'): void;
    /** Return active agents for a repo (snapshot for newly connected clients). */
    getAgentActivity(repo: string): AgentActivityEntry[];
    /** Send the current agent-activity map to a newly connected client. */
    sendAgentActivitySnapshot(ws: WebSocket, folders: Set<string> | null): void;
    /** Send the full current presence map to a single newly connected client. */
    sendPresenceSnapshot(ws: WebSocket, folders: Set<string> | null): void;
    private broadcastOnline;
    private sendOnlineSnapshot;
    private clearUserPresence;
    broadcast(event: WsEvent): void;
    /** Send an event directly to all connections belonging to a specific user, bypassing repo filtering. */
    broadcastToUser(userId: string, event: WsEvent): void;
    /**
     * Update every connected socket owned by `userId` so its cached folder
     * access set includes `folderName`. Called when access is granted at
     * runtime (folder created, access_granted) — without this, the
     * connection-time snapshot stays stale and `canReceive` keeps dropping
     * `entity_change` broadcasts for the new folder until the user reconnects.
     * No-op for clients in open mode (`folders === null`, already receives all).
     */
    addUserFolder(userId: string, folderName: string): void;
    /** Inverse of `addUserFolder` — drop `folderName` from the cached set on
     *  every connection owned by `userId`. Called on access revocation so
     *  `canReceive` stops admitting broadcasts for a folder the user no
     *  longer has access to. */
    removeUserFolder(userId: string, folderName: string): void;
    /** Record an app-level pong from a client (called by ws route on message). */
    handlePong(ws: WebSocket): void;
    /** Iterate over all connected client sockets (for collab broadcasting). */
    forEachSocket(fn: (ws: WebSocket) => void): void;
    get connectionCount(): number;
    destroy(): void;
    private canReceive;
}
export {};
