[GM Workbench] Read-aloud description generator (A3) #141
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Motivation / Context
The GM Workbench catalog (
docs/.internal/gm-planning-expansion-2026-07-15.md§3, Group A) identifies boxed-text/read-aloud description generation as one of the two highest value-to-effort tools with zero overlap against existing issues — the report recommends shipping it first alongside the rumor generator. GMs constantly need sensory descriptions for places, NPCs, items, and scenes to read aloud at the table; today there is no tool for this at all, only the name generator.This is the first tool built on top of the Phase 0 foundation (#134, #136, #138) and establishes the pattern later tools (random tables, loot, rumors) follow: a registered tool, a result that can be saved as new lore or appended to an existing entry via the draft rail.
Approach
tool_id="description"or similar) in theGENERATOR_TOOLSregistry (generation_service.py, from #136).LoreEntry), tone,game_system. Uses thecampaign_contextand, when a lore entry is picked,lore_contextbuilders from #136.generate_structured_textin JSON mode (fromllm_service.py, #134). Model the prompt/system-prompt pattern on the existing name-generator constants (_NAME_GENERATOR_SYSTEM_PROMPT,audio_service.py:220-228) and per-category hints (_NAME_CATEGORY_DESCRIPTION_HINTS,audio_service.py:163-218) — this tool is prose-shaped rather than options-shaped, but reuses the samegenerate_structured_text/JSON-mode call pattern.sync_allowed=True), same as the cache-miss name path (campaigns.py:3119-3139).GenerationResult(scratch, #138) by default. From there:LoreEntrydirectly, same pattern as canonical-name auto-creation (sessions.py:283-296).LoreEntryDraftviadraft_service.py, reusing the existing draft-review rail (start_merge_draft,draft_service.py:93) andWikiDraftReview.jsx— the same review surface #130 uses for its draft modes.GeneratorPanel.Dependencies
llm_service) — generation calls route through it.GenerationResultrows for the scratchpad.Out of scope
GeneratorPanel) — #153 (Workbench UX reorganization); this issue ships its own minimal panel.Acceptance criteria
descriptiontool is registered and reachable viaPOST /api/campaigns/{campaign_id}/workbench/description/generate, GM-only.game_systemcontext, returns both a one-liner and a full-paragraph description.GenerationResulthistory (scratchpad).LoreEntrywith the generated description inbody.LoreEntryDraftthat surfaces inWikiDraftReview.jsxfor GM review/edit/approve — never auto-writes to an existing entry's body.Picking this up (first Workbench tool, on the merged #134/#136/#138 foundation) on
feat/141-description-generator→ PR ontofeat/v3.10-gm-workbench. Registers adescriptiontool (one-liner + full paragraph, boxed/read-aloud), a minimal generate panel, and the reusable result-landing actions — save-as-new-lore, append-to-entry via aLoreEntryDraft(WikiDraftReview), copy — that the other five generators will reuse. Backend + minimal frontend; full workbench shell is #153.Done — merged to the
feat/v3.10-gm-workbenchintegration branch via PR #219.Delivered
descriptiongenerator (read-aloud one-liner + paragraph, tone-aware, optionally grounded in a picked lore entry) on the generic workbench engine (#136), persisting aGenerationResult(#138).WorkbenchResultActionsbar (the next five generators reuse it): Save as lore entry, Append to entry (POST /lore/{entry_id}/append-draft→ a READY review draft, no LLM call, GM-only, one-per-entry), Copy.params, so entry-scoped tools fold the picked entry into the prompt.Verification (Docker): backend 694 passed, frontend 353 passed / 35 files, prod build OK,
ruff check+ruff format --checkclean, eslint 0 errors.Two test-only issues fixed during verification: the GM-only test hit the shared
get_current_useroverride collision (requesting bothgm_client+player_clientin one test) — reseeded viadb, droppedgm_client; and the clipboard spy is now installed afteruserEvent.setup()viadefineProperty(jsdomnavigator.clipboardis getter-only). No bot API surface touched.