From 5303a933dd2b681edfac31af5a6339494c042606 Mon Sep 17 00:00:00 2001 From: Lasmar Khalifa Date: Fri, 26 Jun 2026 12:19:45 -0400 Subject: [PATCH] Add taskoutput tool result formatter --- .../agent/providers/claude/tool_result.rb | 33 +++++++++++++ .../providers/claude/tool_result_test.rb | 48 +++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/lib/roast/cogs/agent/providers/claude/tool_result.rb b/lib/roast/cogs/agent/providers/claude/tool_result.rb index f3fe7b98..979b5029 100644 --- a/lib/roast/cogs/agent/providers/claude/tool_result.rb +++ b/lib/roast/cogs/agent/providers/claude/tool_result.rb @@ -292,6 +292,25 @@ def format_task ok_line(preview) end + # Formats a TaskOutput tool-result line. + # + # Content: the polled task's payload – an XML-ish string carrying a + # (or ) tag and an body. + # + # Output: "TASKOUTPUT OK " – the text inside , or + # inside when is absent. The status is + # omitted when neither tag is present, leaving a bare "TASKOUTPUT OK". + # + # Examples: + # TASKOUTPUT OK completed + # TASKOUTPUT OK pending + # TASKOUTPUT OK + # + #: () -> String + def format_taskoutput + ok_line(tag_text("status") || tag_text("retrieval_status")) + end + #: () -> String def format_unknown "UNKNOWN [#{tool_name}] OK #{tool_use_description}\n#{content}" @@ -324,6 +343,20 @@ def error_line "#{tool_name.to_s.upcase} ERROR #{message}".strip end + # Returns the stripped text inside the first pair in the + # result content, or nil when the tag is absent. The body is captured + # verbatim by a non-greedy match, so — unlike an XML parser — a body + # that itself contains bare angle brackets (such as an error message) + # is extracted intact. + # + # Matches a single, flat element: the lazy capture stops at the first + # closing , and nested same-name tags are not handled. + # + #: (String) -> String? + def tag_text(tag) + content.to_s[%r{<#{Regexp.escape(tag)}>(.*?)}m, 1]&.strip + end + # Truncates to TRUNCATE_LIMIT chars, appending "..." when cut. nil -> "". # #: (String?) -> String diff --git a/test/roast/cogs/agent/providers/claude/tool_result_test.rb b/test/roast/cogs/agent/providers/claude/tool_result_test.rb index d9e04e57..1a0dc9f0 100644 --- a/test/roast/cogs/agent/providers/claude/tool_result_test.rb +++ b/test/roast/cogs/agent/providers/claude/tool_result_test.rb @@ -928,6 +928,54 @@ class Claude::ToolResultTest < ActiveSupport::TestCase assert_equal "TASK OK #{"x" * (Claude::ToolResult::TRUNCATE_LIMIT - 3)}...", output end + test "format_taskoutput surfaces the status tag" do + tool_use_message = Claude::Messages::ToolUseMessage.new( + type: :tool_use, + hash: { name: "taskoutput", input: {} }, + ) + tool_result = Claude::ToolResult.new( + tool_use: tool_use_message, + content: "completeddone", + is_error: false, + ) + + output = tool_result.format + + assert_equal "TASKOUTPUT OK completed", output + end + + test "format_taskoutput falls back to retrieval_status when status is absent" do + tool_use_message = Claude::Messages::ToolUseMessage.new( + type: :tool_use, + hash: { name: "taskoutput", input: {} }, + ) + tool_result = Claude::ToolResult.new( + tool_use: tool_use_message, + content: "pending", + is_error: false, + ) + + output = tool_result.format + + assert_equal "TASKOUTPUT OK pending", output + end + + test "format_taskoutput reports a bare OK when no status tag is present" do + tool_use_message = Claude::Messages::ToolUseMessage.new( + type: :tool_use, + hash: { name: "taskoutput", input: {} }, + ) + tool_result = Claude::ToolResult.new( + tool_use: tool_use_message, + content: "just output", + is_error: false, + ) + + output = tool_result.format + + assert_equal "TASKOUTPUT OK", output + end + test "ok_line renders a bare OK line when given no parts" do tool_use_message = Claude::Messages::ToolUseMessage.new( type: :tool_use,