Libra CodeHub

CedarCopilot/cedar-mail

Branch: staging

fix(agents): wire the Chats tab to real threads, scoped SELF-only

merged#2771CedarCopilot

CedarCopilot wants to merge 2 commits into staging from fix/chat-tab-self-scope

Live on prod since Sat, 3 of 11 surfaces workingTimeline and evidence
  1. Opened
    Sep 12, 2026, 2:10 PM
  2. Merged
    Sep 12, 2026, 2:48 PM
  3. Live on prod
    Sep 12, 2026, 2:48 PM
  4. Observed 18 hours, 11 surfaces, 197 requests
    Sep 12, 2026, 2:48 PM
  5. Verified

    Live on prod since Sat, 3 of 11 surfaces working

    Sep 12, 2026, 2:48 PM
  6. Pipelines steady after this deploy
    Sep 12, 2026, 2:48 PM
  7. Sep 12, 2026, 2:48 PM

Behaviors Libra is checking

Chat summaries obtain non-deleted message counts with one grouped count lookup for the returned threads instead of issuing one count query per thread.Not checked
prod
The Agent Workspace Chats tab shows the signed-in user's non-deleted chat threads for the selected agent, newest first, with thread name, timestamp, message count, and primary-conversation details.Not checked
prod
When an org admin views another user's agent, the Chats tab remains empty rather than exposing that user's thread identifiers or showing threads that cannot be opened.Not checked
prod
Executions and Chats load independently in the Runs view, so ready data on one sub-tab remains visible while the other request is still pending and only the selected sub-tab displays a skeleton.Not checked
prod
Chat thread reads and writes are restricted to the session user's own threads, so cross-user requests are denied even for org admins.Not checked
prod

Libra measured 3 of 11 production surfaces on prod. 1 surface had under 20 requests, so Libra has not judged it; 7 surfaces had no requests at all. Libra checks hourly for 3 days after each deploy.

Summary

Follow-up to #2766. That PR closed the chat-thread cross-user IDOR but left agent.getRuns's Chats tab as a hardcoded stub ({ chats: [] }) with a comment claiming it was waiting on chat_threads.agent_id , a column that, per docs/agent-workspace.md's Phase 8, already existed and was fully wired everywhere else. Investigating why an org admin's report ("a teammate's chat silently fails to load") wasn't actually fixed by #2766 turned up two things:

  1. The Chats tab itself was never implemented, independent of any authorization gap , agent.ts's getRuns unconditionally returned { executions: [], chats: [] } for kind: 'chats', and the frontend (AgentView.tsx) only ever queried kind: 'executions', so AgentRunsTab's chats prop was always undefined. Fixed: a new getAgentChats query (mirrors getAgentRuns's shape , one query for threads, one grouped follow-up for message counts, using the existing getPrimaryConversation helper for the subject binding) and a second useQuery in AgentView.tsx alongside the executions one, since the router answers one half of the union per kind.
  2. Admin-view isn't safe here yet. The embedded chat viewer (ChatRow's openChatThread → store.switchThread → loadThreadMessages → chat.getMessages) has no concept of an admin-scoped viewing target anywhere in the store , it always loads by the session user's own id. Listing a teammate's chats in the tab would only produce a list that breaks the moment a row is clicked, recreating the original bug one layer deeper. So this PR scopes chats to SELF only, deliberately not admin-viewable:
    • authorize.ts's chat_thread:user policy tightened from SELF_OR_ORG_ADMIN (added in #2766) to SELF.
    • agent.ts's getRuns guards the chats branch directly (actorUserId === ctx.sessionUser.id) since it resolves its actor through subagent_execution, not chat_thread , the policy table alone doesn't reach it.

An org admin viewing a teammate's agent now sees an honest empty Chats tab instead of a broken one. Widening this back to admin-viewable is future work once the chat store carries a real viewing scope.

