feat(team): add read-only mailbox/task activity API and real-time events - #740
Open
piorpua wants to merge 16 commits into
Open
feat(team): add read-only mailbox/task activity API and real-time events#740piorpua wants to merge 16 commits into
piorpua wants to merge 16 commits into
Conversation
added 16 commits
July 29, 2026 15:01
Back the Team "message & task flow" view with read-only endpoints and two
symmetric WebSocket channels so the frontend can render every member's
mailbox and task board with live updates.
- api-types: add TeamMailboxMessageResponse, TeamTaskResponse (no metadata),
TeamMailboxChange/TeamTaskChange enums, and the TeamMailboxChangedPayload /
TeamTaskChangedPayload event payloads; re-export from the crate root.
- db: add ITeamRepository::list_messages_by_team (team-wide, created_at DESC,
limit-capped) and list_messages_by_ids (batched IN lookup); implement for
SQLite and all mock repos.
- team: add activity_mapping (row/domain -> response, files JSON degrades to
empty on parse error), a team.mailboxChanged event constant, and
TeamEventEmitter::broadcast_task_changed / broadcast_mailbox_changed.
- team: wire an optional emitter into Mailbox and TaskBoard via with_events;
write/read-mark emit mailboxChanged, create/update emit taskChanged
(deletion is a status=deleted update). Inject a single shared emitter at the
sole production session construction site.
- team: add TeamSessionService::list_team_mailbox / list_team_tasks (ownership
enforced, limit clamped to [1,1000], tasks sorted DESC with id tiebreak) and
GET /api/teams/{id}/mailbox and /tasks routes.
Events log only non-sensitive correlation fields (id/change/team_id); message
content, summary, and task subject never appear in logs. Read endpoints log
team_id and result count at info. Adds unit and integration tests covering
ordering, limit clamping, mapping, emitter wiring, and ownership errors.
…29-001-001 # Conflicts: # crates/aionui-db/src/repository/sqlite_team.rs # crates/aionui-db/tests/team_repository.rs # crates/aionui-team/src/mailbox.rs # crates/aionui-team/src/session.rs # crates/aionui-team/src/task_board.rs # crates/aionui-team/src/test_utils.rs # crates/aionui-team/tests/common/mod.rs # crates/aionui-team/tests/session_service_integration.rs
… to user These three team WebSocket events serialized their payload with a raw serde_json::to_value, omitting the user_id that the event-bus → WebSocket bridge requires. Since #669 (multi-account user scope isolation) the bridge drops any non-whitelisted event lacking user_id, so these events never reached the renderer: the activity board never updated task status, message read/unread badges, or newly created tasks/messages in real time. Route them through scoped_payload() like every other team event so the bridge delivers them via broadcast_to_user, reusing the existing per-user / per-team delivery scope. No bridge or global-whitelist changes needed.
The `SchedulerAction` enum, `TeammateManager::execute_action`, its private `handle_*` arms, and `parse_tool_call` were a vestige of an earlier design where MCP tool calls were parsed into `SchedulerAction`s and executed via `finalize_turn(actions)`. The live path moved to `dispatch_tool` → `exec_*` long ago: `parse_tool_call` was only ever called from unit tests, and `execute_action` was only reachable from `finalize_turn`, which both production callers invoke with an empty action slice. The crate's own backend-audit already flagged `parse_tool_call` as dead code. Remove the dead symbols and simplify `finalize_turn` to take just a slot id (it now delegates straight to `mark_idle`, where the leader re-wake and idle-notification bookkeeping already live). Tests that exercised only the dead wrappers are dropped; those covering still-live behaviour (`request_shutdown_agent`, leader re-wake on all-idle, idle-set-once) are rewired to the live API so coverage is preserved. No production behaviour change: the tool execution path (MCP + CLI, shared via TeamToolExecutor → dispatch_tool) is untouched.
Assigning a team task to a teammate previously did nothing to notify or wake the owner — the wake path was coupled only to mailbox messages (team_send_message / shutdown), so a lead that created a task without also sending a message left a dormant owner asleep and the task unattended. team_task_create / team_task_update now notify and lazily wake the assigned teammate, and completing a task wakes the owners of any downstream tasks it unblocks. Delivery reuses the exact agent-to-agent message path (mailbox write + enqueue + event-loop notify), so an already-working owner has the notice queued and drained on its next turn rather than interrupted, and a dormant owner is warmed up — identical reliability to a direct message. A pure gate (should_wake_task_owner) restricts wakes to a live, non-lead teammate other than the caller, on a non-terminal, unblocked task; blocked tasks wake later via the upstream-completion unblock path. The assignment notice carries the task subject + description so the teammate (prompted to read its mailbox for assignments) has the full hand-off without a separate message. check_unblocks now also broadcasts team.taskChanged for each downstream task whose dependency set changed, so the activity board reflects the unblock. Lead prompt updated (Method Y): team_task_create auto-notifies/wakes the owner, so the lead no longer needs a separate team_send_message to hand off or wake — messages are for follow-up conversation.
Pre-existing formatting drift across a few crates plus one clippy::cloned_ref_to_slice_refs lint in a mailbox test were failing the `just push` gate (cargo fmt --all --check + clippy --workspace --all-targets -D warnings). Format the affected files and replace `&[m.id.clone()]` with `std::slice::from_ref(&m.id)`. No behaviour change.
Sync AionCore worktree baseline up to release v0.1.56.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds the backend read APIs and real-time events that power a new read-only Message & Task activity view for Team mode. Each team member (including the leader) already has a mailbox and a task-board persisted server-side, but there was no user-facing way to read the whole team's mailbox/tasks, and the message/task change events were asymmetric. This PR fills that capability gap.
This is one half of a two-repo change and is consumed by the paired AionUi PR (branch
aionissue/task-20260729-001-001). Both should be merged together.Read-only scope: SELECT read paths plus in-memory mapping. No DB schema change, no migration, no backfill.
What changed
aionui-api-types): newTeamMailboxMessageResponse,TeamTaskResponse,TeamMailboxChange/TeamTaskChangeenums,TeamTaskChangedPayload/TeamMailboxChangedPayload, plus a unified paginated feed (TeamActivityKind,TeamActivityItemResponse,TeamActivityCursor,TeamActivityPageResponse). No axum/HTTP dependency added.aionui-db):list_messages_by_team(DESC + clamped limit),list_messages_by_ids(batched/chunked IN), keyset-paginatedlist_messages_by_team_pagedandlist_tasks_paged, andlist_tasks_by_idsfor dependency-label resolution — added to the trait, the SQLite implementation, and mocks.aionui-team): newactivity_mapping(row/domain → response types; malformedfilesJSON degrades to empty with awarn);eventsgains ateam.mailboxChangedconstant plusbroadcast_task_changed/broadcast_mailbox_changed, with payloads scoped per user (task/mailbox/slot-work events all narrowed to avoid cross-user leakage); mailbox / task-board emit events at their single write choke points via an optionally-injected emitter (no-op when absent); the emitter is injected at the sole production construction site.list_team_mailbox/list_team_tasks/list_team_activity, all gated through the existing owned-team load (team-not-found → 404, cross-user → Forbidden) with limit clamping.GET /api/teams/{id}/mailbox,GET /api/teams/{id}/tasks(supports?ids=for dependency resolution), andGET /api/teams/{id}/activity(unified cursor-paginated feed withdirection/kind/limitand fallback), reusing the existing team auth middleware.Adjacent changes reviewers should note separately
SchedulerActiondispatch path (related to tightening the single write path).Layering / conventions
aionui-team, notaionui-db; response types stay inaionui-api-typeswith no HTTP-framework dependency; route handlers only transform request/response, logic stays in the service.Testing
Local pre-push gate passed on the branch:
just migration-check— passed (migrations immutable; none added).cargo fmt --all/ lint-fix (cargo clippy --fix --workspace -- -D warnings) — clean, no changes.cargo nextest run --workspace— 8016 passed, 22 skipped.Covered: activity DTO/enum/payload serde;
list_messages_by_teamordering/limit andlist_messages_by_idsbatching;activity_mapping; emitter wiring (mailbox / task-board) and event shapes; and read-endpoint service integration tests (happy / 404 / forbidden / no-metadata).Not exercised here (needs manual/live verification with the frontend): end-to-end WS delivery over a real connection, cross-user WS isolation under live traffic, and the paginated feed under infinite scroll.
Notes
main. The branch merged upstream releasev0.1.56; that release is already contained inmain, so this PR's diff is fix-only (no upstream migrations or release content leak into scope).