Skip to content

Latest commit

 

History

History
204 lines (150 loc) · 7.57 KB

File metadata and controls

204 lines (150 loc) · 7.57 KB

Hent-ai Agent Runbook

Build

cd openclaw/
npm install        # if deps changed

No separate build step — TypeScript is loaded via tsx at runtime.

Test

cd openclaw/
npx vitest run     # all tests, must pass before push

Release Regression Gate

Before any release or main push, run the blocking local gate from the repository root:

node scripts/release-gate.mjs

Equivalent package script:

npm run release:check

List the exact lanes without executing any tests:

node scripts/release-gate.mjs --list-json

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

Standalone Service Startup

The canonical runtime is the HTTP service. Start it from service/:

HENT_AI_SERVICE_TOKEN=... \
HENT_AI_DB_PATH=./hent-ai.sqlite \
npm start

The 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 start

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

Remote Verifier Configuration

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_KIND
  • HENT_AI_VERIFIER_ENDPOINT
  • HENT_AI_VERIFIER_TOKEN
  • HENT_AI_VERIFIER_MODEL_OR_ROUTE
  • HENT_AI_VERIFIER_TIMEOUT_MS
  • HENT_AI_VERIFIER_EXTRA_HEADERS_JSON for provider-specific headers
  • HENT_AI_VERIFIER_EXTRA_BODY_JSON for 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.

Image Generation Job Path

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.

Deploy

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.

Common Operations

Set channel mapping

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.

Validate attachment path

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.

Incident Patterns

Cheer false positive (2026-05-19)

  • Symptom: unwanted "화이팅!" + cheer.png sent to channel
  • Cause: cheer intent classifier misclassified task request as emotional support
  • Fix: tighten buildCheerIntentPrompt with negative examples
  • Prevention: any prompt change → test with real frustration messages

Manifest deletion (2026-05-18)

  • Symptom: private asset set disappeared
  • Cause: Python script deleted entire section instead of targeted edit
  • Fix: manual manifest reconstruction
  • Prevention: always git diff after any JSON manipulation script

Path mismatch (2026-05-19)

  • 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

Forbidden Actions

  • git push --force on main
  • Modifying production manifest.json without backup
  • Running image generation in CI/test without mocks
  • Merging PRs without test pass confirmation