apps/web webhook routes. It is the first production path
for bidirectional agent presence across Discord and Zalo.
Runtime Boundary
Deploying an agent enables anapps/web webhook endpoint:
apps/web loads the channel config from root workspace_secrets, creates a
Chat SDK runtime through @tuturuuu/ai/chat-sdk, and runs the response loop with
AI SDK ToolLoopAgent.
Production deployments use Redis so thread subscriptions, dedupe, and locks
survive cold starts and multiple instances. Runtime resolution checks, in order:
- Root workspace secret
AI_AGENT_CHAT_SDK_STATE_REDIS_URL. - Environment variables
AI_AGENT_CHAT_SDK_STATE_REDIS_URL,REDIS_URL, orDOCKER_WEB_REDIS_URL. - The bundled blue/green Docker Redis service at
redis://redis:6379when a blue/green runtime mount or Docker SRH URL is present.
ai_agents product metadata. Do not add agent-specific browser memory
calls; all recall, writes, and deletes must stay behind the server-owned
@tuturuuu/ai/memory boundary and workspace memory APIs.
Webhook URLs are generated from the canonical public platform origin, not the
incoming request host. Set AI_AGENT_WEBHOOK_ORIGIN for an explicit override;
otherwise WEB_APP_URL, NEXT_PUBLIC_WEB_APP_URL, NEXT_PUBLIC_APP_URL,
PLATFORM_BUILD_DEPLOYMENT_URL, and production fallback https://tuturuuu.com
are used before local request-origin fallback.
Configuration Storage
Agent configuration stays under root workspace secrets:AI_AGENT_ZALO_PERSONAL_ENABLED = true; do not gate it with process
environment variables.
External chat history is mirrored separately in the private schema:
apps/web routes call the private RPCs for listing,
syncing, drafting, and sending. Browser clients never read these private tables
directly.
Chat Discovery
The shared chat UI displays mirrored external AI-agent threads as read-only AI conversations in the workspace selected for the agent channel. This works inapps/web and the standalone apps/chat app. Chat users can inspect mirrored
history there, while root AI-agent admins can use the setup, operations, and
manual-response controls from Infrastructure > AI Agents or the apps/chat
agent details sidebar.
The root workspace may still show enabled agent channel setup entries so
operators can jump to channel configuration. Those setup entries are not the
message mirror; mirrored external threads use ai-agent-thread-<uuid>
conversation IDs.
Root AI-agent admins can also manage those setup entries from the standalone
apps/chat internal workspace. The internal workspace resolves to
ROOT_WORKSPACE_ID, and admin-visible setup conversations include the minimum
agent/channel IDs needed by the sidebar operations panel. Non-admin chat
viewers still receive scrubbed setup metadata.
Manual Response Console
Infrastructure > AI Agents is the full setup and operations surface:- Configure Discord and Zalo credentials, workspace mapping, auto-response, and history sync per channel.
- Inspect mirrored external threads and messages.
- Trigger an on-demand external history sync when the adapter supports it, including phone-approved transfer sync for personal Zalo accounts.
- Generate a draft with a custom operator prompt and mirrored history context.
- Send the reviewed draft back to the external platform exactly as edited.
apps/chat for root
AI-agent admins when an external AI-agent thread is selected: sync external
history, draft a manual response, send the reviewed response, and refresh the
mirrored conversation.
Testing From Apps/Chat
Useapps/chat as the operator smoke-test surface after deployment:
- Open the
internalworkspace and select the AI-agent setup conversation. - Confirm setup data loads instead of the metadata-hidden notice.
- Run Test as a readiness check for required credentials and channel status. The diagnostics list should pass for agent enabled, channel enabled, deployed status, required secrets, webhook URL, workspace mapping, adapter account mapping, and recent error state. This does not replace a live external-platform round trip.
- For Discord, set the generated webhook URL as the Interactions Endpoint URL so Discord performs its endpoint verification. Then install the app and send a live mention/message in the mapped channel. The official references are Discord interactions and the Discord quickstart.
- For personal Zalo, set the channel to
Personal account, open the operations tab, run Pair QR, scan the QR code from the Zalo mobile app, then run Validate, Sync history, Sync phone, Deploy, and Start. Approve the transfer-sync prompt on the phone when Sync phone requests it. Use a second Zalo account or group sender for the live inbound-message test. - From the setup conversation, open the Thread tab to list recent mirrored external threads for the selected agent channel. Use Sync external thread from that list when a mirrored thread exists.
- Select a mirrored
ai-agent-thread-<uuid>conversation and use Sync external thread. A zero-message result means the adapter found no newer messages. - Draft and send a manual response from the thread panel, then confirm the mirrored conversation refreshes.
Discord And Zalo Setup
For Discord, configure the app with the generated webhook URL, then store the application ID, public key, bot token, guild ID, and optional mention role IDs on the Discord channel. Discord message events require Gateway delivery. For a self-hosted watcher, runapps/discord/ai_agent_gateway_watcher.py with:
AI_AGENT_DISCORD_GATEWAY_WATCHER_SECRET value as a root
workspace secret in apps/web. The watcher calls the deployed apps/web watcher
config endpoint to discover enabled, deployed Discord channel webhooks, and
apps/web only returns channels mapped to ROOT_WORKSPACE_ID. Set
DISCORD_AI_AGENT_GATEWAY_CHANNEL_ID when one watcher should pin a single
root-internal Discord channel.
The watcher forwards raw Gateway packets using the Chat SDK Discord forwarding
contract (GATEWAY_<event> JSON plus x-discord-gateway-token) so apps/web
continues to own AI-agent runtime, identity mapping, mirroring, and responses.
Gateway-token forwarded webhook requests are rejected for non-root workspaces;
normal Discord HTTP interactions still use the configured channel webhook path.
Keep the bot token value in runtime secrets only.
For local smoke tests, DISCORD_AI_AGENT_GATEWAY_WEBHOOK_URL may be set to one
generated Discord channel webhook URL instead of using auto-discovery.
For Zalo, configure the bot webhook URL in the Zalo Bot dashboard, set the
secret token to the channel’s webhookSecret, and store the bot token plus OA ID
on the Zalo channel.
For experimental personal Zalo account testing, set the Zalo account mode to
Personal account, then use Pair QR from the apps/chat setup
conversation or the shared AI-agent operations panel. The QR flow runs
zca-js loginQR() server-side, stores personalCookieJson, personalImei,
and personalUserAgent as channel secrets, and records zaloPersonalOwnId
after confirmation. The browser receives only QR/session status, never raw
cookies, IMEI, or user agent values.
Manual cookie JSON, IMEI, and user agent entry remains a fallback for local
debugging. After either setup path, use Validate to confirm login, then
use Sync history to import Zalo Web-visible personal user and group threads
into Tuturuuu Chat. When the mobile device owns additional history, use
Sync phone and approve the transfer-sync request in the Zalo mobile app.
The phone-transfer action is owned by apps/web; apps/chat reaches it only
through the internal API proxy, and the browser never receives cookies, IMEI,
user agent values, private transfer keys, or raw transfer payloads. Historical
sync persists mirrored conversations and messages only; it does not trigger AI
auto-responses for old messages. Deploy the channel and use Start/Stop
for the live listener lifecycle.
Personal Zalo channels do not receive webhooks, and only one Zalo Web listener
can be active per personal account at a time. Use test accounts only; the
upstream zca-js package is an unofficial Zalo Web automation API and can
trigger account locks or bans.
For experimental root-internal external chatbots, choose the explicit
Internal workspace option in the AI-agent workspace picker. It stores
workspaceId = ROOT_WORKSPACE_ID on the channel and keeps the runtime, test,
deploy, and mirror paths scoped to the internal workspace.
Mapped User Requirement
Agents never write as a global service user in v1. Each inbound external user must map to a Tuturuuu workspace user before task or calendar tools can run. Discord uses the existingdiscord_guild_members mapping for the configured
guild. Zalo uses root secret identity links:
manage_projects; calendar writes require manage_calendar. If permissions are
missing, the agent replies with a permission-aware message instead of writing.