# StoryMoss (草苔) v0.30.45 架构文档 > **v0.30.45**:修复文思活跃模式续写提示词泄露(LLM 思维链泄露到正文)。根因四层叠加:①`llm/openai.rs` 的 `resolve_content`(v0.30.25 引入,当时为修复 DeepSeek 推理模型空 content 静默失败而加的 `reasoning_content` fallback:`Message`/`OpenAiDelta` 结构体加 `reasoning_content: Option` 字段,非流式/流式在 `content` 为空时 fallback 到 `reasoning_content`)在 `content` 为空时错误回退到 `reasoning_content`(CoT),把思维链当正文返回--当推理模型把全部 token 预算花在 CoT 上时 `content` 恒为空,fallback 恰好把 CoT 当作"正文"投递给前端;②`max_tokens: 2048` 对推理模型过小,CoT 耗尽全部 token 预算导致 `content` 恒为空、整段被 CoT 占据;③`sanitize_novel_output`(v0.23.36 引入,逐行去 markdown/截断尾部元评论/剥离前导过渡语/去整行小节标题批注)无法识别裸 CoT 思维链(无固定围栏/标记,纯推理行);④writer 提示词从未显式禁止推理输出。修复:①移除 `resolve_content` 的 `reasoning_content` 回退(`content` 为空即返回空,不再用 CoT 兜底--v0.30.25 的 fallback 本是为避免"空 content 静默失败",但推理模型空 content 多因 CoT 耗尽预算,fallback 反而把 CoT 当正文,弊大于利);②`max_tokens` 2048 -> 4096,给推理模型留足正文预算;③新增 `detect_and_strip_bare_cot`(检测 ≥3 条 CoT 信号行触发剥离),接入 `sanitize_novel_output` 后处理;④writer 提示词新增反推理指令(禁止输出思考过程/推理链)。验证:`cargo test --lib` 1091 passed / 2 ignored(+4);`npx vitest run` 352 passed / 3 skipped;`cargo clippy --lib` 539(零新增);`tsc`/`fmt`/`architecture_guard`/`format:check` 全绿。 > **v0.30.44**:修复文思活跃模式续写报"生成过程异常结束,未收到有效内容"。根因:`smartExecuteInFlightRef.current = false` 在 smartExecute resolve 后、内容处理前被提前清除(`handleRequestGeneration` line ~3231 + `handleSmartGeneration` line ~4194)--后台活动同步回调(`useBackendActivityStore` 订阅,100ms 防抖,`startTransition` 包裹 `setIsGenerating`)在内容处理期间(isAlreadyPresent 检查 / active mode 追加 / appendAiContent 同步步骤)把 `isGenerating` 置 false,触发安全网 effect(`!isGenerating && smartExecuteNeedDiagnosticRef.current && !lastGenerationCancelledRef.current` -> `captureDiagnosticInfo('生成过程异常结束,未收到有效内容')`)误报。`handleRequestGeneration` 活跃模式分支还错误地走了打字机幽灵文本(`requestAnimationFrame` 3 字符/帧),而非直接 `appendAiContent` 追加到编辑器正文。修复:①移除两处 smartExecute resolve 后的提前 `smartExecuteInFlightRef.current = false`;改为在各内容交付退出路径(active mode append / isFirstChapterReady / ghost text / aborted / isAlreadyPresent / isBootstrapCompleted&&delivered / 打字机完成 / displayText 空 bail / background bootstrap / genesis 首章)统一清除 `smartExecuteInFlightRef` + `smartExecuteNeedDiagnosticRef`;`handleSmartGeneration` 的 `finally` 块在 `setIsGenerating(false)` 之后兜底清除 flight 标志防泄漏(确保 `setIsGenerating` 触发安全网检查时 flight 仍为 true)。②`handleRequestGeneration` 活跃模式分支在打字机之前直接 `appendAiContent(displayText, 'auto')` + 清除两标志 + `setIsGenerating(false)`,绕过打字机(与 `handleSmartGeneration` 活跃模式行为一致)。纯前端修复(`FrontstageApp.tsx`),无 Rust 变更。验证:`npx vitest run` 352 passed / 3 skipped(+2);`tsc`/`fmt`/`clippy`(538 零新增)/`architecture_guard`/`format:check` 全绿。 > **v0.30.43**:修复续写内容丢失根因--flushSceneSave 读取滞后的 latestContentRef(RichTextEditor 200ms HTML 防抖窗口)而非编辑器实际 HTML,关闭/切章时最后 200ms 的输入丢失;onChapterUpdated 用 DB 旧内容覆写编辑器但不更新 latestContentRef,用户未保存输入被覆写后不可逆丢失。修复:flushSceneSave 改为直接读 editorRef.getHTML()(编辑器实际内容),回写 latestContentRef 保持一致;onChapterUpdated 新增守卫--latestContentRef 与 DB 内容不同时跳过覆写,覆写后同步 latestContentRef。纯前端修复(FrontstageApp.tsx),无 Rust 变更。验证:cargo test --lib 1087 passed;npx vitest run 350 passed / 3 skipped(+1);tsc/fmt/clippy(538 零新增)/architecture_guard/format:check 全绿。 > **v0.30.42**:修复世界观生成失败(LLM 返回 markdown 代码块包裹的 JSON + 未转义引号 + 静默失败 + prompt 字段名不匹配)。根因(issue #14):用户报告"世界观生成失败,请重试",但日志显示 LLM API 调用成功返回内容(7636 字符),失败发生在下游 JSON 解析且完全无错误日志。模型将 JSON 包裹在 ` ```json ... ``` ` 代码块中、或在字符串值内直接换行/使用裸双引号,`serde_json::from_str` 静默失败。①`agents/novel_creation.rs:118` 用严格 `serde_json::from_str` 解析全量响应(含围栏)直接失败;②`agency/coordinator.rs::parse_lenient` 用 `rfind('}')` 会被尾部杂散 `}` 误导且无法修复字符串内裸换行;③`novel_creation_world_options.md` prompt 要求"concepts 数组"但代码读 `parsed["world_buildings"]`,即使解析成功也找不到数组;prompt 缺少格式约束。三层修复:①`parse_lenient` 改为先调 `crate::narrative::extract_and_sanitize_json`(剥离 markdown 围栏 / 推理链、括号深度匹配跳过尾部杂散 `}`、修复字符串内未转义换行、移除 BOM/注释/尾随逗号),失败回退旧首尾花括号截取--覆盖 agency 全部 JSON 解析路径(concept_pack / producer_depth_assets 世界观 / editor 裁决 / retrieval plan);`extract_and_sanitize_json` 已存在于 `narrative` 且被 memory/analysis 等模块使用,`agents` 已有 `crate::narrative::strip_reasoning_blocks` 先例,无新跨层依赖(architecture_guard 仅约束 db/domain,agency→narrative 无环)。②`novel_creation.rs` 提取 `parse_world_options_response` 纯函数先剥离围栏再 `serde_json::from_str`;失败时 `log::warn!` 记录错误 + raw 长度 + 200 字片段(此前完全静默);元素反序列化 `unwrap` 改 `map_err` 不再 panic。③两份 prompt 修正字段名(`concepts` -> `world_buildings`)+ 补全 schema + 新增格式约束(禁 markdown 围栏、引号转义、JSON 外无文字)。验证:`cargo test --lib` 1087 passed / 2 ignored(+5);`npx vitest run` 349 passed / 3 skipped;`tsc`/`fmt`/`clippy`(538 零新增)/`architecture_guard`/`format:check` 全绿。 > **v0.30.41**:修复续写内容被假阳性去重静默丢弃(模型回显指令 + 短文本假阳性 + 内容丢失)。根因:用户诊断报告显示续写生成时 LLM(deepseek-v4)成功返回 2511 字符,但前端仅显示 6 字符("续写\n黑暗。"),随后报"生成过程异常结束,未收到有效内容"。根因链:①模型在生成内容开头回显用户指令"续写"(非正文);②打字机动画首帧仅 3 字符("续写\n"),归一化后 2 字符"续写"几乎必然出现在 9656 字已有正文中;③`isTextDuplicate`(`src-frontend/src/utils/textCleanup.ts`)假阳性返回 true,`setGeneratedText` 跳过赋值并 `markAccepted` 存入 2 字符指纹;④生成内容被静默丢弃。两层修复:①`isTextDuplicate` 新增最小长度守卫--归一化后 < 30 字符的生成文本直接返回 false,不进行去重检查(短文本几乎必然是已有正文的子串,去重无意义且会误杀首帧);②新增 `stripInstructionEcho(generated, userInput)`(`textCleanup.ts`)剥离模型回显的用户指令前缀(按归一化前缀匹配 + 换行/标点边界剥离),在 `handleRequestGeneration` 和 `handleSmartGeneration` 的 `sanitizeContinuationOutput` 后调用。纯前端修复,无 Rust 变更。验证:`npx vitest run` 349 passed / 3 skipped(+13);`tsc`/`format:check`/`architecture_guard` 全绿。 > **v0.30.40**:修复代理工作室不显示活动记录数据(`activeRunId` 仅从事件捕获 + 无 `list_runs` 命令)。根因:`AgencyStudio.tsx`(幕后代理工作室页面)的 `activeRunId` **仅从实时事件捕获**(`agency-agent-activity` / `agency-run-progress` / `agency-board-changed` 三个 `listen` 回调),IPC 查询 `getRun`/`listBoard` 的 `enabled: !!activeRunId`。用户在 run 启动后或完成后才打开页面时,无事件到达,`activeRunId` 恒 null,页面永远显示"暂无活动"。且 `agency_runs` 表虽有持久化数据(`idx_agency_runs_story` 索引),但**无 `list_runs` 命令**供前端发现已有 run;`agency-agent-activity` / `agency-run-progress` 事件 fire-and-forget(`app.emit` only,不写 DB),时间线数据页面卸载即丢失。修复三层:①后端新增 `AgencyRepository::list_runs_for_story(story_id, limit)` + `agency_list_runs` Tauri 命令(`commands.rs`,limit=20,注册到 `handlers.rs`),按 `created_at DESC` 列出 story 的全部 run;②前端 `AgencyStudio.tsx` 新增 `useQuery(['agency-runs', storyId], listRuns)` + `useEffect` 在 runs 到达且 `!activeRunId` 时取 `runs[0].id` 水合(实时事件仍可覆盖);③时间线从仅 live 事件改为三源合并(live 事件 + board items 历史重建 + run 生命周期),`board items` 的 `created_at`/`producer`/`zone`/`key`/`summary` 生成历史时间线条目,`run` 的 `created_at`/`updated_at` 生成生命周期条目,`(at, text)` 去重排序截断 100 条,无需新表/迁移。附带:新增 run 选择器 `