Перейти до вмісту

Доменні події

DomainEventBus — це внутрішньопроцесна широкомовна шина публікації/підписки. Видавці викликають publish(event); підписники споживають типізований потік on<T>(). Кожна подія реалізує DomainEvent і несе occurredAt.

Шина працює всередині cc_server. Кожен видавець і кожен живий підписник живуть там. Жоден клієнт не бачить цієї шини — сервер пересилає дібрану підмножину як фрейми JSON-RPC notifications/*, відфільтровані для кожного підписника за членством у робочому просторі.

Показник Кількість
Конкретні класи подій 54
Файли джерел (packages/cc_domain/lib/core/domain/events/) 14 (плюс domain_event_bus.dart)
Абстрактні бази, які ніколи не публікуються безпосередньо 1 (TaskLifecycleEvent, ієрархія sealed)

Кожна подія з каталогу нижче живе в цьому єдиному каталозі; доменних подій, оголошених деінде, немає.

Події робочих просторів, агентів і репозиторіїв

Розділ із назвою «Події робочих просторів, агентів і репозиторіїв»
Подія Корисне навантаження Спрацьовує, коли
WorkspaceCreated workspaceId Робочий простір створено
AgentRunCompleted agentId, runId?, workspaceId?, conversationId? Агент завершує запуск (успішно або з помилкою)
AgentAwaitingInput workspaceId, kind, summary, spaceId?, conversationId?, agentId?, agentName? Агент починає чекати на людину: схвалення, відповідь на запитання ask_user або облікові дані для зупиненого запуску (kind — approval, question або credential)
RepoAdded repoId, path, workspaceId Репозиторій зареєстровано; запускає фонову індексацію коду
SkillUpdated workspaceId, slug, origin, computedHash, scanVerdict? Навичку встановлено або оновлено; подія несе вердикт сканування її ланцюга постачання

Перший блок — це власний життєвий цикл PR. Другий — стеження за авторськими PR: сигнали про PR, який відкрили ви, і кожен несе forUserId, щоб опитування могло спрямувати їх до людини, якої вони стосуються, а не розсилати всім.

Подія Корисне навантаження Спрацьовує, коли
PullRequestPublished prId, workspaceId, repoOwner, repoName Агент відкриває PR
PullRequestStatusChanged status, workspaceId, prId?, repoFullName?, prNumber? PR змерджено/закрито/відкрито/перевідкрито/схвалено
PrMerged prId, workspaceId, agentId PR змерджено — вузький сигнал лише про мердж, збережений для аналітики й сповіщень
PrReviewRequested workspaceId, repoOwner, repoName, prNumber, prTitle GitHub просить рев’ю в користувача сервера (опитування сповіщень, review_requested)
PrMentioned workspaceId, repoOwner, repoName, prNumber, prTitle Користувача сервера згадали в PR (опитування сповіщень, mention)
ExternalPrMerged workspaceId, repoOwner, repoName, prNumber, prTitle, mergedByLogin? Змерджено зовнішньо відстежуваний (неагентський) PR, виявлений опитуванням сповіщень GitHub; відображається на категорію сповіщень prMerged
ExternalPrDetected repoOwner, repoName, prNumber, prTitle, author, workspaceId? Неагентський PR знайдено опитуванням — лише для конвеєрів (живить external_pr_welcome), не породжує сповіщення
PrHeadChanged repoOwner, repoName, prNumber, prTitle, previousHeadSha, headSha, workspaceId Головний SHA PR зрушив — автор зробив push
ReviewBecameStale workspaceId, spaceId, repoOwner, repoName, prNumber, prTitle, reviewedHeadSha, headSha Завершена ШІ-перевірка більше не описує PR, який вона перевіряла. Спрацьовує лише тоді, коли для заміненого коміту існує перевірка, тож залишається сигналом, а не нагадуванням на кожен push

Усі шість несуть workspaceId, repoOwner, repoName, prNumber, prTitle і необов’язковий forUserId.

Подія Додаткове корисне навантаження Спрацьовує, коли
PrMergeReadinessChanged ready, reason PR, який ви написали, став готовим до злиття або перестав ним бути
PrReviewDecisionChanged decision, reviewersRemaining, approverLogin? Рецензент схвалив, попросив змін або його схвалення скасували
PrChecksStatusChanged failing, failingCheckName?, failingCheckUrl? CI став червоним або відновився
PrCommentMentioned commentId, authorLogin, bodyPreview, isReviewComment, threadId?, path?, line? Хтось згадав вас у коментарі до PR або коментарі рев’ю
PrThreadReplied commentId, authorLogin, bodyPreview, threadId?, path?, line? Хтось відповів у гілці рев’ю, у якій ви берете участь
PrThreadResolved threadId, commentId?, path?, line? Хтось закрив гілку рев’ю, у якій ви берете участь
Подія Корисне навантаження Спрацьовує, коли
MessageReceived spaceId, messageId, senderName, contentPreview, isAgentMessage, workspaceId?, mentions, requestedByUserId?, senderUserId? Надійшло нове повідомлення
SpaceCreated spaceId, workspaceId Простір створено
SpaceDeleted spaceId, workspaceId Простір видалено
SpaceProvisioningChanged workspaceId, spaceId, status, step? Робочий простір простора входить у підготовку, досягає кроку (клонування репозиторію, checkout PR, налаштування агента) або переходить у ready/failed — чат-міст описує це на своїй картці завдання
Подія Корисне навантаження Спрацьовує, коли
TicketCreated ticketId, workspaceId Тікет створено
TicketAssigned ticketId, ticketTitle, ticketBody?, ticketUrl?, assignedAgentId?, assigneeType, assignedTeamId?, workspaceId Тікет призначено принципалові або команді
TicketCompleted ticketId Робота завершується (термінальний стан)
TicketFailed ticketId, errorMessage Агент зазнає невдачі (термінальний стан)
TicketCancelled ticketId Тікет скасовано (термінальний стан)
TicketStatusChanged ticketId, workspaceId, from, to Будь-яка зміна статусу
TicketReassigned ticketId, workspaceId, fromAgentId?, toAgentId? Тікет перепризначено
TicketDetailsUpdated ticketId, workspaceId Зміна назви, опису або пріоритету

Уніфікований потік життєвого циклу завдань — один відправлений запуск агента, ключем якого є ідентифікатор його журналу запуску. Кожна подія несе taskId, монотонний для кожного завдання seq, workspaceId?, agentId? і phase; рядки нижче перелічують лише додаткові поля.

Подія Корисне навантаження Спрацьовує, коли
TaskDispatched — Завдання передано бекенду на виконання
TaskRunning — Завдання почало видавати результат
TaskWaitingLocalDirectory lockedPath, holderTaskId? Завдання припарковано — потрібний йому на диску замок шляху належить іншому завданню
TaskProgress note? Контрольна точка поступу посеред запуску
TaskMessage messageType, content Потокове повідомлення (text/thinking/tool_use/tool_result/error); подається у фазі progress
TaskCompleted summary? Завдання завершилося успішно
TaskFailed errorMessage Завдання завершилося з помилкою
TaskCancelled — Завдання скасовано до завершення

Перелік TaskPhase також має член queued, але ніщо не публікує подію про чергу — класу TaskQueued не існує. Перший спостережуваний фрейм завдання — TaskDispatched.

Подія Корисне навантаження Спрацьовує, коли
PipelineRunCompleted workspaceId, pipelineRunId, templateId Запуск завершується
PipelineRunCancelled workspaceId, pipelineRunId, templateId Запуск скасовано (користувачем або системою) — свідома зупинка, а не помилка
PipelineRunFailed workspaceId, pipelineRunId, templateId, errorMessage Запуск зазнає невдачі

Публікуються лише термінальні переходи. Немає ні PipelineRunStarted, ні події на кожен крок: поступ кроку доходить до клієнтів через власні рядки запуску та потік життєвого циклу завдань того агента, якого він відправив, а не через шину.

Подія Корисне навантаження Спрацьовує, коли
ActivityLogged id, actorType, actorId?, action, entityType, entityId?, details?, workspaceId?, runId? Створено запис журналу аудиту
BudgetThresholdCrossed scopeType, scopeId, spentCents, budgetCents, isHardStop Витрати перевищили поріг
Подія Корисне навантаження Спрацьовує, коли
WorkspaceMemberAdded workspaceId, userId, role Користувач приєднується до робочого простору (погашення запрошення або додавання адміністратором)
WorkspaceMemberRemoved workspaceId, userId Учасника видалено — живі сесії цього користувача в межах робочого простору мусять негайно перевірити доступ
WorkspaceMemberRoleChanged workspaceId, userId, role Роль учасника змінюється

Членство — це вся поверхня ідентичності на шині. Створення користувачів і відкликання пристроїв відбуваються, але не публікуються як доменні події — відкликання застосовується безпосередньо до живих сесій, а не через шину.

Подія Корисне навантаження Спрацьовує, коли
CalendarAuthExpired workspaceId, accountEmail Токен оновлення OAuth під’єднаного облікового запису остаточно недійсний (публікується один раз на епізод від’єднання)
MeetingStartingSoon workspaceId, eventId, title, startTime, meetingUrl? Подія календаря починається в межах налаштованого вікна завчасності
MeetingRecordingStopped workspaceId, meetingId, title, userNotes, transcript, summaryInstructions? Запис зустрічі завершується й запускає вбудований конвеєр meeting_summary
Подія Корисне навантаження Спрацьовує, коли
RigClosedEvent workspaceId, rigId, reason Rig зник разом із причиною закриття; споживачі реагують лише на backendFailure — кожна інша причина або запитана, або супроводжується RigReaped
RigControlChanged workspaceId, rigId, controller? Людина взяла ексклюзивне керування rig або повернула його (controller дорівнює null під час повернення — подія не фіксує, хто відпустив)
RigReaped workspaceId, rigId, reason, agentId? Система повернула машину: простій, TTL або витіснення через тиск на пам’ять (повідомляється як idleTimeout), разом з агентом, що керував

Відкриття rig не є подією. Завантаження завершується, і рядок rig переходить у стан ready; нічого не публікується, тож підписатися на RigOpened немає на що.

Кілька підсистем, які ззовні виглядають керованими подіями, такими не є. Вони перелічені тут, бо їхня відсутність — це проєктне рішення, яке варто знати, перш ніж шукати точку підключення:

Піддомен Як натомість рухається стан
Пам’ять record_memory_fact та подібні інструменти пишуть і повертають результат; пригадування читає. Ніщо не публікує подію про факт чи конфлікт
Оркестрація Пропозиція, її затвердження та згенерований запуск конвеєра — це рядки й виклики RPC
Документи планів submit_plan пише ревізію; затвердження матеріалізує оркестрацію. Ні про перше, ні про друге не оголошується
Артефакти publish_artifact / revise_artifact пишуть ревізії безпосередньо
Схвалення Ескалацію обробляє всередині сервіс робочого процесу схвалення
Синхронізація тікетів Координатор постачальників підписується на події тікетів вище; вхідні вебхуки застосовуються безпосередньо
Оновлення календаря Синхронізація робить upsert подій, а клієнт перечитує їх; широкомовного сповіщення про оновлення немає
Злиття worktree Записуються в worktree_merge_log і читаються звідти

Кожен підписник нижче створюється всередині runCcServer (або сервісу, який він будує). Клієнтських підписників немає.

Підписник Події, які він споживає
WorkspaceSeeder WorkspaceCreated
Засівання стартового набору eval (вбудовано) WorkspaceCreated
Підготовка чат-простору (вбудовано) SpaceCreated
PipelineTriggerDispatcher Усі DomainEvent; кожен увімкнений тригер фільтрує за власним типом
SubPipelineResumeListener PipelineRunCompleted, PipelineRunFailed
PipelineCostRollupListener AgentRunCompleted
BudgetEvaluationListener AgentRunCompleted
MeetingSummaryReconciler PipelineRunCompleted, PipelineRunFailed, PipelineRunCancelled (лише запуски 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 — додає повідомлення простою мовою у смугу коригування агента, що керує
StaleReviewWatcher PrHeadChanged — публікує ReviewBecameStale, лише коли для заміненого коміту існує завершена перевірка
GitHubPrConversationBridge AgentRunCompleted — вихідні відповіді в розмові PR
NotificationFeedRecorder Двадцять два класи подій сповіщень нижче
RemoteEventForwarder Ті самі двадцять два плюс TicketReassigned і кожен TaskLifecycleEvent. Три події членства лише анулюють його кеш членства для кожного робочого простору; вони не пересилаються як фрейми
LocalRpcServer / RemoteRelayHost WorkspaceMemberRemoved — відкидає підписки на робочі простори, прикріплені до цього користувача; сокет залишається відкритим

TicketAssigned — це сигнал аудиту й сповіщень та тригер конвеєра. Призначення агенту фіксує відповідальність і не запускає жодного виконання — диспетчера тікетів не існує. Виняток — призначення команді (заданий assignedTeamId): TeamRoutingService запускає лідера команди.

TicketRemoteSyncHandler оголошений, але ніколи не створюється в продакшн-коді, тож сьогодні він ні на що не підписаний. Клієнтський NotificationEventMapper створюється лише у власному тесті — це залишок внутрішньопроцесного шляху з часів до розділення на клієнт і сервер. Живі спливні сповіщення надходять із дротових фреймів нижче.

На сервері NotificationFeedRecorder і RemoteEventForwarder перетворюють ці події на дротові фрейми notifications/* (notification_wire.dart). На клієнті RpcNotificationMapper → mapNotificationFrame перетворює фрейм на AppNotification.

Існує рівно сімнадцять категорій сповіщень. Кілька подій відображаються на більш ніж один дротовий метод, бо фрейм несе перехід (невдача чи відновлення), а категорія несе питання («що робить CI?»).

Категорія Вихідна подія Дротовий метод
newMessage MessageReceived notifications/message_received (згадує вас)
agentRunCompleted MessageReceived notifications/message_received (is_agent_message, без згадки вас)
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

Фрейм перехоплення керування rig не доставляється назад тому, хто взяв кермо; повернення не фільтрується (подія не фіксує, хто відпустив), і лише закриття з backendFailure породжує фрейм rig_closed — інші причини або запитані, або вже покриті фреймом збирання.

AgentAwaitingInput публікує AgentAwaitingInputPublisher: він стежить за новими записами в реєстрах схвалень і блокувань через облікові дані, а про кожне запитання ask_user йому повідомляє AgentQuestionService. Одне очікування — одна подія; коли очікування завершується, нічого не публікується.

П’ять категорій авторських PR несуть forUserId, тож доходять до автора, а не до всіх у робочому просторі.

RemoteEventForwarder додатково пересилає TicketReassigned як notifications/ticket_reassigned, а кожен TaskLifecycleEvent — як notifications/task_<phase>. Жодне з них не відображається на категорію сповіщень — вони керують живим інтерфейсом, а не центром сповіщень. Події членства не пересилаються як фрейми: вони анулюють кеш пересилача, а хости сесій відкидають підписки на робочі простори видаленого учасника.