Backend:
- room_ai_list: batch-fetch models, skip entries where model_name
cannot be resolved (instead of falling back to "AI {uid}")
- room_ai_upsert: return None for model_name when lookup fails
(instead of "AI {uid}")
Frontend:
- room-context: discard configs with missing modelName after retries
- DiscordMemberList: filter out configs without modelName
- MessageInput: filter out configs without modelName
- RoomSettingsPanel: prefer model_name from API, fallback to
availableModels lookup, never render raw UID
- RoomAiTasksPanel: fix broken id/name mapping (was cfg.id/cfg.name
which don't exist), filter out configs without model_name
Billing is now handled internally by chat_service.process via record_ai_session.
Remove the old billing.rs file and explicit record_ai_usage calls from all 4
AI streaming modes (nonstreaming, react_nonstreaming, react_streaming, streaming).
- make_persist_fn now accepts embed_service, collects persisted text messages
- Filters non-text, non-empty, non-system/tool messages
- Groups by room→project_name, batch-embeds via embed_memories_batch
- Removes old per-message synchronous embed_memory call
- Workers thread embed_service through to persist_fn
- ai_react_nonstreaming now passes real input/output tokens to billing
- Was passing hardcoded 0,0 despite destructuring token data
- Also fix unused variable warnings
- process_react now returns (String, i64, i64) tuple with token counts
- Extract token stats from rig Agent FinalResponse usage field
- Both streaming and non-streaming ReAct modes now bill correctly
- Use AgentBuilder for native tool-calling with stream_prompt()
- Add RecordingTool wrapper preserving retry + DB recording
- Fix tool_choice bug in do_completion (same as call_stream_once)
- Add seq field to RoomMessageStreamChunkEvent for strict ordering
- Map streaming events: Text→Answer, Reasoning→Thought, ToolCall→Action
- Only final event has done=true, removed premature stream ending
- Store __chunks__ JSON in thinking_content for ordered replay
- Import room_message_reaction, room_message_edit_history, room_notifications modules
- Fix room_message_edit_history: no Room column, use subquery via messages
- Change publish_project_room_event from Result to () handling
- Add QuerySelect import for limit() method in workers.rs
- Fix second copy of push_subscription unwrap that was in a
tokio::spawn block with different indentation
- Replace constant UUID parse unwrap with expect()
- SSH rate limiter: wire SshRateLimiter into SSHServer with IP-based
rate limiting on new_client connections
- Room startup: cap initial room load at 1000 via limit() to prevent
resource exhaustion on large instances
- WS token exposure: only include token in URL for cross-origin
connections; same-origin web clients authenticate via secure cookies
- CSRF: confirmed SameSite::Lax + Secure + HttpOnly are all set
(session config defaults)
When a user mentions a repository in room chat, extract the repo name
from @[repo:name:label] brackets, look up the full repo model from the
database, and inject its details (name, description, default branch,
visibility) into the AI message context. Works independently of
embed_service availability.
Add RoomAiUpdated, RepoCreated, RepoUpdated, RepoDeleted event types.
Publish RoomAiUpdated after room_ai upsert/delete and repo events
after repo create/update. Always set model_name in AI list response
(fallback to "AI {uuid}" when model lookup fails) so frontend never
displays a raw UUID.
1. WS disconnect now unsubscribes from user_notification_inner.
Previously, every WebSocket connection created a broadcast channel
for user notifications that was never removed on disconnect, causing
unbounded growth proportional to unique connected users over time.
2. Room worker tasks now use the manager's room_shutdown_txs channel
instead of a local broadcast channel. shutdown_room() sends on this
channel, so when a room is deleted the worker task receives the signal
and terminates, releasing its DashMap (capacity 10,000) and all
captured closures. Previously the worker ran forever.
- Save thinking_content as {"__chunks__": [{type, content}]} for replay
- Tool call sanitization — don't expose raw results to frontend
- Billing record_ai_usage integration
- Room service module refactoring into service/ directory
- Add sender_type field to TypingEvent (user/ai)
- Change Redis TTL from 10s to 60s for AI typing persistence
- Broadcast typing.start/stop with sender_type=ai when AI stream starts/ends
- Replay active AI typing events from Redis on new WS subscribe
- Fix ai.stream_chunk WS payload missing display_name and chunk_type
- Add initial thinking chunk on AI stream start for immediate indicator
- message.rs: after creating a text message, spawn async task to
embed and upsert into Qdrant collection "room:{project}:{room_id}"
- service.rs: RoomService now takes optional EmbedService, embed on
every message creation
RoomMessageEvent was losing the AI model name because the
From<RoomMessageEnvelope> impl hardcoded display_name: None.
Add display_name to RoomMessageEnvelope and propagate it through
all AI streaming code paths (chat, ReAct, non-streaming).
Member messages keep display_name: None.
- ReAct streaming: collect all ReactStep chunks into reasoning_buffer;
if no Answer step is emitted, persist the full reasoning chain instead
of empty content
- All AI error paths (reasoning loop failure, non-streaming errors) now
send user-visible [AI error: ...] messages instead of silently dropping
- Fix borrow checker: clone content before struct init, use should_log bool
to avoid double-borrow on err_msg
service.rs: Replace per-message Lua+DB seq with simple INCR, only
reconcile DB every 1000 messages (99.9% queries eliminated).
storage.rs: Replace N+1 GET loop with single MGET for both
get_user_sessions and get_workspace_sessions (N+1 → 2 roundtrips).
RoomConnectionManager now holds a cache field and typing_inner broadcast
map. broadcast_typing() persists start/stop to Redis (SETEX 10s / DEL)
and broadcasts via tokio channel. ws_universal.rs handles TypingStart/
TypingStop actions and streams typing events to WS clients.
Add TypingEvent struct in queue::types for broadcast-based typing
indicators, and TypingStart/TypingStop variants in RoomEventType for
WebSocket event dispatch.
- Remove all use slog::* imports and log: slog::Logger fields
- Replace slog macros with tracing::{info!, warn!, error!, debug!}
- metrics.rs: upgrade metrics 0.21→0.22, remove register_*! macros,
use functional API: metrics::gauge!(), metrics::counter!(),
metrics::histogram!(), metrics::describe_gauge!() etc.
- RoomMetrics: all fields now use functional metrics API, dynamic
room_id labels passed as owned String to avoid lifetime issues
- RoomService: remove pub log: slog::Logger field
- connection.rs: remove log from subscribe_room_events,
subscribe_project_room_events, subscribe_task_events_fn
Frontend:
- Add Discord-style 3-column layout (server icons / channel sidebar / chat)
- AI Studio design system: new CSS token palette (--room-* vars)
- Replace all hardcoded Discord colors with CSS variable tokens
- Add RoomSettingsPanel (name, visibility, AI model management)
- Settings + Member list panels mutually exclusive (don't overlap)
- AI models shown at top of member list with green accent
- Fix TS errors: TipTap SuggestionOptions, unused imports, StarterKit options
- Remove MentionInput, MentionPopover, old room components (废弃代码清理)
Backend:
- RoomAiResponse returns model_name from agents.model JOIN
- room_ai_list and room_ai_upsert fetch model name for each config
- AiConfigData ws-protocol interface updated with model_name
Note: RoomSettingsPanel UI still uses shadcn defaults (未完全迁移到AI Studio)
Backend:
- Add MENTION_TAG_RE matching new `<mention type="..." id="...">label</mention>` format
- Extend extract_mentions() and resolve_mentions() to parse new format (legacy backward-compatible)
Frontend:
- New src/lib/mention-ast.ts: AST types (TextNode, MentionNode, AiActionNode),
parse() and serialize() functions for AST↔HTML conversion
- MentionPopover: load @repository: from project repos, @ai: from room_ai configs
(not room members); output new HTML format with ID instead of label
- MessageMentions: use AST parse() for rendering (falls back to legacy parser)
- ChatInputArea: insertMention now produces `<mention type="user" id="...">label</mention>`
- RoomParticipantsPanel: onMention passes member UUID to insertMention
- RoomContext: add projectRepos and roomAiConfigs for mention data sources
- Fix ReactionGroup.count: i64 -> i32 and users: Vec<Uuid> -> Vec<String>
to match frontend ReactionItem (count: number, users: string[]).
Mismatched types caused the WS reaction update to silently fail.
Also update ReactionItem in api/ws_types.rs to match.
- Add activeRoomIdRef guard in onRoomReactionUpdated to prevent stale
room state from processing outdated events after room switch.
- Switch from prev.map() to targeted findIndex+spread in onRoomReactionUpdated
to avoid unnecessary array recreation.
Frontend:
- P0: Replace constant estimateSize(40px) with content-based estimation
using line count and reply presence for accurate virtual list scroll
- P1: Replace Shadow DOM custom elements with styled spans for @mentions,
eliminating expensive attachShadow calls per mention instance
- P1: Remove per-message ResizeObserver (one per bubble), replace with
static inline toolbar layout to avoid observer overhead
- P2: Fix WS token re-fetch on every room switch by preserving token
across navigation and not clearing activeRoomIdRef on cleanup
Backend:
- P1: Fix reaction check+insert race condition by moving into transaction
instead of separate query + on-conflict insert
- P2: Fix N+1 queries in get_mention_notifications with batch fetch
for users and rooms using IN clauses
- P2: Update room_last_activity in broadcast_stream_chunk to prevent
idle room cleanup during active AI streaming
- P3: Use enum comparison instead of to_string() in room_member_leave
- reaction.rs: query before insert to detect new vs duplicate reactions,
only publish Redis event when a reaction was actually added
- room.rs: delete Redis seq key on room deletion to prevent seq
collision on re-creation
- message.rs: use Redis-atomic next_room_message_seq_internal for
concurrent safety; look up sender display name once for both
mention notifications and response body; add warn log when
should_ai_respond fails instead of silent unwrap_or(false)
- ws_universal.rs: re-check room access permission when re-subscribing
dead streams after error to prevent revoked permissions being bypassed
- RoomChatPanel.tsx: truncate reply preview content to 80 chars
- RoomMessageList.tsx: remove redundant inline style on message row div
Backend:
- Atomic seq assignment via Redis Lua script: INCR + GET run atomically
inside a Lua script, preventing duplicate seqs under concurrent requests.
DB reconciliation only triggers on cross-server handoff (rare path).
- Broadcast channel capacity: 10,000 → 100,000 to prevent message drops
under high-throughput rooms.
Frontend:
- Optimistic sendMessage: adds message to UI immediately (marked
isOptimistic=true) so user sees it instantly. Replaces with
server-confirmed message on success, marks as isOptimisticError on
failure. Fire-and-forget to IndexedDB for persistence.
- Seq-based dedup in onRoomMessage: replaces optimistic message by
matching seq, preventing duplicates when WS arrives before REST confirm.
- Reconnect jitter: replaced deterministic backoff with full jitter
(random within backoff window), preventing thundering herd on server
restart.
- Visual WS status dot in room header: green=connected, amber
(pulsing)=connecting, red=error/disconnected.
- isPending check extended to cover both old 'temp-' prefix and new
isOptimistic flag, showing 'Sending...' / 'Failed' badges.
The frontend WebSocket client sends room_public, but the HTTP fallback
sends it as a JSON body parsed directly into RoomCreateRequest/RoomUpdateRequest
which expects public. Add #[serde(rename = "room_public")] so both
ws params and HTTP JSON body work consistently.