Files
picoclaw/.ralph/progress.txt
yinwm 35fa64cde8 feat: US-015 - Add EditTool tests
Added comprehensive test coverage for EditTool (EditFileTool, AppendFileTool) with 10 test cases:
- TestEditTool_EditFile_Success: Verifies successful file editing
- TestEditTool_EditFile_NotFound: Verifies error handling for non-existent files
- TestEditTool_EditFile_OldTextNotFound: Verifies error when old_text not found
- TestEditTool_EditFile_MultipleMatches: Verifies error for multiple occurrences
- TestEditTool_EditFile_OutsideAllowedDir: Verifies directory restriction
- TestEditTool_EditFile_MissingPath: Verifies missing path parameter
- TestEditTool_EditFile_MissingOldText: Verifies missing old_text parameter
- TestEditTool_EditFile_MissingNewText: Verifies missing new_text parameter
- TestEditTool_AppendFile_Success: Verifies successful file appending
- TestEditTool_AppendFile_MissingPath: Verifies missing path for append
- TestEditTool_AppendFile_MissingContent: Verifies missing content for append

EditTool implementation already conforms to ToolResult specification:
- EditFile returns SilentResult('File edited: ...')
- AppendFile returns SilentResult('Appended to ...')
- Errors return ErrorResult with IsError=true

EditFileTool includes security feature: optional directory restriction to prevent editing files outside allowed paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 19:55:57 +08:00

