@sema-agent/core
Version:
Stateless, task-oriented AI agent core
111 lines (110 loc) • 7.49 kB
JavaScript
import { Type } from "typebox";
import { errorResult } from "../core/tools.js";
import { toolFace } from "../core/tool-catalog-entries.js";
export function createTodoWriteTool() {
const lists = new Map();
return {
name: "TodoWrite",
...toolFace("todo-write"),
description: "Create and update a task list for the current session. The list is rendered to the user as your working plan.\n" +
"\n" +
'- Each todo has `content`, `status` ("pending" | "in_progress" | "completed"), and `activeForm` (present-tense label shown while in progress).\n' +
"- Send the full list each call; it replaces the previous one.\n" +
"- Keep one item `in_progress` at a time and mark it `completed` when done.",
descriptionClassic: "Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.\n" +
"It also helps the user understand the progress of the task and overall progress of their requests.\n" +
"\n" +
"## When to Use This Tool\n" +
"Use this tool proactively in these scenarios:\n" +
"\n" +
"1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions\n" +
"2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations\n" +
"3. User explicitly requests todo list - When the user directly asks you to use the todo list\n" +
"4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)\n" +
"5. After receiving new instructions - Immediately capture user requirements as todos\n" +
"6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time\n" +
"7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation\n" +
"\n" +
"## When NOT to Use This Tool\n" +
"\n" +
"Skip using this tool when:\n" +
"1. There is only a single, straightforward task\n" +
"2. The task is trivial and tracking it provides no organizational benefit\n" +
"3. The task can be completed in less than 3 trivial steps\n" +
"4. The task is purely conversational or informational\n" +
"\n" +
"NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.\n" +
"\n" +
"## Task States and Management\n" +
"\n" +
"1. **Task States**: Use these states to track progress:\n" +
" - pending: Task not yet started\n" +
" - in_progress: Currently working on (limit to ONE task at a time)\n" +
" - completed: Task finished successfully\n" +
"\n" +
" **IMPORTANT**: Task descriptions must have two forms:\n" +
' - content: The imperative form describing what needs to be done (e.g., "Run tests", "Build the project")\n' +
' - activeForm: The present continuous form shown during execution (e.g., "Running tests", "Building the project")\n' +
"\n" +
"2. **Task Management**:\n" +
" - Update task status in real-time as you work\n" +
" - Mark tasks complete IMMEDIATELY after finishing (don't batch completions)\n" +
" - Exactly ONE task must be in_progress at any time (not less, not more)\n" +
" - Complete current tasks before starting new ones\n" +
" - Remove tasks that are no longer relevant from the list entirely\n" +
"\n" +
"3. **Task Completion Requirements**:\n" +
" - ONLY mark a task as completed when you have FULLY accomplished it\n" +
" - If you encounter errors, blockers, or cannot finish, keep the task as in_progress\n" +
" - When blocked, create a new task describing what needs to be resolved\n" +
" - Never mark a task as completed if:\n" +
" - Tests are failing\n" +
" - Implementation is partial\n" +
" - You encountered unresolved errors\n" +
" - You couldn't find necessary files or dependencies\n" +
"\n" +
"4. **Task Breakdown**:\n" +
" - Create specific, actionable items\n" +
" - Break complex tasks into smaller, manageable steps\n" +
" - Use clear, descriptive task names\n" +
" - Always provide both forms:\n" +
' - content: "Fix authentication bug"\n' +
' - activeForm: "Fixing authentication bug"\n' +
"\n" +
"When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.\n" +
"\n" +
"Pass the COMPLETE todo list each call — it replaces the prior list.",
parameters: Type.Object({
todos: Type.Array(Type.Object({
content: Type.String({ description: "The task, in imperative form (e.g. \"Run the tests\")." }),
status: Type.Union([Type.Literal("pending"), Type.Literal("in_progress"), Type.Literal("completed")]),
activeForm: Type.String({ minLength: 1, description: "Present-continuous form shown while in progress (e.g. \"Running the tests\")." }),
})),
}),
execute: (args, ctx) => {
const key = ctx?.sessionId ?? "";
const shape = (t) => ({ content: t.content, status: t.status, activeForm: t.activeForm });
const oldTodos = (lists.get(key) ?? []).map(shape);
const written = args.todos;
for (let i = 0; i < written.length; i++) {
const activeForm = written[i].activeForm;
if (typeof activeForm !== "string" || activeForm.length === 0) {
const what = activeForm === undefined ? "is missing" : "has an empty or non-string";
return errorResult(`Error (TodoWrite): todo ${i + 1} of ${written.length} ${what} \`activeForm\` — every todo needs the ` +
`present-continuous label shown while it runs (e.g. content "Run the tests" → activeForm "Running the ` +
`tests"), alongside its imperative \`content\`. Nothing was stored; resend the COMPLETE list with the ` +
`label filled in on every item.`);
}
}
const allDone = written.every((t) => t.status === "completed");
lists.set(key, allDone ? [] : written);
const newTodos = written.map(shape);
const open = written.filter((t) => t.status !== "completed").length;
return {
content: `Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. ` +
`Please proceed with the current tasks if applicable (${written.length} total, ${open} open)`,
details: { type: "todo", oldTodos, newTodos },
};
},
};
}