diff --git a/api/agent_sessions.py b/api/agent_sessions.py index dce28853..fbbe8d8d 100644 --- a/api/agent_sessions.py +++ b/api/agent_sessions.py @@ -199,6 +199,8 @@ def _is_continuation_session(parent: dict | None, child: dict | None) -> bool: """ if not parent or not child: return False + if str(child.get('session_source') or '').strip().lower() == 'fork': + return False parent_source = str(parent.get('source') or '').strip().lower() child_source = str(child.get('source') or '').strip().lower() if parent_source and child_source and parent_source != child_source: @@ -496,6 +498,7 @@ def read_session_lineage_report(db_path: Path, session_id: str | None, max_hops: return _empty_lineage_report(sid) source_expr = _optional_col('source', session_cols) + session_source_expr = _optional_col('session_source', session_cols) title_expr = _optional_col('title', session_cols) started_expr = _optional_col('started_at', session_cols, '0') ended_expr = _optional_col('ended_at', session_cols) @@ -509,6 +512,7 @@ def read_session_lineage_report(db_path: Path, session_id: str | None, max_hops: f""" SELECT s.id, {source_expr}, + {session_source_expr}, {title_expr}, {started_expr}, {parent_expr}, @@ -551,6 +555,7 @@ def read_session_lineage_report(db_path: Path, session_id: str | None, max_hops: f""" SELECT s.id, {source_expr}, + {session_source_expr}, {title_expr}, {started_expr}, {parent_expr}, diff --git a/tests/test_session_lineage_report.py b/tests/test_session_lineage_report.py index a95b0bb5..ea77d2c6 100644 --- a/tests/test_session_lineage_report.py +++ b/tests/test_session_lineage_report.py @@ -18,6 +18,7 @@ def _ensure_state_db(path): CREATE TABLE sessions ( id TEXT PRIMARY KEY, source TEXT, + session_source TEXT, title TEXT, model TEXT, started_at REAL NOT NULL, @@ -31,14 +32,14 @@ def _ensure_state_db(path): return conn -def _insert_state_row(conn, sid, *, parent=None, ended_at=None, end_reason=None, started_at=None, source="webui"): +def _insert_state_row(conn, sid, *, parent=None, ended_at=None, end_reason=None, started_at=None, source="webui", session_source=None): conn.execute( """ INSERT INTO sessions - (id, source, title, model, started_at, message_count, parent_session_id, ended_at, end_reason) - VALUES (?, ?, ?, 'openai/gpt-5', ?, 2, ?, ?, ?) + (id, source, session_source, title, model, started_at, message_count, parent_session_id, ended_at, end_reason) + VALUES (?, ?, ?, ?, 'openai/gpt-5', ?, 2, ?, ?, ?) """, - (sid, source, sid.replace("_", " "), started_at or time.time(), parent, ended_at, end_reason), + (sid, source, session_source, sid.replace("_", " "), started_at or time.time(), parent, ended_at, end_reason), ) conn.commit() @@ -104,6 +105,32 @@ def test_lineage_report_keeps_cross_surface_parent_out_of_hidden_segments(tmp_pa conn.close() +def test_lineage_report_keeps_explicit_forks_out_of_hidden_segments(tmp_path): + conn = _ensure_state_db(tmp_path / "state.db") + t0 = time.time() - 100 + try: + _insert_state_row(conn, "lineage_report_root", started_at=t0, ended_at=t0 + 5, end_reason="compression") + _insert_state_row( + conn, + "lineage_report_fork", + parent="lineage_report_root", + started_at=t0 + 6, + session_source="fork", + ) + + report = agent_sessions.read_session_lineage_report(tmp_path / "state.db", "lineage_report_fork") + + assert report["lineage_key"] == "lineage_report_fork" + assert report["tip_session_id"] == "lineage_report_fork" + assert report["total_segments"] == 1 + assert [s["session_id"] for s in report["segments"]] == ["lineage_report_fork"] + assert report["segments"][0]["role"] == "tip" + assert report["children"] == [] + assert report["manual_review"] is False + finally: + conn.close() + + def test_lineage_report_surfaces_non_continuation_children_without_mutation(tmp_path): conn = _ensure_state_db(tmp_path / "state.db") t0 = time.time() - 100