Skip to content

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-place docker restart to 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 instance

Current State (2026-05-15)

StepStatusNotes
WeChat Pay flow✅ DONEReal pay works, webhook verified
DB provisioning✅ DONEOrder PAID, Subscription ACTIVE, HermesInstance created
spawn-profile.sh called❌ NOT DONEprovisionPaidOrder() creates DB only, no real container
HermesInstance.baseUrl❌ Placeholderhttps://shared.hermes.local
llmApiKey in DB❌ Schema missingHermesInstance has no llmApiKey column
WeChat QR returned❌ NOT DONENo Hermes API call after spawn
Frontend shows QR❌ NOT DONEStatusPage 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.

FindingDetail
Dashboard port9119 (not 8642 as assumed in healthcheck)
Gateway porthealthcheck at 8642 doesn't start; gateway_running=true on 9119
Session tokenEmbedded in HTML: window.__HERMES_SESSION_TOKEN__
API spec/openapi.json lists all routes
WeChat supportNOT in dashboard API. Platform toolsets: telegram, discord, whatsapp, slack, qqbot, yuanbao — no weixin
WeChat env varWEIXIN_HOME_CHANNEL= exists in .env template but no dashboard binding flow
OAuth providersanthropic, 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/create with scene_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 offset

Options:

  • SQLite offset tableprofiles.db tracks 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:

  1. SSH exec spawn-profile.sh with PROFILE_NAME={subscriptionId}, LLM_API_KEY
  2. Parse stdout: TWILIGHT_API_TOKEN=, CONTAINER_NAME=
  3. Inspect Docker network IP of container
  4. 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:

  1. POST /services/instance/:subscriptionId (new NestJS route)
  2. Backend calls WeChat API to create parameterized QR: POST https://api.weixin.qq.com/cgi-bin/qrcode/create with scene_str={subscriptionId}, expire 604800s
  3. Return https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=xxx to frontend
  4. Frontend displays QR
  5. When user scans → WeChat calls our existing webhook (/payments/wechat/webhook) — actually needs a separate Events webhook for Official Account (scan event)
  6. On scan event: save openid → subscriptionId mapping, 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].HostPort

Store 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.

团队内部文档