diff --git a/.ralph/prd.json b/.ralph/prd.json index 3d3460c..e76862e 100644 --- a/.ralph/prd.json +++ b/.ralph/prd.json @@ -91,7 +91,7 @@ "Typecheck passes" ], "priority": 6, - "passes": false, + "passes": true, "notes": "" }, { diff --git a/.ralph/progress.txt b/.ralph/progress.txt index 0c16929..132f32f 100644 --- a/.ralph/progress.txt +++ b/.ralph/progress.txt @@ -6,12 +6,13 @@ Tool 返回值结构化重构 - 将 Tool 接口返回值从 (string, error) 改 ## Progress -### Completed (4/21) +### Completed (5/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 processing logic +- US-006: Add AsyncCallback type and AsyncTool interface ### In Progress @@ -24,7 +25,7 @@ Tool 返回值结构化重构 - 将 Tool 接口返回值从 (string, error) 改 | 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 | Pending | | +| US-006 | Add AsyncCallback type and AsyncTool interface | Completed | | | US-007 | Heartbeat async task execution support | Pending | | | US-008 | Inject callback into async tools in AgentLoop | Pending | | | US-009 | State save atomicity - SetLastChannel | Pending | | @@ -84,4 +85,20 @@ Tool 返回值结构化重构 - 将 Tool 接口返回值从 (string, error) 改 - **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`,让子代理完成时能够通知主循环。 + --- \ No newline at end of file diff --git a/pkg/tools/base.go b/pkg/tools/base.go index 5f87a54..b131746 100644 --- a/pkg/tools/base.go +++ b/pkg/tools/base.go @@ -2,6 +2,7 @@ package tools import "context" +// Tool is the interface that all tools must implement. type Tool interface { Name() string Description() string @@ -16,6 +17,58 @@ type ContextualTool interface { SetContext(channel, chatID string) } +// AsyncCallback is a function type that async tools use to notify completion. +// When an async tool finishes its work, it calls this callback with the result. +// +// The ctx parameter allows the callback to be canceled if the agent is shutting down. +// The result parameter contains the tool's execution result. +// +// Example usage in an async tool: +// +// func (t *MyAsyncTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +// // Start async work in background +// go func() { +// result := doAsyncWork() +// if t.callback != nil { +// t.callback(ctx, result) +// } +// }() +// return AsyncResult("Async task started") +// } +type AsyncCallback func(ctx context.Context, result *ToolResult) + +// AsyncTool is an optional interface that tools can implement to support +// asynchronous execution with completion callbacks. +// +// Async tools return immediately with an AsyncResult, then notify completion +// via the callback set by SetCallback. +// +// This is useful for: +// - Long-running operations that shouldn't block the agent loop +// - Subagent spawns that complete independently +// - Background tasks that need to report results later +// +// Example: +// +// type SpawnTool struct { +// callback AsyncCallback +// } +// +// func (t *SpawnTool) SetCallback(cb AsyncCallback) { +// t.callback = cb +// } +// +// func (t *SpawnTool) Execute(ctx context.Context, args map[string]interface{}) *ToolResult { +// go t.runSubagent(ctx, args) +// return AsyncResult("Subagent spawned, will report back") +// } +type AsyncTool interface { + Tool + // SetCallback registers a callback function to be invoked when the async operation completes. + // The callback will be called from a goroutine and should handle thread-safety if needed. + SetCallback(cb AsyncCallback) +} + func ToolToSchema(tool Tool) map[string]interface{} { return map[string]interface{}{ "type": "function",