Merge main into Scene 7 evidence workflow

This commit is contained in:
2026-08-22 17:23:47 +02:00
16 changed files with 210 additions and 51 deletions
+9 -3
View File
@@ -33,7 +33,7 @@ const evidenceJudge = createEvidenceJudgeFromEnv()
const levels = createLevelRepository(pool, editingEnabled, objectStorage, evidenceJudge)
const narrative = createNarrativeRepository(pool, objectStorage)
const storyGraph = createStoryGraphRepository(pool)
const STORY_NODE_TYPES: StoryNodeType[] = ['cutscene', 'dialogue', 'level', 'det_gate', 'llm_gate']
const STORY_NODE_TYPES: StoryNodeType[] = ['cutscene', 'dialogue', 'level', 'det_gate', 'llm_gate', 'merit', 'phone']
function wantsEdit(req: express.Request) {
return editingEnabled && req.query.edit === '1' && hasAdminClaim(req)
@@ -314,13 +314,13 @@ app.post('/api/admin/npcs', requireAdmin, async (req, res, next) => {
try {
if (!requireEditing(res)) return
if (!req.body?.key) return res.status(400).json({ error: 'An NPC key is required' })
res.status(201).json(await narrative.createNpc({ key: String(req.body.key), name: String(req.body.name || ''), role: String(req.body.role || ''), defaultPose: req.body.defaultPose || null }))
res.status(201).json(await narrative.createNpc({ key: String(req.body.key), name: String(req.body.name || ''), role: String(req.body.role || ''), defaultPose: req.body.defaultPose || null, phoneNumber: req.body.phoneNumber ?? null, email: req.body.email ?? null }))
} catch (error) { next(error) }
})
app.patch('/api/admin/npcs/:id', requireAdmin, async (req, res, next) => {
try {
if (!requireEditing(res)) return
const npc = await narrative.updateNpc(String(req.params.id), { name: req.body?.name, role: req.body?.role, defaultPose: req.body?.defaultPose })
const npc = await narrative.updateNpc(String(req.params.id), { name: req.body?.name, role: req.body?.role, defaultPose: req.body?.defaultPose, phoneNumber: req.body?.phoneNumber, email: req.body?.email })
npc ? res.json(npc) : res.status(404).json({ error: 'NPC not found' })
} catch (error) { next(error) }
})
@@ -451,6 +451,12 @@ app.delete('/api/admin/utterances/:id', requireAdmin, async (req, res, next) =>
} catch (error) { next(error) }
})
// Play mode: the launchable case list for the splash picker (entrypoint required).
app.get('/api/mysteries', async (_req, res, next) => {
try { res.json(await narrative.listPlayableMysteries()) }
catch (error) { next(error) }
})
// Narrative authoring: create a mystery and its NPC cast. The flow (cutscenes,
// dialogue, levels) lives in the story graph, seeded separately.
app.post('/api/mysteries', requireAdmin, async (req, res, next) => {