Commit Graph

401 Commits

Author SHA1 Message Date
hailin c10a54116d fix(compose): pass OPENCLAW_LLM_GATEWAY_KEY/URL to agent-service container
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:47:34 -07:00
hailin 9e56b489c4 docs(env): add OPENCLAW_LLM_GATEWAY_KEY/URL to .env.example
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 15:38:21 -07:00
hailin 54ed8290b1 fix(deploy): patch models.generated.js for LLM gateway + fix AGENTS.md template symlink
After container starts, sed-replace api.anthropic.com with iConsulting LLM gateway URL
in all models.generated.js files (ANTHROPIC_BASE_URL env alone is not enough since
baseUrl is hardcoded). Also create missing AGENTS.md template symlink so OpenClaw
does not 500 on workspace init.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 15:25:52 -07:00
hailin 50401660ef fix(dingtalk): exclude removed instances from routing + clear binding on remove
Two bugs fixed:
1. findByDingTalkUserId now filters status != 'removed' so a re-bound new instance
   is not shadowed by an old removed one with the same DingTalk user ID.
2. When an agent is deleted (removed), its dingtalkUserId is cleared so the
   DingTalk ID is freed for reuse by the next binding.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 14:49:00 -07:00
hailin a8b5571aea fix(deploy): pre-create workspace dir with correct ownership for OpenClaw containers
OpenClaw runs as node user (uid 1000) but the host directory was created as root,
causing EACCES when the container tried to create /home/node/.openclaw/workspace.

Now mkdir workspace/ and chown -R 1000:1000 before starting the container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 14:32:50 -07:00
hailin 495407d25b fix(agent): pass sessionId to system prompt for text chat OAuth trigger
Text sessions were not passing sessionId to SystemPromptBuilder, causing
Claude to use the `initiate_dingtalk_binding` custom tool (claude_api only).
When the engine is claude_agent_sdk, this tool does not exist → 404.

Fix: pass session.id as sessionId to systemPromptBuilder.build() in
agent.controller.ts. Claude will now use the wget oauth-trigger endpoint
for ALL session types (text and voice), which works with every engine.

Also: store userId (staffId) as the DingTalk binding ID when resolvable,
falling back to openId. Bot messages deliver senderStaffId which matches
userId, not openId — this prevents the "binding not found" routing failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 14:20:58 -07:00
hailin 3ca3982c28 fix(dingtalk): correct greeting flow — use userId (staffId) for batchSend
Problem: sendGreeting() was passing openId as `userIds` to batchSend, but
the API requires the enterprise staffId (userId). This caused HTTP 400
"staffId.notExisted" for every OAuth-bound greeting.

Fix:
1. completeOAuthBinding now resolves unionId → userId via
   oapi.dingtalk.com/topapi/user/getbyunionid with corp app token.
   Non-fatal: if the user has no enterprise context, greeting is skipped
   with a clear log explaining why (no Contact.User.Read permission or
   user is not an enterprise member).

2. sendGreeting accepts userId (staffId) and openId separately; uses
   the correct staffId for batchSend. If userId is undefined, emits a
   WARN and skips (user gets greeting on first message instead).

3. routeToAgent now tries senderStaffId as fallback if senderId lookup
   misses — handles edge cases where DingTalk delivers staffId in senderId.

4. Added detailed logging: all three IDs (openId, unionId, userId) are
   logged at binding time so future issues are immediately diagnosable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 13:53:08 -07:00
hailin 13f2d68754 feat(ux): agent list refresh + OAuth keep-alive + deploy token fix
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>
2026-03-08 13:26:05 -07:00
hailin 5cf72c4780 feat(dingtalk+bridge): event-based agent reply + greeting on binding
openclaw-bridge:
- index.ts: /task endpoint now calls chatSendAndWait() with idempotencyKey
  (removes broken timeoutSeconds param; uses caller-supplied msgId for dedup)
