diff --git a/backend/app/gateway/routers/artifacts.py b/backend/app/gateway/routers/artifacts.py index 78ea5fa000..9cd5d1f2f6 100644 --- a/backend/app/gateway/routers/artifacts.py +++ b/backend/app/gateway/routers/artifacts.py @@ -175,9 +175,15 @@ async def get_artifact(thread_id: str, path: str, request: Request, download: bo return FileResponse(path=actual_path, filename=actual_path.name, media_type=mime_type, headers=_build_attachment_headers(actual_path.name)) if mime_type and mime_type.startswith("text/"): - return PlainTextResponse(content=actual_path.read_text(encoding="utf-8"), media_type=mime_type) + try: + return PlainTextResponse(content=actual_path.read_text(encoding="utf-8"), media_type=mime_type) + except UnicodeDecodeError: + pass # Fall through to binary response if is_text_file_by_content(actual_path): - return PlainTextResponse(content=actual_path.read_text(encoding="utf-8"), media_type=mime_type) + try: + return PlainTextResponse(content=actual_path.read_text(encoding="utf-8"), media_type=mime_type) + except UnicodeDecodeError: + pass # Fall through to binary response return Response(content=actual_path.read_bytes(), media_type=mime_type, headers={"Content-Disposition": _build_content_disposition("inline", actual_path.name)}) diff --git a/backend/packages/harness/deerflow/sandbox/tools.py b/backend/packages/harness/deerflow/sandbox/tools.py index 32ee7d6468..3451f282d1 100644 --- a/backend/packages/harness/deerflow/sandbox/tools.py +++ b/backend/packages/harness/deerflow/sandbox/tools.py @@ -1569,6 +1569,9 @@ def str_replace_tool( if replace_all: content = content.replace(old_str, new_str) else: + count = content.count(old_str) + if count > 1: + return f"Error: The string to replace appears {count} times in {requested_path}. Use replace_all=True to replace all occurrences, or provide a more specific string that appears exactly once." content = content.replace(old_str, new_str, 1) sandbox.write_file(path, content) return "OK"