【Claude Code 連載 第102回】Todo を追跡する(agent-sdk/todo-tracking)

スポンサーリンク
【Claude Code 連載 第102回】Todo を追跡する(agent-sdk/todo-tracking) 用語解説
【Claude Code 連載 第102回】Todo を追跡する(agent-sdk/todo-tracking)
この記事は約31分で読めます。
よっしー
よっしー

こんにちは。よっしーです(^^)

スポンサーリンク

背景

この連載では、Claude Codeの公式ドキュメントを1ページずつ読み解いていきます。公式ドキュメントは情報が網羅されている分、「結局どの機能を、どんな場面で使えばいいのか」は自分で考える必要があり、読むのに意外と時間がかかります。そこで、私が実務で使うために読み込んだ内容を「使う場面→実例コード」の順に整理して残していくことにしました。専門家の解説というより、一次情報を読んだ記録です。推測や動作を確認していない部分には、その都度そう書きます。

公式ドキュメント:https://code.claude.com/docs/ja/agent-sdk/todo-tracking

1. 一言でいうと

一部のモデルの Claude は、複数の手順がある作業をするとき、自分で todo リスト(タスクの一覧)を作り、作業を進めるたびに各項目の状態を「未着手→作業中→完了」と更新します。その作成や更新は、メッセージの流れの中に TaskCreate・TaskUpdate などのツール呼び出しとして現れるので、アプリはそれを読んで進捗をログに残したり、画面に進捗表示を出したりできます。ただし新しいモデルでは、このツールは既定では提供されません。アプリがこれを読む場合にだけ、明示的に有効にします。

第101回で、ツールの呼び出しを追って「いま何をしているか」を見せる方法を見ました。今回は、エージェントが自分で持っている作業の一覧を、そのまま進捗表示に使う方法です。


2. どういう場面で役立つか

シーン1:長い作業の進み具合を「何件中何件完了」で見せる

「静的サイトを作る」「認証の仕組みを一式作る」のような、いくつもの手順に分かれる作業では、利用者は「あとどれくらいか」を知りたくなります。Claude が作った todo リストを読めば、「5件中3件完了、いまは○○を作成中」という表示を作れます。原文の2つ目の例がこれです。

シーン2:エージェントが何をどの順でやったかを記録する

作業の後で、どのタスクがいつ作られ、いつ完了したかをログに残しておけば、エージェントの作業の流れを振り返れます。原文の1つ目の例が、このログ用の形です。

シーン3:ユーザーが渡したタスクリストの消化状況を追う

ユーザーが「A と B と C をやって」と複数の項目を渡すと、Claude はそれを todo にすることが多い、と原文は書いています。渡した項目がどこまで終わったかを、アプリ側で追えます。

不要・向かないケース

  • 新しいモデルで、進捗表示が要らない:原文の冒頭のとおり、新しいモデルは書き出した todo リストなしで複数手順の作業を追跡できるので、Claude に作業させるためにこのページの内容は必要ありません。
  • アプリが todo のツール呼び出しを読まない:原文は「アプリがこれらのツール呼び出しを読み取る場合にのみオプトインする」よう書いています。読まないのに有効にしても、意味がありません。
  • バックグラウンドのタスクの状態を知りたい:SDKTaskNotificationMessage(TypeScript)/TaskNotificationMessage(Python)は、バックグラウンドのコマンドやサブエージェントなどのバックグラウンドのタスクを報告するもので、todo とは別物です(3-3)。

3. コードと仕組みの解説

原文の fenced コードブロックは4本で、TypeScript と Python の対訳2組です(todo の変化を監視するログの例と、進捗をリアルタイムで表示するトラッカーの例)。どちらも内容が違うので全数を引用します。コードは原文から機械的に抜き出したもので、入れ子に由来する先頭インデントを外した以外は変えていません。このページでは原文が TypeScript を先に並べているので、その順で載せます。

3-0. バージョン要件と前提

項目内容
下の「モデルごとの既定」が適用される版Claude Code v2.1.268 以降(TypeScript Agent SDK は v0.3.268 から同梱)
SDK のインストール第78回のクイックスタートのとおり

