Sfoglia il codice sorgente

feat(core): add chapterNumber param to sub_agent tool for auditor/reviser

Previously auditor and reviser always operated on the latest chapter.
Now the agent can pass an explicit chapterNumber so "重写第5章" targets
chapter 5 instead of always hitting the latest.

- Added chapterNumber to SubAgentParams schema
- auditor: pipeline.auditDraft(bookId, chapterNumber)
- reviser: pipeline.reviseDraft(bookId, chapterNumber, mode)
- Updated system prompt to document the parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 6 mesi fa
parent
commit
988a4ce1e4

+ 6 - 4
packages/core/src/agent/agent-system-prompt.ts

@@ -72,9 +72,10 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 
 
 - **sub_agent** — 委托子智能体执行重操作:
 - **sub_agent** — 委托子智能体执行重操作:
   - agent="writer" 写下一章
   - agent="writer" 写下一章
-  - agent="auditor" 审计章节质量
-  - agent="reviser" 修订章节
+  - agent="auditor" 审计章节质量(可指定 chapterNumber)
+  - agent="reviser" 修订章节(可指定 chapterNumber 和修订模式)
   - agent="exporter" 导出书籍
   - agent="exporter" 导出书籍
+  - **chapterNumber 参数**:auditor 和 reviser 支持指定章节号,不指定则默认最新章节
 - **read** — 读取书籍的设定文件或章节内容
 - **read** — 读取书籍的设定文件或章节内容
 - **revise_chapter** — 对已有章节做精修/重写/返工
 - **revise_chapter** — 对已有章节做精修/重写/返工
 - **write_truth_file** — 整文件覆盖真相文件(story_bible、volume_outline、book_rules、current_focus 等)
 - **write_truth_file** — 整文件覆盖真相文件(story_bible、volume_outline、book_rules、current_focus 等)
@@ -118,9 +119,10 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
 
 
 - **sub_agent** — Delegate to sub-agents:
 - **sub_agent** — Delegate to sub-agents:
   - agent="writer" for writing next chapter
   - agent="writer" for writing next chapter
-  - agent="auditor" for chapter quality audit
-  - agent="reviser" for chapter revision
+  - agent="auditor" for chapter quality audit (supports chapterNumber)
+  - agent="reviser" for chapter revision (supports chapterNumber + mode via instruction)
   - agent="exporter" for book export
   - agent="exporter" for book export
+  - **chapterNumber param**: auditor and reviser accept an explicit chapter number; omit for latest
 - **read** — Read truth files or chapter content
 - **read** — Read truth files or chapter content
 - **revise_chapter** — Rewrite or polish an existing chapter
 - **revise_chapter** — Rewrite or polish an existing chapter
 - **write_truth_file** — Replace a canonical truth file in story/
 - **write_truth_file** — Replace a canonical truth file in story/

+ 7 - 165
packages/core/src/agent/agent-tools.ts

