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 Recuento
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 ningún evento de dominio declarado en otro sitio.

Eventos de espacios de trabajo, agentes y repos

Sección titulada «Eventos de espacios de trabajo, agentes y repos»
Evento Carga útil Se activa 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 la 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.

Evento Carga útil Se activa 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 a fusiones que se conserva para análisis 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 (no de un agente), detectada por el sondeo de notificaciones de GitHub; se corresponde con la categoría de notificación prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR de origen no agente encontrada mediante sondeo: solo para pipelines (alimenta external_pr_welcome), no genera ninguna notificación
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId El SHA head de la PR cambió: el autor hizo push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Una revisión con IA finalizada ya no describe la PR que revisó. Solo se activa cuando existe una revisión del commit sustituido, así que sigue siendo una señal y no un aviso por push

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

Evento Carga útil adicional Se activa cuando
PrMergeReadinessChanged ready, reason Una PR que escribiste pasó a ser fusionable, o dejó de serlo
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Un revisor aprobó, solicitó cambios o se anuló una aprobación suya
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? La CI pasó a estar en rojo, o se recuperó
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Alguien te mencionó en un comentario de una PR o de una revisión
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Alguien respondió en un hilo de revisión en el que estás
PrThreadResolved threadId, commentId?, path?, line? Alguien resolvió un hilo de revisión en el que estás
Evento Carga útil Se activa 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 o fallido: el puente de chat lo narra en su tarjeta de tarea
Evento Carga útil Se activa 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 finaliza (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. Todos los eventos llevan taskId, un seq monotónico por tarea, workspaceId?, agentId? y una phase; las filas de abajo solo enumeran los campos adicionales.

Evento Carga útil Se activa cuando
TaskDispatched — La tarea se entrega a un backend para su ejecución
TaskRunning — La tarea empezó a producir salida
TaskWaitingLocalDirectory lockedPath, holderTaskId? La tarea queda en espera: otra tarea posee el bloqueo de ruta en disco que necesita
TaskProgress note? Punto de control de progreso a mitad de ejecución
TaskMessage messageType, content Mensaje transmitido en flujo (text/thinking/tool_use/tool_result/error); se informa 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 en cola: no existe ninguna clase TaskQueued. La primera trama observable de una tarea es TaskDispatched.

Evento Carga útil Se activa cuando
PipelineRunCompleted workspaceId, pipelineRunId, templateId La ejecución finaliza
PipelineRunCancelled workspaceId, pipelineRunId, templateId Ejecución cancelada (por el usuario o por 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 ningún PipelineRunStarted ni ningún evento por paso: el progreso de un paso llega a los clientes a través de las propias filas de la ejecución y del flujo del ciclo de vida de la tarea del agente que haya despachado, no a través del bus.

Evento Carga útil Se activa 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 activa cuando
WorkspaceMemberAdded workspaceId, userId, role Un usuario se une a un espacio de trabajo (canje de una invitación o alta de un administrador)
WorkspaceMemberRemoved workspaceId, userId Se elimina a un miembro: las sesiones activas de ese usuario con ámbito del 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 que hay 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 activa 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á a punto de empezar dentro de la ventana de antelación configurada
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? Una grabación de reunión finaliza y activa el pipeline integrado meeting_summary
Evento Carga útil Se activa cuando
RigClosedEvent workspaceId, rigId, reason Un rig desapareció, con su motivo de cierre; los consumidores solo reaccionan a backendFailure: cualquier otro motivo es solicitado o va 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 reclamó una máquina: por inactividad, por TTL o por desalojo debido a presión de memoria (notificado como idleTimeout), con el agente que la estaba dirigiendo

La apertura de un rig no es un evento. El arranque se completa 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 son. Se enumeran aquí porque su ausencia es una decisión de diseño que conviene conocer antes de ponerse a buscar un enganche:

Subdominio Cómo cambia el estado en su lugar
Memoria record_memory_fact y sus compañeras escriben y devuelven; la recuperación lee. Nada publica al registrar 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 gestiona dentro del servicio de flujo de aprobación
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 worktree 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
Siembra de la suite de evaluación inicial (en línea) WorkspaceCreated
Aprovisionamiento de espacios 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 las 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 claro en el canal de dirección del agente que la está dirigiendo
StaleReviewWatcher PrHeadChanged: publica ReviewBecameStale solo cuando existe una revisión finalizada del commit sustituido
GitHubPrConversationBridge AgentRunCompleted: respuestas salientes de conversaciones de PR
NotificationFeedRecorder Los veintidós eventos de clase de notificación de 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 enganchadas; el socket permanece abierto

TicketAssigned es una señal de auditoría y de notificación, y un disparador de pipeline. La asignación a un agente registra la titularidad 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 propio test: es el vestigio de la ruta en proceso anterior a la separación entre cliente y servidor. Los toasts en vivo proceden de las tramas de 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 corresponden con más de un método de trama, porque la trama transporta el cambio (fallo frente a recuperación) mientras que la categoría transporta la pregunta («¿qué está haciendo la CI?»).

Categoría Evento de origen Método de 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ó los mandos, 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 los cubre la trama de recolecció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 PR propias llevan forUserId, así que llegan al autor y no a todos los miembros del espacio de trabajo.

RemoteEventForwarder reenvía además TicketReassigned como notifications/ticket_reassigned y todos los TaskLifecycleEvent como notifications/task_<phase>. Ninguno de los dos se corresponde con una categoría de notificación: alimentan 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.