Ver Fonte

feat: 计划执行模式工具层面强制计划阶段仅使用读取类工具

- 标准模式和严格模式统一采用工具层面强制计划执行设计
- 计划阶段过滤写作执行类工具(write_chapter、run_chapter_workflow、apply_skill等)
- 仅保留读取类工具(read_*、list_*、search_chapters、load_context、trim_context、web_search)
- 用户确认计划后(followup阶段)所有工具自动恢复可用
- 系统提示词明确标注计划阶段工具使用限制,形成双重保障
- 移除AI会话写入确认流程,内容直接输出展示
- 修复生成与校验步骤格式校验内容读取问题(从store读取而非闭包变量)
- 修复快速模式下计划执行开关无效问题(快速模式禁用计划执行按钮)
Mochocyang há 2 meses atrás
pai
commit
bdccf186cb
30 ficheiros alterados com 512 adições e 245 exclusões
  1. 6 2
      src/components/chat/chapter-plan-confirm-dialog.tsx
  2. 42 0
      src/components/chat/chat-message.spec.tsx
  3. 2 2
      src/components/chat/chat-message.tsx
  4. 59 40
      src/components/chat/chat-panel.spec.tsx
  5. 34 96
      src/components/chat/chat-panel.tsx
  6. 1 0
      src/hooks/use-agent-config.ts
  7. 22 2
      src/lib/agent/ai-chat-workflow-convergence.spec.ts
  8. 2 2
      src/lib/agent/capabilities/registry.ts
  9. 12 5
      src/lib/agent/capabilities/selector.spec.ts
  10. 17 10
      src/lib/agent/capabilities/selector.ts
  11. 2 2
      src/lib/agent/capabilities/types.ts
  12. 2 2
      src/lib/agent/pipeline.ts
  13. 15 3
      src/lib/agent/plan-execute-policy.spec.ts
  14. 11 8
      src/lib/agent/plan-execute-policy.ts
  15. 25 0
      src/lib/agent/plugins/build-system-prompt-plugin.spec.ts
  16. 4 5
      src/lib/agent/plugins/build-system-prompt-plugin.ts
  17. 160 0
      src/lib/agent/plugins/select-capabilities-plugin.spec.ts
  18. 28 3
      src/lib/agent/plugins/select-capabilities-plugin.ts
  19. 9 14
      src/lib/agent/plugins/select-skills-plugin.spec.ts
  20. 19 16
      src/lib/agent/plugins/select-skills-plugin.ts
  21. 2 2
      src/lib/agent/tools/index.ts
  22. 4 4
      src/lib/agent/tools/run-chapter-workflow.ts
  23. 1 1
      src/lib/agent/workflow-mode.spec.ts
  24. 3 2
      src/lib/agent/workflow-mode.ts
  25. 1 1
      src/lib/llm-client.ts
  26. 5 4
      src/lib/novel/deep-chapter-generation.spec.ts
  27. 9 9
      src/lib/novel/deep-chapter-generation.ts
  28. 2 2
      src/lib/novel/skill-library.ts
  29. 2 0
      src/lib/tauri-fetch.ts
  30. 11 8
      src/stores/wiki-store.ts

+ 6 - 2
src/components/chat/chapter-plan-confirm-dialog.tsx

@@ -1,5 +1,7 @@
 import { useState, useCallback, useEffect, useRef } from "react"
 import { X, Check, SkipForward, Edit3, ListChecks } from "lucide-react"
