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>
This commit is contained in:
fanghanjun
2026-04-16 10:52:36 -07:00
parent 52250539db
commit a57f9db0d5
6 changed files with 35 additions and 73 deletions
@@ -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");
});
@@ -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");
-2
View File
@@ -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
View File
@@ -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(绝不能用 writerwriter 会写新的第 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
View File
@@ -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(
-1
View File
@@ -2,7 +2,6 @@ export { buildAgentSystemPrompt } from "./agent-system-prompt.js";
export {
createSubAgentTool,
createReadTool,
createReviseChapterTool,
createWriteTruthFileTool,
createRenameEntityTool,
createPatchChapterTextTool,