- openclaw-client.ts: added onEvent() subscription + chatSendAndWait() that
  subscribes to 'chat' WS events, waits for state='final' matching runId,
  and extracts text from the message payload

dingtalk-router:
- After OAuth binding completes, sends a proactive greeting to the user via
  DingTalk batchSend API (/v1.0/robot/oToMessages/batchSend) introducing the
  agent by name and explaining what it can do

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 13:18:52 -07:00
hailin aca4dd0177 feat(openclaw-bridge): patch models.generated.js to route Anthropic calls via LLM gateway
Replace hardcoded https://api.anthropic.com with http://154.84.135.121:3008 in
@mariozechner/pi-ai's models.generated.js at image build time. Uses find+xargs
to be version-agnostic. The gateway key sk-gw-oc-881239445c76b8d349a13be9fc4507a3
is configured in auth-profiles.json on the mounted volume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 13:15:29 -07:00
hailin 54e6f13405 fix(dingtalk): bridge 请求参数错误导致消息无法转发给小龙虾
问题:routeToAgent 调用 OpenClaw bridge /task 时传了 timeoutSeconds
(bridge schema 不认识),且缺少必须字段 idempotencyKey,导致 bridge
返回 INVALID_REQUEST,机器人沉默不回复。

修复:
- 移除 timeoutSeconds(不是 bridge API 参数)
- 改用 msg.msgId 作为 idempotencyKey(每条消息唯一,满足 bridge 要求)

根因定位:
  docker logs openclaw-83cc9ac3 显示
  "invalid chat.send params: must have required property 'idempotencyKey';
   at root: unexpected property 'timeoutSeconds'"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 11:53:52 -07:00
hailin 64499a5d86 feat(dingtalk): 小龙虾招募全语音/文字引导流程 + OAuth 一键授权卡片
## 功能说明
用户通过语音或文字说「帮我招募一只小龙虾」,iAgent 全程引导完成
OpenClaw 实例创建 + 钉钉 OAuth 一键授权绑定。

## 核心设计
- 语音场景 (claude_agent_sdk): Claude 通过 Bash/wget 调用内部 HTTP
  端点触发 OAuth,绕开 ToolExecutor 限制,两引擎均兼容
- 文字场景 (claude_api): 使用 initiate_dingtalk_binding 自定义工具,
  通过 uiEvent 机制传递 OAuth URL

## agent-service 变更
- agent-engine.port.ts: EngineStreamEvent 联合类型新增 oauth_prompt
- allowed-tools-resolver.service.ts: initiate_dingtalk_binding 加入
  ALL_SDK_TOOLS / admin / operator 工具白名单
- tool-executor.ts: 新增 executeInitiateDingTalkBinding(),调用内部
  oauth/init 端点获取 OAuth URL,返回 uiEvent
- claude-api-engine.ts: 在 tool_result 之后检查 result.uiEvent 并
  yield 出去;buildToolDefinitions 注册 initiate_dingtalk_binding schema
- system-prompt-builder.ts:
  - SystemPromptContext 新增 sessionId? 字段
  - 语音 session (sessionId 存在) → Step 3 使用 wget 调用
    POST /sessions/{sessionId}/dingtalk/oauth-trigger(两引擎通用)
  - 文字 session (无 sessionId) → Step 3 调用 initiate_dingtalk_binding
    工具(claude_api 专用)
- voice-session.controller.ts:
  - 注入 AgentStreamGateway / DingTalkRouterService / AgentInstanceRepository
  - startVoiceSession: 提前确定 sessionId,在 build() 前传入,使系统
    提示能内嵌正确的端点 URL
  - 新增 POST :sessionId/dingtalk/oauth-trigger — 无 JWT(内部端点,
    由 Claude Bash 工具调用),sessionId 作为能力令牌;生成 OAuth URL
    并通过 gateway.emitStreamEvent 直接推送 oauth_prompt 事件到 WS 流