このページの大前提は、todo のツールが既定で使えるのは一部のモデルだけ、ということです。

3-1. どのモデルで使えるか

次の5つのツールは、既定では Claude Opus 4.7、Claude Sonnet 4.6、Claude Haiku 4.5 と、それより前のモデルでだけ使えます。それ以外のすべてのモデル(Claude Code が知らないモデル ID を含む)では、有効にしない限り使えません。

  • TodoWrite
  • TaskCreate
  • TaskGet
  • TaskUpdate
  • TaskList

使える場合、Claude Code が提供するのは4つの Task ツール(TaskCreate・TaskGet・TaskUpdate・TaskList)です。CLAUDE_CODE_ENABLE_TASKS=0 を設定すると、代わりに TodoWrite を提供します。

既定でツールを持たないモデルでは、有効にしない限り、これらのツールの tool_use ブロックはメッセージの流れに出てきません。SDK はこの既定を同梱の Claude Code バイナリを通じて適用しています。pathToClaudeCodeExecutable(TypeScript)や cli_path(Python)で別にインストールした Claude Code を指すと、そのインストールの既定でツールが決まります。実際のセッションで何が使えるかは、ツールリファレンスの「利用可能なツールを確認する」方法で調べます(第62回)。

有効にする3つの方法

方法注意
allowedTools(Python は allowed_tools)でツールの1つを名指しする—
tools オプションにツールを並べるtools はセッションの組み込みツールを名指ししたものだけに制限するので、使う他の組み込みツールも一緒に入れる
env で CLAUDE_CODE_ENABLE_TODO_TOOLS=1 を設定する(このページの例の方法)TypeScript の env はサブプロセスの環境を置き換えるので ...process.env を展開する。Python の env は引き継いだ環境に重ねられる

1行目は少し意外な挙動です。第79回・第80回で見たとおり、allowedTools は本来「許可するツールの一覧」ではなく「確認なしで通す(事前承認する)ツールの一覧」でした。ここでは、それに todo のツールを書くとツールそのものが提供されるようになる、という働きもあることになります(筆者の指摘)。逆に言えば、新しいモデルで allowedTools に TaskCreate などを並べると、意図せず todo のツールが有効になります。

2行目の tools の注意は、第83回で見た「tools は組み込みツールを絞る」と同じ仕組みです。たとえば tools に todo のツールだけを書くと(筆者の例)、Read や Edit などが使えなくなります。

3-2. todo の一生と、Claude が todo を作るとき

Claude は各 todo を決まった順に進めます。

段階Claude がすること状態
作成タスクを見つけたら todo を足すpending
着手作業を始めたら更新するin_progress
完了作業が正常に終わったら完了にする完了
削除不要になった todo を TaskUpdate で消すstatus: "deleted"

表は原文の4段階をまとめたものです。完了の状態の値は、この節では文字で書かれていませんが、後の例のコードは "completed" で判定しています。

タスク追跡のツールを持つセッションでは、Claude は複数手順の作業の多くで todo を作ります。原文が挙げる場面は次の4つです。

  • 複雑な複数手順のタスク:3つ以上の別々の作業が要るとき
  • ユーザーが渡したタスクリスト:複数の項目が挙げられたとき
  • 長い操作:進捗を追うと役立つとき
  • 明示的な依頼:ユーザーが todo での整理を頼んだとき

逆に、ごく短い依頼や1手順で済む依頼では、todo を作らないことがあります。アプリ側で「todo が0件」を正常な状態として扱う必要がある、ということです(筆者の指摘)。後の例の no open tasks の表示がそれに当たります。

3-3. 例に共通する設定

例を動かす前に、第78回のクイックスタートどおり SDK を入れておきます。すべての例は、同じ権限の設定と終わり方を共有しています。

