- Add gitignore and prettier configuration files for project scaffolding
- Implement room access control service with project member verification
- Create user access key management with CRUD operations and activity logging
- Add accordion UI component for frontend expandable sections
- Implement room AI configuration with list, upsert, and delete operations
- Add AI event types for agent join/leave/status change tracking
- Create streaming AI processing services for mode and react patterns
- Build room AI service with model detection and idempotency handling
- Integrate chat service orchestration for AI message processing
- Add typing indicators and stream cancellation for AI interactions
- Implement mention parsing and context extraction for AI agents
Add use-ai-streaming hook for SSE-based AI response streaming,
use-room-messages for real-time message updates, and wire up
room context + ws protocol changes.
Backend:
- New GET /api/agents/models/catalog endpoint with page/per_page/search
params, excludes deprecated models, returns pricing data via
model→version→pricing join
- ModelWithPricingResponse includes input_price, output_price, currency
- ModelListResponse with pagination metadata (total, page, per_page)
- Batch-fetches default versions + latest pricing to avoid N+1
Frontend:
- RoomSettingsPanel: replace Dialog with inline two-step panel
- Step 1: paginated model browser with search, shows context length,
max output tokens, pricing per 1K tokens, capability/modality badges
- Step 2: selected model info card + AI configuration form
- Removed Dialog import and related unused dependencies
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
- Implement SSHandle struct with comprehensive Git service handling capabilities
- Add support for multiple authentication methods including password, public key and certificate
- Integrate Git command parsing and execution with proper channel management
- Implement branch protection rules enforcement during Git operations
- Add robust error handling and logging for SSH connections and Git processes
- Create secure Git command execution with environment isolation
- Implement proper resource cleanup for channels and subprocesses
- Add support for receive-pack, upload-pack and upload-archive services
- Integrate with existing authz and database services for permission checks
- Implement proper data forwarding between SSH channels and Git processes
fix(config): improve environment loading with error reporting
- Replace silent dotenv loading failures with informative error messages
- Handle global config race conditions safely during application startup
- Improve config loading reliability and debugging capabilities
fix(link-unfurl): handle server-side rendering compatibility
- Add undefined window object check for SSR environments
- Prevent client-side only code from breaking server-side rendering
refactor(agent): improve tool registry error handling
- Replace panics with graceful error logging for duplicate tool registrations
- Add proper error type definitions for tool registry operations
- Implement safe merging of registries with duplicate detection
fix(room-context): enhance WebSocket connection reliability
- Add proper error handling for room subscription operations
- Improve connection management with better error suppression
- Add console warnings for debugging connection issues
feat(ws-client): add comprehensive WebSocket client implementation
- Create RoomWsClient class with complete WebSocket communication layer
- Implement request-response pattern with timeout handling
- Add support for various room-related events and actions
- Include proper connection status tracking and management
- Implement callback system for different event types
- Add automatic reconnection and error recovery mechanisms
Update changelog with new features: semantic search for messages and tags,
room-scoped search, and incremental tag indexing. All 4 languages (en/cn/de/fr).
Add room parameter (UUID) and pn (project name) to search/messages API.
Service layer supports filtering messages by room and project scope.
Frontend search page updated with room-scoped search support.
- atob() returns Latin-1 binary string, not UTF-8
- Chinese characters decoded incorrectly causing mojibake
- Use TextDecoder("utf-8") with Uint8Array for correct handling
- Clear activeAiStream, streamingChunks, and timers when room changes
- Add showCursor prop to OrderedStreamChunks — only show cursor
during active streaming, not for saved content
Create changelog page with language tabs (EN/CN/DE/FR),
timeline layout, and MDX rendering. Add generate script
to build changelog data from mdx files.
- commits.tsx: fix Unix timestamp (time_secs) not multiplied by 1000
- RoomMessageSearch.tsx: add explicit generic type for resp.data
- RoomPinPanel.tsx: make sender_id optional (string | null)
- message-list.tsx: remove unused index variable in map
- repository-context.tsx: use correct RepoInfo field names
- use-audio-recording.ts: use audioStream state instead of undefined var
- universal-ws.ts: rename unused id to _id
- 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)
Clear wsToken on auth-related close codes (3000-4999), connection
timeout, and after 3 consecutive reconnect failures so the next connect
attempt fetches a fresh token. Add onRoomAiUpdated and onRepoChanged
callbacks that re-fetch AI configs and repo list when pushed via WS.
Fix AI member list to never display raw UUID.
Add 'repo' to MentionType across all editor types, include repos in the
@ trigger pool, add repo badge (green chip), Repos section in the
mention dropdown, and MentionBadge styles. Wire projectRepos from
room context into IMEditor mentionItems.
- TypingUsers state split by sender_type: AI vs human typing
- AI typing shows "{Name} is thinking..." with accent color
- Human typing shows "{Name} is typing..." with muted style
- AI typing relies on backend 60s TTL stop (no client-side 4s fallback)
- Add Page Visibility API to reconnect WS on tab become visible
- Add debug logs for typing flow tracing
- Pass sender_type through WS room.typing event routing
- MessageInput: ignore empty text in handleEditorUpdate to avoid
TipTap's onUpdate("") on init clearing the typing state
- DiscordChatPanel: show typing indicator when other users are typing
- room-context: wire onTypingStart/Stop into ws callbacks
CommandPalette: replace workspaceProjects with getCurrentUserProjects
(no workspace dependency so it works outside WorkspaceProvider).
Repos fetched per-project to preserve correct /repository/ns/repo
routes. Keyboard shortcut correctly matches Ctrl+Alt+F / Cmd+Ctrl+F.
sidebar-user: fix notification button layout — bell icon and label
now on the same row instead of separate stacked elements.
- Remove duplicate smooth scroll effect from DiscordChatPanel; handle
all scroll logic in MessageList instead
- MessageList: track isInitialLoadRef to instant-jump to bottom on
first load (no animation), and only auto-scroll for new messages
when user is already near the bottom
- sender.ts: getSenderDisplayName rejects UUID values and falls back
to 'AI' for AI messages; getSenderModelId uses display_name
- DiscordChatPanel: typing indicator with animated dots and user names
- MessageActions: quick emoji bar (👍❤️😂🎉😮) on hover
- MessageList: group consecutive messages from same sender within 5min
- MessageInput/IMEditor: @here/@channel special mention suggestions
- DiscordChannelSidebar: useDroppable on category headers for drag-drop,
empty categories now render, rooms/categories loaded via REST API
- room-context: typingUsers state, REST roomList/categoryList, category
merge into rooms
New endpoints: GET /api/users/{username}/activity, GET /api/users/{username}/stars,
GET /api/users/{username}/following. Updated types: UserActivityItem, UserActivityResponse,
UserStarsResponse, RepoStarItem, ProjectFollowItem, UserCard.
- room-context: dedup by id not seq (streaming seq=0); single atomic
setStreamingContent with delta detection; preserve reactions from WS
- MessageBubble: fix avatar lookup (members before IIFE); handleReaction
deps (no message.reactions); add reactions to wsMessageToUiMessage
- MessageInput: memoize mentionItems; fix upload path with VITE_API_BASE_URL
- IMEditor: warn on upload failure instead of silent swallow
- RoomSettingsPanel: sync form on room switch; loadModels before useEffect
- DiscordChatPanel: extract inline callbacks to useCallback stable refs
- Fix DraggableRow: remove opacity-0 wrapper that was hiding
RoomButton; drag handles now work without blocking clicks
- Move "+ Add Category" to bottom of channel list
- Reduce channel item padding (6px → 4px) for compact look
- Shrink # hash icon to 12px
- Add explicit text-align: left to channel names
- Reduce Add Category button/input to 10px font
- Rewrite DiscordChannelSidebar with @dnd-kit drag-and-drop:
rooms are sortable within categories; dragging onto a different
category header assigns the room to that category
- Add inline 'Add Category' button: Enter/Esc to confirm/cancel
- Wire category create/move handlers in room.tsx via RoomContext
- Fix onAiStreamChunk to accumulate content properly and avoid
redundant re-renders during AI streaming (dedup guard)
- No backend changes needed: category CRUD and room category update
endpoints were already wired
Add the second half of the password reset flow: /password/confirm API
endpoint with token validation, transactional password update, and a
frontend confirm-password-reset-page with proper error handling for
expired/used tokens. Updates generated SDK/client bindings.
- Add showSearch state and RoomMessageSearch panel (420px slide-in)
- Search button now toggles panel and highlights when active
- Clicking a search result scrolls to the message and closes panel
- Add msg-{id} anchors on message containers for scroll-into-view
- Refactor room-context.tsx with improved WebSocket state management
- Enhance room-ws-client.ts with reconnect logic and message handling
- Update Discord layout components with message editor improvements
- Add WebSocket universal endpoint support in ws_universal.rs
- Add /invitations route with dedicated page and sidebar button
- Display both project invitations (accept/decline) and workspace invitations (accept only)
- Merge and sort both invitation types by most recent first
- Export new SDK functions: workspaceMyInvitations, workspaceAcceptInvitationBySlug
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)
Root cause: when MentionInput updates innerHTML to reflect value changes,
the browser resets the selection to position 0. The selectionchange event
handler reads this wrong cursor position and sets ms.cursorOffset=0,
breaking mentionState calculation for the popover.
Fix:
- MentionInput sets window.__mentionBlockSelection=true before innerHTML
- Clears it via requestAnimationFrame after caret is restored
- selectionchange handler skips cursor reading when flag is set
The per-render tracking effect was reading DOM cursor position before
the browser had finished positioning the caret after innerHTML updates,
causing ms.cursorOffset to be set to the old position and overwriting
the deferred correct value.
Switch to document.selectionchange event which:
- Fires only when the user actually moves the cursor (arrow keys, mouse clicks)
- Does NOT fire during programmatic DOM updates
- Uses focusNode/focusOffset directly from the selection API
This completely eliminates the timing race between render effects and
programmatic cursor updates.
Root cause: after onCategoryEnter sets value="@ai:", the MentionInput sync
effect sets innerHTML with the new content. The browser caret may be at
position 2 (not moved from the user's perspective), but prevCursorRef was
4. The tracking effect read DOM caret=2 and overwrote ms.setCursorOffset(4)
→ cursor jumped to position 2.
Fix: skip tracking effect update when:
- count (DOM caret) is at end of text (ms.value.length), AND
- prevCursorRef was also at end of text
This means the caret hasn't actually moved from the user's POV (just
positioned by the browser after innerHTML), so don't update state.
Root cause: when onCategoryEnter scheduled a deferred setCursorOffset,
the tracking effect ran BEFORE setTimeout and read the OLD DOM cursor
position (still "@a" → position 2), overwriting the new cursor position
of 4 in a subsequent render.
Fix: add skipCursorTrackingRef flag. Set it before setTimeout fires,
clear it when setTimeout executes. The tracking effect checks the flag
and skips its update during the flush window.
Same fix applied to insertCategory imperative handle.
When @category is selected, ms.setCursorOffset and ms.setValue were called
in the same event loop tick — the cursor sync effect later read the DOM
before the new mention value flushed, restoring the old caret position.
Defer ms.setCursorOffset via setTimeout so it fires after the DOM
reconciliation. Same fix applied to insertCategory imperative handle.
The root cause: handleMentionSelect set draft to mention HTML, but the
subsequent MentionInput.onInput event fired with plain text (after the
programmatic DOM insert) and overwrote the draft.
Solution: replace pendingSyncRef trick with a clean isUserInputRef flag.
- useEffect: if getPlainText(el) === value, DOM already correct (skip).
Mark isUserInputRef = false so next useEffect skips caret restore.
- handleInput: always set isUserInputRef = true before calling onChange.
This eliminates the pendingSyncRef/__mentionSkipSync global flag mess
entirely.
- handleSelectRef.current() was called without argument → suggestion was
undefined → suggestion.type threw. Now passes the selected item.
- key prop moved to outer map wrapper div so React can diff the list
correctly. Inner SuggestionItem/CategoryHeader no longer need keys.
useMemo received deps as trailing comma-separated args instead of array.
Replace all render-body reads of mentionStateRef.current with reactive
mentionState from useMemo. Keep ref for event listener closures only.
ShouldBelow was computed but ignored — popover always rendered below the
cursor. Now uses shouldBelow correctly to render above when insufficient
space below. Also removes unused cRect clone measurement.
Effect watches mentionState and toggles showMentionPopover accordingly.
Without this, ms.setShowMentionPopover was never called in onChange,
so the popover never appeared.
- Fix undefined setDraftAndNotify (replaced with onDraftChangeRef + ms.setValue)
- Add DOM→ms.cursorOffset tracking on every render via TreeWalker
- Fix mention insertion caret placement: skip MentionInput sync-effect caret
restoration when parent sets value (via __mentionSkipSync window flag)
- Remove unused _draftTick state
Bug #1: setDraftAndNotify referenced but never defined → runtime crash
Bug #2: ms.cursorOffset never updated from DOM → popover always at wrong position
Bug #3: mention insertion saved pre-insertion caret, restored on longer HTML → cursor inside mention tag
Bug #4: _draftTick declared but state value never read → dead code
Replace module-level refs and circular dependencies with a centralized
mention state hook (useMentionState) and shared types (mention-types).
- Introduce mention-types.ts for shared types (avoid circular deps)
- Introduce mention-state.ts with useMentionState hook (replace global refs)
- Rewrite MentionInput as contenteditable with AST-based mention rendering
- Rewrite MentionPopover to use props-only communication (no module refs)
- Fix MessageMentions import cycle (mention-types instead of MentionPopover)
- Wire ChatInputArea to useMentionState with proper cursor handling
- MentionInput: use forwarded ref, internal state ref, pendingSyncRef
flag to prevent value↔DOM sync cycles; getContentText walks both
text nodes and non-editable mention spans to preserve mention length
- RoomChatPanel: replace cursorRef with getCaretOffset()/setCaretOffset()
helpers, remove stale cursorRef.current references throughout
- MentionPopover: update textareaRef type to HTMLDivElement,
handleSelect uses TreeWalker to read from contenteditable div,
positioning effect always uses TreeWalker for text measurement
AI mentions now render as "🤖 AI: name" buttons instead of plain text.
Clicking a 🤖 button inserts the @ai:mention into the message input at
the cursor position, enabling users to summon that AI model into the conversation.
Implementation:
- MessageMentions renders AI mentions as styled buttons with 🤖 icon
- Click dispatches 'mention-click' CustomEvent on document
- ChatInputArea listens and inserts the mention HTML at cursor
Previously availableModels was fetched in parallel with roomAiConfigs, so
the first call to fetchRoomAiConfigs had empty availableModels and all AI
configs showed model IDs instead of names.
Now fetchRoomAiConfigs loads the model list itself when called, guaranteeing
that model names are always available.
Problem: dispatchEvent('input') doesn't trigger React's onChange in React 17+.
Solution: pass onCategoryEnter callback from ChatInputArea to MentionPopover.
When Enter is pressed on a category, MentionPopover calls onCategoryEnter(category)
which directly calls React setState (onDraftChange, setCursorPosition,
setShowMentionPopover) inside ChatInputArea, properly triggering re-render.
When a category (Repository/User/AI) is selected and Enter is pressed,
append ':' to the textarea value to trigger the next-level item list.
E.g. '@ai' + Enter → '@ai:' → shows AI model items.
Previous approach used a native addEventListener in MentionPopover to handle
Enter, but it wasn't firing reliably due to complex event ordering.
New approach: ChatInputArea.handleKeyDown detects @ mention directly from
the DOM (not React state), reads the selected item from module-level
mentionVisibleRef/mentionSelectedIdxRef, and performs the insertion directly.
This completely bypasses the native listener timing issues.
Root cause: MentionPopover's native keydown listener fires before React
state updates, so handleSelect read stale inputValue/cursorPosition props
and silently returned early.
Fix: handleSelect now reads textarea.value/selectionStart directly from
the DOM, matching the approach already used in ChatInputArea's
handleMentionSelect. No more stale closure.
Problem: showMentionPopover state is stale when handleKeyDown fires
immediately after handleChange (both in same event loop), causing Enter
to be silently swallowed.
Solution:
- Read textarea.value directly in handleKeyDown to detect @ mentions
- Module-level refs (mentionSelectedIdxRef, mentionVisibleRef) share
selection state between MentionPopover and ChatInputArea
- handleMentionSelect reads DOM instead of relying on props state
ChatInputArea is defined before RoomChatPanel in the file, so its JSX
runs before mentionConfirmRef is declared inside RoomChatPanel. Moving the
ref to module level ensures it's initialized before either component renders.