diff --git a/offline-backend-20260512/backend/app/gateway/routers/intent.py b/offline-backend-20260512/backend/app/gateway/routers/intent.py index 8001d2e4..0835bb73 100644 --- a/offline-backend-20260512/backend/app/gateway/routers/intent.py +++ b/offline-backend-20260512/backend/app/gateway/routers/intent.py @@ -111,13 +111,26 @@ async def stream_intent(request: Request, payload: dict = Body(...)) -> Streamin model_name=model_name, thread_id=thread_id, new_message=message, - # Intent agent shouldn't dispatch, search, write files, or - # view images — its only tool of note is ask_clarification. + # Intent agent only ever needs ask_clarification. Block + # everything else so the model can't drift into solutioning + # (e.g. saving a design doc with write_file) — the model + # tends to ignore SOUL prose, hard exclusion is reliable. excluded_tools=[ "web_search", "present_files", "view_image", "agent_orchestration", + "bash", + "ls", + "read_file", + "write_file", + "str_replace", + "memory", + "hindsight_recall", + "hindsight_reflect", + "hindsight_retain", + "task", + "write_todos", ], skill_stop_names=[], ) @@ -163,14 +176,33 @@ async def stream_intent(request: Request, payload: dict = Body(...)) -> Streamin parts.append(question) content = "\n\n".join(parts) if parts else content + # Scan ALL AI messages for the [INTENT_READY] marker, not just + # dispatch_msg.content. The model often emits the summary in an + # earlier AI message and then keeps yapping (or doing extra + # tool calls) — the marker is the source of truth, wherever it + # appears in the run. summary: dict[str, Any] | None = None - match = _INTENT_READY_PATTERN.search(content) - if match: - try: - summary = json.loads(match.group(1)) - except json.JSONDecodeError as exc: - logger.warning("intent summary JSON parse failed: %s", exc) - summary = None + ready_text: str = "" + for msg in reversed(messages): + if not isinstance(msg, dict): + continue + if (msg.get("type") or "").lower() not in ("ai", "aimessage", "aimessagechunk"): + continue + msg_text = _flatten_content(msg.get("content", "")) + m = _INTENT_READY_PATTERN.search(msg_text) + if m: + try: + summary = json.loads(m.group(1)) + except json.JSONDecodeError as exc: + logger.warning("intent summary JSON parse failed: %s", exc) + summary = None + ready_text = msg_text + break + # When the marker was found in an earlier message, surface that + # message's text (which contains the user-facing 📋 汇总 + JSON) + # instead of the final filler AI text like "好的,收到". + if summary and ready_text: + content = ready_text status_value = "done" if summary else "asking" final_frame: dict[str, Any] = {