מדריך קלוד קוד בעברית

תיעוד 190

מעקב אחר משימות

מעקב אחר משימות (todos) בסשנים של Agent SDK, והצגת ההתקדמות של Claude באפליקציה שלך מתוך קריאות כלים מובנות

Claude Code מספק את כלי מעקב המשימות כברירת מחדל רק במודלים שמפורטים תחת זמינות מודלים. מודלים חדשים יותר עוקבים אחרי עבודה מרובת שלבים בלי רשימת משימות כתובה, ולכן בהם אין צורך בשום דבר מהעמוד הזה כדי ש-Claude יבצע משימות מרובות שלבים.

בסשן שיש בו את כלי מעקב המשימות, Claude שומר רשימת משימות כתובה, ומעדכן את הסטטוס של כל פריט תוך כדי העבודה. כל שינוי מופיע בזרם ההודעות כקריאת כלי מובנית. הפעילו את האפשרות בסשן רק כשהאפליקציה שלכם קוראת את קריאות הכלים האלה, בין אם כדי לתעד פעילות משימות ובין אם כדי להציג תצוגת התקדמות משלה.

#זמינות מודלים

[!NOTE]

הכלים הבאים זמינים כברירת מחדל רק ב-Claude Opus 4.7, Claude Sonnet 4.6, Claude Haiku 4.5, ובמודלים מוקדמים יותר. בכל מודל אחר, כולל מזהי מודלים ש-Claude Code לא מזהה, הם אינם זמינים אלא אם בוחרים להפעיל אותם:

  • TodoWrite
  • TaskCreate
  • TaskGet
  • TaskUpdate
  • TaskList

בכל מקום שבו הכלים זמינים, 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 מעביר כל משימה במחזור חיים צפוי:

  1. נוצרה: Claude מוסיף את המשימה כ-pending כשהוא מזהה משימה
  2. הופעלה: Claude מגדיר את המשימה ל-in_progress כשהוא מתחיל את העבודה
  3. הושלמה: Claude מסמן אותה כהושלמה כשהמשימה מסתיימת בהצלחה
  4. הוסרה: 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())

#תיעוד קשור