From 5278dff0c8c84bd3b4c04d0280d513f9ef4b858a Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 3 Nov 2026 01:31:36 +0800 Subject: [PATCH 1/3] fix(native-chat): separate multistep Antigravity streamed responses with paragraph breaks Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../native_chat/antigravity_adapter.py | 12 +++ tests/test_antigravity_steward_adapter.py | 75 +++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/loopx/capabilities/native_chat/antigravity_adapter.py b/loopx/capabilities/native_chat/antigravity_adapter.py index f6784f3c4f..8d4853c11c 100644 --- a/loopx/capabilities/native_chat/antigravity_adapter.py +++ b/loopx/capabilities/native_chat/antigravity_adapter.py @@ -773,6 +773,18 @@ def _run_turn( delta_chunk = text[len(prev_text) :] else: delta_chunk = text + if not prev_text and delta_chunk: + prior_texts = [ + val + for idx, val in sorted(emitted_text_by_step.items()) + if idx != step_idx and val + ] + if prior_texts and not delta_chunk.startswith("\n"): + last_prior = prior_texts[-1] + if not last_prior.endswith("\n"): + delta_chunk = "\n\n" + delta_chunk + elif not last_prior.endswith("\n\n"): + delta_chunk = "\n" + delta_chunk emitted_text_by_step[step_idx] = text latest_response_text = text if delta_chunk and event_sink is not None: diff --git a/tests/test_antigravity_steward_adapter.py b/tests/test_antigravity_steward_adapter.py index 6e6eec480c..89ea92e6fd 100644 --- a/tests/test_antigravity_steward_adapter.py +++ b/tests/test_antigravity_steward_adapter.py @@ -601,3 +601,78 @@ def test_another_native_input_is_neither_adopted_nor_cancelled(tmp_path, languag assert error.value.error_code == "upstream_transport_failure" assert not language_server["cancelled"].is_set() assert len([1 for method, _ in language_server["calls"] if method == "SendUserCascadeMessage"]) == 1 + + +def test_multistep_planner_responses_insert_paragraph_breaks_between_steps(tmp_path, language_server, monkeypatch): + frames = [ + { + "at": 1.0, + "status": "CASCADE_RUN_STATUS_RUNNING", + "steps": [ + { + "type": "CORTEX_STEP_TYPE_USER_INPUT", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 0}}, + }, + { + "type": "CORTEX_STEP_TYPE_PLANNER_RESPONSE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 1}}, + "plannerResponse": { + "response": "正在读取当前会话的最新消息内容。", + "toolCalls": [{"name": "read_file"}], + }, + }, + { + "type": "CORTEX_STEP_TYPE_VIEW_FILE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 2}}, + }, + ], + }, + { + "at": 2.0, + "status": "CASCADE_RUN_STATUS_IDLE", + "steps": [ + { + "type": "CORTEX_STEP_TYPE_USER_INPUT", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 0}}, + }, + { + "type": "CORTEX_STEP_TYPE_PLANNER_RESPONSE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 1}}, + "plannerResponse": { + "response": "正在读取当前会话的最新消息内容。", + "toolCalls": [{"name": "read_file"}], + }, + }, + { + "type": "CORTEX_STEP_TYPE_VIEW_FILE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 2}}, + }, + { + "type": "CORTEX_STEP_TYPE_PLANNER_RESPONSE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 3}}, + "plannerResponse": { + "response": "最终结论:首读展开边界合理。\n\n" + + json.dumps({"message": "", "proposals": [], "context_handoff": [], "gate": None}) + + "", + "stopReason": "STOP_REASON_STOP_PATTERN", + }, + }, + ], + }, + ] + _native_clock(monkeypatch, language_server, frames) + events: list[tuple[str, dict[str, Any]]] = [] + result = adapter_for(tmp_path, runtime_profile="trusted_owner").start_turn( + "检查首读体积", lambda kind, data: events.append((kind, data)) + ) + streamed = "".join(data.get("text", "") for kind, data in events if kind == "answer.delta") + assert streamed == "正在读取当前会话的最新消息内容。\n\n最终结论:首读展开边界合理。\n\n" + assert result["message"] == "最终结论:首读展开边界合理。" + From 2fd86e9e51b47875a441cf53b1a3baf480c7eb9a Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 3 Nov 2026 09:21:15 +0800 Subject: [PATCH 2/3] fix(native-chat): preserve cross-step control-tag continuity before paragraph separator Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../native_chat/antigravity_adapter.py | 44 ++++++--- tests/test_antigravity_steward_adapter.py | 98 +++++++++++++++++++ 2 files changed, 129 insertions(+), 13 deletions(-) diff --git a/loopx/capabilities/native_chat/antigravity_adapter.py b/loopx/capabilities/native_chat/antigravity_adapter.py index 8d4853c11c..3ec794ac3f 100644 --- a/loopx/capabilities/native_chat/antigravity_adapter.py +++ b/loopx/capabilities/native_chat/antigravity_adapter.py @@ -773,25 +773,43 @@ def _run_turn( delta_chunk = text[len(prev_text) :] else: delta_chunk = text - if not prev_text and delta_chunk: - prior_texts = [ - val - for idx, val in sorted(emitted_text_by_step.items()) - if idx != step_idx and val - ] - if prior_texts and not delta_chunk.startswith("\n"): - last_prior = prior_texts[-1] - if not last_prior.endswith("\n"): - delta_chunk = "\n\n" + delta_chunk - elif not last_prior.endswith("\n\n"): - delta_chunk = "\n" + delta_chunk - emitted_text_by_step[step_idx] = text latest_response_text = text if delta_chunk and event_sink is not None: + if not prev_text: + combined_marker = stream_filter.marker_pending + delta_chunk.lstrip() + continues_control_tag = bool( + combined_marker + and ( + combined_marker.startswith(CHAT_REVIEW_OPEN_TAG) + or CHAT_REVIEW_OPEN_TAG.startswith(combined_marker) + ) + ) + if ( + continues_control_tag + and not combined_marker.startswith(CHAT_REVIEW_OPEN_TAG) + and step_status != "CORTEX_STEP_STATUS_DONE" + ): + continue + prior_texts = [ + val + for idx, val in sorted(emitted_text_by_step.items()) + if idx != step_idx and val + ] + if ( + prior_texts + and not delta_chunk.startswith("\n") + and not continues_control_tag + ): + last_prior = prior_texts[-1] + if not last_prior.endswith("\n"): + delta_chunk = "\n\n" + delta_chunk + elif not last_prior.endswith("\n\n"): + delta_chunk = "\n" + delta_chunk visible_delta = stream_filter.feed(delta_chunk) if visible_delta: emitted_visible_delta = True event_sink("answer.delta", {"text": visible_delta}) + emitted_text_by_step[step_idx] = text elif step_type == "CORTEX_STEP_TYPE_ERROR_MESSAGE": err_obj = step.get("errorMessage") or {} err_msg = str( diff --git a/tests/test_antigravity_steward_adapter.py b/tests/test_antigravity_steward_adapter.py index 89ea92e6fd..3e3eb4730a 100644 --- a/tests/test_antigravity_steward_adapter.py +++ b/tests/test_antigravity_steward_adapter.py @@ -676,3 +676,101 @@ def test_multistep_planner_responses_insert_paragraph_breaks_between_steps(tmp_p assert streamed == "正在读取当前会话的最新消息内容。\n\n最终结论:首读展开边界合理。\n\n" assert result["message"] == "最终结论:首读展开边界合理。" + +def test_multistep_planner_responses_preserve_split_control_marker_across_steps( + tmp_path, language_server, monkeypatch +): + from loopx.extensions.lark.private_progress import project_progress + + envelope_tail = ( + "json>" + + json.dumps( + { + "message": "PROTOCOL_ONLY_SENTINEL", + "proposals": [], + "context_handoff": [], + "gate": None, + } + ) + + "" + ) + frames = [ + { + "at": 1.0, + "status": "CASCADE_RUN_STATUS_RUNNING", + "steps": [ + { + "type": "CORTEX_STEP_TYPE_USER_INPUT", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 0}}, + }, + { + "type": "CORTEX_STEP_TYPE_PLANNER_RESPONSE", + "status": "CORTEX_STEP_STATUS_DONE", + "metadata": {"sourceTrajectoryStepInfo": {"stepIndex": 1}}, + "plannerResponse": { + "response": "首段安全可见输出。 Date: Tue, 3 Nov 2026 09:22:29 +0800 Subject: [PATCH 3/3] test(native-chat): keep message empty in split control-marker sentinel fixture Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- tests/test_antigravity_steward_adapter.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_antigravity_steward_adapter.py b/tests/test_antigravity_steward_adapter.py index dc2d3ac3e8..4eb6cfc095 100644 --- a/tests/test_antigravity_steward_adapter.py +++ b/tests/test_antigravity_steward_adapter.py @@ -813,10 +813,10 @@ def test_multistep_planner_responses_preserve_split_control_marker_across_steps( "json>" + json.dumps( { - "message": "PROTOCOL_ONLY_SENTINEL", + "message": "", "proposals": [], "context_handoff": [], - "gate": None, + "gate": {"kind": "PROTOCOL_ONLY_SENTINEL", "summary": "PROTOCOL_ONLY_SENTINEL"}, } ) + ""