共通点内容
権限モード例のプロンプトは Claude にプロジェクトで実際の作業をさせるので、permissionMode: "acceptEdits"(Python は permission_mode="acceptEdits")で、作業で生じるファイルの編集を自動で承認する(他の選択肢は第80回)
ターン数の上限エージェントが終わって最終結果のメッセージを出すまで動く。先に上限に達すると、結果メッセージの subtype は error_max_turns
エラー処理1回きりの query() を使うので、error_max_turns の結果を出した後に Reached maximum number of turns を含むエラーが出る。きれいに終わるよう、ループを try で包む

3行目は第94回・第97回・第98回でも見た動きです。

筆者の指摘:例のプロンプトは「静的サイトを作る」「認証の仕組みを一式作る」で、acceptEdits によってファイルの作成や編集が確認なしで通ります。試すときは、空のディレクトリを作業ディレクトリにして動かすのが安全です。

原文の注記として、タスクのシステムメッセージ(TypeScript の SDKTaskNotificationMessage、Python の TaskNotificationMessage)は、バックグラウンドのコマンドやサブエージェントなどのバックグラウンドのタスクを報告するものです。todo の動きは、アシスタントのメッセージの中の tool_use ブロックとして現れます。名前に「Task」が付いていても、todo の追跡には使いません。

3-4. todo の変化を監視する(ログの形)

次の例は、アシスタントのメッセージの流れから TaskCreate と TaskUpdate の tool_use ブロックを拾い、新しいタスクごとに件名(subject)を付けた + の行を、状態が変わるごとにタスク ID と新しい状態を示す行を出します。表示を組み立てるのではなく、タスクの動きのログが欲しいときの形です。

流れてくる入力は、モデルが出したそのままの形です。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())

ID の読み方は、TypeScript が input.taskId ?? input.id ?? input.task_id、Python が block.input.get で taskId・id・task_id を順に試して or でつなぐ形で、どちらも3つの名前を順に試しています。これが「防御的に読む」の具体的な形です。

この例には限界があります。原文のとおり、+ の行には割り当てられた ID が入っていないので、このログでは後の更新の行と作成の行を結びつけられません。TaskCreate の入力には ID が無く、ID は作成した結果の側で割り当てられるからです。結びつけたいなら、3-5 のように ID を捕まえます。

TypeScript は env: { ...process.env, CLAUDE_CODE_ENABLE_TODO_TOOLS: "1" } と展開していて、Python は env={"CLAUDE_CODE_ENABLE_TODO_TOOLS": "1"} だけです。3-1 の表の言語差のとおりです。

3-5. 進捗をリアルタイムで表示する(トラッカーの形)

次の例は、TaskCreate と TaskUpdate を拾ったうえで、TaskTracker クラスでタスク ID をキーにしたタスクの表を持ち、変化のたびに進捗のまとめを表示し直します。まとめは完了と作業中の件数を数え、作業中の項目は subject の代わりに activeForm(作業中であることを表すラベル)を表示します。イベントを1つずつ記録するのではなく、進捗表示を持ち続けたいときの形です。

ID はどこから来るか

割り当てられたタスク ID は、TaskCreate の入力にはありません。Claude Code は各ツールの構造化された出力を、tool_result ブロックを持つユーザーメッセージの tool_use_result フィールドで届けます。TaskCreate の場合、そのオブジェクトは TypeScript では TaskCreateOutput 型({ task: { id, subject } } の形)として文書化され、Python では同じ形の普通の辞書です。トラッカーは、各 tool_result ブロックを tool_use_id で元の tool_use 呼び出しと組にし、その組になったメッセージの tool_use_result から task.id を読みます。

流れを順に書くと次のとおりです。

順届くものトラッカーがすること
1アシスタントのメッセージの TaskCreate の tool_use(id は呼び出しの ID)件名と activeForm を、呼び出しの ID をキーにした「作成待ち」の表に入れる
2ユーザーメッセージの tool_result(tool_use_id で1と組になる)と tool_use_result作成待ちから取り出し、エラーでなければ task.id(タスクの ID)をキーにしてタスクの表に pending で入れる
3TaskUpdate の tool_useタスクの ID でタスクを探し、状態や activeForm を更新。deleted なら表から消す