## voice-agent 变更
- agent.py: 构造 AgentServiceLLM 时传入 room=ctx.room
- agent_llm.py:
  - __init__ 增加 room 参数,存储为 self._room
  - 新增 _publish_oauth_prompt(evt_data): null-safe,通过 LiveKit
    publish_data(topic="oauth_prompt") 推送到 Flutter
  - _do_inject_voice / _do_inject / _do_stream_voice / _do_stream:
    处理 oauth_prompt 事件 → asyncio.create_task(_publish_oauth_prompt)
  - 替换已弃用的 asyncio.ensure_future / get_event_loop().create_task
    → asyncio.create_task(Python 3.10+ 兼容)

## Flutter 变更
- agent_call_page.dart: DataReceivedEvent 监听 topic="oauth_prompt",
  解析 url/instanceName,弹出 _showOAuthBottomSheet(深色主题,🦞
  图标,「立即授权」按钮 launchUrl externalApplication)
- stream_event.dart: 新增 OAuthPromptEvent(url, instanceId, instanceName)
- stream_event_model.dart: toEntity() 新增 'oauth_prompt' case
- chat_message.dart: MessageType 枚举新增 oauthPrompt
- chat_providers.dart: _handleStreamEvent 新增 OAuthPromptEvent case,
  生成 type=oauthPrompt 的 ChatMessage(metadata 含 url/instanceName)
- chat_page.dart: 新增 oauthPrompt 时间线节点 + _OAuthPromptCard 组件
  (「立即授权」按钮,launchUrl externalApplication);import url_launcher

## 修复的关键 Bug
1. [严重] initiate_dingtalk_binding 只对 claude_api 有效,语音默认用
   claude_agent_sdk → 新 wget 端点两引擎均可用
2. [严重] 文字聊天页面不处理 oauth_prompt 事件(静默丢弃)→ 补全
   Flutter 4 处代码(entity/model/provider/page)
3. [中]   _publish_oauth_prompt 缺 local_participant null 检查 → 已修复
4. [轻]   asyncio.ensure_future / get_event_loop() 弃用警告 → 已修复

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 11:22:06 -07:00
hailin 3d626aebb5 feat(dingtalk): OAuth one-tap binding + voice tool + public Kong route
- 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>
2026-03-08 09:09:00 -07:00
hailin 2d0bdbd27f feat(agent): voice-triggered DingTalk binding + GET instances by user
- Add GET /api/v1/agent/instances/user/:userId endpoint so Claude can
  look up the caller's agent instances without knowing the ID upfront
- Update SystemPromptBuilder DingTalk section with centralized binding
  flow (one-time code via iAgent DingTalk bot, no per-instance creds)
- VoiceSessionController.startVoiceSession now extracts userId from JWT
  and builds a full iAgent system prompt (userId + DingTalk instructions)
  so Claude knows who is speaking and how to call the binding API
- VoiceSessionManager.executeTurn now uses the session's stored system
  prompt (base context + voice rules) and allows the Bash tool so Claude
  can call internal APIs via wget during voice conversations

User flow: speak "帮我绑定钉钉" → Claude lists instances → generates
code via POST /api/v1/agent/channels/dingtalk/bind/:id → speaks code
letter-by-letter → user sends code in DingTalk → binding completes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 08:49:13 -07:00
hailin db0e1f1439 fix(dingtalk): robustness pass — 5 bugs fixed, stability 10/10
Critical fixes:
- ws.on('message') fully wrapped in try/catch — uncaught exception in
  wsSend() no longer propagates to EventEmitter boundary and crashes process
- wsSend() helper: checks readyState === OPEN before send(), never throws
- Stale-WS guard: close/message events from old WS ignored after reconnect
  (ws !== this.ws check); terminateCurrentWs() closes old WS before new one
- Queue tail: .catch(() => {}) appended to guarantee promise always resolves,
  preventing permanently dead queue tail from silently dropping future tasks
- DISCONNECT frame handler: force-close + reconnect immediately

