diff --git a/packages/core/src/__tests__/agent-system-prompt.test.ts b/packages/core/src/__tests__/agent-system-prompt.test.ts index 4ddf6f7..552ff73 100644 --- a/packages/core/src/__tests__/agent-system-prompt.test.ts +++ b/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"); }); diff --git a/packages/core/src/__tests__/agent-tools.test.ts b/packages/core/src/__tests__/agent-tools.test.ts index 0a8b0a6..5e2c4be 100644 --- a/packages/core/src/__tests__/agent-tools.test.ts +++ b/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"); diff --git a/packages/core/src/agent/agent-session.ts b/packages/core/src/agent/agent-session.ts index 8df153a..8531c7d 100644 --- a/packages/core/src/agent/agent-session.ts +++ b/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), diff --git a/packages/core/src/agent/agent-system-prompt.ts b/packages/core/src/agent/agent-system-prompt.ts index a8469a3..b38de1b 100644 --- a/packages/core/src/agent/agent-system-prompt.ts +++ b/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 diff --git a/packages/core/src/agent/agent-tools.ts b/packages/core/src/agent/agent-tools.ts index 9a061a6..fec1e8f 100644 --- a/packages/core/src/agent/agent-tools.ts +++ b/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 { - return { - name: "revise_chapter", - description: "Revise a specific chapter through the deterministic revision pipeline.", - label: "Revise Chapter", - parameters: ReviseChapterParams, - async execute(_toolCallId, params): Promise> { - 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