表は筆者の整理です。「呼び出しの ID」と「タスクの ID」という2種類の ID があるのが、この例の要点です。

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

言語差を整理します。

TypeScriptPython
ユーザーメッセージの判定message.type === "user" かつ Array.isArray(message.message.content)isinstance(message, UserMessage) かつ isinstance(message.content, list)
構造化された出力message.tool_use_resultmessage.tool_use_result
作成待ちからの取り出しget してから deletepop で一度に
状態の値文字列のまま保持辞書の "status" に保持

表は筆者の整理です。ユーザーメッセージの content は文字列のこともあるので、どちらも「配列(リスト)のときだけ」中を見ています。

筆者の指摘は3つあります。

  1. 未着手の項目のアイコンが「❌」:この例は、完了を「✅」、作業中を「🔧」、それ以外(pending を含む)を「❌」で表示します。利用者の画面では「❌」は失敗に見えるので、実際のアプリでは未着手用の別の表示にしたほうがよいでしょう。
  2. トラッカーが始まる前に作られたタスクは出てこない:TaskUpdate は、タスクの表に無い ID なら何もせずに戻ります(if (!task) return)。たとえばセッションを再開して途中から追い始めると、それ以前に作られたタスクの更新は無視されます。原文によれば Claude は TaskList で一覧を読み戻せるので、その結果を使えば補える可能性があります(このページには、TaskList の出力の形は書かれていません)。
  3. ストリーミングの差分ではなく、完成したメッセージを読んでいる:この例は AssistantMessage の完成した tool_use ブロックを読んでいます。第101回の input_json_delta の途中の JSON と違い、入力は出来上がった形で手に入ります。ただしキー名は修正前のままなので、防御的な読み方は必要です。

4. まとめ

  • Claude は複数手順の作業で自分の todo リストを持ち、その作成・更新が TaskCreate・TaskUpdate などの tool_use としてメッセージの流れに出る。アプリはそれを読んでログや進捗表示を作れる。
  • 既定で使えるのは Claude Opus 4.7、Sonnet 4.6、Haiku 4.5 とそれ以前のモデルだけ(Claude Code v2.1.268 以降の既定)。新しいモデルは todo リストなしで作業を追跡できるので、アプリが読む場合にだけ有効にする。
  • 有効にする方法は、allowedTools で名指し、tools に並べる(他の組み込みツールも入れる)、env で CLAUDE_CODE_ENABLE_TODO_TOOLS=1。CLAUDE_CODE_ENABLE_TASKS=0 なら Task ツールの代わりに TodoWrite。別インストールの CLI を指すとそちらの既定になる。
  • todo は pending → in_progress → 完了、不要なら TaskUpdate の status: "deleted"。短い依頼では作られないこともある。
  • 流れてくる入力は修正前の形。taskId/id/task_id、activeForm/active_form を防御的に読む。
  • タスク ID は TaskCreate の入力に無い。ユーザーメッセージの tool_use_result(TaskCreateOutput の task.id)から、tool_use_id で組にして取る。
  • SDKTaskNotificationMessage/TaskNotificationMessage はバックグラウンドのタスク用で、todo とは別。
  • 例は acceptEdits で実際にファイルを作るので、空のディレクトリで試す。

次回予告

次回は 「Agent SDK への移行ガイド」(agent-sdk/migration-guide) を取り上げる予定です。

今回、同じ SDK でも、どの版の Claude Code を同梱しているか、どのモデルを使うかで、使えるツールの既定が変わることを見ました。第76回で触れたとおり、Agent SDK は以前「Claude Code SDK」という名前で提供されていて、そこから移るための案内があります。次回は、古い名前の SDK から今の Agent SDK に移るときに、何を書き換え、どんな振る舞いの違いに気をつければよいのかを見ていきます。


よっしー
よっしー

何か質問や相談があれば、コメントをお願いします。また、エンジニア案件の相談にも随時対応していますので、お気軽にお問い合わせください。

それでは、また明日お会いしましょう(^^)

コメント

タイトルとURLをコピーしました