High fixes:
- sessionWebhookExpiredTime unit auto-detection: values < 1e11 treated as
  seconds (×1000), values >= 1e11 treated as ms — prevents always-blocked reply
- httpsPost response capped at 256 KB to prevent memory spike on bad response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 08:22:08 -07:00
hailin 8751c85881 feat(dingtalk): unified DingTalk bot router with binding flow
- Add DingTalkRouterService: maintains single DingTalk Stream WS
  connection, handles binding codes, routes messages to agent containers
- Add AgentChannelController: POST bind/:id, GET status/:id, POST unbind/:id
- Add findByDingTalkUserId() to AgentInstanceRepository
- Add dingTalkUserId field to AgentInstance entity + migration 011
- Register DingTalkRouterService + AgentChannelController in AgentModule
- Add IT0_DINGTALK_CLIENT_ID/SECRET env vars to docker-compose.yml
- Flutter: DingTalk bind UI in _InstanceCard (bottom sheet with code
  display, countdown, auto-poll, open DingTalk deep link, bound badge)

Robustness improvements in DingTalkRouterService:
  - Concurrent connect guard (connecting flag)
  - Periodic cleanup timer for dedup/rateWindows/bindingCodes maps
  - Non-text message graceful reply
  - Empty senderStaffId guard
  - serverHost null guard before bridge call
  - unref() cleanup timers from event loop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 08:12:27 -07:00
hailin 20e96f31cd fix(openclaw-bridge): use regex replace for base64url (ES2020 compat)
replaceAll() requires ES2021+, tsconfig targets ES2020.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:40:56 -07:00
hailin a7c5b264fa fix(openclaw-bridge): implement correct v3 device auth protocol
- device.id = SHA256(rawPubKey, hex) — not a random UUID
- device.publicKey = raw 32-byte key encoded as base64url (not SPKI DER)
- sign the full v3 payload string (not just raw nonce bytes):
  "v3|{deviceId}|{clientId}|{mode}|{role}|{scopes}|{ts}|{token}|{nonce}|{platform}|"
- device.signature encoded as base64url

Matches buildDeviceAuthPayloadV3/verifyDeviceSignature from openclaw dist.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:40:15 -07:00
hailin bb4b73f847 fix(openclaw-bridge): include nonce in device handshake params
The gateway expects device.nonce (the challenge nonce) to be echoed
back in the connect request. Without it the connection is rejected with
'device: must have required property nonce'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:32:29 -07:00
hailin 00a944c9a9 fix(openclaw-bridge): use 'gateway-client'/'backend' as WS client id/mode
openclaw gateway validates client.id against a fixed set of known IDs.
Using a random UUID caused the connection to be rejected immediately with
'client/id must be equal to constant'. Use 'gateway-client' + 'backend'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:30:06 -07:00
hailin d13b9b7df9 fix(openclaw-bridge): add --allow-unconfigured to gateway command
openclaw gateway requires either a config file or --allow-unconfigured
flag to start without prior setup. Without it the process exits with
'Missing config' and enters a restart loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:25:39 -07:00
hailin 72825d5526 fix(openclaw-bridge): add 'gateway --port 18789' subcommand to openclaw supervisord entry
Without a subcommand the CLI prints help and exits (status 1), causing
supervisord to restart it in a loop and the OC gateway to never start.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 07:07:39 -07:00
hailin a47e894d4b fix(bridge): upgrade Node from 20 to 22 — openclaw requires >=22.12.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 06:23:46 -07:00
hailin 805cd849fb fix(bridge): use correct openclaw entry point dist/index.js
openclaw package.json main=dist/index.js, not dist/openclaw.mjs.
The bin wrapper file is not copied into the Docker image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 06:21:56 -07:00
hailin 09e50154d3 fix(deploy): pass AGENT_SERVICE_PUBLIC_URL into agent-service container
Required so agent-service can inject IT0_AGENT_SERVICE_URL into openclaw
containers when deploying agent instances to pool servers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 06:17:15 -07:00
hailin c3b31ebf47 fix(bridge): remove environment= from supervisord programs, inherit from container
supervisord %(ENV_VAR)s expansion fails hard if the variable is absent.
For optional vars like DINGTALK_CLIENT_ID this crashes the entire supervisor.
Fix: remove all per-program environment= directives. All vars are injected
via docker run -e and supervisord child processes inherit them automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 06:12:52 -07:00
hailin 5ec6f113cd feat(agent): DingTalk channel binding support in instance controller + system prompt
- agent-instance.controller.ts: accept dingTalkClientId/dingTalkClientSecret
  in POST /instances body, forward to deploy service
