cd openclaw/
npm install # if deps changedNo separate build step — TypeScript is loaded via tsx at runtime.
cd openclaw/
npx vitest run # all tests, must pass before pushBefore any release or main push, run the blocking local gate from the repository root:
node scripts/release-gate.mjsEquivalent package script:
npm run release:checkList the exact lanes without executing any tests:
node scripts/release-gate.mjs --list-jsonThe full gate runs Service TypeScript and all Service tests with live Discord
credentials unset, OpenClaw TypeScript and all OpenClaw tests, Generate build and
tests, Shared tests, Hermes unittest discovery, and static manifest, asset, and
entrypoint assertions. Any child failure makes the gate exit nonzero and blocks
the release.
The trusted GitHub Release workflow runs this same gate. Its dry_run input
defaults to true, and only its publish job receives contents: write. Operators
must dispatch from the hard-pinned main branch and dry-run first; changing the
repository default branch does not change release authority. The workflow
creates an immutable annotated tag, safely
resumes when the same-SHA tag already exists, fails on a different-SHA tag, and
does nothing when the same-SHA GitHub release already exists. It never deploys
production or restarts the gateway. Pull-request merges remain human-only.
See Release Process for the permanent dev, release, and
main branch flow, merge methods, RC and hotfix back-sync, dispatch steps, smoke
checks, and rollback.
The canonical runtime is the HTTP service. Start it from service/:
HENT_AI_SERVICE_TOKEN=... \
HENT_AI_DB_PATH=./hent-ai.sqlite \
npm startThe service listens on HENT_AI_HOST / HENT_AI_PORT (127.0.0.1:8787 by default) and logs startup diagnostics for disabled conversation config, missing Discord poller env, missing bot user id, missing conversation provider, and missing final-response verifier.
For Discord group-chat participation, configure the service-owned poller and OpenAI-compatible conversation provider:
HENT_AI_CONVERSATION_ENABLED=true \
HENT_AI_DISCORD_POLLER_TOKEN=... \
HENT_AI_DISCORD_POLLER_CHANNELS=123456789012345678 \
HENT_AI_DISCORD_POLLER_BOT_USER_ID=... \
HENT_AI_CONVERSATION_PROVIDER_ENDPOINT=https://provider.example/v1/chat/completions \
HENT_AI_CONVERSATION_PROVIDER_TOKEN=... \
HENT_AI_CONVERSATION_PROVIDER_MODEL=... \
npm startUseful conversation controls include HENT_AI_CONVERSATION_RECENT_TURNS, HENT_AI_CONVERSATION_CONTEXT_REFRESH, HENT_AI_CONVERSATION_COMPACTION_INTERVAL_MS, HENT_AI_CONVERSATION_MAX_CHUNKS, HENT_AI_CONVERSATION_MAX_CHUNK_CHARS, HENT_AI_CONVERSATION_BASE_PAUSE_MS, HENT_AI_CONVERSATION_PER_CHAR_MS, and HENT_AI_CONVERSATION_MAX_DELIVERY_ATTEMPTS.
Production final-response verification uses an external verifier provider. Configure it through deployment environment variables or service config; do not put literal token values in docs or logs:
HENT_AI_VERIFIER_PROVIDER_KINDHENT_AI_VERIFIER_ENDPOINTHENT_AI_VERIFIER_TOKENHENT_AI_VERIFIER_MODEL_OR_ROUTEHENT_AI_VERIFIER_TIMEOUT_MSHENT_AI_VERIFIER_EXTRA_HEADERS_JSONfor provider-specific headersHENT_AI_VERIFIER_EXTRA_BODY_JSONfor provider-specific request body fields
Missing endpoint, token, model/route, or invalid timeout/header/body JSON fails verifier config creation. Per-request provider failures return no verdict rather than using deterministic fallback.
The service exposes an async generation path. POST /v1/assets/generate creates a queued job; a worker/provider later processes that job with runNextGenerationJob(db, provider, { assetRoot }).
Minimum request shape:
{
"prompt": "image prompt",
"assetSetId": "gothic-v1",
"emotion": "sorry",
"filename": "sorry.png"
}Provider result shape for generated image persistence:
{
"dataBase64": "<base64 image bytes>",
"contentType": "image/png",
"metadata": {}
}When assetRoot is supplied, the worker writes the image under generated/<assetSetId>/<emotion>/<jobId>-<filename>, upserts storage_objects and assets, strips inline base64 from the stored job result, and exposes the image through /static/.... Tests must keep providers mocked; do not trigger paid image generation in CI.
For the community-cron workflow, POST /v1/assets/generate also accepts a cron selector request:
{
"communitySelector": {
"conversationWindow": [
{ "authorId": "u1", "content": "hello", "createdAt": "2026-06-03T00:00:00Z" }
],
"draftReply": "draft reply text",
"channelId": "123",
"profileId": "gothic-v1",
"assetSetId": "gothic-v1"
}
}The service also exposes GET /v1/channels/cron-enabled, which returns the service-owned cron allowlist plus a revision token so OpenClaw can decide when to refresh its cached channel set.
Plugin is loaded by OpenClaw gateway from plugins.load.paths config. Current production-style setup should load this repository's openclaw/ adapter and enable plugins.entries.hent-ai-service-adapter with the hentAiService connection config.
After code changes or load-path changes: gateway restart/reload required (openclaw gateway restart from main session, NOT from Discord embedded session).
Example plugin path: /Users/iyen/projects/Hent-ai/openclaw or the checked-out repo path currently used by the gateway.
Do not use the old plugins.entries.emotion-image OpenClaw config entry for current service-adapter installs.
Channel/profile state is service-owned. Use the Hent-ai service API rather than local OpenClaw files:
curl -X PUT "$HENT_AI_SERVICE_URL/v1/channels/$DISCORD_CHANNEL_ID/mapping" \
-H "Authorization: Bearer $HENT_AI_SERVICE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profileId": "gothic-v1",
"assetSetId": "gothic-v1",
"mode": "normal",
"enabled": true,
"cronEnabled": false
}'For Discord threads, repeat the mapping for the thread id if replies are delivered in the thread.
Use a real assistant final reply and then check Discord readback for non-empty attachments. Direct/proactive message.send and fallback cron delivery can bypass the OpenClaw reply_payload_sending hook and are not valid attachment E2E tests.
- Symptom: unwanted "화이팅!" + cheer.png sent to channel
- Cause: cheer intent classifier misclassified task request as emotional support
- Fix: tighten
buildCheerIntentPromptwith negative examples - Prevention: any prompt change → test with real frustration messages
- Symptom: private asset set disappeared
- Cause: Python script deleted entire section instead of targeted edit
- Fix: manual manifest reconstruction
- Prevention: always
git diffafter any JSON manipulation script
- Symptom: private mode on but default images shown
- Cause: plugin imageDir pointed to old path, overrides saved to new path
- Fix: unified to ~/projects/Hent-ai as SSOT
- Prevention: after path changes, verify plugin's loaded imageDir in gateway logs
git push --forceon main- Modifying production manifest.json without backup
- Running image generation in CI/test without mocks
- Merging PRs without test pass confirmation