kotonebot 0.3.1__py3-none-any.whl → 0.5.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.
Files changed (66) hide show
  1. kotonebot/__init__.py +39 -39
  2. kotonebot/backend/bot.py +312 -302
  3. kotonebot/backend/color.py +525 -525
  4. kotonebot/backend/context/__init__.py +3 -3
  5. kotonebot/backend/context/context.py +49 -56
  6. kotonebot/backend/context/task_action.py +183 -175
  7. kotonebot/backend/core.py +129 -126
  8. kotonebot/backend/debug/entry.py +89 -89
  9. kotonebot/backend/debug/mock.py +78 -78
  10. kotonebot/backend/debug/server.py +222 -222
  11. kotonebot/backend/debug/vars.py +351 -351
  12. kotonebot/backend/dispatch.py +227 -227
  13. kotonebot/backend/flow_controller.py +196 -196
  14. kotonebot/backend/loop.py +12 -88
  15. kotonebot/backend/ocr.py +535 -529
  16. kotonebot/backend/preprocessor.py +103 -103
  17. kotonebot/client/__init__.py +9 -9
  18. kotonebot/client/device.py +528 -502
  19. kotonebot/client/fast_screenshot.py +377 -377
  20. kotonebot/client/host/__init__.py +43 -12
  21. kotonebot/client/host/adb_common.py +107 -94
  22. kotonebot/client/host/custom.py +118 -114
  23. kotonebot/client/host/leidian_host.py +196 -201
  24. kotonebot/client/host/mumu12_host.py +353 -358
  25. kotonebot/client/host/protocol.py +214 -213
  26. kotonebot/client/host/windows_common.py +58 -55
  27. kotonebot/client/implements/__init__.py +71 -7
  28. kotonebot/client/implements/adb.py +89 -85
  29. kotonebot/client/implements/adb_raw.py +162 -158
  30. kotonebot/client/implements/nemu_ipc/__init__.py +11 -7
  31. kotonebot/client/implements/nemu_ipc/external_renderer_ipc.py +284 -284
  32. kotonebot/client/implements/nemu_ipc/nemu_ipc.py +327 -327
  33. kotonebot/client/implements/remote_windows.py +188 -192
  34. kotonebot/client/implements/uiautomator2.py +85 -81
  35. kotonebot/client/implements/windows.py +176 -168
  36. kotonebot/client/protocol.py +69 -69
  37. kotonebot/client/registration.py +24 -24
  38. kotonebot/config/base_config.py +96 -96
  39. kotonebot/config/manager.py +36 -36
  40. kotonebot/errors.py +76 -71
  41. kotonebot/interop/win/__init__.py +10 -0
  42. kotonebot/interop/win/_mouse.py +311 -0
  43. kotonebot/interop/win/message_box.py +313 -313
  44. kotonebot/interop/win/reg.py +37 -37
  45. kotonebot/interop/win/shortcut.py +43 -43
  46. kotonebot/interop/win/task_dialog.py +513 -469
  47. kotonebot/logging/__init__.py +2 -2
  48. kotonebot/logging/log.py +17 -17
  49. kotonebot/primitives/__init__.py +17 -17
  50. kotonebot/primitives/geometry.py +862 -290
  51. kotonebot/primitives/visual.py +63 -63
  52. kotonebot/tools/mirror.py +354 -354
  53. kotonebot/ui/file_host/sensio.py +36 -36
  54. kotonebot/ui/file_host/tmp_send.py +54 -54
  55. kotonebot/ui/pushkit/__init__.py +3 -3
  56. kotonebot/ui/pushkit/image_host.py +88 -87
  57. kotonebot/ui/pushkit/protocol.py +13 -13
  58. kotonebot/ui/pushkit/wxpusher.py +54 -53
  59. kotonebot/ui/user.py +148 -143
  60. kotonebot/util.py +436 -409
  61. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/METADATA +82 -76
  62. kotonebot-0.5.0.dist-info/RECORD +71 -0
  63. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/licenses/LICENSE +673 -673
  64. kotonebot-0.3.1.dist-info/RECORD +0 -70
  65. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/WHEEL +0 -0
  66. {kotonebot-0.3.1.dist-info → kotonebot-0.5.0.dist-info}/top_level.txt +0 -0