323 lines
17 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Ralph Progress: tool-result-refactor
# Branch: ralph/tool-result-refactor
## Overview
Tool 返回值结构化重构 - 将 Tool 接口返回值从 (string, error) 改为结构化 ToolResult支持异步任务删除字符串匹配黑魔法
## Progress
### Completed (15/21)
- US-001: Add ToolResult struct and helper functions
- US-002: Modify Tool interface to return *ToolResult
- US-004: Delete isToolConfirmationMessage function (already removed in commit 488e7a9)
- US-005: Update AgentLoop tool result logic
- US-006: Add AsyncCallback type and AsyncTool interface
- US-007: Heartbeat async task execution support
- US-008: Inject callback into async tools in AgentLoop
- US-009: State save atomicity - SetLastChannel
- US-010: Update RecordLastChannel to use atomic save
- US-011: Refactor MessageTool to use ToolResult
- US-012: Refactor ShellTool to use ToolResult
- US-013: Refactor FilesystemTool to use ToolResult
- US-014: Refactor WebTool to use ToolResult
- US-015: Refactor EditTool to use ToolResult
- US-012: Refactor ShellTool to use ToolResult
- US-013: Refactor FilesystemTool to use ToolResult
### In Progress
### Blocked
### Pending
| ID | Title | Status | Notes |
|----|-------|--------|-------|
| US-003 | Modify ToolRegistry to process ToolResult | Pending | registry.go already updated |
| US-004 | Delete isToolConfirmationMessage function | Completed | Already removed in commit 488e7a9 |
| US-005 | Update AgentLoop tool result processing logic | Completed | No test files in pkg/agent yet |
| US-006 | Add AsyncCallback type and AsyncTool interface | Completed | |
| US-007 | Heartbeat async task execution support | Completed | |
| US-008 | Inject callback into async tools in AgentLoop | Completed | |
| US-009 | State save atomicity - SetLastChannel | Completed | |
| US-010 | Update RecordLastChannel to use atomic save | Completed | |
| US-011 | Refactor MessageTool to use ToolResult | Completed | Added test file message_test.go |
| US-012 | Refactor ShellTool to use ToolResult | Completed | |
| US-013 | Refactor FilesystemTool to use ToolResult | Completed | Added test file filesystem_test.go |
| US-014 | Refactor WebTool to use ToolResult | Completed | Added test file web_test.go |
| US-015 | Refactor EditTool to use ToolResult | Completed | Added test file edit_test.go |
| US-015 | Refactor EditTool to use ToolResult | Completed | |
| US-016 | Refactor CronTool to use ToolResult | Pending | |
| US-017 | Refactor SpawnTool to use AsyncTool and callbacks | Pending | |
| US-018 | Refactor SubagentTool to use ToolResult | Pending | |
| US-019 | Enable heartbeat by default in config | Pending | |
| US-020 | Move heartbeat log to memory directory | Pending | |
| US-021 | Heartbeat calls ExecuteHeartbeatWithTools | Pending | |
---
## [2026-02-12] - US-002
- What was implemented:
- 修复了所有剩余 Tool 实现的 Execute 方法返回值类型:
- `shell.go`: ExecTool 成功时返回 UserResultForUser=命令输出),失败时返回 ErrorResult
- `spawn.go`: SpawnTool 成功返回 NewToolResult失败返回 ErrorResult
- `web.go`: WebSearchTool 和 WebFetchTool 返回 ToolResultForUser=内容ForLLM=摘要)
- `edit.go`: EditFileTool 和 AppendFileTool 成功返回 SilentResult失败返回 ErrorResult
- `filesystem.go`: ReadFileTool、WriteFileTool、ListDirTool 成功返回 SilentResult 或 NewToolResult失败返回 ErrorResult
- 临时禁用了 cronTool 相关代码main.go等待 US-016 完成
- Files changed:
- `pkg/tools/shell.go`
- `pkg/tools/spawn.go`
- `pkg/tools/web.go`
- `pkg/tools/edit.go`
- `pkg/tools/filesystem.go`
- `cmd/picoclaw/main.go`
- **Learnings for future iterations:**
- **Patterns discovered:** 代码重构需要分步骤进行。先修改接口签名,再修改实现,最后处理调用方。
- **Gotchas encountered:** 临时禁用的代码(如 cronTool需要同时注释掉所有相关的启动/停止调用,否则会编译失败。
- **Useful context:** `cron.go` 已被临时禁用(包含注释说明),将在 US-016 中恢复。main.go 中的 cronTool 相关代码也已用注释标记为临时禁用。
---
## [2026-02-12] - US-005
- What was implemented:
- 修改 `runLLMIteration` 返回值,增加 `lastToolResult *tools.ToolResult` 参数
- 在工具执行循环中,立即发送非 Silent 的 ForUser 内容给用户
- 使用 `toolResult.ForLLM` 发送内容给 LLM
- 实现了 Silent 标志检查:`if !toolResult.Silent && toolResult.ForUser != ""`
- 记录最后执行的工具结果用于后续决策
- Files changed:
- `pkg/agent/loop.go`
- **Learnings for future iterations:**
- **Patterns discovered:** 工具结果的处理需要区分两个目的地LLM (ForLLM) 和用户 (ForUser)。用户消息应该在工具执行后立即发送,而不是等待 LLM 的最终响应。
- **Gotchas encountered:** 编辑大文件时要小心不要引入重复代码。我之前编辑时没有完整替换代码块,导致有重复的代码段。
- **Useful context:** `opts.SendResponse` 参数控制是否发送响应给用户。当工具设置了 `ForUser` 时,即使 Silent=false也只有在 `SendResponse=true` 时才会发送。
---
## [2026-02-12] - US-006
- What was implemented:
- 在 `pkg/tools/base.go` 中定义 `AsyncCallback` 函数类型
- 定义 `AsyncTool` 接口,包含 `SetCallback(cb AsyncCallback)` 方法
- 添加完整的 godoc 注释,包含使用示例
- Files changed:
- `pkg/tools/base.go`
- **Learnings for future iterations:**
- **Patterns discovered:** Go 接口的设计应该是可选的组合模式。`AsyncTool` 是一个可选接口,工具可以选择实现以支持异步操作。
- **Gotchas encountered:** 无
- **Useful context:** 这个模式将在 US-008 中用于 `SpawnTool`,让子代理完成时能够通知主循环。
---
## [2026-02-12] - US-007
- What was implemented:
- 在 `pkg/heartbeat/service.go` 中添加了本地 `ToolResult` 结构体定义(避免循环依赖)
- 定义了 `HeartbeatHandler` 函数类型:`func(prompt string) *ToolResult`
- 在 `HeartbeatService` 中添加了 `onHeartbeatWithTools` 字段
- 添加了 `SetOnHeartbeatWithTools(handler HeartbeatHandler)` 方法来设置新的处理器
- 添加了 `ExecuteHeartbeatWithTools(prompt string)` 公开方法
- 添加了内部方法 `executeHeartbeatWithTools(prompt string)` 来处理工具结果
- 更新了 `checkHeartbeat()` 方法,优先使用新的工具支持处理器
- 异步任务检测:当 `result.Async == true` 时,记录日志并立即返回
- 错误处理:当 `result.IsError == true` 时,记录错误日志
- 普通完成:记录完成日志
- Files changed:
- `pkg/heartbeat/service.go`
- `pkg/heartbeat/service_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** 为了避免循环依赖heartbeat 包定义了自己的本地 `ToolResult` 结构体,而不是导入 `pkg/tools` 包。
- **Gotchas encountered:** 原始代码中的 `running()` 函数逻辑有问题(新创建的服务会被认为是"正在运行"的),但这不在本次修改范围内。
- **Useful context:** 心跳服务现在支持两种处理器:旧的 `onHeartbeat (返回 string, error)` 和新的 `onHeartbeatWithTools (返回 *ToolResult)`。新的处理器优先级更高。
---
## [2026-02-12] - US-008
- What was implemented:
- 修改 `ToolRegistry.ExecuteWithContext` 方法签名,增加 `asyncCallback AsyncCallback` 参数
- 在 `ExecuteWithContext` 中检查工具是否实现 `AsyncTool` 接口
- 如果工具实现 `AsyncTool` 且回调非空,调用 `SetCallback` 设置回调
- 添加日志记录异步回调注入
- 在 `AgentLoop.runLLMIteration` 中定义 `asyncCallback` 回调函数
- 回调函数使用 `al.bus.PublishOutbound` 发送结果给用户
- 更新 `Execute` 方法以适配新的签名(传递 nil 作为回调)
- 添加完整的日志记录异步工具结果发送
- Files changed:
- `pkg/tools/registry.go`
- `pkg/agent/loop.go`
- **Learnings for future iterations:**
- **Patterns discovered:** 回调函数应该在工具执行循环中定义,这样可以捕获 `opts.Channel` 和 `opts.ChatID` 等上下文信息。
- **Gotchas encountered:** 更新方法签名时需要同时更新所有调用点。我修改了 `ExecuteWithContext` 的签名,所以也更新了 `Execute` 方法的调用。
- **Useful context:** 异步工具完成时会调用回调,回调将 `ForUser` 内容发送给用户。这允许长时间运行的操作(如子代理)在后台完成并通知用户,而不阻塞主循环。
---
## [2026-02-12] - US-009
- What was implemented:
- 创建新的 `pkg/state` 包,包含状态管理和原子保存功能
- 定义 `State` 结构体,包含 `LastChannel`、`LastChatID` 和 `Timestamp` 字段
- 定义 `Manager` 结构体,使用 `sync.RWMutex` 保护并发访问
- 实现 `NewManager(workspace string)` 构造函数,创建状态目录并加载现有状态
- 实现 `SetLastChannel(workspace, channel string)` 方法,使用临时文件 + 重命名模式实现原子保存
- 实现 `SetLastChatID(workspace, chatID string)` 方法
- 实现 `GetLastChannel()` 和 `GetLastChatID()` getter 方法
- 实现 `saveAtomic()` 内部方法,使用 `os.WriteFile` 写入临时文件,然后用 `os.Rename` 原子性地重命名
- 如果重命名失败,清理临时文件
- 实现 `load()` 方法,从磁盘加载状态
- 添加完整的测试:`TestAtomicSave`、`TestSetLastChatID`、`TestAtomicity_NoCorruptionOnInterrupt`、`TestConcurrentAccess`、`TestNewManager_ExistingState`、`TestNewManager_EmptyWorkspace`
- Files changed:
- `pkg/state/state.go` (新增)
- `pkg/state/state_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** 临时文件 + 重命名模式是实现原子写入的标准方法。在 POSIX 系统上,`os.Rename` 是原子操作。
- **Gotchas encountered:** 临时文件必须与目标文件在同一文件系统中,否则 `os.Rename` 会失败。
- **Useful context:** 这个模式将在 US-010 中用于 `RecordLastChannel`,确保状态更新的原子性。
---
## [2026-02-12] - US-010
- What was implemented:
- 在 AgentLoop 中添加 `state *state.Manager` 字段
- 在 `NewAgentLoop` 中初始化 `stateManager := state.NewManager(workspace)`
- 实现 `RecordLastChannel(channel string)` 方法,调用 `al.state.SetLastChannel(al.workspace, channel)`
- 实现 `RecordLastChatID(chatID string)` 方法,调用 `al.state.SetLastChatID(al.workspace, chatID)`
- 添加完整的测试:`TestRecordLastChannel`、`TestRecordLastChatID`、`TestNewAgentLoop_StateInitialized`
- 验证状态持久化:创建新的 AgentLoop 实例后状态仍然保留
- Files changed:
- `pkg/agent/loop.go`
- `pkg/agent/loop_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** 状态管理应该集中在一个专门的包中(如 `pkg/state`),而不是分散在各个模块中。
- **Gotchas encountered:** 测试中的 mock Provider 需要实现完整的 `LLMProvider` 接口,包括 `GetDefaultModel()` 方法。
- **Useful context:** `RecordLastChannel` 方法现在可以用于跟踪用户最后一次活跃的频道,这对于跨会话的上下文恢复很有用。
---
## [2026-02-12] - US-011
- What was implemented:
- MessageTool 已经完全使用 ToolResult 返回值(无需修改实现)
- 添加了完整的测试文件 `pkg/tools/message_test.go`,包含 10 个测试用例
- 测试覆盖了所有验收标准:
- 发送成功返回 SilentResultSilent=true
- ForLLM 包含发送状态描述
- ForUser 为空(用户已直接收到消息)
- 发送失败返回 ErrorResultIsError=true
- 支持自定义 channel/chat_id 参数
- 支持错误场景(缺少 content、无目标 channel、未配置回调
- Files changed:
- `pkg/tools/message_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** MessageTool 实现已经符合 ToolResult 规范,只需要添加测试覆盖即可。
- **Gotchas encountered:** 测试文件使用 `map[string]interface{}` 作为 args 参数类型,与 Tool.Execute 签名一致。
- **Useful context:** MessageTool 的设计模式是"用户已直接收到消息,所以 ForUser 为空且 Silent=true",避免重复发送。
---
## [2026-02-12] - US-012
- What was implemented:
- ShellTool 已经完全使用 ToolResult 返回值(无需修改实现)
- 添加了完整的测试文件 `pkg/tools/shell_test.go`,包含 9 个测试用例
- 测试覆盖了所有验收标准:
- 成功返回 ForUser = 命令输出
- 失败返回 IsError = true
- ForLLM 包含完整输出和退出码
- 自定义工作目录测试
- 危险命令阻止测试
- 缺少命令错误处理
- Stderr 捕获测试
- 输出截断测试
- 工作空间限制测试
- Files changed:
- `pkg/tools/shell_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** ShellTool 实现已经符合 ToolResult 规范,只需要添加测试覆盖即可。测试覆盖了成功、失败、超时、自定义目录、安全防护等多种场景。
- **Gotchas encountered:** 无
- **Useful context:** ShellTool 的安全防护机制包括:危险命令检测(如 `rm -rf`)、工作空间路径遍历检测、命令超时控制。
---
## [2026-02-12] - US-013
- What was implemented:
- FilesystemTool 已经完全使用 ToolResult 返回值(无需修改实现)
- 添加了完整的测试文件 `pkg/tools/filesystem_test.go`,包含 10 个测试用例
- 测试覆盖了所有验收标准:
- ReadFileTool 成功返回 NewToolResultForLLM=内容ForUser=空)
- WriteFileTool 成功返回 SilentResultSilent=trueForUser=空)
- ListDirTool 成功返回 NewToolResult列出文件和目录
- 错误场景返回 ErrorResultIsError=true
- 缺少参数的错误处理
- 目录自动创建功能测试
- 默认路径处理测试
- Files changed:
- `pkg/tools/filesystem_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** FilesystemTool 中不同操作使用不同的返回值模式ReadFile/ListDir 使用 NewToolResult仅设置 ForLLMWriteFile 使用 SilentResult设置 Silent=true
- **Gotchas encountered:** 最初误解了 NewToolResult 的行为,以为它会设置 ForUser但实际上它只设置 ForLLM。
- **Useful context:** WriteFileTool 会自动创建不存在的目录(使用 os.MkdirAll这是文件写入工具的重要功能。
---
## [2026-02-12] - US-014
- What was implemented:
- WebTool 已经完全使用 ToolResult 返回值(无需修改实现)
- 添加了完整的测试文件 `pkg/tools/web_test.go`,包含 9 个测试用例
- 测试覆盖了所有验收标准:
- WebFetchTool 成功返回 ForUser=获取的内容ForLLM=摘要
- WebSearchTool 缺少 API Key 返回 ErrorResult
- URL 验证测试(无效 URL、不支持 scheme、缺少域名
- JSON 内容处理测试
- HTML 提取和清理测试(移除 script/style 标签)
- 内容截断测试
- 缺少参数错误处理
- Files changed:
- `pkg/tools/web_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** WebTool 使用 httptest.NewServer 创建模拟服务器进行测试,避免依赖外部 API。WebFetchTool 返回 JSON 格式的结构化内容给用户,包含 url、status、extractor、truncated、length、text 字段。
- **Gotchas encountered:** 无
- **Useful context:** WebSearchTool 需要配置 BRAVE_API_KEY 环境变量才能正常工作。WebFetchTool 支持多种内容类型JSON格式化、HTML文本提取、纯文本。
---
## [2026-02-12] - US-015
- What was implemented:
- EditTool 已经完全使用 ToolResult 返回值(无需修改实现)
- 添加了完整的测试文件 `pkg/tools/edit_test.go`,包含 10 个测试用例
- 测试覆盖了所有验收标准:
- EditFileTool 成功返回 SilentResultSilent=trueForUser=空)
- AppendFileTool 成功返回 SilentResult
- 文件不存在错误处理
- old_text 不存在错误处理
- 多次匹配错误处理
- 目录限制测试
- 缺少参数错误处理
- Files changed:
- `pkg/tools/edit_test.go` (新增)
- **Learnings for future iterations:**
- **Patterns discovered:** EditTool 包含两个子工具EditFileTool替换文本和 AppendFileTool追加内容。都返回 SilentResult 避免重复发送内容给用户。
- **Gotchas encountered:** 无
- **Useful context:** EditFileTool 支持可选的目录限制,用于安全控制,防止编辑允许目录外的文件。
---