android-midscene-automation 0.2.4 → 1.1.1

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 (525) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +159 -29
  3. package/artemis-main/.dockerignore +15 -0
  4. package/artemis-main/.env.example +34 -0
  5. package/artemis-main/CONTRIBUTING.md +35 -0
  6. package/artemis-main/Dockerfile +68 -0
  7. package/artemis-main/LICENSE +202 -0
  8. package/artemis-main/Makefile +115 -0
  9. package/artemis-main/README.md +322 -0
  10. package/artemis-main/README_CN.md +320 -0
  11. package/artemis-main/apps/__init__.py +15 -0
  12. package/artemis-main/apps/admin_console/__init__.py +19 -0
  13. package/artemis-main/apps/admin_console/core/__init__.py +13 -0
  14. package/artemis-main/apps/admin_console/core/config.py +43 -0
  15. package/artemis-main/apps/admin_console/core/security.py +178 -0
  16. package/artemis-main/apps/admin_console/core/state.py +243 -0
  17. package/artemis-main/apps/admin_console/database/__init__.py +13 -0
  18. package/artemis-main/apps/admin_console/database/connection.py +60 -0
  19. package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
  20. package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
  21. package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
  22. package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
  23. package/artemis-main/apps/admin_console/index.html +3787 -0
  24. package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
  25. package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
  26. package/artemis-main/apps/admin_console/routers/media.py +198 -0
  27. package/artemis-main/apps/admin_console/routers/replay.py +91 -0
  28. package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
  29. package/artemis-main/apps/admin_console/routers/steps.py +109 -0
  30. package/artemis-main/apps/admin_console/routers/stream.py +56 -0
  31. package/artemis-main/apps/admin_console/routers/system.py +761 -0
  32. package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
  33. package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
  34. package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
  35. package/artemis-main/apps/admin_console/server.py +508 -0
  36. package/artemis-main/apps/admin_console/services/__init__.py +13 -0
  37. package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
  38. package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
  39. package/artemis-main/apps/admin_console/services/media_service.py +623 -0
  40. package/artemis-main/apps/admin_console/services/model_service.py +148 -0
  41. package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
  42. package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
  43. package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
  44. package/artemis-main/apps/showcase_ui/README.md +31 -0
  45. package/artemis-main/apps/showcase_ui/angular.json +132 -0
  46. package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
  47. package/artemis-main/apps/showcase_ui/package.json +39 -0
  48. package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
  49. package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
  50. package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
  51. package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
  52. package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
  53. package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
  54. package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
  55. package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
  56. package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
  57. package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
  58. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
  59. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
  60. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
  61. package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
  62. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
  63. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
  64. package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
  65. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
  66. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
  67. package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
  68. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
  69. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
  70. package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
  71. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
  72. package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
  73. package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
  74. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
  75. package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
  76. package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
  77. package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
  78. package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
  79. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
  80. package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
  81. package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
  82. package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
  83. package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
  84. package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
  85. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
  86. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
  87. package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
  88. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
  89. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
  90. package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
  91. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
  92. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
  93. package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
  94. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
  95. package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
  96. package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
  97. package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
  98. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
  99. package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
  100. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
  101. package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
  102. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
  103. package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
  104. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
  105. package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
  106. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
  107. package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
  108. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
  109. package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
  110. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
  111. package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
  112. package/artemis-main/apps/showcase_ui/src/index.html +37 -0
  113. package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
  114. package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
  115. package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
  116. package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
  117. package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
  118. package/artemis-main/artemis/__init__.py +44 -0
  119. package/artemis-main/artemis/__main__.py +18 -0
  120. package/artemis-main/artemis/_legacy_imports.py +94 -0
  121. package/artemis-main/artemis/_version.py +30 -0
  122. package/artemis-main/artemis/agents/__init__.py +17 -0
  123. package/artemis-main/artemis/agents/checker/checker.json +9 -0
  124. package/artemis-main/artemis/agents/checker/checker.py +859 -0
  125. package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
  126. package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
  127. package/artemis-main/artemis/agents/explorer/constants.py +52 -0
  128. package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
  129. package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
  130. package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
  131. package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
  132. package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
  133. package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
  134. package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
  135. package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
  136. package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
  137. package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
  138. package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
  139. package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
  140. package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
  141. package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
  142. package/artemis-main/artemis/agents/flash/runner.py +1167 -0
  143. package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
  144. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
  145. package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
  146. package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
  147. package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
  148. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
  149. package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
  150. package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
  151. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
  152. package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
  153. package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
  154. package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
  155. package/artemis-main/artemis/agents/operator/operator.json +3 -0
  156. package/artemis-main/artemis/agents/operator/operator.py +1394 -0
  157. package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
  158. package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
  159. package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
  160. package/artemis-main/artemis/agents/planner/planner.json +32 -0
  161. package/artemis-main/artemis/agents/planner/planner.py +436 -0
  162. package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
  163. package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
  164. package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
  165. package/artemis-main/artemis/agents/validator/categories.py +33 -0
  166. package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
  167. package/artemis-main/artemis/agents/validator/incidents.py +120 -0
  168. package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
  169. package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
  170. package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
  171. package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
  172. package/artemis-main/artemis/agents/validator/validator.py +188 -0
  173. package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
  174. package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
  175. package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
  176. package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
  177. package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
  178. package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
  179. package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
  180. package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
  181. package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
  182. package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
  183. package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
  184. package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
  185. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
  186. package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
  187. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
  188. package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
  189. package/artemis-main/artemis/clients/accessibility_client.py +438 -0
  190. package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
  191. package/artemis-main/artemis/config/__init__.py +309 -0
  192. package/artemis-main/artemis/config/agent.py +1098 -0
  193. package/artemis-main/artemis/config/constants.py +151 -0
  194. package/artemis-main/artemis/config/core/__init__.py +24 -0
  195. package/artemis-main/artemis/config/core/engine.py +118 -0
  196. package/artemis-main/artemis/config/core/interpolator.py +78 -0
  197. package/artemis-main/artemis/config/llm.py +267 -0
  198. package/artemis-main/artemis/config/paths.py +244 -0
  199. package/artemis-main/artemis/config/runtime.py +197 -0
  200. package/artemis-main/artemis/config/settings.py +315 -0
  201. package/artemis-main/artemis/constants.py +47 -0
  202. package/artemis-main/artemis/context.py +289 -0
  203. package/artemis-main/artemis/controllers/__init__.py +41 -0
  204. package/artemis-main/artemis/controllers/controller_factory.py +41 -0
  205. package/artemis-main/artemis/controllers/device_controller.py +29 -0
  206. package/artemis-main/artemis/controllers/types.py +37 -0
  207. package/artemis-main/artemis/controllers/unified_controller.py +649 -0
  208. package/artemis-main/artemis/core/__init__.py +21 -0
  209. package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
  210. package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
  211. package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
  212. package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
  213. package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
  214. package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
  215. package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
  216. package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
  217. package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
  218. package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
  219. package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
  220. package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
  221. package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
  222. package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
  223. package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
  224. package/artemis-main/artemis/core/tool_declaration.py +43 -0
  225. package/artemis-main/artemis/core/tool_failure.py +56 -0
  226. package/artemis-main/artemis/data_engine/context_vars.py +24 -0
  227. package/artemis-main/artemis/data_engine/engine.py +1615 -0
  228. package/artemis-main/artemis/data_engine/history_reader.py +142 -0
  229. package/artemis-main/artemis/data_engine/models.py +131 -0
  230. package/artemis-main/artemis/data_engine/storage.py +1245 -0
  231. package/artemis-main/artemis/data_engine/trace.py +727 -0
  232. package/artemis-main/artemis/drivers/__init__.py +48 -0
  233. package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
  234. package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
  235. package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
  236. package/artemis-main/artemis/drivers/android/recorder.py +57 -0
  237. package/artemis-main/artemis/drivers/base.py +290 -0
  238. package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
  239. package/artemis-main/artemis/drivers/factory.py +80 -0
  240. package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
  241. package/artemis-main/artemis/drivers/types.py +67 -0
  242. package/artemis-main/artemis/graph/checkpoints.py +1243 -0
  243. package/artemis-main/artemis/graph/graph.py +1055 -0
  244. package/artemis-main/artemis/graph/perception.py +234 -0
  245. package/artemis-main/artemis/graph/state.py +151 -0
  246. package/artemis-main/artemis/graph/visibility.py +238 -0
  247. package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
  248. package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
  249. package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
  250. package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
  251. package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
  252. package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
  253. package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
  254. package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
  255. package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
  256. package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
  257. package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
  258. package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
  259. package/artemis-main/artemis/interfaces/cli/main.py +109 -0
  260. package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
  261. package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
  262. package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
  263. package/artemis-main/artemis/llm/__init__.py +25 -0
  264. package/artemis-main/artemis/llm/fake_model.py +179 -0
  265. package/artemis-main/artemis/llm/google/__init__.py +57 -0
  266. package/artemis-main/artemis/llm/google/provider.py +185 -0
  267. package/artemis-main/artemis/llm/google/usage.py +148 -0
  268. package/artemis-main/artemis/llm/parser.py +32 -0
  269. package/artemis-main/artemis/llm/reliability.py +267 -0
  270. package/artemis-main/artemis/llm/router.py +389 -0
  271. package/artemis-main/artemis/llm/schemas.py +36 -0
  272. package/artemis-main/artemis/llm/structured.py +196 -0
  273. package/artemis-main/artemis/main.py +58 -0
  274. package/artemis-main/artemis/mcp/__init__.py +20 -0
  275. package/artemis-main/artemis/mcp/action_executor.py +760 -0
  276. package/artemis-main/artemis/mcp/action_manifest.py +320 -0
  277. package/artemis-main/artemis/mcp/action_names.py +172 -0
  278. package/artemis-main/artemis/mcp/action_server.py +202 -0
  279. package/artemis-main/artemis/mcp/action_session.py +245 -0
  280. package/artemis-main/artemis/mcp/action_specs.py +868 -0
  281. package/artemis-main/artemis/mcp/action_types.py +86 -0
  282. package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
  283. package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
  284. package/artemis-main/artemis/mcp/actuators/base.py +113 -0
  285. package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
  286. package/artemis-main/artemis/mcp/adb_server.py +490 -0
  287. package/artemis-main/artemis/mcp/observation.py +133 -0
  288. package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
  289. package/artemis-main/artemis/memory/__init__.py +90 -0
  290. package/artemis-main/artemis/memory/chunking.py +1843 -0
  291. package/artemis-main/artemis/memory/context_policy.py +226 -0
  292. package/artemis-main/artemis/memory/step_capsule.md +73 -0
  293. package/artemis-main/artemis/memory/step_memory.py +281 -0
  294. package/artemis-main/artemis/memory/transcript.py +798 -0
  295. package/artemis-main/artemis/platform/__init__.py +61 -0
  296. package/artemis-main/artemis/platform/base.py +104 -0
  297. package/artemis-main/artemis/platform/darwin.py +184 -0
  298. package/artemis-main/artemis/platform/linux.py +207 -0
  299. package/artemis-main/artemis/platform/windows.py +193 -0
  300. package/artemis-main/artemis/resources/__init__.py +46 -0
  301. package/artemis-main/artemis/runtime/__init__.py +108 -0
  302. package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
  303. package/artemis-main/artemis/runtime/awake_lease.py +283 -0
  304. package/artemis-main/artemis/runtime/awake_service.py +343 -0
  305. package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
  306. package/artemis-main/artemis/runtime/daemon_client.py +422 -0
  307. package/artemis-main/artemis/runtime/device_lock.py +1013 -0
  308. package/artemis-main/artemis/runtime/device_pool.py +478 -0
  309. package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
  310. package/artemis-main/artemis/runtime/process_probe.py +98 -0
  311. package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
  312. package/artemis-main/artemis/runtime/supervisor.py +121 -0
  313. package/artemis-main/artemis/runtime/trace_store.py +375 -0
  314. package/artemis-main/artemis/sdk/__init__.py +26 -0
  315. package/artemis-main/artemis/sdk/agent.py +485 -0
  316. package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
  317. package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
  318. package/artemis-main/artemis/sdk/constants.py +15 -0
  319. package/artemis-main/artemis/sdk/run_outcome.py +58 -0
  320. package/artemis-main/artemis/sdk/types/__init__.py +61 -0
  321. package/artemis-main/artemis/sdk/types/agent.py +89 -0
  322. package/artemis-main/artemis/sdk/utils.py +28 -0
  323. package/artemis-main/artemis/services/llm.py +1040 -0
  324. package/artemis-main/artemis/services/token_meter.py +267 -0
  325. package/artemis-main/artemis/telemetry/__init__.py +35 -0
  326. package/artemis-main/artemis/telemetry/models.py +64 -0
  327. package/artemis-main/artemis/telemetry/replay.py +34 -0
  328. package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
  329. package/artemis-main/artemis/telemetry/tracer.py +85 -0
  330. package/artemis-main/artemis/toolchain/__init__.py +52 -0
  331. package/artemis-main/artemis/toolchain/descriptors.py +92 -0
  332. package/artemis-main/artemis/toolchain/resolver.py +185 -0
  333. package/artemis-main/artemis/tools/base.py +232 -0
  334. package/artemis-main/artemis/tools/command_tool.py +1240 -0
  335. package/artemis-main/artemis/tools/committee_prompts.json +5 -0
  336. package/artemis-main/artemis/tools/committee_tool.py +422 -0
  337. package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
  338. package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
  339. package/artemis-main/artemis/tools/explorer_tool.py +581 -0
  340. package/artemis-main/artemis/tools/history/__init__.py +398 -0
  341. package/artemis-main/artemis/tools/history/replay.py +120 -0
  342. package/artemis-main/artemis/tools/history/screenshot.py +139 -0
  343. package/artemis-main/artemis/tools/history/search.py +563 -0
  344. package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
  345. package/artemis-main/artemis/tools/index.py +47 -0
  346. package/artemis-main/artemis/tools/log_tool.py +108 -0
  347. package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
  348. package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
  349. package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
  350. package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
  351. package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
  352. package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
  353. package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
  354. package/artemis-main/artemis/tools/scratchpad.py +819 -0
  355. package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
  356. package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
  357. package/artemis-main/artemis/tools/video_tool.py +261 -0
  358. package/artemis-main/artemis/tools/wait_tool.py +97 -0
  359. package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
  360. package/artemis-main/artemis/utils/coordinates.py +606 -0
  361. package/artemis-main/artemis/utils/credentials_validator.py +201 -0
  362. package/artemis-main/artemis/utils/cv_canvas.py +189 -0
  363. package/artemis-main/artemis/utils/cython_compat.py +45 -0
  364. package/artemis-main/artemis/utils/element_hit_test.py +80 -0
  365. package/artemis-main/artemis/utils/image_diff.py +335 -0
  366. package/artemis-main/artemis/utils/image_hash.py +80 -0
  367. package/artemis-main/artemis/utils/notes.py +470 -0
  368. package/artemis-main/artemis/utils/ocr_api.py +105 -0
  369. package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
  370. package/artemis-main/artemis/utils/plan_grammar.py +513 -0
  371. package/artemis-main/artemis/utils/python_executor.py +213 -0
  372. package/artemis-main/artemis/utils/startup_progress.py +60 -0
  373. package/artemis-main/artemis/utils/task_tree.py +1174 -0
  374. package/artemis-main/artemis/utils/text.py +37 -0
  375. package/artemis-main/artemis/utils/ui_filter.py +651 -0
  376. package/artemis-main/artemis/utils/video.py +685 -0
  377. package/artemis-main/artemis/utils/visualization.py +802 -0
  378. package/artemis-main/config/artemis.example.jsonc +12 -0
  379. package/artemis-main/mcp_server/README.md +111 -0
  380. package/artemis-main/mcp_server/__init__.py +21 -0
  381. package/artemis-main/mcp_server/__main__.py +20 -0
  382. package/artemis-main/mcp_server/background/__init__.py +15 -0
  383. package/artemis-main/mcp_server/background/task_runner.py +453 -0
  384. package/artemis-main/mcp_server/base.py +49 -0
  385. package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
  386. package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
  387. package/artemis-main/mcp_server/notifiers/base.py +45 -0
  388. package/artemis-main/mcp_server/notifiers/composite.py +87 -0
  389. package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
  390. package/artemis-main/mcp_server/notifiers/file.py +69 -0
  391. package/artemis-main/mcp_server/notifiers/script.py +90 -0
  392. package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
  393. package/artemis-main/mcp_server/rules.md +68 -0
  394. package/artemis-main/mcp_server/server.py +106 -0
  395. package/artemis-main/mcp_server/tools/__init__.py +29 -0
  396. package/artemis-main/mcp_server/tools/device_state.py +107 -0
  397. package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
  398. package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
  399. package/artemis-main/mcp_server/tools/task_manager.py +627 -0
  400. package/artemis-main/mcp_server/tools/task_runner.py +548 -0
  401. package/artemis-main/mcp_server/utils/__init__.py +15 -0
  402. package/artemis-main/mcp_server/utils/device_utils.py +180 -0
  403. package/artemis-main/mcp_server/utils/env_utils.py +65 -0
  404. package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
  405. package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
  406. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
  407. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
  408. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
  409. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
  410. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
  411. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
  412. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
  413. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
  414. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
  415. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
  416. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
  417. package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
  418. package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
  419. package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
  420. package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
  421. package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
  422. package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
  423. package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
  424. package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
  425. package/artemis-main/packages/artemis-client/README.md +112 -0
  426. package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
  427. package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
  428. package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
  429. package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
  430. package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
  431. package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
  432. package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
  433. package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
  434. package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
  435. package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
  436. package/artemis-main/pyproject.toml +259 -0
  437. package/artemis-main/pyright-core.json +17 -0
  438. package/artemis-main/scripts/check_dependency_sources.py +68 -0
  439. package/artemis-main/scripts/install_deps.ps1 +520 -0
  440. package/artemis-main/scripts/install_deps.sh +533 -0
  441. package/artemis-main/scripts/quality_ratchet.py +97 -0
  442. package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
  443. package/artemis-main/scripts/run_edge_case_part.py +327 -0
  444. package/artemis-main/scripts/run_ui.py +242 -0
  445. package/artemis-main/scripts/start.ps1 +500 -0
  446. package/artemis-main/scripts/test_all_edge_cases.py +269 -0
  447. package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
  448. package/artemis-main/setup.py +164 -0
  449. package/artemis-main/start.bat +35 -0
  450. package/artemis-main/start.sh +423 -0
  451. package/artemis-main/third_party/mobile_use/LICENSE +190 -0
  452. package/artemis-main/third_party/mobile_use/METADATA +27 -0
  453. package/artemis-main/third_party/mobile_use/NOTICE +12 -0
  454. package/artemis-main/third_party/mobile_use/__init__.py +19 -0
  455. package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
  456. package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
  457. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
  458. package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
  459. package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
  460. package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
  461. package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
  462. package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
  463. package/artemis-main/third_party/mobile_use/config/output.py +184 -0
  464. package/artemis-main/third_party/mobile_use/context.py +119 -0
  465. package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
  466. package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
  467. package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
  468. package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
  469. package/artemis-main/third_party/mobile_use/graph.py +54 -0
  470. package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
  471. package/artemis-main/third_party/mobile_use/main.py +130 -0
  472. package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
  473. package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
  474. package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
  475. package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
  476. package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
  477. package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
  478. package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
  479. package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
  480. package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
  481. package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
  482. package/artemis-main/third_party/mobile_use/testing.py +59 -0
  483. package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
  484. package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
  485. package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
  486. package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
  487. package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
  488. package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
  489. package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
  490. package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
  491. package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
  492. package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
  493. package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
  494. package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
  495. package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
  496. package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
  497. package/artemis-main/uv.lock +4908 -0
  498. package/bin/android-midscene-automation.js +1 -5
  499. package/bin/artemis-config.mjs +15 -0
  500. package/bin/artemis-embedded.py +56 -0
  501. package/bin/start-artemis.mjs +30 -0
  502. package/bin/start-dev.mjs +112 -0
  503. package/bin/test_artemis_embedded.py +44 -0
  504. package/package.json +4 -2
  505. package/server/artemis.ts +34 -0
  506. package/server/http-api.ts +12 -0
  507. package/src/App.vue +10 -5
  508. package/src/api.ts +5 -0
  509. package/src/appium-recorder/AppiumPage.vue +1 -1
  510. package/src/appium-recorder/components/ComponentInspector.vue +1 -1
  511. package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
  512. package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
  513. package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
  514. package/src/appium-recorder/flow-appearance.ts +4 -4
  515. package/src/components/AppSidebar.vue +8 -7
  516. package/src/components/MidsceneMaintenanceNotice.vue +17 -0
  517. package/src/components/analytics/AnalysisCharts.vue +4 -4
  518. package/src/components/analytics/ReportAnalysis.vue +1 -1
  519. package/src/components/generator/TestCaseFileUpload.vue +1 -1
  520. package/src/components/help/HelpCenter.vue +56 -37
  521. package/src/pages/AiGeneratorPage.vue +1 -1
  522. package/src/pages/ArtemisPage.vue +72 -0
  523. package/src/pages/ConfigPage.vue +18 -19
  524. package/src/style.css +340 -121
  525. package/src/types.ts +1 -1
