minitap-mobile-use 2.2.0__py3-none-any.whl → 2.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of minitap-mobile-use might be problematic. Click here for more details.

Files changed (59) hide show
  1. minitap/mobile_use/agents/contextor/contextor.py +6 -4
  2. minitap/mobile_use/agents/cortex/cortex.md +114 -27
  3. minitap/mobile_use/agents/cortex/cortex.py +8 -5
  4. minitap/mobile_use/agents/executor/executor.md +15 -10
  5. minitap/mobile_use/agents/executor/executor.py +6 -5
  6. minitap/mobile_use/agents/executor/utils.py +2 -1
  7. minitap/mobile_use/agents/hopper/hopper.py +6 -3
  8. minitap/mobile_use/agents/orchestrator/orchestrator.py +26 -11
  9. minitap/mobile_use/agents/outputter/outputter.py +6 -3
  10. minitap/mobile_use/agents/outputter/test_outputter.py +104 -42
  11. minitap/mobile_use/agents/planner/planner.md +20 -22
  12. minitap/mobile_use/agents/planner/planner.py +10 -7
  13. minitap/mobile_use/agents/planner/types.py +4 -2
  14. minitap/mobile_use/agents/planner/utils.py +14 -0
  15. minitap/mobile_use/agents/summarizer/summarizer.py +2 -2
  16. minitap/mobile_use/config.py +6 -1
  17. minitap/mobile_use/context.py +13 -3
  18. minitap/mobile_use/controllers/mobile_command_controller.py +1 -14
  19. minitap/mobile_use/graph/state.py +7 -3
  20. minitap/mobile_use/sdk/agent.py +204 -29
  21. minitap/mobile_use/sdk/examples/README.md +19 -1
  22. minitap/mobile_use/sdk/examples/platform_minimal_example.py +46 -0
  23. minitap/mobile_use/sdk/services/platform.py +244 -0
  24. minitap/mobile_use/sdk/types/__init__.py +14 -14
  25. minitap/mobile_use/sdk/types/exceptions.py +57 -0
  26. minitap/mobile_use/sdk/types/platform.py +125 -0
  27. minitap/mobile_use/sdk/types/task.py +60 -17
  28. minitap/mobile_use/servers/device_hardware_bridge.py +3 -2
  29. minitap/mobile_use/servers/stop_servers.py +11 -12
  30. minitap/mobile_use/servers/utils.py +6 -9
  31. minitap/mobile_use/services/llm.py +89 -5
  32. minitap/mobile_use/tools/index.py +2 -8
  33. minitap/mobile_use/tools/mobile/back.py +3 -3
  34. minitap/mobile_use/tools/mobile/clear_text.py +67 -38
  35. minitap/mobile_use/tools/mobile/erase_one_char.py +5 -4
  36. minitap/mobile_use/tools/mobile/{take_screenshot.py → glimpse_screen.py} +23 -15
  37. minitap/mobile_use/tools/mobile/input_text.py +67 -16
  38. minitap/mobile_use/tools/mobile/launch_app.py +54 -22
  39. minitap/mobile_use/tools/mobile/long_press_on.py +15 -8
  40. minitap/mobile_use/tools/mobile/open_link.py +15 -8
  41. minitap/mobile_use/tools/mobile/press_key.py +15 -8
  42. minitap/mobile_use/tools/mobile/stop_app.py +14 -8
  43. minitap/mobile_use/tools/mobile/swipe.py +11 -5
  44. minitap/mobile_use/tools/mobile/tap.py +103 -21
  45. minitap/mobile_use/tools/mobile/wait_for_animation_to_end.py +3 -3
  46. minitap/mobile_use/tools/test_utils.py +377 -0
  47. minitap/mobile_use/tools/types.py +35 -0
  48. minitap/mobile_use/tools/utils.py +149 -39
  49. minitap/mobile_use/utils/recorder.py +1 -1
  50. minitap/mobile_use/utils/test_ui_hierarchy.py +178 -0
  51. minitap/mobile_use/utils/ui_hierarchy.py +11 -4
  52. {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/METADATA +6 -4
  53. minitap_mobile_use-2.4.0.dist-info/RECORD +99 -0
  54. minitap/mobile_use/tools/mobile/copy_text_from.py +0 -73
  55. minitap/mobile_use/tools/mobile/find_packages.py +0 -69
  56. minitap/mobile_use/tools/mobile/paste_text.py +0 -62
  57. minitap_mobile_use-2.2.0.dist-info/RECORD +0 -96
  58. {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/WHEEL +0 -0
  59. {minitap_mobile_use-2.2.0.dist-info → minitap_mobile_use-2.4.0.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Literal
3
+ from typing import Annotated, Literal
4
4
 
5
5
  from langchain_core.messages import ToolMessage
6
6
  from langchain_core.tools import tool
@@ -8,17 +8,19 @@ from langchain_core.tools.base import InjectedToolCallId
8
8
  from langgraph.prebuilt import InjectedState
9
9
  from langgraph.types import Command
10
10
  from pydantic import BaseModel
11
- from typing import Annotated
12
11
 
13
12
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
14
13
  from minitap.mobile_use.context import MobileUseContext
14
+ from minitap.mobile_use.controllers.mobile_command_controller import get_screen_data
15
15
  from minitap.mobile_use.controllers.mobile_command_controller import (
16
16
  input_text as input_text_controller,
17
17
  )
18
18
  from minitap.mobile_use.graph.state import State
19
19
  from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
20
+ from minitap.mobile_use.tools.types import Target
20
21
  from minitap.mobile_use.tools.utils import focus_element_if_needed, move_cursor_to_end_if_bounds
21
22
  from minitap.mobile_use.utils.logger import get_logger
23
+ from minitap.mobile_use.utils.ui_hierarchy import find_element_by_resource_id, get_element_text
22
24
 
23
25
  logger = get_logger(__name__)
24
26
 
@@ -42,12 +44,12 @@ def _controller_input_text(ctx: MobileUseContext, text: str) -> InputResult:
42
44
 
43
45
  def get_input_text_tool(ctx: MobileUseContext):
44
46
  @tool
45
- def input_text(
47
+ async def input_text(
46
48
  tool_call_id: Annotated[str, InjectedToolCallId],
47
49
  state: Annotated[State, InjectedState],
48
50
  agent_thought: str,
49
51
  text: str,
50
- text_input_resource_id: str,
52
+ target: Target,
51
53
  ):
52
54
  """
53
55
  Focus a text field and type text into it.
@@ -55,32 +57,69 @@ def get_input_text_tool(ctx: MobileUseContext):
55
57
  - Ensure the corresponding element is focused (tap if necessary).
56
58
  - If bounds are available, tap near the end to place the cursor at the end.
57
59
  - Type the provided `text` using the controller.
60
+
61
+ Args:
62
+ tool_call_id: The ID of the tool call.
63
+ state: The state of the agent.
64
+ agent_thought: The thought of the agent.
65
+ text: The text to type.
66
+ target: The target of the text input (if available).
58
67
  """
59
- focused = focus_element_if_needed(ctx=ctx, resource_id=text_input_resource_id)
60
- if focused:
61
- move_cursor_to_end_if_bounds(ctx=ctx, state=state, resource_id=text_input_resource_id)
68
+ focused = focus_element_if_needed(ctx=ctx, target=target)
69
+ if not focused:
70
+ error_message = "Failed to focus the text input element before typing."
71
+ tool_message = ToolMessage(
72
+ tool_call_id=tool_call_id,
73
+ content=input_text_wrapper.on_failure_fn(text, error_message),
74
+ additional_kwargs={"error": error_message},
75
+ status="error",
76
+ )
77
+ return Command(
78
+ update=await state.asanitize_update(
79
+ ctx=ctx,
80
+ update={
81
+ "agents_thoughts": [agent_thought, error_message],
82
+ EXECUTOR_MESSAGES_KEY: [tool_message],
83
+ },
84
+ agent="executor",
85
+ ),
86
+ )
87
+
88
+ move_cursor_to_end_if_bounds(ctx=ctx, state=state, target=target)
62
89
 
63
90
  result = _controller_input_text(ctx=ctx, text=text)
64
-
65
91
  status: Literal["success", "error"] = "success" if result.ok else "error"
66
- content_msg = (
67
- input_text_wrapper.on_success_fn(text)
92
+
93
+ text_input_content = ""
94
+ if status == "success" and target.resource_id:
95
+ screen_data = get_screen_data(screen_api_client=ctx.screen_api_client)
96
+ state.latest_ui_hierarchy = screen_data.elements
97
+ element = find_element_by_resource_id(
98
+ ui_hierarchy=state.latest_ui_hierarchy,
99
+ resource_id=target.resource_id,
100
+ index=target.resource_id_index,
101
+ )
102
+ if element:
103
+ text_input_content = get_element_text(element)
104
+
105
+ agent_outcome = (
106
+ input_text_wrapper.on_success_fn(text, text_input_content, target.resource_id)
68
107
  if result.ok
69
- else input_text_wrapper.on_failure_fn(text)
108
+ else input_text_wrapper.on_failure_fn(text, result.error)
70
109
  )
71
110
 
72
111
  tool_message = ToolMessage(
73
112
  tool_call_id=tool_call_id,
74
- content=content_msg,
113
+ content=agent_outcome,
75
114
  additional_kwargs={"error": result.error} if not result.ok else {},
76
115
  status=status,
77
116
  )
78
117
 
79
118
  return Command(
80
- update=state.sanitize_update(
119
+ update=await state.asanitize_update(
81
120
  ctx=ctx,
82
121
  update={
83
- "agents_thoughts": [agent_thought],
122
+ "agents_thoughts": [agent_thought, agent_outcome],
84
123
  EXECUTOR_MESSAGES_KEY: [tool_message],
85
124
  },
86
125
  agent="executor",
@@ -90,8 +129,20 @@ def get_input_text_tool(ctx: MobileUseContext):
90
129
  return input_text
91
130
 
92
131
 
132
+ def _on_input_success(text, text_input_content, text_input_resource_id):
133
+ """Success message handler for input text operations."""
134
+ if text_input_resource_id is not None:
135
+ return (
136
+ f"Typed {repr(text)}.\n"
137
+ f"Here is the whole content of input with id {repr(text_input_resource_id)}: "
138
+ f"{repr(text_input_content)}"
139
+ )
140
+ else:
141
+ return "Typed text, should now verify before moving forward"
142
+
143
+
93
144
  input_text_wrapper = ToolWrapper(
94
145
  tool_fn_getter=get_input_text_tool,
95
- on_success_fn=lambda text: f"Successfully typed {text}",
96
- on_failure_fn=lambda text: f"Failed to input text {text}",
146
+ on_success_fn=_on_input_success,
147
+ on_failure_fn=lambda text, error: f"Failed to input text {repr(text)}. Reason: {error}",
97
148
  )
@@ -1,44 +1,76 @@
1
+ from typing import Annotated
2
+
1
3
  from langchain_core.messages import ToolMessage
2
4
  from langchain_core.tools import tool
3
5
  from langchain_core.tools.base import InjectedToolCallId
6
+ from langgraph.prebuilt import InjectedState
4
7
  from langgraph.types import Command
8
+
9
+ from minitap.mobile_use.agents.hopper.hopper import HopperOutput, hopper
5
10
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
11
+ from minitap.mobile_use.context import MobileUseContext
6
12
  from minitap.mobile_use.controllers.mobile_command_controller import (
7
13
  launch_app as launch_app_controller,
8
14
  )
9
- from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
10
- from typing import Annotated
11
- from minitap.mobile_use.context import MobileUseContext
15
+ from minitap.mobile_use.controllers.platform_specific_commands_controller import list_packages
12
16
  from minitap.mobile_use.graph.state import State
13
- from langgraph.prebuilt import InjectedState
17
+ from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
18
+
19
+
20
+ async def find_package(ctx: MobileUseContext, app_name: str) -> str | None:
21
+ """
22
+ Finds the package name for a given application name.
23
+ """
24
+ all_packages = list_packages(ctx=ctx)
25
+ try:
26
+ hopper_output: HopperOutput = await hopper(
27
+ ctx=ctx,
28
+ request=f"I'm looking for the package name of the following app: '{app_name}'",
29
+ data=all_packages,
30
+ )
31
+ # Assuming hopper_output.output directly contains the package name
32
+ return hopper_output.output
33
+ except Exception as e:
34
+ print(f"Failed to find package for '{app_name}': {e}")
35
+ return None
14
36
 
15
37
 
16
38
  def get_launch_app_tool(ctx: MobileUseContext):
17
39
  @tool
18
- def launch_app(
40
+ async def launch_app(
19
41
  tool_call_id: Annotated[str, InjectedToolCallId],
20
42
  state: Annotated[State, InjectedState],
43
+ app_name: str,
21
44
  agent_thought: str,
22
- package_name: str,
23
- ):
45
+ ) -> Command:
24
46
  """
25
- Launch an application on the device using the package name on Android, bundle id on iOS.
47
+ Finds and launches an application on the device using its natural language name.
26
48
  """
27
- output = launch_app_controller(ctx=ctx, package_name=package_name)
28
- has_failed = output is not None
29
- tool_message = ToolMessage(
30
- tool_call_id=tool_call_id,
31
- content=launch_app_wrapper.on_failure_fn(package_name)
32
- if has_failed
33
- else launch_app_wrapper.on_success_fn(package_name),
34
- additional_kwargs={"error": output} if has_failed else {},
35
- status="error" if has_failed else "success",
36
- )
49
+ package_name = await find_package(ctx=ctx, app_name=app_name)
50
+
51
+ if not package_name:
52
+ tool_message = ToolMessage(
53
+ tool_call_id=tool_call_id,
54
+ content=launch_app_wrapper.on_failure_fn(app_name, "Package not found."),
55
+ status="error",
56
+ )
57
+ else:
58
+ output = launch_app_controller(ctx=ctx, package_name=package_name)
59
+ has_failed = output is not None
60
+ tool_message = ToolMessage(
61
+ tool_call_id=tool_call_id,
62
+ content=launch_app_wrapper.on_failure_fn(app_name, output)
63
+ if has_failed
64
+ else launch_app_wrapper.on_success_fn(app_name),
65
+ additional_kwargs={"error": output} if has_failed else {},
66
+ status="error" if has_failed else "success",
67
+ )
68
+
37
69
  return Command(
38
- update=state.sanitize_update(
70
+ update=await state.asanitize_update(
39
71
  ctx=ctx,
40
72
  update={
41
- "agents_thoughts": [agent_thought],
73
+ "agents_thoughts": [agent_thought, tool_message.content],
42
74
  EXECUTOR_MESSAGES_KEY: [tool_message],
43
75
  },
44
76
  agent="executor",
@@ -50,6 +82,6 @@ def get_launch_app_tool(ctx: MobileUseContext):
50
82
 
51
83
  launch_app_wrapper = ToolWrapper(
52
84
  tool_fn_getter=get_launch_app_tool,
53
- on_success_fn=lambda package_name: f"App {package_name} launched successfully.",
54
- on_failure_fn=lambda package_name: f"Failed to launch app {package_name}.",
85
+ on_success_fn=lambda app_name: f"App '{app_name}' launched successfully.",
86
+ on_failure_fn=lambda app_name, error: f"Failed to launch app '{app_name}': {error}",
55
87
  )
@@ -1,8 +1,11 @@
1
+ from typing import Annotated
2
+
1
3
  from langchain_core.messages import ToolMessage
2
4
  from langchain_core.tools import tool
3
5
  from langchain_core.tools.base import InjectedToolCallId
4
6
  from langgraph.prebuilt import InjectedState
5
7
  from langgraph.types import Command
8
+
6
9
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
7
10
  from minitap.mobile_use.context import MobileUseContext
8
11
  from minitap.mobile_use.controllers.mobile_command_controller import SelectorRequest
@@ -11,37 +14,41 @@ from minitap.mobile_use.controllers.mobile_command_controller import (
11
14
  )
12
15
  from minitap.mobile_use.graph.state import State
13
16
  from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
14
- from typing import Annotated
15
17
 
16
18
 
17
19
  def get_long_press_on_tool(ctx: MobileUseContext):
18
20
  @tool
19
- def long_press_on(
21
+ async def long_press_on(
20
22
  tool_call_id: Annotated[str, InjectedToolCallId],
21
23
  state: Annotated[State, InjectedState],
22
24
  agent_thought: str,
23
25
  selector_request: SelectorRequest,
24
26
  index: int | None = None,
25
- ):
27
+ ) -> Command:
26
28
  """
27
29
  Long press on a UI element identified by the given selector.
28
30
  An index can be specified to select a specific element if multiple are found.
29
31
  """
30
32
  output = long_press_on_controller(ctx=ctx, selector_request=selector_request, index=index)
31
33
  has_failed = output is not None
34
+
35
+ agent_outcome = (
36
+ long_press_on_wrapper.on_failure_fn()
37
+ if has_failed
38
+ else long_press_on_wrapper.on_success_fn()
39
+ )
40
+
32
41
  tool_message = ToolMessage(
33
42
  tool_call_id=tool_call_id,
34
- content=long_press_on_wrapper.on_failure_fn()
35
- if has_failed
36
- else long_press_on_wrapper.on_success_fn(),
43
+ content=agent_outcome,
37
44
  additional_kwargs={"error": output} if has_failed else {},
38
45
  status="error" if has_failed else "success",
39
46
  )
40
47
  return Command(
41
- update=state.sanitize_update(
48
+ update=await state.asanitize_update(
42
49
  ctx=ctx,
43
50
  update={
44
- "agents_thoughts": [agent_thought],
51
+ "agents_thoughts": [agent_thought, agent_outcome],
45
52
  EXECUTOR_MESSAGES_KEY: [tool_message],
46
53
  },
47
54
  agent="executor",
@@ -1,8 +1,11 @@
1
+ from typing import Annotated
2
+
1
3
  from langchain_core.messages import ToolMessage
2
4
  from langchain_core.tools import tool
3
5
  from langchain_core.tools.base import InjectedToolCallId
4
6
  from langgraph.prebuilt import InjectedState
5
7
  from langgraph.types import Command
8
+
6
9
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
7
10
  from minitap.mobile_use.context import MobileUseContext
8
11
  from minitap.mobile_use.controllers.mobile_command_controller import (
@@ -10,35 +13,39 @@ from minitap.mobile_use.controllers.mobile_command_controller import (
10
13
  )
11
14
  from minitap.mobile_use.graph.state import State
12
15
  from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
13
- from typing import Annotated
14
16
 
15
17
 
16
18
  def get_open_link_tool(ctx: MobileUseContext):
17
19
  @tool
18
- def open_link(
20
+ async def open_link(
19
21
  tool_call_id: Annotated[str, InjectedToolCallId],
20
22
  state: Annotated[State, InjectedState],
21
23
  agent_thought: str,
22
24
  url: str,
23
- ):
25
+ ) -> Command:
24
26
  """
25
27
  Open a link on a device (i.e. a deep link).
26
28
  """
27
29
  output = open_link_controller(ctx=ctx, url=url)
28
30
  has_failed = output is not None
31
+
32
+ agent_outcome = (
33
+ open_link_wrapper.on_failure_fn()
34
+ if has_failed
35
+ else open_link_wrapper.on_success_fn(url)
36
+ )
37
+
29
38
  tool_message = ToolMessage(
30
39
  tool_call_id=tool_call_id,
31
- content=open_link_wrapper.on_failure_fn()
32
- if has_failed
33
- else open_link_wrapper.on_success_fn(url),
40
+ content=agent_outcome,
34
41
  additional_kwargs={"error": output} if has_failed else {},
35
42
  status="error" if has_failed else "success",
36
43
  )
37
44
  return Command(
38
- update=state.sanitize_update(
45
+ update=await state.asanitize_update(
39
46
  ctx=ctx,
40
47
  update={
41
- "agents_thoughts": [agent_thought],
48
+ "agents_thoughts": [agent_thought, agent_outcome],
42
49
  EXECUTOR_MESSAGES_KEY: [tool_message],
43
50
  },
44
51
  agent="executor",
@@ -1,8 +1,11 @@
1
+ from typing import Annotated
2
+
1
3
  from langchain_core.messages import ToolMessage
2
4
  from langchain_core.tools import tool
3
5
  from langchain_core.tools.base import InjectedToolCallId
4
6
  from langgraph.prebuilt import InjectedState
5
7
  from langgraph.types import Command
8
+
6
9
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
7
10
  from minitap.mobile_use.context import MobileUseContext
8
11
  from minitap.mobile_use.controllers.mobile_command_controller import Key
@@ -11,33 +14,37 @@ from minitap.mobile_use.controllers.mobile_command_controller import (
11
14
  )
12
15
  from minitap.mobile_use.graph.state import State
13
16
  from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
14
- from typing import Annotated
15
17
 
16
18
 
17
19
  def get_press_key_tool(ctx: MobileUseContext):
18
20
  @tool
19
- def press_key(
21
+ async def press_key(
20
22
  tool_call_id: Annotated[str, InjectedToolCallId],
21
23
  state: Annotated[State, InjectedState],
22
24
  agent_thought: str,
23
25
  key: Key,
24
- ):
26
+ ) -> Command:
25
27
  """Press a key on the device."""
26
28
  output = press_key_controller(ctx=ctx, key=key)
27
29
  has_failed = output is not None
30
+
31
+ agent_outcome = (
32
+ press_key_wrapper.on_failure_fn(key)
33
+ if has_failed
34
+ else press_key_wrapper.on_success_fn(key)
35
+ )
36
+
28
37
  tool_message = ToolMessage(
29
38
  tool_call_id=tool_call_id,
30
- content=press_key_wrapper.on_failure_fn(key)
31
- if has_failed
32
- else press_key_wrapper.on_success_fn(key),
39
+ content=agent_outcome,
33
40
  additional_kwargs={"error": output} if has_failed else {},
34
41
  status="error" if has_failed else "success",
35
42
  )
36
43
  return Command(
37
- update=state.sanitize_update(
44
+ update=await state.asanitize_update(
38
45
  ctx=ctx,
39
46
  update={
40
- "agents_thoughts": [agent_thought],
47
+ "agents_thoughts": [agent_thought, agent_outcome],
41
48
  EXECUTOR_MESSAGES_KEY: [tool_message],
42
49
  },
43
50
  agent="executor",
@@ -1,43 +1,49 @@
1
+ from typing import Annotated
2
+
1
3
  from langchain_core.messages import ToolMessage
2
4
  from langchain_core.tools import tool
3
5
  from langchain_core.tools.base import InjectedToolCallId
4
6
  from langgraph.prebuilt import InjectedState
5
7
  from langgraph.types import Command
8
+
6
9
  from minitap.mobile_use.constants import EXECUTOR_MESSAGES_KEY
7
10
  from minitap.mobile_use.context import MobileUseContext
8
11
  from minitap.mobile_use.controllers.mobile_command_controller import stop_app as stop_app_controller
9
12
  from minitap.mobile_use.graph.state import State
10
13
  from minitap.mobile_use.tools.tool_wrapper import ToolWrapper
11
- from typing import Annotated
12
14
 
13
15
 
14
16
  def get_stop_app_tool(ctx: MobileUseContext):
15
17
  @tool
16
- def stop_app(
18
+ async def stop_app(
17
19
  tool_call_id: Annotated[str, InjectedToolCallId],
18
20
  state: Annotated[State, InjectedState],
19
21
  agent_thought: str,
20
22
  package_name: str | None = None,
21
- ):
23
+ ) -> Command:
22
24
  """
23
25
  Stops current application if it is running.
24
26
  You can also specify the package name of the app to be stopped.
25
27
  """
26
28
  output = stop_app_controller(ctx=ctx, package_name=package_name)
27
29
  has_failed = output is not None
30
+
31
+ agent_outcome = (
32
+ stop_app_wrapper.on_failure_fn(package_name)
33
+ if has_failed
34
+ else stop_app_wrapper.on_success_fn(package_name)
35
+ )
28
36
  tool_message = ToolMessage(
29
37
  tool_call_id=tool_call_id,
30
- content=stop_app_wrapper.on_failure_fn(package_name)
31
- if has_failed
32
- else stop_app_wrapper.on_success_fn(package_name),
38
+ content=agent_outcome,
33
39
  additional_kwargs={"error": output} if has_failed else {},
34
40
  status="error" if has_failed else "success",
35
41
  )
36
42
  return Command(
37
- update=state.sanitize_update(
43
+ update=await state.asanitize_update(
38
44
  ctx=ctx,
39
45
  update={
40
- "agents_thoughts": [agent_thought],
46
+ "agents_thoughts": [agent_thought, agent_outcome],
41
47
  EXECUTOR_MESSAGES_KEY: [tool_message],
42
48
  },
43
49
  agent="executor",
@@ -24,26 +24,32 @@ from minitap.mobile_use.tools.tool_wrapper import CompositeToolWrapper
24
24
 
25
25
  def get_swipe_tool(ctx: MobileUseContext) -> BaseTool:
26
26
  @tool
27
- def swipe(
27
+ async def swipe(
28
28
  tool_call_id: Annotated[str, InjectedToolCallId],
29
29
  state: Annotated[State, InjectedState],
30
30
  agent_thought: str,
31
31
  swipe_request: SwipeRequest,
32
- ):
32
+ ) -> Command:
33
33
  """Swipes on the screen."""
34
34
  output = swipe_controller(ctx=ctx, swipe_request=swipe_request)
35
35
  has_failed = output is not None
36
+
37
+ agent_outcome = (
38
+ swipe_wrapper.on_success_fn() if not has_failed else swipe_wrapper.on_failure_fn()
39
+ )
40
+
36
41
  tool_message = ToolMessage(
37
42
  tool_call_id=tool_call_id,
38
- content=swipe_wrapper.on_failure_fn() if has_failed else swipe_wrapper.on_success_fn(),
43
+ content=agent_outcome,
39
44
  additional_kwargs={"error": output} if has_failed else {},
40
45
  status="error" if has_failed else "success",
41
46
  )
47
+
42
48
  return Command(
43
- update=state.sanitize_update(
49
+ update=await state.asanitize_update(
44
50
  ctx=ctx,
45
51
  update={
46
- "agents_thoughts": [agent_thought],
52
+ "agents_thoughts": [agent_thought, agent_outcome],
47
53
  EXECUTOR_MESSAGES_KEY: [tool_message],
48
54
  },
49
55
  agent="executor",