#!/usr/bin/env python3 import sys p = "/data/ai/audio/server/src/modules/book-generator/langgraph-controller.ts" src = open(p).read() old = """ setImmediate(async () => { try { await richOutlineNode(initState); // richOutlineNode 内部已通过 bookStore.update 写入 outlineJson await prisma.book.update({ where: { id: idNum }, data: { genStage: 'outline_ready' }, }); console.log(`[Interactive] 大纲生成完成: bookId=${bookId}`); } catch (err) { console.error(`[Interactive] 大纲生成失败: bookId=${bookId}`, err); await prisma.book.update({ where: { id: idNum }, data: { genStage: 'outline_failed', errorMsg: err instanceof Error ? err.message : '大纲生成失败' }, }); } });""" new = """ setImmediate(async () => { try { // 关键修复:richOutlineNode 内部 catch 块会 `return { error, finished: true }` // 而不是抛错,导致外层 try 看不到失败,于是这里把 `failed` 标记覆盖成 `outline_ready`。 // 现在先检查返回值,如果节点内部标记失败就把 error 抛回,让 catch 走正确的分支。 const result: any = await richOutlineNode(initState); if (result && (result.error || result.success === false)) { throw new Error(result.error || 'richOutlineNode 内部失败'); } await prisma.book.update({ where: { id: idNum }, data: { genStage: 'outline_ready' }, }); console.log(`[Interactive] 大纲生成完成: bookId=${bookId}`); } catch (err) { console.error(`[Interactive] 大纲生成失败: bookId=${bookId}`, err); // 节点内部可能已经写了 failed / partial_outline,不要盲目覆盖。 // 仅当数据库仍处于 outline_generating(说明节点没来得及写)时才补写 outline_failed。 const current = await prisma.book.findUnique({ where: { id: idNum } }); if (current && current.genStage === 'outline_generating') { await prisma.book.update({ where: { id: idNum }, data: { genStage: 'outline_failed', errorMsg: err instanceof Error ? err.message : '大纲生成失败' }, }); } else { console.log(`[Interactive] 保留节点内部标记: genStage=${current?.genStage}`); } } });""" if old not in src: print("ERROR: old block not found") sys.exit(1) src = src.replace(old, new, 1) open(p, 'w').write(src) print("patched langgraph-controller.ts OK")