diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index 34d59bc5a..2441c20c5 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -1287,9 +1287,7 @@ class FeishuChannel(BaseChannel): # next segment appends to the same card. buf = self._stream_bufs.get(chat_id) if buf and buf.card_id and buf.text: - if buf.tool_hint_len > 0: - buf.text = buf.text[:-buf.tool_hint_len] - buf.tool_hint_len = 0 + buf.tool_hint_len = 0 buf.sequence += 1 await loop.run_in_executor( None, self._stream_update_text_sync, buf.card_id, buf.text, buf.sequence, @@ -1299,9 +1297,7 @@ class FeishuChannel(BaseChannel): buf = self._stream_bufs.pop(chat_id, None) if not buf or not buf.text: return - if buf.tool_hint_len > 0: - buf.text = buf.text[:-buf.tool_hint_len] - buf.tool_hint_len = 0 + buf.tool_hint_len = 0 if buf.card_id: buf.sequence += 1 await loop.run_in_executor( @@ -1338,7 +1334,6 @@ class FeishuChannel(BaseChannel): buf = _FeishuStreamBuf() self._stream_bufs[chat_id] = buf if buf.tool_hint_len > 0: - buf.text = buf.text[:-buf.tool_hint_len] buf.tool_hint_len = 0 buf.text += delta if not buf.text.strip(): diff --git a/tests/channels/test_feishu_streaming.py b/tests/channels/test_feishu_streaming.py index 3683d0f07..1559ef8d3 100644 --- a/tests/channels/test_feishu_streaming.py +++ b/tests/channels/test_feishu_streaming.py @@ -316,8 +316,8 @@ class TestToolHintInlineStreaming: ch._client.im.v1.message.create.assert_not_called() @pytest.mark.asyncio - async def test_tool_hint_stripped_on_next_delta(self): - """When new delta arrives, the previously appended tool hint is removed.""" + async def test_tool_hint_preserved_on_next_delta(self): + """When new delta arrives, the tool hint is kept as permanent content and delta appends after it.""" ch = _make_channel() suffix = "\n\n---\nšŸ”§ web_fetch(\"url\")" ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf( @@ -330,7 +330,9 @@ class TestToolHintInlineStreaming: await ch.send_delta("oc_chat1", " continued") buf = ch._stream_bufs["oc_chat1"] - assert buf.text == "Partial answer continued" + assert "Partial answer" in buf.text + assert "šŸ”§ web_fetch" in buf.text + assert buf.text.endswith(" continued") assert buf.tool_hint_len == 0 @pytest.mark.asyncio @@ -377,8 +379,8 @@ class TestToolHintInlineStreaming: assert buf.text.endswith("šŸ”§ $ git status") @pytest.mark.asyncio - async def test_tool_hint_stripped_on_resuming_flush(self): - """When _resuming flushes the buffer, tool hint suffix is cleaned.""" + async def test_tool_hint_preserved_on_resuming_flush(self): + """When _resuming flushes the buffer, tool hint is kept as permanent content.""" ch = _make_channel() suffix = "\n\n---\nšŸ”§ $ cd /project" ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf( @@ -391,12 +393,13 @@ class TestToolHintInlineStreaming: await ch.send_delta("oc_chat1", "", metadata={"_stream_end": True, "_resuming": True}) buf = ch._stream_bufs["oc_chat1"] - assert buf.text == "Partial answer" + assert "Partial answer" in buf.text + assert "šŸ”§ $ cd /project" in buf.text assert buf.tool_hint_len == 0 @pytest.mark.asyncio - async def test_tool_hint_stripped_on_final_stream_end(self): - """When final _stream_end closes the card, tool hint suffix is cleaned from text.""" + async def test_tool_hint_preserved_on_final_stream_end(self): + """When final _stream_end closes the card, tool hint is kept in the final text.""" ch = _make_channel() suffix = "\n\n---\nšŸ”§ web_fetch(\"url\")" ch._stream_bufs["oc_chat1"] = _FeishuStreamBuf( @@ -411,7 +414,7 @@ class TestToolHintInlineStreaming: assert "oc_chat1" not in ch._stream_bufs update_call = ch._client.cardkit.v1.card_element.content.call_args[0][0] - assert "šŸ”§" not in update_call.body.content + assert "šŸ”§" in update_call.body.content class TestSendMessageReturnsId: