Files
hermes-agent/tests/hermes_cli/test_history_commentary_display.py
Brooklyn Nicholson 4546cd40ec fix(sessions): show a steer as the user's own words in REST history
session.resume already unwraps steer rows; the REST projection the
desktop hydrates from after a turn did not, so a correction sent during a
tool swapped its bubble for the model-facing OUT-OF-BAND marker text.
2026-09-30 12:01:29 -05:00

517 lines
18 KiB
Python

"""Public Codex commentary is a display projection, never a replay mutation."""
from hermes_cli.web_routers.sessions import _project_for_display
from tui_gateway.server import _history_to_messages
from agent.history_commentary import visible_commentary
from hermes_constants import get_hermes_home_override
import asyncio
import pytest
def _row(text="I will inspect the files."):
return {
"role": "assistant",
"content": "",
"reasoning": f"Private summary.\n\n{text}",
"reasoning_content": f"Private summary.\n\n{text}",
"codex_message_items": [
{
"type": "message",
"role": "assistant",
"phase": "commentary",
"content": [{"type": "output_text", "text": text}],
},
{
"type": "message",
"role": "assistant",
"phase": "analysis",
"content": [{"type": "output_text", "text": "private analysis"}],
},
],
"tool_calls": [
{
"id": "call-1",
"type": "function",
"function": {"name": "terminal", "arguments": "{}"},
}
],
"timestamp": 1,
}
def test_rest_history_separates_public_commentary_without_changing_source():
row = _row()
projected = _project_for_display([row])[0]
assert projected["display_commentary"] == ["I will inspect the files."]
assert projected["display_reasoning"] == "Private summary."
assert projected["codex_message_items"] == row["codex_message_items"]
assert projected["tool_calls"] == row["tool_calls"]
assert row["reasoning"] == "Private summary.\n\nI will inspect the files."
def test_gateway_history_uses_same_display_projection():
row = _row()
projected = _history_to_messages([row])[0]
assert projected["display_commentary"] == ["I will inspect the files."]
assert projected["display_reasoning"] == "Private summary."
assert projected["codex_message_items"] == row["codex_message_items"]
assert row["reasoning"].endswith("I will inspect the files.")
def test_rest_history_shows_a_steer_as_the_users_own_words():
from agent.prompt_builder import steer_user_row
row = steer_user_row("fix the header too")
projected = _project_for_display([row])[0]
# Same words session.resume shows; the model-facing marker stays in the source.
assert projected["display_content"] == "fix the header too"
assert _history_to_messages([row])[0]["text"] == "fix the header too"
assert row["content"].startswith("[OUT-OF-BAND USER MESSAGE")
def test_history_applies_live_stripping_and_redaction_without_changing_raw_items(
monkeypatch,
):
monkeypatch.setattr("agent.redact._redact_enabled", lambda: True)
raw = (
"Start. <think>private scratchpad</think> Key=" + "sk-" + "demo0123456789abcdef"
)
row = _row(raw)
for projected in (_project_for_display([row])[0], _history_to_messages([row])[0]):
assert projected["display_commentary"] == [visible_commentary(raw)]
assert "<think>" not in projected["display_commentary"][0]
assert "sk-demo0123456789abcdef" not in projected["display_commentary"][0]
assert projected["display_reasoning"] == "Private summary."
assert projected["codex_message_items"] == row["codex_message_items"]
@pytest.mark.parametrize(
"disabled",
[
{"show_commentary": False},
{"show_commentary": "false"},
{"interim_assistant_messages": False},
],
)
def test_owner_profile_settings_apply_to_rest_and_rpc(monkeypatch, tmp_path, disabled):
from hermes_cli import config as config_mod
enabled_home, disabled_home = tmp_path / "enabled", tmp_path / "disabled"
observed = []
def config():
home = get_hermes_home_override()
observed.append(str(home))
return {"display": {} if str(home) == str(enabled_home) else disabled}
monkeypatch.setattr(config_mod, "load_config", config)
row = _row()
for adapter in (
lambda home: _project_for_display([row], home=home)[0],
lambda home: _history_to_messages([row], profile_home=home)[0],
):
assert adapter(enabled_home)["display_commentary"] == [
"I will inspect the files."
]
hidden = adapter(disabled_home)
assert hidden["display_commentary"] == []
assert hidden["display_reasoning"] == "Private summary."
assert hidden["reasoning"] == row["reasoning"]
assert adapter(enabled_home)["display_reasoning"] == "Private summary."
assert observed == [str(enabled_home), str(disabled_home), str(enabled_home)] * 2
def test_rest_pages_bind_the_history_owner_for_messages_and_around(
monkeypatch, tmp_path
):
from hermes_cli import config as config_mod
from hermes_cli.web_routers import sessions
import hermes_state_timeline
row = _row()
homes = {name: tmp_path / name for name in ("visible", "hidden")}
monkeypatch.setattr(
sessions, "_cron_profile_home", lambda profile: (profile, homes[profile])
)
monkeypatch.setattr(
config_mod,
"load_config",
lambda: {
"display": {
"show_commentary": str(get_hermes_home_override())
== str(homes["visible"])
}
},
)
class FakeDB:
def resolve_session_id(self, sid):
return sid
def resolve_resume_session_id(self, sid):
return sid
def get_messages(self, *args, **kwargs):
return [row]
monkeypatch.setattr(
sessions, "_with_db", lambda profile, fn, read_only: fn(FakeDB())
)
monkeypatch.setattr(sessions, "_timeline_session_id", lambda db, sid, owner: sid)
monkeypatch.setattr(
hermes_state_timeline,
"get_session_messages_around",
lambda *args, **kwargs: {"messages": [row], "pagination": {}},
)
async def exercise():
for profile, expected in (
("visible", ["I will inspect the files."]),
("hidden", []),
):
page = await sessions.get_session_messages(
"sid",
profile=profile,
limit=120,
offset=0,
order="latest",
include_compacted=True,
)
around = await sessions.get_session_messages_around(
"sid", row_id=1, profile=profile, limit=120
)
assert page["messages"][0]["display_commentary"] == expected
assert around["messages"][0]["display_commentary"] == expected
assert page["profile"] == around["profile"] == profile
asyncio.run(exercise())
def test_unscoped_rest_history_uses_custom_home_of_its_database(monkeypatch, tmp_path):
from hermes_cli import config as config_mod
from hermes_cli.web_routers import sessions
from hermes_constants import reset_hermes_home_override, set_hermes_home_override
import hermes_state_timeline
custom, default = tmp_path / "custom", tmp_path / "default"
monkeypatch.setattr(
sessions, "_cron_profile_home", lambda profile: ("default", default)
)
monkeypatch.setattr(
config_mod,
"load_config",
lambda: {
"display": {
"show_commentary": str(get_hermes_home_override()) != str(custom)
}
},
)
row = _row()
class FakeDB:
def resolve_session_id(self, sid):
return sid
def resolve_resume_session_id(self, sid):
return sid
def get_messages(self, *args, **kwargs):
return [row]
monkeypatch.setattr(
sessions, "_with_db", lambda profile, fn, read_only: fn(FakeDB())
)
monkeypatch.setattr(sessions, "_timeline_session_id", lambda db, sid, owner: sid)
monkeypatch.setattr(
hermes_state_timeline,
"get_session_messages_around",
lambda *args, **kwargs: {"messages": [row], "pagination": {}},
)
async def exercise():
page = await sessions.get_session_messages(
"sid",
profile=None,
limit=120,
offset=0,
order="latest",
include_compacted=True,
)
around = await sessions.get_session_messages_around(
"sid",
row_id=1,
profile=None,
limit=120,
)
assert page["messages"][0]["display_commentary"] == []
assert around["messages"][0]["display_commentary"] == []
token = set_hermes_home_override(str(custom))
try:
asyncio.run(exercise())
finally:
reset_hermes_home_override(token)
def test_cold_resume_uses_the_resumed_session_home(monkeypatch, tmp_path):
from hermes_cli import config as config_mod
from tui_gateway.server import _Resume
home = tmp_path / "disabled"
monkeypatch.setattr(
config_mod,
"load_config",
lambda: {
"display": {"show_commentary": str(get_hermes_home_override()) != str(home)}
},
)
resume = _Resume.__new__(_Resume)
resume.omit_messages = False
resume.profile_home = home
assert resume.messages([_row()])[0]["display_commentary"] == []
def test_joined_canonical_commentary_uses_sanitized_display_copy(monkeypatch):
monkeypatch.setattr("agent.redact._redact_enabled", lambda: True)
raw = "Checking. Key=" + "sk-" + "demo0123456789abcdef"
row = _row(raw)
row["content"] = raw
visible = _project_for_display([row])[0]
assert visible["display_content"] == visible_commentary(raw)
assert visible["content"] == raw
assert visible["display_reasoning"] == "Private summary."
from agent.history_commentary import project_history_commentary
from hermes_cli import config as config_mod
monkeypatch.setattr(
config_mod, "load_config", lambda: {"display": {"show_commentary": False}}
)
hidden = project_history_commentary([row])[0]
assert hidden["display_content"] == visible_commentary(raw)
assert hidden["display_commentary"] == []
def test_only_exact_delimiter_bounded_copy_is_removed():
row = _row("Same phrase")
row["reasoning"] = "Summary mentioning Same phrase as an incidental substring."
projected = _project_for_display([row])[0]
assert projected["display_reasoning"] == row["reasoning"]
row["reasoning"] = "Before.\n\nSame phrase\n\nAfter."
assert _project_for_display([row])[0]["display_reasoning"] == "Before.\n\nAfter."
def test_foreign_and_malformed_items_are_not_public():
row = _row()
row["codex_message_items"][0]["role"] = "user"
assert "display_commentary" not in _project_for_display([row])[0]
row["codex_message_items"] = "{invalid JSON"
assert "display_commentary" not in _project_for_display([row])[0]
def test_mixed_canonical_content_is_sanitized_without_truncating_possible_final(
monkeypatch,
):
raw = "Checking. <think>hidden</think> key=" + "sk-" + "demo0123456789abcdef"
row = _row(raw)
row["content"] = raw + "\n\nFinal result."
monkeypatch.setattr("agent.redact._redact_enabled", lambda: True)
for projected in (_project_for_display([row])[0], _history_to_messages([row])[0]):
assert projected["display_commentary"] == []
assert projected["display_content"] == visible_commentary(row["content"])
assert raw not in projected["display_reasoning"]
from hermes_cli import config as config_mod
monkeypatch.setattr(
config_mod, "load_config", lambda: {"display": {"show_commentary": False}}
)
for projected in (_project_for_display([row])[0], _history_to_messages([row])[0]):
assert projected["display_commentary"] == []
assert projected["display_content"] == visible_commentary(row["content"])
assert row["content"].startswith(raw)
@pytest.mark.parametrize("final", ["Checking. The answer is 42.", "Checking."])
def test_stream_recovered_final_without_final_sidecar_is_authoritative(
monkeypatch, final
):
from types import SimpleNamespace
from agent.turn_finalizer import _close_transcript_tail
from hermes_cli import config as config_mod
row = _row("Checking.")
agent = SimpleNamespace(_db_flush_scan_prefix=None)
_close_transcript_tail(agent, [row], final, False, True)
assert row["content"] == final
for visible in (True, False):
monkeypatch.setattr(
config_mod,
"load_config",
lambda visible=visible: {"display": {"show_commentary": visible}},
)
for projected in (
_project_for_display([row])[0],
_history_to_messages([row])[0],
):
assert (
projected.get(
"display_content", projected.get("content", projected.get("text"))
)
== final
)
@pytest.mark.parametrize("final", ["Checking. The answer is 42.", "Checking."])
@pytest.mark.parametrize("phase", ["final", "final_answer", None])
def test_canonical_content_matching_final_item_is_never_stripped(
monkeypatch, final, phase
):
from hermes_cli import config as config_mod
row = _row("Checking.")
row["content"] = final
item = {
"type": "message",
"role": "assistant",
"content": [{"type": "output_text", "text": final}],
}
if phase is not None:
item["phase"] = phase
row["codex_message_items"].append(item)
for visible in (True, False):
monkeypatch.setattr(
config_mod,
"load_config",
lambda visible=visible: {"display": {"show_commentary": visible}},
)
for projected in (
_project_for_display([row])[0],
_history_to_messages([row])[0],
):
assert (
projected.get(
"display_content", projected.get("content", projected.get("text"))
)
== final
)
assert projected["display_commentary"] == (["Checking."] if visible else [])
def test_mixed_canonical_preserves_final_repetition_of_a_commentary_phrase(monkeypatch):
from hermes_cli import config as config_mod
row = _row("Checking")
row["content"] = "Checking\n\nFinal: Checking completed."
for visible in (True, False):
monkeypatch.setattr(
config_mod,
"load_config",
lambda visible=visible: {"display": {"show_commentary": visible}},
)
for projected in (
_project_for_display([row])[0],
_history_to_messages([row])[0],
):
assert projected["display_content"] == row["content"]
assert projected["display_commentary"] == []
row["codex_message_items"].append({
"type": "message",
"role": "assistant",
"phase": "commentary",
"content": [{"type": "output_text", "text": "Second update"}],
})
row["content"] = "Checking\n\nSecond update\n\nFinal: Checking completed."
monkeypatch.setattr(
config_mod, "load_config", lambda: {"display": {"show_commentary": True}}
)
for projected in (_project_for_display([row])[0], _history_to_messages([row])[0]):
assert projected["display_content"] == row["content"]
assert projected["display_commentary"] == []
def test_legacy_newline_and_ambiguous_repeats_do_not_leak_public_text_into_thinking():
row = _row("Public update.")
row["reasoning"] = "Private before.\nPublic update.\nPrivate after."
assert (
_project_for_display([row])[0]["display_reasoning"]
== "Private before.\nPrivate after."
)
row["reasoning"] = (
"Private before.\n\nPublic update.\n\nPrivate after.\n\nPublic update."
)
assert (
_project_for_display([row])[0]["display_reasoning"]
== "Private before.\n\nPrivate after."
)
assert row["reasoning"].endswith("Public update.")
def test_overlapping_public_items_do_not_leave_partial_text_in_final_or_thinking(
monkeypatch,
):
from hermes_cli import config as config_mod
row = _row("Start")
row["codex_message_items"].insert(
1,
{
"type": "message",
"role": "assistant",
"phase": "commentary",
"content": [{"type": "output_text", "text": "Start\nTail public"}],
},
)
row["reasoning"] = "Private.\n\nStart\nTail public"
row["content"] = "Start\nTail public\n\nFinal answer."
for visible in (True, False):
monkeypatch.setattr(
config_mod,
"load_config",
lambda visible=visible: {"display": {"show_commentary": visible}},
)
for projected in (
_project_for_display([row])[0],
_history_to_messages([row])[0],
):
assert projected["display_content"] == row["content"]
assert projected["display_reasoning"] == "Private."
assert projected["display_commentary"] == []
# Without an attributable leading commentary span, this belongs to the final.
row["content"] = "Leading Start\nTail public trailing"
for visible in (True, False):
monkeypatch.setattr(
config_mod,
"load_config",
lambda visible=visible: {"display": {"show_commentary": visible}},
)
for projected in (
_project_for_display([row])[0],
_history_to_messages([row])[0],
):
assert projected["display_content"] == "Leading Start\nTail public trailing"
assert projected["display_commentary"] == (
["Start", "Start\nTail public"] if visible else []
)
row["content"] = "Start\nTail publicFinal answer."
monkeypatch.setattr(
config_mod, "load_config", lambda: {"display": {"show_commentary": True}}
)
for projected in (_project_for_display([row])[0], _history_to_messages([row])[0]):
assert projected["display_content"] == row["content"]
assert projected["display_commentary"] == []