Current section
Files
Jump to
Current section
Files
lib/cmdc/tool/write_todos.ex
defmodule CMDC.Tool.WriteTodos do
@moduledoc """
更新 Agent 的 Todo 任务列表。
通过返回 `{:effect, {:update_todos, todos}}` 触发 Agent 上下文更新,
同时通过 `emit` 闭包广播 `{:todo_change, session_id, todos}` 事件。
## Todo 条目格式
每条 todo 为一个 map,包含:
- `"id"` — 唯一标识符
- `"content"` — 任务内容描述
- `"status"` — `"pending"` | `"in_progress"` | `"completed"` | `"cancelled"`
## 与 ToolRunner 的约定
返回 `{:effect, {:update_todos, todos}}`,ToolRunner 将其归一化为
`"[effect applied]"` 文本传给 LLM,状态更新由 Agent 读取。
实际上通过 `emit` 闭包将 `:todo_change` 事件写入 EventBus,
外部监听者(TUI、Facade API)可通过订阅 EventBus 获取最新 todos。
"""
@behaviour CMDC.Tool
@impl true
def name, do: "write_todos"
@impl true
def description do
"Update the task todo list. Call this when planning multi-step work " <>
"or tracking progress. Each item needs an id, content, and status " <>
"(pending/in_progress/completed/cancelled). " <>
"Only one item should be in_progress at a time."
end
@impl true
def meta(%{"todos" => todos}) when is_list(todos) do
total = length(todos)
done = Enum.count(todos, &(Map.get(&1, "status") == "completed"))
"WriteTodos #{done}/#{total} done"
end
def meta(_), do: "WriteTodos"
@impl true
def parameters do
%{
"type" => "object",
"properties" => %{
"todos" => %{
"type" => "array",
"description" => "Complete list of todos (replaces current list).",
"items" => %{
"type" => "object",
"properties" => %{
"id" => %{
"type" => "string",
"description" => "Unique identifier for the todo item."
},
"content" => %{
"type" => "string",
"description" => "Description of the task."
},
"status" => %{
"type" => "string",
"enum" => ["pending", "in_progress", "completed", "cancelled"],
"description" => "Current status of the todo item."
}
},
"required" => ["id", "content", "status"]
}
}
},
"required" => ["todos"]
}
end
@impl true
def execute(%{"todos" => todos}, context) when is_list(todos) do
session_id = context.session_id
emit = Map.get(context, :emit)
if is_function(emit, 1) do
emit.({:todo_change, session_id, todos})
else
CMDC.EventBus.broadcast(session_id, {:todo_change, session_id, todos})
end
summary = build_summary(todos)
{:ok, summary}
end
def execute(_args, _ctx), do: {:error, "Missing required parameter: todos"}
# ==========================================================================
# 私有辅助
# ==========================================================================
defp build_summary(todos) do
total = length(todos)
by_status = Enum.group_by(todos, &Map.get(&1, "status", "pending"))
pending = length(Map.get(by_status, "pending", []))
in_progress = length(Map.get(by_status, "in_progress", []))
completed = length(Map.get(by_status, "completed", []))
cancelled = length(Map.get(by_status, "cancelled", []))
"""
Todo list updated (#{total} items):
- pending: #{pending}
- in_progress: #{in_progress}
- completed: #{completed}
- cancelled: #{cancelled}\
"""
end
end