diff --git a/lib/roast/cogs/agent/providers/claude/tool_use.rb b/lib/roast/cogs/agent/providers/claude/tool_use.rb index 7f8e9881..c5f476b9 100644 --- a/lib/roast/cogs/agent/providers/claude/tool_use.rb +++ b/lib/roast/cogs/agent/providers/claude/tool_use.rb @@ -237,6 +237,35 @@ def format_skill args ? "SKILL #{skill} (#{truncate(args)})" : "SKILL #{skill}" end + # Formats a Task tool-use line. + # + # Input fields: + # :description (String) – short label for the task [required] + # :run_in_background (bool) – run the agent asynchronously [optional] + # :subagent_type (String) – which agent type to spawn [optional] + # :model (String) – model override for the agent [optional] + # + # Output: "TASK ", with " (
)" appended when any + # optional field is set: "background" (when :run_in_background is set), + # then :subagent_type, then :model — joined with " · " in that order and + # shown as raw values (no key= labels). :description is truncated to + # TRUNCATE_LIMIT chars; the other fields are not. + # + # Examples: + # TASK Find all callers (background · Explore · opus) + # TASK Summarize the diff + # + #: () -> String + def format_task + description = truncate(input[:description]) + details = [ + ("background" if input[:run_in_background]), + input[:subagent_type], + input[:model], + ].compact.join(" · ") + details.empty? ? "TASK #{description}" : "TASK #{description} (#{details})" + end + #: () -> String def format_unknown "UNKNOWN [#{name}] #{input.inspect}" diff --git a/test/roast/cogs/agent/providers/claude/tool_use_test.rb b/test/roast/cogs/agent/providers/claude/tool_use_test.rb index e338cdc8..0b7468a1 100644 --- a/test/roast/cogs/agent/providers/claude/tool_use_test.rb +++ b/test/roast/cogs/agent/providers/claude/tool_use_test.rb @@ -292,6 +292,44 @@ class ToolUseTest < ActiveSupport::TestCase assert_equal "SKILL #{long} (#{truncated})", output end + # format_task + + test "format_task renders the description alone with no optional fields" do + tool_use = ToolUse.new(name: :task, input: { description: "Find all callers" }) + + output = tool_use.format + + assert_equal "TASK Find all callers", output + end + + test "format_task joins background, subagent type, and model in order" do + input = { description: "Audit", run_in_background: true, subagent_type: "Explore", model: "opus" } + tool_use = ToolUse.new(name: :task, input: input) + + output = tool_use.format + + assert_equal "TASK Audit (background · Explore · opus)", output + end + + test "format_task omits background when run_in_background is false" do + tool_use = ToolUse.new(name: :task, input: { description: "Audit", run_in_background: false }) + + output = tool_use.format + + assert_equal "TASK Audit", output + end + + test "format_task truncates the description but not the subagent type or model" do + long = "a" * (ToolUse::TRUNCATE_LIMIT + 10) + truncated = "#{"a" * (ToolUse::TRUNCATE_LIMIT - 3)}..." + input = { description: long, subagent_type: long, model: long } + tool_use = ToolUse.new(name: :task, input: input) + + output = tool_use.format + + assert_equal "TASK #{truncated} (#{long} · #{long})", output + end + test "format calls format_unknown for unknown tool" do tool_use = ToolUse.new(name: :unknown_tool, input: { arg: "value" })