@@ -0,0 +1,819 @@
1
+ # Copyright 2026 Google LLC
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from typing import Any
16
+
17
+ from langchain_core.messages import ToolMessage
18
+ from langchain_core.tools.base import BaseTool
19
+ from pydantic import BaseModel, Field
20
+
21
+ from artemis.core.tool_failure import ToolFailure
22
+ from artemis.context import ArtemisContext
23
+ from artemis.data_engine.trace import trace_langchain_tool
24
+ from artemis.drivers.base import BaseDeviceDriver
25
+ from artemis.graph.state import State
26
+ from artemis.tools.base import ArtemisTool
27
+ from artemis.tools.tool_wrapper import ToolWrapper
28
+ from artemis.utils.cython_compat import CyFunctionDetector
29
+ from artemis.utils.notes import (
30
+ APPEND_NOTE_ARG_CONTENT_DESC,
31
+ APPEND_NOTE_ARG_KEY_DESC,
32
+ APPEND_NOTE_DOCSTRING,
33
+ LIST_NOTES_DOCSTRING,
34
+ READ_NOTE_ARG_KEY_DESC,
35
+ READ_NOTE_DOCSTRING,
36
+ SAVE_NOTE_ARG_CONTENT_DESC,
37
+ SAVE_NOTE_ARG_KEY_DESC,
38
+ SAVE_NOTE_DOCSTRING,
39
+ UPDATE_NOTE_ARG_KEY_DESC,
40
+ UPDATE_NOTE_ARG_REPLACEMENT_DESC,
41
+ UPDATE_NOTE_ARG_TARGET_DESC,
42
+ UPDATE_NOTE_DOCSTRING,
43
+ append_note_content,
44
+ format_list_notes_failure,
45
+ format_list_notes_success,
46
+ format_read_note_failure,
47
+ format_read_note_success,
48
+ list_notes_info,
49
+ read_note_content,
50
+ save_note_content,
51
+ update_note_content,
52
+ )
53
+ from third_party.mobile_use.tools.tool_wrapper import make_save_note_wrapper
54
+ from third_party.mobile_use.utils.logger import get_logger
55
+
56
+ logger = get_logger(__name__)
57
+
58
+
59
+ class SaveNoteArgs(BaseModel):
60
+ """Arguments schema for saving notes."""
61
+
62
+ model_config = {"ignored_types": (CyFunctionDetector,)}
63
+ key: str = Field(..., description=SAVE_NOTE_ARG_KEY_DESC)
64
+ content: str = Field(..., description=SAVE_NOTE_ARG_CONTENT_DESC)
65
+
66
+
67
+ class AppendNoteArgs(BaseModel):
68
+ """Arguments schema for appending notes."""
69
+
70
+ model_config = {"ignored_types": (CyFunctionDetector,)}
71
+ key: str = Field(..., description=APPEND_NOTE_ARG_KEY_DESC)
72
+ content: str = Field(..., description=APPEND_NOTE_ARG_CONTENT_DESC)
73
+
74
+
75
+ class UpdateNoteArgs(BaseModel):
76
+ """Arguments schema for updating notes."""
77
+
78
+ model_config = {"ignored_types": (CyFunctionDetector,)}
79
+ key: str = Field(..., description=UPDATE_NOTE_ARG_KEY_DESC)
80
+ target: str = Field(..., description=UPDATE_NOTE_ARG_TARGET_DESC)
81
+ replacement: str = Field(..., description=UPDATE_NOTE_ARG_REPLACEMENT_DESC)
82
+
83
+
84
+ class ReadNoteArgs(BaseModel):
85
+ """Arguments schema for reading notes."""
86
+
87
+ model_config = {"ignored_types": (CyFunctionDetector,)}
88
+ key: str = Field(..., description=READ_NOTE_ARG_KEY_DESC)
89
+ start_line: int | None = Field(None, description="Start line to read (1-indexed, inclusive)")
90
+ end_line: int | None = Field(None, description="End line to read (1-indexed, inclusive)")
91
+
92
+
93
+ class ListNotesArgs(BaseModel):
94
+ """Arguments schema for listing notes."""
95
+
96
+ model_config = {"ignored_types": (CyFunctionDetector,)}
97
+
98
+
99
+ class SaveNoteTool(ArtemisTool):
100
+ """Universal tool for saving notes to persistent memory."""
101
+
102
+ def __init__(self):
103
+ super().__init__(
104
+ name="save_note",
105
+ description=SAVE_NOTE_DOCSTRING,
106
+ args_schema=SaveNoteArgs,
107
+ category="memory",
108
+ )
109
+
110
+ # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
111
+ async def execute(
112
+ self,
113
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
114
+ ctx: ArtemisContext | None = None,
115
+ key: str | None = None,
116
+ content: str | None = None,
117
+ tool_call_id: str | None = None,
118
+ state: State | None = None,
119
+ **kwargs: Any,
120
+ ) -> Any:
121
+ k = (
122
+ key
123
+ if key is not None
124
+ else (
125
+ kwargs.get("key")
126
+ or kwargs.get("Key")
127
+ or kwargs.get("title")
128
+ or kwargs.get("Title")
129
+ or ""
130
+ )
131
+ )
132
+ c = (
133
+ content
134
+ if content is not None
135
+ else (kwargs.get("content") or kwargs.get("Content") or "")
136
+ )
137
+ tcid = (tool_call_id if tool_call_id is not None else kwargs.get("tool_call_id")) or ""
138
+ st = state if state is not None else kwargs.get("state")
139
+
140
+ try:
141
+ logger.info(f"save_note called with key='{k}'")
142
+ base_dir = (
143
+ ctx.data_engine.base_dir
144
+ if (
145
+ ctx
146
+ and getattr(ctx, "data_engine", None)
147
+ and getattr(ctx.data_engine, "base_dir", None)
148
+ )
149
+ else "."
150
+ )
151
+ save_note_content(base_dir, k, c)
152
+ agent_outcome = f"Saved note '{k}'."
153
+ status = "success"
154
+ except Exception as e: # pylint: disable=broad-exception-caught
155
+ agent_outcome = f"Failed to save note {k}.md: {e}"
156
+ status = "error"
157
+
158
+ if st is not None:
159
+ return ToolMessage(
160
+ tool_call_id=tcid,
161
+ content=agent_outcome,
162
+ status=status,
163
+ )
164
+
165
+ return agent_outcome
166
+
167
+
168
+ # Universal tool instance & aliases
169
+ save_note = SaveNoteTool()
170
+ SaveNote = SaveNoteTool
171
+
172
+
173
+ def get_save_note_tool(ctx: ArtemisContext) -> BaseTool:
174
+ """Exports save_note as a LangChain BaseTool."""
175
+ return trace_langchain_tool(save_note.to_langchain_tool(ctx), ctx)
176
+
177
+
178
+ class AppendNoteTool(ArtemisTool):
179
+ """Universal tool for appending text content to notes in persistent memory."""
180
+
181
+ def __init__(self):
182
+ super().__init__(
183
+ name="append_note",
184
+ description=APPEND_NOTE_DOCSTRING,
185
+ args_schema=AppendNoteArgs,
186
+ category="memory",
187
+ )
188
+
189
+ # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
190
+ async def execute(
191
+ self,
192
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
193
+ ctx: ArtemisContext | None = None,
194
+ key: str | None = None,
195
+ content: str | None = None,
196
+ tool_call_id: str | None = None,
197
+ state: State | None = None,
198
+ **kwargs: Any,
199
+ ) -> Any:
200
+ k = (
201
+ key
202
+ if key is not None
203
+ else (
204
+ kwargs.get("key")
205
+ or kwargs.get("Key")
206
+ or kwargs.get("title")
207
+ or kwargs.get("Title")
208
+ or ""
209
+ )
210
+ )
211
+ c = (
212
+ content
213
+ if content is not None
214
+ else (kwargs.get("content") or kwargs.get("Content") or "")
215
+ )
216
+ tcid = (tool_call_id if tool_call_id is not None else kwargs.get("tool_call_id")) or ""
217
+ st = state if state is not None else kwargs.get("state")
218
+
219
+ try:
220
+ logger.info(f"append_note called with key='{k}'")
221
+ base_dir = (
222
+ ctx.data_engine.base_dir
223
+ if (
224
+ ctx
225
+ and getattr(ctx, "data_engine", None)
226
+ and getattr(ctx.data_engine, "base_dir", None)
227
+ )
228
+ else "."
229
+ )
230
+ append_note_content(base_dir, k, c)
231
+ agent_outcome = f"Appended to note '{k}'."
232
+ status = "success"
233
+ except Exception as e: # pylint: disable=broad-exception-caught
234
+ agent_outcome = f"Failed to append note {k}.md: {e}"
235
+ status = "error"
236
+
237
+ if st is not None:
238
+ return ToolMessage(
239
+ tool_call_id=tcid,
240
+ content=agent_outcome,
241
+ status=status,
242
+ )
243
+
244
+ return agent_outcome
245
+
246
+
247
+ # Universal tool instance & aliases
248
+ append_note = AppendNoteTool()
249
+ AppendNote = AppendNoteTool
250
+
251
+
252
+ def get_append_note_tool(ctx: ArtemisContext) -> BaseTool:
253
+ """Exports append_note as a LangChain BaseTool."""
254
+ return trace_langchain_tool(append_note.to_langchain_tool(ctx), ctx)
255
+
256
+
257
+ class UpdateNoteTool(ArtemisTool):
258
+ """Universal tool for updating notes by replacing a target string in persistent memory."""
259
+
260
+ def __init__(self):
261
+ super().__init__(
262
+ name="update_note",
263
+ description=UPDATE_NOTE_DOCSTRING,
264
+ args_schema=UpdateNoteArgs,
265
+ category="memory",
266
+ )
267
+
268
+ # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
269
+ async def execute(
270
+ self,
271
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
272
+ ctx: ArtemisContext | None = None,
273
+ key: str | None = None,
274
+ target: str | None = None,
275
+ replacement: str | None = None,
276
+ tool_call_id: str | None = None,
277
+ state: State | None = None,
278
+ **kwargs: Any,
279
+ ) -> Any:
280
+ k = (
281
+ key
282
+ if key is not None
283
+ else (
284
+ kwargs.get("key")
285
+ or kwargs.get("Key")
286
+ or kwargs.get("title")
287
+ or kwargs.get("Title")
288
+ or ""
289
+ )
290
+ )
291
+ t = target if target is not None else (kwargs.get("target") or kwargs.get("Target") or "")
292
+ r = (
293
+ replacement
294
+ if replacement is not None
295
+ else (kwargs.get("replacement") or kwargs.get("Replacement") or "")
296
+ )
297
+ tcid = (tool_call_id if tool_call_id is not None else kwargs.get("tool_call_id")) or ""
298
+ st = state if state is not None else kwargs.get("state")
299
+
300
+ try:
301
+ logger.info(f"update_note called with key='{k}'")
302
+ base_dir = (
303
+ ctx.data_engine.base_dir
304
+ if (
305
+ ctx
306
+ and getattr(ctx, "data_engine", None)
307
+ and getattr(ctx.data_engine, "base_dir", None)
308
+ )
309
+ else "."
310
+ )
311
+ warning = update_note_content(base_dir, k, t, r)
312
+ if warning:
313
+ agent_outcome = f"Updated note '{k}'.\nWARNING: {warning}"
314
+ else:
315
+ agent_outcome = f"Updated note '{k}'."
316
+ status = "success"
317
+ except Exception as e: # pylint: disable=broad-exception-caught
318
+ agent_outcome = f"Failed to update note '{k}': {e}"
319
+ status = "error"
320
+
321
+ if st is not None:
322
+ return ToolMessage(
323
+ tool_call_id=tcid,
324
+ content=agent_outcome,
325
+ status=status,
326
+ )
327
+
328
+ return agent_outcome
329
+
330
+
331
+ # Universal tool instance & aliases
332
+ update_note = UpdateNoteTool()
333
+ UpdateNote = UpdateNoteTool
334
+
335
+
336
+ def get_update_note_tool(ctx: ArtemisContext) -> BaseTool:
337
+ """Exports update_note as a LangChain BaseTool."""
338
+ return trace_langchain_tool(update_note.to_langchain_tool(ctx), ctx)
339
+
340
+
341
+ class ReadNoteTool(ArtemisTool):
342
+ """Universal tool for reading previously saved notes from persistent memory."""
343
+
344
+ def __init__(self):
345
+ super().__init__(
346
+ name="read_note",
347
+ description=READ_NOTE_DOCSTRING,
348
+ args_schema=ReadNoteArgs,
349
+ category="memory",
350
+ )
351
+
352
+ # pylint: disable=too-many-arguments,too-many-positional-arguments,too-many-locals
353
+ async def execute(
354
+ self,
355
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
356
+ ctx: ArtemisContext | None = None,
357
+ key: str | None = None,
358
+ start_line: int | None = None,
359
+ end_line: int | None = None,
360
+ tool_call_id: str | None = None,
361
+ state: State | None = None,
362
+ **kwargs: Any,
363
+ ) -> Any:
364
+ k = (
365
+ key
366
+ if key is not None
367
+ else (
368
+ kwargs.get("key")
369
+ or kwargs.get("Key")
370
+ or kwargs.get("title")
371
+ or kwargs.get("Title")
372
+ or ""
373
+ )
374
+ )
375
+ sl = (
376
+ start_line
377
+ if start_line is not None
378
+ else (kwargs.get("start_line") or kwargs.get("StartLine"))
379
+ )
380
+ el = end_line if end_line is not None else (kwargs.get("end_line") or kwargs.get("EndLine"))
381
+ tcid = (tool_call_id if tool_call_id is not None else kwargs.get("tool_call_id")) or ""
382
+ st = state if state is not None else kwargs.get("state")
383
+
384
+ try:
385
+ logger.info(f"read_note called with key='{k}'")
386
+ base_dir = (
387
+ ctx.data_engine.base_dir
388
+ if (
389
+ ctx
390
+ and getattr(ctx, "data_engine", None)
391
+ and getattr(ctx.data_engine, "base_dir", None)
392
+ )
393
+ else "."
394
+ )
395
+ content = read_note_content(base_dir, k, sl, el)
396
+ agent_outcome = read_note_wrapper.on_success_fn(k, content, sl, el)
397
+ status = "success"
398
+ except Exception as e: # pylint: disable=broad-exception-caught
399
+ if "not found" in str(e).lower():
400
+ agent_outcome = read_note_wrapper.on_failure_fn(k)
401
+ else:
402
+ agent_outcome = f"Failed to read note '{k}': {e}"
403
+ status = "error"
404
+
405
+ if st is not None:
406
+ return ToolMessage(
407
+ tool_call_id=tcid,
408
+ content=agent_outcome,
409
+ status=status,
410
+ )
411
+
412
+ return agent_outcome
413
+
414
+
415
+ # Universal tool instance & aliases
416
+ read_note = ReadNoteTool()
417
+ ReadNote = ReadNoteTool
418
+
419
+
420
+ def get_read_note_tool(ctx: ArtemisContext) -> BaseTool:
421
+ """Exports read_note as a LangChain BaseTool."""
422
+ return trace_langchain_tool(read_note.to_langchain_tool(ctx), ctx)
423
+
424
+
425
+ class ListNotesTool(ArtemisTool):
426
+ """Universal tool for listing all stored note keys in persistent memory."""
427
+
428
+ def __init__(self):
429
+ super().__init__(
430
+ name="list_notes",
431
+ description=LIST_NOTES_DOCSTRING,
432
+ args_schema=ListNotesArgs,
433
+ category="memory",
434
+ )
435
+
436
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
437
+ async def execute(
438
+ self,
439
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
440
+ ctx: ArtemisContext | None = None,
441
+ tool_call_id: str | None = None,
442
+ state: State | None = None,
443
+ **kwargs: Any,
444
+ ) -> Any:
445
+ tcid = (tool_call_id if tool_call_id is not None else kwargs.get("tool_call_id")) or ""
446
+ st = state if state is not None else kwargs.get("state")
447
+
448
+ try:
449
+ logger.info("list_notes called")
450
+ base_dir = (
451
+ ctx.data_engine.base_dir
452
+ if (
453
+ ctx
454
+ and getattr(ctx, "data_engine", None)
455
+ and getattr(ctx.data_engine, "base_dir", None)
456
+ )
457
+ else "."
458
+ )
459
+ notes_info = list_notes_info(base_dir)
460
+ agent_outcome = list_notes_wrapper.on_success_fn(notes_info)
461
+ status = "success"
462
+ except Exception as e: # pylint: disable=broad-exception-caught
463
+ agent_outcome = f"Failed to list notes: {e}"
464
+ status = "error"
465
+
466
+ if st is not None:
467
+ return ToolMessage(
468
+ tool_call_id=tcid,
469
+ content=agent_outcome,
470
+ status=status,
471
+ )
472
+
473
+ return agent_outcome
474
+
475
+
476
+ # Universal tool instance & aliases
477
+ list_notes = ListNotesTool()
478
+ ListNotes = ListNotesTool
479
+
480
+
481
+ def get_list_notes_tool(ctx: ArtemisContext) -> BaseTool:
482
+ """Exports list_notes as a LangChain BaseTool."""
483
+ return trace_langchain_tool(list_notes.to_langchain_tool(ctx), ctx)
484
+
485
+
486
+ class ReadNotePureTool(ArtemisTool):
487
+ """Universal pure tool for reading previously saved notes from persistent memory."""
488
+
489
+ def __init__(self):
490
+ super().__init__(
491
+ name="read_note_pure",
492
+ description=READ_NOTE_DOCSTRING,
493
+ args_schema=ReadNoteArgs,
494
+ category="memory",
495
+ )
496
+
497
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
498
+ async def execute(
499
+ self,
500
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
501
+ ctx: ArtemisContext | None = None,
502
+ key: str | None = None,
503
+ start_line: int | None = None,
504
+ end_line: int | None = None,
505
+ **kwargs: Any,
506
+ ) -> str:
507
+ k = (
508
+ key
509
+ if key is not None
510
+ else (
511
+ kwargs.get("key")
512
+ or kwargs.get("Key")
513
+ or kwargs.get("title")
514
+ or kwargs.get("Title")
515
+ or ""
516
+ )
517
+ )
518
+ sl = (
519
+ start_line
520
+ if start_line is not None
521
+ else (kwargs.get("start_line") or kwargs.get("StartLine"))
522
+ )
523
+ el = end_line if end_line is not None else (kwargs.get("end_line") or kwargs.get("EndLine"))
524
+
525
+ try:
526
+ logger.info(f"read_note_pure called with key='{k}'")
527
+ base_dir = (
528
+ ctx.data_engine.base_dir
529
+ if (
530
+ ctx
531
+ and getattr(ctx, "data_engine", None)
532
+ and getattr(ctx.data_engine, "base_dir", None)
533
+ )
534
+ else "."
535
+ )
536
+ content = read_note_content(base_dir, k, sl, el)
537
+ return format_read_note_success(k, content, sl, el)
538
+ except Exception as e: # pylint: disable=broad-exception-caught
539
+ return format_read_note_failure(k, str(e))
540
+
541
+
542
+ # Universal pure tool instance & aliases
543
+ read_note_pure = ReadNotePureTool()
544
+ ReadNotePure = ReadNotePureTool
545
+
546
+
547
+ def get_read_note_tool_pure(ctx: ArtemisContext) -> BaseTool:
548
+ """Exports read_note_pure as a LangChain BaseTool named 'read_note'."""
549
+ return read_note_pure.to_langchain_tool(ctx, name="read_note")
550
+
551
+
552
+ class ListNotesPureTool(ArtemisTool):
553
+ """Universal pure tool for listing all stored note keys in persistent memory."""
554
+
555
+ def __init__(self):
556
+ super().__init__(
557
+ name="list_notes_pure",
558
+ description=LIST_NOTES_DOCSTRING,
559
+ args_schema=ListNotesArgs,
560
+ category="memory",
561
+ )
562
+
563
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
564
+ async def execute(
565
+ self,
566
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
567
+ ctx: ArtemisContext | None = None,
568
+ **kwargs: Any,
569
+ ) -> str:
570
+ try:
571
+ logger.info("list_notes_pure called")
572
+ base_dir = (
573
+ ctx.data_engine.base_dir
574
+ if (
575
+ ctx
576
+ and getattr(ctx, "data_engine", None)
577
+ and getattr(ctx.data_engine, "base_dir", None)
578
+ )
579
+ else "."
580
+ )
581
+ notes_info = list_notes_info(base_dir)
582
+ return format_list_notes_success(notes_info)
583
+ except Exception as e: # pylint: disable=broad-exception-caught
584
+ return format_list_notes_failure(str(e))
585
+
586
+
587
+ # Universal pure tool instance & aliases
588
+ list_notes_pure = ListNotesPureTool()
589
+ ListNotesPure = ListNotesPureTool
590
+
591
+
592
+ def get_list_notes_tool_pure(ctx: ArtemisContext) -> BaseTool:
593
+ """Exports list_notes_pure as a LangChain BaseTool named 'list_notes'."""
594
+ return list_notes_pure.to_langchain_tool(ctx, name="list_notes")
595
+
596
+
597
+ class SaveNotePureTool(ArtemisTool):
598
+ """Universal pure tool for saving notes to persistent memory."""
599
+
600
+ def __init__(self):
601
+ super().__init__(
602
+ name="save_note_pure",
603
+ description=SAVE_NOTE_DOCSTRING,
604
+ args_schema=SaveNoteArgs,
605
+ category="memory",
606
+ )
607
+
608
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
609
+ async def execute(
610
+ self,
611
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
612
+ ctx: ArtemisContext | None = None,
613
+ key: str | None = None,
614
+ content: str | None = None,
615
+ **kwargs: Any,
616
+ ) -> str:
617
+ k = (
618
+ key
619
+ if key is not None
620
+ else (
621
+ kwargs.get("key")
622
+ or kwargs.get("Key")
623
+ or kwargs.get("title")
624
+ or kwargs.get("Title")
625
+ or ""
626
+ )
627
+ )
628
+ c = (
629
+ content
630
+ if content is not None
631
+ else (kwargs.get("content") or kwargs.get("Content") or "")
632
+ )
633
+
634
+ try:
635
+ logger.info(f"save_note_pure called with key='{k}'")
636
+ base_dir = (
637
+ ctx.data_engine.base_dir
638
+ if (
639
+ ctx
640
+ and getattr(ctx, "data_engine", None)
641
+ and getattr(ctx.data_engine, "base_dir", None)
642
+ )
643
+ else "."
644
+ )
645
+ save_note_content(base_dir, k, c)
646
+ return f"Saved note '{k}'."
647
+ except Exception as e: # pylint: disable=broad-exception-caught
648
+ return ToolFailure(f"Failed to save note {k}.md: {e}")
649
+
650
+
651
+ # Universal pure tool instance & aliases
652
+ save_note_pure = SaveNotePureTool()
653
+ SaveNotePure = SaveNotePureTool
654
+
655
+
656
+ def get_save_note_tool_pure(ctx: ArtemisContext) -> BaseTool:
657
+ """Exports save_note_pure as a LangChain BaseTool named 'save_note'."""
658
+ return save_note_pure.to_langchain_tool(ctx, name="save_note")
659
+
660
+
661
+ class UpdateNotePureTool(ArtemisTool):
662
+ """Universal pure tool for updating notes by replacing a target string in persistent memory."""
663
+
664
+ def __init__(self):
665
+ super().__init__(
666
+ name="update_note_pure",
667
+ description=UPDATE_NOTE_DOCSTRING,
668
+ args_schema=UpdateNoteArgs,
669
+ category="memory",
670
+ )
671
+
672
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
673
+ async def execute(
674
+ self,
675
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
676
+ ctx: ArtemisContext | None = None,
677
+ key: str | None = None,
678
+ target: str | None = None,
679
+ replacement: str | None = None,
680
+ **kwargs: Any,
681
+ ) -> str:
682
+ k = (
683
+ key
684
+ if key is not None
685
+ else (
686
+ kwargs.get("key")
687
+ or kwargs.get("Key")
688
+ or kwargs.get("title")
689
+ or kwargs.get("Title")
690
+ or ""
691
+ )
692
+ )
693
+ t = target if target is not None else (kwargs.get("target") or kwargs.get("Target") or "")
694
+ r = (
695
+ replacement
696
+ if replacement is not None
697
+ else (kwargs.get("replacement") or kwargs.get("Replacement") or "")
698
+ )
699
+
700
+ try:
701
+ logger.info(f"update_note_pure called with key='{k}'")
702
+ base_dir = (
703
+ ctx.data_engine.base_dir
704
+ if (
705
+ ctx
706
+ and getattr(ctx, "data_engine", None)
707
+ and getattr(ctx.data_engine, "base_dir", None)
708
+ )
709
+ else "."
710
+ )
711
+ warning = update_note_content(base_dir, k, t, r)
712
+ if warning:
713
+ return f"Updated note '{k}'.\nWARNING: {warning}"
714
+ return f"Updated note '{k}'."
715
+ except Exception as e: # pylint: disable=broad-exception-caught
716
+ return ToolFailure(f"Failed to update note '{k}': {e}")
717
+
718
+
719
+ # Universal pure tool instance & aliases
720
+ update_note_pure = UpdateNotePureTool()
721
+ UpdateNotePure = UpdateNotePureTool
722
+
723
+
724
+ def get_update_note_tool_pure(ctx: ArtemisContext) -> BaseTool:
725
+ """Exports update_note_pure as a LangChain BaseTool named 'update_note'."""
726
+ return update_note_pure.to_langchain_tool(ctx, name="update_note")
727
+
728
+
729
+ class AppendNotePureTool(ArtemisTool):
730
+ """Universal pure tool for appending text content to notes in persistent memory."""
731
+
732
+ def __init__(self):
733
+ super().__init__(
734
+ name="append_note_pure",
735
+ description=APPEND_NOTE_DOCSTRING,
736
+ args_schema=AppendNoteArgs,
737
+ category="memory",
738
+ )
739
+
740
+ # pylint: disable=too-many-arguments,too-many-positional-arguments
741
+ async def execute(
742
+ self,
743
+ driver: BaseDeviceDriver | None = None, # pylint: disable=unused-argument
744
+ ctx: ArtemisContext | None = None,
745
+ key: str | None = None,
746
+ content: str | None = None,
747
+ **kwargs: Any,
748
+ ) -> str:
749
+ k = (
750
+ key
751
+ if key is not None
752
+ else (
753
+ kwargs.get("key")
754
+ or kwargs.get("Key")
755
+ or kwargs.get("title")
756
+ or kwargs.get("Title")
757
+ or ""
758
+ )
759
+ )
760
+ c = (
761
+ content
762
+ if content is not None
763
+ else (kwargs.get("content") or kwargs.get("Content") or "")
764
+ )
765
+
766
+ try:
767
+ logger.info(f"append_note_pure called with key='{k}'")
768
+ base_dir = (
769
+ ctx.data_engine.base_dir
770
+ if (
771
+ ctx
772
+ and getattr(ctx, "data_engine", None)
773
+ and getattr(ctx.data_engine, "base_dir", None)
774
+ )
775
+ else "."
776
+ )
777
+ append_note_content(base_dir, k, c)
778
+ return f"Appended to note '{k}'."
779
+ except Exception as e: # pylint: disable=broad-exception-caught
780
+ return ToolFailure(f"Failed to append note {k}.md: {e}")
781
+
782
+
783
+ # Universal pure tool instance & aliases
784
+ append_note_pure = AppendNotePureTool()
785
+ AppendNotePure = AppendNotePureTool
786
+
787
+
788
+ def get_append_note_tool_pure(ctx: ArtemisContext) -> BaseTool:
789
+ """Exports append_note_pure as a LangChain BaseTool named 'append_note'."""
790
+ return append_note_pure.to_langchain_tool(ctx, name="append_note")
791
+
792
+
793
+ save_note_wrapper = make_save_note_wrapper(get_save_note_tool)
794
+
795
+ read_note_wrapper = ToolWrapper(
796
+ tool_fn_getter=get_read_note_tool,
797
+ on_success_fn=format_read_note_success,
798
+ on_failure_fn=lambda key: format_read_note_failure(key, "not found"),
799
+ )
800
+
801
+ list_notes_wrapper = ToolWrapper(
802
+ tool_fn_getter=get_list_notes_tool,
803
+ on_success_fn=format_list_notes_success,
804
+ on_failure_fn=lambda: format_list_notes_failure("error"),
805
+ )
806
+
807
+
808
+ update_note_wrapper = ToolWrapper(
809
+ tool_fn_getter=get_update_note_tool,
810
+ on_success_fn=lambda key: f"Updated note '{key}'.",
811
+ on_failure_fn=lambda key: f"Failed to update note '{key}'.",
812
+ )
813
+
814
+
815
+ append_note_wrapper = ToolWrapper(
816
+ tool_fn_getter=get_append_note_tool,
817
+ on_success_fn=lambda key: f"Appended to note '{key}'.",
818
+ on_failure_fn=lambda key: f"Failed to append note '{key}'.",
819
+ )