From 7aa973caf30bb23875f45f33fc405afe10bfc0f9 Mon Sep 17 00:00:00 2001 From: lihujun Date: Fri, 18 Sep 2026 14:50:17 +0800 Subject: [PATCH 1/8] Fix Unicode input fallback when clipboard writes are denied --- artemis/clients/screen_client_factory.py | 14 ++- artemis/clients/ui_automator_client.py | 3 +- artemis/drivers/android/adb_driver.py | 23 ++-- .../clients/test_screen_client_factory.py | 46 ++++++++ tests/unit/test_android_text_input.py | 104 ++++++++++++++++++ 5 files changed, 175 insertions(+), 15 deletions(-) create mode 100644 tests/unit/test_android_text_input.py diff --git a/artemis/clients/screen_client_factory.py b/artemis/clients/screen_client_factory.py index 311f2629e..93149d12f 100644 --- a/artemis/clients/screen_client_factory.py +++ b/artemis/clients/screen_client_factory.py @@ -300,8 +300,18 @@ def get_screenshot_base64(self) -> str | None: def set_clipboard(self, text: str) -> bool: return self._call("set_clipboard", text) - def send_text(self, text: str) -> Any: - return self._call("send_text", text) + def send_text(self, text: str) -> bool: + result = self._call("send_text", text) + if result is False and self._active_backend == "helper": + # A healthy helper may observe a field but be unable to set its text. + # Try IME input without marking the whole helper service unavailable. + self._ensure_device_online() + try: + return self.uiautomator.send_text(text) + finally: + # UiAutomation can unbind the helper even when input fails. + self._set_active("uiautomator", "Helper rejected direct text input") + return result def clear_text(self) -> bool: return self._call("clear_text") diff --git a/artemis/clients/ui_automator_client.py b/artemis/clients/ui_automator_client.py index 12a73fb04..d3eb3da6b 100644 --- a/artemis/clients/ui_automator_client.py +++ b/artemis/clients/ui_automator_client.py @@ -324,7 +324,7 @@ def press_key(self, key: str): device = self._ensure_connected() return device.press(key=key) - def send_text(self, text: str) -> None: + def send_text(self, text: str) -> bool: """Send text input to the device using FastInputIME. This method supports special characters (e.g., 'ö') that ADB shell @@ -343,6 +343,7 @@ def send_text(self, text: str) -> None: # Give FastInputIME time to process the broadcast and commit text # before switching it off and killing it. time.sleep(0.5) + return True finally: device.set_fastinput_ime(False) diff --git a/artemis/drivers/android/adb_driver.py b/artemis/drivers/android/adb_driver.py index 4a543810b..639e54f86 100644 --- a/artemis/drivers/android/adb_driver.py +++ b/artemis/drivers/android/adb_driver.py @@ -325,21 +325,15 @@ async def input_text(self, text: str, clear_existing: bool = True) -> bool: # Normalize literal escaped newlines from LLM / tool call serialization norm_text = text.replace(r"\r\n", "\n").replace(r"\n", "\n").replace(r"\r", "\n") - # 1. Tier 1: Try clipboard injection + KEYCODE_PASTE (Zero IME interference, preserves multiline, works for all charsets) + # Prefer direct input. A background clipboard write can be silently + # denied by Android even when the helper reports success. if self._ui_adb_client: try: - set_clip_ok = False - if hasattr(self._ui_adb_client, "set_clipboard"): - set_clip_ok = self._ui_adb_client.set_clipboard(norm_text) - elif hasattr(self._ui_adb_client, "_device") and self._ui_adb_client._device: - self._ui_adb_client._device.set_clipboard(norm_text) - set_clip_ok = True - - if set_clip_ok: - await asyncio.to_thread(self.device.shell, "input keyevent 279") + result = self._ui_adb_client.send_text(norm_text) + if result is True: return True except Exception as e: - logger.debug(f"Clipboard paste fallback to ADB input: {e}") + logger.debug(f"Direct text input failed, trying ADBKeyboard: {e}") # 2. Tier 2: Check if ADBKeyboard is currently active try: @@ -355,7 +349,12 @@ async def input_text(self, text: str, clear_existing: bool = True) -> bool: # ADBKeyboard probe/broadcast failed; fall through to native input. logger.debug(f"ADBKeyboard IME path failed, falling back to ADB input: {e}") - # 3. Tier 3: Universal Native ADB input text fallback + # Native adb input text cannot reliably enter Unicode characters. + if not norm_text.isascii(): + logger.warning("Unicode input failed: no supported input channel succeeded") + return False + + # 3. Tier 3: Native ADB input text fallback for ASCII lines = norm_text.split("\n") for i, line in enumerate(lines): if i > 0: diff --git a/tests/unit/clients/test_screen_client_factory.py b/tests/unit/clients/test_screen_client_factory.py index 836af3b94..474d291a5 100644 --- a/tests/unit/clients/test_screen_client_factory.py +++ b/tests/unit/clients/test_screen_client_factory.py @@ -225,3 +225,49 @@ def test_disconnect_stops_the_uiautomator_server_it_started(composite): client.connect() client.disconnect() u2.disconnect.assert_called_once_with(stop_server=True) + + +def test_rejected_helper_text_uses_uiautomator(composite): + client, helper, u2, _, _ = composite + helper.send_text.return_value = False + u2.send_text.return_value = True + assert client.send_text("你好") is True + u2.send_text.assert_called_once_with("你好") + assert client.active_backend == "uiautomator" + + +def test_successful_helper_text_is_not_duplicated(composite): + client, helper, u2, _, _ = composite + helper.send_text.return_value = True + assert client.send_text("你好") is True + u2.send_text.assert_not_called() + + +def test_rejected_uiautomator_text_is_not_retried(composite): + client, helper, u2, _, _ = composite + helper.send_text.side_effect = RuntimeError("unavailable") + u2.send_text.return_value = False + assert client.send_text("你好") is False + u2.send_text.assert_called_once_with("你好") + + +def test_failed_ime_fallback_releases_uiautomation_before_helper_retry(composite): + client, helper, u2, _, _ = composite + helper.send_text.return_value = False + u2.send_text.side_effect = RuntimeError("IME unavailable") + with pytest.raises(RuntimeError, match="IME unavailable"): + client.send_text("你好") + assert client.active_backend == "uiautomator" + helper.get_hierarchy.return_value = "" + assert client.get_hierarchy() == "" + u2.stop_server.assert_called_once() + assert client.active_backend == "helper" + + +def test_rejected_text_does_not_start_fallback_on_offline_device(composite): + client, helper, _, factory, clock = composite + helper.send_text.return_value = False + clock["state"] = "offline" + with pytest.raises(DeviceOfflineError): + client.send_text("你好") + factory.assert_not_called() diff --git a/tests/unit/test_android_text_input.py b/tests/unit/test_android_text_input.py new file mode 100644 index 000000000..02eab8ea6 --- /dev/null +++ b/tests/unit/test_android_text_input.py @@ -0,0 +1,104 @@ +"""Regression coverage for rejected clipboard writes and Unicode input fallback.""" + +import base64 +from unittest.mock import MagicMock + +import pytest + +from artemis.clients.ui_automator_client import UIAutomatorClient +from artemis.drivers.android.adb_driver import AndroidAdbDriver + + +@pytest.fixture +def input_path(): + device = MagicMock() + device.shell.return_value = "com.example/.Keyboard" + client = MagicMock() + client.send_text.return_value = True + # The real helper can claim clipboard success despite a denied write. + client.set_clipboard.return_value = True + adb = MagicMock() + adb.device.return_value = device + action = AndroidAdbDriver("test", adb, client).input_text + return action, device, client + + +@pytest.mark.asyncio +async def test_direct_unicode_input_does_not_paste_or_duplicate(input_path): + action, device, client = input_path + assert await action("héllo\n你好", clear_existing=False) is True + client.send_text.assert_called_once_with("héllo\n你好") + client.set_clipboard.assert_not_called() + assert [c.args[0] for c in device.shell.call_args_list] == ["input keyevent 123"] + + +@pytest.mark.asyncio +@pytest.mark.parametrize("failure", [False, RuntimeError("unsupported field")]) +async def test_failed_direct_input_falls_back_to_adbkeyboard(input_path, failure): + action, device, client = input_path + if isinstance(failure, Exception): + client.send_text.side_effect = failure + else: + client.send_text.return_value = failure + device.shell.return_value = "com.android.adbkeyboard/.AdbIME" + assert await action("你好", clear_existing=False) is True + payload = base64.b64encode("你好".encode()).decode() + device.shell.assert_any_call(f"am broadcast -a ADB_INPUT_B64 --es msg '{payload}'") + client.set_clipboard.assert_not_called() + + +@pytest.mark.asyncio +async def test_unicode_without_supported_channel_reports_failure(input_path): + action, device, client = input_path + client.send_text.return_value = False + assert await action("你好", clear_existing=False) is False + assert not any(c.args[0].startswith("input text ") for c in device.shell.call_args_list) + client.set_clipboard.assert_not_called() + + +def test_uiautomator_send_text_returns_success_and_restores_ime(monkeypatch): + device = MagicMock() + monkeypatch.setattr(UIAutomatorClient, "_ensure_connected", lambda self: device) + monkeypatch.setattr("artemis.clients.ui_automator_client.time.sleep", lambda _: None) + client = object.__new__(UIAutomatorClient) + assert client.send_text("你好") is True + device.send_keys.assert_called_once_with("你好") + assert [c.args for c in device.set_fastinput_ime.call_args_list] == [(True,), (False,)] + + +@pytest.mark.asyncio +async def test_ascii_fallback_still_inputs_text(input_path): + action, device, client = input_path + client.send_text.return_value = False + assert await action("hello world", clear_existing=False) is True + device.shell.assert_any_call("input text hello%sworld") + + +@pytest.mark.asyncio +async def test_driver_normalizes_escaped_newlines_before_direct_input(): + client = MagicMock() + client.send_text.return_value = True + driver = AndroidAdbDriver("test", MagicMock(), client) + assert await driver.input_text(r"你好\n世界") is True + client.send_text.assert_called_once_with("你好\n世界") + + +def test_uiautomator_send_text_restores_ime_on_error(monkeypatch): + device = MagicMock() + device.send_keys.side_effect = RuntimeError("input failed") + monkeypatch.setattr(UIAutomatorClient, "_ensure_connected", lambda self: device) + monkeypatch.setattr("artemis.clients.ui_automator_client.time.sleep", lambda _: None) + with pytest.raises(RuntimeError, match="input failed"): + object.__new__(UIAutomatorClient).send_text("你好") + device.set_fastinput_ime.assert_called_with(False) + + +@pytest.mark.asyncio +async def test_unicode_without_client_or_adbkeyboard_fails(): + device = MagicMock() + device.shell.return_value = "com.example/.Keyboard" + adb = MagicMock() + adb.device.return_value = device + action = AndroidAdbDriver("test", adb).input_text + assert await action("你好", clear_existing=False) is False + assert not any(c.args[0].startswith("input text ") for c in device.shell.call_args_list) From eb52e2f625cf2658bb595f610565955ba0def343 Mon Sep 17 00:00:00 2001 From: lihujun Date: Fri, 18 Sep 2026 15:01:02 +0800 Subject: [PATCH 2/8] Preserve literal Unicode goals in Flash prompts --- artemis/agents/flash/flash_runner.md | 6 ++++++ artemis/agents/flash/runner.py | 6 +++++- tests/unit/agents/test_flash_runner.py | 16 ++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/artemis/agents/flash/flash_runner.md b/artemis/agents/flash/flash_runner.md index 3c21cf8d9..9e2599241 100644 --- a/artemis/agents/flash/flash_runner.md +++ b/artemis/agents/flash/flash_runner.md @@ -3,6 +3,12 @@ You are an autonomous and highly efficient Android Device Execution Agent. Your **Objective: {{ goal }}** +The same original objective as an ASCII-escaped JSON string (an exact character reference): +```json +{{ goal_json }} +``` +When entering user-provided text, copy the requested text exactly from the original objective. Preserve emoji, variation selectors, skin tones, joiners, punctuation and whitespace; do not substitute a visually similar character or reinterpret a symbol by its name. JSON Unicode escapes may be used in tool arguments; they decode to the actual characters, not literal backslash text. Before reporting completion, compare the field's contents with the original requested text, not just with your previous tool arguments. + --- # 1. COGNITIVE PROTOCOL diff --git a/artemis/agents/flash/runner.py b/artemis/agents/flash/runner.py index 936f60655..ec9a3160e 100644 --- a/artemis/agents/flash/runner.py +++ b/artemis/agents/flash/runner.py @@ -296,7 +296,11 @@ def _render_system_prompt(self, tools_declaration: list) -> str: prompt_path = Path(__file__).parent / "flash_runner.md" prompt_template = prompt_path.read_text(encoding="utf-8") available_tools = frozenset(t.name for t in tools_declaration) - return Template(prompt_template).render(goal=self.goal, available_tools=available_tools) + return Template(prompt_template).render( + goal=self.goal, + goal_json=json.dumps(self.goal, ensure_ascii=True), + available_tools=available_tools, + ) # ------------------------------------------------------------------ # Per-turn helpers (observe / think) diff --git a/tests/unit/agents/test_flash_runner.py b/tests/unit/agents/test_flash_runner.py index f188b5e1b..d1b153e2e 100644 --- a/tests/unit/agents/test_flash_runner.py +++ b/tests/unit/agents/test_flash_runner.py @@ -556,3 +556,19 @@ async def test_final_report_persists_native_thinking(mock_context): assert kwargs["operator_raw_thinking"] == "final text" assert kwargs["operator_native_thinking"] == "native summary" assert isinstance(messages[-1], ToolMessage) + + +@pytest.mark.parametrize('text', ['你好中文英😅', '👍🏽👩‍💻🇨🇳❤️', r'路径\n"原文"']) +def test_prompt_preserves_literal_unicode_goal_as_json(mock_context, text): + import json + + goal = '在当前输入框输入:' + text + with patch('artemis.controllers.unified_controller.get_driver'): + runner = FlashRunner(mock_context, goal=goal) + prompt = runner._render_system_prompt(runner._get_tools()) + # A second, ASCII-only representation makes the exact code points available + # even when the model misreads an emoji glyph (😅 was changed to ㊅ in a trace). + encoded = json.dumps(goal, ensure_ascii=True) + assert encoded in prompt + assert json.loads(encoded) == goal + assert 'original objective' in prompt From 3446fd117359c4684b66a759618c84bf2d4ad61a Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Mon, 21 Sep 2026 17:15:06 +0800 Subject: [PATCH 3/8] fix: resolve visual summarizer from agent model configuration --- artemis/agents/flash/summarizer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artemis/agents/flash/summarizer.py b/artemis/agents/flash/summarizer.py index 56c5e0985..e844d9c31 100644 --- a/artemis/agents/flash/summarizer.py +++ b/artemis/agents/flash/summarizer.py @@ -168,7 +168,7 @@ def __init__( if model_name: self._llm = get_google_llm(model_name=target_model, temperature=0.0) else: - self._llm = get_llm(ctx, name="summarizer", is_utils=True) + self._llm = get_llm(ctx, name="summarizer") except Exception: self._llm = get_google_llm(model_name=target_model, temperature=0.0) try: From 744c8b47a972e73d6bf9130108dd47fe09d75ad1 Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Tue, 22 Sep 2026 10:44:49 +0800 Subject: [PATCH 4/8] fix: make checker reports compatible with thinking models --- artemis/agents/checker/checker.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/artemis/agents/checker/checker.py b/artemis/agents/checker/checker.py index 40a90e292..77a145ed3 100644 --- a/artemis/agents/checker/checker.py +++ b/artemis/agents/checker/checker.py @@ -408,7 +408,9 @@ def _format_check_items(check_items: list) -> str: async def _structured_report(llm, messages) -> CheckReport: - structured_llm = llm.with_structured_output(CheckReport) + structured_llm = llm.with_structured_output( + CheckReport, method="function_calling", tool_choice="auto" + ) # A conversation must end with a user turn: Gemini rejects requests whose # last message is the model's own answer ("Requests ending with a model # turn are not supported"), which is exactly the state after the loop's @@ -416,7 +418,7 @@ async def _structured_report(llm, messages) -> CheckReport: if messages and isinstance(messages[-1], AIMessage): messages = [ *messages, - HumanMessage(content="Now provide your structured verdict report."), + HumanMessage(content="Now call the CheckReport tool with your structured verdict report."), ] result = await invoke_llm_with_timeout_message(structured_llm.ainvoke(messages)) if isinstance(result, CheckReport): @@ -482,7 +484,7 @@ async def _run_check_loop( messages.append( HumanMessage( content=( - "This is your final iteration; provide your structured verdict report now." + "This is your final iteration; call CheckReport with your verdict report now." ) ) ) @@ -497,6 +499,7 @@ async def _run_check_loop( break messages.append(response) + deferred_images: list[HumanMessage] = [] for tc in response.tool_calls: tool_name = tc["name"].split(":")[-1] if ":" in tc["name"] else tc["name"] args = dict(tc["args"]) @@ -522,9 +525,15 @@ async def _run_check_loop( # Screenshots (get_step_screenshot) enter the conversation in the # carrier the model's provider accepts; text results stay a plain # ToolMessage. - messages.extend( - tool_result_messages(tc["id"], result_obj, name=tool_name, status=status, llm=llm) - ) + for message in tool_result_messages( + tc["id"], result_obj, name=tool_name, status=status, llm=llm + ): + if isinstance(message, HumanMessage): + deferred_images.append(message) + else: + messages.append(message) + # Complete every tool_call before introducing user-carried screenshots. + messages.extend(deferred_images) if report is None: report = CheckReport(verdicts=[]) From d19a9f431538091b515be8ab585b39b5a47e086e Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:07:32 +0800 Subject: [PATCH 5/8] fix: preserve Pro outputter settings and persist task reports Preserve configured Outputter values when CLI overrides are omitted, so WebUI enablement does not reset force_synthesis. Save generated text or structured output to notes/output.md for the Task Report card. Add regression coverage for configuration inheritance, explicit overrides, report persistence, and disabled output. Validation: 57 related pytest tests passed; Ruff and git diff checks passed. --- artemis/interfaces/cli/commands/run.py | 18 +++- artemis/sdk/agent.py | 14 +++ tests/unit/sdk/test_output_report.py | 130 +++++++++++++++++++++++++ 3 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 tests/unit/sdk/test_output_report.py diff --git a/artemis/interfaces/cli/commands/run.py b/artemis/interfaces/cli/commands/run.py index 91a5d0440..a298dc1c2 100644 --- a/artemis/interfaces/cli/commands/run.py +++ b/artemis/interfaces/cli/commands/run.py @@ -22,7 +22,12 @@ from adbutils import AdbClient from langchain_core.callbacks.base import Callbacks -from artemis.config import checker_overrides_for_level, initialize_llm_config, settings +from artemis.config import ( + checker_overrides_for_level, + initialize_llm_config, + load_agent_config, + settings, +) from artemis.utils.startup_progress import publish_startup_progress from artemis import Agent, Builders from artemis.sdk.types.task import AgentProfile @@ -116,9 +121,16 @@ async def execute_task( config.with_flash_step_summarizer(enabled=enable_step_summarizer) if enable_outputter is not None or force_output_synthesis is not None: + outputter_defaults = load_agent_config().outputter config.with_outputter( - enabled=enable_outputter if enable_outputter is not None else True, - force_synthesis=bool(force_output_synthesis), + enabled=enable_outputter + if enable_outputter is not None + else outputter_defaults.enabled, + force_synthesis=( + force_output_synthesis + if force_output_synthesis is not None + else outputter_defaults.force_synthesis + ), ) if ( diff --git a/artemis/sdk/agent.py b/artemis/sdk/agent.py index a0028eb19..72823a63f 100644 --- a/artemis/sdk/agent.py +++ b/artemis/sdk/agent.py @@ -17,6 +17,7 @@ import asyncio import inspect +import json import os import re @@ -101,6 +102,7 @@ remove_steps_json_from_trace_folder, ) from artemis.utils.startup_progress import publish_startup_progress +from artemis.utils.notes import save_note_content logger = get_logger(__name__) @@ -1267,6 +1269,18 @@ async def _extract_output( output_config=output_config or OutputConfig(), graph_output=state, ) + if ctx.data_engine and structured_output is not None: + report = ( + structured_output + if isinstance(structured_output, str) + else "```json\n" + + json.dumps(structured_output, ensure_ascii=False, indent=2) + + "\n```" + ) + try: + save_note_content(ctx.data_engine.base_dir, "output", report) + except OSError as exc: + logger.warning(f"[{task_name}] Failed to save Task Report: {exc}") logger.info(f"[{task_name}] Structured output: {structured_output}") record_events( output_path=request.llm_output_path, diff --git a/tests/unit/sdk/test_output_report.py b/tests/unit/sdk/test_output_report.py new file mode 100644 index 000000000..b07f413ea --- /dev/null +++ b/tests/unit/sdk/test_output_report.py @@ -0,0 +1,130 @@ +"""Regression coverage for Pro report configuration and UI report persistence.""" + +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from artemis.config import OutputConfig +from artemis.sdk.agent import Agent + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "configured_enabled,configured_force,enabled,force,expected_enabled,expected_force", + [ + (True, True, True, None, True, True), + (True, True, True, False, True, False), + (True, True, False, None, False, True), + (False, False, None, True, False, True), + ], +) +async def test_cli_preserves_unspecified_outputter_settings( + monkeypatch, + configured_enabled, + configured_force, + enabled, + force, + expected_enabled, + expected_force, +): + import artemis.interfaces.cli.commands.run as cli + + builder = MagicMock() + agent = MagicMock() + agent.init = AsyncMock() + agent.run_task = AsyncMock() + agent.clean = AsyncMock() + monkeypatch.setattr(cli, "initialize_llm_config", MagicMock()) + monkeypatch.setattr(cli, "AgentProfile", MagicMock()) + builders = MagicMock() + builders.AgentConfig.with_default_profile.return_value = builder + monkeypatch.setattr(cli, "Builders", builders) + monkeypatch.setattr(cli, "Agent", MagicMock(return_value=agent)) + monkeypatch.setattr(cli, "publish_startup_progress", MagicMock()) + monkeypatch.setattr( + cli, + "load_agent_config", + lambda: SimpleNamespace( + outputter=SimpleNamespace(enabled=configured_enabled, force_synthesis=configured_force) + ), + ) + monkeypatch.setenv("ARTEMIS_TASK_INGRESS", "test") + + await cli.execute_task( + "Test goal", + device_serial="mock-device", + profile="pro", + enable_outputter=enabled, + force_output_synthesis=force, + ) + + builder.with_outputter.assert_called_once_with( + enabled=expected_enabled, + force_synthesis=expected_force, + ) + + +@pytest.mark.asyncio +@pytest.mark.parametrize("result", ["# 测试报告\n通过", {"结论": "通过"}]) +async def test_pro_synthesis_saves_ui_report_without_output_description( + monkeypatch, tmp_path, result +): + import artemis.sdk.agent as sdk + + synthesize = AsyncMock(return_value=result) + monkeypatch.setattr(sdk, "outputter", synthesize) + ctx = SimpleNamespace( + execution_setup=SimpleNamespace( + outputter=SimpleNamespace(enabled=True, force_synthesis=True), + disable_outputter=False, + ), + data_engine=SimpleNamespace(base_dir=tmp_path), + ) + request = SimpleNamespace(llm_output_path=None, output_format=None) + output = await Agent._extract_output( + object.__new__(Agent), + "test", + ctx, + request, + OutputConfig(), + SimpleNamespace(), + ) + + assert output == result + synthesize.assert_awaited_once() + report = (tmp_path / "notes" / "output.md").read_text(encoding="utf-8") + assert "通过" in report + if isinstance(result, str): + assert report == result + else: + import json + + assert json.loads(report.removeprefix("```json\n").removesuffix("\n```")) == result + + +@pytest.mark.asyncio +async def test_explicit_disable_does_not_generate_report(monkeypatch, tmp_path): + import artemis.sdk.agent as sdk + + synthesize = AsyncMock() + monkeypatch.setattr(sdk, "outputter", synthesize) + ctx = SimpleNamespace( + execution_setup=SimpleNamespace( + outputter=SimpleNamespace(enabled=True, force_synthesis=True), + disable_outputter=False, + ), + data_engine=SimpleNamespace(base_dir=tmp_path), + ) + output = await Agent._extract_output( + object.__new__(Agent), + "test", + ctx, + SimpleNamespace(llm_output_path=None, output_format=None), + OutputConfig(enable_outputter=False), + SimpleNamespace(), + ) + + assert output is None + synthesize.assert_not_awaited() + assert not (tmp_path / "notes" / "output.md").exists() From d56745171225a6787cb319c73d0365e764297d5f Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Tue, 22 Sep 2026 16:48:14 +0800 Subject: [PATCH 6/8] fix: use compatible structured output for Hopper --- artemis/agents/hopper/hopper.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/artemis/agents/hopper/hopper.py b/artemis/agents/hopper/hopper.py index ad9e23b80..8844bbc91 100644 --- a/artemis/agents/hopper/hopper.py +++ b/artemis/agents/hopper/hopper.py @@ -72,12 +72,14 @@ async def hopper( HumanMessage(content=f"{request}\nHere is the data you must dig:\n{data}"), ] - llm = get_llm(ctx=ctx, name="hopper", is_utils=True).with_structured_output(HopperOutput) + llm = get_llm(ctx=ctx, name="hopper", is_utils=True).with_structured_output( + HopperOutput, method="function_calling", tool_choice="auto" + ) try: if use_fallback: llm_fallback = get_llm( ctx=ctx, name="hopper", is_utils=True, use_fallback=True - ).with_structured_output(HopperOutput) + ).with_structured_output(HopperOutput, method="function_calling", tool_choice="auto") response: HopperOutput = await with_fallback( main_call=lambda: invoke_llm_with_timeout_message(llm.ainvoke(messages)), fallback_call=lambda: invoke_llm_with_timeout_message( From 755a1274edc3156f7d3a9c4b6974d687f9a80b9b Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Wed, 23 Sep 2026 15:03:23 +0800 Subject: [PATCH 7/8] fix: keep outputter tool replies before screenshot images --- artemis/agents/outputter/outputter.py | 22 +++++++--- tests/unit/agents/test_outputter.py | 60 +++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/artemis/agents/outputter/outputter.py b/artemis/agents/outputter/outputter.py index aed600d82..758fd8446 100644 --- a/artemis/agents/outputter/outputter.py +++ b/artemis/agents/outputter/outputter.py @@ -19,7 +19,7 @@ from pathlib import Path from jinja2 import Template -from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage +from langchain_core.messages import BaseMessage, HumanMessage, SystemMessage, ToolMessage from artemis.core.tool_failure import ToolFailure, is_tool_failure from artemis.config import OutputConfig from artemis.context import ArtemisContext @@ -268,6 +268,8 @@ async def outputter( raw_answer = response.content break + tool_replies: list[ToolMessage] = [] + image_messages: list[BaseMessage] = [] for tc in response.tool_calls: tool_name = tc["name"].split(":")[-1] if ":" in tc["name"] else tc["name"] args = tc["args"] @@ -304,11 +306,19 @@ async def outputter( result = f"Error running tool {tool_name}: {e}" status = "error" - # Step screenshots enter the conversation in the carrier the - # model's provider accepts; text results stay a plain ToolMessage. - messages.extend( - tool_result_messages(tc["id"], result, name=tool_name, status=status, llm=llm) - ) + # OpenAI-compatible providers carry screenshot images in a user + # message. All tool calls must receive their replies before that + # message is inserted into the conversation. + for message in tool_result_messages( + tc["id"], result, name=tool_name, status=status, llm=llm + ): + if isinstance(message, ToolMessage): + tool_replies.append(message) + else: + image_messages.append(message) + + messages.extend(tool_replies) + messages.extend(image_messages) if raw_answer is None: raw_answer = "Error: Outputter failed to resolve the query within maximum turns." diff --git a/tests/unit/agents/test_outputter.py b/tests/unit/agents/test_outputter.py index 668e5b3f8..ad384ca03 100644 --- a/tests/unit/agents/test_outputter.py +++ b/tests/unit/agents/test_outputter.py @@ -453,6 +453,66 @@ def get_llm_side_effect(ctx, name, is_utils=False, use_fallback=False): assert result == "Video played successfully." +@patch("artemis.agents.outputter.outputter.get_read_note_tool_pure") +@patch("artemis.agents.outputter.outputter.get_history_tools") +@patch("artemis.agents.outputter.outputter.get_llm") +@pytest.mark.asyncio +async def test_outputter_replies_to_all_tools_before_screenshot_images( + mock_get_llm, mock_get_history_tools, mock_get_read_note, mock_context, mock_state +): + from langchain_core.messages import AIMessage, HumanMessage, ToolMessage + from langchain_core.tools import StructuredTool + + async def screenshot(step_number: int): + return [ + {"type": "text", "text": f"Screenshot of step {step_number}"}, + {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,AA=="}}, + ] + + async def read_note(key: str): + return f"Note {key}" + + screenshot_tool = StructuredTool.from_function( + coroutine=screenshot, name="get_step_screenshot", description="Get screenshot" + ) + read_note_tool = StructuredTool.from_function( + coroutine=read_note, name="read_note", description="Read note" + ) + mock_get_history_tools.return_value = (screenshot_tool, screenshot_tool, screenshot_tool) + mock_get_read_note.return_value = read_note_tool + + model = Mock() + model.endpoint.provider = "deepseek" + bound_model = Mock() + bound_model.ainvoke = AsyncMock( + side_effect=[ + AIMessage( + content="", + tool_calls=[ + {"name": "get_step_screenshot", "args": {"step_number": 10}, "id": "shot-10"}, + {"name": "read_note", "args": {"key": "result"}, "id": "note"}, + {"name": "get_step_screenshot", "args": {"step_number": 11}, "id": "shot-11"}, + ], + ), + AIMessage(content="Verified.", tool_calls=[]), + ] + ) + model.bind_tools.return_value = bound_model + mock_get_llm.return_value = model + + answer = await outputter( + ctx=mock_context, + output_config=OutputConfig(structured_output=None, output_description=None), + graph_output=mock_state, + ) + + sent = bound_model.ainvoke.call_args_list[1].args[0] + assert [message.tool_call_id for message in sent[3:6]] == ["shot-10", "note", "shot-11"] + assert all(isinstance(message, ToolMessage) for message in sent[3:6]) + assert all(isinstance(message, HumanMessage) for message in sent[6:8]) + assert answer == "Verified." + + @patch("artemis.agents.outputter.outputter.get_llm") @pytest.mark.asyncio async def test_outputter_executes_save_note_tool(mock_get_llm, mock_context, mock_state): From bb9053ac7f37bb5710ede1e77b8ae039fd2a6c48 Mon Sep 17 00:00:00 2001 From: lihujun101 <34243386+lihujun101@users.noreply.github.com> Date: Mon, 28 Sep 2026 14:05:55 +0800 Subject: [PATCH 8/8] fix: target editable fields for helper Unicode input Skip focused nodes that cannot be edited so keyboard views do not intercept text input. Refresh accessibility nodes on Android 13+ to avoid stale text and focus. Bundle helper 1.2.2 and exclude non-directory SDK entries during the APK build. --- .../ArtemisAccessibilityHelper.apk | Bin 37353 -> 33257 bytes .../app/build.gradle.kts | 4 ++-- .../com/artemis/helper/HierarchyDumper.java | 11 ++++++++++- .../artemis-accessibility-helper/build_apk.sh | 4 ++-- .../helper_manifest.json | 8 ++++---- 5 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk b/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk index 16f4480091f821f104423e2bb2a2a7e249f075af..dea5d1e7e332b00594e149ab806333e36da8d633 100644 GIT binary patch delta 27075 zcmX_`byQT(|NmK*P*SAcw6wIeG>V8cvUI0(NatP%Eh4G>0 zh~2Cw65FD?l_5_8HPYg~VZP2K7Vdx1<-RkV`QSS|+#<@Tc)d*?FFSeWtjWT4xbMA_ z#UX%Q72>IZK@8J{E1b}xu}!`JzhpOYO206`7~rhC$HBM(PdMTDC$v`2nQ}s8y5Cm! zH^FsZt+1axMLkDjnFM9rFF+^ z?9~(cS=+g#7WJ0KMI{9E>P7#eIL5Wmqc6~nXP!|J(0p6)zKijeljdVKP>Flzm{KLnbd#Lb<7Nn0(Tjcl}eqQlWvqz}ihajy>Jiyvf>nfN+4oWs@(p@BF8!ihrDfRb2n5&R24?t`4ki9cI-cOh{r4 zslDSxz6%r}Gb9Cv9bZU|3!C#f^cwbw(d4?U3&*E=t`IjP~{5xkdx*Kbqhisl2 z;BtMMv)UrkUtaf(YJ9=aPv2Ucnkb2;IrolV$~R73Y)?Bi_R33h0JbBzbKplF>$SU^ z^H;~P8XSM3WxAvGK~Y(&WgoXxsM`Eao0Tbhq?3@dgJj!m!dT;tOl^;~l2xlR7aIfi z*3zc_Yx#Qb6MNifDWN7?q>p2}!PFH`p+pUj$H1jD{pL*Kt6ti8M}{lB!Iq!p46{si z?wqYnk)tjD)itn`0~Zy6H?;v$D#lfdf*D8lCbJ7GV8b)|bc{BU2krTjWz(uBC~P0pzJFSb8C1;{YkG&{nsp8&xQ?LF(z>i)Ha{T*HOW!rt! z*IGp`Wgm21%<+J0)T)rkPQ69FlgTfU0T)?u!zpv)_4<{fY+SKr?QmR6I;A;ma3jLp!r;JiHKy^qeB)yW zk;UGvOx+gN9}qbXhMTp!i>I5VwL7o5o4cj(JSGNKT^G&}81Q$NH12NEpqw_sz(6-* zVBkMGo~5I?yZd8PD{G(3Kzv!Fo|%WVHa_~HTJBJm3=Di<9z`EE{%0?qGO{qnQF8lY z#5n<@LXurRA|plsM+zhBZ2t7W)2wy5YDut&ktghqHq*hb(bj3ulkOj_(t z&=RpE0UFDP9z}Y>c|vr;2YdZa2v?6_8dLxyg?)x=!+GGwa5)(C$4klk2%-#r07=>g*_%GpL=YYeQ;_Wyvz0R=s-gP#G!wk^WQhPx_CbtNl@KdPIcO-j8siEigOLp~0pH`b zW1~rZ7;=%}j9Oc>pij}%kMV;~1WrWZ46ruX zQ+_$mIcpK_X^YC`nfWl<*1Tt3#%I12dXeSOFju(FqOg z%&s24=J_tp``|HfUr-L156pmZ^f~`pR;mU+9Gr@2ik*$I0#@Q>+XL}p#DKc6r7@Lw z-?V{kKuus7+!gFdX|OSl18kD3=LhOi<55P#^P&lP5 zNAD2M3(Nq#X8LAI)YB7oc;Jg;fqbPG7zZFCkS<0Oh#1s`Wdf4MbmRNB0;UNvhB3mC zu({w3n4#<-`M7c}^*_weVD}(C3~x+g40SLcrVBV_jC2b_SeDY2(N*Ha5>A(UqEt`r z%IhlZN_ip$=ZD+C7=nPg;H#i|zTDN|7vse5fNonUIYxVzv>@JBd?E0HU| ztJq^tQm%rJoqIlx@fKD9iw!mfb%9JU?w_EkeCT{geQ*KPlN!42W}Mn!J6Y*hG(U)w zK*=yFn1C7SIG!W+DApwiHwga`fW_dxAl;xY@G*!1^mFb7V1`rkOpnkJ_bu!V+yd?f z_k-)eL2yc#GE4?z!(bC+g*g=zV8*L~#|_fK+z&Q}nV9|e8o&8TGwo~40IFv2YaB@| z$){)vz=z5Q?8ERHv}yFsAICoEeJa~Sb71`kZ+#ghYEA`m5b4r3JqiiIZfAw@lN zUHPDekpvzJ@|0z-!Tk@12$Tb=0y*Oz;n0JjPuoH5_=V62Tk)D_rP#CJL)j<);f!K- zVk{9X5lRwC;~#P!>z|$hDpO;oN&N3;neU?@O_wfkV3FckSOTXdJ}gO1kI)Y zhvk9U7EA)4f+fQUVLUM1VE^E6K{+5kjB3mWZ~&H`b1)(1k}Pi=t`;U&FrBPu4MR9+ z2c!#b0Au z?7EE8f!!WN{1RISGa9C2Mm+w%v4C|k=0HstS73FFF-$&80CbPjj*q7KAc1%LlpDMd zBn5lvDt00dfBEP`P&h6uHpnJu6$D_dV9_@H>BB(70H$2fKa2y6-!PCFZ8Oemyd_*o zOi64>a63jjQ9HI?=NB*VS&%R6c`l9~wj*w9@XHf1xHz2m@m+k6MhAc!CB|c69xz-~ zFlR0eip-V9m9}Oz!VfGM%w#5A^F$9+kFhK(Ah2-UgpFe8j0_dh)xArOx&?l?XH#^*;m zpTiB{L~!Xxrb-@(a)9{ z0c3}b1!Pu<@KFTK zg~3~&Es=Z~dgIFaqzYu7&aP4!`ZM-bAW1R#?{nE#2&^gPS2d^9VsI>m3>G~$J(wQ%Q4Ih2VER1w!F%*Yp%Vz)rhNqy3Zlh@K9vMZ;-m2% zjhps_2Hpe%z+>Ppun7i~7!C3vd4!rB&I-4NkHNSe)u|gi7c3gog$aOHuvRedLG55P z@rgPdFnydG{2$eb3+oDw4bs=6oW-OGiiWY73IFUGVLs`WWz0WhRubhFuTnr}7V9G0 zV8;_^las*o*bG!1?3)mfrwHXhb)C*Yw2p(9}+aZJFr{n-xE^ zw?|m9tfI-tKza>dogWLXzdBR4JlVvMb)Gjo#W(foC=tkVBck4H!kJLFc%%b@rz)c4(8)_FMPgQ_B{m(qRk7)6SyC>ZzhkMDwv;yC% zF{2Svf@{eUQ|y)M>x^znhkK$#^na-9bl@2pb3_IN&D#cu?g>U@Y}QRNyR?ul>67kq zY$!6)W0BJ-m3Cq@)5xuAfEY??L^o}+TI7_6P1cAzRJOWsejZ>#i?l2B4>{k|kZG1JxK(rta9SLm2F zBW}m`9wI)bHN1X>Cx1M5?w?>_*V64?@-;iTJ_?F04hA0nb&mY)Qld8uk9{g1MJRtO z9QdUEiICw*yDejR-Yx?{cH6Y)R&x};DzT#YI~&CX`5oJ9Z1I3Jjk!R@A(y%Ha`Ag- zI;h6fu|tb$YU-z#ExO;LJe#LA5F&Q1@@r`e_fXlH1ziEBhO8;If?f zvWsw_{1l+;iFQcutg5~(??(Dk3O|G3SF)4SPnc(DOmid!v=-~y-(V|py_DOo3m?U- z#OhF=G`YdvWx8Am%?~T&WGFUKB%Ul`k%|ov4=gE^@@On1_lW844ExHR{$A)#YL}{B zE2W8SWm%ntF0X`D>YYrY&{rstLD80W+=JF+Xgd%U6QVQaDE>x8`!3~U^l%f-#SNe0 zSUx+el#04y*;)C-^)#b#Wbv58jfAQxhH=czti4}y;m$-sE*IR6@I%>+1Gdl&pE$a# zuxwRl7iySy>%{u|K%Jlb?Z>EML;`&ww_@dHywR^b>fVy;(Ro5ZE@E? zac9z-B}=|5&I6}|4MFQCH_7RzJo(AbbZ5%!WvkBQzTLONj&tph!*EV^9>wK)$g7e3 z*S6xl5<`L>0XV)dIsaA1_SfLkr{+Vf{U`y|JbVIq8CE;qXytU(RNEUdMfY8|aqf!H z>l^NhIKG6na)hP^@x{~*_7pyXdfd?%O2lnUzFsnpVeEKFhevc-d8>Ht?0Sg+Zl1F$Xolmw#Ca;I{KQCZkI}Sgm&5lm?P3?9_d3yt% z9O=G4h{Sl?RppOlBzD^=ESdAk6O(x``Y`<^Z~B;+2i6@&H8Xmb8MAAX?* zlEN{-j6x$zp1Fo)`i5^l5kwU_|ii$HADmk-fHXbN_RiFTo^9bmq z*f}NLeOs}M!ws=wIl@%D>q>|mXUc&?84_DOpT2FIUXBn_EpgbJ5mKMRRK*wOne<30 zb{ERxLQvXe6e_6?dbB0wnRLyZV=L~A?II1G!_aSY3?o**fOn7gA`r+vm1&kaA&^zv%n){ovwfSnFE-7WAn;t4!WsMX&AM~Q!&lfNnP4tgMrY`R-X61ZW^2x3`}(`t zUR=G}(0=YOf2-ysXf8tT%Z@pDmr-gRCUVgkwmgCQlj5pTh{c2(M4R}k!dFi3>dfQRk9w0}vSbgCF&84h z?+^UiXvRIcm@vIcZSswQW$AV1pXmQ$>#R{~WpU?k#VI|aV@%~zCwaTi-N};9>*@_B zw>)ACbK*Uivv{Ul-U2V}$0-Sly(gLoSRWY1NBlbf0bfwdI8z>qhKzvhD{X`UBE0Y* zhAm2gg4sV(D$BLq9>UqX_XyKrV_b*e{pXtQE@N)MFlkeFSQzs4+W^D~rSK~dB zi)YTmf$m$`vZbun4zGNhR_v+ws&lqwM^`GXK{r?sc4Ho&;}6R=qLoGER?SgHI1u5k zqsc>(LeF!{ZvTeVfpe+WC3^dOo$hly>V$LqoA2HJ>RaclU+l2&6ek4{0wGtXx|1ZK zf6j%|BTuBW#-xoO0?&OFZ`VqeIetKcs|0W+bcjhs8c{(}<; zl%k;?yO31q%PF`$q`l#+Om%xRlF(Wzuc`M;v1xC=(XQ2aibyrtBuUxS-J+|WU{?yK zYcGRyxU)SD+H_MsanAO)%ZqP7SQxSId)E~+hiJY(zPXXVx%q3t+P50yv$*_@VX6EQ z`t>K85QYqA=rsJAnyDod1AtE{5hy^geyNz_I2Ia?Vit!Y6fZVVnYR%~v!~i6;iloJ zoITWD;^68}1WmYlOYBbKz#Q%y-HjX8D-~zH7}K8{ZO^()`sUKt=MbpZ>jmPSM}=o% z_nLoAD>hOS{bI~wx(H9b5h(JKaG5YvpC@_{vib1eIuPr#6DY}`662j$2S~p|)1NO7 z-%i(cCT?V{GKmMFe6Qk{NMDOV+1B~mb-!NEKf3}NV{#bDIxtUJuTBF5u5Z%B?}-BM z(i7JmJkT5BD`89e^}e!))wnvNCE*h4Cy}GflG&Unp5SCiLMdbxkrfN_CCn ziMr@l!DqZmVddtlcNA-v0L|~Yl{mSX6vXt(D`F44QW_!rY5a819#|DSOG%r;*I1+~ zLpypx$LSa3b}(Qw*3;L#xF)Xkmmsc>>i6rBRK?1#_y~G$Hr917_KocqRkFSMB;U-{ z{Be@1$lin$IInPAMN*KQlH-eh3Z0F_j6|Umt|A?k2DvDeT4J|)0gAL0_AU2>vj|B6 zRbNN7ZMx4$Xe(*Q-@tCqWzpQrS5anP^N;BDQ|si-$Om~=`If?T9fdH<@e=a_pNy!5 z9m(551w1&aG7<;~qrz_kmg=T75}5Loo|Ojvnpg;G<0g)J8#U+iB=75hhRj>{guEZN zPW>uj3)UvmYvh#Smw>id+|^Jm5%ppDHmjUvxI4Stw`YS&VV#lV^;WxYLfZ$%uSnA4 z38IPDKH^4kqqqeS z^bcEn*2U_HwW!ZZ>2pVBsM&uDI!i*in;);_pGbI$7qvAJmWoAe zvG|VeI?W@Z!qD1Ha#b)l+xz`;OaH2z#2|80+SaBgz)U2LkPH)2TE0YR1rpo6ts1n0 zx5Eg`=>NCt2;D2=W*l3T;2ipF%zE~hvw@ZieI{VW83zy#%Ny~(qI-!+@xmBX^Xv!M z3bQ3{>@R9XA^WD(MeIP6 zfoI&ZJ@r;kC~|*i5OiZ|4SxtT)8>2x=RYKQr`40oTnilmEW?j$>S1QYoJt9d*0{cX zQXh5o!Xu3WAA--)im&mQznL+Pn{qlih;hH06=(tE&Jrf@jN^2vg3ksMt8VH@N-MN< zsZ9K+?6^D@ylhp;bw*8Y3i9@*iRd&FWLW<{B@yu|g1X9K^(-1D0h&C{@0X)Im%&?8 z>XBc2f{NKJqSPXnzdJ-F{K8lJ-xDu(i<#HD?od37N`oe}{QM7n=1yODUL85Wj&ST5 z5clT~;4G>$;hgquJ||Gyj4E~KY+Dm6`&F%0HXN4O7tPKE`A_@;GfMw;cXNIOH!o5@gL38W~XLpxzcvu>NKZuExVA{ zUDhO?y`jk3{$!#z5e@3b!xXd2?o{@bL2zdb)aEMQ*)*eShz-;dd8 z;1lau_amx#e*S>FBDtfn-=?Uhlz(fE=Hotl@|3{lbZDj#>Al9gEJJD!8~DoaS?a=y zw}XOKX_(f~_=b_dmdwv({Pd9;Zqp+?sZOX(X}!iZ9P%WGb4^^oYVx{y{IFlXxG#}i zg5Lo_hEro@M`B7u`&|qHfO@}}x3!-a9h}_oQcP9TQ`m*51Kd;aO^QDZRY{r4l9Ku4NLE!UfaXqRuf+jmUpQavko@aKgLm_d?CBKM7Izt= zCZRtp#s~Wc_wD@i02$q)fr6g$$%IpZs^2*Y1gWM9i))@~)9N z%GZhH#7?kCDjNvyA}KOR^zA5T2ZfZQjYyY`5rB=N1Kvn_pfq6v@3W&!ZU>!7Vn6Zx z*cuq z#(e3vTt;UjKx#~rdGh&B?imU_Z{-x$_3e@|HP=EO@_@8(alWQ!m*0=oRZOmbzqc}*~EiPB3kISZv6dL`2mD*2Ix$@W;g z)JS_!T+F2i*7wt*tz*&wws3RyH7AqfE2R&&#c9Gkplln>(=&*0O!`z^%bX>%X!6ZN zQ2t2*bz3s+BD(Q|Z=Rlow|H29@X2&m47J32O!1+d`^;q`LL_6uRPQg5alLCCeyfm_ z7d`GeBn3++~FqOnU+30$`^agHP$>wyU2b< zI<9zAK#t+^sdq?%2}fVp+*zhka|HWJol0?uPsyeR6EHByhPLBj?!njx6xm=P_B+!Xj>!HFk*} z4-Q|5^BYqY(v76;Qnz;h--6XYc!SPy63?sIB?Egro=rM@e~MsG_er6E$P{}2DD0As zKm5S>!0R&DQcMfQw4=UW?BICZypzVXGgo$p^ zLx5I8HJ`?7NyXE;g3obI*#-GbEp5+2QsZnklEPKnXYpeXOM2a z=8fKSqfMnBR=sXvW?hyun(de?E-9^*B()Z^Zael#`hFVGPOLh($N&+`H%;la+4e&|2$ z^g&jp=pF+_tYPp3EKW?idjghY@WXVS9jc&>QhieY>WanyQ4m2 zb!@vI1*mlZFY%fGzgw)Aco| zdc*yqA+vAB78}}^7q9yUCzeplwBGc~#nOz9rUPm3e2BeNzLTJ&`mpdh^BI31C_9KT$8_g}+<>rC!~ zZp4jqs&vHoLluLTG=85>)FNN-qZ z&1|0)ihrW3pEnoP;qlunLcUR-LP=gag+DEJ?DzFamcr1nZ0xme`X}}cF1M_?SS=H< zdrZ-XL*cb_kgsavXYASaRVx{PFOMx<+la;Ep%)L_6(m?XAhzSWiOoR!KqcxVbqC%I<&aeYJUO2QGe{<#=uUwWmu>#7?O$jdgT*M zduGK~Su>G-?h&=>$}P(mPCl8D2Fe+=G{#Z?5t#OAt$x|K8;Wl#w%JL_rD4;$L9f7G zjq_ZZ49y#5yD9Bv(O6wuUG%%t9(Tb{J09$mk$+Rl$GX_5k}?6XOFgb%e_K~HNI;rr zAaIvMHErZ&1Duo|!f>|fgHU`e&2CfmQOvz4Ut2P+WF9?A(DuMwy2Yw}5`@D0iWwH3?LE)sA)?srh)T0{thI!k8B=*J1 zsV%5};CZk3q zQd2Hl%{zWh3}S*LJ1cinQ`Z|j4?m__pCwxA5!@R(&qerkdfE8#yIqKvuvJ%inZ%xW zKu}5pB}17~;kU38YGVA(jJI}jB@tf*VjTQ*1e_Y&DS=5f!5O^6nwkAfi$PyT&EX%J z`KA3og=l8O@QRAU=wGLmwT|g}ev?gh)92I`7K3F=8+ECg2VKRJodlh8vhh6ZtN|w;znYK-DojLASB7HrQqC%$^JI8UO zamC`blgY*#yNk=nxA@x*yE2lJ6(axqvtp=3Ml=EI&(E5s94l#=!1I${_kz!Lz5JuYqNRK)Sn|X1=i3&xv7|Y7AACqX z*|(OWLUiI5pRPf~g%5ZkV&_zIg@p9b;oU80-isnMV_XF6XTtqls&v9)y3~D$8V{r0 zy^ajHu;}dnN9dnT>{ObBRCmZFNwJDAt^Z_*@*StL6-7!_Dy~2r90~%~bzj?!1z&M4 zG*uLH@+)sMeCCVie-}K9Gwhq(f2TYh_G>Bf`p<)37<{_@YC|2_6*}LW9DJX_q<0*^ zRO1_+DfVHMF_YojJ2(2Dpsu%j-&LjYx6|4I$PJU8Q`X|jue+s7o<%!}^_i-*m6tiJ z>1f(|iEDzy<6@&5+LS`a{=ihI5@h&uO-9&v<$mhTOk>%nov&amFMobeY6(O|F6+#6 zX@=czEthFcaNf?1w;A$k0weZ>%NEH}csX2Um)Ks3gK~cO(_CRX&=9<#na-Cb5;A4L z*{hPFVs=+u#d}tgR%Gffa^i0`_c8L|nQHwX zJ#`80{Wv!HqvLr*SgBvC&Yd+RS=jJEDY_Z*%6?u~@8#V-C%2A)O-t$MI@Sn=YMxF` zlmWh=2aV5n<8Y*bsZg!cb$xT?f8GfIj?)gi4Swn1y4;~i8T7NuWPOZ{h=11dACuJW zd4~A2(C<0r1F33a4?RvCOJ_!+QL72&r+Q^PRqqsIFq_G>i6oR3UO=?;Q3ta4JL`II zI2{APX5Xsy7hPkQ?VC$9{xi1>tHLbR(H#>1CsxEzuAOU2<PKgQ+>8C)UVG$XOxyH!=_Y%^P|TT!(AkVw7-g;R7t!g zMVLy8b=*!PoZPN5cC{;~vMc~nPimfNyZH}UvxEcU3xy_6{Uio0Q^s5z^JN3^D`u%` z=2!EyPnMXtoHnb@cWP1%aPd8}J{){12n^V)E#Q)C9C#SMF zlDOCH{X?Me5{}7b_>uZp^@IjfjWd^{-G+GHghX%V{Y;o@cJ-yOVk<^EcIHmoKN%L$6s)flBr70kh4!sE2>5 zc=~mj<@WmNsr&;FQ2ienX)Qz6GX!)P2VWIIFUe?+ZZRPd8aR?-yu8ga`8CDTwh_8} zPLoYs+oNI-H!8xO=vDLwI$C2s@yiswn1y2#M+67T-po}Hk^!7}NG4bdg z>@vD2-}cg6eFk^83uvqkMP-pR#KtK`+(l_sKJ0aPL)o*hL{S|#9EF$vvKFe!Y)HPW z%dzwgvIJ6&dxu}kiLez;X3tJjFM5NP%-h%w8B^SE# zD^xP(I+x8P+J}0*8LgS*`bO)l?M$60es*MjEgWq=ND8zKVfls*^%OzBb}sM_o7RVX zG_m@~yvU^RxPCTJA#5}%r(VUyM9dobv!0@GT=)cDbynI3(Coeu?&NU`7)$UHHuPg5 zJ^;2(jeU5o-iQzG2mr4OJ@%~(6KbehW)~i2oIjL*W;Z0%eK@E5509kBzAi-)yj`CSA*_3I&yeMa-aEpE7;`b{58 zDyM`@Iccsh;}&cPl8$8;JtC8*O*#o@UBo^O{oLd*wHgV24@|*5!8(O!ZLDf~Th>38 zbMoERwJ~#IUtRC_8oytsW%6I(+VaWZ^GP~sE-itr6NwQUcuH;hL~;0&e5N_$@cRq! zd@%l~m?=lT_lu?=HfJSCGp3s>9q)QNOI;O*!bL&fCXyDia$isIeQ>KeH7LOEsPSgC zm)nsr$$OV7F#EngT)kSCwwBF(EBmMXv8h`0PnYm_OO|fNgGoEZ}t+!WDD4;>wjP@PQ!w3dmHmV0f_XE-PZ-Fy&+ZhWlre!RvvERLjz z4mteePkNh>G4g3)54IZ8-7tj&9`7}uXf0b3F=dL>32K^w4&{bOo`h+K2iZ}2MzPz) zQm)x(-)@I4IhB;7O=Hg6KN0nfoEhYkf-`l0H%yd-J$N8^nysar)_5|vN&cb$$61mZ z^>3M^eV9RlU4{U zTq37i*c9>WlfIx@=W|a*lQhRM&OeL}!@3qTr#F&!!wMg|?8W^)h19$2EQ~g)UM?Gd zp)8>Y?A=66AOfS5rw`GKBo! zeL(Qn?awG@Oz|u9>_Xf3jZBoq@8$M4gTnp`HJj%rgbhzg}M{@P+Eklw5O*Nt4N_#`yOj zf3`wL#xP_GuYKXn>EGCQ>8B;z1-(^}&|x&v)pUsn5H3vB9y|8)eFvuGcFPwhuIhz2D0S*s)Xm zH#znw`(6H&iG^&Gzv@lCI#dr{{`NPXIfwl|F$W^X>%O!pLzELnYq#RzBRuMbn2}6Q zvHsjX=egx9Hae|mC1d}z7Y8uhQ@ZPTLZ4OvR7x_RdNa9=H}!}cv2A3XwY~N_2Rf$h ziJekBA_LUpr@mVGq@$fNtDlahc;H0iXG4$(@k9I~<`G>UbnBWf0aWaqF+`^4R3c3^NZ9F-ij%( z)#2S=J7B43)Wx#MGj^`CC)goTi`;B6c{8TZ{rf?;dX0H2Z_k)oul8@}P^0^`$#9c&EkKWJi^dSH zIHq*;y2&8J)`ETE@>kX_cbY~273LVwmpEE?NTcJhgMA~pNE)z^(D@Q*8c1~XR$Acz z{HJ|I@-8RTJfW(GYb4P`<;P1uB`mp$4d0Ma4Q-81x@&P9j~nd%&D>0o7^Dz)(F+8B z{s6^(nogRz)e^K#jShS-z3laVR0u&?IN+<3J<+KBC()zuI!oD&cG^}DzEcep$DMJR zdKQxBxfmRqs8`z}y2NPI1#-40VI;aU3xxO}=qYa+_vEvlOPTMuHp(V}l#DgfHQ%{BDvZ!RK>oT=d zuHo791?OCOaO~2L3y7uU4s#Ko8H^G*IaL{?DDMCF z?e-^Wy9EQ*9PB{?vqD))MBVvs7O(oe*2LF{6)!D`(QazmIh4*5X*%O5hr(Xe-d8z4 zk#-8o#rdYsAF4~$Z$yX?37+n1_ic=!8)N$Pc3 zkLUVCNODNG{9mdqflsC40t!AANb@8p8z1f`dH9u+GX;90iVyF;aQqk>c#R_%-56u#r@)*LaQwyT z_M773eMRFTptNx5Xtx+IP}NzT@`VcmH%AI$dfp?6)*1rMd=5?d!#2Z}yvd5n+_8Rc zDq2P(Jq#$KUyG zTXuD>>8S^21R4#JE{%U)GF{wL54e(A7d7s9Lz21Z8Z}2KR(I%})iPG3E!nk;_zLnp zJXOWXTc(>m2l#RQ69<^p5T3=fpVKxyN(x(kzEJJxx}ZK9c@R}Q{Bq>BC!{Y^qCyyT z#OazPhYkal_BR3LS&j~=eU&pGUsmDM#w=dGEcXwoV$Ie>2m#!SDmo?C&uVqcT5 zi1Ls;kmvae9CphEg!$fF1)Ok{z2kLM!yZWNb#4UW)o@eAmKoKl+GOM+rhut|E*80? zWM4X(EwS&6RqS)a)_zPbt1LWF4e75rK1wWV;+8(G^t0>)kePcF?zkQlcaX2)f-?ka z#d6Wc53M~ubJOe++>~z3O5fO9{ChKMJ}{Ob=ug>R#5837@1)cP7ahm6_{7)EH%E#k zClUBm0wbkwni`*-vwM@VIw)Z)BK9TwZS|2Ceeu$CpQdlg-5&c#OZLUMEHuloK74rN zqm`D}q6X%|?~yi$ot}0wzvq&~*{VoJ@+!ViwS)-7%KE7%&sH7!;HmF6!8P^I z=sOleqwKqbq>@dFe;p~SGy~}Ia$%9`9t&VMv*GXYAk1@@`BntM|82TK2O2ZtAO53q zUvQFKl+U=wHw*XKT({0B+$dY1eUv1``+(9E=Dl$a8h=vj2uJ{aiHrUC6oRx)_qo+W_Lf{~J4XI(bIvJ~EG&96u=?-)rr7Dg zZKB9^>@Fq`1716?cXpO$2EmP^k<=e^Lqit({?D+_!5f@?mFxH5%aZtlJ`(*jMcgH5 zQ#v7CllUw3Tv5#~-W+M8eKXZs7GO-P#yegqqT`>VT^CQQm+0S_ItS?ONRju;GrNe4 zoE%!X6rFO|+Cx8Ubz|sXL$MZ`}KRb=wk@sio^-ag*0D39yz4uio20Hysk-(?C&el@JsgJ2v#UwHeBk=%7drf5vn;f_BnA( zXq{bVO5@D5XC^&QsgPx5F=bWyB)sOetzXIR_3MGF)UxksZtAcP0q%?nl_!PoB-uj^ zE04kg7~*~DdXY{kZ%PiE-U0qyv^## z9gu0`2NQ_x;PFVa-=*!KI?L!sZT)7KZPF{%U(&`!E6F-@oz;tXT62Dc4}Yly8$OY5 zpV9fzInsTEwn2}+c^NbLXeWlvzZ}uCJrUKfKn(w7rUA?;!lF96O6(uQcyy#2R` z-1w0a?O|NqO}>OSUsmD5lcZ>a>i9JHe#d+JFV&~i98aMUsjHzH0s1jtWi-2*1?a3F zZn09G)RT-~Z;n2*_x56&oqNmuOC|9_8eG20*c5fe`3uhrajurPK`?`vkJE|Vuzm|bdrc9MKqha1-dLX0dv6`y=sPfefr z?GUUA^TuuwSGYb-C%V^ezo(HIu4SGR_CD)6<-a~&e|{fY8RgugKIGFE-^H`4`?h?* zx_)L_BsD213A2IqtoplM+PX5U5?}F{g;vXNONArj(dxD~@%MoGy#&(`9RrPgo{bvk z%{Z!)$Y?I&qAY5(@dE^c&5h#TZyD)mMqbeu`>T#cm5fzO!9nRT& ztj&6PB#ES+U9lJ!pf_t9H^(El6GpeUx9?q@=Dwq|M(BoOLnXvfLlsp8o=xAlM)yjM zC)%qW(D%v;fJ27pcLqXJ$uN)K``fWUTh6>w`eUhDY?*gn4|6+<7-i(dro4nlGtKEW zx8$}Q^|Njid9-1#-B_a)nq0Tu$I>z^U+rd$^#1!j(;F-)og5oj_+sam?4D=47E>RFW)D zOz=aOg(FR6BL6$_ZRqX380jlsb#FR-d00u_(h^5Lo&ROdlK$!3=&EaIimQ3=8lC4I zE31(D$~wd-F+r_0&SB+qVbQ!RpHRN=^;JR5X_JP7<$=hbmd@9v!ya?nW2Hs@TTK6i zq4h6}76G)LF4V5sSIFpgeIhEup%4E|CSw)-VeOP+?ezZ|`|6mwgD1}7g`!1@ySqHx zi@QUyKHMJe{w-1{E)S=;dvS;2?yfEF?tcCKE_au^ziyMsCbPTQWOgz$o6O87ftZb$ z3?l|brEqY}Ti?t)q8k*K=;b=gm%!^!>_#MN(sUJh+GZ-ve5TZsl`KlTYnC+KS0g1o za7K1Ue_+e6r@vwpxr6(IAawvTHRQG}@DYzc)g;YF0_f6-ifemDr=kL?_&RH;0BNK( zNF0WsVeY&O{IJ5G#=&9IJehl@?8>cczGc|Q&olWy=TawXS}(6YkO}Ll>-Z}*nLSVK zR$e|Q?ba+i4)w=d^k{93ff?TjQoY7V>S;z4I1F3&S?$>)iJzlmrg^#BB}jg5SOolgdoNV^|!Y2n(Ch3Fi&I@r>Q2zY^45?OQ(Q@api{nytB8Nx!B%i{wY}j0C0$ z3iyWjgmTpV%I62bE(aVOG-Y|FzxfO_Ce>68Cl%e$g^46w%$fm_vdZ4LEfCzpHVhD} zbpFww-MfF1>Dn}hNug8Yan?Bh?U&fLh5Mui^Y0X9f{F@ZY>zjB)hC4-o&7%eE|qlR zHMS5k5>fZ#U9Bw|%9XJOw3G3?IBm)XJYTYzkFQBTBi1?M6X)DZgGr&fr$j8-XN#$= zUld1}twQ93Ax;1kjiz6fl%(1v3MF_&_|b%qY}hlmd1ilt$;zH#NR&e_9^AVTQd8eNDn?t_kk(Io_*a`D6Lb23?iDeb zRAg3Y9n7v*UF9WYpwr<{XIoeP@cxzQ4-%-SI3Y6mcmOyD8`Lxu^x}_mIlGV2^P9E# zDS!1x7N#2Lzld&>?xE4TA-QHx|N2;+z(a->k*a&G!NinVTbTB?FH~vsdUn7m%$jz` z%^Rwq5Gf%DD^p28a^&fIY=x{->t#N0jczJ7>PVpB9{ISh+hN~UnV0A2+P3Awjxa_u zCNt23KmydeWss2kvIW2yojLj0WeGb4Y~`$rs-)5%65N^D#$>Q(cVfh!YsD1A?I?hO zaVaVTseL~knI>~rS7w71HNjIz2cq|TW4ocSWnUb36A-wm*2ZhNsl9poN1T5>2TJW^ zq;=)3EI~Wn+=<4eJ#T2wQ#T6}PjqsKzH%gGI8T8%iE-K7_cHS?6y&Dt!h9&lKk|n_{qZJRk=pNKW_fUz3CRd99Jon zJj>2pN6vGNXY*`O5zAID@Lt%t+?@$8<>yJd70)n~Ip)VwczUKBO--^*dQ>X6OytYa z6!6zYV6y8uJu?(j;CHG=9slztM;_M%eO#8U>7QR38li=7Y0mw}8gABEASV&i(n4k? zD7a)z&pd? zd&h*-EYl3@+`_C?i|!1I9+{pw=^-gD(9xFJ>0|@-%h#j3AT|F~X{&UoQ&wmvpseQA zAX&6jVlH^&M|;nMoi$mNeR}ql1pVM&vDIE|5{9v>TRqJj?@~6}t(9B(uXQ)?r2#NEvg-%qa?0KB(LwCw}R&$Rg{^+Ns z#m%$hhXs3YZgdTDf>qxBI?_|IP(`hBQj&)ED!`gfO>&YUlj-`Jj|^!C{{eA^v)k;( z+ntt&UlPjU&BeN%mfN7iBjs|NWf4W9qu2go8}EE^NfuG){!QDEWfy4k zV2|d;DA3kqWnmfUL}4tyxH(4a50SWNiM!8?FAej7AWJ=N`Z#$I)MN8+of6lZ$91hw zI+s=&1~~g7eXcg1t+f9NNLfD!f$68Y79*UGcI%Pm6Ew%J&<29y){Wkbs5N;Vm_b4l zjj&#}&u<-dJi54GTXxcI_Y$VugyqavM`~iQvW9ZT@2_RKcJ&4xocC!hxx!tq8>(3R zhvA#mSnU0mgf+}n475!C4VMlY9Okn+nvwq!unV37kpe+Q9Tby5owccl-_i0dS`-o7 znq5@Dso2)7m`6{`<9tIu%SjXw5#dk;bZAj&EQ*K$SEu~2vYU*h^y~l`4I|Ve^r`2f zxS)h- zwW61PD8hWijEn*#DOaCJNha>HM{)>|mlzlWRs{X>t7M{(GqPnbmnC!sCP^{wFv`l>e6Ko$ zyBtMP`0;`0vzzbIKA+XBRX7!$ngU$W?c>#Y5{3hKBD0Jc?QQMWW|v8d9qf2md8fo_4s^Yv`tDC4=MgdMT+>z59^qDVh$I}xde1qqzI#C2OSE?`{x@oTS6ml1ur*?Xd-;b8%Wy&-;yW(qT~%Kc7FkGJ1LEeG`8;2U zM_?Pr>_3yX%+n>nQgT|EeMX>!HQSJtZg!W#W^IaS4u075k!bnEuGo(#v)Qp8hks|} zNG-yLY363(0x~#+gg*twZ3MAjsXDqM=m8WRpZIS=@)6EeB0rSe^?lId#OXD6Opf_2 zO^z8^tEI3^_J=$s42PlN|NPH<;}~2J)-E9ykhaZFhv53do2+APftU#izSJnTvnFRk7g2P}Lg$aRvi^ zAOQn;jkLu|slXQz;ogEIs33zfe&liCAWX3un>~a*1nXN!&~z2u$m7lPOigBa`P#Pb zp9bqZwqdC2rQXzMX$3g*dQfGB!{U@(gBr`e_NhKxhK;rOU;x=t-}{QaEWDcfIx84o z=j;*$G#nz-yY4x5v^ZmK$zf$~dkX+!XorRtb3H&bN!uDE`~KJT_ z&+DAfQXHvrUf&YTB-lS5ZwZXb(;lde+b)$Z8vSad<0DoXecH95{%C9wQ;FUmv`fT2 za6*dO?+yK~_~uDim^Zm@O%`Dj-@7Uhma;FlWKqm;srh(y(V~(sn`C%iEtx_%!tRME z^OwoQcteGDKLR1or$@NQybS2R2rzPW56YC`Q0UT3hg@GI2sxxVT<7C(C)`~WhjhJm zebEOE23Z{rJU`?zHIHdBC5j9u>nHw z<4i0Bnx!0IHW;ucvgBg09wGN3*^LhRWrs?-DkkwpP87#5l!C>lV-5^&vGabzfxJN^ z=t+Bo=VC3LT}Q~%xOf=^G0{mB4b&yL=tdglCa9LSilUVgG_7fI!Nb2$o({YzToR@MrPO3DhZZ`HXfIjpCv?iXx)Ls;YQri| zZL%_fu)ySm>8qww(bTlukw(K!k-`k#fczHdW?5vi%B`lj0N`B&$lE$muZq?&86c78 z?s%nZlVrrCyK;M?`2mSbf{XeR4@I)G!TkJ%Tns`|&moh)?K8d=6)Y7gb}u=g_XomJ zcnjBB1>IrRw-ikdZ(b4`Xh?q^nwq%1I;e*enonT@m$kcgb2_ zosXe*HQ&>jbS9xq6#sxJNC5}3l@TF3At^nC&n%BC3|v6iv@Of z(CE=ff!;$$GLW!t`L%R@NJFshPsm5!bKI4X%jIBJxrdg& z=j&+4!kNsrAC%}raCpQ!H9PE~npzKLa(20U^+j{o+pOcDA+uCEWE(nUGlbm~`18uC zXpWuL7|G2tBn)Z2z>CcGp(%gontrvqw(`qW;O!^*!NmQmOUuR#9`1XL;AJXfO|PyJ z?Cb-?ZejDA_;NzxV+;hUEM_O?rexp1-io%U0^7+q1}~OJZ}xee1_yk^pLcxupKGF( z<7t^gTqOKv`cK_%h;$l34eG-ok0)zoJQeHWC8HJgtHfVqQb#Kx<>0l&<+bU$YPCK) zV70EL8ZDW(z7AB+&11XV)Dp$XYmWk|u(l~qPQO+vv!3r4Zx2XbyxPi;TZc_piTCjo zj2VujpCyW}eV||ATIAx4WS}0kSV=(XKMe7YXv2i3YC`oPomIH0+cF1SDJ#26;T26Bx^*=gHsJHseQ6M)ve3e zk(a?tjXmCeK~zr8ha@cjLeGM+wt?NF0B>XY{PH1_I`|5!m56{X!6x2SviMfOrO3+$ zOZikld7YkGA={sYf2X156nv@Z4Aq2~DK|;U8$ng#sSheCEq&m*-qyRm++!}(3fG8s z&mF3j1)7UoQcLE@I=HeEhe;&OkP?uFU9Jn2;zptMBl#dc$=TA3%yUBq7nddHE80qI zw*Jt{db!|=s;^H6;v5CwNy-9hA97EgBkCC9-CgVcZsIj#x7L4ed~IOVE^vseRy>Y( zrC-j8cO^eVAC~p>@vdqNVkYU+AKJHir&@%LV{`B&X83PSLUa=My@WW=?mW~m{>JRY zD(;}^8hxE{8{Fp-a*vf^=2{6mui?JOP-s*A&rD^Q?G+eYNa?T;*!CS$0r4v&&4F5 zUdg;QEYECNqNv;_292plzEit9Ss}@0`RtSZ;MKfs_EhW|*TKh|N}OgT80&enb-6HK z?U6tZ%^xh}qU;o*x1RyEa+8)h_JdQkC!++HcwZ~($hf2eMLUmTQT*>thotz0sPhk} z_|LuU7%Bw8YP*|QbqE-UJx(>3+6?^eWhWx=#&AO&KJ3xQDc#@Dpmw~1UlSOIEd1M2 zD0QvKcB~*>7R5@Up9yFM1sQMHgM@t~grAmd<0We72~aE7>Oui^>L7*yTj4fUF&kQ< zW?tLrQq?qSG;xEG>c5F@C*n+{wuZu&)dnW>32HVy(O2IZm6YWg9Jz8zScKu)6V~#4 z39Z0w)uNzps3F$0$KoaStLk%21=uQ1B*N2W0sYjZvJ|E-y+5_kai?8nz^6#D&1Y=QZ0NNC>i(*7DOBcI$mAL0)rAYtKAWoj#;i0j`a(WuecN8U&>lOzE~ z3DY<$BuH6PL?2A_PM8QHyT)jW{sq_+WkPd*H^4g-)Ym{LF|+AOyyBf~ih8^Z+3&vD z`;YsPz2%D5v|}pBG>;%|5N4DDIGLEDd48WJS;zqs1(oqg$~`jH=#9M@*X}UFh|v1| zXBXSYp>*mauXjw^hen8x-1ulYNkY$RA`ZVUnq!S|XzHKr`Qw^-G!_6L? z@LbS@+8C(1*4LWnGFI9fBl{ zNNWdt9PwEw6ZbOR^+V7lnK6CuB>^Q9NP!OSx(z#0sf7G#gyAkYXK9->y-gh&EN)=w_z9yLY`rylaP2t2ZdAjzlE0RZZT^&+GyBF;uTf zaq0dij*P1F|_1Wgc23yv2f@8`g%pES3tI{ENqa%RuN zs$?SX&xe!vf)%TuGaFln?1e>6uJQSUmVDfMdK%g$T$_?!3?_11`ow9x9Dhp-_q$Ud z2{3#b_ZmG~_4?O@(dTr{%z`}=Cu-W5I;TxWQdW)V%8Op+d#%aA1UmBDm(^w|ln>O} zH}04)lcwqdZz;~Hm>nG4-GsQuwT*PyU85YRJi!FAvJLN74sy%_R4y8q*rAiU6=~SD zOw4S(O17W;k$=(i4%;a)(_rjL{$|Ah#*F$PGI`O&Ilf=m+Q|rQE&a?ZFl=C#e>^1i z3784u;_e}h52s>-tF*YTz973)o@s;hRk3B?x#rmi;<;ZFkgSA1WD)cYVxBbgn0~J( zhF^|{7HKZ?!i9M~YSLI+NLQy3#NUWB;8rU;(Y!224cCwq+&&fzNNE*H6TmP5xxQe1 zY`GnZ9dlwtf_FTk^!d3k`Qb4Z+xT?RY0)y^@9wtl-5qh==Xk9z4?BJP*?gFg^o z1>|E;rbf}prb@A96Uv*L$)SGJ(#RZy8n@x1ePuj_xpcUHqJM`3j1FYcuO-m_N|ul@ zh*!ob7~(Exju25Ko_cQq{&*-2tcN4mF!lCV3{vA>jVQXf{_yDIus+FF{i}*-pNy}$ zk-KQLVQ2wOKlq`*b@29rr~op`z5KSlmP>6ERc#k!Ka$T&j|7rlstz3^pkC4>X->WD zS7RQ`uTS~{cCv>c#`w7nk8sC3u*hOb(nH6NNZu{#;yWSm{|LDJ9WT@lkUrMR>xPGv zD?mbOR8yzgrc;-~9}|vKOpx-+259F$er&Ce{ax81x7ovC7eN(ATBtPnD71R>d0kgR zde#2N!XN(Z_V!*HfjKmF{)6&DXZ)RZ)=>5&!rD~ITf823p;Q9gjmul!LK5EZNCKbh z$Q{X~A}nmVJ!5Sq@QvkLfjV(C%u&gdKT3+W{ju5>ff&>)TCc<{-_dxa`$NBI?Ib|H z-WUYxD%0wIWzT=KAGEF@uFvrsfd^Id(%s|@(eYENy1&QGG@xK1{CO{%3tliSVZa?5 zLME+P)&RS7IByH7ci@-yo5pka^(wqcMZCogIyPIsc^hiZT~#fWEb7ZkG+S}2cP(P7oV0g>brmI7XSN22#Xy=nG)CKkDLW@}v=sq=4F$Lg{{dN}}W$PnOTd<)j_vNk5USy1wa&;w*lI3$I$>SQX@` zBa;#ESX8~MK-cG2yQ7d^EaCOl%uRzvThL__k@};K*7Y(IA2dAzMH*)_vuAB8F8o>#B$*#PPA#6E%++i4T z8Q)GKW;5;d8cWh#!MGC_?FmlO+=hdUGrL>?myK`m3rcd;7s4))>hD)gEG!sjx=Ob@ zSRx`J#|DhgfS2&*=Lb|lU@&#@UE8x(merW0!QT6w`>}Gd9ahd*wlk1g@4LfzIo2Ud z>1yBp)gD>TovD)d^1r=t=9tQGbIreisJAASoxc2rmrBv56R^nS-dqkmDtF-~teB_b zgw^Nd90Z41R0EosRle+H6D9*{JMqgO!xuMVE^|DG0rz|Tjlm2xzi^_&qDsnR*EHlcX+wgOav}ToH{N4Pl zYkpjdDqi$UASC>yqoL>_WM1XBViOs~%V$1c|t?24_@ij5*4 zV|c#_AY>>kg~Hy~&iou%-ExaJrz{R@SNb%YjFv0cRotMF&aJyXg2Wv)SD}_C*GyqM znQ-hvQo!P*bU@NpnQbeE6u#3Q&MJPwCJ`R{#1%DiqqP^^2MM1~%E05%rn4<)v@Mrj zOu=y%=lC=+f0EiRRgRIu@X--E2T>jJrJOx=)pxJV31d^IiU3e>|2C!0A7p>jkyhjt&kT`!}cT%?=E!K^^5jzbx}DmxfQfPUA9hN06#cbrZh`G}Tz<`>=Na zHdYLq(jls)q=JKWHbc`iBRK<&ZpWDY76N;QW_rd)XY5*6cl%^7% zpIt+G*wP{*`y#YpYxJU`#Fk?S(qsE^*is^*(#;iD>=?tyq{ekWluuxe=P;+T-UnLS z@z{&`uApp5Cw9naNf@9VX7`nDYoI&>$^i?wp85eXb!HiqkpN3dWV32V*3~@15qoK3 zK1K~ae@i(5&CqAre@)hO%lJV^yFV6#@`X6KN~a>530|_or2VEsZKJeBu65Lm^}bS+2Bet)w(OV?(1^e`Guo4UvZcbrq1O z=zS0@3$)y7oM!CKo1%gBH@3xWQI^r)|?{up;@uX zh4Q^;aBG8|aI2573yv++;IjwH7tik1_GEfK@aDo05pNF_nI)R>61ZqmE-aC)-_Sx-qxju8lq!FXTcEiRoqK2cP9M%q(bt2 zWcaRwtm=2fqpv@Q{NCT@Mg`LGdEUsu(`A`l?BA+yE3v+e`U}7f-9pdfokGj(pF3%= ziL)ESSV_e)!@w8mv5`RSXr`zvK%FKad#$&9EiQYS(|Uc+H!$XW$WG7|p;C((YK`ZG zk+Xs76ywV_=EMB>Z0uMxHjVYs|D%Ml^UF(E8J}zL^CqX$KefKOv+Q_ysJ+~!qv$d- z(+jcuDnn!-set6s344r#bVdgAKtUM@1;iX5QF+mkplnop;Q%?vFJT3tortkL#0b&+ zdvq>WAksRF`>rlLbmC)Y-`?x@x^wJ~S`19MW!-xQjZbqcIU=TQuF*nPE6<-v58sT& zb6l?Zy>E#xh(1T~zG}$7uznI9fpubksr0ow?&PyM6!O4D!ak$m>+2%=>1z`0+kU6M zN0jbuhpyDqvJ9+}_bw|}&$krb-*WMSGZA)J+M?w zlZ!ksZZbK`k7jGj4op!_`3f^B{O}#K`Y~Wh7cRI6I|{d65k0?N?Q%bB4oqFxbU5q6 zjG4CeeFyJ$vw7?s2~}n~oXoXBVwhgX2DFx+-kmvZ@B^PZB#PGZHlTzeMH^?_&k-S6 zhiy5Q!S~bO8%6Rq*^qE@A4&aQ&jpPZM5;2?z{q^(zT9ZQ?Z$Gk*YnhwGp5Q$3(_+j zp$HluqUV!kz3om?p1-0jBHzOpefo>=oAh_gJ2r$> zRUKt8iJuP18l%k))Gs`s|IBOG|4$UtpW^=YT1DQnz4`}|VoNrS%YwKA$<{-S4?5fO z30PPrM4#?iwxf#w=Z=^~1F!kNQ!8 z(H}j71zfTLMQql~*Crlxbaa2-vT&81RWleqN=Op0UWR3(nq}6~!{P?!F8MPDGf{=s z{XS3I({_>2lu5JNg0Ggp7K_GsVaJ+v$%q;7*V~miRxtMSIiu}>h(sE)sGc&1ee=S! zjz+cf8+OsZfZ{~;1vMS|kS`MPcE|1ko~OuWR+PS9eWg&-j<0zEN~3|0#sZR%JH~f4 zUlSp^>hC5KKcX%4xwO1n@sx2Dwp*BS5DO1i9v9`Ds=9Fybj`Z_sEk2Yzr6Z(vt03rp1Xb1V-?`TrhzV=GoPV)a-f z5wbt5j2!Hz{LB68IMnfP;PM0Bl%yRL1R^_RDyY<+#)6HXib4ANhobv28tOK-@x^Yrc!|$I=+O|YXTRY+Z zc@~;1pY5 z3LWPQ<{ukivf1aU1x>^M=Ei@@e|dz_Z|p%#&PP_}`KZE4@!`+9h@K7@=$B)n%bEDy zXD=4A+b8mUIz3I4Umrwsic;2!XYdKLqGg-IeEI-a$i zlaaCJyJoB+hJczodHBgd6#+jv>&9gkHi3A#fxlt&R-67s`MZP56=SRWXwrHUxl1QJ z2L>bG2v&YCA2|L9^)F;bXDU_Wa#htTd%uTY;#5vR76rWAOPJ!pV8(u&UWImPO@8vS z$sW@N(=5*p|FLC%{%saSxTBmnW(&=9br)dlloc22U`e2gM1B{=khf!wA^DSm*2myH z!Q!p+oqN^orF_`#o?Btfj^v;(#bIqc! zml3@y^#Elq1v9Cc8 zfT1U1LlXDvUC@f=ru;j~UN+=VLddt9{U%!m!yRRT7VS?R$79WI$<+ZaMo8pLXOnVQ zO!w{Pv0U;OJ+v2}5B?Q{NJjh;YB|u)k}$$FBuS4v^Kzyi_^42%Ccm~K4v5U{;;Z6# z&>Bu2D}GJ;pb|uCU5Uq2IOn8_mL?%Dn&*Z@c=m;~GsGPV9Sor)L#B=iG{*Q=?0zGuADeCvM&DANLCBoxt;LpDu0<6 zs*{oCnmC(YAD;_uVk(GPr-6~s=&u}ZwVH&j=mQkNxFM`yMSGUt?C-z=Fc7Hjnwa@Z zx+x!A(R)u(-Sa6M5Auq*zg;nAm3Y>IoXbG zkD$1*+OS*jP#6S8XmTv0a5&<-{|87!RRIPT2kt+<=YM*n{})N*-@<=Ket<9ZHI@+h ze|s$`MI0`ymnD=hj*|R8%>S55rhkx2eg6pef1Ud8jAsq?j-&eTrvF-rfBr7&AN}Xf R|1(_epzU#V2;ct0`5)azL>T}8 delta 27946 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 getActiveRawRoots(AccessibilityService service) { + // A snapshot or input action must use current nodes. On some OEM builds, + // cached focus/text can survive several actions in the same window. + if (Build.VERSION.SDK_INT >= 33) { + service.clearCache(); + } List roots = new ArrayList<>(); Set seenHashes = new HashSet<>(); boolean hasAppWindow = false; @@ -788,10 +793,14 @@ public static AccessibilityNodeInfo findInputNode(AccessibilityService service) for (RawRootEntry entry : roots) { try { AccessibilityNodeInfo focused = entry.root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT); - if (focused != null) { + if (focused != null && focused.isEditable() && focused.isEnabled() + && focused.isVisibleToUser()) { found = focused; return found; } + if (focused != entry.root) { + safeRecycle(focused); + } } catch (Throwable ignored) {} } for (RawRootEntry entry : roots) { diff --git a/packages/artemis-accessibility-helper/build_apk.sh b/packages/artemis-accessibility-helper/build_apk.sh index 91bcf5a6a..b86cb56bf 100755 --- a/packages/artemis-accessibility-helper/build_apk.sh +++ b/packages/artemis-accessibility-helper/build_apk.sh @@ -47,8 +47,8 @@ if [ -z "$SDK_ROOT" ] || [ ! -d "$SDK_ROOT" ]; then exit 1 fi -BUILD_TOOLS="$(find "$SDK_ROOT/build-tools" -maxdepth 1 -mindepth 1 | sort -V | tail -n 1)" -PLATFORM="$(find "$SDK_ROOT/platforms" -maxdepth 1 -mindepth 1 | sort -V | tail -n 1)" +BUILD_TOOLS="$(find "$SDK_ROOT/build-tools" -maxdepth 1 -mindepth 1 -type d | sort -V | tail -n 1)" +PLATFORM="$(find "$SDK_ROOT/platforms" -maxdepth 1 -mindepth 1 -type d | sort -V | tail -n 1)" if [ -z "$BUILD_TOOLS" ] || [ ! -d "$BUILD_TOOLS" ]; then echo "Error: No build-tools found under $SDK_ROOT/build-tools" >&2 diff --git a/packages/artemis-accessibility-helper/helper_manifest.json b/packages/artemis-accessibility-helper/helper_manifest.json index f8d979ddb..ec06d2662 100644 --- a/packages/artemis-accessibility-helper/helper_manifest.json +++ b/packages/artemis-accessibility-helper/helper_manifest.json @@ -1,7 +1,7 @@ { "package": "com.artemis.helper", - "version_code": 6, - "version_name": "1.2.0", - "sha256": "133e373fe03615c251cd74b03a38539c844d09a074954bca2d2dde6684561969", - "built_at": "2026-09-10T23:13:11+00:00" + "version_code": 8, + "version_name": "1.2.2", + "sha256": "009183e099dccaa8c3fd5c9db0a786e445f55f98d9511d0472185e5a31656e7c", + "built_at": "2026-09-28T05:51:55+00:00" }