Add a tabbed session inbox to the Duo side panel behind a feature flag
Problem
The panel session list is reverse chronological, so a session waiting on your approval sits between two finished ones and is distinguished only by a status icon. Search and sort help you find a session you already know about; they do not help you notice the one that needs you.
Proposal
Add ee/app/assets/javascripts/ai/duo_agents_platform/panel/agent_session_inbox.vue,
used only by the panel router. It renders the search bar from
#616492 unchanged, then a GlTabs header with Needs a decision and All,
then the list. The existing agent_flow_list.vue rows stay for now; the row redesign
is a separate issue.
One query serves both tabs, as two aliased connections on duoWorkflowWorkflows:
needsDecision: duoWorkflowWorkflows(
type: "non_foundational_chat_agents"
statusGroup: AWAITING_INPUT
first: $first
search: $search
sort: $sort
) {
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
edges { node { ...FlowFragment } }
}
all: duoWorkflowWorkflows(
type: "non_foundational_chat_agents"
first: $first
after: $after
search: $search
sort: $sort
) {
pageInfo { hasNextPage hasPreviousPage startCursor endCursor }
edges { node { ...FlowFragment } }
}Both aliases must pass type: "non_foundational_chat_agents", which is what the
panel's current getUserAgentFlows query passes. Dropping it would pull chat
threads into the session list: Ai::DuoWorkflows::WorkflowsFinder maps that type
to without_workflow_definition(Ai::FoundationalChatAgent.workflow_definitions),
which excludes chat and agentic_chat/v1 along with the other foundational chat
agents. Chat sessions are the single largest definition group in a typical
database, so this is not a rounding error.
Both aliases share the search and sort variables, so the unchanged search bar
filters both tabs consistently, and one query keeps the existing single 10s poll
rather than doubling it.
Tab counts come from what is loaded rather than a server aggregate: edges.length
for that connection, rendered as 20+ when its hasNextPage is true. Page size is
20 via DEFAULT_AGENT_PLATFORM_PAGINATION_VARIABLES. No backend change.
The decision tab's empty state is its own state, not the generic "no sessions" one: "Nothing needs you right now", with "Your agents are grinding away. The moment one of them wants a decision, it lands here." and a See what's running button that switches to the All tab.
Acceptance criteria
- Panel sessions route renders the new component when
duo_panel_session_inboxis enabled, and the current component when it is not. - Needs a decision shows sessions in
statusGroup: AWAITING_INPUT; All shows every non-chat session. - Neither tab lists Duo Chat threads: both aliases pass
type: "non_foundational_chat_agents", matching the current panel query. A spec asserts the variable is sent. - Both tab labels carry a count badge showing
n, or20+when that connection has a next page. - Typing in the search bar or changing the sort updates both tabs.
- Empty decision tab shows the dedicated empty state and its button switches to All.
- Feature flag
duo_panel_session_inbox(type: wip,default_enabled: false, actorcurrent_user) is pushed fromee/lib/ee/gitlab/gon_helper.rb.
Technical notes
AWAITING_INPUT is input_required, plan_approval_required and
tool_call_approval_required per Ai::DuoWorkflows::Workflow::GROUPED_STATUSES.
Paused and failed sessions stay in All.
non_foundational_chat_agents excludes every foundational chat agent, not only
Duo Chat, so agents like duo_planner/v1 and orbit_agent are filtered out too.
That is what the panel does today, so keep it and treat any change to the
definition set as a separate decision.
Keep the edges { node } shape. A second shape (nodes) for the same field and args
collides in the Apollo cache. There is no custom keyArgs for
duoWorkflowWorkflows, so each alias caches under its own args — assert that in the
spec rather than assuming it. Cursors are per alias, so each tab needs its own
after/before variables if both paginate.
Out of scope
Row layout, per-row action buttons, and read state.