Просмотр исходного кода

refactor(core): drop revise_chapter agent tool, route through sub_agent reviser

The revise_chapter tool and sub_agent(agent="reviser") both wrapped the
same pipeline.reviseDraft() call with an almost-identical parameter set.
Listing both in the system prompt made the model oscillate between them.

Removes createReviseChapterTool, its export, registration, and tests.
Updates the system prompt and edit/write tool descriptions to point at
sub_agent(reviser) for whole-chapter rewrites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fanghanjun 6 месяцев назад
Родитель
Сommit
a57f9db0d5

+ 12 - 3
packages/core/src/__tests__/agent-system-prompt.test.ts

@@ -55,7 +55,6 @@ describe("buildAgentSystemPrompt", () => {
       expect(prompt).toContain("mode");
       expect(prompt).toContain("approvedOnly");
       expect(prompt).toContain("read");
-      expect(prompt).toContain("revise_chapter");
       expect(prompt).toContain("write_truth_file");
       expect(prompt).toContain("rename_entity");
       expect(prompt).toContain("patch_chapter_text");
@@ -65,12 +64,22 @@ describe("buildAgentSystemPrompt", () => {
       expect(prompt).toContain("ls");
     });
 
-    it("with-book prompt steers high-risk edits to dedicated deterministic tools", () => {
+    it("Chinese prompt does NOT mention revise_chapter (merged into sub_agent reviser)", () => {
+      const prompt = buildAgentSystemPrompt("my-book", "zh");
+      expect(prompt).not.toContain("revise_chapter");
+    });
+
+    it("English prompt does NOT mention revise_chapter (merged into sub_agent reviser)", () => {
+      const prompt = buildAgentSystemPrompt("novel", "en");
+      expect(prompt).not.toContain("revise_chapter");
+    });
+
+    it("with-book prompt steers chapter rewrites to sub_agent reviser, not a separate tool", () => {
       const prompt = buildAgentSystemPrompt("my-book", "zh");
       expect(prompt).toContain("改设定/改真相文件");
       expect(prompt).toContain("write_truth_file");
       expect(prompt).toContain("用户要求重写/精修已有章节");
-      expect(prompt).toContain("revise_chapter");
+      expect(prompt).toContain("reviser");
       expect(prompt).toContain("也可以直接使用 edit / write");
     });
 

+ 0 - 22
packages/core/src/__tests__/agent-tools.test.ts

@@ -7,7 +7,6 @@ import {
   createSubAgentTool,
   createPatchChapterTextTool,
   createRenameEntityTool,
-  createReviseChapterTool,
   createWriteFileTool,
   createWriteTruthFileTool,
 } from "../agent/agent-tools.js";
@@ -65,27 +64,6 @@ describe("agent deterministic writing tools", () => {
       .resolves.toContain("distrusts the guild");
   });
 
-  it("routes chapter rewrite requests through reviseDraft with the requested mode", async () => {
-    const pipeline = {
-      reviseDraft: vi.fn(async () => ({
-        chapterNumber: 3,
-        wordCount: 1300,
-        fixedIssues: [],
-        applied: true,
-        status: "ready-for-review" as const,
-      })),
-    };
-    const tool = createReviseChapterTool(pipeline as never, "harbor");
-
-    const result = await tool.execute("tool-2", {
-      chapterNumber: 3,
-      mode: "rewrite",
-    });
-
-    expect(pipeline.reviseDraft).toHaveBeenCalledWith("harbor", 3, "rewrite");
-    expect(result.content[0]?.type).toBe("text");
-  });
-
   it("renames entities through the deterministic edit controller", async () => {
     const tool = createRenameEntityTool({} as never, root, "harbor");
 

+ 0 - 2
packages/core/src/agent/agent-session.ts

@@ -8,7 +8,6 @@ import {
   createPatchChapterTextTool,
   createRenameEntityTool,
   createSubAgentTool,
-  createReviseChapterTool,
   createReadTool,
   createEditTool,
   createWriteFileTool,
@@ -237,7 +236,6 @@ export async function runAgentSession(
         tools: [
           createSubAgentTool(pipeline, bookId, projectRoot),
           createReadTool(projectRoot),
-          createReviseChapterTool(pipeline, bookId),
           createWriteTruthFileTool(pipeline, projectRoot, bookId),
           createRenameEntityTool(pipeline, projectRoot, bookId),
           createPatchChapterTextTool(pipeline, projectRoot, bookId),

+ 18 - 12
packages/core/src/agent/agent-system-prompt.ts

@@ -73,17 +73,20 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 ## 可用工具
 
 - **sub_agent** — 委托子智能体执行重操作:
-  - agent="writer" 写下一章(参数:chapterWordCount 覆盖字数)
-  - agent="auditor" 审计章节质量(参数:chapterNumber 指定章节)
-  - agent="reviser" 修订章节(参数:chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
+  - agent="writer" **续写下一章**(接着已写的最后一章往下写,无法指定章节号。参数:chapterWordCount)
+  - agent="auditor" 审计**已有章节**(参数:chapterNumber 指定第几章,不传则审最新一章)
+  - agent="reviser" 修改**已有章节**(**必须传 chapterNumber 指明改第几章**。参数:chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
   - agent="exporter" 导出书籍(参数:format: txt/md/epub, approvedOnly: true/false)
+  - **writer vs reviser 选择规则**(极易出错,看清楚):
+    - 用户说"改/修订/重写第 N 章"、"第 N 章 xxx 写得不好" → **reviser** + chapterNumber=N(绝不能用 writer,writer 会写新的第 N+1 章)
+    - 用户说"写下一章"、"继续写"、"再来一章" → **writer**(不要用 reviser,更不要不带 chapterNumber 调 reviser)
+    - 用户没说章节号、只说"改一下刚才那章" → **reviser** + chapterNumber=最新已写章节号
 - **read** — 读取书籍的设定文件或章节内容
-- **revise_chapter** — 对已有章节做精修/重写/返工
 - **write_truth_file** — 整文件覆盖真相文件(story_bible、volume_outline、book_rules、current_focus 等)
 - **rename_entity** — 统一改角色/实体名
 - **patch_chapter_text** — 对已有章节做局部定点修补
 - **edit** — 在设定文件里做精确字符串替换(章节正文请用 patch_chapter_text)
-- **write** — 新建文件,或者重写整个文件(已有内容会被覆盖;真相文件优先用 write_truth_file,章节修订用 revise_chapter)
+- **write** — 新建文件,或者重写整个文件(已有内容会被覆盖;真相文件优先用 write_truth_file,整章精修/重写请用 sub_agent 的 reviser)
 - **grep** — 搜索内容(如"哪一章提到了某个角色")
 - **ls** — 列出文件或章节
 
@@ -92,7 +95,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 - 写章节、修订、审计等重操作 → 使用 sub_agent 委托对应子智能体
 - 用户问设定相关问题 → 先用 read 读取对应文件再回答
 - 用户想改设定/改真相文件 → 优先用 write_truth_file
-- 用户要求重写/精修已有章节 → 用 revise_chapter
+- 用户要求重写/精修已有章节 → sub_agent(agent="reviser", chapterNumber=N, mode=...)
 - 用户要求角色或实体改名 → 用 rename_entity
 - 用户要求对某一章做局部小修 → 用 patch_chapter_text
 - 当你已经明确目标文件和内容时,也可以直接使用 edit / write
@@ -122,17 +125,20 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 ## Available Tools
 
 - **sub_agent** — Delegate to sub-agents:
-  - agent="writer" write next chapter (params: chapterWordCount for word count override)
-  - agent="auditor" audit chapter quality (params: chapterNumber to target specific chapter)
-  - agent="reviser" revise chapter (params: chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
+  - agent="writer" **continue writing the NEXT chapter** (always appends after the latest written chapter; cannot target a specific number. params: chapterWordCount)
+  - agent="auditor" audit an **EXISTING chapter** (params: chapterNumber to target a specific chapter; omit for the latest)
+  - agent="reviser" modify an **EXISTING chapter** (**chapterNumber is required to identify which chapter**. params: chapterNumber, mode: spot-fix/polish/rewrite/rework/anti-detect)
   - agent="exporter" export book (params: format: txt/md/epub, approvedOnly: true/false)
+  - **writer vs reviser — common mistake, read carefully**:
+    - User says "revise/rewrite/fix chapter N" or "chapter N has issues" → **reviser** with chapterNumber=N (never writer — writer would produce a new chapter N+1)
+    - User says "write the next chapter" / "continue" / "one more chapter" → **writer** (never reviser, and never call reviser without chapterNumber)
+    - User refers to "that chapter we just did" without a number → **reviser** with chapterNumber=latest-written
 - **read** — Read truth files or chapter content
-- **revise_chapter** — Rewrite or polish an existing chapter
 - **write_truth_file** — Replace a canonical truth file in story/
 - **rename_entity** — Rename a character or entity across the book
 - **patch_chapter_text** — Apply a local deterministic patch to a chapter
 - **edit** — Exact string replacement on setting files (use patch_chapter_text for chapter text)
-- **write** — Create a new file, or fully replace an existing file's content (prefer write_truth_file for canonical truth files, revise_chapter for chapter revisions)
+- **write** — Create a new file, or fully replace an existing file's content (prefer write_truth_file for canonical truth files; for whole-chapter rewrites call sub_agent with agent="reviser")
 - **grep** — Search content across chapters
 - **ls** — List files or chapters
 
@@ -141,7 +147,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 - Use sub_agent for heavy operations (writing, revision, auditing)
 - Use read first for settings inquiries
 - Use write_truth_file for truth files and setting changes
-- Use revise_chapter for rewrite/polish/rework of existing chapters
+- For rewrite/polish/rework of an existing chapter → sub_agent(agent="reviser", chapterNumber=N, mode=...)
 - Use rename_entity for character/entity renames
 - Use patch_chapter_text for local chapter fixes
 - Use edit / write directly when you already know the exact target file and replacement content

+ 5 - 33
packages/core/src/agent/agent-tools.ts

@@ -1,7 +1,7 @@
 import { Type, type Static } from "@mariozechner/pi-ai";
 import type { AgentTool, AgentToolResult, AgentToolUpdateCallback } from "@mariozechner/pi-agent-core";
 import type { PipelineRunner } from "../pipeline/runner.js";
-import { DEFAULT_REVISE_MODE, type ReviseMode } from "../agents/reviser.js";
+import { type ReviseMode } from "../agents/reviser.js";
 import { readFile, writeFile, readdir, stat } from "node:fs/promises";
 import { join, normalize, resolve } from "node:path";
 import { StateManager } from "../state/manager.js";
@@ -226,36 +226,6 @@ export function createSubAgentTool(
 // 2. Deterministic writing tools
 // ---------------------------------------------------------------------------
 
-const ReviseChapterParams = Type.Object({
-  bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
-  chapterNumber: Type.Number({ description: "Chapter number to revise." }),
-  mode: Type.Optional(Type.Union([
-    Type.Literal("spot-fix"),
-    Type.Literal("polish"),
-    Type.Literal("rewrite"),
-    Type.Literal("rework"),
-    Type.Literal("anti-detect"),
-  ])),
-});
-
-export function createReviseChapterTool(
-  pipeline: PipelineRunner,
-  activeBookId: string | null,
-): AgentTool<typeof ReviseChapterParams> {
-  return {
-    name: "revise_chapter",
-    description: "Revise a specific chapter through the deterministic revision pipeline.",
-    label: "Revise Chapter",
-    parameters: ReviseChapterParams,
-    async execute(_toolCallId, params): Promise<AgentToolResult<undefined>> {
-      const bookId = resolveToolBookId("revise_chapter", params.bookId, activeBookId);
-      const mode = params.mode ?? DEFAULT_REVISE_MODE;
-      await pipeline.reviseDraft(bookId, params.chapterNumber, mode);
-      return textResult(`Revision (${mode}) complete for "${bookId}" chapter ${params.chapterNumber}.`);
-    },
-  };
-}
-
 const WriteTruthFileParams = Type.Object({
   bookId: Type.Optional(Type.String({ description: "Book ID. Omit to use the active book." })),
   fileName: Type.String({ description: "Truth file name under story/, e.g. story_bible.md or current_focus.md." }),
@@ -395,7 +365,8 @@ export function createEditTool(projectRoot: string): AgentTool<typeof EditParams
     description:
       "Edit a file under books/ via exact string replacement. " +
       "old_string must appear exactly once in the file. " +
-      "For chapter text use patch_chapter_text; for canonical truth files (story_bible/volume_outline/book_rules/current_focus) prefer write_truth_file.",
+      "For chapter text use patch_chapter_text; for canonical truth files (story_bible/volume_outline/book_rules/current_focus) prefer write_truth_file; " +
+      "to rewrite or polish a whole chapter call sub_agent with agent=\"reviser\".",
     label: "Edit File",
     parameters: EditParams,
     async execute(
@@ -439,7 +410,8 @@ export function createWriteFileTool(projectRoot: string): AgentTool<typeof Write
     description:
       "Create a new file, or fully replace an existing file's content under books/. " +
       "Parent directories are created automatically. Existing content is overwritten silently — " +
-      "for canonical truth files prefer write_truth_file; for chapter revisions use revise_chapter.",
+      "for canonical truth files prefer write_truth_file; " +
+      "for whole-chapter rewrites/polishing call sub_agent with agent=\"reviser\".",
     label: "Write File",
     parameters: WriteFileParams,
     async execute(

+ 0 - 1
packages/core/src/agent/index.ts

@@ -2,7 +2,6 @@ export { buildAgentSystemPrompt } from "./agent-system-prompt.js";
 export {
   createSubAgentTool,
   createReadTool,
-  createReviseChapterTool,
   createWriteTruthFileTool,
   createRenameEntityTool,
   createPatchChapterTextTool,