Skip to content

Conversation

@crspeller
Copy link
Member

@crspeller crspeller commented Nov 26, 2025

Summary

Adds AI agent streaming support to the Mattermost Mobile app, enabling interaction with AI agents similar to the web/desktop experience. The implementation includes:

  • Real-time streaming: WebSocket streaming of agent responses
  • Reasoning summaries: Collapsible display of agent reasoning with loading states
  • Tool call approval: Interactive UI for approving or rejecting tool calls with visual feedback
  • Citations & annotations: Display of citations with clickable references (no inline references)
  • Generation controls: Stop and regenerate buttons for managing agent responses

The architecture uses an ephemeral streaming store (StreamingStore) that handles transient WebSocket events (custom_mattermost-ai_uppsdate) and properly cleans up state on generation end, allowing components to seamlessly transition to reading persisted database data through standard POST_EDITED events.

It does NOT include any UI beyond the posts themselves. That UI is in a separate PR: #9318

Also adds a CLAUDE.md file for ease of agentic coding with the mobile app using learnings from making this PR and the follow up.

Ticket Link

https://mattermost.atlassian.net/browse/MM-66762

Device Information

This PR was tested on: Pixel 7 android 16

Screenshots

Screenshot_1764163115 Screenshot_1764163216 Video Screenshot_1764163467

Release Note

Added AI agent streaming support with real-time message updates, reasoning summaries, tool call approval UI, citations display, and generation controls (stop/regenerate).

Implements Phase 4 from the agents mobile plan:
- WebSocket event handling for annotations control signal
- CitationsList component displaying sources below agent responses
- Collapsible sources section with source count
- Each citation shows title, domain, and opens URL on tap
- Citations persisted in post.props.annotations
- Touch-optimized UI with 44pt minimum tap targets
- Remove optimistic WebSocket event approach that didn't work
- Clear component streaming state on ENDED event to force switch to persisted data
- Remove delays in streaming store cleanup (500ms and 100ms)
- POST_EDITED event now properly updates UI for both accept and reject
- Remove debug console.log statements
- Fix ESLint issues (unused vars, nested callbacks, nested ternary)
@github-actions
Copy link

github-actions bot commented Nov 26, 2025

Coverage Comparison Report

Generated on December 08, 2025 at 18:32:14 UTC

+-----------------+------------+------------+-----------+
| Metric          | Main       | This PR    | Diff      |
+-----------------+------------+------------+-----------+
| Lines           |     86.28% |     86.29% |     0.01% |
| Statements      |     86.16% |     86.17% |     0.01% |
| Branches        |     72.54% |     72.64% |     0.10% |
| Functions       |     85.54% |     85.41% |    -0.13% |
+-----------------+------------+------------+-----------+
| Total           |     82.63% |     82.62% |    -0.01% |
+-----------------+------------+------------+-----------+

@crspeller crspeller mentioned this pull request Nov 26, 2025
@crspeller crspeller added 2: Dev Review Requires review by a core commiter 2: UX Review Requires review by a UX Designer labels Nov 26, 2025
Copy link
Contributor

@enahum enahum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall fantastic job, most comments are mostly about makeup, great first contribution to this project :)


import {CONTROL_SIGNALS} from '@agents/constants';
import {StreamingEvents, type StreamingState, type PostUpdateWebsocketMessage} from '@agents/types';
import {DeviceEventEmitter} from 'react-native';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if we could have this store use observables instead of emitting events? Did you look into that?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, just followed the pattern with DeviceEventEmitter for the typing indicator.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok not a blocker.. but it would be nice to explore the use of observables instead of emitting events.

My reason for this is that observables are better for state management while DeviceEventEmitter is mostly to communicate between native and JS event bridges, sure it works here, but I think its worth exploring. You can check how it was done with @products/calls/state/*

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Looks better I think. Good call.

@crspeller crspeller requested a review from enahum November 28, 2025 16:13
Copy link
Contributor

@enahum enahum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really have anything blocking, but I would love to hear your response to some of my comments before approving.


## Important Development Notes
- Always run the checks before committing code
- Always test any changes using the mobile mcp before calling something complete or committing code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curious, what mcp?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


import {CONTROL_SIGNALS} from '@agents/constants';
import {StreamingEvents, type StreamingState, type PostUpdateWebsocketMessage} from '@agents/types';
import {DeviceEventEmitter} from 'react-native';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok not a blocker.. but it would be nice to explore the use of observables instead of emitting events.

My reason for this is that observables are better for state management while DeviceEventEmitter is mostly to communicate between native and JS event bridges, sure it works here, but I think its worth exploring. You can check how it was done with @products/calls/state/*

@crspeller crspeller requested a review from enahum December 4, 2025 21:45
@crspeller
Copy link
Member Author

@enahum Ready for review again. Addressed feedback and moved to using observables.

Copy link
Contributor

@enahum enahum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much better with the observables, thank you for that!!

There are a couple of non blocking comments.

Great job on this one!

@@ -0,0 +1,294 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice

Copy link

@nickmisasi nickmisasi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@crspeller crspeller removed 2: Dev Review Requires review by a core commiter 2: UX Review Requires review by a UX Designer labels Dec 9, 2025
@crspeller crspeller removed the request for review from asaadmahmood December 9, 2025 11:56
@crspeller crspeller merged commit 0c675ec into main Dec 9, 2025
6 checks passed
@crspeller crspeller deleted the agents branch December 9, 2025 11:56
@amyblais amyblais added this to the v2.36.0 milestone Dec 9, 2025
@amyblais amyblais added the Docs/Needed Requires documentation label Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Docs/Needed Requires documentation release-note

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants