When a user places a voice call from an instance chat page (AgentInstanceChatPage),
the call is now routed through the OpenClaw bridge instead of iAgent's
VoiceSessionManager SDK loop. iAgent voice behavior is completely unchanged.
Changes:
- Flutter: ChatPage accepts agentInstanceId, passes to AgentCallPage
- Flutter: AgentCallPage sends instance_id in livekit/token request body
- Flutter: AgentInstanceChatPage passes instance.id as agentInstanceId
- voice-service: livekit_token.py includes instance_id in dispatch metadata
- voice-agent: agent.py reads instance_id from metadata, passes to LLM
- voice-agent: agent_llm.py stores _instance_id, sends instanceId in voice/start
- agent-service voice/start: stores instanceId in session metadata; skips
VoiceSessionManager for instance mode (iAgent path unchanged)
- agent-service voice/inject: detects instanceId in session metadata and
fire-and-forgets to OpenClaw bridge; callback via openclaw-app-callback
emits text WS events that voice-agent collects for TTS playback
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
chatRepositoryProvider and sendMessageUseCaseProvider were not overridden
in AgentInstanceChatPage's ProviderScope, causing ChatNotifier.sendMessage
to use the parent scope's ChatRemoteDatasource (iAgent endpoint
POST /api/v1/agent/tasks) instead of AgentInstanceChatDatasource
(OpenClaw endpoint POST /api/v1/agent/instances/:id/tasks).
Fix: override both providers in the child scope so the full call chain
(sendMessage → SendMessage → ChatRepositoryImpl → createTask) routes
to the correct instance-specific endpoint.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The floating iAgent chat button was visible on the /my-agents page,
causing users to accidentally open the iAgent chat instead of their
own agent instance's chat. Hide the FAB when the My Agents tab is
active (currentIndex == 1).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter references Play Core split-install APIs that aren't bundled in
standard APK builds — suppress R8 errors with dontwarn.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
R8 fails on missing optional HMS dependencies (HiAnalytics, libcore,
BouncyCastle). Add dontwarn rules and wire proguard-rules.pro to release
build type.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add hide RemoteMessage,Importance to huawei_push import (conflicts with
firebase_messaging and flutter_local_notifications)
- Replace removed HuaweiPush/Event API with Push.getTokenStream/onMessageReceivedStream
- Replace non-existent DioClient() with standalone Dio instance;
init() now accepts optional authToken parameter
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Huawei agcp plugin requires com.android.tools.build:gradle in buildscript
classpath but new Flutter plugin DSL only declares it via settings.gradle.kts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GET /instances returned all tenant instances for admin accounts,
causing cross-user agent visibility. Changed to
GET /instances/user/:userId so each user only sees their own agents.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
After verifying that the OpenClaw gateway's chat.send WebSocket RPC
accepts an 'attachments' array (confirmed from openclaw/openclaw source
and documentation), implement end-to-end image/file attachment support
for instance chat:
Bridge (openclaw-client.ts):
- chatSendAndWait() now accepts optional `attachments[]` parameter
- Passes attachments to chat.send RPC only when non-empty
Bridge (index.ts):
- /task-async accepts `attachments[]` from request body
- Forwards to chatSendAndWait unchanged
Backend (agent.controller.ts):
- executeInstanceTask() accepts IT0 attachment format
{ base64Data, mediaType, fileName? }
- Converts to OpenClaw format { name, mimeType, media: "data:..." }
- Saves attachments to conversation history via contextService
- Forwards to bridge via bridgeAttachments spread
Flutter (agent_instance_chat_remote_datasource.dart):
- createTask() now includes attachments in POST body when present
Flutter (chat_page.dart):
- Reverted Fix 5 (disabled button) — attachment button fully enabled
in instance mode since the bridge now supports it
Attachment format (OpenClaw wire):
{ name: string, mimeType: string, media: "data:<mime>;base64,<data>" }
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix 2 — Callback timeout wiring:
- Store callbackTimer in pendingCallbackTimers Map after creation
- handleOpenClawAppCallback clears the timer immediately on arrival,
preventing spurious "timeout" errors when the bridge replies in time
Fix 3 — Provider scope isolation:
- Override agentStatusProvider and robotStateProvider in child ProviderScope
so the robot avatar/FAB reflects the instance chat state, not iAgent's
Fix 4 — Voice routing to OpenClaw:
- AgentInstanceChatDatasource.sendVoiceMessage() now calls transcribeAudio()
then routes the transcript through instance-specific createTask() endpoint,
ensuring voice messages reach the user's OpenClaw agent, not iAgent
Fix 5 — Attachment UI in instance mode:
- Attachment button shown as disabled (onPressed: null) with explanatory
tooltip ("附件功能暂不支持智能体对话") when agentName != null
- Prevents misleading UX where attachments appear to work but are silently
dropped by the OpenClaw bridge
Fix 6 — DB schema template:
- Add agent_instance_id UUID NULL to agent_sessions table in migration 002
(tenant schema template) so new tenants get the column from creation
- Add covering index idx_agent_sessions_instance for efficient instance queries
All TypeScript and Flutter analyze checks pass clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
JWT payload was missing 'name' field — phone-invited users showed
empty name after app restart (session restore from JWT).
Also added phone fallback in Flutter _decodeUserFromJwt.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Phone-invited users register with phone+password.
Changed identifier field from email-only to email/phone,
removed @ validation so phone numbers pass through.
Backend already auto-detects email vs phone.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The _showOAuthBottomSheet title/subtitle were hardcoded to 钉钉. Now detects
channel from the URL (feishu.cn → 飞书, else → 钉钉) and shows correct text
and button color (#3370FF for Feishu, #1677FF for DingTalk).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter:
- my_agents_page: refresh agent list on every My Agents tab tap
(ref.invalidate in ScaffoldWithNav.onDestinationSelected)
- chat_page + my_agents_page: activate AudioSession before launching OAuth
browser so iOS keeps network connections alive in background; deactivate
when app resumes or binding polling completes
agent-service deploy:
- Write openclaw.json with correct gateway token and auth-profiles.json
with API key BEFORE starting the container, so OpenClaw and bridge
always agree on the auth token (fixes token_mismatch on new deployments)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- DingTalk binding UX replaced with OAuth one-tap flow:
- GET /api/v1/agent/channels/dingtalk/oauth/init returns OAuth URL
- GET /api/v1/agent/channels/dingtalk/oauth/callback (public, no JWT)
exchanges code+state for openId, saves binding, returns HTML page
- oauthStates Map with 10-min TTL; state validated before exchange
- msg.senderId (openId) aligned with OAuth openId for consistent routing
- CODE_TTL_MS extended from 5→15 min (fallback code method preserved)
- Kong: dingtalk-oauth-public service declared before agent-service
so callback path matches without JWT plugin
- Voice sessions: use stored session.systemPrompt + voice rules;
allowedTools includes Bash so Claude can call internal APIs
- Flutter _DingTalkBindSheet: OAuth-first UX with code-based fallback
phases: idle→loadingOAuth→waitingOAuth→success + polling every 2s
- docker-compose: IT0_BASE_URL env var for agent-service (redirect URI)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New agents (shown first in horizontal scroll):
- 日常办公助手 / Office Assistant
- 在线客服智能体 / Customer Service Bot
- 市场营销助手 / Marketing Assistant
- 外语学习助手 / Language Tutor
All 4 agents fully localized in en/zh/zh_TW.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also replace 'OpenClaw' with '小龙虾' in English user-facing strings.
'My Agents' plural (section names) intentionally kept as-is.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- home_page.dart: use l10n for greeting, default username, agent status, message count
- app_en.arb: fix appTitle back to 'iAgent' (was incorrectly changed to 'My Agent')
- Add defaultUserName and agentInConversation keys to en/zh/zh_TW ARBs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ListTile in Material 3 constrains trailing to ~72px, causing long title text
like "Refer & Earn" to be squeezed vertically letter-by-letter. Custom layout
uses Expanded on the title to take all available space, with trailing/chevron
floated to the right — matching how major apps handle settings rows.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Flutter gen-l10n added zh translation comments and reflowed long lines.
No functional changes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add google_fonts ^6.2.1; apply Inter via GoogleFonts.interTextTheme
for both dark and light themes (English/Latin chars use Inter,
CJK chars fall back to system font automatically)
- Add _showLanguagePicker bottom sheet in profile page with 4 options:
Auto (follow system), 简体中文, 繁體中文, English
- Wire language row onTap to open the picker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 27 new l10n keys (ARB + generated dart) for the referral screen,
covering both Enterprise tab and personal circle tab strings
- Replace all hardcoded Chinese strings in referral_screen.dart with
l10n calls (tab labels, section headers, status labels, rules, etc.)
- Fix language auto-detection: default to '' instead of 'en', and
return null from localeProvider to follow device locale
- Fix 'Refer & Earn' vertical text: wrap trailing with Flexible in
_SettingsRow on profile page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Migration 011: 4 new tables (user_referral_codes, user_referral_relationships,
user_point_transactions, user_point_balances)
- Referral service: user-level repositories, use cases, and controller endpoints
(GET /me/user, /me/circle, /me/points; POST /internal/user-register)
- Admin endpoints: user-circles, user-points, user-balances listing
- Auth service: fire-and-forget user referral registration on signup
- Flutter: 2-tab UI (企业推荐 / 我的圈子) with personal code card,
points balance, circle member list, and points history
- Web admin: 2 new tabs (用户圈子 / 用户积分) with transaction ledger and balance leaderboard
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
livekit_client 2.3.1+hotfix.1 removed the `subscribe` parameter from Timeouts,
causing build failure. Pinning to 2.6.4 (which has subscribe) and bumping
device_info_plus to ^12.3.0 as required by livekit_client >=2.6.0.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three settings rows are hidden via `if (false)` — all code is fully preserved
and can be restored by removing the condition:
- 对话引擎: 引擎切换属平台级配置,普通用户无需感知
- 检查更新: 启动时已静默后台检查,无需额外入口
- 语音 I/O 测试: 仅供开发调试,正式版不对用户展示
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add animated robot avatar widget (CustomPainter, 5 states: idle/thinking/executing/speaking/alert)
- Add FloatingRobotFab that mirrors chatProvider AgentStatus as robot animation state
- Replace 5-tab nav (dashboard/chat/tasks/alerts/settings) with 4-tab (home/my-agents/billing/profile)
- Chat is now pushed full-screen from the robot FAB with slide-up transition
- HomePage: active agent status card + official agent horizontal scroll + quick tips
- MyAgentsPage: empty state with 3-step guide + template grid; shows list when agents exist
- ProfilePage: merged settings + prominent billing entry (replaces old SettingsPage as tab)
- ChatPage AppBar: robot avatar replaces plain text title, reflects real-time agent state
- Add agentConfigs endpoint to ApiEndpoints
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- auth-service: add SmsService (Aliyun SMS) + RedisProvider for OTP storage
- POST /api/v1/auth/sms/send — send OTP (rate limited 1/min per phone)
- POST /api/v1/auth/sms/verify — verify OTP only
- POST /api/v1/auth/login/otp — passwordless login with phone + OTP
- register endpoint now requires smsCode when registering with phone
- Web Admin register page: add OTP input + 60s countdown button for phone mode
- Flutter login page: add 验证码登录 tab with phone + OTP flow
- SMS enabled via ALIYUN_ACCESS_KEY_ID/SECRET + SMS_ENABLED=true env vars
- Falls back to mock mode (logs code) when env vars not set
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Whisper detects language from audio content — speaks Chinese gets Chinese,
speaks English gets English. App language setting is irrelevant to STT.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Flutter: language='auto' omits the language field → backend receives none
- Backend: no language field → passes undefined to STT service
- STT service: language=undefined → omits language param from Whisper request
- Whisper auto-detects language per utterance when no hint is provided
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Reads settingsProvider.language (BCP-47 code) and passes it to the
Whisper transcribe call instead of hardcoding 'zh'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>