Saltar para o conteúdo

Eventos de domínio

O DomainEventBus é um barramento de publicação/subscrição com difusão e em processo. Os publicadores chamam publish(event); os subscritores consomem um stream tipado on<T>(). Todos os eventos implementam DomainEvent e transportam occurredAt.

O barramento é executado dentro do cc_server. Todos os publicadores e todos os subscritores em atividade residem aí. Nenhum cliente vê este barramento — o servidor reencaminha um subconjunto curado como frames JSON-RPC notifications/*, filtrado por subscritor com base na associação ao espaço de trabalho.

Medida Contagem
Classes de eventos concretas 54
Ficheiros de origem (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)

Todos os eventos do catálogo abaixo vivem nesse único diretório; não há eventos de domínio declarados em mais nenhum sítio.

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

O primeiro bloco é o ciclo de vida da própria PR. O segundo é a vigilância das PRs próprias: sinais sobre uma PR da sua autoria, cada um transportando forUserId para que a sondagem os possa encaminhar para a pessoa em causa em vez de os difundir.

Evento Payload Emitido quando
PullRequestPublished prId, workspaceId, repoOwner, repoName O agente abre uma PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? A PR é fundida/fechada/aberta/reaberta/aprovada
PrMerged prId, workspaceId, agentId A PR é fundida — um sinal estreito, apenas de merge, mantido para análises/notificações
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle O GitHub pede a revisão do utilizador do servidor (sondagem de notificações, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle O utilizador do servidor é mencionado numa PR (sondagem de notificações, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Uma PR acompanhada externamente (não criada por um agente) foi fundida, detetada pela sondagem de notificações do GitHub; corresponde à categoria de notificação prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR não criada por um agente encontrada por sondagem — apenas para pipelines (alimenta o external_pr_welcome), não gera qualquer notificação
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId O SHA de topo da PR mudou — o autor fez push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Uma revisão com IA concluída já não descreve a PR que reviu. Só é emitido quando existe uma revisão para o commit substituído, por isso mantém-se um sinal em vez de um aviso a cada push

Os seis transportam workspaceId, repoOwner, repoName, prNumber, prTitle e um forUserId opcional.

Evento Payload adicional Emitido quando
PrMergeReadinessChanged ready, reason Uma PR da sua autoria ficou pronta para merge, ou deixou de estar
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Um revisor aprovou, pediu alterações ou teve uma aprovação anulada
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? A CI ficou vermelha, ou recuperou
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Alguém o mencionou num comentário de PR ou num comentário de revisão
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Alguém respondeu num tópico de revisão em que participa
PrThreadResolved threadId, commentId?, path?, line? Alguém resolveu um tópico de revisão em que participa
Evento Payload Emitido quando
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Chega uma nova mensagem
SpaceCreated spaceId, workspaceId O espaço é criado
SpaceDeleted spaceId, workspaceId O espaço é eliminado
SpaceProvisioningChanged workspaceId, spaceId, status, step? O espaço de trabalho do espaço entra em aprovisionamento, atinge um passo (clonar um repo, fazer checkout de uma PR, configurar um agente) ou passa a pronto/falhado — a ponte de chat narra-o no respetivo cartão de tarefa
Evento Payload Emitido 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 equipa
TicketCompleted ticketId O trabalho termina (terminal)
TicketFailed ticketId, errorMessage O agente falha (terminal)
TicketCancelled ticketId O ticket é cancelado (terminal)
TicketStatusChanged ticketId, workspaceId, from, to Qualquer alteração de estado
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? O ticket é reatribuído
TicketDetailsUpdated ticketId, workspaceId O título, a descrição ou a prioridade são alterados

O fluxo unificado do ciclo de vida de tarefas — uma execução de agente despachada, identificada pelo id do respetivo registo de execução. Todos os eventos transportam taskId, um seq monótono por tarefa, workspaceId?, agentId? e uma phase; as linhas abaixo listam apenas os campos adicionais.

Evento Payload Emitido quando
TaskDispatched — A tarefa é entregue a um backend para execução
TaskRunning — A tarefa começou a produzir saída
TaskWaitingLocalDirectory lockedPath, holderTaskId? Tarefa em espera — outra tarefa detém o bloqueio do caminho em disco de que precisa
TaskProgress note? Ponto de controlo de progresso a meio da execução
TaskMessage messageType, content Mensagem transmitida em fluxo (text/thinking/tool_use/tool_result/error); comunicada na fase progress
TaskCompleted summary? A tarefa terminou com êxito
TaskFailed errorMessage A tarefa terminou com um erro
TaskCancelled — A tarefa foi cancelada antes de terminar

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

Evento Payload Emitido quando
PipelineRunCompleted workspaceId, pipelineRunId, templateId A execução termina
PipelineRunCancelled workspaceId, pipelineRunId, templateId A execução é cancelada (pelo utilizador ou pelo sistema) — uma paragem deliberada, não um erro
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage A execução falha

Só são publicadas as transições terminais. Não existe nenhum PipelineRunStarted nem qualquer evento por passo: o progresso de um passo chega aos clientes através das próprias linhas da execução e do fluxo do ciclo de vida de tarefas do agente que esta despachou, e não através do barramento.

Evento Payload Emitido quando
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? É criada uma entrada no registo de auditoria
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop O gasto ultrapassa o limite
Evento Payload Emitido quando
WorkspaceMemberAdded workspaceId, userId, role Um utilizador entra num espaço de trabalho (resgate de convite ou adição por um administrador)
WorkspaceMemberRemoved workspaceId, userId Um membro é removido — as sessões ativas desse utilizador com âmbito no espaço de trabalho têm de voltar a verificar o acesso de imediato
WorkspaceMemberRoleChanged workspaceId, userId, role O papel de um membro é alterado

A associação é toda a superfície de identidade no barramento. A criação de utilizadores e a revogação de dispositivos acontecem, mas não são publicadas como eventos de domínio — a revogação é imposta diretamente contra as sessões ativas, em vez de ser encaminhada pelo barramento.

Evento Payload Emitido quando
CalendarAuthExpired workspaceId, accountEmail O token de atualização OAuth de uma conta ligada fica permanentemente inválido (publicado uma vez por episódio de desligamento)
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? Uma gravação de reunião termina e desencadeia o pipeline incorporado meeting_summary
Evento Payload Emitido quando
RigClosedEvent workspaceId, rigId, reason Um rig desapareceu, com o respetivo motivo de fecho; os consumidores só reagem a backendFailure — qualquer outro motivo é pedido ou é acompanhado por um RigReaped
RigControlChanged workspaceId, rigId, controller? Uma pessoa assumiu o controlo exclusivo de um rig ou devolveu-o (controller nulo ao libertar — o evento não regista quem largou o controlo)
RigReaped workspaceId, rigId, reason, agentId? O sistema recuperou uma máquina: inatividade, TTL ou despejo por pressão de memória (comunicado como idleTimeout), com o agente que a conduzia

A abertura de um rig não é um evento. O arranque conclui-se e a linha do rig passa a pronto; nada é publicado, por isso não há nenhum RigOpened a que subscrever.

Vários subsistemas que, vistos de fora, parecem orientados a eventos não o são. Estão listados aqui porque a sua ausência é uma decisão de conceção que vale a pena conhecer antes de procurar um hook:

Subdomínio Como o estado se altera em vez disso
Memória record_memory_fact e afins escrevem e devolvem; a recordação lê. Nada publica sobre um facto ou conflito
Orquestração A proposta, a sua aprovação e a execução de pipeline gerada são linhas e chamadas RPC
Documentos de plano submit_plan escreve uma revisão; a aprovação materializa uma orquestração. Nenhum dos dois é anunciado
Artefactos publish_artifact / revise_artifact escrevem 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 subscreve os eventos de tickets acima; os webhooks de entrada são aplicados diretamente
Atualização do calendário Uma sincronização faz upsert dos eventos e o cliente volta a ler; não há nenhuma difusão de atualização
Merges de worktrees Registados em worktree_merge_log e lidos a partir daí

Todos os subscritores abaixo são construídos dentro do runCcServer (ou de um serviço que este constrói). Não existe nenhum subscritor do lado do cliente.

Subscritor Eventos que consome
WorkspaceSeeder WorkspaceCreated
Sementeira inicial das suites de avaliação (em linha) WorkspaceCreated
Aprovisionamento do espaço de chat (em linha) SpaceCreated
PipelineTriggerDispatcher Todos os DomainEvent; cada disparador ativado filtra pelo seu 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 no canal 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 das notificações abaixo
RemoteEventForwarder Esses vinte e dois, mais TicketReassigned e todos os TaskLifecycleEvent. Os três eventos de associação apenas invalidam a respetiva cache de associação por espaço de trabalho; não são reencaminhados como frames
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — elimina as subscrições de espaços de trabalho associadas a esse utilizador; o socket mantém-se aberto

O TicketAssigned é um sinal de auditoria e de notificação e um disparador de pipeline. A atribuição a um agente regista a propriedade e não inicia nenhuma execução — não existe nenhum despachante de tickets. A atribuição a uma equipa (assignedTeamId definido) é a exceção: o TeamRoutingService despacha o líder da equipa.

O TicketRemoteSyncHandler está declarado mas nunca é construído em código de produção, por isso hoje não subscreve nada. O NotificationEventMapper do lado do cliente só é construído no seu próprio teste — é o vestígio do percurso em processo de antes da separação entre cliente e servidor. Os avisos em direto vêm dos frames de protocolo abaixo.

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

Existem exatamente dezassete categorias de notificação. Vários eventos correspondem a mais do que um método de transmissão, porque o frame transporta a transição (falhou versus recuperou) enquanto a categoria transporta a pergunta («o que está a CI a fazer?»).

Categoria Evento de origem Método de transmissão
newMessage MessageReceived notifications/message_received (menciona-o)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, sem o mencionar)
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 assunção do controlo do rig não é entregue de volta a quem pegou no volante, uma libertação não é filtrada (o evento não regista quem largou o controlo) e só um fecho por backendFailure produz um frame rig_closed — os outros motivos ou são pedidos ou já estão cobertos pelo frame de recuperação.

AgentAwaitingInput é publicado pelo AgentAwaitingInputPublisher, que vigia os registos de aprovações e de bloqueios de credenciais à procura de novas entradas e é informado de cada pergunta ask_user pelo AgentQuestionService. Uma espera corresponde a um evento; nada é publicado quando a espera termina.

As cinco categorias de PRs próprias transportam forUserId, por isso chegam ao autor em vez de chegarem a todos no espaço de trabalho.

O RemoteEventForwarder reencaminha ainda o TicketReassigned como notifications/ticket_reassigned e todos os TaskLifecycleEvent como notifications/task_<phase>. Nenhum deles corresponde a uma categoria de notificação — alimentam a interface em direto, não o centro de notificações. Os eventos de associação não são reencaminhados como frames: invalidam a cache do reencaminhador e os anfitriões de sessão eliminam as subscrições de espaços de trabalho do membro removido.