Ver código fonte

feat(agent): 添加 write 工具,支持创建和覆盖写入文件

pi-agent 会话新增 write 工具,路径限制在 books/ 下,
自动创建父目录,safeBooksPath 防路径穿越。

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fanghanjun 6 meses atrás
pai
commit
72fa992df8

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

@@ -8,6 +8,7 @@ import {
   createSubAgentTool,
   createReadTool,
   createEditTool,
+  createWriteFileTool,
   createGrepTool,
   createLsTool,
 } from "./agent-tools.js";
@@ -233,6 +234,7 @@ export async function runAgentSession(
           createSubAgentTool(pipeline, bookId),
           createReadTool(projectRoot),
           createEditTool(projectRoot),
+          createWriteFileTool(projectRoot),
           createGrepTool(projectRoot),
           createLsTool(projectRoot),
         ],

+ 2 - 0
packages/core/src/agent/agent-system-prompt.ts

@@ -78,6 +78,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
   - agent="exporter" 导出书籍(参数:format: txt/md/epub, approvedOnly: true/false)
 - **read** — 读取书籍的设定文件或章节内容
 - **edit** — 编辑设定文件(如修改角色名、调整世界观)
+- **write** — 创建新文件或覆盖写入已有文件
 - **grep** — 搜索内容(如"哪一章提到了某个角色")
 - **ls** — 列出文件或章节
 
@@ -119,6 +120,7 @@ export function buildAgentSystemPrompt(bookId: string | null, language: string):
   - agent="exporter" export book (params: format: txt/md/epub, approvedOnly: true/false)
 - **read** — Read truth files or chapter content
 - **edit** — Edit truth files (rename characters, adjust world settings)
+- **write** — Create a new file or overwrite an existing file
 - **grep** — Search content across chapters
 - **ls** — List files or chapters
 

+ 36 - 0
packages/core/src/agent/agent-tools.ts

@@ -247,6 +247,42 @@ export function createEditTool(projectRoot: string): AgentTool<typeof EditParams
   };
 }
 
+// ---------------------------------------------------------------------------
+// 3b. Write File Tool
+// ---------------------------------------------------------------------------
+
+const WriteFileParams = Type.Object({
+  path: Type.String({ description: "File path relative to books/" }),
+  content: Type.String({ description: "Full file content to write" }),
+});
+
+export function createWriteFileTool(projectRoot: string): AgentTool<typeof WriteFileParams> {
+  const booksRoot = join(projectRoot, "books");
+
+  return {
+    name: "write",
+    description:
+      "Create a new file or overwrite an existing file. " +
+      "Path is relative to books/. Parent directories are created automatically.",
+    label: "Write File",
+    parameters: WriteFileParams,
+    async execute(
+      _toolCallId: string,
+      params: Static<typeof WriteFileParams>,
+    ): Promise<AgentToolResult<undefined>> {
+      try {
+        const filePath = safeBooksPath(booksRoot, params.path);
+        const { mkdir } = await import("node:fs/promises");
+        await mkdir(resolve(filePath, ".."), { recursive: true });
+        await writeFile(filePath, params.content, "utf-8");
+        return textResult(`File "${params.path}" written successfully.`);
+      } catch (err: any) {
+        return textResult(`Failed to write "${params.path}": ${err?.message ?? String(err)}`);
+      }
+    },
+  };
+}
+
 // ---------------------------------------------------------------------------
 // 4. Grep Tool
 // ---------------------------------------------------------------------------

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

@@ -1,3 +1,3 @@
 export { buildAgentSystemPrompt } from "./agent-system-prompt.js";
-export { createSubAgentTool, createReadTool, createEditTool, createGrepTool, createLsTool } from "./agent-tools.js";
+export { createSubAgentTool, createReadTool, createEditTool, createWriteFileTool, createGrepTool, createLsTool } from "./agent-tools.js";
 export { runAgentSession, evictAgentCache, type AgentSessionConfig, type AgentSessionResult } from "./agent-session.js";