Closes the remote POSIX workspace path preservation gap across Session and streaming. Profile-aware runtime path resolution. Co-authored-by: alfred-rootson <alfred.wojtasordzik@gmail.com>
574 lines
22 KiB
Python
574 lines
22 KiB
Python
import copy
|
|
import queue
|
|
import threading
|
|
from types import SimpleNamespace
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from api.models import Session
|
|
from api.session_ops import (
|
|
RegenerationUnavailable,
|
|
RegenerationPlan,
|
|
apply_regeneration_plan,
|
|
plan_regeneration,
|
|
restore_regeneration_state,
|
|
snapshot_regeneration_state,
|
|
)
|
|
|
|
from tests._issue6611_fixture import load_issue6611_fixture
|
|
|
|
|
|
def _session():
|
|
rows = [
|
|
{"role": "user", "content": "prompt", "id": "u1", "_source": "webui"},
|
|
{"role": "assistant", "content": "failed"},
|
|
]
|
|
return Session(
|
|
session_id="transaction6611",
|
|
messages=copy.deepcopy(rows),
|
|
context_messages=copy.deepcopy(rows),
|
|
workspace="C:/workspace",
|
|
)
|
|
|
|
|
|
def test_plan_installs_rows_and_context_as_one_prepared_pair():
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
assert isinstance(plan, RegenerationPlan)
|
|
assert plan.canonical_rows == session.messages
|
|
assert plan.canonical_context == session.context_messages
|
|
applied, retained_context_user = apply_regeneration_plan(
|
|
session,
|
|
plan,
|
|
return_context_user=True,
|
|
)
|
|
assert applied
|
|
assert retained_context_user is session.context_messages[0]
|
|
assert session.messages == plan.canonical_rows[: plan.truncation_boundary]
|
|
assert session.context_messages == plan.canonical_context[: plan.truncation_boundary]
|
|
assert any(row.get("content") == "prompt" for row in session.context_messages)
|
|
assert [row["role"] for row in session.messages] == ["user"]
|
|
|
|
|
|
def test_apply_consumes_prepared_pair_without_ambient_authority_read(monkeypatch):
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
monkeypatch.setattr(
|
|
"api.session_ops.regeneration_state",
|
|
lambda _session: (_ for _ in ()).throw(AssertionError("ambient read")),
|
|
)
|
|
session.messages[0]["content"] = "changed"
|
|
assert apply_regeneration_plan(session, plan)
|
|
assert session.messages == plan.canonical_rows[: plan.truncation_boundary]
|
|
|
|
|
|
def test_complete_session_snapshot_restores_every_attribute():
|
|
session = _session()
|
|
session.compression_state = {"marker": "kept"}
|
|
session._anchor_scene_index = 7
|
|
before = copy.deepcopy(session.__dict__)
|
|
snapshot = snapshot_regeneration_state(session)
|
|
session.messages.clear()
|
|
session.context_messages.clear()
|
|
session.compression_state["marker"] = "changed"
|
|
session._anchor_scene_index = 99
|
|
restore_regeneration_state(session, snapshot)
|
|
assert session.__dict__ == before
|
|
|
|
|
|
def test_persisted_preacceptance_rollback_restores_exact_snapshot(monkeypatch):
|
|
session = _session()
|
|
snapshot = snapshot_regeneration_state(session)
|
|
persisted = []
|
|
monkeypatch.setattr(Session, "save", lambda self, **_kwargs: persisted.append(copy.deepcopy(self.__dict__)))
|
|
session.active_stream_id = "stale"
|
|
session.pending_user_message = "changed"
|
|
restore_regeneration_state(session, snapshot)
|
|
session.save(touch_updated_at=False)
|
|
assert persisted == [snapshot]
|
|
|
|
|
|
def test_noop_rejection_does_not_need_persisted_rollback():
|
|
session = _session()
|
|
snapshot = snapshot_regeneration_state(session)
|
|
restore_regeneration_state(session, snapshot)
|
|
assert session.__dict__ == snapshot
|
|
|
|
|
|
def test_locked_stale_plan_does_not_restore_a_request_time_snapshot(monkeypatch):
|
|
from api import routes
|
|
|
|
session = _session()
|
|
session.active_stream_id = "accepted-after-browser-validation"
|
|
current_state = copy.deepcopy(session.__dict__)
|
|
|
|
def stale_plan(*_args, **_kwargs):
|
|
raise RegenerationUnavailable("stale_regeneration_revision")
|
|
|
|
monkeypatch.setattr("api.session_ops.plan_regeneration", stale_plan)
|
|
result = routes._start_regeneration_stream_locked(
|
|
session,
|
|
turn=SimpleNamespace(revision="old-revision"),
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
normalized_model=False,
|
|
diag=None,
|
|
goal_related=False,
|
|
source="webui",
|
|
moa_config=None,
|
|
backend_is_gateway=False,
|
|
)
|
|
assert result["code"] == "stale_regeneration_revision"
|
|
assert session.__dict__ == current_state
|
|
|
|
|
|
def test_locked_unexpected_plan_error_does_not_restore_a_request_time_snapshot(monkeypatch):
|
|
from api import routes
|
|
|
|
session = _session()
|
|
session.active_stream_id = "accepted-after-browser-validation"
|
|
current_state = copy.deepcopy(session.__dict__)
|
|
|
|
def unexpected_plan(*_args, **_kwargs):
|
|
raise RuntimeError("plan failed")
|
|
|
|
monkeypatch.setattr("api.session_ops.plan_regeneration", unexpected_plan)
|
|
with pytest.raises(RuntimeError, match="plan failed"):
|
|
routes._start_regeneration_stream_locked(
|
|
session,
|
|
turn=SimpleNamespace(revision="old-revision"),
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
normalized_model=False,
|
|
diag=None,
|
|
goal_related=False,
|
|
source="webui",
|
|
moa_config=None,
|
|
backend_is_gateway=False,
|
|
)
|
|
assert session.__dict__ == current_state
|
|
|
|
|
|
def test_locked_preacceptance_exception_restores_the_transaction_snapshot(monkeypatch):
|
|
from api import routes
|
|
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
before = copy.deepcopy(session.__dict__)
|
|
|
|
def fail_prepare(*_args, **_kwargs):
|
|
raise RuntimeError("prepare failed")
|
|
|
|
monkeypatch.setattr(routes, "_prepare_chat_start_session_for_stream", fail_prepare)
|
|
with pytest.raises(RuntimeError, match="prepare failed"):
|
|
routes._start_regeneration_stream_locked(
|
|
session,
|
|
turn=plan.turn,
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
normalized_model=False,
|
|
diag=None,
|
|
goal_related=False,
|
|
source="webui",
|
|
moa_config=None,
|
|
backend_is_gateway=False,
|
|
)
|
|
assert session.__dict__ == before
|
|
|
|
|
|
def test_locked_postacceptance_workspace_exception_does_not_restore_turn(monkeypatch):
|
|
from api import routes
|
|
import api.turn_journal as turn_journal
|
|
|
|
session = _session()
|
|
plan = plan_regeneration(session)
|
|
monkeypatch.setattr(routes, "register_session_writeback_owner", lambda *_args: None)
|
|
monkeypatch.setattr(routes, "clear_session_writeback_owner_if_owned", lambda *_args: None)
|
|
monkeypatch.setattr(routes, "register_stream_owner", lambda *_args: None)
|
|
monkeypatch.setattr(routes, "unregister_stream_owner", lambda *_args: None)
|
|
monkeypatch.setattr(routes, "create_stream_channel", lambda: queue.Queue())
|
|
monkeypatch.setattr(turn_journal, "append_turn_journal_event", lambda *_args, **_kwargs: {"turn_id": "turn-6611"})
|
|
monkeypatch.setattr(Session, "save", lambda *_args, **_kwargs: None)
|
|
monkeypatch.setattr(routes, "set_last_workspace", lambda *_args, **_kw: (_ for _ in ()).throw(RuntimeError("workspace failed")))
|
|
|
|
class FakeThread:
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
|
|
def start(self):
|
|
return None
|
|
|
|
def join(self, timeout=None):
|
|
return None
|
|
|
|
monkeypatch.setattr(routes.threading, "Thread", FakeThread)
|
|
before = copy.deepcopy(session.__dict__)
|
|
with pytest.raises(RuntimeError, match="workspace failed") as raised:
|
|
routes._start_regeneration_stream_locked(
|
|
session,
|
|
turn=plan.turn,
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
normalized_model=False,
|
|
diag=None,
|
|
goal_related=False,
|
|
source="webui",
|
|
moa_config=None,
|
|
backend_is_gateway=False,
|
|
)
|
|
assert raised.value._regeneration_accepted is True
|
|
assert session.active_stream_id is not None
|
|
assert session.__dict__ != before
|
|
|
|
|
|
def test_chat_start_losing_regeneration_preserves_locked_send_winner(monkeypatch, tmp_path):
|
|
from api import routes
|
|
from api import models as models_api
|
|
import api.runtime_adapter as runtime_adapter
|
|
|
|
session = _session()
|
|
session.session_id = "route-race-6611"
|
|
session.model_explicit_pick_signature = "before-regeneration"
|
|
revision = plan_regeneration(session).revision
|
|
monkeypatch.setattr(models_api, "SESSION_DIR", tmp_path)
|
|
monkeypatch.setattr(models_api, "SESSION_INDEX_FILE", tmp_path / "_index.json")
|
|
monkeypatch.setattr(runtime_adapter, "runtime_adapter_runner_enabled", lambda: False)
|
|
monkeypatch.setattr(routes, "_get_or_materialize_session", lambda *_args, **_kwargs: session)
|
|
monkeypatch.setattr(routes, "_agent_runtime_barrier_response", lambda **_kwargs: None)
|
|
monkeypatch.setattr(routes, "_session_visible_to_active_profile", lambda *_args: True)
|
|
monkeypatch.setattr(routes, "_get_active_profile_name", lambda: "default")
|
|
monkeypatch.setattr(routes, "_read_profile_model_config", lambda *_args: (None, None, {}))
|
|
monkeypatch.setattr(routes, "_resolve_chat_workspace_for_regeneration", lambda *_args: "C:/workspace")
|
|
monkeypatch.setattr(routes, "get_config_snapshot", lambda: {})
|
|
monkeypatch.setattr(routes, "webui_gateway_chat_enabled", lambda *_args: False)
|
|
monkeypatch.setattr(routes, "_resolve_compatible_session_model_state", lambda *_args, **_kwargs: ("model", "provider", False))
|
|
monkeypatch.setattr(routes, "_repair_foreign_session_model_provider", lambda *_args, **_kwargs: "provider")
|
|
monkeypatch.setattr(routes, "compression_recovery_payload_for_session", lambda *_args: None)
|
|
|
|
winner_started = threading.Event()
|
|
winner_finished = threading.Event()
|
|
|
|
def lose_after_locked_send(*_args, **_kwargs):
|
|
def normal_send_winner():
|
|
with routes._get_session_agent_lock(session.session_id):
|
|
session.active_stream_id = "winner-stream"
|
|
session.pending_user_message = "winner prompt"
|
|
session.pending_started_at = 222.0
|
|
session.pending_user_source = "webui"
|
|
session.save(touch_updated_at=False)
|
|
winner_started.set()
|
|
winner_finished.set()
|
|
|
|
thread = threading.Thread(target=normal_send_winner)
|
|
thread.start()
|
|
assert winner_started.wait(1)
|
|
assert winner_finished.wait(1)
|
|
thread.join(timeout=1)
|
|
return {
|
|
"error": "stale regeneration revision",
|
|
"code": "stale_regeneration_revision",
|
|
"_status": 409,
|
|
}
|
|
|
|
captured = {}
|
|
monkeypatch.setattr(routes, "_start_run", lose_after_locked_send)
|
|
monkeypatch.setattr(
|
|
routes,
|
|
"j",
|
|
lambda _handler, data, status=200, **_kwargs: captured.update(data=data, status=status),
|
|
)
|
|
routes._handle_chat_start(
|
|
None,
|
|
{
|
|
"session_id": session.session_id,
|
|
"regenerate": True,
|
|
"regeneration_revision": revision,
|
|
"explicit_model_pick": True,
|
|
},
|
|
)
|
|
assert captured["status"] == 409
|
|
assert (
|
|
session.active_stream_id,
|
|
session.pending_user_message,
|
|
session.pending_started_at,
|
|
session.pending_user_source,
|
|
) == ("winner-stream", "winner prompt", 222.0, "webui")
|
|
assert session.model_explicit_pick_signature == "before-regeneration"
|
|
reloaded = Session.load(session.session_id)
|
|
assert (
|
|
reloaded.active_stream_id,
|
|
reloaded.pending_user_message,
|
|
reloaded.pending_started_at,
|
|
reloaded.pending_user_source,
|
|
) == ("winner-stream", "winner prompt", 222.0, "webui")
|
|
|
|
|
|
def test_regeneration_lock_winner_blocks_a_later_normal_send(monkeypatch):
|
|
from api import routes
|
|
|
|
session = _session()
|
|
accepted = threading.Event()
|
|
normal_checked = threading.Event()
|
|
normal_result = {}
|
|
|
|
monkeypatch.setattr(routes, "_agent_runtime_barrier_response", lambda **_kwargs: None)
|
|
monkeypatch.setattr(routes, "_active_run_stream_for_session", lambda *_args: None)
|
|
|
|
def accepted_regeneration(value, **_kwargs):
|
|
value.active_stream_id = "accepted-regeneration"
|
|
value.pending_user_message = "regenerated prompt"
|
|
accepted.set()
|
|
|
|
def ordinary_send_observer():
|
|
with routes._get_session_agent_lock(value.session_id):
|
|
normal_result["active_stream_id"] = value.active_stream_id
|
|
normal_result["pending_user_message"] = value.pending_user_message
|
|
normal_checked.set()
|
|
|
|
thread = threading.Thread(target=ordinary_send_observer)
|
|
thread.start()
|
|
assert not normal_checked.wait(0.05)
|
|
return {"stream_id": "accepted-regeneration", "session_id": value.session_id}
|
|
|
|
monkeypatch.setattr(routes, "_start_regeneration_stream_locked", accepted_regeneration)
|
|
result = routes._start_chat_stream_for_session(
|
|
session,
|
|
msg="ignored",
|
|
attachments=[],
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
external_runtime_owned=False,
|
|
regeneration=SimpleNamespace(revision="revision"),
|
|
)
|
|
|
|
assert accepted.is_set()
|
|
assert result["stream_id"] == "accepted-regeneration"
|
|
assert normal_checked.wait(1)
|
|
assert normal_result == {
|
|
"active_stream_id": "accepted-regeneration",
|
|
"pending_user_message": "regenerated prompt",
|
|
}
|
|
|
|
|
|
def test_prepare_mirrors_active_turn_token_to_context_before_timestamp_mutation(monkeypatch):
|
|
from api import routes
|
|
|
|
session = _session()
|
|
retained_user = session.messages[0]
|
|
session.context_messages = [
|
|
{"role": "user", "content": "prompt"},
|
|
{"role": "user", "content": "prompt"},
|
|
]
|
|
retained_context_user = session.context_messages[1]
|
|
monkeypatch.setattr(routes, "register_session_writeback_owner", lambda *_args: None)
|
|
routes._prepare_chat_start_session_for_stream(
|
|
session,
|
|
msg="prompt",
|
|
attachments=[],
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
stream_id="token-parity-stream",
|
|
started_at=99.0,
|
|
retained_user=retained_user,
|
|
retained_context_user=retained_context_user,
|
|
defer_save=True,
|
|
)
|
|
assert retained_user["timestamp"] == 99.0
|
|
assert "timestamp" not in session.context_messages[0]
|
|
assert "_active_turn_token" not in session.context_messages[0]
|
|
assert retained_context_user["timestamp"] == 99.0
|
|
assert retained_context_user["_active_turn_token"] == retained_user["_active_turn_token"]
|
|
|
|
|
|
def test_prepare_marks_new_fork_turn_in_eager_materialization(monkeypatch):
|
|
from api import routes
|
|
from api.streaming import _materialize_active_turn_user
|
|
|
|
session = Session(
|
|
session_id="fork-prepare-6611",
|
|
messages=[],
|
|
context_messages=[],
|
|
session_source="fork",
|
|
parent_session_id="parent-6611",
|
|
)
|
|
monkeypatch.setattr(routes, "register_session_writeback_owner", lambda *_args: None)
|
|
monkeypatch.setattr(routes, "get_webui_session_save_mode", lambda: "eager")
|
|
routes._prepare_chat_start_session_for_stream(
|
|
session,
|
|
msg="new fork prompt",
|
|
attachments=[],
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
stream_id="fork-prepare-stream",
|
|
started_at=99.0,
|
|
defer_save=True,
|
|
)
|
|
assert len(session.messages) == 1
|
|
assert session.messages[0]["_fork_child_turn"] == session.session_id
|
|
|
|
deferred = Session(
|
|
session_id="fork-deferred-6611",
|
|
messages=[],
|
|
context_messages=[],
|
|
session_source="fork",
|
|
parent_session_id="parent-6611",
|
|
)
|
|
routes._prepare_chat_start_session_for_stream(
|
|
deferred,
|
|
msg="deferred fork prompt",
|
|
attachments=[],
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
stream_id="fork-deferred-stream",
|
|
started_at=100.0,
|
|
defer_save=True,
|
|
)
|
|
materialized = _materialize_active_turn_user(
|
|
{
|
|
"text": deferred.pending_user_message,
|
|
"source": deferred.pending_user_source,
|
|
"timestamp": deferred.pending_started_at,
|
|
"session_id": deferred.session_id,
|
|
},
|
|
deferred.pending_user_message,
|
|
deferred.pending_user_source,
|
|
)
|
|
assert materialized["_fork_child_turn"] == deferred.session_id
|
|
|
|
|
|
def test_issue_artifact_rows_follow_production_regeneration_and_error_settlement(monkeypatch, tmp_path):
|
|
"""Exercise production plan/apply/prepare/materializer/save-reload helpers, not HTTP integration."""
|
|
from api import routes
|
|
from api import models as models_api
|
|
from api.session_ops import apply_regeneration_plan, plan_regeneration
|
|
from api.streaming import _materialize_pending_user_turn_before_error
|
|
|
|
rows = load_issue6611_fixture()["rows"]
|
|
session = Session(
|
|
session_id="artifact-production-6611",
|
|
messages=copy.deepcopy(rows),
|
|
context_messages=copy.deepcopy(rows),
|
|
workspace="C:/workspace",
|
|
)
|
|
plan = plan_regeneration(session)
|
|
assert apply_regeneration_plan(session, plan)
|
|
monkeypatch.setattr(routes, "register_session_writeback_owner", lambda *_args: None)
|
|
routes._prepare_chat_start_session_for_stream(
|
|
session,
|
|
msg=rows[0]["content"],
|
|
attachments=[],
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
stream_id="artifact-production-stream",
|
|
started_at=99.0,
|
|
retained_user=session.messages[-1],
|
|
defer_save=True,
|
|
)
|
|
assert [row["role"] for row in session.messages].count("user") == 1
|
|
assert session.messages[0]["content"] == rows[0]["content"]
|
|
assert _materialize_pending_user_turn_before_error(session) is False
|
|
session.active_stream_id = None
|
|
session.pending_user_message = None
|
|
session.pending_attachments = []
|
|
session.pending_started_at = None
|
|
session.pending_user_source = None
|
|
session.messages.append(copy.deepcopy(rows[1]))
|
|
assert [row["role"] for row in session.messages] == ["user", "assistant"]
|
|
assert [row["content"] for row in session.messages if row["role"] == "user"] == [rows[0]["content"]]
|
|
monkeypatch.setattr(models_api, "SESSION_DIR", tmp_path)
|
|
monkeypatch.setattr(models_api, "SESSION_INDEX_FILE", tmp_path / "_index.json")
|
|
session.save(touch_updated_at=False)
|
|
reloaded = Session.load(session.session_id)
|
|
assert reloaded is not None
|
|
assert [row["role"] for row in reloaded.messages] == ["user", "assistant"]
|
|
assert [row["content"] for row in reloaded.messages if row["role"] == "user"] == [rows[0]["content"]]
|
|
|
|
|
|
def test_locked_start_always_replans_after_browser_validation():
|
|
source = (Path(__file__).parents[1] / "api" / "routes.py").read_text(encoding="utf-8")
|
|
start = source.index("def _start_regeneration_stream_locked(")
|
|
end = source.index("def _active_run_stream_for_session", start)
|
|
body = source[start:end]
|
|
assert "plan = plan_regeneration(" in body
|
|
assert "expected_revision=turn.revision" in body
|
|
assert "lock_held=True" in body
|
|
|
|
|
|
def test_regeneration_preview_has_no_request_snapshot_or_outer_restore_owner():
|
|
source = (Path(__file__).parents[1] / "api" / "routes.py").read_text(encoding="utf-8")
|
|
chat_start = source.index("def _handle_chat_start(")
|
|
chat_end = source.index("def _resolve_chat_workspace_with_recovery", chat_start)
|
|
body = source[chat_start:chat_end]
|
|
assert "regeneration_snapshot = snapshot_regeneration_state" not in body
|
|
assert "transaction_snapshot" not in body
|
|
assert "_restore_regeneration_preacceptance" not in body
|
|
regeneration_preview = body[body.index('if body.get("regenerate") is True:'):body.index('diag.stage("normalize_message")')]
|
|
assert "_clear_stale_stream_state" not in regeneration_preview
|
|
assert "model_explicit_pick_signature" not in regeneration_preview
|
|
|
|
|
|
def test_concurrent_normal_winner_survives_regeneration_409_in_memory_and_after_reload(monkeypatch, tmp_path):
|
|
from api import routes
|
|
from api import models as models_api
|
|
|
|
session = _session()
|
|
session.session_id = "race-6611"
|
|
session.active_stream_id = "stale-stream"
|
|
session.pending_user_message = "stale prompt"
|
|
session.pending_started_at = 111.0
|
|
session.pending_user_source = "webui"
|
|
monkeypatch.setattr(models_api, "SESSION_DIR", tmp_path)
|
|
monkeypatch.setattr(models_api, "SESSION_INDEX_FILE", tmp_path / "_index.json")
|
|
session.save(touch_updated_at=False)
|
|
|
|
winner_started = threading.Event()
|
|
winner_finished = threading.Event()
|
|
|
|
def stale_plan(*_args, **_kwargs):
|
|
def normal_winner():
|
|
winner_started.set()
|
|
session.active_stream_id = "winner-stream"
|
|
session.pending_user_message = "winner prompt"
|
|
session.pending_started_at = 222.0
|
|
session.pending_user_source = "webui"
|
|
session.save(touch_updated_at=False)
|
|
winner_finished.set()
|
|
|
|
threading.Thread(target=normal_winner).start()
|
|
assert winner_started.wait(1)
|
|
assert winner_finished.wait(1)
|
|
raise RegenerationUnavailable("stale_regeneration_revision")
|
|
|
|
monkeypatch.setattr("api.session_ops.plan_regeneration", stale_plan)
|
|
result = routes._start_regeneration_stream_locked(
|
|
session,
|
|
turn=SimpleNamespace(revision="old-revision"),
|
|
workspace="C:/workspace",
|
|
model="model",
|
|
model_provider="provider",
|
|
normalized_model=False,
|
|
diag=None,
|
|
goal_related=False,
|
|
source="webui",
|
|
moa_config=None,
|
|
backend_is_gateway=False,
|
|
)
|
|
assert result["_status"] == 409
|
|
assert (session.active_stream_id, session.pending_user_message, session.pending_started_at, session.pending_user_source) == (
|
|
"winner-stream", "winner prompt", 222.0, "webui"
|
|
)
|
|
reloaded = Session.load(session.session_id)
|
|
assert (reloaded.active_stream_id, reloaded.pending_user_message, reloaded.pending_started_at, reloaded.pending_user_source) == (
|
|
"winner-stream", "winner prompt", 222.0, "webui"
|
|
)
|