@@ -1,3 +1,3 @@
1
- from .context import *
2
- from .context import _c
3
- from .task_action import task, action, task_registry, action_registry, current_callstack, Task, Action, tasks_from_id
1
+ from .context import *
2
+ from .context import _c
3
+ from .task_action import task, action, task_registry, action_registry, current_callstack, Task, Action, tasks_from_id
@@ -175,6 +175,8 @@ class ContextGlobalVars:
175
175
  self.__vars = dict[str, Any]()
176
176
  self.flow: FlowController = FlowController()
177
177
  """流程控制器,负责停止、暂停、恢复等操作"""
178
+ self.screenshot_data: MatLike | None = None
179
+ """截图数据"""
178
180
 
179
181
  def __getitem__(self, key: str) -> Any:
180
182
  return self.__vars[key]
@@ -197,6 +199,7 @@ class ContextGlobalVars:
197
199
  def clear(self):
198
200
  self.__vars.clear()
199
201
  self.flow.reset() # 重置流程控制器
202
+ self.screenshot_data = None
200
203
 
201
204
  def check_flow_control():
202
205
  """
@@ -220,45 +223,40 @@ class ContextStackVars:
220
223
  自动截图。即调用 `color`、`image`、`ocr` 上的方法时,会自动更新截图。
221
224
  * `manual`
222
225
  完全手动截图,不自动截图。如果在没有截图数据的情况下调用 `color` 等的方法,会抛出异常。
223
- * `manual-inherit`:
224
- 第一张截图继承自调用者,此后同手动截图。
225
- 如果调用者没有截图数据,则继承的截图数据为空。
226
- 如果在没有截图数据的情况下调用 `color` 等的方法,会抛出异常。
226
+ * ~~`manual-inherit`~~:
227
+ 已废弃。
227
228
  """
228
- self._screenshot: MatLike | None = None
229
- """截图数据"""
230
- self._inherit_screenshot: MatLike | None = None
231
- """继承的截图数据"""
232
229
 
233
230
  @property
234
231
  def screenshot(self) -> MatLike:
235
232
  match self.screenshot_mode:
236
- case 'manual':
237
- if self._screenshot is None:
238
- raise ValueError("No screenshot data found.")
239
- return self._screenshot
240
- case 'manual-inherit':
241
- # TODO: 这一部分要考虑和 device.screenshot() 合并
242
- if self._inherit_screenshot is not None:
243
- self._screenshot = self._inherit_screenshot
244
- self._inherit_screenshot = None
245
- if self._screenshot is None:
246
- raise ValueError("No screenshot data found.")
247
- return self._screenshot
233
+ case 'manual' | 'manual-inherit':
234
+ if vars.screenshot_data is None:
235
+ raise ValueError("No screenshot data found. Did you forget to call `device.screenshot()`?")
236
+ return vars.screenshot_data
248
237
  case 'auto':
249
- self._screenshot = device.screenshot()
250
- return self._screenshot
238
+ device.screenshot()
239
+ if vars.screenshot_data is None:
240
+ raise ValueError("No screenshot data found. Did you forget to call `device.screenshot()`?")
241
+ return vars.screenshot_data
251
242
  case _:
252
243
  raise ValueError(f"Invalid screenshot mode: {self.screenshot_mode}")
253
244
 
245
+ @property
246
+ @deprecated('Use `vars.screenshot_data` instead.')
247
+ def _screenshot(self) -> MatLike | None:
248
+ return vars.screenshot_data
249
+
250
+ @_screenshot.setter
251
+ @deprecated('Use `vars.screenshot_data` instead.')
252
+ def _screenshot(self, value: MatLike | None) -> None:
253
+ vars.screenshot_data = value
254
+
254
255
  @staticmethod
255
256
  def push(*, screenshot_mode: ScreenshotMode | None = None) -> 'ContextStackVars':
256
257
  vars = ContextStackVars()
