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>
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>
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>
- 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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
- 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>
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>
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>
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>
- 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>
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>
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>
TypeScript strict check rejects NotificationItem|{} union as
Partial<CreateNotificationPayload>&{id?}. Add explicit cast to satisfy
the type checker without changing runtime behavior.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
notification-service does not use Prisma/ORM (raw SQL via TypeORM DataSource).
Dockerfile.service unconditionally copies the prisma/ directory from builder stage,
which fails with 'not found' when the directory doesn't exist.
Adding a .gitkeep placeholder so the COPY succeeds; the subsequent
prisma generate step is skipped because no schema.prisma is present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, pnpm install --prod in the production stage doesn't know
about referral-service dependencies (@nestjs/core etc.) and they are missing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dockerfile.service copies prisma/ from each service; referral-service uses
TypeORM instead of Prisma, so an empty placeholder is needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>