From 349ff0848177a8bf91c7688cf9e38abcf574122a Mon Sep 17 00:00:00 2001 From: swaylq Date: Tue, 16 Jun 2026 23:30:46 +0800 Subject: [PATCH] fix: stop type() action from eating trailing 'n'/'\' chars before newline `parsing_response_to_pyautogui_code` strips a trailing newline marker from typed content so Enter can be pressed separately. It did this with `stripped_content.rstrip("\\n").rstrip("\n")`, but `str.rstrip` treats its argument as a *set of characters*, so `rstrip("\\n")` also removes any trailing `n` and `\` characters that are part of the real content. After parsing, a real trailing newline is converted to the literal `\n` (via `action.replace("\n", "\\n")`), so this path is hit for ordinary typed text. As a result: "Login\n" -> types "Logi" (the real 'n' was eaten) "Run\n" -> types "Ru" "python\n" -> types "pytho" Replace the `rstrip` chain with explicit suffix removal that only strips actual newline markers (`\n` and the escaped `\\n`), preserving content characters. Behavior for stripping the trailing newline and pressing Enter is unchanged. Adds regression tests covering the over-strip cases and the no-newline case. Co-Authored-By: Claude Opus 4.8 --- codes/tests/action_parser_test.py | 34 +++++++++++++++++++++++++++++++ codes/ui_tars/action_parser.py | 11 ++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/codes/tests/action_parser_test.py b/codes/tests/action_parser_test.py index ca04cdc..25ad8e2 100644 --- a/codes/tests/action_parser_test.py +++ b/codes/tests/action_parser_test.py @@ -32,6 +32,40 @@ def test_parsing_response_to_pyautogui_code(self): code = parsing_response_to_pyautogui_code(responses, 224, 224) self.assertIn('pyautogui.hotkey', code) + def test_type_with_trailing_newline_preserves_content(self): + # After parsing, a real trailing newline becomes the literal "\\n". + # The trailing newline must be stripped (so Enter is pressed + # separately) WITHOUT eating real trailing "n"/"\\" characters from + # the typed text. Regression test for the rstrip("\\n") char-set bug + # that turned "Login\\n" into "Logi" and "Run\\n" into "Ru". + for raw, expected in [ + ("Login\\n", "Login"), + ("Run\\n", "Run"), + ("python\\n", "python"), + ("hello world\\n", "hello world"), + ]: + responses = { + "action_type": "type", + "action_inputs": {"content": raw}, + } + code = parsing_response_to_pyautogui_code( + responses, 1080, 1920, input_swap=False + ) + self.assertIn(f"pyautogui.write('{expected}'", code) + # A trailing newline still triggers a separate Enter press. + self.assertIn("pyautogui.press('enter')", code) + + def test_type_without_newline_is_unchanged(self): + responses = { + "action_type": "type", + "action_inputs": {"content": "Login"}, + } + code = parsing_response_to_pyautogui_code( + responses, 1080, 1920, input_swap=False + ) + self.assertIn("pyautogui.write('Login'", code) + self.assertNotIn("pyautogui.press('enter')", code) + if __name__ == '__main__': unittest.main() diff --git a/codes/ui_tars/action_parser.py b/codes/ui_tars/action_parser.py index 2b722f4..b29661f 100644 --- a/codes/ui_tars/action_parser.py +++ b/codes/ui_tars/action_parser.py @@ -405,8 +405,15 @@ def parsing_response_to_pyautogui_code(responses, content = action_inputs.get("content", "") content = escape_single_quotes(content) stripped_content = content - if content.endswith("\n") or content.endswith("\\n"): - stripped_content = stripped_content.rstrip("\\n").rstrip("\n") + # Strip the trailing newline marker(s) so Enter can be pressed + # separately. Use suffix removal rather than ``str.rstrip("\\n")``, + # because ``rstrip`` treats its argument as a set of characters and + # would also eat real trailing ``n``/``\`` characters from the + # content (e.g. "Login\n" -> "Logi", "Run\n" -> "Ru"). + while stripped_content.endswith("\\n"): + stripped_content = stripped_content[:-2] + while stripped_content.endswith("\n"): + stripped_content = stripped_content[:-1] if content: if input_swap: pyautogui_code += f"\nimport pyperclip"