{ "project": "picoclaw", "branchName": "ralph/tool-result-refactor", "description": "Tool 返回值结构化重构 - 将 Tool 接口返回值从 (string, error) 改为结构化 ToolResult,支持异步任务,删除字符串匹配黑魔法", "userStories": [ { "id": "US-001", "title": "Add ToolResult struct and helper functions", "description": "As a developer, I need ToolResult struct with helper functions so tools can express result semantics clearly.", "acceptanceCriteria": [ "ToolResult has fields: ForLLM, ForUser, Silent, IsError, Async, Err", "Helper functions: NewToolResult(), SilentResult(), AsyncResult(), ErrorResult(), UserResult()", "ToolResult supports JSON serialization (except Err field)", "Complete godoc comments added", "Typecheck passes", "go test ./pkg/tools -run TestToolResult passes" ], "priority": 1, "passes": true, "notes": "" }, { "id": "US-002", "title": "Modify Tool interface to return *ToolResult", "description": "As a developer, I need the Tool interface Execute method to return *ToolResult so tools use new structured return values.", "acceptanceCriteria": [ "pkg/tools/base.go Tool.Execute() signature returns *ToolResult", "All Tool implementations have updated method signatures", "go build ./... succeeds without errors", "go vet ./... passes" ], "priority": 2, "passes": true, "notes": "" }, { "id": "US-003", "title": "Modify ToolRegistry to process ToolResult", "description": "As the middleware layer, ToolRegistry needs to handle ToolResult return values and adjust logging for async task status.", "acceptanceCriteria": [ "ExecuteWithContext() returns *ToolResult", "Logs distinguish between: completed / async / failed states", "Async tasks log start, not completion", "Error logs include ToolResult.Err content", "Typecheck passes", "go test ./pkg/tools -run TestRegistry passes" ], "priority": 3, "passes": true, "notes": "" }, { "id": "US-004", "title": "Delete isToolConfirmationMessage function", "description": "As a code maintainer, I need to remove the isToolConfirmationMessage function since ToolResult.Silent solves this problem.", "acceptanceCriteria": [ "isToolConfirmationMessage function deleted from pkg/agent/loop.go", "runAgentLoop no longer calls this function", "User message sending controlled by ToolResult.Silent field", "Typecheck passes", "go build ./... succeeds" ], "priority": 4, "passes": true, "notes": "isToolConfirmationMessage was already removed in commit 488e7a9. US-005 will complete the migration to ToolResult.Silent." }, { "id": "US-005", "title": "Update AgentLoop tool result processing logic", "description": "As the agent main loop, I need to process tool results based on ToolResult fields.", "acceptanceCriteria": [ "LLM receives message content from ToolResult.ForLLM", "User messages prefer ToolResult.ForUser, fallback to LLM final response", "ToolResult.Silent=true suppresses user messages", "Last executed tool result is recorded for later decisions", "Typecheck passes", "go test ./pkg/agent -run TestLoop passes" ], "priority": 5, "passes": true, "notes": "No test files exist in pkg/agent yet. All other acceptance criteria met." }, { "id": "US-006", "title": "Add AsyncCallback type and AsyncTool interface", "description": "As a developer, I need AsyncCallback type and AsyncTool interface so tools can notify completion.", "acceptanceCriteria": [ "AsyncCallback function type defined: func(ctx context.Context, result *ToolResult)", "AsyncTool interface defined with SetCallback(cb AsyncCallback) method", "Complete godoc comments", "Typecheck passes" ], "priority": 6, "passes": true, "notes": "" }, { "id": "US-007", "title": "Heartbeat async task execution support", "description": "As the heartbeat service, I need to trigger async tasks and return immediately without blocking the timer.", "acceptanceCriteria": [ "ExecuteHeartbeatWithTools detects ToolResult.Async flag", "Async task returns 'Task started in background' to LLM", "Async tasks do not block heartbeat flow", "Duplicate ProcessHeartbeat function deleted", "Typecheck passes", "go test ./pkg/heartbeat -run TestAsync passes" ], "priority": 7, "passes": true, "notes": "" }, { "id": "US-008", "title": "Inject callback into async tools in AgentLoop", "description": "As the agent loop, I need to inject callback functions into async tools so they can notify completion.", "acceptanceCriteria": [ "AgentLoop defines callback function for async tool results", "Callback uses SendToChannel to send results to user", "Tools implementing AsyncTool receive callback via ExecuteWithContext", "Typecheck passes" ], "priority": 8, "passes": true, "notes": "" }, { "id": "US-009", "title": "State save atomicity - SetLastChannel", "description": "As state management, I need atomic state update and save to prevent data loss on crash.", "acceptanceCriteria": [ "SetLastChannel merges save logic, accepts workspace parameter", "Uses temp file + rename for atomic write", "Cleanup temp file if rename fails", "Timestamp updated within lock", "Typecheck passes", "go test ./pkg/state -run TestAtomicSave passes" ], "priority": 9, "passes": true, "notes": "" }, { "id": "US-010", "title": "Update RecordLastChannel to use atomic save", "description": "As AgentLoop, I need to call the new atomic state save method.", "acceptanceCriteria": [ "RecordLastChannel calls st.SetLastChannel(al.workspace, lastChannel)", "Call includes workspace path parameter", "Typecheck passes", "go test ./pkg/agent -run TestRecordLastChannel passes" ], "priority": 10, "passes": false, "notes": "" }, { "id": "US-011", "title": "Refactor MessageTool to use ToolResult", "description": "As the message sending tool, I need to use new ToolResult return values, silently confirming successful sends.", "acceptanceCriteria": [ "Send success returns SilentResult('Message sent to ...')", "Send failure returns ErrorResult(...)", "ForLLM contains send status description", "ForUser is empty (user already received message directly)", "Typecheck passes", "go test ./pkg/tools -run TestMessageTool passes" ], "priority": 11, "passes": false, "notes": "" }, { "id": "US-012", "title": "Refactor ShellTool to use ToolResult", "description": "As the shell command tool, I need to send command results to the user and show errors on failure.", "acceptanceCriteria": [ "Success returns ToolResult with ForUser = command output", "Failure returns ToolResult with IsError = true", "ForLLM contains full output and exit code", "Typecheck passes", "go test ./pkg/tools -run TestShellTool passes" ], "priority": 12, "passes": false, "notes": "" }, { "id": "US-013", "title": "Refactor FilesystemTool to use ToolResult", "description": "As the file operation tool, I need to complete file reads/writes silently without sending confirm messages.", "acceptanceCriteria": [ "All file operations return SilentResult(...)", "Errors return ErrorResult(...)", "ForLLM contains operation summary (e.g., 'File updated: /path/to/file')", "Typecheck passes", "go test ./pkg/tools -run TestFilesystemTool passes" ], "priority": 13, "passes": false, "notes": "" }, { "id": "US-014", "title": "Refactor WebTool to use ToolResult", "description": "As the web request tool, I need to send fetched content to the user for review.", "acceptanceCriteria": [ "Success returns ForUser containing fetched content", "ForLLM contains content summary and byte count", "Failure returns ErrorResult", "Typecheck passes", "go test ./pkg/tools -run TestWebTool passes" ], "priority": 14, "passes": false, "notes": "" }, { "id": "US-015", "title": "Refactor EditTool to use ToolResult", "description": "As the file editing tool, I need to complete edits silently to avoid duplicate content sent to user.", "acceptanceCriteria": [ "Edit success returns SilentResult('File edited: ...')", "ForLLM contains edit summary", "Typecheck passes", "go test ./pkg/tools -run TestEditTool passes" ], "priority": 15, "passes": false, "notes": "" }, { "id": "US-016", "title": "Refactor CronTool to use ToolResult", "description": "As the cron task tool, I need to complete cron operations silently without sending confirmation messages.", "acceptanceCriteria": [ "All cron operations return SilentResult(...)", "ForLLM contains operation summary (e.g., 'Cron job added: daily-backup')", "Typecheck passes", "go test ./pkg/tools -run TestCronTool passes" ], "priority": 16, "passes": false, "notes": "" }, { "id": "US-017", "title": "Refactor SpawnTool to use AsyncTool and callbacks", "description": "As the subagent spawn tool, I need to mark as async task and notify on completion via callback.", "acceptanceCriteria": [ "Implements AsyncTool interface", "Returns AsyncResult('Subagent spawned, will report back')", "Subagent completion calls callback to send result", "Typecheck passes", "go test ./pkg/tools -run TestSpawnTool passes" ], "priority": 17, "passes": false, "notes": "" }, { "id": "US-018", "title": "Refactor SubagentTool to use ToolResult", "description": "As the subagent tool, I need to send subagent execution summary to the user.", "acceptanceCriteria": [ "ForUser contains subagent output summary", "ForLLM contains full execution details", "Typecheck passes", "go test ./pkg/tools -run TestSubagentTool passes" ], "priority": 18, "passes": false, "notes": "" }, { "id": "US-019", "title": "Enable heartbeat by default in config", "description": "As system config, heartbeat should be enabled by default as it is a core feature.", "acceptanceCriteria": [ "DefaultConfig() Heartbeat.Enabled changed to true", "Can override via PICOCLAW_HEARTBEAT_ENABLED=false env var", "Config documentation updated showing default enabled", "Typecheck passes", "go test ./pkg/config -run TestDefaultConfig passes" ], "priority": 19, "passes": false, "notes": "" }, { "id": "US-020", "title": "Move heartbeat log to memory directory", "description": "As heartbeat service, logs should go to memory directory for LLM access and knowledge system integration.", "acceptanceCriteria": [ "Log path changed from workspace/heartbeat.log to workspace/memory/heartbeat.log", "Directory auto-created if missing", "Log format unchanged", "Typecheck passes", "go test ./pkg/heartbeat -run TestLogPath passes" ], "priority": 20, "passes": false, "notes": "" }, { "id": "US-021", "title": "Heartbeat calls ExecuteHeartbeatWithTools", "description": "As heartbeat service, I need to call the tool-supporting execution method.", "acceptanceCriteria": [ "executeHeartbeat calls handler.ExecuteHeartbeatWithTools(...)", "Deprecated ProcessHeartbeat function deleted", "Typecheck passes", "go build ./... succeeds" ], "priority": 21, "passes": false, "notes": "" } ] }