mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-08-27 10:51:26 -04:00
fix(code): recognize in-flight trace messages (#5738)
`/trace` no longer claims a thread is empty while its first user turn is still finishing. --- The empty-thread check previously relied only on checkpoint state, which can lag behind the visible response until the turn exits. It now recognizes the locally active user message while preserving the existing checkpoint and fail-open behavior. Verified with focused `/trace` and conversation-message unit tests plus scoped Ruff and ty checks. Made by [Open SWE](https://openswe.vercel.app/agents/af6a9bd2-0d10-5856-afdc-b7cca575d418) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -16190,13 +16190,25 @@ class DeepAgentsApp(App):
|
||||
"""Check whether the current thread has at least one human message.
|
||||
|
||||
Returns:
|
||||
`True` if the conversation contains a `HumanMessage`, `False`
|
||||
`True` if the conversation contains a `HumanMessage` or a local user
|
||||
turn is still in flight (its worker has started), `False`
|
||||
otherwise. On transient errors (network, corrupt state) returns
|
||||
`True` so callers do not block or warn based on an unreliable
|
||||
empty-thread check.
|
||||
"""
|
||||
if not self._agent or not self._lc_thread_id:
|
||||
return False
|
||||
# `_agent_turn_started` (not just `_agent_running`) is required: a
|
||||
# message that failed to send (agent unavailable) leaves
|
||||
# `_active_user_message` mounted, and a later non-message operation
|
||||
# such as goal-criteria generation also sets `_agent_running`. Only a
|
||||
# turn whose worker actually started means the prompt is in flight.
|
||||
if (
|
||||
self._agent_running
|
||||
and self._agent_turn_started
|
||||
and self._active_user_message is not None
|
||||
):
|
||||
return True
|
||||
try:
|
||||
# Use the shared helper so the thread is registered first
|
||||
# (`aensure_thread`, remote agents only) in server mode — otherwise
|
||||
|
||||
@@ -24615,6 +24615,42 @@ class TestHasConversationMessages:
|
||||
|
||||
assert await app._has_conversation_messages() is False
|
||||
|
||||
async def test_returns_true_for_inflight_user_message(self) -> None:
|
||||
"""Should detect a submitted user turn before its checkpoint commits."""
|
||||
app = DeepAgentsApp()
|
||||
async with app.run_test():
|
||||
app._agent = AsyncMock()
|
||||
app._lc_thread_id = "t1"
|
||||
app._agent_running = True
|
||||
app._agent_turn_started = True
|
||||
app._active_user_message = UserMessage("hi")
|
||||
|
||||
assert await app._has_conversation_messages() is True
|
||||
|
||||
async def test_ignores_unsent_message_during_non_message_operation(self) -> None:
|
||||
"""A prompt retained from a failed send must not suppress the warning.
|
||||
|
||||
If the agent was unavailable when the user submitted, `_send_to_agent`
|
||||
reports the error but never clears `_active_user_message`. A later
|
||||
operation that reserves `_agent_running` (e.g. goal-criteria
|
||||
generation) must not make the empty thread look non-empty.
|
||||
"""
|
||||
app = DeepAgentsApp()
|
||||
async with app.run_test():
|
||||
state = MagicMock()
|
||||
state.values = {}
|
||||
agent = AsyncMock()
|
||||
agent.aget_state = AsyncMock(return_value=state)
|
||||
app._agent = agent
|
||||
app._lc_thread_id = "t1"
|
||||
# Busy from a non-message operation; the mounted prompt was never
|
||||
# sent, so its worker never started a turn.
|
||||
app._agent_running = True
|
||||
app._agent_turn_started = False
|
||||
app._active_user_message = UserMessage("hi")
|
||||
|
||||
assert await app._has_conversation_messages() is False
|
||||
|
||||
async def test_returns_false_when_only_system_messages(self) -> None:
|
||||
"""Should return False when messages list has no HumanMessage."""
|
||||
from langchain_core.messages import SystemMessage
|
||||
|
||||
Reference in New Issue
Block a user