Files
zalbot/docs/api-contracts.md
Kaysser Kayyali e2c92e854f
Some checks failed
tests / Unit tests (Node 22) (push) Failing after 2m13s
Add unit tests for LLM clients, persona loader, and XP/Foundry rewards
Expands the unit test suite from 320 to 380 tests (+60) and adds a
Gitea Actions CI workflow. Closes all six follow-up recommendations
from the test-architecture validation report.

New tests (tests/unit/):
  - ollamaClient.test.ts          — Ollama SDK wrapper, options passthrough
  - litellmClient.test.ts         — OpenAI SDK wrapper, model fallback
  - personaLoader.test.ts         — Zod validation + cache invalidation
  - foundryReward.test.ts         — Tool plugin: lookup, errors, partial grants
  - xpAwarder.test.ts             — Bulk XP awards + per-player skip reasons
  - redisErrorPath.test.ts        — Singleton error handler does not crash
  - messageRouterRunLLMTurn.test.ts — 18 cases for the runtime heart:
    narrative-only path, tool dispatch, filter correction, retry loop
    guard, missed-skill-check heuristic, typing indicator interval,
    LLM error fallback, archive on resolve.

Coverage (line %):
  - harness/litellmClient.ts      0 → 100
  - harness/ollamaClient.ts       0 → 100
  - harness/tools/foundryReward.ts 0 → 100
  - session/xpAwarder.ts          0 → 100
  - persona/loader.ts             0 → 100
  - db/redis.ts                   0 → 100
  - bot/handlers/messageRouter.ts 0 → 39.86 (runLLMTurn now covered)

Tooling:
  - package.json: + test:coverage, test:watch scripts
  - devDep: @vitest/coverage-v8@^3.1.0
  - tests/README.md: conventions, anti-patterns, template map
  - .gitignore: exclude coverage/
  - .gitea/workflows/test.yml: Node 22, npm cache, tsc --noEmit gate

Documentation (from earlier /bmad-document-project run, now committed):
  - docs/index.md
  - docs/project-overview.md
  - docs/architecture.md
  - docs/deployment-guide.md
  - docs/api-contracts.md
  - docs/data-models.md
  - docs/source-tree-analysis.md
  - docs/component-inventory.md
  - docs/development-guide.md
  - _bmad-output/test-artifacts/automate-validation-report.md

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-19 05:59:13 +00:00

6.2 KiB

API Contracts

External interfaces for the Mardonar Encounter Engine. Generated 2026-06-19.

The bot has two distinct "API" surfaces: the Discord slash-command surface (player/admin) and the JSON-RPC surface used to talk to GraphMCP. The LLM's tool surface is documented in architecture.md §5.2.

1. Discord slash commands

All commands are registered via src/scripts/deploy-commands.ts (Discord REST v10). The bot responds only in channels listed in DISCORD_ALLOWED_CHANNELS (empty = none).

/dndname

Subcommand Args Effect
set name: string (required) Register or update your D&D character name
show Echo your current registered name
clear Remove your registration

/character

Subcommand Args Effect
register foundry Browse and claim a Foundry VTT actor (modal-driven)
register custom Set a custom character (modal-driven)
show Display your current character profile
view Fetch live character stats from Foundry VTT
clear Delete your character profile
admin list Show all guild character registrations
admin remove user: discord user (required) Remove another user's registration
admin give Give an item to a Foundry character (modal-driven)

/encounter

Subcommand Args Effect
start spec: string (required, file in ./specs/) Load spec, open a new encounter thread
random Start a randomly selected encounter
status Show current encounter status (phase, players, history length)
stats Show encounter run statistics
audit DM the most recent encounter summary file
end notes: string (optional) Force-resolve the encounter (admin override)
list Show all active encounters in this server
generate theme: string (required) LLM-generate a spec from a short description
spec Send the YAML spec for the current encounter thread

/encounters

Opens a select-menu + search modal flow that calls GraphMCP search_encounters and get_encounter.

/roll

Subcommand Args Effect
action Manual dice roll outside an encounter

/actions

In-character action shortcuts.

/turn

Turn management.

/xp

Subcommand Args Effect
award amount: number (required) Award XP to a character via VTT relay

Button / modal interactions

customId Type Handler
give_modal modal submit handleGiveModal
character_custom_modal modal submit handleCustomRegisterModal
foundry_link_modal modal submit handleFoundryLinkModal
encounters_select string select handleEncounterSelect
encounters_search_btn button handleSearchButton
encounters_search_modal modal submit handleSearchModalSubmit
(skill check buttons) button / modal isSkillCheckInteractionhandleRollInteraction

2. GraphMCP JSON-RPC

Base URL: GRAPHMCP_URL (default http://localhost:9000). Endpoint: POST {GRAPHMCP_URL}/mcp Content-Type: application/json

Request body (JSON-RPC 2.0):

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "<tool_name>",
    "arguments": { ... }
  }
}

Response body:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [
      { "text": "<JSON-stringified payload>" }
    ]
  }
}

Or on error:

{ "jsonrpc": "2.0", "id": 1, "error": { "message": "..." } }

The bot's client (src/graphmcp/client.ts) parses the inner text field as JSON.

query_as_npc

Arguments:

{ npc_name: string; question: string; limit?: number }

Returns NPCQueryResult:

{
  npc: string;
  tier: string;
  horizon_count: number;
  chunks: { text: string; score: number; source: 'message' | 'lore'; author: string; timestamp: string }[];
  graph_context: {
    enc_id: string; enc_title: string; enc_type: string;
    enc_timestamp: string; enc_summary: string;
    featured_entities: string[]; locations: string[];
  }[];
}

Used for NPC memory injection at session start. Filtered by GRAPHMCP_SCORE_THRESHOLD and capped at GRAPHMCP_NPC_MEMORY_LIMIT.

Arguments:

{ query: string; limit?: number }

Returns SemanticSearchResult:

{ chunks: { content: string; score: number; source?: string }[] }

Used by @Zalram mention handler.

log_encounter

Arguments:

{
  title: string;
  participants: string;
  summary: string;
  location?: string;        // default ''
  type?: string;            // default 'encounter'
}

Returns LogEncounterResult:

{
  enc_id: string;
  title: string;
  participants: string;
  location: string;
  timestamp: string;
}

Called from the encounter resolve path to write a permanent encounter node.

list_encounters

Arguments:

{ limit?: number }   // default 10

Returns EncounterResultItem[]:

{ id: string; title: string; location: string; timestamp: string; summary: string }[]

search_encounters

Arguments:

{ query?: string; location?: string; participant?: string; limit?: number }

Returns EncounterResultItem[].

get_encounter

Arguments:

{ id: string }

Returns EncounterDetails:

{
  id: string; title: string; location: string; timestamp: string;
  summary: string; type: string;
  participants: string[]; featured_entities: string[];
}

3. Redis contract

The bot writes to these key patterns:

Key Type TTL Owner
session:{threadId} string (JSON SessionState) SESSION_TTL_HOURS (12h) sessionManager
guild_threads:{guildId} set of thread IDs inherits session TTL sessionManager
(player registry, character registry — pattern in src/session/playerRegistry.ts and characterRegistry.ts) varies varies respective module

SessionState JSON shape: see src/types/index.ts.

raw.messages is a Redis stream published to by graphmcp/ingest.ts (fire-and-forget per encounter message). The bot does not read from it — the GraphMCP discord-connector does.