Salta ai contenuti

Eventi di dominio

DomainEventBus è un bus di pubblicazione/sottoscrizione con broadcast in-process. I publisher chiamano publish(event); i sottoscrittori consumano un flusso tipizzato on<T>(). Ogni evento implementa DomainEvent e porta con sé occurredAt.

Il bus viene eseguito all’interno di cc_server. Ogni publisher e ogni sottoscrittore attivo risiede lì. Nessun client vede questo bus: il server inoltra un sottoinsieme selezionato come frame JSON-RPC notifications/*, filtrati per sottoscrittore in base all’appartenenza allo spazio di lavoro.

Misura Conteggio
Classi di evento concrete 54
File sorgente (packages/cc_domain/lib/core/domain/events/) 14 (più domain_event_bus.dart)
Basi astratte mai pubblicate direttamente 1 (TaskLifecycleEvent, una gerarchia sealed)

Ogni evento del catalogo seguente vive in quella sola directory; non ci sono eventi di dominio dichiarati altrove.

Evento Payload Emesso quando
WorkspaceCreated workspaceId Lo spazio di lavoro viene creato
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? L’agente termina un’esecuzione (con o senza successo)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? L’agente inizia ad aspettare una persona: un’approvazione, una domanda ask_user o un’esecuzione ferma su una credenziale (kind vale approval, question o credential)
RepoAdded repoId, path, workspaceId Un repo viene registrato; avvia l’indicizzazione del codice in background
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Una skill viene installata o aggiornata, con il suo esito di scansione della supply chain

Il primo blocco è il ciclo di vita proprio della PR. Il secondo è il monitoraggio delle PR proprie: segnali su una PR che tu hai aperto, ognuno con forUserId, così il polling può indirizzarli alla persona a cui si riferiscono invece di trasmetterli in broadcast.

Evento Payload Emesso quando
PullRequestPublished prId, workspaceId, repoOwner, repoName Un agente apre una PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR unita/chiusa/aperta/riaperta/approvata
PrMerged prId, workspaceId, agentId La PR viene unita: un segnale ristretto, solo di unione, mantenuto per analisi e notifiche
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub richiede la revisione dell’utente del server (polling delle notifiche, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle L’utente del server viene menzionato su una PR (polling delle notifiche, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Una PR monitorata dall’esterno (non di un agente) è stata unita, rilevata dal polling delle notifiche di GitHub; corrisponde alla categoria di notifica prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? PR non di un agente trovata tramite polling — solo per le pipeline (alimenta external_pr_welcome), non genera alcuna notifica
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId L’SHA di head della PR è cambiato: l’autore ha eseguito il push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Una revisione IA conclusa non descrive più la PR che ha revisionato. Viene emesso solo quando esiste una revisione per il commit sostituito, quindi resta un segnale e non un ping a ogni push

Tutti e sei portano workspaceId, repoOwner, repoName, prNumber, prTitle e un forUserId opzionale.

Evento Payload aggiuntivo Emesso quando
PrMergeReadinessChanged ready, reason Una PR che hai creato è diventata unibile, o ha smesso di esserlo
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Un revisore ha approvato, chiesto modifiche, oppure un’approvazione è stata annullata
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? La CI è fallita, o è tornata verde
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Qualcuno ti ha menzionato in un commento di una PR o in un commento di revisione
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Qualcuno ha risposto in una discussione di revisione a cui partecipi
PrThreadResolved threadId, commentId?, path?, line? Qualcuno ha risolto una discussione di revisione a cui partecipi
Evento Payload Emesso quando
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Arriva un nuovo messaggio
SpaceCreated spaceId, workspaceId Lo spazio viene creato
SpaceDeleted spaceId, workspaceId Lo spazio viene eliminato
SpaceProvisioningChanged workspaceId, spaceId, status, step? Lo spazio di lavoro dello spazio entra in provisioning, raggiunge un passaggio (clonare un repo, fare il checkout di una PR, configurare un agente) o passa a pronto/non riuscito — il ponte di chat lo racconta sulla scheda della sua attività
Evento Payload Emesso quando
TicketCreated ticketId, workspaceId Il ticket viene creato
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Ticket assegnato a un principale o a un team
TicketCompleted ticketId Il lavoro termina (terminale)
TicketFailed ticketId, errorMessage L’agente fallisce (terminale)
TicketCancelled ticketId Ticket annullato (terminale)
TicketStatusChanged ticketId, workspaceId, from, to Qualsiasi cambio di stato
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Ticket riassegnato
TicketDetailsUpdated ticketId, workspaceId Titolo, descrizione o priorità cambiano

Il flusso unificato del ciclo di vita delle attività — una singola esecuzione dell’agente inviata, con chiave il suo id di registro di esecuzione. Ogni evento porta taskId, un seq monotono per attività, workspaceId?, agentId? e una phase; le righe seguenti elencano solo i campi aggiuntivi.

Evento Payload Emesso quando
TaskDispatched — L’attività viene consegnata a un backend per l’esecuzione
TaskRunning — L’attività ha iniziato a produrre output
TaskWaitingLocalDirectory lockedPath, holderTaskId? Attività in attesa — un’altra attività possiede il lock sul percorso su disco che le serve
TaskProgress note? Punto di controllo intermedio dell’esecuzione
TaskMessage messageType, content Messaggio in streaming (text/thinking/tool_use/tool_result/error); segnalato nella fase progress
TaskCompleted summary? L’attività è terminata con successo
TaskFailed errorMessage L’attività è terminata con un errore
TaskCancelled — Attività annullata prima del completamento

L’enum TaskPhase ha anche un membro queued, ma nulla pubblica un evento di accodamento — non esiste alcuna classe TaskQueued. Il primo frame osservabile di un’attività è TaskDispatched.

Evento Payload Emesso quando
PipelineRunCompleted workspaceId, pipelineRunId, templateId L’esecuzione termina
PipelineRunCancelled workspaceId, pipelineRunId, templateId Esecuzione annullata (dall’utente o dal sistema) — un arresto deliberato, non un errore
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage L’esecuzione non riesce

Vengono pubblicate solo le transizioni terminali. Non esiste alcun PipelineRunStarted né alcun evento per singolo passaggio: l’avanzamento di un passaggio raggiunge i client tramite le righe dell’esecuzione stessa e il flusso del ciclo di vita delle attività dell’agente che ha inviato, non tramite il bus.

Evento Payload Emesso quando
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Viene creata una voce della traccia di controllo
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop La spesa supera la soglia
Evento Payload Emesso quando
WorkspaceMemberAdded workspaceId, userId, role Un utente entra in uno spazio di lavoro (riscatto di un invito o aggiunta da parte di un amministratore)
WorkspaceMemberRemoved workspaceId, userId Un membro viene rimosso — le sessioni attive di quell’utente relative allo spazio di lavoro devono ricontrollare subito l’accesso
WorkspaceMemberRoleChanged workspaceId, userId, role Il ruolo di un membro cambia

L’appartenenza è l’intera superficie di identità sul bus. La creazione di utenti e la revoca dei dispositivi avvengono, ma non vengono pubblicate come eventi di dominio — la revoca viene applicata direttamente sulle sessioni attive invece di passare dal bus.

Evento Payload Emesso quando
CalendarAuthExpired workspaceId, accountEmail Il token di refresh OAuth di un account collegato non è più valido in modo permanente (pubblicato una volta per ogni episodio di disconnessione)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Un evento del calendario sta per iniziare entro la finestra di anticipo configurata
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? La registrazione di una riunione termina e attiva la pipeline integrata meeting_summary
Evento Payload Emesso quando
RigClosedEvent workspaceId, rigId, reason Un rig è scomparso, con il suo motivo di chiusura; i consumatori agiscono solo su backendFailure — ogni altro motivo è richiesto o accompagnato da un RigReaped
RigControlChanged workspaceId, rigId, controller? Una persona ha preso il controllo esclusivo di un rig o l’ha restituito (controller null al rilascio — l’evento non registra chi ha mollato)
RigReaped workspaceId, rigId, reason, agentId? Il sistema ha recuperato una macchina: inattività, TTL o sfratto per pressione sulla memoria (segnalato come idleTimeout), con l’agente che la guidava

L’apertura di un rig non è un evento. L’avvio si completa e la riga del rig passa a pronto; non viene pubblicato nulla, quindi non esiste alcun RigOpened a cui sottoscriversi.

Diversi sottosistemi che dall’esterno sembrano guidati dagli eventi non lo sono. Sono elencati qui perché la loro assenza è una decisione di progettazione che vale la pena conoscere prima di mettersi a cercare un hook:

Sottodominio Come si muove invece lo stato
Memoria record_memory_fact e affini scrivono e restituiscono; il richiamo legge. Nulla pubblica su un fatto o un conflitto
Orchestrazione La proposta, la sua approvazione e l’esecuzione di pipeline generata sono righe e chiamate RPC
Documenti di piano submit_plan scrive una revisione; l’approvazione materializza un’orchestrazione. Nessuno dei due viene annunciato
Artefatti publish_artifact / revise_artifact scrivono direttamente le revisioni
Approvazioni L’escalation è gestita all’interno del servizio del flusso di approvazione
Sincronizzazione dei ticket Il coordinatore dei fornitori si sottoscrive agli eventi dei ticket qui sopra; i webhook in entrata vengono applicati direttamente
Aggiornamento del calendario Una sincronizzazione fa l’upsert degli eventi e il client li rilegge; non c’è alcuna trasmissione dell’aggiornamento
Unioni di worktree Registrate in worktree_merge_log e lette da lì

Ogni sottoscrittore seguente viene costruito all’interno di runCcServer (o di un servizio che esso costruisce). Non esiste alcun sottoscrittore lato client.

Sottoscrittore Eventi che consuma
WorkspaceSeeder WorkspaceCreated
Seeding delle eval suite iniziali (inline) WorkspaceCreated
Provisioning dello spazio di chat (inline) SpaceCreated
PipelineTriggerDispatcher Ogni DomainEvent; ogni trigger abilitato filtra per il proprio tipo
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (solo le esecuzioni 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 — inietta un avviso in linguaggio semplice sulla corsia di guida dell’agente che guida
StaleReviewWatcher PrHeadChanged — pubblica ReviewBecameStale solo quando esiste una revisione conclusa per il commit sostituito
GitHubPrConversationBridge AgentRunCompleted — risposte in uscita nelle conversazioni delle PR
NotificationFeedRecorder I ventidue eventi della classe delle notifiche qui sotto
RemoteEventForwarder Quei ventidue più TicketReassigned e ogni TaskLifecycleEvent. I tre eventi di appartenenza invalidano soltanto la sua cache di appartenenza per spazio di lavoro; non vengono inoltrati come frame
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — elimina le sottoscrizioni agli spazi di lavoro collegate a quell’utente; il socket resta aperto

TicketAssigned è un segnale di audit e di notifica e un trigger di pipeline. L’assegnazione a un agente registra la proprietà e non avvia alcuna esecuzione — non esiste alcun dispatcher dei ticket. L’assegnazione a un team (assignedTeamId impostato) è l’eccezione: TeamRoutingService invia il leader del team.

TicketRemoteSyncHandler è dichiarato ma non viene mai costruito nel codice di produzione, quindi oggi non si sottoscrive a nulla. Il NotificationEventMapper lato client viene costruito solo nel suo test — è il residuo del percorso in-process precedente alla separazione client/server. I toast dal vivo arrivano dai frame descritti sotto.

Sul server, NotificationFeedRecorder e RemoteEventForwarder trasformano questi eventi in frame notifications/* (notification_wire.dart). Sul client, RpcNotificationMapper → mapNotificationFrame trasforma un frame in una AppNotification.

Ci sono esattamente diciassette categorie di notifica. Diversi eventi corrispondono a più di un metodo di rete, perché il frame porta il cambiamento (fallito o ripristinato) mentre la categoria porta la domanda («che cosa sta facendo la CI?»).

Categoria Evento di origine Metodo di rete
newMessage MessageReceived notifications/message_received (ti menziona)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, senza menzionarti)
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

Il frame di presa in carico di un rig non viene recapitato a chi ha preso il volante, un rilascio non è filtrato (l’evento non registra chi ha mollato) e solo una chiusura con backendFailure produce un frame rig_closed — gli altri motivi sono richiesti oppure già coperti dal frame di recupero.

AgentAwaitingInput viene pubblicato da AgentAwaitingInputPublisher, che osserva i registri delle approvazioni e dei blocchi delle credenziali in cerca di nuove voci e riceve da AgentQuestionService la notifica di ogni domanda ask_user. Un’attesa corrisponde a un evento; quando l’attesa finisce non viene pubblicato nulla.

Le cinque categorie delle PR proprie portano forUserId, quindi raggiungono l’autore invece di tutti nello spazio di lavoro.

RemoteEventForwarder inoltra inoltre TicketReassigned come notifications/ticket_reassigned e ogni TaskLifecycleEvent come notifications/task_<phase>. Nessuno dei due corrisponde a una categoria di notifica — guidano l’interfaccia dal vivo, non il centro notifiche. Gli eventi di appartenenza non vengono inoltrati come frame: invalidano la cache del forwarder e gli host delle sessioni eliminano le sottoscrizioni agli spazi di lavoro del membro rimosso.