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"},
}
)
+ ""