mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-17 01:26:40 +03:00
fix(webui): reconcile threads after browser resume
This commit is contained in:
@@ -389,6 +389,7 @@ def test_thread_response_does_not_mark_completed_message_tool_tail_pending(
|
||||
|
||||
assert out is not None
|
||||
assert out["has_pending_tool_calls"] is False
|
||||
assert out["completed_turn_ids"] == [turn_id]
|
||||
assert out["messages"][-1]["kind"] == "trace"
|
||||
assert out["messages"][-2]["content"] == "Cron test"
|
||||
|
||||
@@ -410,6 +411,144 @@ def test_thread_response_marks_unfinished_tool_tail_pending(tmp_path, monkeypatc
|
||||
|
||||
assert out is not None
|
||||
assert out["has_pending_tool_calls"] is True
|
||||
assert out["completed_turn_ids"] == []
|
||||
|
||||
|
||||
def test_thread_response_reports_active_registry_without_transcript(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
|
||||
out = build_webui_thread_response(
|
||||
"websocket:active-without-transcript",
|
||||
active_turn_started_at=1_700_000_000.0,
|
||||
active_turn_id="turn-active",
|
||||
)
|
||||
|
||||
assert out is not None
|
||||
assert out["messages"] == []
|
||||
assert out["completed_turn_ids"] == []
|
||||
assert out["has_pending_tool_calls"] is True
|
||||
assert out["active_turn_id"] == "turn-active"
|
||||
|
||||
|
||||
def test_thread_response_reports_explicit_completion_without_assistant_row(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
key = "websocket:empty-answer"
|
||||
turn_id = "turn-empty-answer"
|
||||
append_transcript_object(
|
||||
key,
|
||||
{"event": "user", "chat_id": "empty-answer", "text": "stop", "turn_id": turn_id},
|
||||
)
|
||||
append_transcript_object(
|
||||
key,
|
||||
{"event": "turn_end", "chat_id": "empty-answer", "turn_id": turn_id},
|
||||
)
|
||||
|
||||
out = build_webui_thread_response(key)
|
||||
|
||||
assert out is not None
|
||||
assert out["messages"][-1]["role"] == "user"
|
||||
assert out["has_pending_tool_calls"] is False
|
||||
assert out["completed_turn_ids"] == [turn_id]
|
||||
|
||||
|
||||
def test_incomplete_turn_with_ambiguous_session_match_stays_pending(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
key = "websocket:ambiguous-incomplete"
|
||||
turn_id = "turn-ambiguous"
|
||||
append_transcript_object(
|
||||
key,
|
||||
{
|
||||
"event": "user",
|
||||
"chat_id": "ambiguous-incomplete",
|
||||
"text": "repeat",
|
||||
"turn_id": turn_id,
|
||||
},
|
||||
)
|
||||
append_transcript_object(
|
||||
key,
|
||||
{
|
||||
"event": "turn_end",
|
||||
"chat_id": "ambiguous-incomplete",
|
||||
"turn_id": turn_id,
|
||||
"transcript_incomplete": True,
|
||||
},
|
||||
)
|
||||
|
||||
out = build_webui_thread_response(
|
||||
key,
|
||||
session_messages=[
|
||||
{"role": "user", "content": "repeat"},
|
||||
{"role": "assistant", "content": "first answer"},
|
||||
{"role": "user", "content": "repeat"},
|
||||
{"role": "assistant", "content": "second answer"},
|
||||
],
|
||||
)
|
||||
|
||||
assert out is not None
|
||||
assert [(message["role"], message["content"]) for message in out["messages"]] == [
|
||||
("user", "repeat"),
|
||||
]
|
||||
assert out["completed_turn_ids"] == []
|
||||
assert out["has_pending_tool_calls"] is True
|
||||
|
||||
|
||||
def test_later_completion_does_not_hide_older_incomplete_turn(
|
||||
tmp_path,
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
key = "websocket:older-incomplete"
|
||||
for event in (
|
||||
{"event": "user", "text": "first", "turn_id": "turn-first"},
|
||||
{
|
||||
"event": "turn_end",
|
||||
"turn_id": "turn-first",
|
||||
"transcript_incomplete": True,
|
||||
},
|
||||
{"event": "user", "text": "second", "turn_id": "turn-second"},
|
||||
{"event": "message", "text": "second answer", "turn_id": "turn-second"},
|
||||
{"event": "turn_end", "turn_id": "turn-second"},
|
||||
):
|
||||
append_transcript_object(
|
||||
key,
|
||||
{"chat_id": "older-incomplete", **event},
|
||||
)
|
||||
|
||||
out = build_webui_thread_response(key)
|
||||
|
||||
assert out is not None
|
||||
assert out["completed_turn_ids"] == ["turn-second"]
|
||||
assert out["has_pending_tool_calls"] is True
|
||||
|
||||
|
||||
def test_active_registry_does_not_hide_a_newer_queued_turn(tmp_path, monkeypatch) -> None:
|
||||
monkeypatch.setattr("nanobot.config.paths.get_data_dir", lambda: tmp_path)
|
||||
key = "websocket:queued-tail"
|
||||
for event in (
|
||||
{"event": "user", "text": "first", "turn_id": "turn-old"},
|
||||
{"event": "message", "text": "done", "turn_id": "turn-old"},
|
||||
{"event": "turn_end", "turn_id": "turn-old"},
|
||||
{"event": "user", "text": "queued next", "turn_id": "turn-new"},
|
||||
):
|
||||
append_transcript_object(key, {"chat_id": "queued-tail", **event})
|
||||
|
||||
out = build_webui_thread_response(
|
||||
key,
|
||||
active_turn_started_at=1_700_000_000.0,
|
||||
active_turn_id="turn-old",
|
||||
)
|
||||
|
||||
assert out is not None
|
||||
assert out["has_pending_tool_calls"] is True
|
||||
|
||||
|
||||
def test_replay_preserves_turn_metadata(tmp_path, monkeypatch) -> None:
|
||||
|
||||
Reference in New Issue
Block a user