主题
Hermes WeChat Provisioning — Post-Payment Flow
⚠ SUPERSEDED 2026-05-16 by
2026-05-16-instance-lifecycle-rebuild.md. QR source switched from WeChat Official Account (cgi-bin/qrcode/create) to iLink bot platform. Container lifecycle switched from in-placedocker restartto state-machine + ProvisionWorker (immutable containers, destroy+recreate on rebind). Kept for historical context. Do not implement from this file.
Date: 2026-05-15
Branch: feat/v0.4.1-ecs-autodeploy
Status: ❌ Superseded — see header banner.
Target Flow (from design diagram)
user client frontend
|
| scan price QR → pay
↓
user backend pricing (twilight-app-backend :4000)
|
| POST /instance/:id (subscriptionId)
↓
Hermes service ← spawn-profile.sh creates container
|
| return instance info {
| hermesUrl, mcpUrl,
| wechatQrLink, ← WeChat bot QR for user to scan
| twilightToken ← TWILIGHT_API_TOKEN
| }
↓
user backend pricing
|
| return WeChat bot QR to frontend
↓
user client frontend
|
| display QR → user scans in WeChat
↓
user's WeChat binds to this Hermes instanceCurrent State (2026-05-15)
| Step | Status | Notes |
|---|---|---|
| WeChat Pay flow | ✅ DONE | Real pay works, webhook verified |
| DB provisioning | ✅ DONE | Order PAID, Subscription ACTIVE, HermesInstance created |
spawn-profile.sh called | ❌ NOT DONE | provisionPaidOrder() creates DB only, no real container |
HermesInstance.baseUrl | ❌ Placeholder | https://shared.hermes.local |
llmApiKey in DB | ❌ Schema missing | HermesInstance has no llmApiKey column |
| WeChat QR returned | ❌ NOT DONE | No Hermes API call after spawn |
| Frontend shows QR | ❌ NOT DONE | StatusPage shows raw JSON only |
First paid test subscription: cmp6vr0zq000bp601beipc3nr (SHARED, placeholder baseUrl)
Hermes API Discovery (2026-05-15)
Explored nousresearch/hermes-agent:0.13.0 running on ECS.
| Finding | Detail |
|---|---|
| Dashboard port | 9119 (not 8642 as assumed in healthcheck) |
| Gateway port | healthcheck at 8642 doesn't start; gateway_running=true on 9119 |
| Session token | Embedded in HTML: window.__HERMES_SESSION_TOKEN__ |
| API spec | /openapi.json lists all routes |
| WeChat support | NOT in dashboard API. Platform toolsets: telegram, discord, whatsapp, slack, qqbot, yuanbao — no weixin |
| WeChat env var | WEIXIN_HOME_CHANNEL= exists in .env template but no dashboard binding flow |
| OAuth providers | anthropic, claude-code, nous, openai-codex, qwen-oauth, minimax — no WeChat |
Conclusion: WeChat integration requires a separate layer outside Hermes dashboard.
Revised Architecture
After payment:
NestJS backend
├── spawn-profile.sh → Hermes container (port 9119 dashboard, 8642 gateway)
├── WeChat Official Account API → generate "follow QR"
│ ↓ user scans QR
│ WeChat webhook → our backend receives openid
│ Backend updates HermesInstance: WEIXIN_HOME_CHANNEL=<openid>
│ Backend restarts Hermes container with updated .env
└── returns to frontend: { hermesUrl, followQrCode }API Contract: POST /services/instance/:subscriptionId
This is our own NestJS endpoint (not a Hermes API call). Hermes does not expose a WeChat binding API.
Request (frontend → NestJS backend)
http
POST /services/instance/{subscriptionId}
Authorization: Bearer <jwt>Response
json
{
"hermesUrl": "http://172.x.x.x:9119",
"dashboardToken": "...",
"wechatFollowQr": "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=...",
"status": "provisioning" | "ready"
}WeChat QR source: WeChat Official Account API
POST /cgi-bin/qrcode/createwithscene_str={subscriptionId}, returns ticket →showqrcode?ticket=xxx
Port Allocation Strategy
Single host, multiple Hermes containers. Each needs a unique port.
SHARED tier: one container, one port (e.g. 8642) — all shared users
DEDICATED tier: port = 8700 + sequential offsetOptions:
- SQLite offset table —
profiles.dbtracks name→port - Dynamic: docker inspect — read actual host port after
docker run -p 0:8642 - Simple: hash(userId) % 1000 + 8700
Recommended: docker run -p 127.0.0.1:0:8642 → Docker assigns free port → inspect to get it.
Implementation Tasks
Task 1: DB Schema Migration ✅ DONE (2026-05-15)
Added llmApiKey, llmApiTokenId, hostPort via direct ALTER TABLE on ECS Postgres. Prisma schema already has these fields; no new migration needed.
sql
ALTER TABLE "HermesInstance"
ADD COLUMN "llmApiKey" text,
ADD COLUMN "llmApiTokenId" integer,
ADD COLUMN "hostPort" integer;Task 2: Fix spawn-profile.sh healthcheck port
Dashboard is on 9119, not 8642. Fix spawn-profile.sh healthcheck:
bash
--health-cmd "curl -sf http://127.0.0.1:9119/api/status || exit 1"Task 3: Wire spawn-profile.sh into provisioning.service.ts
After provisionPaidOrder() creates DB record:
- SSH exec
spawn-profile.shwithPROFILE_NAME={subscriptionId},LLM_API_KEY - Parse stdout:
TWILIGHT_API_TOKEN=,CONTAINER_NAME= - Inspect Docker network IP of container
- Update
HermesInstance:baseUrl=http://{containerIp}:9119,externalRef={containerName}
Implementation approach: child_process.exec on ECS (backend runs on same host as Docker).
Task 4: WeChat Official Account QR flow
Prerequisite: WeChat Official Account credentials (AppID + AppSecret).
Steps:
POST /services/instance/:subscriptionId(new NestJS route)- Backend calls WeChat API to create parameterized QR:
POST https://api.weixin.qq.com/cgi-bin/qrcode/createwithscene_str={subscriptionId}, expire 604800s - Return
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=xxxto frontend - Frontend displays QR
- When user scans → WeChat calls our existing webhook (
/payments/wechat/webhook) — actually needs a separate Events webhook for Official Account (scan event) - On scan event: save
openid → subscriptionIdmapping, update Hermes.env
Task 5: Frontend StatusPage
Replace raw JSON with:
- Plan + expiry info
- "添加微信机器人" section with QR image
- Polling: detect scan event, show "已添加 ✓ 开始对话"
Task 6: Healthcheck fix + Hermes port in spawn-profile.sh
Update spawn-profile.sh to expose port 9119 to host with dynamic allocation:
bash
docker run -p 127.0.0.1:0:9119 ...
# Then: docker inspect → HostConfig.PortBindings["9119/tcp"][0].HostPortStore hostPort in DB for routing.
Immediate Next Step
Fix spawn-profile.sh healthcheck (8642 → 9119), then implement Task 3 (wire provisioning).
WeChat Official Account QR (Task 4) requires AppID/AppSecret — get these first.