agent-ecosystem/docs/iterations/iteration-04-messaging-review.md
2026-02-21 09:47:24 +02:00

15 KiB
Raw Blame History

Итерация 04 — Messaging + Review (Inbox + ReviewDialog)

Эта итерация добавляет панель активности (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у с описанием правок
  • 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: string
    • text: string (plain text или JSON-строка)
    • timestamp: string (ISO)
    • read: boolean
    • summary?: string
    • color?: string
    • messageId?: string (в наших исходящих обязателен)
  • SendMessageRequest:
    • member: string
    • text: string
    • summary?: string
    • from?: 'user' | string (по умолчанию 'user')
  • SendMessageResult:
    • deliveredToInbox: boolean (означает “записано в файл и verify прошёл”)
    • messageId: string

Расширяем TeamData:

  • messages: InboxMessage[] (агрегированные по всем inbox, отсортированные по timestamp)

Расширяем ResolvedTeamMember:

  • status: 'active' | 'idle' | 'terminated' | 'unknown'
  • lastActiveAt: string | null
  • messageCount: number
  • color?: string

Правила статуса (из docs/team-management/README.md):

  • ACTIVE: last activity < 5 минут
  • IDLE: last activity ≥ 5 минут
  • TERMINATED: получен shutdown-event с approve=true (в доках встречаются разные названия, поэтому считаем terminated если structured text после 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, как единая операция):

  1. Удалить kanban-state запись для taskId (remove)
  2. Обновить task файл: status = 'in_progress' (atomic write + verify)
  3. Отправить сообщение 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 при конфликте.
  • Качество
    • 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.tsx
  • src/renderer/components/team/activity/ActivityItem.tsx
  • src/renderer/components/team/activity/MessageComposer.tsx
  • src/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 — типы компилируются

  1. Обновить src/shared/types/team.ts и src/shared/types/api.ts (контракты выше).
  2. pnpm typecheck

CP1 — Inbox read/write в main готов (без UI)

  1. Inbox read

    • listInboxNames(teamName) (уже есть/или оставить) — список *.json без расширения
    • getMessagesFor(teamName, member):
      • ENOENT → []
      • invalid JSON → [] + warning
    • getMessages(teamName):
      • читает все inbox *.json (параллельно, best-effort)
      • merge в один массив
      • сортировка по timestamp (desc)
  2. Inbox write: atomic + verify + retry

Реализация должна следовать research-inbox.md:

  • Каждое сообщение, которое пишет приложение, включает:
    • messageId = crypto.randomUUID()
    • from = 'user' (по умолчанию)
    • read = false
    • timestamp = 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 или reuse validateMemberName)
    • UI в этой итерации не обязано показывать выбор from; по умолчанию отправляем from: 'user'
    • для отладки можно вызывать через DevTools sendMessage(..., { from:'debug-user', ... })
  1. pnpm test (если добавлялись новые unit-тесты) и pnpm typecheck

CP2 — Review actions в main + IPC

  1. Task status write (Fix)

Новый TeamTaskWriter.updateStatus(teamName, taskId, status):

  • читает ~/.claude/tasks/{teamName}/{taskId}.json
  • меняет только status
  • пишет через atomicWriteAsync
  • перечитывает и проверяет, что status равен нужному
  • при конфликте (агент перезаписал) → throw, чтобы UI показал warning
  1. TeamDataService
  • getTeamData(teamName) теперь возвращает также messages
  • requestReview(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 выше)
  1. IPC
  • TEAM_SEND_MESSAGE: принимает teamName, SendMessageRequest
  • TEAM_REQUEST_REVIEW: принимает teamName, taskId
  • TEAM_UPDATE_KANBAN: принимает teamName, taskId, UpdateKanbanPatch (расширенный)

Guards:

  • validateMemberName() обязателен (защита от path traversal, member используется в пути inbox файла).
  • validateTaskId() уже есть с итерации 03.
  1. pnpm typecheck

CP3 — UI: ActivityTimeline + MessageComposer + ReviewDialog

  1. Layout
  • Team tab становится 3-панельным:
    • слева Members
    • центр Kanban
    • справа Activity (messages + composer)

Чтобы не потерять доступ к “сырому списку задач”, фиксируем простой UI-компромисс:

  • правый panel становится табами: Activity и Tasks
  • по умолчанию открываем Activity, но Tasks остаётся доступным (и переиспользует текущий TaskList)
  1. ActivityTimeline
  • Рендерит TeamData.messages (последние N, например 200) с:
    • цветной dot (если есть color)
    • from, summary (или короткий preview text)
    • timestamp (relative time)
  1. MessageComposer
  • select получателя (из members)
  • textarea (минимум 12 строки)
  • send → вызывает store action → IPC sendMessage
  • состояния:
    • sending / delivered / error
  1. ReviewDialog
  • открывается при “Request Changes”
  • comment optional
  • submit → updateKanban(..., { op:'request_changes', comment })
  1. Ручная проверка
  • Отправить сообщение → файл inbox обновился, message появился в ActivityTimeline после refresh.
  • Нажать Request Review → задача в REVIEW, при наличии reviewerа в kanban-state.reviewers ему ушло сообщение.
  • Approve → задача в APPROVED.
  • Request Changes → задача ушла исполнителю (status=in_progress) + отправлено сообщение ownerу.
  1. pnpm test

Риски и митигации

  • Race condition inbox: атомарная запись не решает overwrite race, поэтому делаем messageId verify + retry/backoff, плюс in-process withInboxLock.
  • Конфликт при записи task.status: после write делаем verify; если agent перезаписал — показываем warning в UI, не делаем silent fail.
  • Большие inbox: ограничиваем количество отображаемых сообщений (например 200) и добавляем “Show more” позже (итерация 05).