mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-06-10 08:32:09 +00:00
fix(delegate): flatten content blocks in live overlay tail + AUTHOR_MAP
Follow-up on the cherry-picked content-block fix. _extract_output_tail
(the live subagent overlay) still used crude str(content), which renders
a "[{'type': 'text'...}]" blob and — worse — mislabels a block-wrapped
"Error: ..." result as is_error=False. Route it through the same
_stringify_tool_content helper so error detection and previews work at
both consumer sites.
- delegate_tool.py: _extract_output_tail uses _stringify_tool_content
- tests: add _extract_output_tail content-block test (error detection +
clean preview)
- release.py: AUTHOR_MAP entry for randomsnowflake (CI gate)
This commit is contained in:
parent
f83918c31d
commit
f8a241e105
3 changed files with 41 additions and 3 deletions
|
|
@ -45,6 +45,7 @@ ACP_REGISTRY_MANIFEST = REPO_ROOT / "acp_registry" / "agent.json"
|
|||
|
||||
# Auto-extracted from noreply emails + manual overrides
|
||||
AUTHOR_MAP = {
|
||||
"al@randomsnowflake.me": "randomsnowflake",
|
||||
"834740219@qq.com": "ViewWay",
|
||||
"harjoth.khara@gmail.com": "harjothkhara",
|
||||
"129007007+HeLLGURD@users.noreply.github.com": "HeLLGURD",
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from tools.delegate_tool import (
|
|||
_build_child_agent,
|
||||
_build_child_progress_callback,
|
||||
_build_child_system_prompt,
|
||||
_extract_output_tail,
|
||||
_strip_blocked_tools,
|
||||
_resolve_child_credential_pool,
|
||||
_resolve_delegation_credentials,
|
||||
|
|
@ -585,6 +586,40 @@ class TestDelegateObservability(unittest.TestCase):
|
|||
self.assertEqual(trace[0]["status"], "ok")
|
||||
self.assertGreater(trace[0]["result_bytes"], 0)
|
||||
|
||||
def test_output_tail_flattens_list_content_blocks(self):
|
||||
"""_extract_output_tail (live overlay) must flatten content-block lists
|
||||
so error markers buried inside blocks are detected and previews are
|
||||
real text, not a "[{'type': 'text'...}]" repr blob."""
|
||||
result = {
|
||||
"messages": [
|
||||
{"role": "assistant", "tool_calls": [
|
||||
{"id": "t1", "function": {"name": "terminal", "arguments": "{}"}}
|
||||
]},
|
||||
{"role": "tool", "tool_call_id": "t1", "content": [
|
||||
{"type": "text", "text": "Error: command not found"},
|
||||
]},
|
||||
{"role": "assistant", "tool_calls": [
|
||||
{"id": "t2", "function": {"name": "vision", "arguments": "{}"}}
|
||||
]},
|
||||
{"role": "tool", "tool_call_id": "t2", "content": [
|
||||
{"type": "text", "text": "all good"},
|
||||
{"type": "image_url", "image_url": {"url": "data:x"}},
|
||||
]},
|
||||
]
|
||||
}
|
||||
tail = _extract_output_tail(result, max_entries=8, max_chars=600)
|
||||
by_tool = {t["tool"]: t for t in tail}
|
||||
|
||||
# Block-wrapped error is correctly flagged (crude str() would miss it).
|
||||
self.assertTrue(by_tool["terminal"]["is_error"])
|
||||
self.assertEqual(by_tool["terminal"]["preview"], "Error: command not found")
|
||||
# Non-error multimodal result is not flagged, and the text is readable.
|
||||
self.assertFalse(by_tool["vision"]["is_error"])
|
||||
self.assertIn("all good", by_tool["vision"]["preview"])
|
||||
# No raw content-block repr leaked into any preview.
|
||||
for entry in tail:
|
||||
self.assertNotIn("'type'", entry["preview"])
|
||||
|
||||
def test_tool_trace_detects_error(self):
|
||||
"""Tool results containing 'error' should be marked as error status."""
|
||||
parent = _make_mock_parent(depth=0)
|
||||
|
|
|
|||
|
|
@ -261,9 +261,11 @@ def _extract_output_tail(
|
|||
break
|
||||
if not isinstance(msg, dict) or msg.get("role") != "tool":
|
||||
continue
|
||||
content = msg.get("content") or ""
|
||||
if not isinstance(content, str):
|
||||
content = str(content)
|
||||
# Flatten content-block lists/dicts to text so the overlay shows real
|
||||
# output (not a "[{'type': 'text'...}]" blob) and error detection can
|
||||
# see markers buried inside content blocks. Crude str() here would
|
||||
# mislabel a block-wrapped "Error: ..." result as is_error=False.
|
||||
content = _stringify_tool_content(msg.get("content") or "")
|
||||
is_error = _looks_like_error_output(content)
|
||||
tool_name = pending_call_by_id.get(msg.get("tool_call_id") or "", "tool")
|
||||
# Preserve line structure so the overlay's wrapped scroll region can
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue