- 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
- Add record_ai_session() helper calling billing::record_ai_usage()
- Replace all Set(None) cost/currency with actual calculated values
- Cost computed from model_pricing via Decimal precision
- 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
- issue_triage.rs: use check_project_access instead of nonexistent get_project_member
- email/lib.rs: make EMAIL_REGEX pub to suppress dead_code warning
- tracing_fmt.rs: minor import ordering cleanup and code formatting
- 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
- Add Offline status to ModelStatus enum
- Sync marks all models offline first, then activates found ones
- Deduplicate by model name (ignoring provider)
- Deactivate orphaned models (offline -> deprecated)
- Add models_offline and models_deactivated to SyncModelsResponse
- Add deduplicate_existing_models() for cleanup
- Rename upsert_model to upsert_model_by_name
- Start SSH rate limiter cleanup task that was missing (prevent memory leak)
- Create single ToolContext outside tool execution loop so max_tool_calls
and max_depth guards actually fire across batch tool calls (was creating
fresh context per call, bypassing all limits)
- 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.
When APP_AI_BASIC_URL already ends with /v1 (e.g. openrouter.ai/api/v1),
appending /v1/models produces /v1/v1/models. Detect trailing /v1 and
only append /models in that case.
Move git_tools, file_tools, and project_tools from libs/service into a
new libs/fctool crate with correct workspace dependencies. Fixes the
rev.len() >= 40 bug in all git tool resolve functions (OID check needs
exact 40-char hex, not just >= 40). Adds 4 new git blob tools
(blob_get, blob_exists, blob_content, blob_create). Fixes parameter
naming inconsistency in repos.rs and adds project_name to list_repos
output. Removes unused excel/pdf/ppt/word file tools.
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.
When stdout is connected to a TTY, use tracing_subscriber's pretty
format with colors instead of single-line JSON. Non-TTY (container
logs, pipes) continue to output JSON for log aggregation.
Override auto-detection via APP_LOG_FORMAT=json|pretty.
Also adds APP_LOG_PRETTY=true to use serde_json::to_string_pretty
for human-readable JSON output (useful for development/debugging).
The upstream AI endpoint already returns complete model metadata:
- name, owned_by, context_length, max_output_tokens
- capabilities (vision, tool_call, reasoning)
- pricing (input, output, cache_read, cache_write, currency)
Remove the OpenRouter fallback entirely and parse the upstream
response directly for all sync operations. Both sync_upstream_models
(API) and sync_once (background task) now use a single unified path.
Changes:
- Remove OpenRouter types and fetch_openrouter_models()
- Add UpstreamModel / UpstreamCapabilities / UpstreamPricing types
- Parse capabilities from upstream instead of inferring from name
- Use real pricing from upstream instead of defaulting to 0.00
- Simplify sync flow: list → parse → upsert (no filtering/matching)
- Add provider normalizations for moonshot, zai, minimax, qwen
The upstream AI endpoint returns an OpenAI-compatible format, but the
response body parsing was fragile. Make it resilient:
1. Try standard OpenAI format: { "data": [{id}, ...] }
2. Try raw array: [{id}, ...]
3. Try alternate format: { "models": [{id}, ...] }
4. Log actual response body (first 500 chars) when all formats fail
Also adds a warning log with the raw response on parse failure so
future debugging is straightforward.
The check_compatibility(false) method was added in the previous commit
but does not exist in sqlx 0.8.x used by sea-orm 2.0. The warning
"Failed to obtain server version" is cosmetic and does not affect
functionality.
When OpenRouter's public /api/v1/models endpoint fails (network error,
timeout, parse failure), the entire sync was aborted — meaning models
accessible from the user's AI endpoint were never synced.
Now: if OpenRouter fetch fails, fall back to sync_models_direct for all
available models instead of returning an error. Both sync_upstream_models
(API) and sync_once (background task) have this fix.
Cloud-managed PostgreSQL variants (PolarDB, CockroachDB, etc.) may
not return a standard version string, causing:
"Failed to obtain server version. Unable to check client-server
compatibility."
Setting check_compatibility(false) on both writer and reader
connections silences this harmless warning.
When upstream /v1/models returns models not yet in OpenRouter's catalog
(e.g. brand-new models like DeepSeek-V4), also upsert them through the
same pipeline (provider → model → version → pricing → capabilities →
parameter_profile) with inferred defaults, instead of silently dropping
them. Previously the direct-sync fallback only triggered when *zero*
OpenRouter matches existed.
Remove daily report system (page, API routes, cron scheduler) as it is
no longer needed. Add /api/metrics endpoint exposing total and time-
windowed counts (27h, 7d, 30d) for users, workspaces, projects, repos,
rooms, and skills.
Also clean up dead code:
- Remove OpenRouter sync and alerts check routes
- Remove syncModels/checkAlerts from adminrpc client
- Remove unused adminRpcAvailable state from platform sessions page
- Fix handleEdit displayName comparison bug in platform users page
- Simplify pricing sync to create 0-price defaults
Add MsgJsonFormat custom event formatter that outputs JSON with _msg as
the first field, required by VictoriaLogs for full-text search. HTTP
middleware stores interpolated "METHOD /path" in thread-local buffer
for the formatter to read on span-close events.
- 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
- StreamChunk/StreamChunkType types for preserving arrival order
- Chunk collection in call_stream_once and process_stream
- Add "error sending request" and "Http client error" to retryable errors
- StreamResult includes chunks vector for ordered replay
CNPG's cluster-ro service already handles load balancing and failover,
so the application-level Vec + random_range is redundant.
- db_read: Vec<DatabaseConnection> → Option<DatabaseConnection>
- database_read_replicas returns Option<String> instead of Vec<String>
- health checks now explicitly ping both writer() and reader()
- remove unused rand dependency from libs/db
- Main sitemap index at /sitemap.xml referencing 4 sub-sitemaps
- /sidemap/static: fixed routes (homepage, auth, marketing pages)
- /sidemap/users: public user profiles sorted alphabetically
- /sidemap/projects: public projects sorted alphabetically
- /sidemap/repos: public repos sorted alphabetically
- Redis cache with 8h TTL (no refresh on access), key: sidemap:{type}
- robots.txt Sitemap URL uses main_domain() with https:// forced
- All sitemap loc entries use https:// base URL