chore: uptick

This commit is contained in:
Brooklyn Nicholson
2026-05-02 03:19:39 -05:00
parent 420f68e4e2
commit db884f4646
240 changed files with 25206 additions and 3155 deletions
+168 -2
View File
@@ -59,6 +59,28 @@ def test_write_json_returns_false_on_broken_pipe(monkeypatch):
assert server.write_json({"ok": True}) is False
def test_dispatch_rejects_non_object_request():
resp = server.dispatch([])
assert resp == {
"jsonrpc": "2.0",
"id": None,
"error": {"code": -32600, "message": "invalid request: expected an object"},
}
def test_dispatch_rejects_non_object_params():
resp = server.dispatch(
{"id": "1", "method": "session.create", "params": []}
)
assert resp == {
"jsonrpc": "2.0",
"id": "1",
"error": {"code": -32602, "message": "invalid params: expected an object"},
}
def test_load_enabled_toolsets_prefers_tui_env(monkeypatch):
monkeypatch.setenv("HERMES_TUI_TOOLSETS", "web, terminal, ,memory")
@@ -115,7 +137,10 @@ def test_load_enabled_toolsets_rejects_disabled_mcp_env(monkeypatch, capsys):
)
monkeypatch.setattr(config_mod, "load_config", lambda: {"platform_toolsets": {"cli": ["memory"]}})
assert server._load_enabled_toolsets() == ["memory"]
# Sorted: ["kanban", "memory"]. `kanban` is auto-recovered by
# _get_platform_tools because it's a non-configurable platform toolset
# whose tools live in hermes-cli's universe (see toolsets.py).
assert server._load_enabled_toolsets() == ["kanban", "memory"]
err = capsys.readouterr().err
assert "ignoring disabled MCP servers" in err
assert "mcp-off" in err
@@ -134,7 +159,7 @@ def test_load_enabled_toolsets_falls_back_when_tui_env_invalid(monkeypatch, caps
monkeypatch.setattr(config_mod, "load_config", lambda: {"platform_toolsets": {"cli": ["memory"]}})
assert server._load_enabled_toolsets() == ["memory"]
assert server._load_enabled_toolsets() == ["kanban", "memory"]
assert "using configured CLI toolsets" in capsys.readouterr().err
@@ -980,6 +1005,21 @@ def test_config_busy_get_and_set(monkeypatch):
assert ("display.busy_input_mode", "interrupt") in writes
def test_config_set_yolo_process_scope_treats_false_like_env_as_disabled(monkeypatch):
monkeypatch.setenv("HERMES_YOLO_MODE", "false")
resp = server.handle_request(
{
"id": "1",
"method": "config.set",
"params": {"key": "yolo"},
}
)
assert resp["result"]["value"] == "1"
assert os.environ.get("HERMES_YOLO_MODE") == "1"
def test_config_get_statusbar_survives_non_dict_display(monkeypatch):
monkeypatch.setattr(server, "_load_cfg", lambda: {"display": "broken"})
@@ -1898,6 +1938,55 @@ def test_input_detect_drop_attaches_image(monkeypatch):
assert resp["result"]["text"] == "[User attached image: cat.png]"
def test_input_detect_drop_path_with_spaces(tmp_path):
"""input.detect_drop correctly handles image paths containing spaces."""
# Create a minimal PNG file with a space in its name
img = tmp_path / "screenshot with spaces.png"
img.write_bytes(b"\x89PNG\r\n\x1a\n") # valid PNG header
server._sessions["sid"] = _session()
resp = server.handle_request(
{
"id": "2",
"method": "input.detect_drop",
"params": {"session_id": "sid", "text": str(img)},
}
)
assert resp["result"]["matched"] is True
assert resp["result"]["is_image"] is True
assert resp["result"]["path"] == str(img)
assert resp["result"]["text"] == f"[User attached image: {img.name}]"
# Verify attachment was recorded in the session
assert len(server._sessions["sid"]["attached_images"]) == 1
assert server._sessions["sid"]["attached_images"][0] == str(img)
def test_input_detect_drop_path_with_spaces_and_remainder(tmp_path):
"""input.detect_drop splits remainder when path contains spaces."""
img = tmp_path / "photo with space.jpg"
img.write_bytes(b"\xff\xd8\xff" + b"fakejpeg") # minimal-ish JPEG header
server._sessions["sid"] = _session()
user_input = f"{img} describe this image"
resp = server.handle_request(
{
"id": "3",
"method": "input.detect_drop",
"params": {"session_id": "sid", "text": user_input},
}
)
assert resp["result"]["matched"] is True
assert resp["result"]["is_image"] is True
assert resp["result"]["path"] == str(img)
# Remainder becomes the text sent to the model
assert resp["result"]["text"] == "describe this image"
assert server._sessions["sid"]["attached_images"][0] == str(img)
def test_rollback_restore_resolves_number_and_file_path():
calls = {}
@@ -2214,6 +2303,83 @@ def test_prompt_submit_history_version_match_persists_normally(monkeypatch):
server._sessions.pop("sid", None)
def test_prompt_submit_can_truncate_before_user_ordinal(monkeypatch):
"""Desktop user-message edits should restart the turn from the edited user."""
seen = {}
class _Agent:
def run_conversation(
self, prompt, conversation_history=None, stream_callback=None
):
seen["prompt"] = prompt
seen["history"] = conversation_history
return {
"final_response": "edited reply",
"messages": [
*(conversation_history or []),
{"role": "user", "content": prompt},
{"role": "assistant", "content": "edited reply"},
],
}
class _ImmediateThread:
def __init__(self, target=None, daemon=None):
self._target = target
def start(self):
self._target()
original_history = [
{"role": "user", "content": "first"},
{"role": "assistant", "content": "first reply"},
{"role": "user", "content": "second"},
{"role": "assistant", "content": "second reply"},
]
server._sessions["sid"] = _session(agent=_Agent(), history=original_history)
class _StubDb:
def __init__(self):
self.replaced = []
def replace_messages(self, session_id, messages):
self.replaced.append((session_id, list(messages)))
stub_db = _StubDb()
try:
monkeypatch.setattr(server.threading, "Thread", _ImmediateThread)
monkeypatch.setattr(server, "_get_usage", lambda _a: {})
monkeypatch.setattr(server, "render_message", lambda _t, _c: "")
monkeypatch.setattr(server, "_emit", lambda *a: None)
monkeypatch.setattr(server, "_get_db", lambda: stub_db)
resp = server.handle_request(
{
"id": "1",
"method": "prompt.submit",
"params": {
"session_id": "sid",
"text": "edited second",
"truncate_before_user_ordinal": 1,
},
}
)
assert resp.get("result"), f"got error: {resp.get('error')}"
assert seen["prompt"] == "edited second"
assert seen["history"] == original_history[:2]
assert server._sessions["sid"]["history"] == [
*original_history[:2],
{"role": "user", "content": "edited second"},
{"role": "assistant", "content": "edited reply"},
]
assert server._sessions["sid"]["history_version"] == 2
assert stub_db.replaced == [("session-key", original_history[:2])]
finally:
server._sessions.pop("sid", None)
# ---------------------------------------------------------------------------
# session.interrupt must only cancel pending prompts owned by the calling
# session — it must not blast-resolve clarify/sudo/secret prompts on