+import type { AiWorkflowMode, LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode"
+import { getWorkflowModeLabel, resolveAiWorkflowMode } from "@/lib/agent/workflow-mode"
 export { buildChapterPlanSelfCheckPrompt } from "@/lib/novel/chapter-plan-self-check"
 
 export const CHAPTER_PLAN_MARKER_START = "<!-- chapter_plan -->"
@@ -62,7 +64,7 @@ export function isChapterPlanExecutionFollowup(content: string): boolean {
 interface ChapterPlanConfirmDialogProps {
   open: boolean
   planContent: string
-  aiWorkflowMode: "fast" | "standard" | "strict"
+  aiWorkflowMode: LegacyAiWorkflowMode
   onConfirm: () => void
   onSkip: () => void
   onModify?: (modifiedPlan: string) => void
@@ -82,6 +84,8 @@ export function ChapterPlanConfirmDialog({
   onRevisePlan,
   onCancel,
 }: ChapterPlanConfirmDialogProps) {
+  const resolvedAiWorkflowMode: AiWorkflowMode = resolveAiWorkflowMode(aiWorkflowMode)
+  const workflowModeLabel = getWorkflowModeLabel(resolvedAiWorkflowMode)
   const [editing, setEditing] = useState(false)
   const [editedContent, setEditedContent] = useState(planContent)
   const [selfChecking, setSelfChecking] = useState(false)
@@ -169,7 +173,7 @@ export function ChapterPlanConfirmDialog({
             <div>
               <h3 className="font-semibold">章节创作计划</h3>
               <p className="text-xs text-muted-foreground">
-                {aiWorkflowMode === "strict" ? "严格模式" : "标准模式"} · 请确认 AI 的创作计划
+                {workflowModeLabel}模式 · 请确认 AI 的创作计划
               </p>
             </div>
           </div>

+ 42 - 0
src/components/chat/chat-message.spec.tsx

@@ -116,6 +116,48 @@ describe("agent stage stream integration", () => {
     expect(html).toContain("这是最终正文。")
   })
 
+  it("keeps write approval actions visible when structured stages are present", () => {
+    const message: DisplayMessage = {
+      id: "assistant-approval",
+      role: "assistant",
+      content: "等待确认保存。",
+      timestamp: 1,
+      conversationId: "conv-1",
+      agentStages: [
+        {
+          id: "write_confirmation",
+          title: "写入确认",
+          status: "approval_required",
+          summary: "等待用户确认写入。",
+          events: [],
+        },
+      ],
+      agentToolCalls: [
+        {
+          id: "write-approval",
+          name: "write_chapter",
+          params: { name: "第1章", content: "正文" },
+          result: "预览内容",
+          status: "approval_required",
+          startedAt: 1,
+          finishedAt: 2,
+        },
+      ],
+    }
+
+    const html = renderToStaticMarkup(
+      <ChatMessage
+        message={message}
+        onConfirmToolSave={() => {}}
+        onRejectTool={() => {}}
+      />,
+    )
+
+    expect(html).toContain("写入确认")
+    expect(html).toContain("确认保存")
+    expect(html).toContain("放弃")
+  })
+
   it("keeps old tool workflow fallback when structured stages are absent", () => {
     const message: DisplayMessage = {
       id: "assistant-2",

+ 2 - 2
src/components/chat/chat-message.tsx

@@ -161,8 +161,8 @@ export function ChatMessage({
             <>
               {message.agentStages && message.agentStages.length > 0 ? (
                 <AgentStageStream stages={message.agentStages} />
-              ) : message.agentToolCalls &&
-                message.agentToolCalls.length > 0 ? (
+              ) : null}
+              {message.agentToolCalls && message.agentToolCalls.length > 0 ? (
                 <AgentToolCallMessage
                   toolCalls={message.agentToolCalls}
                   contextTrace={message.contextTrace}

+ 59 - 40
src/components/chat/chat-panel.spec.tsx

@@ -83,19 +83,23 @@ describe("chat-panel agent reference integration", () => {
     expect(source).toContain("不要输出读取说明、执行总结、完成目标表格、章节结构、后续建议")
   })
 
-  it("uses the three-level AI workflow mode instead of a single deep mode prompt", () => {
+  it("uses three AI workflow modes instead of a single deep mode prompt", () => {
     expect(source).toContain("aiWorkflowMode")
     expect(source).toContain("setAiWorkflowMode")
     expect(source).toContain("快速模式")
     expect(source).toContain("标准模式")
     expect(source).toContain("严格模式")
+    expect(source).toContain('label: "标准"')
+    expect(source).toContain('mode: "standard"')
     expect(source).not.toContain("用户已开启深度模式,请在必要时进行更完整的章节规划和资料读取。")
   })
 
   it("shows route descriptions in the workflow mode menu", () => {
+    expect(source).toContain("description: \"普通对话")
     expect(source).toContain("description: \"轻量直出")
-    expect(source).toContain("description: \"基础收尾")
     expect(source).toContain("description: \"完整质检")
+    expect(source).toContain("快速模式像普通对话一样直接出结果")
+    expect(source).toContain("读取上下文、生成任务书和正文初稿后直接完成")
     expect(source).toContain("workflowModeDropdownStyle.width")
     expect(source).toContain("routeDescription")
   })
@@ -109,25 +113,29 @@ describe("chat-panel agent reference integration", () => {
     expect(source).toContain("prePluginResult?.selectedSkills")
   })
 
-  it("keeps Plan Execute as an independent switch outside fast standard strict modes", () => {
+  it("keeps Plan Execute as an independent switch outside fast and strict modes", () => {
     expect(source).toContain("planExecuteEnabled")
     expect(source).toContain("setPlanExecuteEnabled")
     expect(source).toContain("aiSessionPlanExecuteLabel")
     expect(source).toContain("计划执行")
-    expect(source).toContain("aria-pressed={planExecuteEnabled}")
+    expect(source).toContain("aria-pressed={planExecuteEnabled && aiWorkflowMode !== \"fast\"}")
+    expect(source).toContain("disabled={aiWorkflowMode === \"fast\"}")
   })
 
-  it("injects Plan Execute policy only when the independent switch is enabled", () => {
+  it("injects Plan Execute policy only when the independent switch is enabled and not in fast mode", () => {
     expect(source).toContain("buildPlanExecutePolicyPrompt")
-    expect(source).toContain("options.planExecuteEnabled")
+    expect(source).toContain("options.planExecuteEnabled && options.aiWorkflowMode !== \"fast\"")
     expect(source).toContain("run_chapter_workflow")
     expect(source).not.toContain("标准模式:复杂小说任务先给出简短计划")
     expect(source).not.toContain("严格模式:复杂小说任务必须先规划")
   })
 
-  it("makes standard mode include final simple review and de-AI polish without implying Plan Execute", () => {
-    expect(source).toContain("标准模式:读取必要上下文,生成正文后执行基础自检与简单去AI味。")
-    expect(source).not.toContain("标准模式:复杂小说任务先给出简短计划")
+  it("makes fast mode behave like ordinary chat without automatic skill or review loop", () => {
+    expect(source).toContain("快速模式:像普通对话一样直接出结果")
+    expect(source).toContain("不自动启用 Skill")
+    expect(source).toContain("不走多任务写作循环")
+    expect(source).toContain("不分析剧情走向")
+    expect(source).toContain("标准模式:读取上下文,生成任务书和正文初稿后直接完成,不做正文后审核。")
   })
 
   it("keeps configured reasoning without imposing an app-side output token cap for chapter generation", () => {
@@ -192,13 +200,27 @@ describe("chat-panel agent reference integration", () => {
     expect(source).toContain('rawTaskRoute.intent !== "general_chat"')
     expect(source).toContain("const taskRoute = shouldRunNovelPrePluginChain ? rawTaskRoute : null")
 
-    const guardIndex = source.indexOf("const shouldRunNovelPrePluginChain")
+    const guardIndex = source.lastIndexOf("const shouldRunNovelPrePluginChain")
     const runIndex = source.indexOf("await runNovelPrePluginChain({")
-    const virtualRouteIndex = source.indexOf("if (novelMode && effectiveTaskRoute)")
+    const guardedRunIndex = source.indexOf("if (shouldRunNovelPrePluginChain && effectiveTaskRoute)")
 
     expect(guardIndex).toBeGreaterThan(-1)
     expect(runIndex).toBeGreaterThan(-1)
-    expect(virtualRouteIndex).toBeLessThan(runIndex)
+    expect(guardedRunIndex).toBeGreaterThan(guardIndex)
+    expect(guardedRunIndex).toBeLessThan(runIndex)
+  })
+
+  it("does not run the novel pre-plugin chain in fast mode even when Plan Execute is enabled", () => {
+    expect(source).toContain("aiWorkflowMode !== \"fast\" && planExecuteEnabled")
+    const planExecuteActiveMatch = source.match(/planExecuteActive\s*=/)
+    expect(planExecuteActiveMatch).not.toBeNull()
+    const planExecuteActiveLine = source.slice(
+      (planExecuteActiveMatch?.index ?? 0),
+      (planExecuteActiveMatch?.index ?? 0) + 200,
+    )
+    expect(planExecuteActiveLine).toContain("aiWorkflowMode !== \"fast\"")
+    expect(planExecuteActiveLine).toContain("planExecuteEnabled")
+    expect(planExecuteActiveLine).toContain("planExecutionFollowup")
   })
 
   it("passes workflow mode and available skills into the novel pre-plugin chain", () => {
@@ -218,14 +240,10 @@ describe("chat-panel agent reference integration", () => {
     expect(source).toContain("prePluginSystemPrompt")
   })
 
-  it("skips the character soul confirmation dialog in fast workflow mode", () => {
-    expect(source).toContain('aiWorkflowMode !== "fast"')
-    expect(source).toContain('contextPack.characterAuras.trim()')
-    const guardIndex = source.indexOf('aiWorkflowMode !== "fast"')
-    const requestIndex = source.indexOf("await requestSoulDialog(contextPack.characterAuras)")
-    expect(guardIndex).toBeGreaterThan(-1)
-    expect(requestIndex).toBeGreaterThan(-1)
-    expect(guardIndex).toBeLessThan(requestIndex)
+  it("does not show the character soul confirmation dialog in any workflow mode", () => {
+    expect(source).not.toContain("pendingSoulDialog")
+    expect(source).not.toContain("requestSoulDialog")
+    expect(source).not.toContain("本次写作将注入角色灵魂上下文")
   })
 
   it("passes MCP capabilities from agent config into the novel pre-plugin chain", () => {
@@ -272,7 +290,7 @@ describe("chat-panel agent reference integration", () => {
 
   it("settles visible tool calls when generation is cancelled from any chat confirmation path", () => {
     expect(source).toContain('agentToolCalls: settleRunningAgentToolCalls(message.agentToolCalls, "cancelled")')
-    expect(source).toContain("content: \"已取消本次生成,角色灵魂上下文未发送给模型。\"")
+    expect(source).toContain("已取消计划执行,未进入正文生成。")
     expect(source).toContain("已停止生成。")
   })
 
@@ -304,14 +322,12 @@ describe("chat-panel agent reference integration", () => {
     expect(source).not.toContain("await runDeepChapterGeneration(")
   })
 
-  it("validates chapter content before confirming draft saves", () => {
-    const confirmIndex = source.indexOf("const handleConfirmToolSave")
-    const validationIndex = source.indexOf("validateChapterBeforeSave", confirmIndex)
-    const confirmDraftIndex = source.indexOf("confirmDraft(project.path", confirmIndex)
-
-    expect(confirmIndex).toBeGreaterThan(-1)
-    expect(validationIndex).toBeGreaterThan(confirmIndex)
-    expect(validationIndex).toBeLessThan(confirmDraftIndex)
+  it("does not include write tool confirmation handlers (write tools disabled)", () => {
+    expect(source).not.toContain("const handleConfirmToolSave")
+    expect(source).not.toContain("extractDraftIdFromWritePreview")
+    expect(source).not.toContain("confirmDraft(project.path, draftId")
+    expect(source).not.toContain("onConfirmToolSave={handleConfirmToolSave}")
+    expect(source).not.toContain("onRejectTool={handleRejectTool}")
   })
 
   it("keeps up to three working or today's conversations in the top toolbar and moves the rest into history", () => {
@@ -390,9 +406,9 @@ describe("chat-panel chapter plan confirm integration (Stage C)", () => {
     expect(afterPlanConfirm).toContain("setActiveConversation(capturedConvId)")
   })
 
-  it("disables Plan Execute protocol for confirmed plan follow-up messages to avoid planning loops", () => {
+  it("disables Plan Execute protocol for confirmed plan follow-up messages and fast mode", () => {
     expect(source).toContain("isChapterPlanExecutionFollowup")
-    expect(source).toContain("const planExecuteActive = planExecuteEnabled && !planExecutionFollowup")
+    expect(source).toContain("aiWorkflowMode !== \"fast\" && planExecuteEnabled && !planExecutionFollowup")
     expect(source).toContain("planExecuteEnabled: planExecuteActive")
   })
 
@@ -437,11 +453,14 @@ describe("chat-panel post-write check integration (Stage D)", () => {
     expect(stageDBlock).not.toContain("rewrite_chapter")
   })
 
-  it("reads the final assistant content from the store (same as Stage C)", () => {
+  it("reads the final assistant content from the store before Stage D (shared with format validation)", () => {
     const stageDIndex = source.indexOf("=== Stage D: 写后剧情自检 ===")
+    const beforeStageDBlock = source.slice(0, stageDIndex)
+    expect(beforeStageDBlock).toContain("useChatStore.getState()")
+    expect(beforeStageDBlock).toContain("lastAssistantForValidation")
+    expect(beforeStageDBlock).toContain("finalContent = lastAssistantForValidation?.content ??")
     const stageDBlock = source.slice(stageDIndex, stageDIndex + 1200)
-    expect(stageDBlock).toContain("useChatStore.getState()")
-    expect(stageDBlock).toContain("lastAssistant")
+    expect(stageDBlock).toContain("const chapterContent = finalContent")
   })
 
   it("excludes content carrying the chapter_plan marker", () => {
@@ -496,16 +515,16 @@ describe("aiWorkflowMode store 读取", () => {
 })
 
 describe("resolver 卸载清理", () => {
-  it("useEffect 卸载钩子中清理 pending resolver", () => {
-    expect(source).toMatch(/soulDialogResolverRef\.current = null/)
+  it("useEffect 卸载钩子中清理章节计划 pending resolver", () => {
     expect(source).toMatch(/chapterPlanResolverRef\.current = null/)
-    expect(source).toMatch(/return \(\) => \{[\s\S]*?ResolverRef\.current/)
+    expect(source).toMatch(/return \(\) => \{[\s\S]*?chapterPlanResolverRef\.current/)
   })
 })
 
-describe("SoulDialog 输入框禁用一致性", () => {
-  it("pendingSoulDialog.open 时禁用主输入框", () => {
-    expect(source).toMatch(/disabled=\{isStreaming \|\| pendingChapterPlan\.open \|\| pendingSoulDialog\.open\}/)
+describe("章节计划弹窗输入框禁用一致性", () => {
+  it("pendingChapterPlan.open 时禁用主输入框", () => {
+    expect(source).toMatch(/disabled=\{isStreaming \|\| pendingChapterPlan\.open\}/)
+    expect(source).not.toContain("pendingSoulDialog.open")
   })
 })
 

+ 34 - 96
src/components/chat/chat-panel.tsx

@@ -1,9 +1,8 @@
-import { useRef, useEffect, useCallback, useState, useMemo, type CSSProperties } from "react"
+import { useRef, useEffect, useCallback, useState, useMemo, type CSSProperties } from "react"
 import { createPortal } from "react-dom"
 import { useTranslation } from "react-i18next"
 import { BookOpen, Plus, Trash2, MessageSquare, FileEdit, Drama, ListChecks, ChevronDown, Check, History } from "lucide-react"
 import { Button } from "@/components/ui/button"
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"
 import { ChatMessage, StreamingMessage } from "./chat-message"
 import { ChatDockControls } from "./chat-dock-controls"
@@ -100,8 +99,6 @@ import { buildInitialContextTraceInfo } from "@/lib/agent/context-trace-builders
 import { runPostWriteCheckAI } from "@/lib/agent/plugins/post-write-check-ai"
 import { buildSelectedSkillsPrompt } from "@/lib/agent/plugins/select-skills-plugin"
 import { buildResultProtocolTrace } from "@/lib/novel/result-parser"
-import { validateChapterBeforeSave } from "@/lib/novel/result-save-guard"
-import { confirmDraft } from "@/lib/novel/draft-manager"
 // import { getLoadedCategories, DATA_SOURCE_CATEGORY_LABELS } from "@/lib/novel/classification"
 // import { RetrievalStore } from "@/lib/novel/retrieval"
 // import { RetrievalStatusIndicator } from "@/components/novel/retrieval-status-indicator"
@@ -127,14 +124,14 @@ const aiWorkflowModeOptions: Array<{
   {
     mode: "fast",
     label: "快速",
-    description: "轻量直出",
-    routeDescription: "读取上下文、生成任务书和正文初稿后直接完成,不做正文后审核。",
+    description: "普通对话",
+    routeDescription: "快速模式像普通对话一样直接出结果,可读取上下文,但不自动启用 Skill、不走多任务写作循环、不分析剧情走向。",
   },
   {
     mode: "standard",
     label: "标准",
-    description: "基础收尾",
-    routeDescription: "读取必要上下文,生成正文后做简单审查、去AI味和计划验收。",
+    description: "轻量直出",
+    routeDescription: "读取上下文、生成任务书和正文初稿后直接完成,不做正文后审核。",
   },
   {
     mode: "strict",
@@ -283,21 +280,25 @@ function buildChatAgentSystemPrompt(options: {
     lines.push("小说模式下,如果用户要求生成、续写或改写章节,只输出可直接放入章节库的正文。")
     lines.push("章节生成、续写或改写任务的最终回复必须只包含章节正文,不要把工具读取过程、写作计划或执行过程展示给用户。")
     lines.push("不要输出读取说明、执行总结、完成目标表格、章节结构、后续建议、引用来源或 Markdown 表格;章节标题和正文以外的内容都不要输出。")
-    lines.push("章节生成、续写、改写或润色应优先调用 run_chapter_workflow 工具。")
+    if (options.aiWorkflowMode === "fast") {
+      lines.push("快速模式下可以读取必要上下文;除非用户明确要求使用工作流或 Skill,否则不要主动调用 run_chapter_workflow。")
+    } else {
+      lines.push("章节生成、续写、改写或润色应优先调用 run_chapter_workflow 工具。")
+    }
   }
   if (options.aiWorkflowMode) {
     switch (options.aiWorkflowMode) {
       case "fast":
-        lines.push("快速模式:优先直接回答或生成,减少非必要分析。")
+        lines.push("快速模式:像普通对话一样直接出结果,不自动启用 Skill,不走多任务写作循环,不分析剧情走向。")
         break
       case "standard":
-        lines.push("标准模式:读取必要上下文,生成正文后执行基础自检与简单去AI味。")
+        lines.push("标准模式:读取上下文,生成任务书和正文初稿后直接完成,不做正文后审核。")
         break
       case "strict":
         lines.push("严格模式:读取更完整上下文,执行更严格的审稿、返修和一致性检查。如果有外部搜索需求,必须使用 web_search 工具,不得声称已经搜索。未使用联网资料时,在回复末尾注明。")
         break
       }
-    if (options.planExecuteEnabled) {
+    if (options.planExecuteEnabled && options.aiWorkflowMode !== "fast") {
       lines.push(buildPlanExecutePolicyPrompt(options.aiWorkflowMode))
     }
   }
@@ -692,7 +693,6 @@ export function ChatPanel() {
   const activeStreamSessionsRef = useRef<Record<string, number>>({})
   const scrollContainerRef = useRef<HTMLDivElement>(null)
   const bottomRef = useRef<HTMLDivElement>(null)
-  const soulDialogResolverRef = useRef<((confirmed: boolean) => void) | null>(null)
   const userScrolledUpRef = useRef(false)
   const lastScrollTopRef = useRef(0)
 
@@ -707,7 +707,6 @@ export function ChatPanel() {
   const planExecuteEnabled = useWikiStore((s) => s.planExecuteEnabled)
   const setPlanExecuteEnabled = useWikiStore((s) => s.setPlanExecuteEnabled)
   const [isSavingChapter, setIsSavingChapter] = useState(false)
-  const [pendingSoulDialog, setPendingSoulDialog] = useState({ open: false, summary: "" })
   const deepChapterEnabled = useWikiStore((s) => s.deepChapterEnabled)
   // 故事框架绑定状态
   const [activeBinding, setActiveBinding] = useState<{ binding: FrameworkBinding; framework: StoryFramework } | null>(null)
@@ -808,7 +807,7 @@ export function ChatPanel() {
         deepChapterEnabled,
         chatEditModeEnabled,
         aiWorkflowMode,
-        planExecuteEnabled,
+        planExecuteEnabled: aiWorkflowMode !== "fast" && planExecuteEnabled,
         projectName: project?.name,
         bindingTitle: activeBinding?.framework.title,
       }),
@@ -890,20 +889,6 @@ export function ChatPanel() {
     ],
     [agentSkillConfig, conversations, outlineConversations],
   )
-  const closeSoulDialog = useCallback((confirmed: boolean) => {
-    const resolver = soulDialogResolverRef.current
-    soulDialogResolverRef.current = null
-    setPendingSoulDialog({ open: false, summary: "" })
-    resolver?.(confirmed)
-  }, [])
-
-  const requestSoulDialog = useCallback((summary: string) => {
-    setPendingSoulDialog({ open: true, summary })
-    return new Promise<boolean>((resolve) => {
-      soulDialogResolverRef.current = resolve
-    })
-  }, [])
-
   // === Stage C: 章节计划确认 ===
   const [pendingChapterPlan, setPendingChapterPlan] = useState<{
     open: boolean
@@ -927,10 +912,6 @@ export function ChatPanel() {
 
   useEffect(() => {
     return () => {
-      if (soulDialogResolverRef.current) {
-        soulDialogResolverRef.current(false)
-        soulDialogResolverRef.current = null
-      }
       if (chapterPlanResolverRef.current) {
         chapterPlanResolverRef.current("cancel")
         chapterPlanResolverRef.current = null
@@ -1095,7 +1076,8 @@ export function ChatPanel() {
       const plainText = text.trim()
       const userVisibleText = (displayText ?? plainText).trim()
       const planExecutionFollowup = isChapterPlanExecutionFollowup(plainText)
-      const planExecuteActive = planExecuteEnabled && !planExecutionFollowup
+      const planExecuteActive =
+        aiWorkflowMode !== "fast" && planExecuteEnabled && !planExecutionFollowup
       setDeAiSkillWarningMessage("")
 
       if (!plainText) {
@@ -1208,7 +1190,7 @@ export function ChatPanel() {
       void contextPack
       let novelContextPrompt: string = ""
       let prePluginResult: PrePluginChainResult | null = null
-      const shouldRunNovelPrePluginChain = novelMode
+      const shouldRunNovelPrePluginChain = novelMode && (aiWorkflowMode !== "fast" || planExecuteActive)
       void shouldRunNovelPrePluginChain
       abortControllersRef.current[capturedConvId] = controller
       let hasAgentError = false
@@ -1281,7 +1263,7 @@ export function ChatPanel() {
           }
         : taskRoute
 
-      if (novelMode && effectiveTaskRoute) {
+      if (shouldRunNovelPrePluginChain && effectiveTaskRoute) {
         try {
           prePluginResult = await runNovelPrePluginChain({
             input: {
@@ -1376,21 +1358,6 @@ export function ChatPanel() {
             nextChapterAdvice: "",
             revisionDirectives: "",
           }))
-          if (aiWorkflowMode !== "fast" && contextPack.characterAuras.trim()) {
-            const confirmed = await requestSoulDialog(contextPack.characterAuras)
-            if (!confirmed) {
-              finishAgentSession(() => {
-                updateAgentAssistantMessage(assistantMessage.id, (message) => ({
-                  ...message,
-                  content: "已取消本次生成,角色灵魂上下文未发送给模型。",
-                  agentToolCalls: settleRunningAgentToolCalls(message.agentToolCalls, "cancelled"),
-                  agentStages: settleRunningAgentStages(message.agentStages, "cancelled"),
-                  isAgentRunning: false,
-                }))
-              })
-              return
-            }
-          }
           const novelConfig = useWikiStore.getState().novelConfig
           const budget = novelConfig.contextTokenBudget > 0 ? novelConfig.contextTokenBudget : undefined
           novelContextPrompt = [
@@ -1506,7 +1473,11 @@ export function ChatPanel() {
             if (contextTrace && effectiveTaskRoute) {
               const traceInfo = buildInitialContextTraceInfo(effectiveTaskRoute, prePluginResult, { workflowMode: aiWorkflowMode })
               contextTrace = setContextInfo(contextTrace, traceInfo)
-              const finalContent = assistantMessage.content || ""
+              const storeStateForValidation = useChatStore.getState()
+              const lastAssistantForValidation = storeStateForValidation.messages.find(
+                (m) => m.id === assistantMessage.id && m.role === "assistant",
+              )
+              const finalContent = lastAssistantForValidation?.content ?? ""
               if (finalContent) {
                 const protocolTrace = buildResultProtocolTrace("chapter", finalContent)
                 contextTrace = setContextInfo(contextTrace, { ...traceInfo, resultProtocol: protocolTrace })
@@ -1517,12 +1488,7 @@ export function ChatPanel() {
                 effectiveTaskRoute.intent === "write_chapter" ||
                 effectiveTaskRoute.intent === "continue_chapter"
               ) {
-                // 与 Stage C 一致:从 store 读取最终内容(闭包 assistantMessage 不会随流式更新)
-                const storeState = useChatStore.getState()
-                const lastAssistant = storeState.messages.find(
-                  (m) => m.id === assistantMessage.id && m.role === "assistant",
-                )
-                const chapterContent = lastAssistant?.content ?? ""
+                const chapterContent = finalContent
                 // 排除含 chapter_plan 标记的内容(计划本身不是正文)与空内容
                 const hasChapterPlanMarker = chapterContent.includes("chapter_plan")
                 if (chapterContent && !hasChapterPlanMarker) {
@@ -1621,7 +1587,6 @@ export function ChatPanel() {
       projectPath,
       referenceDraftConversationId,
       requestChapterPlanConfirm,
-      requestSoulDialog,
       selectedFile,
       setActiveConversation,
       setConversationInputDraft,
@@ -1715,19 +1680,6 @@ export function ChatPanel() {
     await handleSendRef.current(prompt, [], "继续未完成")
   }, [isStreaming])
 
-
-
-  const handleConfirmToolSave = useCallback(async (_projectPath: string) => {
-    // validate chapter before confirming save
-    const draft = ""
-    const validation = validateChapterBeforeSave(draft)
-    if (!validation.ok) {
-      console.warn("Chapter validation failed:", validation.trace)
-      return    }
-    if (!project) return
-    await confirmDraft(project.path, draft)
-  }, [])
-  void handleConfirmToolSave
   const handleWriteToWiki = useCallback(async () => {
     if (!project) return
     const pp = normalizePath(project.path)
@@ -1913,15 +1865,16 @@ export function ChatPanel() {
                               type="button"
                               variant="ghost"
                               size="sm"
-                              aria-pressed={planExecuteEnabled}
+                              aria-pressed={planExecuteEnabled && aiWorkflowMode !== "fast"}
+                              disabled={aiWorkflowMode === "fast"}
                               className={`h-8 shrink-0 rounded-full border px-2.5 text-xs ${
-                                planExecuteEnabled
+                                planExecuteEnabled && aiWorkflowMode !== "fast"
                                   ? "border-primary bg-primary text-primary-foreground shadow-sm hover:bg-primary/90 hover:text-primary-foreground"
                                   : "border-border text-muted-foreground hover:bg-accent hover:text-foreground"
-                              }`}
+                              } disabled:cursor-not-allowed disabled:opacity-50`}
                               onClick={() => setPlanExecuteEnabled(!planExecuteEnabled)}
-                              title={planExecuteEnabled ? "关闭计划执行模式" : "开启计划执行模式"}
-                              aria-label={planExecuteEnabled ? "关闭计划执行模式" : "开启计划执行模式"}
+                              title={aiWorkflowMode === "fast" ? "快速模式下不支持计划执行,请切换到标准或严格模式" : planExecuteEnabled ? "关闭计划执行模式" : "开启计划执行模式"}
+                              aria-label={aiWorkflowMode === "fast" ? "快速模式下不支持计划执行,请切换到标准或严格模式" : planExecuteEnabled ? "关闭计划执行模式" : "开启计划执行模式"}
                             />
                           )}
                         >
@@ -1929,7 +1882,9 @@ export function ChatPanel() {
                           计划执行
                         </TooltipTrigger>
                         <TooltipContent side="top" className="max-w-xs leading-5">
-                          开启后,本次写作会先创建计划,等待确认后再执行;可与快速、标准、严格任一模式组合使用。
+                          {aiWorkflowMode === "fast"
+                            ? "快速模式下不支持计划执行,请切换到标准或严格模式。"
+                            : "开启后,本次写作会先创建计划,等待确认后再执行;可与标准、严格模式组合使用。"}
                         </TooltipContent>
                       </Tooltip>
                       <Tooltip>
@@ -2001,7 +1956,7 @@ export function ChatPanel() {
             <ReferenceInput
               value={referenceText}
               tokens={currentTokens}
-              disabled={isStreaming || pendingChapterPlan.open || pendingSoulDialog.open}
+              disabled={isStreaming || pendingChapterPlan.open}
               isStreaming={isStreaming}
               onStop={handleStop}
               rightControls={
@@ -2036,23 +1991,6 @@ export function ChatPanel() {
             onClose={() => setReferencePickerOpen(false)}
           />
         </div>
-        <Dialog open={pendingSoulDialog.open} onOpenChange={(open) => { if (!open) closeSoulDialog(false) }}>
-          <DialogContent>
-            <DialogHeader>
-              <DialogTitle>本次写作将注入角色灵魂上下文</DialogTitle>
-              <DialogDescription>
-                下列内容会进入本次写作上下文包。角色灵魂会增强人物气质、语言倾向和判断方式,但仍服从大纲、人物小传与当前剧情。
-              </DialogDescription>
-            </DialogHeader>
-            <div className="max-h-72 overflow-y-auto rounded-md border bg-muted/20 p-3 text-xs leading-6 text-muted-foreground whitespace-pre-wrap">
-              {pendingSoulDialog.summary}
-            </div>
-            <DialogFooter>
-              <Button variant="outline" onClick={() => closeSoulDialog(false)}>取消本次生成</Button>
-              <Button onClick={() => closeSoulDialog(true)}>继续生成</Button>
-            </DialogFooter>
-          </DialogContent>
-        </Dialog>
         {pendingChapterPlan.open && (
           <ChapterPlanConfirmDialog
             open={pendingChapterPlan.open}

+ 1 - 0
src/hooks/use-agent-config.ts

@@ -145,6 +145,7 @@ export function useAgentConfig(systemPrompt: string, getPlanBlueprint?: () => st
       draftMode: novelMode,
       projectPath: normalizePath(projectPath),
       getPlanBlueprint,
+      disabledTools: ["write_chapter", "write_outline_node", "write_memory"],
     })
 
     return {

+ 22 - 2
src/lib/agent/ai-chat-workflow-convergence.spec.ts

@@ -147,10 +147,11 @@ describe("AI chat workflow convergence", () => {
       "trim_context",
     ]))
     expect(result.enabledToolNames).not.toContain("write_chapter")
-    expect(result.selectedCapabilities).toContainEqual(expect.objectContaining({ kind: "user_skill", skillId: "output" }))
+    expect(result.enabledToolNames).not.toContain("run_chapter_workflow")
+    expect((result.selectedCapabilities ?? []).some((item) => item.kind === "user_skill")).toBe(false)
   })
 
-  it("standard next chapter enables writing context, selected skill, and write confirmation tool", async () => {
+  it("standard next chapter uses the old fast lightweight workflow route", async () => {
     const result = await runWorkflow({
       userMessage: "帮我写下一章",
       intent: "write_chapter",
@@ -158,6 +159,25 @@ describe("AI chat workflow convergence", () => {
       skills: [threeTurnsSkill(), outputSkill()],
     })
 
+    expect(result.enabledToolNames).toEqual(expect.arrayContaining([
+      "read_chapter",
+      "read_outline",
+      "load_context",
+      "trim_context",
+    ]))
+    expect(result.enabledToolNames).not.toContain("write_chapter")
+    expect(result.enabledToolNames).not.toContain("apply_skill")
+    expect(result.selectedCapabilities).toContainEqual(expect.objectContaining({ kind: "user_skill", skillId: "output" }))
+  })
+
+  it("strict next chapter enables writing workflow, selected skill, and write confirmation tool", async () => {
+    const result = await runWorkflow({
+      userMessage: "帮我写下一章",
+      intent: "write_chapter",
+      mode: "strict",
+      skills: [threeTurnsSkill(), outputSkill()],
+    })
+
     expect(result.enabledToolNames).toEqual(expect.arrayContaining([
       "read_chapter",
       "read_outline",

+ 2 - 2
src/lib/agent/capabilities/registry.ts

@@ -1,8 +1,8 @@
 import type { UserSkill } from "@/lib/novel/skill-library"
 import type { AiCapability, CapabilityIntent, CapabilityKind, CapabilityPermission } from "./types"
-import type { AiWorkflowMode } from "../workflow-mode"
+import type { LegacyAiWorkflowMode } from "../workflow-mode"
 
-const ALL_MODES: AiWorkflowMode[] = ["fast", "standard", "strict"]
+const ALL_MODES: LegacyAiWorkflowMode[] = ["fast", "standard", "strict"]
 
 const WRITING_INTENTS: CapabilityIntent[] = [
   "write_chapter",

+ 12 - 5
src/lib/agent/capabilities/selector.spec.ts

@@ -59,7 +59,7 @@ describe("AI capability selector", () => {
     expect(JSON.stringify(capabilities)).not.toContain("private skill content")
   })
 
-  it("keeps fast writing mode to minimal read/context tools and selected output skills", () => {
+  it("keeps fast writing mode to minimal read/context tools without selected skills", () => {
     const outputSkill = normalizeUserSkill({
       id: "output",
       name: "Output Protocol",
@@ -86,8 +86,8 @@ describe("AI capability selector", () => {
       "tool:read_outline",
       "tool:load_context",
       "tool:trim_context",
-      "skill:output",
     ]))
+    expect(selected.map((item) => item.id)).not.toContain("skill:output")
     expect(selected.some((item) => item.kind === "web_search")).toBe(false)
     expect(selected.some((item) => item.kind === "mcp_tool")).toBe(false)
     expect(JSON.stringify(selected)).not.toContain("private output instructions")
@@ -117,21 +117,28 @@ describe("AI capability selector", () => {
     ]))
   })
 
-  it("selects chapter workflow tool for chapter writing intents", () => {
+  it("selects chapter workflow tool for standard and strict chapter writing intents", () => {
     const capabilities = buildAvailableCapabilities({
       toolNames: ["read_chapter", "run_chapter_workflow"],
       selectedSkills: [],
       mcpCapabilities: [],
     })
 
-    const selected = selectCapabilities({
+    const standardSelected = selectCapabilities({
       capabilities,
       intent: "write_chapter",
       mode: "standard",
       userMessage: "生成第3章",
     })
+    const strictSelected = selectCapabilities({
+      capabilities,
+      intent: "write_chapter",
+      mode: "strict",
+      userMessage: "生成第3章",
+    })
 
-    expect(selected.map((item) => item.toolName)).toContain("run_chapter_workflow")
+    expect(standardSelected.map((item) => item.toolName)).toContain("run_chapter_workflow")
+    expect(strictSelected.map((item) => item.toolName)).toContain("run_chapter_workflow")
   })
 
   it("allows strict knowledge tasks to select future MCP placeholders without executing MCP", () => {

+ 17 - 10
src/lib/agent/capabilities/selector.ts

@@ -1,6 +1,6 @@
 import type { DataSourceCategory } from "@/lib/novel/classification"
 import type { NovelTaskIntent } from "@/lib/novel/task-router"
-import type { AiWorkflowMode } from "../workflow-mode"
+import { resolveAiWorkflowMode, type AiWorkflowMode, type LegacyAiWorkflowMode } from "../workflow-mode"
 import type { AiCapability, SelectedCapabilityTrace } from "./types"
 
 const WRITING_INTENTS = new Set<NovelTaskIntent>([
@@ -19,8 +19,9 @@ const KNOWLEDGE_INTENTS = new Set<NovelTaskIntent>([
   "setting_query",
 ])
 
-const FAST_WRITING_TOOLS = new Set(["read_chapter", "read_outline", "load_context", "trim_context", "run_chapter_workflow"])
-const STANDARD_WRITING_TOOLS = new Set([
+const FAST_WRITING_TOOLS = new Set(["read_chapter", "read_outline", "load_context", "trim_context"])
+const STANDARD_WRITING_TOOLS = new Set(["read_chapter", "read_outline", "load_context", "trim_context", "run_chapter_workflow"])
+const STRICT_WRITING_TOOLS = new Set([
   "read_chapter",
   "read_outline",
   "read_memory",
@@ -44,7 +45,7 @@ const STRICT_EXTRA_TOOLS = new Set([
 export interface SelectCapabilitiesInput {
   capabilities: AiCapability[]
   intent: NovelTaskIntent
-  mode: AiWorkflowMode
+  mode: LegacyAiWorkflowMode
   userMessage: string
   blockedSources?: DataSourceCategory[]
 }
@@ -52,10 +53,11 @@ export interface SelectCapabilitiesInput {
 export function selectCapabilities(input: SelectCapabilitiesInput): SelectedCapabilityTrace[] {
   const blockedSources = new Set(input.blockedSources ?? [])
   const selected: SelectedCapabilityTrace[] = []
+  const resolvedInput = { ...input, mode: resolveAiWorkflowMode(input.mode) }
 
   for (const capability of input.capabilities) {
-    if (!capability.modes.includes(input.mode)) continue
-    const reason = selectionReason(capability, input, blockedSources)
+    if (!capability.modes.includes(resolvedInput.mode)) continue
+    const reason = selectionReason(capability, resolvedInput, blockedSources)
     if (!reason) continue
     selected.push(toTrace(capability, reason))
   }
@@ -65,7 +67,7 @@ export function selectCapabilities(input: SelectCapabilitiesInput): SelectedCapa
 
 function selectionReason(
   capability: AiCapability,
-  input: SelectCapabilitiesInput,
+  input: SelectCapabilitiesInput & { mode: AiWorkflowMode },
   blockedSources: Set<DataSourceCategory>,
 ): string | null {
   if (capability.kind === "mcp_tool") {
@@ -87,9 +89,10 @@ function selectionReason(
 
   if (capability.kind === "user_skill") {
     if (!capability.intents.includes(input.intent) && !capability.intents.includes("general")) return null
-    if (input.mode === "fast") return "fast mode selected only preselected lightweight skills"
+    if (input.mode === "fast") return null
+    if (input.mode === "standard") return "standard mode selected lightweight skill"
     if (input.mode === "strict") return "strict mode selected preselected skill"
-    return "standard mode selected preselected skill"
+    return null
   }
 
   if (capability.kind === "built_in_tool") {
@@ -107,8 +110,12 @@ function builtInToolReason(capability: AiCapability, input: SelectCapabilitiesIn
     return FAST_WRITING_TOOLS.has(name) ? "fast mode minimal writing context" : null
   }
 
+  if (input.mode === "standard" && WRITING_INTENTS.has(input.intent)) {
+    return STANDARD_WRITING_TOOLS.has(name) ? "standard mode lightweight writing workflow" : null
+  }
+
   if (WRITING_INTENTS.has(input.intent)) {
-    if (STANDARD_WRITING_TOOLS.has(name)) return "writing task capability"
+    if (STRICT_WRITING_TOOLS.has(name)) return "writing task capability"
     if (input.mode === "strict" && STRICT_EXTRA_TOOLS.has(name)) return "strict mode extended writing context"
     return null
   }

+ 2 - 2
src/lib/agent/capabilities/types.ts

@@ -1,4 +1,4 @@
-import type { AiWorkflowMode } from "../workflow-mode"
+import type { LegacyAiWorkflowMode } from "../workflow-mode"
 import type { NovelTaskIntent } from "@/lib/novel/task-router"
 
 export type CapabilityKind =
@@ -18,7 +18,7 @@ export interface AiCapability {
   name: string
   kind: CapabilityKind
   permission: CapabilityPermission
-  modes: AiWorkflowMode[]
+  modes: LegacyAiWorkflowMode[]
   intents: CapabilityIntent[]
   toolName?: string
   skillId?: string

+ 2 - 2
src/lib/agent/pipeline.ts

@@ -1,7 +1,7 @@
 import type { AgentConfig } from "./types"
 import type { TaskRouteResult } from "@/lib/novel/task-router"
 import type { ContextPack } from "@/lib/novel/context-engine"
-import type { AiWorkflowMode } from "./workflow-mode"
+import type { LegacyAiWorkflowMode } from "./workflow-mode"
 import type { UserSkill } from "@/lib/novel/skill-library"
 import type { AiCapability, SelectedCapabilityTrace } from "./capabilities/types"
 
@@ -16,7 +16,7 @@ export interface PrePluginInput {
   taskRoute?: TaskRouteResult | null
   effectiveTaskRoute?: TaskRouteResult | null
   contextPack?: ContextPack | null
-  aiWorkflowMode?: AiWorkflowMode
+  aiWorkflowMode?: LegacyAiWorkflowMode
   planExecuteEnabled?: boolean
   availableSkills?: UserSkill[]
   selectedSkills?: UserSkill[]

+ 15 - 3
src/lib/agent/plan-execute-policy.spec.ts

@@ -11,12 +11,13 @@ describe("Plan Execute policy", () => {
   it("creates a lightweight plan in fast mode when Plan Execute is enabled", () => {
     expect(shouldRequirePlan(true, "fast", "write_chapter")).toBe(true)
     expect(buildPlanExecutePolicyPrompt("fast")).toContain("快速模式")
-    expect(buildPlanExecutePolicyPrompt("fast")).toContain("先创建轻量计划")
+    expect(buildPlanExecutePolicyPrompt("fast")).toContain("先给出最短可执行计划")
   })
 
-  it("requires a short plan in standard mode when Plan Execute is enabled", () => {
+  it("creates a lightweight plan in standard mode when Plan Execute is enabled", () => {
     expect(shouldRequirePlan(true, "standard", "write_chapter")).toBe(true)
-    expect(buildPlanExecutePolicyPrompt("standard")).toContain("先给出简短计划")
+    expect(buildPlanExecutePolicyPrompt("standard")).toContain("标准模式")
+    expect(buildPlanExecutePolicyPrompt("standard")).toContain("先创建轻量计划")
   })
 
   it("requires plan execute and review in strict mode when Plan Execute is enabled", () => {
@@ -33,4 +34,15 @@ describe("Plan Execute policy", () => {
     expect(prompt).toContain("确认后动作")
     expect(prompt).toContain("不要把工具流程说明当成计划")
   })
+
+  it.each(["fast", "standard", "strict"] as const)(
+    "requires extractable chapter plan markers in %s mode",
+    (mode) => {
+      const prompt = buildPlanExecutePolicyPrompt(mode)
+
+      expect(prompt).toContain("<!-- chapter_plan -->")
+      expect(prompt).toContain("<!-- /chapter_plan -->")
+      expect(prompt).toContain("等待用户确认")
+    },
+  )
 })

+ 11 - 8
src/lib/agent/plan-execute-policy.ts

@@ -1,4 +1,4 @@
-import type { AiWorkflowMode } from "./workflow-mode"
+import { resolveAiWorkflowMode, type LegacyAiWorkflowMode } from "./workflow-mode"
 import type { NovelTaskIntent } from "@/lib/novel/task-router"
 
 const WRITING_INTENT_LIST: readonly NovelTaskIntent[] = [
@@ -13,32 +13,35 @@ export const WRITING_INTENTS = new Set<NovelTaskIntent>(WRITING_INTENT_LIST)
 
 export function shouldRequirePlan(
   planExecuteEnabled: boolean,
-  _mode: AiWorkflowMode,
+  _mode: LegacyAiWorkflowMode,
   intent?: string | null,
 ): boolean {
   if (!planExecuteEnabled) return false
   return Boolean(intent && WRITING_INTENTS.has(intent as NovelTaskIntent))
 }
 
-export function buildPlanExecutePolicyPrompt(mode: AiWorkflowMode): string {
+export function buildPlanExecutePolicyPrompt(mode: LegacyAiWorkflowMode): string {
+  const resolvedMode = resolveAiWorkflowMode(mode)
   const executablePlanFormat = [
     "计划必须是给用户确认的可执行计划,不要把工具流程说明当成计划。",
+    "计划必须整体包裹在 `<!-- chapter_plan -->` 和 `<!-- /chapter_plan -->` 标记中。",
+    "输出计划后必须暂停,等待用户确认后再进入正文或执行阶段。",
     "计划必须包含:任务目标、已读取依据、缺失资料、执行步骤、确认后动作。",
     "读取资料前先用 list_chapters、list_outlines、list_memories 确认可用文件;不要凭空编造章节、大纲或记忆条目名称。",
     "如果资料缺失,必须在“缺失资料”里说明,并基于已读取内容继续制定可执行方案。",
   ].join("\n")
 
-  if (mode === "fast") {
+  if (resolvedMode === "fast") {
     return [
       "Plan Execute:当前已开启计划执行。",
-      "快速模式:先创建轻量计划,再快速执行。",
+      "快速模式:用户已主动开启计划执行,先给出最短可执行计划再直接执行。",
       "计划最多 3 条,只写将要读取和执行的关键步骤。",
       executablePlanFormat,
       "如果需要生成、续写、改写或润色章节,优先调用 run_chapter_workflow。",
     ].join("\n")
   }
 
-  if (mode === "strict") {
+  if (resolvedMode === "strict") {
     return [
       "Plan Execute:当前已开启计划执行。",
       "严格模式:必须先计划,再执行,再执行后审查。",
@@ -51,8 +54,8 @@ export function buildPlanExecutePolicyPrompt(mode: AiWorkflowMode): string {
 
   return [
     "Plan Execute:当前已开启计划执行。",
-    "标准模式:复杂写作任务先给出简短计划,再执行。",
-    "计划最多 5 条,不能替代正文,不能把计划混入最终章节正文。",
+    "标准模式:先创建轻量计划,再快速执行。",
+    "计划最多 3 条,不能替代正文,不能把计划混入最终章节正文。",
     executablePlanFormat,
     "如果是章节生成、续写、改写或润色,优先调用 run_chapter_workflow。",
   ].join("\n")

+ 25 - 0
src/lib/agent/plugins/build-system-prompt-plugin.spec.ts

@@ -102,4 +102,29 @@ describe("BuildSystemPromptPlugin selected skills", () => {
     const finalPrompt = result.finalSystemPrompt ?? ""
     expect(finalPrompt.length).toBeLessThan(3000)
   })
+
+  it.each(["fast", "standard", "strict"] as const)(
+    "injects chapter plan protocol when Plan Execute is enabled with %s mode",
+    async (mode) => {
+      const plugin = createBuildSystemPromptPlugin({
+        baseSystemPrompt: "base prompt",
+        buildTaskDirectiveFn: () => "task directive",
+      })
+
+      const result = await plugin.run({
+        userMessage: "帮我写下一章",
+        projectPath: "/project",
+        agentConfig: {} as any,
+        novelMode: true,
+        aiWorkflowMode: mode,
+        planExecuteEnabled: true,
+        taskRoute: { intent: "write_chapter", confidence: 0.9, extractedParams: {} },
+      })
+
+      expect(result.finalSystemPrompt).toContain("章节主编策划协议")
+      expect(result.finalSystemPrompt).toContain("<!-- chapter_plan -->")
+      expect(result.finalSystemPrompt).toContain("<!-- /chapter_plan -->")
+      expect(result.finalSystemPrompt).toContain("等待用户确认")
+    },
+  )
 })

+ 4 - 5
src/lib/agent/plugins/build-system-prompt-plugin.ts

@@ -1,7 +1,7 @@
   import type { PrePlugin, PrePluginInput, PrePluginOutput } from "../pipeline"
 import { buildTaskDirective } from "@/lib/novel/task-router"
 import { buildSelectedSkillsPrompt } from "./select-skills-plugin"
-import type { AiWorkflowMode } from "../workflow-mode"
+import { getWorkflowModeLabel, resolveAiWorkflowMode, type LegacyAiWorkflowMode } from "../workflow-mode"
 import { WRITING_INTENTS } from "../plan-execute-policy"
 
   export interface BuildSystemPromptPluginDeps {
@@ -62,10 +62,9 @@ export function createBuildSystemPromptPlugin(deps: BuildSystemPromptPluginDeps
   }
 }
 
-function buildChapterPlanProtocol(mode: AiWorkflowMode): string {
+function buildChapterPlanProtocol(mode: LegacyAiWorkflowMode): string {
   // mode 仅用于在协议头标注当前工作流强度,不改变计划结构。
-  const modeLabel =
-    mode === "fast" ? "快速" : mode === "strict" ? "严格" : "标准"
+  const modeLabel = getWorkflowModeLabel(resolveAiWorkflowMode(mode))
   return [
     "## 章节主编策划协议(本章策划案)",
     "",
@@ -74,7 +73,7 @@ function buildChapterPlanProtocol(mode: AiWorkflowMode): string {
     "",
     "输出规范:",
     "1. 计划必须整体包裹在 `<!-- chapter_plan -->` 和 `<!-- /chapter_plan -->` 标记中。",
-    "2. 计划只供用户确认,正文生成必须等用户确认后再开始。",
+    "2. 计划只供用户确认,正文生成必须等待用户确认后再开始。当前阶段禁用正文生成类工具,只能使用读取类工具收集资料。",
     "3. 计划必须基于会话上下文包;读取资料前先用 list_chapters、list_outlines、list_memories 确认可用文件名,绝不编造资料名称。",
     "4. 计划总长控制在 1200-1800字,避免堆砌分析维度;用结论和执行项表达。",
     "5. 场景必须用 S1/S2/S3 编号,后续正文会按编号执行。",

+ 160 - 0
src/lib/agent/plugins/select-capabilities-plugin.spec.ts

@@ -91,4 +91,164 @@ describe("SelectCapabilitiesPlugin", () => {
     ]))
     expect(result.enabledToolNames).toContain("mcp_graph_query_graph")
   })
+
+  it("filters out writing tools during plan phase in standard mode", async () => {
+    const plugin = createSelectCapabilitiesPlugin()
+    const availableCapabilities = buildAvailableCapabilities({
+      toolNames: [
+        "read_chapter",
+        "read_outline",
+        "load_context",
+        "trim_context",
+        "run_chapter_workflow",
+      ],
+    })
+
+    const result = await plugin.run({
+      userMessage: "写第一章",
+      projectPath: "/project",
+      agentConfig: {} as any,
+      novelMode: true,
+      aiWorkflowMode: "standard",
+      planExecuteEnabled: true,
+      availableCapabilities,
+      selectedSkills: [],
+      taskRoute: { intent: "write_chapter", confidence: 0.9, extractedParams: {} },
+    })
+
+    const toolNames = result.selectedCapabilities
+      .map((c) => c.toolName)
+      .filter((n): n is string => Boolean(n))
+
+    expect(toolNames).toContain("read_chapter")
+    expect(toolNames).toContain("read_outline")
+    expect(toolNames).toContain("load_context")
+    expect(toolNames).toContain("trim_context")
+    expect(toolNames).not.toContain("run_chapter_workflow")
+
+    expect(result.enabledToolNames).not.toContain("run_chapter_workflow")
+    expect(result.enabledToolNames).toContain("read_chapter")
+  })
+
+  it("filters out writing tools during plan phase in strict mode", async () => {
+    const plugin = createSelectCapabilitiesPlugin()
+    const availableCapabilities = buildAvailableCapabilities({
+      toolNames: [
+        "read_chapter",
+        "read_outline",
+        "read_memory",
+        "search_chapters",
+        "list_chapters",
+        "list_outlines",
+        "list_memories",
+        "load_context",
+        "trim_context",
+        "write_chapter",
+        "apply_skill",
+        "run_chapter_workflow",
+      ],
+    })
+
+    const result = await plugin.run({
+      userMessage: "写第一章",
+      projectPath: "/project",
+      agentConfig: {} as any,
+      novelMode: true,
+      aiWorkflowMode: "strict",
+      planExecuteEnabled: true,
+      availableCapabilities,
+      selectedSkills: [],
+      taskRoute: { intent: "write_chapter", confidence: 0.9, extractedParams: {} },
+    })
+
+    const toolNames = result.selectedCapabilities
+      .map((c) => c.toolName)
+      .filter((n): n is string => Boolean(n))
+
+    expect(toolNames).toContain("read_chapter")
+    expect(toolNames).toContain("read_outline")
+    expect(toolNames).toContain("read_memory")
+    expect(toolNames).toContain("search_chapters")
+    expect(toolNames).toContain("list_chapters")
+    expect(toolNames).toContain("list_outlines")
+    expect(toolNames).toContain("list_memories")
+    expect(toolNames).toContain("load_context")
+    expect(toolNames).toContain("trim_context")
+    expect(toolNames).not.toContain("write_chapter")
+    expect(toolNames).not.toContain("apply_skill")
+    expect(toolNames).not.toContain("run_chapter_workflow")
+  })
+
+  it("allows writing tools when plan execute is disabled in standard mode", async () => {
+    const plugin = createSelectCapabilitiesPlugin()
+    const availableCapabilities = buildAvailableCapabilities({
+      toolNames: [
+        "read_chapter",
+        "read_outline",
+        "load_context",
+        "trim_context",
+        "run_chapter_workflow",
+      ],
+    })
+
+    const result = await plugin.run({
+      userMessage: "写第一章",
+      projectPath: "/project",
+      agentConfig: {} as any,
+      novelMode: true,
+      aiWorkflowMode: "standard",
+      planExecuteEnabled: false,
+      availableCapabilities,
+      selectedSkills: [],
+      taskRoute: { intent: "write_chapter", confidence: 0.9, extractedParams: {} },
+    })
+
+    const toolNames = result.selectedCapabilities
+      .map((c) => c.toolName)
+      .filter((n): n is string => Boolean(n))
+
+    expect(toolNames).toContain("run_chapter_workflow")
+    expect(toolNames).toContain("read_chapter")
+  })
+
+  it("allows writing tools when plan execute is disabled in strict mode", async () => {
+    const plugin = createSelectCapabilitiesPlugin()
+    const availableCapabilities = buildAvailableCapabilities({
+      toolNames: [
+        "read_chapter",
+        "read_outline",
+        "read_memory",
+        "search_chapters",
+        "list_chapters",
+        "list_outlines",
+        "list_memories",
+        "load_context",
+        "trim_context",
+        "write_chapter",
+        "apply_skill",
+        "run_chapter_workflow",
+      ],
+    })
+
+    const result = await plugin.run({
+      userMessage: "写第一章",
+      projectPath: "/project",
+      agentConfig: {} as any,
+      novelMode: true,
+      aiWorkflowMode: "strict",
+      planExecuteEnabled: false,
+      availableCapabilities,
+      selectedSkills: [],
+      taskRoute: { intent: "write_chapter", confidence: 0.9, extractedParams: {} },
+    })
+
+    const toolNames = result.selectedCapabilities
+      .map((c) => c.toolName)
+      .filter((n): n is string => Boolean(n))
+
+    expect(toolNames).toContain("write_chapter")
+    expect(toolNames).toContain("apply_skill")
+    expect(toolNames).toContain("run_chapter_workflow")
+    expect(toolNames).toContain("read_chapter")
+  })
 })

+ 28 - 3
src/lib/agent/plugins/select-capabilities-plugin.ts

@@ -1,6 +1,24 @@
 import type { PrePlugin, PrePluginInput, PrePluginOutput } from "../pipeline"
 import { buildAvailableCapabilities } from "../capabilities/registry"
 import { selectCapabilities } from "../capabilities/selector"
+import { resolveAiWorkflowMode } from "../workflow-mode"
+
+const PLAN_PHASE_ALLOWED_TOOLS = new Set([
+  "read_chapter",
+  "read_outline",
+  "read_memory",
+  "read_deduction",
+  "read_chat_history",
+  "read_outline_history",
+  "list_chapters",
+  "list_outlines",
+  "list_memories",
+  "list_deductions",
+  "search_chapters",
+  "load_context",
+  "trim_context",
+  "web_search",
+])
 
 export function createSelectCapabilitiesPlugin(): PrePlugin {
   return {
@@ -21,14 +39,21 @@ export function createSelectCapabilitiesPlugin(): PrePlugin {
       const selectedCapabilities = selectCapabilities({
         capabilities: availableCapabilities,
         intent: route.intent,
-        mode: input.aiWorkflowMode ?? "standard",
+        mode: resolveAiWorkflowMode(input.aiWorkflowMode),
         userMessage: input.userMessage,
         blockedSources: input.blockedSources as any,
       })
 
+      const isPlanPhase = Boolean(input.planExecuteEnabled)
+      const filteredCapabilities = isPlanPhase
+        ? selectedCapabilities.filter(
+            (cap) => cap.toolName && PLAN_PHASE_ALLOWED_TOOLS.has(cap.toolName),
+          )
+        : selectedCapabilities
+
       return {
-        selectedCapabilities,
-        enabledToolNames: selectedCapabilities
+        selectedCapabilities: filteredCapabilities,
+        enabledToolNames: filteredCapabilities
           .map((capability) => capability.toolName)
           .filter((name): name is string => Boolean(name)),
       }

+ 9 - 14
src/lib/agent/plugins/select-skills-plugin.spec.ts

@@ -31,7 +31,7 @@ const availableSkills = [
 ]
 
 describe("SelectSkillsPlugin", () => {
-  it("selects standard writing skills for next chapter generation", async () => {
+  it("selects old fast lightweight skills for standard mode", async () => {
     const plugin = createSelectSkillsPlugin()
 
     const result = await plugin.run({
@@ -45,17 +45,12 @@ describe("SelectSkillsPlugin", () => {
     })
 
     expect(result.selectedSkills?.map((item) => item.name)).toEqual([
-      "章节承接",
-      "下一章计划",
-      "人物动机",
-      "冲突升级",
-      "剧情自检",
       "正文输出协议",
-      "世界观资料",
+      "去AI味",
     ])
   })
 
-  it("supplements standard writing with uploaded writing skills such as 三翻四抖", async () => {
+  it("does not supplement standard lightweight writing with uploaded skills", async () => {
     const plugin = createSelectSkillsPlugin()
 
     const result = await plugin.run({
@@ -80,10 +75,13 @@ describe("SelectSkillsPlugin", () => {
       taskRoute: { intent: "write_chapter", confidence: 0.95, extractedParams: {} },
     })
 
-    expect(result.selectedSkills?.map((item) => item.name)).toContain("三翻四抖")
+    expect(result.selectedSkills?.map((item) => item.name)).toEqual([
+      "正文输出协议",
+      "去AI味",
+    ])
   })
 
-  it("keeps fast mode to output and optional style skills", async () => {
+  it("does not auto-select skills in fast mode", async () => {
     const plugin = createSelectSkillsPlugin()
 
     const result = await plugin.run({
@@ -96,10 +94,7 @@ describe("SelectSkillsPlugin", () => {
       taskRoute: { intent: "write_chapter", confidence: 0.95, extractedParams: {} },
     })
 
-    expect(result.selectedSkills?.map((item) => item.name)).toEqual([
-      "正文输出协议",
-      "去AI味",
-    ])
+    expect(result.selectedSkills).toEqual([])
   })
 
   it("selects strict review and structure skills for key chapter writing", async () => {

+ 19 - 16
src/lib/agent/plugins/select-skills-plugin.ts

@@ -1,5 +1,5 @@
 import type { PrePlugin, PrePluginInput, PrePluginOutput } from "../pipeline"
-import type { AiWorkflowMode } from "../workflow-mode"
+import { resolveAiWorkflowMode, type AiWorkflowMode, type LegacyAiWorkflowMode } from "../workflow-mode"
 import type { NovelTaskIntent } from "@/lib/novel/task-router"
 import type { SkillKind, SkillStage, UserSkill } from "@/lib/novel/skill-library"
 
@@ -53,7 +53,7 @@ export function createSelectSkillsPlugin(): PrePlugin {
       const availableSkills = input.availableSkills ?? []
       if (availableSkills.length === 0) return { selectedSkills: [] }
 
-      const mode = input.aiWorkflowMode ?? "standard"
+      const mode = resolveAiWorkflowMode(input.aiWorkflowMode)
       return {
         selectedSkills: selectSkillsForRoute(availableSkills, route.intent, mode),
       }
@@ -64,50 +64,53 @@ export function createSelectSkillsPlugin(): PrePlugin {
 export function selectSkillsForRoute(
   skills: UserSkill[],
   intent: NovelTaskIntent,
-  mode: AiWorkflowMode,
+  mode: LegacyAiWorkflowMode,
 ): UserSkill[] {
-  const modeSkills = skills.filter((skill) => skill.modes.includes(mode))
+  const resolvedMode = resolveAiWorkflowMode(mode)
+  if (resolvedMode === "fast") return []
+
+  const modeSkills = skills.filter((skill) => skill.modes.includes(resolvedMode))
   if (modeSkills.length === 0) return []
 
   if (WRITING_INTENTS.has(intent)) {
-    return selectWritingSkills(modeSkills, mode)
+    return selectWritingSkills(modeSkills, resolvedMode)
   }
 
   if (intent === "generate_outline") {
-    return selectByShape(modeSkills, mode, {
+    return selectByShape(modeSkills, resolvedMode, {
       kinds: ["planning", "structure", "output"],
       stages: ["planning", "output"],
-      limit: mode === "strict" ? 8 : 5,
+      limit: resolvedMode === "strict" ? 8 : 5,
     })
   }
 
   if (REVIEW_INTENTS.has(intent)) {
-    return selectByShape(modeSkills, mode, {
+    return selectByShape(modeSkills, resolvedMode, {
       kinds: ["review", "knowledge", "output"],
       stages: ["review", "output"],
-      limit: mode === "strict" ? 8 : 5,
+      limit: resolvedMode === "strict" ? 8 : 5,
     })
   }
 
   if (QUERY_INTENTS.has(intent)) {
-    return selectByShape(modeSkills, mode, {
+    return selectByShape(modeSkills, resolvedMode, {
       kinds: ["knowledge", "review", "output"],
       stages: ["planning", "review", "output"],
-      limit: mode === "strict" ? 6 : 3,
+      limit: resolvedMode === "strict" ? 6 : 3,
     })
   }
 
   return []
 }
 
-function selectWritingSkills(skills: UserSkill[], mode: AiWorkflowMode): UserSkill[] {
-  if (mode === "fast") {
+function selectWritingSkills(skills: UserSkill[], mode: Exclude<AiWorkflowMode, "fast">): UserSkill[] {
+  if (mode === "standard") {
     return selectPreferredNames(skills, FAST_WRITING_SKILL_NAMES, 3, false)
   }
   if (mode === "strict") {
     return selectPreferredNames(skills, STRICT_WRITING_SKILL_NAMES, 12)
   }
-  return selectPreferredNames(skills, STANDARD_WRITING_SKILL_NAMES, 8)
+  return []
 }
 
 function selectPreferredNames(skills: UserSkill[], names: string[], limit: number, fillWithRelevant = true): UserSkill[] {
@@ -140,7 +143,7 @@ function selectPreferredNames(skills: UserSkill[], names: string[], limit: numbe
 
 function selectByShape(
   skills: UserSkill[],
-  mode: AiWorkflowMode,
+  mode: Exclude<AiWorkflowMode, "fast">,
   options: { kinds: SkillKind[]; stages: SkillStage[]; limit: number },
 ): UserSkill[] {
   return skills
@@ -159,7 +162,7 @@ function isWritingSkill(skill: UserSkill): boolean {
 
 function scoreSkill(
   skill: UserSkill,
-  mode: AiWorkflowMode,
+  mode: Exclude<AiWorkflowMode, "fast">,
   options: { kinds: SkillKind[]; stages: SkillStage[] },
 ): number {
   let score = 0

+ 2 - 2
src/lib/agent/tools/index.ts

@@ -1,6 +1,6 @@
 import type { ToolRegistry } from "../registry"
 import type { AgentToolEvent, Tool } from "../types"
-import type { AiWorkflowMode } from "@/lib/agent/workflow-mode"
+import type { LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode"
 import { createReadChapterTool } from "./read-chapter"
 import { createReadOutlineTool } from "./read-outline"
 import { createReadMemoryTool } from "./read-memory"
@@ -53,7 +53,7 @@ export interface ToolFactoryOptions {
   enabledToolNames?: string[]
   disabledTools?: string[]
   llmConfig?: LlmConfig
-  aiWorkflowMode?: AiWorkflowMode
+  aiWorkflowMode?: LegacyAiWorkflowMode
   runDeepChapterGeneration?: RunDeepChapterGeneration
   onToolEvent?: (event: AgentToolEvent) => void
   getPlanBlueprint?: () => string | undefined

+ 4 - 4
src/lib/agent/tools/run-chapter-workflow.ts

@@ -1,5 +1,5 @@
 import type { LlmConfig } from "@/stores/wiki-store"
-import type { AiWorkflowMode } from "@/lib/agent/workflow-mode"
+import { resolveAiWorkflowMode, type LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode"
 import type {
   ChapterWorkflowEvent,
   DeepChapterGenerationCallbacks,
@@ -19,7 +19,7 @@ export type RunDeepChapterGeneration = (
 export interface RunChapterWorkflowToolOptions {
   projectPath: string
   llmConfig: LlmConfig
-  aiWorkflowMode: AiWorkflowMode
+  aiWorkflowMode: LegacyAiWorkflowMode
   runDeepChapterGeneration: RunDeepChapterGeneration
   onToolEvent?: (event: AgentToolEvent) => void
   onActivityEvent?: (event: AgentActivityEvent) => void
@@ -34,7 +34,7 @@ interface RunChapterWorkflowParams {
   intent?: string
   userRequest?: string
   chapterNumber?: number
-  workflowMode?: AiWorkflowMode
+  workflowMode?: LegacyAiWorkflowMode
   planBlueprint?: string
 }
 
@@ -54,7 +54,7 @@ function normalizeParams(params: Record<string, unknown>): RunChapterWorkflowPar
     chapterNumber: toNumber(params.chapterNumber),
     workflowMode:
       params.workflowMode === "fast" || params.workflowMode === "standard" || params.workflowMode === "strict"
-        ? params.workflowMode
+        ? resolveAiWorkflowMode(params.workflowMode)
         : undefined,
     planBlueprint: typeof params.planBlueprint === "string" ? params.planBlueprint : undefined,
   }

+ 1 - 1
src/lib/agent/workflow-mode.spec.ts

@@ -16,7 +16,7 @@ describe("workflow mode", () => {
     expect(resolveAiWorkflowMode(true)).toBe("strict")
   })
 
-  it("accepts explicit workflow modes without changing them", () => {
+  it("accepts explicit active workflow modes without changing them", () => {
     const modes: AiWorkflowMode[] = ["fast", "standard", "strict"]
 
     expect(modes.map(resolveAiWorkflowMode)).toEqual(modes)

+ 3 - 2
src/lib/agent/workflow-mode.ts

@@ -1,14 +1,15 @@
 export type AiWorkflowMode = "fast" | "standard" | "strict"
+export type LegacyAiWorkflowMode = AiWorkflowMode
 
 export const DEFAULT_AI_WORKFLOW_MODE: AiWorkflowMode = "standard"
 
-export function resolveAiWorkflowMode(value: boolean | AiWorkflowMode | null | undefined): AiWorkflowMode {
+export function resolveAiWorkflowMode(value: boolean | LegacyAiWorkflowMode | null | undefined): AiWorkflowMode {
   if (value === true) return "strict"
   if (value === false || value == null) return DEFAULT_AI_WORKFLOW_MODE
   return value
 }
 
-export function getWorkflowModeLabel(mode: AiWorkflowMode): string {
+export function getWorkflowModeLabel(mode: LegacyAiWorkflowMode): string {
   switch (mode) {
     case "fast":
       return "快速"

+ 1 - 1
src/lib/llm-client.ts

@@ -422,7 +422,7 @@ export async function streamChat(
         // Stream reader threw a network error mid-response (connection
         // dropped, server closed early, network blip). Same message
         // regardless of whether the webview is WebKit or Chromium.
-        onError(new Error("Connection lost during streaming. Try again."))
+        onError(new Error("流式响应读取中断,请检查网络、代理或接口稳定性后重试。"))
         return
       }
       onError(err instanceof Error ? err : new Error(String(err)))

+ 5 - 4
src/lib/novel/deep-chapter-generation.spec.ts

@@ -965,7 +965,7 @@ describe("runDeepChapterGeneration", () => {
     expect(overrides[1]).toEqual({ reasoning: { mode: "off" } })
   })
 
-  it("uses separate workflow routes for fast standard and strict modes", async () => {
+  it("uses fast, standard, and strict workflow routes", async () => {
     const fastDeps = createDeps()
     await runDeepChapterGeneration(
       { projectPath: "E:/Novel", userRequest: "生成第三章", chapterNumber: 3, llmConfig, aiWorkflowMode: "fast" },
@@ -981,7 +981,7 @@ describe("runDeepChapterGeneration", () => {
       {},
       standardDeps,
     )
-    expect(standardDeps.streamChat).toHaveBeenCalledTimes(3)
+    expect(standardDeps.streamChat).toHaveBeenCalledTimes(2)
     expect(standardDeps.reviewChapter).not.toHaveBeenCalled()
 
     const strictDeps = createDeps()
@@ -1065,8 +1065,9 @@ describe("runDeepChapterGeneration", () => {
       { onWorkflowEvent: (event) => standardEvents.push(event) },
       createDeps(),
     )
-    expect(standardEvents.find((event) => event.name === "chapter_review")?.result).toContain("标准模式跳过")
-    expect(standardEvents.find((event) => event.name === "chapter_final_polish" && event.result)?.result).toContain("简单审查与去AI味完成")
+    expect(standardEvents.some((event) => event.name === "chapter_review")).toBe(false)
+    expect(standardEvents.some((event) => event.name === "chapter_final_polish")).toBe(false)
+    expect(standardEvents.find((event) => event.name === "chapter_complete")?.result).toContain("标准模式写作完成")
   })
 
   it("keeps fast mode on a lightweight route without post-draft plan audits", async () => {

+ 9 - 9
src/lib/novel/deep-chapter-generation.ts

@@ -6,7 +6,7 @@ import {
   type StreamCallbacks,
 } from "@/lib/llm-client";
 import { useWikiStore } from "@/stores/wiki-store";
-import type { AiWorkflowMode } from "@/lib/agent/workflow-mode";
+import { resolveAiWorkflowMode, type AiWorkflowMode, type LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode";
 import type { AgentActivityEvent, AgentActivityKind } from "@/lib/agent/types";
 import {
   isReasoningDisabled,
@@ -59,7 +59,7 @@ export interface DeepChapterGenerationInput {
   goldenThreeChapter?: GoldenThreeChapterRequest;
   dismantlingReferenceDirective?: string;
   llmConfig: LlmConfig;
-  aiWorkflowMode?: AiWorkflowMode;
+  aiWorkflowMode?: LegacyAiWorkflowMode;
   resumeCheckpoint?: DeepChapterGenerationResumeCheckpoint;
   /** 用户在会话层确认的章节计划,作为写作任务书的权威依据注入 brief 阶段。 */
   planBlueprint?: string;
@@ -226,9 +226,9 @@ interface ChapterWorkflowProfile {
 }
 
 function resolveChapterWorkflowProfile(
-  mode: AiWorkflowMode | undefined,
+  mode: LegacyAiWorkflowMode | undefined,
 ): ChapterWorkflowProfile {
-  const resolvedMode = mode ?? "strict";
+  const resolvedMode = mode == null ? "strict" : resolveAiWorkflowMode(mode);
   if (resolvedMode === "fast") {
     return {
       mode: "fast",
@@ -246,13 +246,13 @@ function resolveChapterWorkflowProfile(
     return {
       mode: "standard",
       runPreviousChaptersAnalysis: false,
-      runExecutionContractBuild: true,
+      runExecutionContractBuild: false,
       runAiReview: false,
-      runFinalPolish: true,
+      runFinalPolish: false,
       runPostRevisionReview: false,
-      runPostDraftPlanAudits: true,
-      completionTitle: "完成多任务写作循环",
-      completionResultPrefix: "多任务写作循环完成",
+      runPostDraftPlanAudits: false,
+      completionTitle: "完成标准写作",
+      completionResultPrefix: "标准模式写作完成",
     };
   }
   return {

+ 2 - 2
src/lib/novel/skill-library.ts

@@ -1,4 +1,4 @@
-import type { AiWorkflowMode } from "@/lib/agent/workflow-mode"
+import type { LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode"
 
 export type SkillKind =
   | "style"
@@ -16,7 +16,7 @@ export type SkillStage =
   | "rewrite"
   | "output"
 
-export type SkillMode = AiWorkflowMode
+export type SkillMode = LegacyAiWorkflowMode
 
 export interface SkillCategory {
   id: string

+ 2 - 0
src/lib/tauri-fetch.ts

@@ -82,6 +82,8 @@ export function isFetchNetworkError(err: unknown): boolean {
   if (err.message === "Failed to fetch") return true
   // Tauri plugin-http / Rust reqwest send-stage failure
   if (/error sending request for url/i.test(err.message)) return true
+  // Tauri plugin-http / Rust reqwest response stream decode failure
+  if (/error decoding response body/i.test(err.message)) return true
   if (err.message.includes("network error")) return true
   return false
 }

+ 11 - 8
src/stores/wiki-store.ts

@@ -29,7 +29,7 @@ import {
   resolveStoredVisualStyle,
   type VisualStyle,
 } from "@/lib/visual-style-settings"
-import type { AiWorkflowMode } from "@/lib/agent/workflow-mode"
+import { DEFAULT_AI_WORKFLOW_MODE, resolveAiWorkflowMode, type AiWorkflowMode, type LegacyAiWorkflowMode } from "@/lib/agent/workflow-mode"
 import { DEFAULT_MCP_CONFIG, type McpConfig } from "@/lib/mcp/config"
 
 const GRAPH_LABEL_MODE_KEY = "lk-graph-label-display-mode"
@@ -671,7 +671,7 @@ interface WikiState {
   setSourceWatchConfig: (sourceWatchConfig: SourceWatchConfig) => void
   setNovelMode: (novelMode: boolean) => void
   setChatEditModeEnabled: (enabled: boolean) => void
-  setAiWorkflowMode: (mode: AiWorkflowMode) => void
+  setAiWorkflowMode: (mode: LegacyAiWorkflowMode) => void
   setPlanExecuteEnabled: (enabled: boolean) => void
   setDeepChapterEnabled: (enabled: boolean) => void
   setNovelConfig: (config: Partial<NovelConfig>) => void
@@ -894,7 +894,7 @@ export const useWikiStore = create<WikiState>((set) => ({
 
   novelMode: true,
   chatEditModeEnabled: false,
-  aiWorkflowMode: "standard",
+  aiWorkflowMode: DEFAULT_AI_WORKFLOW_MODE,
   planExecuteEnabled: false,
   deepChapterEnabled: false,
   novelConfig: { ...DEFAULT_NOVEL_CONFIG },
@@ -932,14 +932,17 @@ export const useWikiStore = create<WikiState>((set) => ({
   setSourceWatchConfig: (sourceWatchConfig) => set({ sourceWatchConfig }),
   setNovelMode: (novelMode) => set({ novelMode }),
   setChatEditModeEnabled: (chatEditModeEnabled) => set({ chatEditModeEnabled }),
-  setAiWorkflowMode: (aiWorkflowMode) => set({
-    aiWorkflowMode,
-    deepChapterEnabled: aiWorkflowMode === "strict",
-  }),
+  setAiWorkflowMode: (aiWorkflowMode) => {
+    const resolvedMode = resolveAiWorkflowMode(aiWorkflowMode)
+    set({
+      aiWorkflowMode: resolvedMode,
+      deepChapterEnabled: resolvedMode === "strict",
+    })
+  },
   setPlanExecuteEnabled: (planExecuteEnabled) => set({ planExecuteEnabled }),
   setDeepChapterEnabled: (deepChapterEnabled) => set({
     deepChapterEnabled,
-    aiWorkflowMode: deepChapterEnabled ? "strict" : "standard",
+    aiWorkflowMode: deepChapterEnabled ? "strict" : DEFAULT_AI_WORKFLOW_MODE,
   }),
   setNovelConfig: (config) => set((state) => ({
     novelConfig: { ...state.novelConfig, ...config },