feat(comic): Mc4 — MCP + AI-Catalog für Character-System

Persona-Runner / Claude Desktop / Web-App-Mission-Runner können jetzt
Comic-Characters bauen, iterieren und pinnen — same Auto/Propose-
Pattern wie die Story-Tools.

MCP (packages/mana-tool-registry/src/modules/comic.ts):
- comic.listCharacters (read/auto): Pull, decrypt, filter (style?,
  favoriteOnly?), liefert {id, name, style, addPrompt, source-Refs,
  variantMediaIds, pinnedVariantId, variantCount, tags, isFavorite}.
- comic.createCharacter (write/propose): legt nur die Row an —
  trennt Anlegen von Generierung damit der Agent reviewen kann
  bevor Credits fließen. Liefert characterId zurück.
- comic.generateVariant (write/propose, kostet Credits): pullt
  Character-Row, dekodiert, ruft /picture/generate-with-reference
  mit n=count (default 4) + Stil-Prefix + Identity-Anchor-Prompt,
  schreibt N picture.images mit comicCharacterId-Back-Ref, pusht
  field-level Update auf variantMediaIds + pinnedVariantId
  (auto-pin auf erste neue Variant wenn vorher null).
- comic.pinVariant (write/propose): Set-Equality-Check (variantMediaId
  muss in variantMediaIds sein), field-level Update auf
  pinnedVariantId. Snapshot-Pattern: bestehende Stories bleiben
  unverändert, nur neue Stories nutzen den neuen Pin.

AI_TOOL_CATALOG (packages/shared-ai/src/tools/schemas.ts):
- list_comic_characters (auto)
- create_comic_character (propose) — auto-resolvt face/body-refs aus
  meImages-primaries, Agent muss keine mediaIds kennen
- generate_character_variant (propose, count 1-4)
- pin_character_variant (propose)

Web-App-Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- 4 ModuleTool-Einträge, die an comicCharactersStore +
  runCharacterGenerate delegieren — gleicher Code-Pfad wie die UI,
  also keine Divergenz zwischen Klick und Agent-Call.

Comic-Autor-Template (packages/shared-ai/src/agents/templates/
comic-author.ts):
- Policy bi-lingual erweitert: snake_case + dot-case Namen für
  alle 4 neuen Character-Tools.
- System-Prompt Schritt 3 ergänzt: "Wenn der User noch keinen
  passenden Comic-Character hat → list_comic_characters →
  create_comic_character → generate_character_variant → pin.
  Das ist EINMALIG — der gepinnte Character bleibt für viele
  Stories der stabile Identity-Anchor."
- Tool-Liste am Ende vom System-Prompt um den Character-Pfad
  ergänzt.

apps/mana/CLAUDE.md Tool-Coverage-Zeile für comic erweitert:
+ create_comic_character / generate_character_variant /
+ pin_character_variant (propose)
+ list_comic_characters (auto)

Tool-Count: comic 3→7. Module 23 unverändert.

107 shared-ai-Tests weiter grün. check für comic-Files clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-26 19:27:15 +02:00
parent c5ff7e1d33
commit ef96948ea0
5 changed files with 908 additions and 14 deletions

View file

