-
Notifications
You must be signed in to change notification settings - Fork 1
feat: add first-class user messaging with channels and DMs #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This implements a Slack-like messaging system where human users can: - Register as first-class "user" entities in the relay daemon - Join/leave channels for group communication - Send channel messages to all channel members - Send direct messages to other users or agents - Receive messages from agents and other users Key changes: - Protocol: Added EntityType (agent|user), CHANNEL_* message types - Router: Added channel membership tracking, user entity support - Connection: Added entityType, displayName, avatarUrl to HELLO - RelayClient: Added entityType support for user connections - UserBridge: New bridge between dashboard WebSocket and daemon - Dashboard: Integrated UserBridge for real-time user messaging All 1030 tests pass including 148 new tests for channels/users.
- Add useChannels hook for WebSocket channel management - Add ChannelSidebar component with Slack-like UI - Add ChannelChat component for message display and input - Wire UserBridge into presence WebSocket handler - Add API endpoints for channel operations - Export useChannels from hooks index - Record trail for implementation
Keep both trajectory entries from HEAD and origin/main
- Prefix unused tokenExpiresAt with underscore in onboarding.ts - Remove unused UserBridgeOptions import in user-bridge.test.ts - Wrap case block declarations in braces in useChannels.ts
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a Slack-like messaging system that enables human users to become first-class entities in the relay daemon, allowing them to join channels, send channel messages, and communicate directly with AI agents and other users.
Key Changes:
- Added
EntityType('agent' | 'user') to distinguish between AI agents and human users throughout the protocol - Implemented channel operations (join/leave/message) with membership tracking in the router
- Created
UserBridgeto connect dashboard WebSocket users to the relay daemon as 'user' entities
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/protocol/types.ts |
Added EntityType type and 6 new CHANNEL_* message types to protocol |
src/protocol/channels.ts |
New protocol types and utilities for channel/DM operations |
src/wrapper/client.ts |
Extended RelayClient config with entityType and user display fields |
src/daemon/connection.ts |
Added entityType tracking to connection state |
src/daemon/router.ts |
Implemented channel membership tracking and message routing |
src/dashboard-server/user-bridge.ts |
New bridge connecting dashboard WebSocket users to relay daemon |
src/dashboard-server/server.ts |
Integrated UserBridge with presence WebSocket and added channel REST APIs |
src/dashboard/react-components/hooks/useChannels.ts |
React hook for channel operations via WebSocket |
src/dashboard/react-components/ChannelSidebar.tsx |
UI component for channel navigation |
src/dashboard/react-components/ChannelChat.tsx |
UI component for channel messaging |
src/cloud/api/onboarding.ts |
Renamed unused variable to follow convention |
.trajectories/* |
Updated trajectory tracking metadata |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (ws.readyState !== 1) return; // Not OPEN | ||
|
|
||
| // Determine message type from envelope | ||
| const env = envelope as { type?: string; payload?: { channel?: string; body?: string }; from?: string; to?: string }; |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This complex inline type assertion makes the code harder to read and maintain. Consider extracting this into a named interface or using a more specific envelope type from the protocol imports.
| private getConnectionByName(name: string): RoutableConnection | undefined { | ||
| return this.agents.get(name) ?? this.users.get(name); |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method performs two Map lookups for every call. Consider maintaining a single unified connection map indexed by name to avoid the double lookup, or cache the result if this method is called frequently.
| export function createDmChannelName(...participants: string[]): string { | ||
| if (participants.length < 2) { | ||
| throw new Error('DM requires at least 2 participants'); | ||
| } | ||
| const sorted = [...participants].sort(); | ||
| return `dm:${sorted.join(':')}`; |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error path where participants.length < 2 lacks test coverage. Add a test case that verifies an error is thrown when createDmChannelName is called with fewer than 2 participants.
|
|
||
| case 'channel_message': { | ||
| const channelMsg: ChannelMessage = { | ||
| id: `${Date.now()}-${Math.random().toString(36).slice(2)}`, |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using Date.now() combined with Math.random() for ID generation can lead to collisions. Consider using the uuid library (already imported in other files) to generate proper UUIDs for message IDs.
| } | ||
|
|
||
| function MessageBubble({ message, isOwn }: MessageBubbleProps) { | ||
| const time = new Date(message.timestamp).toLocaleTimeString([], { |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The toLocaleTimeString call doesn't specify a locale, which will use the user's system locale. Consider passing an explicit locale (e.g., 'en-US') for consistent formatting across all users, or document that this is intentional for localization.
| const time = new Date(message.timestamp).toLocaleTimeString([], { | |
| const time = new Date(message.timestamp).toLocaleTimeString('en-US', { |
- Change onMessage from method to property assignment - Use 'any' type for callback params to avoid contravariance issues - Update mock in tests to use property instead of method
This implements a Slack-like messaging system where human users can:
Key changes:
All 1030 tests pass including 148 new tests for channels/users.