fix(messaging): reuse parent CLI session for Telegram reply continuation (#233)
Browse filesPass parent_session_id into get_or_create_session so reply nodes align with
the fork/resume path instead of always allocating a fresh pending session.
Add unit coverage and update integration expectations.
messaging/handler.py
CHANGED
|
@@ -370,7 +370,9 @@ class ClaudeMessageHandler:
|
|
| 370 |
cli_session,
|
| 371 |
session_or_temp_id,
|
| 372 |
is_new,
|
| 373 |
-
) = await self.cli_manager.get_or_create_session(
|
|
|
|
|
|
|
| 374 |
if is_new:
|
| 375 |
temp_session_id = session_or_temp_id
|
| 376 |
else:
|
|
|
|
| 370 |
cli_session,
|
| 371 |
session_or_temp_id,
|
| 372 |
is_new,
|
| 373 |
+
) = await self.cli_manager.get_or_create_session(
|
| 374 |
+
session_id=parent_session_id
|
| 375 |
+
)
|
| 376 |
if is_new:
|
| 377 |
temp_session_id = session_or_temp_id
|
| 378 |
else:
|
tests/messaging/test_handler.py
CHANGED
|
@@ -419,6 +419,54 @@ async def test_process_node_success_flow(handler, mock_cli_manager, mock_platfor
|
|
| 419 |
assert "✅ *Complete*" in last_call[0][2]
|
| 420 |
assert "Hello world" in last_call[0][2]
|
| 421 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 422 |
|
| 423 |
@pytest.mark.asyncio
|
| 424 |
async def test_process_node_error_flow(handler, mock_cli_manager, mock_platform):
|
|
|
|
| 419 |
assert "✅ *Complete*" in last_call[0][2]
|
| 420 |
assert "Hello world" in last_call[0][2]
|
| 421 |
|
| 422 |
+
mock_cli_manager.get_or_create_session.assert_awaited_once_with(session_id=None)
|
| 423 |
+
mock_session.start_task.assert_called_once()
|
| 424 |
+
st_kw = mock_session.start_task.call_args
|
| 425 |
+
assert st_kw.kwargs.get("session_id") is None
|
| 426 |
+
assert st_kw.kwargs.get("fork_session") is False
|
| 427 |
+
|
| 428 |
+
|
| 429 |
+
@pytest.mark.asyncio
|
| 430 |
+
async def test_process_node_reply_uses_parent_session_for_manager_and_fork(
|
| 431 |
+
handler, mock_cli_manager, mock_platform
|
| 432 |
+
):
|
| 433 |
+
"""Telegram follow-ups must reuse parent Claude session (issue #233)."""
|
| 434 |
+
node_id = "child_1"
|
| 435 |
+
mock_node = MagicMock()
|
| 436 |
+
mock_node.incoming.chat_id = "chat_1"
|
| 437 |
+
mock_node.incoming.text = "follow up"
|
| 438 |
+
mock_node.status_message_id = "status_child"
|
| 439 |
+
mock_node.parent_id = "root_msg"
|
| 440 |
+
|
| 441 |
+
parent_claude_session = "claude_sess_parent"
|
| 442 |
+
mock_session = MagicMock()
|
| 443 |
+
mock_session.start_task.return_value = mock_async_gen([{"type": "exit", "code": 0}])
|
| 444 |
+
mock_cli_manager.get_or_create_session.return_value = (
|
| 445 |
+
mock_session,
|
| 446 |
+
parent_claude_session,
|
| 447 |
+
False,
|
| 448 |
+
)
|
| 449 |
+
|
| 450 |
+
mock_tree = MagicMock()
|
| 451 |
+
mock_tree.update_state = AsyncMock()
|
| 452 |
+
mock_tree.root_id = "root_msg"
|
| 453 |
+
mock_tree.to_dict.return_value = {}
|
| 454 |
+
mock_tree.get_parent_session_id = MagicMock(return_value=parent_claude_session)
|
| 455 |
+
|
| 456 |
+
with patch.object(
|
| 457 |
+
handler.tree_queue, "get_tree_for_node", MagicMock(return_value=mock_tree)
|
| 458 |
+
):
|
| 459 |
+
await handler._process_node(node_id, mock_node)
|
| 460 |
+
|
| 461 |
+
mock_tree.get_parent_session_id.assert_called_once_with(node_id)
|
| 462 |
+
mock_cli_manager.get_or_create_session.assert_awaited_once_with(
|
| 463 |
+
session_id=parent_claude_session
|
| 464 |
+
)
|
| 465 |
+
mock_session.start_task.assert_called_once()
|
| 466 |
+
st_kw = mock_session.start_task.call_args
|
| 467 |
+
assert st_kw.kwargs.get("session_id") == parent_claude_session
|
| 468 |
+
assert st_kw.kwargs.get("fork_session") is True
|
| 469 |
+
|
| 470 |
|
| 471 |
@pytest.mark.asyncio
|
| 472 |
async def test_process_node_error_flow(handler, mock_cli_manager, mock_platform):
|
tests/messaging/test_handler_integration.py
CHANGED
|
@@ -95,7 +95,7 @@ async def test_full_conversation_flow_single_user(
|
|
| 95 |
|
| 96 |
assert tree.get_node("m2").state.value == MessageState.COMPLETED.value
|
| 97 |
assert tree.get_node("m2").parent_id == "m1"
|
| 98 |
-
mock_cli_manager.get_or_create_session.assert_called_with(session_id=
|
| 99 |
mock_session2.start_task.assert_called_with(
|
| 100 |
"message 2", session_id="sess1", fork_session=True
|
| 101 |
)
|
|
|
|
| 95 |
|
| 96 |
assert tree.get_node("m2").state.value == MessageState.COMPLETED.value
|
| 97 |
assert tree.get_node("m2").parent_id == "m1"
|
| 98 |
+
mock_cli_manager.get_or_create_session.assert_called_with(session_id="sess1")
|
| 99 |
mock_session2.start_task.assert_called_with(
|
| 100 |
"message 2", session_id="sess1", fork_session=True
|
| 101 |
)
|