Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
131 lines
4.9 KiB
Python
131 lines
4.9 KiB
Python
"""Tests for session identity — telling concurrent clients apart.
|
|
|
|
The router has to attribute an outcome report to the right conversation
|
|
without the client cooperating. Getting this wrong is worse than getting no
|
|
report at all: a misattributed test failure penalizes a model for work it
|
|
never did, and this project has already recorded false failures twice from
|
|
harness bugs that took measurement to catch.
|
|
|
|
So the rule under test is: identify exactly when possible, refuse when not.
|
|
"""
|
|
|
|
import json
|
|
|
|
from dispatcher import session_directory, session_fingerprint
|
|
|
|
|
|
SYS_A = "You are opencode. Project root: /home/alee/Sources/6krrt"
|
|
SYS_B = "You are opencode. Project root: /tmp/otherproj"
|
|
|
|
|
|
def _conv(system, *turns):
|
|
return [{"role": "system", "content": system}] + [
|
|
{"role": "user", "content": t} for t in turns
|
|
]
|
|
|
|
|
|
# --- fingerprints ---------------------------------------------------------
|
|
|
|
def test_two_sessions_are_distinguishable():
|
|
assert session_fingerprint(_conv(SYS_A)) != session_fingerprint(_conv(SYS_B))
|
|
|
|
|
|
def test_a_fingerprint_is_stable_as_a_session_grows():
|
|
# It must survive the conversation accumulating turns, or every message
|
|
# would look like a new session
|
|
short = session_fingerprint(_conv(SYS_A, "first"))
|
|
long = session_fingerprint(_conv(SYS_A, "first", "second", "third"))
|
|
assert short == long
|
|
|
|
|
|
def test_an_empty_conversation_has_no_fingerprint():
|
|
assert session_fingerprint([]) is None
|
|
assert session_fingerprint([{"role": "user", "content": " "}]) is None
|
|
|
|
|
|
def test_multimodal_content_still_fingerprints():
|
|
msgs = [{"role": "system", "content": [{"type": "text", "text": SYS_A}]}]
|
|
assert session_fingerprint(msgs) is not None
|
|
|
|
|
|
# --- directory extraction -------------------------------------------------
|
|
|
|
def test_a_single_mention_is_not_enough_to_claim_a_directory():
|
|
# One stray path proves nothing. Requiring corroboration keeps an
|
|
# unrelated path in a chat message from hijacking attribution.
|
|
assert session_directory(_conv("see /home/alee/Sources/6krrt once")) is None
|
|
|
|
|
|
def test_no_directory_when_the_prompt_names_none():
|
|
assert session_directory(_conv("You are a helpful assistant.")) is None
|
|
|
|
|
|
def test_only_the_opening_messages_are_scanned():
|
|
# A path mentioned deep in a long conversation is not the project root
|
|
msgs = _conv("You are an assistant.", "x", "y") + [
|
|
{"role": "user", "content": "check /tmp/some/other/path"}
|
|
]
|
|
assert session_directory(msgs) is None
|
|
|
|
|
|
# --- directory from the whole conversation --------------------------------
|
|
|
|
def test_directory_comes_from_the_files_the_agent_touches():
|
|
# opencode does not state its project root up front — verified by
|
|
# capturing a real request — but a coding agent names files inside its
|
|
# project constantly.
|
|
conv = [
|
|
{"role": "assistant", "content": "reading /tmp/proj/inventory.py"},
|
|
{"role": "user", "content": "ran /tmp/proj/test_inventory.py"},
|
|
{"role": "assistant", "content": "editing /tmp/proj/inventory.py"},
|
|
]
|
|
assert session_directory(conv) == "/tmp/proj"
|
|
|
|
|
|
def test_a_filename_is_never_mistaken_for_a_directory():
|
|
conv = [{"role": "assistant", "content": "/tmp/proj/only.py " * 3}]
|
|
assert session_directory(conv) == "/tmp/proj"
|
|
|
|
|
|
def test_the_shared_root_wins_over_any_one_subdirectory():
|
|
conv = [{"role": "assistant", "content":
|
|
"/home/alee/Sources/6krrt/routing.py "
|
|
"/home/alee/Sources/6krrt/tests/test_routing.py "
|
|
"/home/alee/Sources/6krrt/scoring.py"}]
|
|
assert session_directory(conv) == "/home/alee/Sources/6krrt"
|
|
|
|
|
|
def test_two_projects_stay_distinct():
|
|
a = [{"role": "assistant", "content": "/tmp/projA/x.py /tmp/projA/y.py /tmp/projA/z.py"}]
|
|
b = [{"role": "assistant", "content": "/tmp/projB/x.py /tmp/projB/y.py /tmp/projB/z.py"}]
|
|
assert session_directory(a) != session_directory(b)
|
|
|
|
|
|
def test_write_tool_calls_outweigh_read_only_venv_mentions():
|
|
# Regression for the real incident: a long stack trace or error output
|
|
# repeated the same dependency path many times (read-only), while the
|
|
# actual work happened through edit tool-calls on project files.
|
|
venv_path = "/home/alee/Sources/6krrt/.venv/lib/python3.10/site-packages/requests/models.py"
|
|
project_file = "/home/alee/Sources/6krrt/src/dispatcher.py"
|
|
read_mentions = [
|
|
{"role": "assistant", "content": f"see {venv_path}"}
|
|
for _ in range(24)
|
|
]
|
|
write_calls = [
|
|
{
|
|
"role": "assistant",
|
|
"content": "",
|
|
"tool_calls": [
|
|
{
|
|
"function": {
|
|
"name": "edit",
|
|
"arguments": json.dumps({"path": project_file}),
|
|
}
|
|
}
|
|
],
|
|
}
|
|
for _ in range(10)
|
|
]
|
|
conv = read_mentions + write_calls
|
|
assert session_directory(conv) == "/home/alee/Sources/6krrt"
|