Ir al contenido

Eventos de dominio

El DomainEventBus es un bus de publicación/suscripción con difusión y en proceso. Quien publica llama a publish(event); quien se suscribe consume un flujo tipado on<T>(). Todos los eventos implementan DomainEvent y llevan occurredAt.

El bus se ejecuta dentro de cc_server. Tanto quien publica como quien está suscrito en vivo residen ahí. Ningún cliente ve este bus: el servidor reenvía un subconjunto seleccionado como tramas JSON-RPC notifications/*, filtradas por suscriptor según la pertenencia al espacio de trabajo.

Medida Cantidad
Clases de evento concretas 54
Archivos de origen (packages/cc_domain/lib/core/domain/events/) 14 (más domain_event_bus.dart)
Bases abstractas que nunca se publican directamente 1 (TaskLifecycleEvent, una jerarquía sellada)

Todos los eventos del catálogo de abajo viven en ese único directorio; no hay eventos de dominio declarados en ningún otro lugar.

Eventos de espacios de trabajo, agentes y repos

Sección titulada «Eventos de espacios de trabajo, agentes y repos»
Evento Carga útil Se emite cuando
WorkspaceCreated workspaceId Se crea un espacio de trabajo
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Un agente termina una ejecución (con éxito o con error)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Un agente empieza a esperar a una persona: una aprobación, una pregunta ask_user o una ejecución detenida por una credencial (kind es approval, question o credential)
RepoAdded repoId, path, workspaceId Se registra un repo; activa la indexación de código en segundo plano
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Se instala o actualiza una skill, con su veredicto del análisis de cadena de suministro

El primer bloque es el ciclo de vida propio de la PR. El segundo es el seguimiento de PRs propias: señales sobre una PR que tú abriste, cada una con forUserId para que el sondeo pueda dirigirlas a la persona a la que conciernen en lugar de difundirlas a todos.

Evento Carga útil Se emite cuando
PullRequestPublished prId, workspaceId, repoOwner, repoName Un agente abre una PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? La PR se fusiona, se cierra, se abre, se reabre o se aprueba
PrMerged prId, workspaceId, agentId La PR se fusiona: una señal acotada, solo de fusión, que se conserva para analíticas y notificaciones
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub solicita la revisión del usuario del servidor (sondeo de notificaciones, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle Se menciona al usuario del servidor en una PR (sondeo de notificaciones, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Se fusiona una PR de seguimiento externo (que no es de un agente), detectada por el sondeo de notificaciones de GitHub; se asigna a la categoría de notificación prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR que no es de un agente, encontrada mediante sondeo: solo para pipelines (impulsa external_pr_welcome), no genera ninguna notificación
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId El SHA de cabeza de la PR cambió: el autor hizo push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Una revisión de IA terminada ya no describe la PR que revisó. Solo se emite cuando existe una revisión para el commit reemplazado, así que sigue siendo una señal y no un aviso por cada push

Las seis llevan workspaceId, repoOwner, repoName, prNumber, prTitle y un forUserId opcional.

Evento Carga útil adicional Se emite cuando
PrMergeReadinessChanged ready, reason Una PR de tu autoría pasó a ser fusionable o dejó de serlo
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Un revisor aprobó, solicitó cambios o se descartó una aprobación
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? La CI se puso en rojo o se recuperó
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Alguien te mencionó en un comentario de PR o de revisión
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Alguien respondió en un hilo de revisión en el que participas
PrThreadResolved threadId, commentId?, path?, line? Alguien resolvió un hilo de revisión en el que participas
Evento Carga útil Se emite cuando
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Llega un mensaje nuevo
SpaceCreated spaceId, workspaceId Se crea un espacio
SpaceDeleted spaceId, workspaceId Se elimina un espacio
SpaceProvisioningChanged workspaceId, spaceId, status, step? El espacio de trabajo del espacio entra en aprovisionamiento, alcanza un paso (clonar un repo, hacer checkout de una PR, configurar un agente) o pasa a listo/fallido: el puente de chat lo narra en su tarjeta de tarea
Evento Carga útil Se emite cuando
TicketCreated ticketId, workspaceId Se crea un ticket
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Ticket asignado a un principal o a un equipo
TicketCompleted ticketId El trabajo termina (terminal)
TicketFailed ticketId, errorMessage El agente falla (terminal)
TicketCancelled ticketId Ticket cancelado (terminal)
TicketStatusChanged ticketId, workspaceId, from, to Cualquier cambio de estado
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Ticket reasignado
TicketDetailsUpdated ticketId, workspaceId Cambian el título, la descripción o la prioridad

El flujo unificado del ciclo de vida de las tareas: una ejecución de agente despachada, identificada por el id de su registro de ejecución. Cada evento lleva taskId, un seq monótono por tarea, workspaceId?, agentId? y una phase; las filas de abajo enumeran solo los campos adicionales.

Evento Carga útil Se emite cuando
TaskDispatched — La tarea se entrega a un backend para su ejecución
TaskRunning — La tarea empezó a producir salida
TaskWaitingLocalDirectory lockedPath, holderTaskId? Tarea en espera: otra tarea posee el bloqueo de la ruta en disco que necesita
TaskProgress note? Punto de control de progreso a mitad de la ejecución
TaskMessage messageType, content Mensaje transmitido (text/thinking/tool_use/tool_result/error); se reporta bajo la fase progress
TaskCompleted summary? La tarea terminó correctamente
TaskFailed errorMessage La tarea terminó con un error
TaskCancelled — La tarea se canceló antes de completarse

La enumeración TaskPhase también tiene un miembro queued, pero nada publica un evento de cola: no existe la clase TaskQueued. La primera trama observable de una tarea es TaskDispatched.

Evento Carga útil Se emite cuando
PipelineRunCompleted workspaceId, pipelineRunId, templateId La ejecución termina
PipelineRunCancelled workspaceId, pipelineRunId, templateId La ejecución se cancela (por el usuario o el sistema): una detención deliberada, no un error
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage La ejecución falla

Solo se publican las transiciones terminales. No existe PipelineRunStarted ni ningún evento por paso: el progreso de un paso llega a los clientes a través de las filas de la propia ejecución y del flujo del ciclo de vida de tareas del agente que haya despachado, no a través del bus.

Evento Carga útil Se emite cuando
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Se crea una entrada del registro de auditoría
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop El gasto supera el umbral
Evento Carga útil Se emite cuando
WorkspaceMemberAdded workspaceId, userId, role Un usuario se une a un espacio de trabajo (canje de invitación o alta por un administrador)
WorkspaceMemberRemoved workspaceId, userId Se elimina a un miembro: las sesiones activas de ese usuario en el espacio de trabajo deben volver a comprobar el acceso de inmediato
WorkspaceMemberRoleChanged workspaceId, userId, role Cambia el rol de un miembro

La pertenencia es toda la superficie de identidad en el bus. La creación de usuarios y la revocación de dispositivos ocurren, pero no se publican como eventos de dominio: la revocación se aplica directamente contra las sesiones activas en lugar de canalizarse por el bus.

Evento Carga útil Se emite cuando
CalendarAuthExpired workspaceId, accountEmail El token de refresco de OAuth de una cuenta conectada queda permanentemente inválido (se publica una vez por cada episodio de desconexión)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Un evento de calendario está por comenzar dentro de la ventana de anticipación configurada
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? Una grabación de reunión termina y activa el pipeline integrado meeting_summary
Evento Carga útil Se emite cuando
RigClosedEvent workspaceId, rigId, reason Un rig desapareció, con su motivo de cierre; los consumidores solo actúan ante backendFailure: cualquier otro motivo es solicitado o viene acompañado de un RigReaped
RigControlChanged workspaceId, rigId, controller? Una persona tomó el control exclusivo de un rig o lo devolvió (controller es nulo al liberarlo: el evento no registra quién lo soltó)
RigReaped workspaceId, rigId, reason, agentId? El sistema recuperó una máquina: inactividad, TTL o expulsión por presión de memoria (reportado como idleTimeout), junto con el agente que la dirigía

La apertura de un rig no es un evento. El arranque termina y la fila del rig pasa a listo; no se publica nada, así que no hay ningún RigOpened al que suscribirse.

Varios subsistemas que desde fuera parecen basados en eventos no lo están. Se enumeran aquí porque su ausencia es una decisión de diseño que conviene conocer antes de salir a buscar un enganche:

Subdominio Cómo se mueve el estado en su lugar
Memoria record_memory_fact y compañía escriben y devuelven; la recuperación lee. Nada se publica ante un hecho o un conflicto
Orquestación La propuesta, su aprobación y la ejecución de pipeline generada son filas y llamadas RPC
Documentos de plan submit_plan escribe una revisión; la aprobación materializa una orquestación. Ninguna de las dos se anuncia
Artefactos publish_artifact / revise_artifact escriben revisiones directamente
Aprobaciones La escalada se maneja dentro del servicio de flujo de trabajo de aprobaciones
Sincronización de tickets El coordinador de proveedores se suscribe a los eventos de tickets de arriba; los webhooks entrantes se aplican directamente
Actualización del calendario Una sincronización hace upsert de los eventos y el cliente vuelve a leer; no hay ninguna difusión de actualización
Fusiones de worktrees Se registran en worktree_merge_log y se leen desde ahí

Todos los suscriptores de abajo se construyen dentro de runCcServer (o de un servicio que este crea). No hay ningún suscriptor del lado del cliente.

Suscriptor Eventos que consume
WorkspaceSeeder WorkspaceCreated
Sembrado de la suite de evaluación inicial (en línea) WorkspaceCreated
Aprovisionamiento del espacio de chat (en línea) SpaceCreated
PipelineTriggerDispatcher Todos los DomainEvent; cada disparador habilitado filtra por su propio tipo
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (solo ejecuciones de meeting_summary)
PipelineStepResumeListener AgentRunCompleted
AgentRunTaskCompleter AgentRunCompleted
GoalSupervisor AgentRunCompleted
CheckerDispatchListener AgentRunCompleted
TeamRoutingService AgentRunCompleted, TicketAssigned
OrchestrationRunListener PipelineRunCancelled, PipelineRunFailed
MultiVendorTicketSyncCoordinator TicketCreated, TicketAssigned, TicketReassigned, TicketStatusChanged, TicketDetailsUpdated
WorktreeGcListener TicketCompleted, TicketCancelled, SpaceDeleted, PrMerged, PullRequestStatusChanged
ChatBridgeService MessageReceived, SpaceProvisioningChanged
ActivityLogPersister ActivityLogged
RigEventListener RigControlChanged, RigReaped, RigClosedEvent: inyecta un aviso en lenguaje sencillo en el canal de dirección del agente que la conduce
StaleReviewWatcher PrHeadChanged: publica ReviewBecameStale solo cuando existe una revisión terminada para el commit reemplazado
GitHubPrConversationBridge AgentRunCompleted: respuestas salientes en conversaciones de PR
NotificationFeedRecorder Los veintidós eventos de clase de notificación que aparecen abajo
RemoteEventForwarder Esos veintidós más TicketReassigned y todos los TaskLifecycleEvent. Los tres eventos de pertenencia solo invalidan su caché de pertenencia por espacio de trabajo; no se reenvían como tramas
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved: descarta las suscripciones a espacios de trabajo que ese usuario tenía conectadas; el socket permanece abierto

TicketAssigned es una señal de auditoría y notificación, y un disparador de pipeline. La asignación a un agente registra la propiedad y no inicia ninguna ejecución: no hay ningún despachador de tickets. La asignación a un equipo (assignedTeamId definido) es la excepción: TeamRoutingService despacha al líder del equipo.

TicketRemoteSyncHandler está declarado, pero nunca se construye en el código de producción, así que hoy no se suscribe a nada. El NotificationEventMapper del lado del cliente solo se construye en su propia prueba: es el vestigio de la ruta en proceso anterior a la separación entre cliente y servidor. Los avisos en vivo provienen de las tramas que se describen abajo.

En el servidor, NotificationFeedRecorder y RemoteEventForwarder convierten estos eventos en tramas notifications/* (notification_wire.dart). En el cliente, RpcNotificationMapper → mapNotificationFrame convierte una trama en una AppNotification.

Hay exactamente diecisiete categorías de notificación. Varios eventos se asignan a más de un método de la trama, porque la trama lleva el matiz (falló o se recuperó) mientras que la categoría lleva la pregunta («¿qué está haciendo la CI?»).

Categoría Evento de origen Método de la trama
newMessage MessageReceived notifications/message_received (te menciona)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, sin mencionarte)
agentAwaitingInput AgentAwaitingInput notifications/agent_awaiting_input
ticketAssigned TicketAssigned notifications/ticket_assigned
ticketStatusChanged TicketStatusChanged notifications/ticket_status_changed
pullRequestPublished PullRequestPublished notifications/pr_published
prMerged PrMerged notifications/pr_merged
prMerged ExternalPrMerged notifications/external_pr_merged
prMentioned PrMentioned notifications/pr_mentioned
prMentioned PrCommentMentioned notifications/pr_comment_mentioned
reviewRequested PrReviewRequested notifications/pr_review_requested
reviewStale ReviewBecameStale notifications/review_stale
prMergeReadiness PrMergeReadinessChanged notifications/pr_ready_to_merge / pr_merge_blocked
prReviewDecision PrReviewDecisionChanged notifications/pr_approved / pr_changes_requested / pr_review_dismissed
prChecksStatus PrChecksStatusChanged notifications/pr_checks_failed / pr_checks_recovered
prThreadActivity PrThreadReplied notifications/pr_thread_replied
prThreadActivity PrThreadResolved notifications/pr_thread_resolved
meetingStartsSoon MeetingStartingSoon notifications/meeting_starting_soon
calendarAuthExpired CalendarAuthExpired notifications/calendar_auth_expired
rigStatusChanged RigControlChanged notifications/rig_control_changed
rigStatusChanged RigReaped notifications/rig_reaped
rigStatusChanged RigClosedEvent notifications/rig_closed

La trama de toma de control de un rig no se entrega de vuelta a quien tomó el control, una liberación no se filtra (el evento no registra quién lo soltó) y solo un cierre por backendFailure produce una trama rig_closed: los demás motivos o son solicitados o ya están cubiertos por la trama de recuperación.

AgentAwaitingInput lo publica AgentAwaitingInputPublisher, que vigila los registros de aprobaciones y de bloqueos de credenciales en busca de entradas nuevas y al que AgentQuestionService avisa de cada pregunta ask_user. Una espera es un evento; cuando la espera termina, no se publica nada.

Las cinco categorías de PRs propias llevan forUserId, así que llegan al autor en lugar de a todos los del espacio de trabajo.

RemoteEventForwarder además reenvía TicketReassigned como notifications/ticket_reassigned y todos los TaskLifecycleEvent como notifications/task_<phase>. Ninguno se asigna a una categoría de notificación: impulsan la interfaz en vivo, no el centro de notificaciones. Los eventos de pertenencia no se reenvían como tramas: invalidan la caché del reenviador y los hosts de sesión descartan las suscripciones a espacios de trabajo del miembro eliminado.