Eventi di dominio
Bus di eventi
Sezione intitolata “Bus di eventi”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.
Eventi di spazi di lavoro, agenti e repo
Sezione intitolata “Eventi di spazi di lavoro, agenti e repo”| 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 |
Eventi delle PR e delle revisioni
Sezione intitolata “Eventi delle PR e delle revisioni”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 |
Il monitoraggio delle PR proprie
Sezione intitolata “Il monitoraggio delle PR proprie”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 |
Eventi di messaggistica
Sezione intitolata “Eventi di messaggistica”| 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à |
Eventi dei ticket
Sezione intitolata “Eventi dei ticket”| 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 |
Eventi del ciclo di vita delle attività
Sezione intitolata “Eventi del ciclo di vita delle attività”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.
Eventi delle pipeline
Sezione intitolata “Eventi delle pipeline”| 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.
Eventi di osservabilità
Sezione intitolata “Eventi di osservabilità”| 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 |
Eventi di identità e appartenenza
Sezione intitolata “Eventi di identità e appartenenza”| 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.
Eventi di calendario e riunioni
Sezione intitolata “Eventi di calendario e riunioni”| 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 |
Eventi dei rig
Sezione intitolata “Eventi dei rig”| 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.
Sottodomini senza alcun evento
Sezione intitolata “Sottodomini senza alcun evento”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ì |
Sottoscrittori
Sezione intitolata “Sottoscrittori”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.
Mappatura delle notifiche
Sezione intitolata “Mappatura delle notifiche”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.