fix(sessions): preserve branch lineage on reopen
This commit is contained in:
@@ -448,14 +448,18 @@ class SessionSessionsMixin:
|
||||
return changed
|
||||
|
||||
def reopen_session(self, session_id: str) -> None:
|
||||
"""Clear ended_at/end_reason so a session can be resumed; first stamp markerless legacy reset
|
||||
children that depend on the parent's mutable end_reason (WHERE shared with the listing predicate
|
||||
so they cannot drift)."""
|
||||
"""Clear ended_at/end_reason so a session can be resumed; first freeze only legacy reset children
|
||||
whose creation order proves they followed the reset. The shared legacy predicate keeps list-time
|
||||
compatibility broad, while this durable backfill must not overwrite explicit fork provenance."""
|
||||
def _do(conn):
|
||||
conn.execute(
|
||||
"UPDATE sessions AS child SET model_config = json_set("
|
||||
"COALESCE(child.model_config, '{}'), '$._reset_from', child.parent_session_id) "
|
||||
f"WHERE child.parent_session_id = ? AND {_sql_json_extract('child.model_config', '$._reset_from')} IS NULL "
|
||||
f"AND {_sql_json_extract('child.model_config', '$._branched_from')} IS NULL "
|
||||
f"AND {_sql_json_extract('child.model_config', '$._delegate_from')} IS NULL "
|
||||
"AND COALESCE(child.source, '') != 'tool' "
|
||||
"AND child.started_at >= (SELECT p.ended_at FROM sessions p WHERE p.id = child.parent_session_id) "
|
||||
f"AND {_legacy_reset_child_sql('child', _session_ids_placeholders(_RESET_END_REASONS))}",
|
||||
(session_id, *_RESET_END_REASONS),
|
||||
)
|
||||
|
||||
@@ -2896,6 +2896,75 @@ class TestListSessionsRich:
|
||||
).fetchall()
|
||||
assert child_id not in {row["id"] for row in ephemeral}
|
||||
|
||||
def test_reopen_keeps_branch_provenance_out_of_legacy_reset_backfill(self, db):
|
||||
"""A same-key branch is never rewritten as a reset successor on reopen."""
|
||||
lane_key = "agent:main:telegram:dm:branch"
|
||||
db.create_session("branch_parent", "telegram", session_key=lane_key)
|
||||
db.create_session(
|
||||
"branch_child",
|
||||
"telegram",
|
||||
session_key=lane_key,
|
||||
parent_session_id="branch_parent",
|
||||
model_config={"_branched_from": "branch_parent"},
|
||||
)
|
||||
db.end_session("branch_parent", "session_switch")
|
||||
|
||||
db.reopen_session("branch_parent")
|
||||
|
||||
child = db.get_session("branch_child")
|
||||
assert child is not None
|
||||
assert json.loads(child["model_config"]) == {"_branched_from": "branch_parent"}
|
||||
assert "branch_child" in [row["id"] for row in db.list_sessions_rich(source="telegram")]
|
||||
|
||||
def test_reopen_does_not_backfill_child_that_precedes_reset_boundary(self, db):
|
||||
"""A pre-marker branch cannot become a reset child after a later reopen cycle."""
|
||||
lane_key = "agent:main:telegram:dm:legacy-branch"
|
||||
db.create_session("legacy_branch_parent", "telegram", session_key=lane_key)
|
||||
db.create_session(
|
||||
"legacy_branch_child",
|
||||
"telegram",
|
||||
session_key=lane_key,
|
||||
parent_session_id="legacy_branch_parent",
|
||||
)
|
||||
db._conn.execute(
|
||||
"UPDATE sessions SET started_at = ? WHERE id = ?", (100.0, "legacy_branch_child")
|
||||
)
|
||||
db._conn.commit()
|
||||
db.end_session("legacy_branch_parent", "branched")
|
||||
db.reopen_session("legacy_branch_parent")
|
||||
db.end_session("legacy_branch_parent", "session_switch")
|
||||
db._conn.execute(
|
||||
"UPDATE sessions SET ended_at = ? WHERE id = ?", (200.0, "legacy_branch_parent")
|
||||
)
|
||||
db._conn.commit()
|
||||
|
||||
db.reopen_session("legacy_branch_parent")
|
||||
|
||||
child = db.get_session("legacy_branch_child")
|
||||
assert child is not None
|
||||
assert child["model_config"] is None
|
||||
|
||||
def test_reopen_still_freezes_markerless_legacy_reset_child(self, db):
|
||||
"""A genuine old reset child remains visible after its parent is reopened."""
|
||||
lane_key = "agent:main:telegram:dm:legacy-reset"
|
||||
db.create_session("legacy_reset_parent", "telegram", session_key=lane_key)
|
||||
db.end_session("legacy_reset_parent", "session_reset")
|
||||
db.create_session(
|
||||
"legacy_reset_child",
|
||||
"telegram",
|
||||
session_key=lane_key,
|
||||
parent_session_id="legacy_reset_parent",
|
||||
)
|
||||
|
||||
db.reopen_session("legacy_reset_parent")
|
||||
|
||||
child = db.get_session("legacy_reset_child")
|
||||
assert child is not None
|
||||
assert json.loads(child["model_config"])["_reset_from"] == "legacy_reset_parent"
|
||||
assert "legacy_reset_child" in [
|
||||
row["id"] for row in db.list_sessions_rich(source="telegram")
|
||||
]
|
||||
|
||||
def test_reset_parent_does_not_surface_unrelated_child(self, db):
|
||||
db.create_session(
|
||||
"reset_parent",
|
||||
|
||||
Reference in New Issue
Block a user