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

תיעוד 149

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

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

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

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

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

הערה: ב-TypeScript Agent SDK גרסה 0.3.233 ומעלה, או ב-Python Agent SDK גרסה 0.2.139 ומעלה, חלה המגבלה הבאה.

הכלים הבאים אינם זמינים ב-Opus 4.8, Sonnet 5, Fable 5, Mythos 5, או בגרסאות מאוחרות יותר של משפחות אלו, אלא אם תבחר להפעיל אותם במפורש:

  • TodoWrite
  • TaskCreate
  • TaskGet
  • TaskUpdate
  • TaskList

בדגמים אחרים, Claude Code מספק את כלי ה-Task כברירת מחדל ואת TodoWrite רק כאשר מגדירים CLAUDE_CODE_ENABLE_TASKS=0.

בדגמים המפורטים, אלא אם תבחר לצרף הפעלה, לא תראה בלוקים של 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. נוצרה (Created): Claude מוסיף את המשימה במצב pending כאשר הוא מזהה משימה
  2. הופעלה (Activated): Claude מגדיר את המשימה כמצב in_progress כאשר הוא מתחיל בעבודה
  3. הושלמה (Completed): Claude מסמן אותה כהושלמה כאשר המשימה מסתיימת בהצלחה
  4. הוסרה (Removed): Claude מוחק משימה שאינו זקוק לה עוד על ידי הגדרת status: "deleted" בקריאת TaskUpdate

#מתי Claude יוצר משימות

בהפעלה שכוללת את כלי מעקב המשימות, Claude יוצר משימות עבור רוב העבודות מרובות השלבים, כגון:

  • משימות מורכבות מרובות שלבים הדורשות שלוש פעולות נפרדות או יותר
  • רשימות משימות שסופקו על ידי המשתמש כאשר מוזכרים מספר פריטים
  • פעולות ארוכות יותר המפיקות תועלת ממעקב אחר התקדמות
  • בקשות מפורשות כאשר משתמשים מבקשים ארגון משימות

Claude עשוי לדלג על משימות עבור בקשות קצרות מאוד או בעלות שלב בודד.

#דוגמאות

לפני הרצת דוגמאות אלה, התקן את Claude Agent SDK על פי מדריך ההתחלה המהירה. כל דוגמה בדף זה חולקת את אותה הגדרת הרשאות ואותה התנהגות יציאה:

  • מצב הרשאות: בקשות הדוגמה מבקשות מ-Claude לבצע עבודה אמיתית בפרויקט, לכן כל דוגמה מגדירה permissionMode: "acceptEdits" (TypeScript) או permission_mode="acceptEdits" (Python) כדי לאשר אוטומטית את עריכות הקבצים שהעבודה מייצרת. ראה מצבי הרשאות לחלופות.
  • מגבלת תורות: כל דוגמה רצה עד שהסוכן מסיים ומניב את הודעת התוצאה הסופית שלו. אם הפעלה מגיעה למגבלת התורות שלה קודם לכן, להודעת תוצאה זו יש את תת הסוג error_max_turns. בדוק את subtype כדי לזהות סיום זה.
  • טיפול בשגיאות: דוגמאות אלה משתמשות בקריאות query() בודדות (single-shot). לאחר החזרת תוצאת error_max_turns, הפונקציה query() מעלה שגיאה הכוללת את Reached maximum number of turns. כל דוגמה עוטפת את הלולאה שלה בבלוק try כדי לצאת בצורה נקייה כאשר זה קורה. ראה טיפול בתוצאה עבור תת הסוגים של תוצאות.

הערה: הודעות מערכת המשימות, ובהן SDKTaskNotificationMessage (TypeScript) או TaskNotificationMessage (Python), מדווחות על משימות רקע כגון פקודות רקע וסוכני משנה. בזרם ההודעות, אתה רואה פעילות משימות כבלוקים של tool_use בהודעות ה-assistant.

#ניטור שינויים במשימות

הדוגמה הבאה מאזינה לזרם ה-assistant עבור בלוקים מסוג tool_use של TaskCreate ו-TaskUpdate, ומדפיסה שורת + עם הנושא של כל משימה חדשה ושורת עדכון עם מזהה המשימה והסטטוס החדש של כל שינוי סטטוס. השתמש במבנה זה כאשר אתה רוצה יומן של פעילות משימות במקום תצוגה מרונדרת. שורות ה-+ אינן כוללות את המזהים שהוקצו, לכן יומן זה אינו יכול לקשר עדכונים בחזרה לפעולות היצירה שלהם. כדי לשמור על קשר זה, שמור את המזהים כפי שעושה הדוגמה הצגת התקדמות בזמן אמת.

הקלט המוזרם של tool_use הוא המבנה הגולמי שהמודל פלט. Claude Code מתקן שמות מפתחות מסוימים שהיו קרובים אך לא מדויקים לפני הביצוע, כשהוא ממפה את id או task_id ל-taskId ואת active_form ל-activeForm, אך תיקון זה אינו משתקף בזרם. קרא את שדות הקלט של TaskUpdate בצורה מגננתית, כפי שעושות שתי הדוגמאות בדף זה, במקום להניח שהשם התקני תמיד קיים.

#TypeScript

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}`);
}

#Python

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())

#הצגת התקדמות בזמן אמת

הדוגמה הבאה מאזינה לזרם ה-assistant עבור בלוקים מסוג tool_use של TaskCreate ו-TaskUpdate, ושומרת מיפוי של משימות לפי מזהה משימה במחלקה TaskTracker, תוך רינדור מחדש של סיכום התקדמות בכל שינוי. הסיכום סופר משימות שהושלמו ומשימות שנמצאות בתהליך, ומציג את התווית activeForm של כל פריט פעיל במקום ה-subject שלו. השתמש במבנה זה כאשר היישום שלך מנהל תצוגת התקדמות במקום לתעד כל אירוע ביומן.

מזהה המשימה שהוקצה אינו מופיע בקלט של TaskCreate. Claude Code מעביר את הפלט המובנה של כל כלי בהודעת המשתמש שנושאת את בלוק ה-tool_result שלו, בתוך השדה tool_use_result. עבור TaskCreate, אובייקט זה מתועד ב-TypeScript בתור TaskCreateOutput תחת סוגי פלט של כלים, וב-Python השדה הוא מילון רגיל באותו מבנה. רכיב המעקב מתאים כל בלוק tool_result לקריאת ה-tool_use שלו לפי tool_use_id וקורא את task.id מתוך ה-tool_use_result של ההודעה המותאמת. Claude יכול לקרוא את הרשימה בחזרה באמצעות TaskList ואת הפרטים המלאים של משימה אחת באמצעות TaskGet.

#TypeScript

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");

#Python

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())

#תיעוד קשור