Pular para o conteúdo

Eventos de domínio

O DomainEventBus é um barramento de publicação/assinatura por difusão que roda em processo. Os publicadores chamam publish(event); os assinantes consomem um stream tipado on<T>(). Todo evento implementa DomainEvent e carrega occurredAt.

O barramento roda dentro do cc_server. Todo publicador e todo assinante ativo ficam lá. Nenhum cliente vê este barramento — o servidor encaminha um subconjunto curado como frames JSON-RPC notifications/*, filtrados por assinante conforme a associação ao espaço de trabalho.

Medida Quantidade
Classes de evento concretas 54
Arquivos-fonte (packages/cc_domain/lib/core/domain/events/) 14 (mais domain_event_bus.dart)
Bases abstratas que nunca são publicadas diretamente 1 (TaskLifecycleEvent, uma hierarquia selada)

Todo evento do catálogo abaixo fica nesse único diretório; não há eventos de domínio declarados em nenhum outro lugar.

Evento Payload Dispara quando
WorkspaceCreated workspaceId O espaço de trabalho é criado
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? O agente termina uma execução (com sucesso ou com falha)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Um agente começa a esperar por uma pessoa: uma aprovação, uma pergunta ask_user ou uma execução parada em uma credencial (kind é approval, question ou credential)
RepoAdded repoId, path, workspaceId Um repo é registrado; dispara a indexação de código em segundo plano
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Uma skill é instalada ou atualizada, carregando o veredito da análise da cadeia de suprimentos

O primeiro bloco é o ciclo de vida da própria PR. O segundo é o acompanhamento de PRs de autoria própria: sinais sobre uma PR que você abriu, cada um carregando forUserId para que a consulta periódica possa encaminhá-los à pessoa a quem dizem respeito em vez de difundi-los.

Evento Payload Dispara quando
PullRequestPublished prId, workspaceId, repoOwner, repoName O agente abre uma PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR mesclada/fechada/aberta/reaberta/aprovada
PrMerged prId, workspaceId, agentId A PR é mesclada — um sinal estreito, apenas de merge, mantido para análises e notificações
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle O GitHub pede a revisão do usuário do servidor (consulta periódica de notificações, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle O usuário do servidor é mencionado em uma PR (consulta periódica de notificações, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Uma PR acompanhada externamente (não de agente) foi mesclada, detectada pela consulta periódica de notificações do GitHub; mapeia para a categoria de notificação prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR não de agente encontrada por consulta periódica — apenas para pipelines (alimenta external_pr_welcome), não gera notificação
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId O SHA de cabeça da PR mudou — o autor fez push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Uma revisão de IA concluída não descreve mais a PR que revisou. Só dispara quando existe uma revisão para o commit substituído, então continua sendo um sinal e não um ping a cada push

Todos os seis carregam workspaceId, repoOwner, repoName, prNumber, prTitle e um forUserId opcional.

Evento Payload adicional Dispara quando
PrMergeReadinessChanged ready, reason Uma PR de sua autoria se tornou mesclável, ou deixou de ser
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Um revisor aprovou, pediu alterações ou teve uma aprovação descartada
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? A CI ficou vermelha, ou se recuperou
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Alguém mencionou você em um comentário de PR ou de revisão
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Alguém respondeu em uma thread de revisão da qual você participa
PrThreadResolved threadId, commentId?, path?, line? Alguém resolveu uma thread de revisão da qual você participa
Evento Payload Dispara quando
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Uma nova mensagem chega
SpaceCreated spaceId, workspaceId O espaço é criado
SpaceDeleted spaceId, workspaceId O espaço é excluído
SpaceProvisioningChanged workspaceId, spaceId, status, step? O espaço de trabalho do espaço entra em provisionamento, chega a uma etapa (clonar um repo, fazer checkout de uma PR, configurar um agente) ou muda para pronto/falhou — a ponte de chat narra isso no cartão de tarefa dela
Evento Payload Dispara quando
TicketCreated ticketId, workspaceId O ticket é criado
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Ticket atribuído a um principal ou a uma equipe
TicketCompleted ticketId O trabalho termina (terminal)
TicketFailed ticketId, errorMessage O agente falha (terminal)
TicketCancelled ticketId Ticket cancelado (terminal)
TicketStatusChanged ticketId, workspaceId, from, to Qualquer mudança de status
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Ticket reatribuído
TicketDetailsUpdated ticketId, workspaceId Título, descrição ou prioridade mudam

O fluxo unificado de ciclo de vida de tarefas — uma execução de agente despachada, identificada pelo id do log de execução. Todo evento carrega taskId, um seq monotônico por tarefa, workspaceId?, agentId? e uma phase; as linhas abaixo listam apenas os campos adicionais.

Evento Payload Dispara quando
TaskDispatched — Tarefa entregue a um backend para execução
TaskRunning — A tarefa começou a produzir saída
TaskWaitingLocalDirectory lockedPath, holderTaskId? Tarefa estacionada — outra tarefa detém o bloqueio do caminho em disco de que ela precisa
TaskProgress note? Checkpoint de progresso no meio da execução
TaskMessage messageType, content Mensagem transmitida em stream (text/thinking/tool_use/tool_result/error); reportada sob a fase progress
TaskCompleted summary? A tarefa terminou com sucesso
TaskFailed errorMessage A tarefa terminou com um erro
TaskCancelled — Tarefa cancelada antes da conclusão

A enum TaskPhase também tem um membro queued, mas nada publica um evento de fila — não existe uma classe TaskQueued. O primeiro frame observável de uma tarefa é TaskDispatched.

Evento Payload Dispara quando
PipelineRunCompleted workspaceId, pipelineRunId, templateId A execução termina
PipelineRunCancelled workspaceId, pipelineRunId, templateId Execução cancelada (pelo usuário ou pelo sistema) — uma parada deliberada, não um erro
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage A execução falha

Somente transições terminais são publicadas. Não existe PipelineRunStarted nem evento por etapa: o progresso de uma etapa chega aos clientes pelas próprias linhas da execução e pelo fluxo de ciclo de vida de tarefa do agente que ela despachou, não pelo barramento.

Evento Payload Dispara quando
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Entrada de trilha de auditoria criada
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop O gasto ultrapassa o limite
Evento Payload Dispara quando
WorkspaceMemberAdded workspaceId, userId, role Um usuário entra em um espaço de trabalho (resgate de convite ou adição por um administrador)
WorkspaceMemberRemoved workspaceId, userId Um membro é removido — as sessões ativas desse usuário no escopo do espaço de trabalho devem verificar o acesso novamente de imediato
WorkspaceMemberRoleChanged workspaceId, userId, role O papel de um membro muda

A associação é toda a superfície de identidade no barramento. A criação de usuários e a revogação de dispositivos acontecem, mas não são publicadas como eventos de domínio — a revogação é aplicada diretamente às sessões ativas em vez de passar pelo barramento.

Evento Payload Dispara quando
CalendarAuthExpired workspaceId, accountEmail O token de atualização OAuth de uma conta conectada fica inválido em definitivo (publicado uma vez por episódio de desconexão)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Um evento de calendário vai começar dentro da janela de antecedência configurada
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? A gravação de uma reunião termina e dispara o pipeline integrado meeting_summary
Evento Payload Dispara quando
RigClosedEvent workspaceId, rigId, reason Um rig desapareceu, com o motivo do fechamento; os consumidores só agem em backendFailure — qualquer outro motivo foi solicitado ou vem acompanhado de um RigReaped
RigControlChanged workspaceId, rigId, controller? Uma pessoa assumiu o controle exclusivo de um rig ou o devolveu (controller nulo ao liberar — o evento não registra quem soltou)
RigReaped workspaceId, rigId, reason, agentId? O sistema recuperou uma máquina: ociosidade, TTL ou despejo por pressão de memória (reportado como idleTimeout), com o agente que a conduzia

A abertura de um rig não é um evento. O boot termina e a linha do rig passa para pronto; nada é publicado, então não há um RigOpened para assinar.

Vários subsistemas que parecem orientados a eventos quando vistos de fora não são. Eles estão listados aqui porque a ausência deles é uma decisão de projeto que vale conhecer antes de sair procurando um hook:

Subdomínio Como o estado avança em vez disso
Memória record_memory_fact e companhia escrevem e retornam; a recuperação lê. Nada publica sobre um fato ou conflito
Orquestração A proposta, sua aprovação e a execução de pipeline gerada são linhas e chamadas RPC
Documentos de plano submit_plan grava uma revisão; a aprovação materializa uma orquestração. Nenhum dos dois é anunciado
Artefatos publish_artifact / revise_artifact gravam revisões diretamente
Aprovações A escalada é tratada dentro do serviço de fluxo de aprovação
Sincronização de tickets O coordenador de fornecedores assina os eventos de ticket acima; os webhooks de entrada são aplicados diretamente
Atualização de calendário Uma sincronização faz upsert dos eventos e o cliente os relê; não há difusão de atualização
Merges de worktree Registrados em worktree_merge_log e lidos de lá

Todos os assinantes abaixo são construídos dentro de runCcServer (ou de um serviço que ele monta). Não existe assinante do lado do cliente.

Assinante Eventos que consome
WorkspaceSeeder WorkspaceCreated
Semeadura inicial de suítes de avaliação (inline) WorkspaceCreated
Provisionamento do espaço de chat (inline) SpaceCreated
PipelineTriggerDispatcher Todo DomainEvent; cada gatilho habilitado filtra pelo próprio tipo
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (apenas execuções 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 — injeta um aviso em linguagem simples na pista de orientação do agente que conduz
StaleReviewWatcher PrHeadChanged — publica ReviewBecameStale apenas quando existe uma revisão concluída para o commit substituído
GitHubPrConversationBridge AgentRunCompleted — respostas de saída em conversas de PR
NotificationFeedRecorder Os vinte e dois eventos da classe de notificação abaixo
RemoteEventForwarder Aqueles vinte e dois mais TicketReassigned e todo TaskLifecycleEvent. Os três eventos de associação apenas invalidam seu cache de associação por espaço de trabalho; eles não são encaminhados como frames
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — descarta as assinaturas de espaço de trabalho anexadas daquele usuário; o socket permanece aberto

TicketAssigned é um sinal de auditoria e de notificação e um gatilho de pipeline. A atribuição a um agente registra a propriedade e não inicia nenhuma execução — não existe um dispatcher de tickets. A atribuição a uma equipe (assignedTeamId definido) é a exceção: o TeamRoutingService despacha o líder da equipe.

O TicketRemoteSyncHandler é declarado, mas nunca é construído no código de produção, então hoje não assina nada. O NotificationEventMapper do lado do cliente é construído apenas em seu próprio teste — é o resquício do caminho em processo de antes da separação entre cliente e servidor. Os toasts ao vivo vêm dos frames de transmissão abaixo.

No servidor, NotificationFeedRecorder e RemoteEventForwarder transformam esses eventos em frames de transmissão notifications/* (notification_wire.dart). No cliente, RpcNotificationMapper → mapNotificationFrame transforma um frame em uma AppNotification.

Há exatamente dezessete categorias de notificação. Vários eventos mapeiam para mais de um método de transmissão, porque o frame carrega a transição (falhou versus recuperou) enquanto a categoria carrega a pergunta (“o que a CI está fazendo?”).

Categoria Evento de origem Método de transmissão
newMessage MessageReceived notifications/message_received (menciona você)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, sem mencionar você)
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

O frame de tomada de controle do rig não é entregue de volta a quem assumiu o volante, uma liberação não é filtrada (o evento não registra quem soltou) e apenas um fechamento por backendFailure produz um frame rig_closed — os outros motivos foram solicitados ou já estão cobertos pelo frame de reap.

AgentAwaitingInput é publicado pelo AgentAwaitingInputPublisher, que observa os registros de aprovações e de bloqueios de credencial em busca de novas entradas e é avisado de cada pergunta ask_user pelo AgentQuestionService. Uma espera é um evento; nada é publicado quando a espera termina.

As cinco categorias de PR de autoria própria carregam forUserId, então chegam ao autor em vez de a todos no espaço de trabalho.

O RemoteEventForwarder ainda encaminha TicketReassigned como notifications/ticket_reassigned e todo TaskLifecycleEvent como notifications/task_<phase>. Nenhum dos dois mapeia para uma categoria de notificação — eles movimentam a interface ao vivo, não a central de notificações. Os eventos de associação não são encaminhados como frames: eles invalidam o cache do encaminhador, e os hosts de sessão descartam as assinaturas de espaço de trabalho do membro removido.