תיעוד 190
מעקב אחר משימות
מעקב אחר משימות (todos) בסשנים של Agent SDK, והצגת ההתקדמות של Claude באפליקציה שלך מתוך קריאות כלים מובנות
Claude Code מספק את כלי מעקב המשימות כברירת מחדל רק במודלים שמפורטים תחת זמינות מודלים. מודלים חדשים יותר עוקבים אחרי עבודה מרובת שלבים בלי רשימת משימות כתובה, ולכן בהם אין צורך בשום דבר מהעמוד הזה כדי ש-Claude יבצע משימות מרובות שלבים.
בסשן שיש בו את כלי מעקב המשימות, Claude שומר רשימת משימות כתובה, ומעדכן את הסטטוס של כל פריט תוך כדי העבודה. כל שינוי מופיע בזרם ההודעות כקריאת כלי מובנית. הפעילו את האפשרות בסשן רק כשהאפליקציה שלכם קוראת את קריאות הכלים האלה, בין אם כדי לתעד פעילות משימות ובין אם כדי להציג תצוגת התקדמות משלה.
#זמינות מודלים
[!NOTE]
הכלים הבאים זמינים כברירת מחדל רק ב-Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5, ובמודלים מוקדמים יותר. בכל מודל אחר, כולל מזהי מודלים ש-Claude Code לא מזהה, הם אינם זמינים אלא אם בוחרים להפעיל אותם:
TodoWriteTaskCreateTaskGetTaskUpdateTaskListבכל מקום שבו הכלים זמינים, Claude Code מספק את ארבעת כלי ה-Task, או את
TodoWriteבמקום זאת כשמגדיריםCLAUDE_CODE_ENABLE_TASKS=0.ערכת ברירת המחדל הזו חלה ב-Claude Code v2.1.268 ואילך, שה-TypeScript Agent SDK אורז החל מ-v0.3.268.
במודל שאין בו את הכלים כברירת מחדל, אלא אם מפעילים את האפשרות בסשן, לא רואים בלוקי tool_use עבורם בזרם ההודעות. ה-Agent SDK מחיל את ברירות המחדל האלה דרך קובץ ההרצה של Claude Code שהוא אורז. אם מפנים את pathToClaudeCodeExecutable (TypeScript) או את cli_path (Python) להתקנת Claude Code משלכם, מקבלים את הכלים שההתקנה הזו מספקת, לפי ברירות המחדל שלה. כדי לראות את הסט המדויק בסשן פעיל, בדקו אילו כלים זמינים. כדי להפעיל את האפשרות בסשן, עשו אחד מהדברים הבאים:
- ציינו אחד מהכלים באפשרות
allowedTools(TypeScript) אוallowed_tools(Python) - פרטו את הכלים באפשרות
tools, שמגבילה את הכלים המובנים של הסשן לאלה שהיא מציינת. כללו את הכלים שאתם רוצים יחד עם שאר הכלים המובנים שבהם אתם משתמשים - הגדירו
CLAUDE_CODE_ENABLE_TODO_TOOLS=1באפשרותenv, כמו בדוגמאות בעמוד הזה. ב-TypeScript,envמחליף את סביבת תהליך המשנה, ולכן פרסו את...process.envכדי לשמור משתנים שעוברים בירושה. ב-Python,envממוזג מעל הסביבה שעברה בירושה
#מחזור החיים של משימה
Claude מעביר כל משימה במחזור חיים צפוי:
- נוצרה: Claude מוסיף את המשימה כ-
pendingכשהוא מזהה משימה - הופעלה: Claude מגדיר את המשימה ל-
in_progressכשהוא מתחיל את העבודה - הושלמה: Claude מסמן אותה כהושלמה כשהמשימה מסתיימת בהצלחה
- הוסרה: Claude מוחק משימה שהוא כבר לא צריך על ידי הגדרת
status: "deleted"בקריאתTaskUpdate
#מתי Claude יוצר משימות
בסשן שיש בו את כלי מעקב המשימות, Claude יוצר משימות עבור רוב העבודה מרובת השלבים, למשל:
- משימות מורכבות מרובות שלבים שדורשות שלוש פעולות נפרדות או יותר
- רשימות משימות שהמשתמש סיפק כשמוזכרים כמה פריטים
- פעולות ארוכות יותר שמרוויחות ממעקב התקדמות
- בקשות מפורשות כשמשתמשים מבקשים ארגון של משימות
Claude עשוי לדלג על משימות עבור בקשות קצרות מאוד או בקשות של שלב אחד.
#דוגמאות
לפני הרצת הדוגמאות האלה, התקינו את Claude Agent SDK לפי ההתחלה המהירה. כל דוגמה בדף הזה חולקת את אותה הגדרת הרשאות ואותה התנהגות יציאה:
- מצב הרשאות: ההנחיות בדוגמה מבקשות מ-Claude לבצע עבודה אמיתית על פרויקט, ולכן כל דוגמה מגדירה
permissionMode: "acceptEdits"(TypeScript) אוpermission_mode="acceptEdits"(Python) כדי לאשר אוטומטית את עריכות הקבצים שהעבודה מייצרת. ראו מצבי הרשאות לחלופות. - מגבלת תורות: כל דוגמה רצה עד שהסוכן מסיים ומחזיר את הודעת התוצאה הסופית שלו. אם סשן מגיע קודם למגבלת התורות שלו, להודעת התוצאה הזו יש את תת הסוג
error_max_turns. בדקו אתsubtypeכדי לזהות את הסיום הזה. - טיפול בשגיאות: הדוגמאות האלה משתמשות בקריאות
query()חד פעמיות. אחרי החזרת תוצאתerror_max_turns,query()מעלה שגיאה שכוללת אתReached maximum number of turns. כל דוגמה עוטפת את הלולאה שלה בבלוק try כדי לצאת בצורה נקייה כשזה קורה. ראו טיפול בתוצאה לתת סוגי התוצאה.
[!NOTE]
הודעות מערכת המשימות, ובהן
SDKTaskNotificationMessage(TypeScript) אוTaskNotificationMessage(Python), מדווחות על משימות רקע כגון פקודות שהועברו לרקע וסוכני משנה. בזרם ההודעות, פעילות המשימות מופיעה כבלוקיtool_useבהודעות העוזר.
#מעקב אחרי שינויים במשימות
הדוגמה הבאה עוקבת אחרי זרם העוזר אחר בלוקי tool_use של TaskCreate ושל TaskUpdate ומדפיסה שורת + עם הנושא של כל משימה חדשה, ושורת עדכון עם מזהה המשימה של כל שינוי סטטוס והסטטוס החדש. השתמשו בצורה הזו כשאתם רוצים יומן של פעילות משימות ולא תצוגה מעובדת. שורות ה-+ לא כוללות את המזהים שהוקצו, ולכן היומן הזה לא יכול להתאים עדכונים חזרה ליצירות שלהם. כדי לשמור על ההתאמה הזו, תפסו את המזהים כמו שהצגת התקדמות בזמן אמת עושה.
הקלט של tool_use בזרם הוא הצורה הגולמית שהמודל פלט. Claude Code מתקן כמה שמות מפתח קרובים אך שגויים לפני הביצוע, וממפה את id או task_id אל taskId ואת active_form אל activeForm, אבל התיקון הזה לא משתקף בזרם. קראו את שדות הקלט של TaskUpdate באופן הגנתי, כמו ששתי הדוגמאות בדף הזה עושות, במקום להניח שהשם הקנוני תמיד נוכח.
import { query } from "@anthropic-ai/claude-agent-sdk";
try {
for await (const message of query({
prompt: "Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
// Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
options: { maxTurns: 15, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
})) {
if (message.type !== "assistant") continue;
for (const block of message.message.content) {
if (block.type !== "tool_use") continue;
if (block.name === "TaskCreate") {
const input = block.input as { subject: string };
console.log(`+ ${input.subject}`);
} else if (block.name === "TaskUpdate") {
const input = block.input as {
taskId?: string;
id?: string;
task_id?: string;
status?: string;
};
const taskId = input.taskId ?? input.id ?? input.task_id;
if (taskId && input.status) console.log(` ${taskId} -> ${input.status}`);
}
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result.
console.log(`Session ended with an error: ${error}`);
}import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, AssistantMessage, ToolUseBlock
async def main():
try:
async for message in query(
prompt="Create a static website with a home page, an about page, and a shared stylesheet, and track progress with todos",
# Keeps the Task tools on models where Claude Code otherwise doesn't provide them.
options=ClaudeAgentOptions(max_turns=15, permission_mode="acceptEdits", env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"}),
):
if not isinstance(message, AssistantMessage):
continue
for block in message.content:
if not isinstance(block, ToolUseBlock):
continue
if block.name == "TaskCreate":
print(f"+ {block.input.get('subject', '')}")
elif block.name == "TaskUpdate" and block.input.get("status"):
task_id = (
block.input.get("taskId")
or block.input.get("id")
or block.input.get("task_id")
)
if task_id:
print(f" {task_id} -> {block.input['status']}")
except Exception as error:
# A single-shot query() raises after yielding an error result.
print(f"Session ended with an error: {error}")
asyncio.run(main())#הצגת התקדמות בזמן אמת
הדוגמה הבאה עוקבת אחרי זרם העוזר אחר בלוקי tool_use של TaskCreate ושל TaskUpdate ושומרת מפה של משימות לפי מזהה משימה במחלקה TaskTracker, ומציגה מחדש סיכום התקדמות בכל שינוי. הסיכום סופר משימות שהושלמו ומשימות שבביצוע ומציג את תווית activeForm של כל פריט פעיל במקום ה-subject שלו. השתמשו בצורה הזו כשהיישום שלכם מתחזק תצוגת התקדמות במקום לרשום כל אירוע ביומן.
מזהה המשימה שהוקצה לא נמצא בקלט של TaskCreate. Claude Code מוסר את הפלט המובנה של כל כלי בהודעת המשתמש שנושאת את בלוק tool_result שלו, בשדה tool_use_result. עבור TaskCreate, האובייקט הזה מתועד ב-TypeScript בתור TaskCreateOutput תחת סוגי פלט של כלים, וב-Python השדה הוא dict רגיל באותה צורה. המעקב מזווג כל בלוק tool_result עם קריאת tool_use שלו לפי tool_use_id וקורא את task.id מ-tool_use_result של ההודעה המזווגת. Claude יכול לקרוא את הרשימה בחזרה עם TaskList ואת הפרטים המלאים של משימה אחת עם TaskGet.
import { query } from "@anthropic-ai/claude-agent-sdk";
type Task = { subject: string; activeForm?: string; status: string };
class TaskTracker {
private tasks = new Map<string, Task>();
private pendingCreates = new Map<string, { subject: string; activeForm?: string }>();
displayProgress() {
if (this.tasks.size === 0) {
console.log("\nProgress: no open tasks\n");
return;
}
const items = [...this.tasks.values()];
const completed = items.filter((t) => t.status === "completed").length;
const inProgress = items.filter((t) => t.status === "in_progress").length;
console.log(`\nProgress: ${completed}/${this.tasks.size} completed`);
console.log(`Currently working on: ${inProgress} task(s)\n`);
for (const [id, task] of this.tasks) {
const icon =
task.status === "completed" ? "✅" : task.status === "in_progress" ? "🔧" : "❌";
const text = task.status === "in_progress" && task.activeForm ? task.activeForm : task.subject;
console.log(`${id}. ${icon} ${text}`);
}
}
handleToolUse(block: { id: string; name: string; input: unknown }) {
if (block.name === "TaskCreate") {
const input = block.input as { subject: string; activeForm?: string; active_form?: string };
this.pendingCreates.set(block.id, {
subject: input.subject,
activeForm: input.activeForm ?? input.active_form,
});
} else if (block.name === "TaskUpdate") {
const input = block.input as {
taskId?: string;
id?: string;
task_id?: string;
status?: string;
activeForm?: string;
active_form?: string;
};
const taskId = input.taskId ?? input.id ?? input.task_id;
if (!taskId) return;
if (input.status === "deleted") {
this.tasks.delete(taskId);
this.displayProgress();
return;
}
const task = this.tasks.get(taskId);
if (!task) return;
if (input.status) task.status = input.status;
const active = input.activeForm ?? input.active_form;
if (active) task.activeForm = active;
this.displayProgress();
}
}
handleToolResult(block: { tool_use_id: string; is_error?: boolean }, result: unknown) {
const create = this.pendingCreates.get(block.tool_use_id);
if (!create) return;
this.pendingCreates.delete(block.tool_use_id);
if (block.is_error) return;
// The result's user message carries the tool's structured output as
// tool_use_result; for TaskCreate that's TaskCreateOutput,
// { task: { id, subject } }.
const out = result as { task?: { id: string } };
if (!out?.task?.id) return;
this.tasks.set(out.task.id, { ...create, status: "pending" });
this.displayProgress();
}
async trackQuery(prompt: string) {
try {
for await (const message of query({
prompt,
options: { maxTurns: 20, permissionMode: "acceptEdits", env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } },
})) {
if (message.type === "assistant") {
for (const block of message.message.content) {
if (block.type === "tool_use") this.handleToolUse(block);
}
}
if (message.type === "user" && Array.isArray(message.message.content)) {
for (const block of message.message.content) {
if (block.type === "tool_result") this.handleToolResult(block, message.tool_use_result);
}
}
}
} catch (error) {
// A single-shot query() throws after yielding an error result,
// such as when the maxTurns limit is hit.
console.log(`Session ended with an error: ${error}`);
}
}
}
// Usage
const tracker = new TaskTracker();
await tracker.trackQuery("Build a complete authentication system with todos");import asyncio
from claude_agent_sdk import (
query,
ClaudeAgentOptions,
AssistantMessage,
UserMessage,
ToolUseBlock,
ToolResultBlock,
)
class TaskTracker:
def __init__(self):
self.tasks: dict[str, dict] = {}
self.pending_creates: dict[str, dict] = {}
def display_progress(self):
if not self.tasks:
print("\nProgress: no open tasks\n")
return
completed = len([t for t in self.tasks.values() if t["status"] == "completed"])
in_progress = len([t for t in self.tasks.values() if t["status"] == "in_progress"])
print(f"\nProgress: {completed}/{len(self.tasks)} completed")
print(f"Currently working on: {in_progress} task(s)\n")
for task_id, task in self.tasks.items():
icon = (
"✅"
if task["status"] == "completed"
else "🔧"
if task["status"] == "in_progress"
else "❌"
)
text = (
task["activeForm"]
if task["status"] == "in_progress" and task.get("activeForm")
else task["subject"]
)
print(f"{task_id}. {icon} {text}")
def handle_tool_use(self, block: ToolUseBlock):
if block.name == "TaskCreate":
self.pending_creates[block.id] = {
"subject": block.input.get("subject", ""),
"activeForm": block.input.get("activeForm") or block.input.get("active_form"),
}
elif block.name == "TaskUpdate":
task_id = (
block.input.get("taskId")
or block.input.get("id")
or block.input.get("task_id")
)
if not task_id:
return
if block.input.get("status") == "deleted":
self.tasks.pop(task_id, None)
self.display_progress()
return
task = self.tasks.get(task_id)
if not task:
return
if block.input.get("status"):
task["status"] = block.input["status"]
active = block.input.get("activeForm") or block.input.get("active_form")
if active:
task["activeForm"] = active
self.display_progress()
def handle_tool_result(self, block: ToolResultBlock, tool_use_result):
create = self.pending_creates.pop(block.tool_use_id, None)
if create is None or block.is_error:
return
# The result's user message carries the tool's structured output as
# tool_use_result; for TaskCreate that's {"task": {"id": ..., "subject": ...}}.
task = (tool_use_result or {}).get("task") or {}
if not task.get("id"):
return
self.tasks[task["id"]] = {**create, "status": "pending"}
self.display_progress()
async def track_query(self, prompt: str):
try:
async for message in query(
prompt=prompt,
options=ClaudeAgentOptions(
max_turns=20,
permission_mode="acceptEdits",
env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"},
),
):
if isinstance(message, AssistantMessage):
for block in message.content:
if isinstance(block, ToolUseBlock):
self.handle_tool_use(block)
if isinstance(message, UserMessage) and isinstance(message.content, list):
for block in message.content:
if isinstance(block, ToolResultBlock):
self.handle_tool_result(block, message.tool_use_result)
except Exception as error:
# A single-shot query() raises after yielding an error result,
# such as when the max_turns limit is hit.
print(f"Session ended with an error: {error}")
# Usage
async def main():
tracker = TaskTracker()
await tracker.track_query("Build a complete authentication system with todos")
asyncio.run(main())#תיעוד קשור
- מדריך Agent SDK - TypeScript: האפשרויות, הטיפוסים וסכמות הכלים של SDK ב-TypeScript, כולל טיפוסי הקלט והפלט של כלי Task
- מדריך Agent SDK - Python: האפשרויות, הטיפוסים ותיעוד הכלים של SDK ב-Python
- קלט בזרימה: שני מצבי הקלט, ומתי להשתמש בקלט בזרימה במקום בקריאות החד פעמיות שהדוגמאות האלה משתמשות בהן
- לתת ל-Claude כלים מותאמים: הגדירו כלים משלכם עם שרת MCP בתוך התהליך של ה-SDK