Test plan

  • pnpm --filter @zero/server run types , clean
  • pnpm --filter @zero/mail run types , clean
  • chat-router-org-admin.test.ts , the four admin-CAN cases flipped to admin-CANNOT (SELF policy refuses everyone but the owner)
  • agent-router-org-admin.test.ts , two new cases: an admin viewing a teammate's agent gets [] for chats even though the stub genuinely has a row for that thread (proves the guard fires, not that data happened to be absent); the teammate viewing their own agent still gets their real chats back
  • agent-router-auth.test.ts , real coverage of getAgentChats's query + mapping (previously only exercised the vacuous empty-stub path)
  • All 54 tests across the four touched server suites pass; 22/22 AgentRunsTab tests pass

šŸ¤– Generated with Claude Code

https://claude.ai/code/session_0187itFj1uakga2xd7kPk4qp

Greptile Summary

This PR replaces the Agent Workspace Chats stub with real, owner-scoped chat history and narrows chat-thread authorization to self access until the embedded viewer supports administered-user scope.

  • Adds a second agent.getRuns query and supplies chat summaries to the Previous Runs UI.
  • Queries agent-bound threads and grouped message counts on the server.
  • Prevents organization administrators from listing or opening teammates' chats through the incomplete viewer path.
  • Updates authorization and router tests for the self-only contract.
  • Conversation-scoped chat history currently fails to apply the supplied conversation filter, and independent request loading unnecessarily blocks both history sub-tabs.

Confidence Score: 4/5

The PR should not merge until conversation-scoped Chats results are restricted to the selected conversati

Show production surfaces and changed-file mapping

Production surfaces

SurfaceRequestsErrorsp95UsersVerdict
orchestrator_agent_step52 → 850 → 0 (0%)251638 ms → 163253 ms34Working
85 requests since the deploy with 0 errors (0 errors in the 52-request baseline before it). p95 163253 ms, was 251638 ms.
model_chunk On-Event Execution Agent (Understudy)35 → 840 → 0 (0%)5 ms → 1 ms0Working
84 requests since the deploy with 0 errors (0 errors in the 35-request baseline before it). p95 1 ms, was 5 ms.
model_chunk Automation Agent (Understudy)26 → 240 → 0 (0%)3 ms → 1 ms0Working
24 requests since the deploy with 0 errors (0 errors in the 26-request baseline before it). p95 1 ms, was 3 ms.
aws-primary.syncThread10 → 47 → 0 (0%)385 ms → 535 ms0Insufficient traffic
4 requests, under the 20 Libra needs
model_chunk Automation Agent (Kimi K3 experiment)0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agentExecutions.getAgentExecutions0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agent.list0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agent.getInvocationSources0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/settings.updateAgentSettings0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.
/api/trpc/agentExecutions.executeFromClientSend2 → 00 → 0 (0%)12935 ms → not measured0No traffic
No requests recorded since this deploy.
model_chunk Automation Agent (Sol experiment)0 → 00 → 0 (0%)not measured0No traffic
No requests recorded since this deploy.

Changed files → surfaces

  • apps/server/src/services/auth/authorize.tsaws-primary.syncThread
  • apps/server/src/trpc/routes/agent.tsorchestrator_agent_stepmodel_chunk On-Event Execution Agent (Understudy)model_chunk Automation Agent (Understudy)model_chunk Automation Agent (Kimi K3 experiment)/api/trpc/agentExecutions.getAgentExecutions/api/trpc/agent.list+4
  • apps/mail/modules/agents/__tests__/AgentRunsTab.test.tsxno production surface mapped
  • apps/mail/modules/agents/components/AgentRunsTab.tsxno production surface mapped
  • apps/mail/modules/agents/components/AgentView.tsxno production surface mapped
  • apps/server/src/trpc/routes/__tests__/agent-router-auth.test.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/agent-router-org-admin.test.tsno production surface mapped
  • apps/server/src/trpc/routes/__tests__/chat-router-org-admin.test.tsno production surface mapped