257
258
  if screenshot_mode is not None:
258
259
  vars.screenshot_mode = screenshot_mode
259
- current = ContextStackVars.current()
260
- if current and vars.screenshot_mode == 'manual-inherit':
261
- vars._inherit_screenshot = current._screenshot
262
260
  ContextStackVars.stack.append(vars)
263
261
  return vars
264
262
 
@@ -266,7 +264,7 @@ class ContextStackVars:
266
264
  def pop() -> 'ContextStackVars':
267
265
  last = ContextStackVars.stack.pop()
268
266
  return last
269
-
267
+
270
268
  @staticmethod
271
269
  def current() -> 'ContextStackVars | None':
272
270
  if len(ContextStackVars.stack) == 0:
@@ -331,7 +329,7 @@ class ContextOcr:
331
329
  )
332
330
  self.context.device.last_find = ret.original_rect if ret else None
333
331
  return ret
334
-
332
+
335
333
  def find_all(
336
334
  self,
337
335
  patterns: Sequence[str | re.Pattern | StringMatchFunction],
@@ -366,7 +364,7 @@ class ContextOcr:
366
364
  ret = engine.expect(ContextStackVars.ensure_current().screenshot, pattern, rect=rect, hint=hint)
367
365
  self.context.device.last_find = ret.original_rect if ret else None
368
366
  return ret
369
-
367
+
370
368
  def expect_wait(
371
369
  self,
372
370
  pattern: str | re.Pattern | StringMatchFunction,
@@ -447,7 +445,7 @@ class ContextImage:
447
445
  等待指定图像出现。
448
446
  """
449
447
  is_manual = is_manual_screenshot_mode()
450
-
448
+
451
449
  start_time = time.time()
452
450
  while True:
453
451
  if is_manual:
@@ -702,7 +700,7 @@ class ContextConfig(Generic[T]):
702
700
  def current(self) -> UserConfig[T]:
703
701
  """
704
702
  当前配置数据。
705
-
703
+
706
704
  如果当前配置不存在,则使用默认值自动创建一个新配置。
707
705
  (不推荐,建议在 UI 中启动前要求用户手动创建,或自行创建一个默认配置。)
708
706
  """
@@ -729,7 +727,7 @@ class Forwarded:
729
727
  if self._FORWARD_getter is None:
730
728
  raise ContextNotInitializedError(f"Forwarded object {self._FORWARD_name} called before initialization.")
731
729
  return getattr(self._FORWARD_getter(), name)
732
-
730
+
733
731
  def __setattr__(self, name: str, value: Any):
734
732
  if name.startswith('_FORWARD_'):
735
733
  return object.__setattr__(self, name, value)
@@ -761,25 +759,20 @@ class ContextDevice(Generic[T_Device], Device):
761
759
  """
762
760
  check_flow_control()
763
761
  global next_wait, last_screenshot_time, next_wait_time
764
- current = ContextStackVars.ensure_current()
765
- if force:
766
- current._inherit_screenshot = None
767
- if current._inherit_screenshot is not None:
768
- img = current._inherit_screenshot
769
- current._inherit_screenshot = None
770
- else:
771
- if self._screenshot_interval is not None:
772
- self._screenshot_interval.wait()
773
-
774
- if next_wait == 'screenshot':
775
- delta = time.time() - last_screenshot_time
776
- if delta < next_wait_time:
777
- sleep(next_wait_time - delta)
778
- last_screenshot_time = time.time()
779
- next_wait_time = 0
780
- next_wait = None
781
- img = self._device.screenshot()
782
- current._screenshot = img
762
+ ContextStackVars.ensure_current()
763
+
764
+ if self._screenshot_interval is not None:
765
+ self._screenshot_interval.wait()
766
+
767
+ if next_wait == 'screenshot':
768
+ delta = time.time() - last_screenshot_time
769
+ if delta < next_wait_time:
770
+ sleep(next_wait_time - delta)
771
+ last_screenshot_time = time.time()
772
+ next_wait_time = 0
773
+ next_wait = None
774
+ img = self._device.screenshot()
775
+ vars.screenshot_data = img
783
776
  return img
784
777
 
785
778
  def __getattribute__(self, name: str):
@@ -787,7 +780,7 @@ class ContextDevice(Generic[T_Device], Device):
787
780
  return object.__getattribute__(self, name)
788
781
  else:
789
782
  return getattr(self._device, name)
790
-
783
+
791
784
  def __setattr__(self, name: str, value: Any):
792
785
  if name in ['_device', 'screenshot', 'of_android', 'of_windows']:
793
786
  return object.__setattr__(self, name, value)
@@ -853,7 +846,7 @@ class Context(Generic[T]):
853
846
  if vars is not None:
854
847
  self.__vars = vars
855
848
  if debug is not None:
856
- self.__debug = debug
849
+ self.__debug = debug
857
850
  if config is not None:
858
851
  self.__config = config
859
852
 
@@ -864,7 +857,7 @@ class Context(Generic[T]):
864
857
  @property
865
858
  def ocr(self) -> 'ContextOcr':
866
859
  return self.__ocr
867
-
860
+
868
861
  @property
869
862
  def image(self) -> 'ContextImage':
870
863
  return self.__image
@@ -876,7 +869,7 @@ class Context(Generic[T]):
876
869
  @property
877
870
  def vars(self) -> 'ContextGlobalVars':
878
871
  return self.__vars
879
-
872
+
880
873
  @property
881
874
  def debug(self) -> 'ContextDebug':
882
875
  return self.__debug
@@ -895,7 +888,7 @@ def rect_expand(rect: Rect, left: int = 0, top: int = 0, right: int = 0, bottom:
895
888
  def use_screenshot(*args: MatLike | None) -> MatLike:
896
889
  for img in args:
897
890
  if img is not None:
898
- ContextStackVars.ensure_current()._screenshot = img # HACK
891
+ vars.screenshot_data = img
899
892
  return img
900
893
  return device.screenshot()
901
894
 
@@ -1006,4 +999,4 @@ def manual_context(screenshot_mode: ScreenshotMode = 'auto') -> ManualContextMan
1006
999
  默认情况下,Context* 类仅允许在 @task/@action 函数中使用。
1007
1000
  如果想要在其他地方使用,使用此函数手动创建一个上下文。
1008
1001
  """
1009
- return ManualContextManager(screenshot_mode)
1002
+ return ManualContextManager(screenshot_mode)
@@ -1,176 +1,184 @@
1
- import logging
2
- from typing import Callable, ParamSpec, TypeVar, overload, Literal
3
- from dataclasses import dataclass
4
-
5
-
6
- from .context import ContextStackVars, ScreenshotMode
7
- from ...errors import TaskNotFoundError
8
-
9
- P = ParamSpec('P')
10
- R = TypeVar('R')
11
- logger = logging.getLogger(__name__)
12
-
13
- TaskRunAtType = Literal['pre', 'post', 'manual', 'regular'] | str
14
-
15
-
16
- @dataclass
17
- class Task:
18
- name: str
19
- id: str
20
- description: str
21
- func: Callable
22
- priority: int
23
- """
24
- 任务优先级,数字越大优先级越高。
25
- """
26
- run_at: TaskRunAtType = 'regular'
27
-
28
-
29
- @dataclass
30
- class Action:
31
- name: str
32
- description: str
33
- func: Callable
34
- priority: int
35
- """
36
- 动作优先级,数字越大优先级越高。
37
- """
38
-
39
-
40
- task_registry: dict[str, Task] = {}
41
- action_registry: dict[str, Action] = {}
42
- current_callstack: list[Task|Action] = []
43
-
44
- def _placeholder():
45
- raise NotImplementedError('Placeholder function')
46
-
47
- def task(
48
- name: str,
49
- task_id: str|None = None,
50
- description: str|None = None,
51
- *,
52
- pass_through: bool = False,
53
- priority: int = 0,
54
- screenshot_mode: ScreenshotMode = 'auto',
55
- run_at: TaskRunAtType = 'regular'
56
- ):
57
- """
58
- `task` 装饰器,用于标记一个函数为任务函数。
59
-
60
- :param name: 任务名称
61
- :param task_id: 任务 ID。如果为 None,则使用函数名称作为 ID。
62
- :param description: 任务描述。如果为 None,则使用函数的 docstring 作为描述。
63
- :param pass_through:
64
- 默认情况下, @task 装饰器会包裹任务函数,跟踪其执行情况。
65
- 如果不想跟踪,则设置此参数为 False。
66
- :param priority: 任务优先级,数字越大优先级越高。
67
- :param run_at: 任务运行时间。
68
- """
69
- # 设置 ID
70
- # 获取 caller 信息
71
- def _task_decorator(func: Callable[P, R]) -> Callable[P, R]:
72
- nonlocal description, task_id
73
- description = description or func.__doc__ or ''
74
- # TODO: task_id 冲突检测
75
- task_id = task_id or func.__name__
76
- task = Task(name, task_id, description, _placeholder, priority, run_at)
77
- task_registry[name] = task
78
- logger.debug(f'Task "{name}" registered.')
79
- if pass_through:
80
- return func
81
- else:
82
- def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
83
- current_callstack.append(task)
84
- vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
85
- ret = func(*args, **kwargs)
86
- ContextStackVars.pop()
87
- current_callstack.pop()
88
- return ret
89
- task.func = _wrapper
90
- return _wrapper
91
- return _task_decorator
92
-
93
- @overload
94
- def action(func: Callable[P, R]) -> Callable[P, R]: ...
95
-
96
- @overload
97
- def action(
98
- name: str,
99
- *,
100
- description: str|None = None,
101
- pass_through: bool = False,
102
- priority: int = 0,
103
- screenshot_mode: ScreenshotMode | None = None,
104
- ) -> Callable[[Callable[P, R]], Callable[P, R]]:
105
- """
106
- `action` 装饰器,用于标记一个函数为动作函数。
107
-
108
- :param name: 动作名称。如果为 None,则使用函数的名称作为名称。
109
- :param description: 动作描述。如果为 None,则使用函数的 docstring 作为描述。
110
- :param pass_through:
111
- 默认情况下, @action 装饰器会包裹动作函数,跟踪其执行情况。
112
- 如果不想跟踪,则设置此参数为 False。
113
- :param priority: 动作优先级,数字越大优先级越高。
114
- :param screenshot_mode: 截图模式。
115
- """
116
- ...
117
-
118
- # TODO: 需要找个地方统一管理这些属性名
119
- ATTR_ORIGINAL_FUNC = '_kb_inner'
120
- ATTR_ACTION_MARK = '__kb_action_mark'
121
- def action(*args, **kwargs):
122
- def _register(func: Callable, name: str, description: str|None = None, priority: int = 0) -> Action:
123
- description = description or func.__doc__ or ''
124
- action = Action(name, description, func, priority)
125
- action_registry[name] = action
126
- logger.debug(f'Action "{name}" registered.')
127
- return action
128
-
129
- if len(args) == 1 and isinstance(args[0], Callable):
130
- func = args[0]
131
- action = _register(_placeholder, func.__name__, func.__doc__)
132
- def _wrapper(*args: P.args, **kwargs: P.kwargs):
133
- current_callstack.append(action)
134
- vars = ContextStackVars.push()
135
- ret = func(*args, **kwargs)
136
- ContextStackVars.pop()
137
- current_callstack.pop()
138
- return ret
139
- setattr(_wrapper, ATTR_ORIGINAL_FUNC, func)
140
- setattr(_wrapper, ATTR_ACTION_MARK, True)
141
- action.func = _wrapper
142
- return _wrapper
143
- else:
144
- name = args[0]
145
- description = kwargs.get('description', None)
146
- pass_through = kwargs.get('pass_through', False)
147
- priority = kwargs.get('priority', 0)
148
- screenshot_mode = kwargs.get('screenshot_mode', None)
149
- def _action_decorator(func: Callable):
150
- nonlocal pass_through
151
- action = _register(_placeholder, name, description)
152
- pass_through = kwargs.get('pass_through', False)
153
- if pass_through:
154
- return func
155
- else:
156
- def _wrapper(*args: P.args, **kwargs: P.kwargs):
157
- current_callstack.append(action)
158
- vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
159
- ret = func(*args, **kwargs)
160
- ContextStackVars.pop()
161
- current_callstack.pop()
162
- return ret
163
- setattr(_wrapper, ATTR_ORIGINAL_FUNC, func)
164
- setattr(_wrapper, ATTR_ACTION_MARK, True)
165
- action.func = _wrapper
166
- return _wrapper
167
- return _action_decorator
168
-
169
- def tasks_from_id(task_ids: list[str]) -> list[Task]:
170
- result = []
171
- for tid in task_ids:
172
- target = next(task for task in task_registry.values() if task.id == tid)
173
- if target is None:
174
- raise TaskNotFoundError(f'Task "{tid}" not found.')
175
- result.append(target)
1
+ import logging
2
+ import warnings
3
+ from dataclasses import dataclass
4
+ from typing_extensions import deprecated
5
+ from typing import Callable, ParamSpec, TypeVar, overload, Literal
6
+
7
+
8
+ from .context import ContextStackVars, ScreenshotMode
9
+ from ...errors import TaskNotFoundError
10
+
11
+ P = ParamSpec('P')
12
+ R = TypeVar('R')
13
+ logger = logging.getLogger(__name__)
14
+
15
+ TaskRunAtType = Literal['pre', 'post', 'manual', 'regular'] | str
16
+
17
+
18
+ @dataclass
19
+ class Task:
20
+ name: str
21
+ id: str
22
+ description: str
23
+ func: Callable
24
+ priority: int
25
+ """
26
+ 任务优先级,数字越大优先级越高。
27
+ """
28
+ run_at: TaskRunAtType = 'regular'
29
+
30
+
31
+ @dataclass
32
+ class Action:
33
+ name: str
34
+ description: str
35
+ func: Callable
36
+ priority: int
37
+ """
38
+ 动作优先级,数字越大优先级越高。
39
+ """
40
+
41
+
42
+ task_registry: dict[str, Task] = {}
43
+ action_registry: dict[str, Action] = {}
44
+ current_callstack: list[Task|Action] = []
45
+
46
+ def _placeholder():
47
+ raise NotImplementedError('Placeholder function')
48
+
49
+ def task(
50
+ name: str,
51
+ task_id: str|None = None,
52
+ description: str|None = None,
53
+ *,
54
+ pass_through: bool = False,
55
+ priority: int = 0,
56
+ screenshot_mode: ScreenshotMode = 'auto',
57
+ run_at: TaskRunAtType = 'regular'
58
+ ):
59
+ """
60
+ `task` 装饰器,用于标记一个函数为任务函数。
61
+
62
+ :param name: 任务名称
63
+ :param task_id: 任务 ID。如果为 None,则使用函数名称作为 ID。
64
+ :param description: 任务描述。如果为 None,则使用函数的 docstring 作为描述。
65
+ :param pass_through:
66
+ 默认情况下, @task 装饰器会包裹任务函数,跟踪其执行情况。
67
+ 如果不想跟踪,则设置此参数为 False。
68
+ :param priority: 任务优先级,数字越大优先级越高。
69
+ :param run_at: 任务运行时间。
70
+ """
71
+ # 设置 ID
72
+ # 获取 caller 信息
73
+ def _task_decorator(func: Callable[P, R]) -> Callable[P, R]:
74
+ nonlocal description, task_id
75
+ description = description or func.__doc__ or ''
76
+ # TODO: task_id 冲突检测
77
+ task_id = task_id or func.__name__
78
+ task = Task(name, task_id, description, _placeholder, priority, run_at)
79
+ task_registry[name] = task
80
+ logger.debug(f'Task "{name}" registered.')
81
+ if pass_through:
82
+ return func
83
+ else:
84
+ def _wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
85
+ current_callstack.append(task)
86
+ vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
87
+ ret = func(*args, **kwargs)
88
+ ContextStackVars.pop()
89
+ current_callstack.pop()
90
+ return ret
91
+ task.func = _wrapper
92
+ return _wrapper
93
+ return _task_decorator
94
+
95
+ @overload
96
+ def action(func: Callable[P, R]) -> Callable[P, R]: ...
97
+
98
+ @deprecated('Use `action` with screenshot_mode=`manual` instead.')
99
+ @overload
100
+ def action(
101
+ name: str,
102
+ *,
103
+ description: str|None = None,
104
+ pass_through: bool = False,
105
+ priority: int = 0,
106
+ screenshot_mode: Literal['manual-inherit'],
107
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]: ...
108
+
109
+ @overload
110
+ def action(
111
+ name: str,
112
+ *,
113
+ description: str|None = None,
114
+ pass_through: bool = False,
115
+ priority: int = 0,
116
+ screenshot_mode: ScreenshotMode | None = None,
117
+ ) -> Callable[[Callable[P, R]], Callable[P, R]]:
118
+ """
119
+ `action` 装饰器,用于标记一个函数为动作函数。
120
+
121
+ :param name: 动作名称。如果为 None,则使用函数的名称作为名称。
122
+ :param description: 动作描述。如果为 None,则使用函数的 docstring 作为描述。
123
+ :param pass_through:
124
+ 默认情况下, @action 装饰器会包裹动作函数,跟踪其执行情况。
125
+ 如果不想跟踪,则设置此参数为 False。
126
+ :param priority: 动作优先级,数字越大优先级越高。
127
+ :param screenshot_mode: 截图模式。
128
+ """
129
+ ...
130
+
131
+ def action(*args, **kwargs):
132
+ def _register(func: Callable, name: str, description: str|None = None, priority: int = 0) -> Action:
133
+ description = description or func.__doc__ or ''
134
+ action = Action(name, description, func, priority)
135
+ action_registry[name] = action
136
+ logger.debug(f'Action "{name}" registered.')
137
+ return action
138
+
139
+ if len(args) == 1 and isinstance(args[0], Callable):
140
+ func = args[0]
141
+ action = _register(_placeholder, func.__name__, func.__doc__)
142
+ def _wrapper(*args: P.args, **kwargs: P.kwargs):
143
+ current_callstack.append(action)
144
+ vars = ContextStackVars.push()
145
+ ret = func(*args, **kwargs)
146
+ ContextStackVars.pop()
147
+ current_callstack.pop()
148
+ return ret
149
+ action.func = _wrapper
150
+ return _wrapper
151
+ else:
152
+ name = args[0]
153
+ description = kwargs.get('description', None)
154
+ pass_through = kwargs.get('pass_through', False)
155
+ priority = kwargs.get('priority', 0)
156
+ screenshot_mode = kwargs.get('screenshot_mode', None)
157
+ if screenshot_mode == 'manual-inherit':
158
+ warnings.warn('`screenshot_mode=manual-inherit` is deprecated. Use `screenshot_mode=manual` instead.')
159
+ def _action_decorator(func: Callable):
160
+ nonlocal pass_through
161
+ action = _register(_placeholder, name, description)
162
+ pass_through = kwargs.get('pass_through', False)
163
+ if pass_through:
164
+ return func
165
+ else:
166
+ def _wrapper(*args: P.args, **kwargs: P.kwargs):
167
+ current_callstack.append(action)
168
+ vars = ContextStackVars.push(screenshot_mode=screenshot_mode)
169
+ ret = func(*args, **kwargs)
170
+ ContextStackVars.pop()
171
+ current_callstack.pop()
172
+ return ret
173
+ action.func = _wrapper
174
+ return _wrapper
175
+ return _action_decorator
176
+
177
+ def tasks_from_id(task_ids: list[str]) -> list[Task]:
178
+ result = []
179
+ for tid in task_ids:
180
+ target = next(task for task in task_registry.values() if task.id == tid)
181
+ if target is None:
182
+ raise TaskNotFoundError(f'Task "{tid}" not found.')
183
+ result.append(target)
176
184
  return result