- system-prompt-builder.ts: add DingTalk 5-step binding guide for iAgent
  so the AI can walk users through connecting their DingTalk account

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 05:12:13 -07:00
hailin b0801e0983 feat(bridge): DingTalk channel plugin + OpenClaw Protocol v3 rewrite
Core changes:
- src/channels/dingtalk.ts: DingTalk Stream SDK channel (no public IP needed)
  - TokenManager: auto-refresh with refreshPromise mutex (prevents race condition)
  - UserQueue: per-user serial queue, max depth 5
  - MsgDedup: O(1) Map<string,timestamp> with 10min TTL + 10k cap
  - RateLimiter: sliding window 10 msg/min per user
  - ResilientOcClient: 10s heartbeat poll + atomic reconnect guard
  - DingTalkStream: exponential backoff reconnect (2s→60s), immediate ACK
  - replyToUser: sessionWebhook expiry check + 4800-char chunking

- src/openclaw-client.ts: rewritten for correct Protocol v3 wire format
  - Request frame: { type:"req", id, method, params }
  - Challenge-response Ed25519 handshake (connect.challenge → connect req)
  - Correct rpc() with configurable timeoutMs

- src/index.ts: fixed RPC method names
  - agent.run → chat.send with { sessionKey, message, timeoutSeconds }
  - metrics.get → gateway.status