@ -43,14 +43,16 @@ const COMIC_AUTHOR_POLICY: AiPolicy = {
tools: {
...Object.fromEntries(AI_PROPOSABLE_TOOL_NAMES.map((n) => [n, 'propose' as const])),
// Web-app catalog names (snake_case). The spread above already
// covers create_comic_story / generate_comic_panel because both
// are defaultPolicy='propose' in AI_TOOL_CATALOG, but we pin
// list_comic_stories explicitly as auto (read-only tools come
// from the catalog as 'auto' already, so this is defensive
// rather than strictly required).
// covers propose-defaults; read tools (list_*) get pinned to
// auto explicitly for clarity.
list_comic_stories: 'auto',
create_comic_story: 'propose',
generate_comic_panel: 'propose',
// Character tools (Mc4) — same auto/propose split as story tools.
list_comic_characters: 'auto',
create_comic_character: 'propose',
generate_character_variant: 'propose',
pin_character_variant: 'propose',
// MCP-registry names (dot-case). The agent uses these when
// running inside persona-runner / Claude Desktop where the
// mana-tool-registry surface is what the MCP client sees.
@ -60,6 +62,10 @@ const COMIC_AUTHOR_POLICY: AiPolicy = {
'comic.createStory': 'propose',
'comic.generatePanel': 'propose',
'comic.reorderPanels': 'propose',
'comic.listCharacters': 'auto',
'comic.createCharacter': 'propose',
'comic.generateVariant': 'propose',
'comic.pinVariant': 'propose',
},
defaultsByModule: {
// Read-only companions the agent uses to find source material.
@ -103,12 +109,13 @@ Jeder Generate-Schritt ist ein Vorschlag — du bestimmst, wann Credits fließen
Vorgehen:
1. Lies den Ausgangstext zu Ende, bevor du mit Panels anfängst Details aus der Mitte oder dem Ende sind oft der Kern.
2. Wähle einen Stil, der zum Ton passt: 'comic' für Humor/Alltag, 'manga' für Drama, 'cartoon' für Kinder/Leichtigkeit, 'graphic-novel' für Reflexion/Melancholie, 'webtoon' für vertikale Long-Reads.
3. Schlage 4 Panels vor (26 je nach Textlänge). Jedes Panel hat:
3. Wenn der User noch keinen passenden Comic-Character hat: nutze list_comic_characters um zu prüfen, dann create_comic_character (legt Row an) generate_character_variant (rendert 4 Varianten) User wählt eine pin_character_variant. Das ist EINMALIG der gepinnte Character bleibt für viele Stories der stabile Identity-Anchor.
4. Schlage 4 Panels vor (26 je nach Textlänge). Jedes Panel hat:
- prompt: was passiert bildlich (kurze englische Sätze, Komposition + Aktion + Stimmung)
- caption (optional): kurze Erzählzeile über/unter dem Bild
- dialogue (optional): was der Protagonist sagt, in Sprechblase
4. Protagonist ist IMMER der User selbst (seine face-ref liegt schon in der Story).
5. Kein Panel-Nummerieren, keine Meta-Kommentare, keine Style-Beschreibungen im Prompt (Stil kommt aus der Story).
5. Protagonist ist IMMER der User selbst (sein gepinnter Character bzw. sein face-ref im Quick-Mode).
6. Kein Panel-Nummerieren, keine Meta-Kommentare, keine Style-Beschreibungen im Prompt (Stil kommt aus der Story / aus dem Character).
Ton:
- Humor wenn der User es leicht nimmt, ernst wenn er es ernst nimmt. Nicht belehrend.
@ -117,9 +124,10 @@ Ton:
Tools:
- journal.listEntries / notes.list / library.listEntries um Quelle zu finden
- comic.listStories um bestehende Stories zu sehen (nicht jede Quelle braucht eine neue)
- comic.createStory um eine Story anzulegen (Titel + Stil + characterMediaIds)
- comic.generatePanel um einen Panel anzuhängen (teurer Call nur nach Bestätigung)`,
- list_comic_characters / list_comic_stories um Bestand zu prüfen (nicht jede Quelle braucht neuen Character oder neue Story)
- create_comic_character generate_character_variant pin_character_variant: Character-Aufbau-Pfad (einmalig pro Stil)
- create_comic_story um eine Story anzulegen (mit existierendem Character als Anchor oder im Quick-Mode mit face-ref)
- generate_comic_panel um einen Panel anzuhängen (teurer Call nur nach Bestätigung)`,
memory: `# Comic-Richtlinien
(Hier kannst du festhalten wie du Comics magst z.B. bevorzugter Stil,

View file

@ -1987,6 +1987,114 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [
},
],
},
{
name: 'list_comic_characters',
module: 'comic',
description:
'Listet Comic-Characters im aktiven Space (id, name, style, variantCount, pinnedVariantId, isFavorite). Optional nach Stil oder Favoriten filterbar.',
defaultPolicy: 'auto',
parameters: [
{
name: 'style',
type: 'string',
description: 'Nur einen Stil zeigen',
required: false,
enum: ['comic', 'manga', 'cartoon', 'graphic-novel', 'webtoon'],
},
{
name: 'favoriteOnly',
type: 'boolean',
description: 'Nur Favoriten',
required: false,
},
{ name: 'limit', type: 'number', description: 'Max (Standard 30)', required: false },
],
},
{
name: 'create_comic_character',
module: 'comic',
description:
'Legt einen neuen Comic-Character an OHNE direkt Varianten zu rendern (Splittet Anlegen von Generierung — User reviewt erst). Charakter-Refs werden automatisch aus dem primary face-ref + body-ref des aktiven Space aufgeloest. Stil ist fix nach Anlage. Gibt characterId zurueck — danach generate_character_variant aufrufen.',
defaultPolicy: 'propose',
parameters: [
{ name: 'name', type: 'string', description: 'Name des Characters', required: true },
{
name: 'style',
type: 'string',
description: 'Visueller Stil',
required: true,
enum: ['comic', 'manga', 'cartoon', 'graphic-novel', 'webtoon'],
},
{
name: 'addPrompt',
type: 'string',
description: 'Zusaetzlicher Prompt (z.B. "freundlicher Ausdruck", "casual outfit")',
required: false,
},
{
name: 'description',
type: 'string',
description: 'Kurze Charakter-Beschreibung',
required: false,
},
{ name: 'tags', type: 'string', description: 'Tags durch Komma getrennt', required: false },
],
},
{
name: 'generate_character_variant',
module: 'comic',
description:
'Rendert N (default 4) Variant-Portraits fuer einen existierenden Comic-Character und appended sie an den Variant-Pool. Konsumiert Credits × count (medium=10c). Auto-pinnt die erste Variante wenn noch keine gepinnt ist. Stil + Source-Refs kommen aus dem Character — nur count + quality + model sind hier waehlbar.',
defaultPolicy: 'propose',
parameters: [
{
name: 'characterId',
type: 'string',
description: 'ID des Characters',
required: true,
},
{
name: 'count',
type: 'number',
description: 'Anzahl Varianten (1-4, default 4)',
required: false,
},
{
name: 'quality',
type: 'string',
description: 'Render-Qualitaet — hoeher = mehr Credits',
required: false,
enum: ['low', 'medium', 'high'],
},
{
name: 'model',
type: 'string',
description: 'Rendering-Backend (default openai/gpt-image-2).',
required: false,
enum: [
'openai/gpt-image-2',
'google/gemini-3-pro-image-preview',
'google/gemini-3.1-flash-image-preview',
],
},
],
},
{
name: 'pin_character_variant',
module: 'comic',
description:
'Setzt einen anderen Variant als kanonischen Look des Comic-Characters. Stories die DANACH erstellt werden nutzen den neuen Pin; bestehende Stories bleiben unveraendert (sie haben den alten Variant zum Story-Create-Zeitpunkt fix gespeichert).',
defaultPolicy: 'propose',
parameters: [
{ name: 'characterId', type: 'string', description: 'ID des Characters', required: true },
{
name: 'variantMediaId',
type: 'string',
description: 'ID der Variante die zum neuen Pin werden soll (muss in variantMediaIds sein)',
required: true,
},
],
},
// ── Augur (signs / fortunes / hunches) ──────────────────────
{