15 KiB
Итерация 04 — Messaging + Review (Inbox + ReviewDialog)
Historical note This document captures the planned scope and assumptions at iteration time. It is not the source of truth for the current product contract. For the current review flow, see ../team-management/README.md and ../team-management/kanban-design.md.
Эта итерация добавляет панель активности (inbox messages) и отправку сообщений тиммейтам, а также закрывает MVP review-flow: Request Review → Approve / Request Changes.
Основание:
docs/team-management/implementation.md(Iteration 4: Messaging + Review)docs/team-management/research-inbox.md(формат, race conditions, messageId verify)docs/team-management/research-messaging.md(почему inbox, ограничения доставки)docs/team-management/kanban-design.md(review flow, allowed transitions)docs/team-management/research-tasks.md(безопасная запись status при Fix)
Цель итерации
- Во вкладке Team появляется ActivityTimeline (история сообщений) и MessageComposer (отправка DM).
- Реализован review-flow:
- Request Review: перевод задачи в
REVIEW+ (если есть reviewer) уведомление в его inbox. - Approve: перевод задачи в
APPROVED. - Request Changes: опциональный комментарий → задача возвращается исполнителю:
- kanban-state запись удаляется (задача вернётся в
IN PROGRESSпоtask.status) task.statusстановитсяin_progress(atomic write + verify)- отправляется сообщение owner’у с описанием правок
- kanban-state запись удаляется (задача вернётся в
- Request Review: перевод задачи в
- Live refresh: изменения inbox/task/kanban отражаются в UI (уже существующий
team:change+ coalesce).
Не-цели (строго вне scope)
- Hard interrupt (прерывание mid-turn) — ограничение платформы, Phase 2.
- Архивация inbox / очистка истории / JSONL формат — Phase 2.
- Полноценный drag-and-drop для review — Phase 2 (пока click-to-move/кнопки).
- Round-robin балансировка reviewer’ов и reviewHistory — Phase 2.
- Любые write-path, связанные с созданием задач и
.highwatermark.
Контракт итерации (Main → Preload → Renderer)
Shared types (src/shared/types/team.ts)
Добавляем/расширяем:
InboxMessage(как вresearch-inbox.md):from: stringtext: string(plain text или JSON-строка)timestamp: string(ISO)read: booleansummary?: stringcolor?: stringmessageId?: string(в наших исходящих обязателен)
SendMessageRequest:member: stringtext: stringsummary?: stringfrom?: 'user' | string(по умолчанию'user')
SendMessageResult:deliveredToInbox: boolean(означает “записано в файл и verify прошёл”)messageId: string
Расширяем TeamData:
messages: InboxMessage[](агрегированные по всем inbox, отсортированные поtimestamp)
Расширяем ResolvedTeamMember:
status: 'active' | 'idle' | 'terminated' | 'unknown'lastActiveAt: string | nullmessageCount: numbercolor?: string
Правила статуса (из docs/team-management/README.md):
ACTIVE: last activity < 5 минутIDLE: last activity ≥ 5 минутTERMINATED: получен shutdown-event с approve=true (в доках встречаются разные названия, поэтому считаем terminated если structuredtextпосле JSON.parse имеет:type === 'shutdown_response' && approve === true, илиtype === 'shutdown_approved', илиtype === 'shutdown_response' && approved === true
- если данных нет →
unknown
IPC каналы
В src/preload/constants/ipcChannels.ts добавляем:
TEAM_SEND_MESSAGE = 'team:sendMessage'TEAM_REQUEST_REVIEW = 'team:requestReview'TEAM_UPDATE_KANBANуже существует с итерации 03 и расширяется (см. ниже) дляrequest_changes.
Важно: в v7-плане перечислены именно team:sendMessage, team:requestReview, team:updateKanban. В этой итерации не вводим новые каналы сверх этого набора.
TeamsAPI (src/shared/types/api.ts)
Расширяем:
sendMessage(teamName: string, req: SendMessageRequest): Promise<SendMessageResult>requestReview(teamName: string, taskId: string): Promise<void>updateKanban(teamName: string, taskId: string, patch: UpdateKanbanPatch): Promise<void>(из итерации 03, расширяем patch)
UpdateKanbanPatch — расширение в итерации 04 (без двусмысленностей)
К существующим операциям добавляем:
{ op: 'request_changes'; comment?: string }
Семантика request_changes (всё внутри main, как единая операция):
- Удалить kanban-state запись для taskId (remove)
- Обновить task файл:
status = 'in_progress'(atomic write + verify) - Отправить сообщение owner’у задачи с комментарием (если owner отсутствует → ошибка)
Definition of Done (DoD)
- Messaging UI
- В Team tab есть ActivityTimeline (список сообщений) и MessageComposer.
- Можно выбрать участника и отправить сообщение → появляется “Delivered” (или ошибка).
- Сообщение записывается в
~/.claude/teams/{team}/inboxes/{member}.jsonсmessageId.
- Review UI
- На задачах в
DONEесть действие “Request Review”. - В
REVIEWесть кнопки “Approve” и “Request Changes”. - “Request Changes” открывает диалог с опциональным комментарием.
- На задачах в
- Backend safety
- Inbox write: atomic write + verify
messageId+ retry/backoff (по плану ниже). - Task status write (Fix): atomic write + verify
status+ warning в UI при конфликте.
- Inbox write: atomic write + verify
- Качество
pnpm typecheckпроходит.pnpm testпроходит.
Выходные изменения (файлы)
Новые файлы
src/main/services/team/TeamInboxWriter.ts(write-path: atomic + verify + retry + lock)src/main/services/team/TeamTaskWriter.ts(минимум:updateStatus(teamName, taskId, status)), чтобы не смешивать чтение и записьsrc/renderer/components/team/activity/ActivityTimeline.tsxsrc/renderer/components/team/activity/ActivityItem.tsxsrc/renderer/components/team/activity/MessageComposer.tsxsrc/renderer/components/team/dialogs/ReviewDialog.tsx
Изменяемые файлы
src/shared/types/team.ts(InboxMessage, SendMessage*, member status,TeamData.messages)src/shared/types/api.ts(TeamsAPI methods)src/preload/constants/ipcChannels.ts(+ 3 канала)src/preload/index.ts(bridge для sendMessage/requestReview/updateKanban)src/main/ipc/guards.ts(+validateMemberName(),validateFromField()если потребуется)src/main/ipc/teams.ts(+ handlers)src/main/services/team/TeamInboxReader.ts(расширить:getMessagesFor(),getMessages())src/main/services/team/TeamMemberResolver.ts(status/messageCount/color/lastActiveAt + terminated detection)src/main/services/team/TeamDataService.ts(подтянуть messages + review methods; опираться на kanban из итерации 03; в requestReview — sendMessage первому reviewer при наличии)src/main/services/team/index.ts(exports)src/renderer/store/slices/teamSlice.ts(actions + sending states)src/renderer/components/team/TeamDetailView.tsx(layout: members + kanban + activity)src/main/services/infrastructure/FileWatcher.ts(ничего нового: inbox/task/kanban уже должны попадать вteam-change)
Порядок работ (runbook) с контрольными точками
CP0 — типы компилируются
- Обновить
src/shared/types/team.tsиsrc/shared/types/api.ts(контракты выше). pnpm typecheck
CP1 — Inbox read/write в main готов (без UI)
-
Inbox read
listInboxNames(teamName)(уже есть/или оставить) — список*.jsonбез расширенияgetMessagesFor(teamName, member):- ENOENT →
[] - invalid JSON →
[]+ warning
- ENOENT →
getMessages(teamName):- читает все inbox
*.json(параллельно, best-effort) - merge в один массив
- сортировка по
timestamp(desc)
- читает все inbox
-
Inbox write: atomic + verify + retry
Реализация должна следовать research-inbox.md:
- Каждое сообщение, которое пишет приложение, включает:
messageId = crypto.randomUUID()from = 'user'(по умолчанию)read = falsetimestamp = new Date().toISOString()
Алгоритм (в main):
read inbox JSON array (если нет → [])
append message
atomicWriteAsync(tmp + rename)
read back
verify messageId exists
если не найден → retry (до 3) с backoff 10/20/40ms
Параллелизм:
- внутри процесса сериализуем записи в один
inboxPathчерезwithInboxLock(inboxPath, fn), чтобы два IPC вызова не затёрли друг друга.
Fallback по from:
- делаем это не магией, а контрактом API:
SendMessageRequest.fromопционален, но если задан — валидируем (validateFromFieldили reusevalidateMemberName)- UI в этой итерации не обязано показывать выбор from; по умолчанию отправляем
from: 'user' - для отладки можно вызывать через DevTools
sendMessage(..., { from:'debug-user', ... })
pnpm test(если добавлялись новые unit-тесты) иpnpm typecheck
CP2 — Review actions в main + IPC
- Task status write (Fix)
Новый TeamTaskWriter.updateStatus(teamName, taskId, status):
- читает
~/.claude/tasks/{teamName}/{taskId}.json - меняет только
status - пишет через
atomicWriteAsync - перечитывает и проверяет, что
statusравен нужному - при конфликте (агент перезаписал) → throw, чтобы UI показал warning
- TeamDataService
getTeamData(teamName)теперь возвращает такжеmessagesrequestReview(teamName, taskId):updateKanban(taskId, { op:'set_column', column:'review' })(reviewStatus: pending)- выбрать reviewer:
- если
kanbanState.reviewers[]не пуст → первый элемент (round-robin — Phase 2) - иначе reviewer отсутствует (manual review)
- если
- если reviewer выбран →
sendMessage(reviewer, "...")
approveделаем напрямую черезupdateKanban(taskId, { op:'set_column', column:'approved' })request_changesделаем черезupdateKanban(taskId, { op:'request_changes', comment })(см. семантику patch выше)
- IPC
TEAM_SEND_MESSAGE: принимаетteamName,SendMessageRequestTEAM_REQUEST_REVIEW: принимаетteamName,taskIdTEAM_UPDATE_KANBAN: принимаетteamName,taskId,UpdateKanbanPatch(расширенный)
Guards:
validateMemberName()обязателен (защита от path traversal, member используется в пути inbox файла).validateTaskId()уже есть с итерации 03.
pnpm typecheck
CP3 — UI: ActivityTimeline + MessageComposer + ReviewDialog
- Layout
- Team tab становится 3-панельным:
- слева Members
- центр Kanban
- справа Activity (messages + composer)
Чтобы не потерять доступ к “сырому списку задач”, фиксируем простой UI-компромисс:
- правый panel становится табами:
ActivityиTasks - по умолчанию открываем
Activity, ноTasksостаётся доступным (и переиспользует текущийTaskList)
- ActivityTimeline
- Рендерит
TeamData.messages(последние N, например 200) с:- цветной dot (если есть
color) from,summary(или короткий previewtext)- timestamp (relative time)
- цветной dot (если есть
- MessageComposer
- select получателя (из
members) - textarea (минимум 1–2 строки)
- send → вызывает store action → IPC
sendMessage - состояния:
- sending / delivered / error
- ReviewDialog
- открывается при “Request Changes”
- comment optional
- submit →
updateKanban(..., { op:'request_changes', comment })
- Ручная проверка
- Отправить сообщение → файл inbox обновился, message появился в ActivityTimeline после refresh.
- Нажать Request Review → задача в REVIEW, при наличии reviewer’а в
kanban-state.reviewersему ушло сообщение. - Approve → задача в APPROVED.
- Request Changes → задача ушла исполнителю (status=in_progress) + отправлено сообщение owner’у.
pnpm test
Риски и митигации
- Race condition inbox: атомарная запись не решает overwrite race, поэтому делаем
messageId verify+ retry/backoff, плюс in-processwithInboxLock. - Конфликт при записи task.status: после write делаем verify; если agent перезаписал — показываем warning в UI, не делаем silent fail.
- Большие inbox: ограничиваем количество отображаемых сообщений (например 200) и добавляем “Show more” позже (итерация 05).