- Dockerfile: adds start-dingtalk.sh COPY + chmod
- supervisord.conf: dingtalk-channel program block (autorestart=unexpected)
- start-dingtalk.sh: exits 0 if DINGTALK_CLIENT_ID unset (no restart loop)
- CHANNEL_DEV_GUIDE.md: full dev guide for future channel integrations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 05:10:01 -07:00
hailin 688219ab74 fix(bridge): remove user= from supervisord.conf to fix non-root startup
Container runs as 'node' user (USER node in Dockerfile). Setting user=root
in [supervisord] causes "Can't drop privilege as nonroot user" error.
Remove all user= directives — user is managed at the Docker/container level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 05:07:25 -07:00
hailin 90f11fc572 fix(agent): pass IT0_AGENT_SERVICE_URL env var to openclaw container
supervisord uses %(ENV_IT0_AGENT_SERVICE_URL)s expansion which fails
if the var is not present, crashing the entire supervisor process.
Add AGENT_SERVICE_PUBLIC_URL config and inject it via docker run -e.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 05:01:55 -07:00
hailin 3790284bc9 fix(agent): use wget instead of curl for internal API calls (curl not in container)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 03:29:53 -07:00
hailin b8979d521e fix(agent): AgentInstanceRepository use DataSource directly, not TenantAwareRepository
agent_instances is in public schema — no tenant context needed.
Fixes 'Tenant context not initialized' when iAgent calls internal API via Bash.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 03:19:01 -07:00
hailin 5c5c365736 chore(agent): add empty prisma dir to fix Docker build COPY step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 03:10:19 -07:00
hailin 49ad47cf59 fix(agent): non-null assertion for serverHost/sshUser in deployToUserServer
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 03:07:42 -07:00
hailin b87cebf465 feat(agent): inject userId into system prompt + fix agent-instance nullable columns
- SystemPromptBuilder: add userId/userEmail to context, expose internal API curl commands for OpenClaw creation
- agent.controller.ts: extract userId from JWT, build system prompt via SystemPromptBuilder so iAgent knows current user
- agent.module.ts: register SystemPromptBuilder as provider
- agent-instance.entity.ts: make serverHost/sshUser nullable (pool mode doesn't set these upfront)
- DB: ALTER TABLE agent_instances DROP NOT NULL on server_host/ssh_user

Now iAgent can create 小龙虾 instances autonomously when user asks in natural language.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 03:05:15 -07:00
hailin 49e48d7b3e feat(flutter): add 4 general-purpose official agents to home page
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>
2026-03-08 01:54:29 -08:00
hailin b666bed740 fix: show OpenClaw in non-Chinese, 小龙虾 only in zh/zh_TW
Flutter app:
- app_zh.arb: OpenClaw → 小龙虾
- app_zh_TW.arb: OpenClaw → 小龍蝦
- app_en.arb: revert 小龙虾 back to OpenClaw

Web admin:
- Add serverPool/openclawInstances keys to en/zh sidebar.json
- en: "OpenClaw Instances", zh: "小龙虾实例"
- sidebar.tsx: use t() instead of hardcoded strings
- openclaw-instances + server-pool pages: use t('openclawInstances')

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:48:25 -08:00
hailin 0846452e8d fix(flutter): replace all 'My Agent' brand references with 'iAgent' in English ARB
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>
2026-03-08 01:35:50 -08:00
hailin 924b826542 fix(flutter): redirect to /home after login (was /dashboard which no longer exists)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:32:54 -08:00
hailin 164d42e6b8 fix(flutter): localize all hardcoded Chinese strings in home_page + restore iAgent brand name
- 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>
2026-03-08 01:23:43 -08:00
hailin 3f47a7b149 feat(web-admin): rename OpenClaw to 小龙虾 in all UI labels
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:17:00 -08:00
hailin 99c883fad9 fix(flutter): replace ListTile with custom InkWell+Row in _SettingsRow to fix vertical text
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>
2026-03-08 01:12:49 -08:00
hailin ece778e593 fix(web-admin): use apiClient for openclaw-instances to forward JWT
Bare fetch() was not sending Authorization header, causing 401.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 01:06:28 -08:00
hailin c33c45d474 chore(flutter): auto-format generated l10n dart files
Flutter gen-l10n added zh translation comments and reflowed long lines.
No functional changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 00:58:40 -08:00
hailin 60f09feb50 feat(flutter): apply Inter font + add language picker in profile
- 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>
2026-03-08 00:57:45 -08:00
hailin b2594317d7 feat(flutter): localize referral page + fix language auto-detection
- 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>
2026-03-08 00:51:25 -08:00
hailin 8262d3f8e3 fix(auth-service): add prisma/.gitkeep for Dockerfile.service COPY step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 00:20:28 -08:00
hailin 4df699348f feat(referral): add user-level personal circle + points system
- 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>
2026-03-08 00:18:17 -08:00
hailin 6be84617d2 feat(flutter): i18n体系(zh/zh_TW/en) + 智能体解聘功能
- 建立完整 flutter_localizations i18n 体系:zh/zh_TW/en 三语言
- l10n.yaml + ARB 文件 (app_zh.arb 约120键作模板,zh_TW/en 对应覆盖)
- localeProvider 连接 SharedPreferences language 设置,实时切换语言
- 设置页加入语言选择器(简体中文/繁体中文/English)
- 我的智能体页实现解聘(解聘确认弹窗 + DELETE API)与重命名功能
- 全部页面 (~18个) UI 字符串替换为 AppLocalizations.of(context).xxx

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 00:05:55 -08:00
hailin 3074ea54a9 fix(dockerfile): add notification-service/package.json to builder and production stages
The package.json was missing from both the builder stage (lines ~20-31)
and the production stage (lines ~60-70), causing pnpm to skip installing
@nestjs/core and all other dependencies for notification-service.
Container started but immediately crashed with 'Cannot find module @nestjs/core'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 23:12:13 -08:00