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
+188
View File
@@ -212,6 +212,82 @@ class TestMessageStorage:
messages = db.get_messages("s1")
assert messages[0]["tool_calls"] == tool_calls
def test_multimodal_list_content_round_trip(self, db):
"""Multimodal ``content`` (list of parts) must survive the SQLite
round-trip. sqlite3 cannot bind Python lists directly, so the DB
layer JSON-encodes structured content on write and decodes on read.
Regression test for the "Error binding parameter 3: type 'list' is
not supported" crash users hit when pasting screenshots into the
TUI (issue #17522).
"""
db.create_session(session_id="s1", source="cli")
content = [
{"type": "text", "text": "describe this screenshot"},
{
"type": "image_url",
"image_url": {"url": "data:image/png;base64,iVBORw0KG..."},
},
]
# Write must not raise
db.append_message("s1", role="user", content=content)
# get_messages decodes back to the original list
msgs = db.get_messages("s1")
assert len(msgs) == 1
assert msgs[0]["content"] == content
# get_messages_as_conversation decodes back to the original list
conv = db.get_messages_as_conversation("s1")
assert len(conv) == 1
assert conv[0] == {"role": "user", "content": content}
def test_dict_content_round_trip(self, db):
"""Dict-shaped content (e.g. provider wrappers) also round-trips."""
db.create_session(session_id="s1", source="cli")
content = {"parts": [{"text": "hi"}]}
db.append_message("s1", role="user", content=content)
msgs = db.get_messages("s1")
assert msgs[0]["content"] == content
def test_string_content_unchanged_by_encoding(self, db):
"""Plain strings must not be wrapped — FTS search and legacy
consumers depend on raw-string storage for text content.
"""
db.create_session(session_id="s1", source="cli")
db.append_message("s1", role="user", content="plain text")
# Peek at the raw column to confirm no encoding was applied
with db._lock:
row = db._conn.execute(
"SELECT content FROM messages WHERE session_id = ?", ("s1",)
).fetchone()
assert row["content"] == "plain text"
def test_replace_messages_handles_multimodal_content(self, db):
"""`replace_messages` (used by /retry, /undo, /compress) must also
handle list content without crashing."""
db.create_session(session_id="s1", source="cli")
content = [
{"type": "text", "text": "look at this"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AAA"}},
]
db.replace_messages(
"s1",
[
{"role": "user", "content": content},
{"role": "assistant", "content": "I see a screenshot."},
],
)
msgs = db.get_messages("s1")
assert len(msgs) == 2
assert msgs[0]["content"] == content
assert msgs[1]["content"] == "I see a screenshot."
def test_get_messages_as_conversation(self, db):
db.create_session(session_id="s1", source="cli")
db.append_message("s1", role="user", content="Hello")
@@ -323,6 +399,27 @@ class TestMessageStorage:
assert msg["reasoning"] == "Thinking about what to say"
assert msg["reasoning_details"] == details
def test_finish_reason_restored_by_get_messages_as_conversation(self, db):
"""finish_reason on assistant messages must survive conversation replay.
Without this, /branch copies and other transcript round-trips silently
drop the provider's stop signal.
"""
db.create_session(session_id="s1", source="cli")
db.append_message(
"s1",
role="assistant",
content="Done",
finish_reason="tool_calls",
)
db.append_message("s1", role="user", content="next")
conv = db.get_messages_as_conversation("s1")
assert conv[0]["role"] == "assistant"
assert conv[0]["finish_reason"] == "tool_calls"
# Non-assistant rows should not have a finish_reason key added.
assert "finish_reason" not in conv[1]
def test_reasoning_content_persisted_and_restored(self, db):
"""reasoning_content must survive session replay as its own field."""
db.create_session(session_id="s1", source="cli")
@@ -1719,6 +1816,97 @@ class TestListSessionsRich:
# No messages, so last_active falls back to started_at
assert sessions[0]["last_active"] == sessions[0]["started_at"]
def test_order_by_last_active_surfaces_recently_touched_older_session_first(self, db):
t0 = 1709500000.0
db.create_session("old", "cli")
db.create_session("new", "cli")
with db._lock:
db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0, "old"))
db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0 + 10, "new"))
db.append_message("old", "user", "old first")
db.append_message("new", "user", "new first")
db.append_message("old", "assistant", "old touched later")
with db._lock:
db._conn.execute(
"UPDATE messages SET timestamp=? WHERE session_id=? AND role=? AND content=?",
(t0 + 1, "old", "user", "old first"),
)
db._conn.execute(
"UPDATE messages SET timestamp=? WHERE session_id=? AND role=? AND content=?",
(t0 + 11, "new", "user", "new first"),
)
db._conn.execute(
"UPDATE messages SET timestamp=? WHERE session_id=? AND role=? AND content=?",
(t0 + 20, "old", "assistant", "old touched later"),
)
db._conn.commit()
assert [s["id"] for s in db.list_sessions_rich(limit=5)] == ["new", "old"]
assert [
s["id"] for s in db.list_sessions_rich(limit=5, order_by_last_active=True)
] == ["old", "new"]
def test_order_by_last_active_uses_compression_tip_activity(self, db):
"""A compression root whose tip was touched recently must rank above
a newer uncompressed session, even when that tip activity lives in a
different row and the outer LIMIT could otherwise cut it.
This is the case that forced SQL-level chain walking: a naive "cap
the SQL fetch at limit*K" optimization would drop the old root off
the SQL page before post-projection could promote it.
"""
t0 = 1709500000.0
db.create_session("root1", "cli")
with db._lock:
db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0, "root1"))
db._conn.execute(
"UPDATE sessions SET ended_at=?, end_reason=? WHERE id=?",
(t0 + 100, "compression", "root1"),
)
db.append_message("root1", "user", "old ask")
# Continuation tip created after root ended; last activity much later.
db.create_session("tip1", "cli", parent_session_id="root1")
with db._lock:
db._conn.execute("UPDATE sessions SET started_at=? WHERE id=?", (t0 + 101, "tip1"))
db.append_message("tip1", "user", "latest message")
# Bunch of newer, uncompressed sessions — fresher start_at but older
# last activity than the tip. Explicitly pin message timestamps so
# they don't pick up wall-clock from append_message.
for i in range(5):
sid = f"newer{i}"
db.create_session(sid, "cli")
with db._lock:
db._conn.execute(
"UPDATE sessions SET started_at=? WHERE id=?",
(t0 + 500 + i, sid),
)
db.append_message(sid, "user", f"msg {i}")
with db._lock:
db._conn.execute(
"UPDATE messages SET timestamp=? WHERE session_id=? AND content=?",
(t0 + 500 + i, sid, f"msg {i}"),
)
# Tip activity timestamp is the latest thing in the DB.
with db._lock:
db._conn.execute(
"UPDATE messages SET timestamp=? WHERE session_id=? AND content=?",
(t0 + 10_000, "tip1", "latest message"),
)
db._conn.commit()
# limit=1 is the stress test: the old root must win the single slot.
top = db.list_sessions_rich(limit=1, order_by_last_active=True)
assert len(top) == 1
# Projection surfaces the tip's id in the root's slot.
assert top[0]["id"] == "tip1"
assert top[0]["_lineage_root_id"] == "root1"
def test_rich_list_includes_title(self, db):
db.create_session("s1", "cli")
db.set_session_title("s1", "refactoring auth")