@@ -60,6 +60,7 @@ const SubAgentParams = Type.Object({
   instruction: Type.String({ description: "Natural language instruction from the main Agent" }),
   instruction: Type.String({ description: "Natural language instruction from the main Agent" }),
   bookId: Type.Optional(Type.String({ description: "Book ID — required for all agents except architect" })),
   bookId: Type.Optional(Type.String({ description: "Book ID — required for all agents except architect" })),
   title: Type.Optional(Type.String({ description: "Architect only: explicit book title. Required when creating a book." })),
   title: Type.Optional(Type.String({ description: "Architect only: explicit book title. Required when creating a book." })),
+  chapterNumber: Type.Optional(Type.Number({ description: "Target chapter number for auditor/reviser. Omit to use the latest chapter." })),
 });
 });
 
 
 function deriveBookIdFromTitle(title: string): string {
 function deriveBookIdFromTitle(title: string): string {
@@ -90,7 +91,7 @@ export function createSubAgentTool(
       _signal?: AbortSignal,
       _signal?: AbortSignal,
       onUpdate?: AgentToolUpdateCallback,
       onUpdate?: AgentToolUpdateCallback,
     ): Promise<AgentToolResult<undefined>> {
     ): Promise<AgentToolResult<undefined>> {
-      const { agent, instruction, bookId, title } = params;
+      const { agent, instruction, bookId, title, chapterNumber } = params;
 
 
       const progress = (msg: string) => {
       const progress = (msg: string) => {
         onUpdate?.(textResult(msg));
         onUpdate?.(textResult(msg));
@@ -130,8 +131,8 @@ export function createSubAgentTool(
 
 
           case "auditor": {
           case "auditor": {
             if (!bookId) return textResult("Error: bookId is required for the auditor agent.");
             if (!bookId) return textResult("Error: bookId is required for the auditor agent.");
-            progress(`Auditing draft for "${bookId}"...`);
-            const audit = await pipeline.auditDraft(bookId);
+            progress(`Auditing chapter ${chapterNumber ?? "latest"} for "${bookId}"...`);
+            const audit = await pipeline.auditDraft(bookId, chapterNumber);
             progress(`Audit complete for "${bookId}".`);
             progress(`Audit complete for "${bookId}".`);
             const issueCount = audit.issues?.length ?? 0;
             const issueCount = audit.issues?.length ?? 0;
             return textResult(
             return textResult(
@@ -150,10 +151,10 @@ export function createSubAgentTool(
                 : /rework|返工/.test(instruction)
                 : /rework|返工/.test(instruction)
                   ? "rework"
                   ? "rework"
                   : "spot-fix";
                   : "spot-fix";
-            progress(`Revising "${bookId}" in ${mode} mode...`);
-            await pipeline.reviseDraft(bookId, undefined, mode);
+            progress(`Revising "${bookId}" chapter ${chapterNumber ?? "latest"} in ${mode} mode...`);
+            await pipeline.reviseDraft(bookId, chapterNumber, mode);
             progress(`Revision complete for "${bookId}".`);
             progress(`Revision complete for "${bookId}".`);
-            return textResult(`Revision (${mode}) complete for "${bookId}".`);
+            return textResult(`Revision (${mode}) complete for "${bookId}" chapter ${chapterNumber ?? "latest"}.`);
           }
           }
 
 
           case "exporter": {
           case "exporter": {
@@ -186,165 +187,6 @@ export function createSubAgentTool(
   };
   };
 }
 }
 
 
-// ---------------------------------------------------------------------------
-// 1.5 Deterministic writing tools
-// ---------------------------------------------------------------------------
-
-const ReviseChapterParams = Type.Object({
-  bookId: Type.Optional(Type.String({ description: "Book ID. Defaults to the active book." })),
-  chapterNumber: Type.Optional(Type.Number({ description: "Chapter number. Defaults to the latest drafted chapter." })),
-  mode: Type.Optional(Type.Union([
-    Type.Literal("polish"),
-    Type.Literal("rewrite"),
-    Type.Literal("rework"),
-    Type.Literal("anti-detect"),
-    Type.Literal("spot-fix"),
-  ])),
-});
-
-export function createReviseChapterTool(
-  pipeline: PipelineRunner,
-  activeBookId: string | null,
-): AgentTool<typeof ReviseChapterParams> {
-  return {
-    name: "revise_chapter",
-    description:
-      "Rewrite or revise an existing chapter through the deterministic chapter revision pipeline. " +
-      "Use this for 精修、重写、返工, not the generic edit tool.",
-    label: "Revise Chapter",
-    parameters: ReviseChapterParams,
-    async execute(
-      _toolCallId: string,
-      params: Static<typeof ReviseChapterParams>,
-    ): Promise<AgentToolResult<undefined>> {
-      try {
-        const bookId = resolveToolBookId("revise_chapter", params.bookId, activeBookId);
-        const result = await pipeline.reviseDraft(
-          bookId,
-          params.chapterNumber as number,
-          (params.mode ?? DEFAULT_REVISE_MODE) as ReviseMode,
-        );
-        const responseText = `Chapter revision completed for "${bookId}"${params.chapterNumber ? ` chapter ${params.chapterNumber}` : ""}.`;
-        return textResult(responseText);
-      } catch (err: any) {
-        return textResult(`Failed to revise chapter: ${err?.message ?? String(err)}`);
-      }
-    },
-  };
-}
-
-const WriteTruthFileParams = Type.Object({
-  bookId: Type.Optional(Type.String({ description: "Book ID. Defaults to the active book." })),
-  fileName: Type.String({ description: "Truth file name, e.g. story_bible.md or current_focus.md" }),
-  content: Type.String({ description: "Complete replacement content for the truth file" }),
-});
-
-export function createWriteTruthFileTool(
-  pipeline: PipelineRunner,
-  projectRoot: string,
-  activeBookId: string | null,
-): AgentTool<typeof WriteTruthFileParams> {
-  const interactionTools = createDeterministicInteractionTools(pipeline, projectRoot);
-
-  return {
-    name: "write_truth_file",
-    description:
-      "Replace a canonical truth file in story/. Use this for story_bible, volume outline, rules, focus, and other book facts.",
-    label: "Write Truth File",
-    parameters: WriteTruthFileParams,
-    async execute(
-      _toolCallId: string,
-      params: Static<typeof WriteTruthFileParams>,
-    ): Promise<AgentToolResult<undefined>> {
-      try {
-        const bookId = resolveToolBookId("write_truth_file", params.bookId, activeBookId);
-        await interactionTools.writeTruthFile(bookId, params.fileName, params.content);
-        return textResult(`Updated ${params.fileName} for "${bookId}".`);
-      } catch (err: any) {
-        return textResult(`Failed to write truth file: ${err?.message ?? String(err)}`);
-      }
-    },
-  };
-}
-
-const RenameEntityParams = Type.Object({
-  bookId: Type.Optional(Type.String({ description: "Book ID. Defaults to the active book." })),
-  oldValue: Type.String({ description: "The old character/entity name" }),
-  newValue: Type.String({ description: "The new character/entity name" }),
-});
-
-export function createRenameEntityTool(
-  pipeline: PipelineRunner,
-  projectRoot: string,
-  activeBookId: string | null,
-): AgentTool<typeof RenameEntityParams> {
-  const interactionTools = createDeterministicInteractionTools(pipeline, projectRoot);
-
-  return {
-    name: "rename_entity",
-    description:
-      "Rename a character or entity across the current book through the deterministic edit controller.",
-    label: "Rename Entity",
-    parameters: RenameEntityParams,
-    async execute(
-      _toolCallId: string,
-      params: Static<typeof RenameEntityParams>,
-    ): Promise<AgentToolResult<undefined>> {
-      try {
-        const bookId = resolveToolBookId("rename_entity", params.bookId, activeBookId);
-        const result = await interactionTools.renameEntity(bookId, params.oldValue, params.newValue);
-        const responseText = (result as { __interaction?: { responseText?: string } }).__interaction?.responseText
-          ?? `Renamed ${params.oldValue} to ${params.newValue} in "${bookId}".`;
-        return textResult(responseText);
-      } catch (err: any) {
-        return textResult(`Failed to rename entity: ${err?.message ?? String(err)}`);
-      }
-    },
-  };
-}
-
-const PatchChapterTextParams = Type.Object({
-  bookId: Type.Optional(Type.String({ description: "Book ID. Defaults to the active book." })),
-  chapterNumber: Type.Number({ description: "Chapter number to patch" }),
-  targetText: Type.String({ description: "Exact text to replace" }),
-  replacementText: Type.String({ description: "Replacement text" }),
-});
-
-export function createPatchChapterTextTool(
-  pipeline: PipelineRunner,
-  projectRoot: string,
-  activeBookId: string | null,
-): AgentTool<typeof PatchChapterTextParams> {
-  const interactionTools = createDeterministicInteractionTools(pipeline, projectRoot);
-
-  return {
-    name: "patch_chapter_text",
-    description:
-      "Apply a local deterministic patch to an existing chapter and mark it for review.",
-    label: "Patch Chapter Text",
-    parameters: PatchChapterTextParams,
-    async execute(
-      _toolCallId: string,
-      params: Static<typeof PatchChapterTextParams>,
-    ): Promise<AgentToolResult<undefined>> {
-      try {
-        const bookId = resolveToolBookId("patch_chapter_text", params.bookId, activeBookId);
-        const result = await interactionTools.patchChapterText(
-          bookId,
-          params.chapterNumber,
-          params.targetText,
-          params.replacementText,
-        );
-        const responseText = (result as { __interaction?: { responseText?: string } }).__interaction?.responseText
-          ?? `Patched chapter ${params.chapterNumber} in "${bookId}".`;
-        return textResult(responseText);
-      } catch (err: any) {
-        return textResult(`Failed to patch chapter text: ${err?.message ?? String(err)}`);
-      }
-    },
-  };
-}
-
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------
 // 2. Read Tool
 // 2. Read Tool
 // ---------------------------------------------------------------------------
 // ---------------------------------------------------------------------------