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.
- package/CHANGELOG.md +10 -0
- package/README.md +159 -29
- package/artemis-main/.dockerignore +15 -0
- package/artemis-main/.env.example +34 -0
- package/artemis-main/CONTRIBUTING.md +35 -0
- package/artemis-main/Dockerfile +68 -0
- package/artemis-main/LICENSE +202 -0
- package/artemis-main/Makefile +115 -0
- package/artemis-main/README.md +322 -0
- package/artemis-main/README_CN.md +320 -0
- package/artemis-main/apps/__init__.py +15 -0
- package/artemis-main/apps/admin_console/__init__.py +19 -0
- package/artemis-main/apps/admin_console/core/__init__.py +13 -0
- package/artemis-main/apps/admin_console/core/config.py +43 -0
- package/artemis-main/apps/admin_console/core/security.py +178 -0
- package/artemis-main/apps/admin_console/core/state.py +243 -0
- package/artemis-main/apps/admin_console/database/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/connection.py +60 -0
- package/artemis-main/apps/admin_console/database/repositories/__init__.py +13 -0
- package/artemis-main/apps/admin_console/database/repositories/session_repository.py +564 -0
- package/artemis-main/apps/admin_console/database/repositories/step_repository.py +512 -0
- package/artemis-main/apps/admin_console/database/repositories/trace_repository.py +133 -0
- package/artemis-main/apps/admin_console/index.html +3787 -0
- package/artemis-main/apps/admin_console/replay_manager.py +2585 -0
- package/artemis-main/apps/admin_console/routers/__init__.py +13 -0
- package/artemis-main/apps/admin_console/routers/media.py +198 -0
- package/artemis-main/apps/admin_console/routers/replay.py +91 -0
- package/artemis-main/apps/admin_console/routers/sessions.py +239 -0
- package/artemis-main/apps/admin_console/routers/steps.py +109 -0
- package/artemis-main/apps/admin_console/routers/stream.py +56 -0
- package/artemis-main/apps/admin_console/routers/system.py +761 -0
- package/artemis-main/apps/admin_console/routers/tasks.py +517 -0
- package/artemis-main/apps/admin_console/schemas/__init__.py +13 -0
- package/artemis-main/apps/admin_console/schemas/task_schema.py +47 -0
- package/artemis-main/apps/admin_console/server.py +508 -0
- package/artemis-main/apps/admin_console/services/__init__.py +13 -0
- package/artemis-main/apps/admin_console/services/device_stream_service.py +137 -0
- package/artemis-main/apps/admin_console/services/ipc_service.py +262 -0
- package/artemis-main/apps/admin_console/services/media_service.py +623 -0
- package/artemis-main/apps/admin_console/services/model_service.py +148 -0
- package/artemis-main/apps/admin_console/services/task_preset_catalog.py +514 -0
- package/artemis-main/apps/admin_console/services/task_queue_service.py +1719 -0
- package/artemis-main/apps/admin_console/services/worker_process_io.py +144 -0
- package/artemis-main/apps/showcase_ui/README.md +31 -0
- package/artemis-main/apps/showcase_ui/angular.json +132 -0
- package/artemis-main/apps/showcase_ui/package-lock.json +9472 -0
- package/artemis-main/apps/showcase_ui/package.json +39 -0
- package/artemis-main/apps/showcase_ui/proxy.conf.json +23 -0
- package/artemis-main/apps/showcase_ui/public/favicon.ico +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.png +0 -0
- package/artemis-main/apps/showcase_ui/public/logo.svg +56 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.html +24 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.scss +49 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.spec.ts +42 -0
- package/artemis-main/apps/showcase_ui/src/app/app.component.ts +32 -0
- package/artemis-main/apps/showcase_ui/src/app/app.config.ts +29 -0
- package/artemis-main/apps/showcase_ui/src/app/app.routes.ts +27 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream-startup.spec.ts +108 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.html +1436 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.scss +3973 -0
- package/artemis-main/apps/showcase_ui/src/app/components/agent-stream/agent-stream.component.ts +2235 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.html +380 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.scss +1171 -0
- package/artemis-main/apps/showcase_ui/src/app/components/chat-interface/chat-interface.component.ts +270 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.html +433 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.scss +987 -0
- package/artemis-main/apps/showcase_ui/src/app/components/floating-video-player/floating-video-player.component.ts +632 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.html +130 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.scss +406 -0
- package/artemis-main/apps/showcase_ui/src/app/components/legacy-agent-stream/legacy-agent-stream.component.ts +74 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.scss +115 -0
- package/artemis-main/apps/showcase_ui/src/app/components/nav-switcher/nav-switcher.component.ts +52 -0
- package/artemis-main/apps/showcase_ui/src/app/core/data/smart-tasks.data.ts +334 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.spec.ts +55 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/language.service.ts +61 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/translate.pipe.ts +11 -0
- package/artemis-main/apps/showcase_ui/src/app/core/i18n/zh-cn.ts +651 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/markdown.model.ts +43 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.spec.ts +47 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/pro-tuning.model.ts +165 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/session.model.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/stream.model.ts +246 -0
- package/artemis-main/apps/showcase_ui/src/app/core/models/system.model.ts +118 -0
- package/artemis-main/apps/showcase_ui/src/app/core/services/task-recommendation.service.ts +111 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.html +2130 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.scss +5204 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/home/home.component.ts +1189 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.html +39 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.scss +151 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/legacy-workspace/legacy-workspace.component.ts +58 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.html +201 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.scss +886 -0
- package/artemis-main/apps/showcase_ui/src/app/pages/workspace/workspace.component.ts +324 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.spec.ts +418 -0
- package/artemis-main/apps/showcase_ui/src/app/services/agent.service.ts +2012 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.spec.ts +173 -0
- package/artemis-main/apps/showcase_ui/src/app/services/system.service.ts +620 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.spec.ts +513 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/action-formatter.util.ts +1015 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.spec.ts +85 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/image-overlay.util.ts +662 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.spec.ts +70 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/markdown-parser.util.ts +383 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.spec.ts +107 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/recording-timeline.util.ts +123 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.spec.ts +92 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/run-info.util.ts +116 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.spec.ts +640 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/stream-aggregator.util.ts +1021 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.spec.ts +375 -0
- package/artemis-main/apps/showcase_ui/src/app/utils/tool-formatter.util.ts +1419 -0
- package/artemis-main/apps/showcase_ui/src/index.html +37 -0
- package/artemis-main/apps/showcase_ui/src/main.ts +22 -0
- package/artemis-main/apps/showcase_ui/src/styles.scss +60 -0
- package/artemis-main/apps/showcase_ui/tsconfig.app.json +23 -0
- package/artemis-main/apps/showcase_ui/tsconfig.json +27 -0
- package/artemis-main/apps/showcase_ui/tsconfig.spec.json +23 -0
- package/artemis-main/artemis/__init__.py +44 -0
- package/artemis-main/artemis/__main__.py +18 -0
- package/artemis-main/artemis/_legacy_imports.py +94 -0
- package/artemis-main/artemis/_version.py +30 -0
- package/artemis-main/artemis/agents/__init__.py +17 -0
- package/artemis-main/artemis/agents/checker/checker.json +9 -0
- package/artemis-main/artemis/agents/checker/checker.py +859 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.json +23 -0
- package/artemis-main/artemis/agents/diagnoser/diagnoser.py +608 -0
- package/artemis-main/artemis/agents/explorer/constants.py +52 -0
- package/artemis-main/artemis/agents/explorer/explorer.json +57 -0
- package/artemis-main/artemis/agents/explorer/explorer.py +473 -0
- package/artemis-main/artemis/agents/explorer/geometry.py +73 -0
- package/artemis-main/artemis/agents/explorer/native_runner.py +878 -0
- package/artemis-main/artemis/agents/explorer/perception_tools.py +632 -0
- package/artemis-main/artemis/agents/explorer/run_setup.py +403 -0
- package/artemis-main/artemis/agents/explorer/screen_index.py +266 -0
- package/artemis-main/artemis/agents/explorer/tiers.py +110 -0
- package/artemis-main/artemis/agents/explorer/tool_declarations.py +394 -0
- package/artemis-main/artemis/agents/explorer/universal_runner.py +317 -0
- package/artemis-main/artemis/agents/flash/context_compressor.py +452 -0
- package/artemis-main/artemis/agents/flash/flash_runner.md +51 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer.md +53 -0
- package/artemis-main/artemis/agents/flash/flash_summarizer_single.md +54 -0
- package/artemis-main/artemis/agents/flash/runner.py +1167 -0
- package/artemis-main/artemis/agents/flash/summarizer.py +511 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.md +8 -0
- package/artemis-main/artemis/agents/history_analyzer/history_analyzer.py +182 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.md +74 -0
- package/artemis-main/artemis/agents/image_processor/image_processor.py +322 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.md +15 -0
- package/artemis-main/artemis/agents/log_analyzer/log_analyzer.py +403 -0
- package/artemis-main/artemis/agents/log_analyzer/log_reader_sub_agent.md +8 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.md +11 -0
- package/artemis-main/artemis/agents/log_analyzer/output_analyzer.py +163 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.json +9 -0
- package/artemis-main/artemis/agents/object_detector/object_detector.py +211 -0
- package/artemis-main/artemis/agents/operator/operator.json +3 -0
- package/artemis-main/artemis/agents/operator/operator.py +1394 -0
- package/artemis-main/artemis/agents/operator/prompts.py +1064 -0
- package/artemis-main/artemis/agents/outputter/outputter.md +36 -0
- package/artemis-main/artemis/agents/outputter/outputter.py +552 -0
- package/artemis-main/artemis/agents/planner/planner.json +32 -0
- package/artemis-main/artemis/agents/planner/planner.py +436 -0
- package/artemis-main/artemis/agents/prompt_assembly.py +79 -0
- package/artemis-main/artemis/agents/summarizer/summarizer.py +190 -0
- package/artemis-main/artemis/agents/validator/action_execution.py +98 -0
- package/artemis-main/artemis/agents/validator/categories.py +33 -0
- package/artemis-main/artemis/agents/validator/execution_loop.py +488 -0
- package/artemis-main/artemis/agents/validator/incidents.py +120 -0
- package/artemis-main/artemis/agents/validator/pixel_safety_net.md +15 -0
- package/artemis-main/artemis/agents/validator/precondition_pixel.py +320 -0
- package/artemis-main/artemis/agents/validator/precondition_xml.py +672 -0
- package/artemis-main/artemis/agents/validator/tool_declarations.py +332 -0
- package/artemis-main/artemis/agents/validator/validator.py +188 -0
- package/artemis-main/artemis/agents/video_analyzer/__init__.py +15 -0
- package/artemis-main/artemis/agents/video_analyzer/audio_native.py +543 -0
- package/artemis-main/artemis/agents/video_analyzer/blackboard.py +821 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_agentic.py +455 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_conversation.py +491 -0
- package/artemis-main/artemis/agents/video_analyzer/chunk_native.py +437 -0
- package/artemis-main/artemis/agents/video_analyzer/conflict_resolution.py +137 -0
- package/artemis-main/artemis/agents/video_analyzer/gemini_files.py +162 -0
- package/artemis-main/artemis/agents/video_analyzer/native_coordinator.py +541 -0
- package/artemis-main/artemis/agents/video_analyzer/reliability.py +128 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_engine.py +487 -0
- package/artemis-main/artemis/agents/video_analyzer/universal_tools.py +323 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.md +51 -0
- package/artemis-main/artemis/agents/video_analyzer/video_analyzer.py +875 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent.md +15 -0
- package/artemis-main/artemis/agents/video_analyzer/video_sub_agent_audio.md +12 -0
- package/artemis-main/artemis/clients/accessibility_client.py +438 -0
- package/artemis-main/artemis/clients/screen_client_factory.py +403 -0
- package/artemis-main/artemis/config/__init__.py +309 -0
- package/artemis-main/artemis/config/agent.py +1098 -0
- package/artemis-main/artemis/config/constants.py +151 -0
- package/artemis-main/artemis/config/core/__init__.py +24 -0
- package/artemis-main/artemis/config/core/engine.py +118 -0
- package/artemis-main/artemis/config/core/interpolator.py +78 -0
- package/artemis-main/artemis/config/llm.py +267 -0
- package/artemis-main/artemis/config/paths.py +244 -0
- package/artemis-main/artemis/config/runtime.py +197 -0
- package/artemis-main/artemis/config/settings.py +315 -0
- package/artemis-main/artemis/constants.py +47 -0
- package/artemis-main/artemis/context.py +289 -0
- package/artemis-main/artemis/controllers/__init__.py +41 -0
- package/artemis-main/artemis/controllers/controller_factory.py +41 -0
- package/artemis-main/artemis/controllers/device_controller.py +29 -0
- package/artemis-main/artemis/controllers/types.py +37 -0
- package/artemis-main/artemis/controllers/unified_controller.py +649 -0
- package/artemis-main/artemis/core/__init__.py +21 -0
- package/artemis-main/artemis/core/diagnostics/__init__.py +90 -0
- package/artemis-main/artemis/core/diagnostics/adb_keys.py +270 -0
- package/artemis-main/artemis/core/diagnostics/adb_server_connection.py +330 -0
- package/artemis-main/artemis/core/diagnostics/device_smoke.py +364 -0
- package/artemis-main/artemis/core/diagnostics/emulator_manager.py +501 -0
- package/artemis-main/artemis/core/diagnostics/engine.py +438 -0
- package/artemis-main/artemis/core/diagnostics/hierarchy_parity.py +216 -0
- package/artemis-main/artemis/core/diagnostics/probes/adb_probe.py +887 -0
- package/artemis-main/artemis/core/diagnostics/probes/base.py +45 -0
- package/artemis-main/artemis/core/diagnostics/probes/credentials_probe.py +296 -0
- package/artemis-main/artemis/core/diagnostics/probes/host_probe.py +427 -0
- package/artemis-main/artemis/core/diagnostics/probes/runtime_probe.py +169 -0
- package/artemis-main/artemis/core/diagnostics/probes/toolchain_probe.py +139 -0
- package/artemis-main/artemis/core/diagnostics/readiness.py +83 -0
- package/artemis-main/artemis/core/diagnostics/schema.py +118 -0
- package/artemis-main/artemis/core/tool_declaration.py +43 -0
- package/artemis-main/artemis/core/tool_failure.py +56 -0
- package/artemis-main/artemis/data_engine/context_vars.py +24 -0
- package/artemis-main/artemis/data_engine/engine.py +1615 -0
- package/artemis-main/artemis/data_engine/history_reader.py +142 -0
- package/artemis-main/artemis/data_engine/models.py +131 -0
- package/artemis-main/artemis/data_engine/storage.py +1245 -0
- package/artemis-main/artemis/data_engine/trace.py +727 -0
- package/artemis-main/artemis/drivers/__init__.py +48 -0
- package/artemis-main/artemis/drivers/android/adb_driver.py +507 -0
- package/artemis-main/artemis/drivers/android/hierarchy.py +64 -0
- package/artemis-main/artemis/drivers/android/input_ime.py +120 -0
- package/artemis-main/artemis/drivers/android/recorder.py +57 -0
- package/artemis-main/artemis/drivers/base.py +290 -0
- package/artemis-main/artemis/drivers/cloud/cloud_driver.py +117 -0
- package/artemis-main/artemis/drivers/factory.py +80 -0
- package/artemis-main/artemis/drivers/mock/mock_driver.py +198 -0
- package/artemis-main/artemis/drivers/types.py +67 -0
- package/artemis-main/artemis/graph/checkpoints.py +1243 -0
- package/artemis-main/artemis/graph/graph.py +1055 -0
- package/artemis-main/artemis/graph/perception.py +234 -0
- package/artemis-main/artemis/graph/state.py +151 -0
- package/artemis-main/artemis/graph/visibility.py +238 -0
- package/artemis-main/artemis/interfaces/cli/__init__.py +19 -0
- package/artemis-main/artemis/interfaces/cli/commands/__init__.py +15 -0
- package/artemis-main/artemis/interfaces/cli/commands/batch.py +278 -0
- package/artemis-main/artemis/interfaces/cli/commands/doctor.py +468 -0
- package/artemis-main/artemis/interfaces/cli/commands/helper.py +226 -0
- package/artemis-main/artemis/interfaces/cli/commands/init.py +193 -0
- package/artemis-main/artemis/interfaces/cli/commands/mcp.py +862 -0
- package/artemis-main/artemis/interfaces/cli/commands/run.py +463 -0
- package/artemis-main/artemis/interfaces/cli/commands/server.py +151 -0
- package/artemis-main/artemis/interfaces/cli/commands/server_lifecycle.py +328 -0
- package/artemis-main/artemis/interfaces/cli/commands/trace.py +111 -0
- package/artemis-main/artemis/interfaces/cli/commands/ui.py +404 -0
- package/artemis-main/artemis/interfaces/cli/main.py +109 -0
- package/artemis-main/artemis/interfaces/sdk/__init__.py +31 -0
- package/artemis-main/artemis/interfaces/sdk/client.py +19 -0
- package/artemis-main/artemis/interfaces/sdk/task.py +72 -0
- package/artemis-main/artemis/llm/__init__.py +25 -0
- package/artemis-main/artemis/llm/fake_model.py +179 -0
- package/artemis-main/artemis/llm/google/__init__.py +57 -0
- package/artemis-main/artemis/llm/google/provider.py +185 -0
- package/artemis-main/artemis/llm/google/usage.py +148 -0
- package/artemis-main/artemis/llm/parser.py +32 -0
- package/artemis-main/artemis/llm/reliability.py +267 -0
- package/artemis-main/artemis/llm/router.py +389 -0
- package/artemis-main/artemis/llm/schemas.py +36 -0
- package/artemis-main/artemis/llm/structured.py +196 -0
- package/artemis-main/artemis/main.py +58 -0
- package/artemis-main/artemis/mcp/__init__.py +20 -0
- package/artemis-main/artemis/mcp/action_executor.py +760 -0
- package/artemis-main/artemis/mcp/action_manifest.py +320 -0
- package/artemis-main/artemis/mcp/action_names.py +172 -0
- package/artemis-main/artemis/mcp/action_server.py +202 -0
- package/artemis-main/artemis/mcp/action_session.py +245 -0
- package/artemis-main/artemis/mcp/action_specs.py +868 -0
- package/artemis-main/artemis/mcp/action_types.py +86 -0
- package/artemis-main/artemis/mcp/actuators/__init__.py +21 -0
- package/artemis-main/artemis/mcp/actuators/adb.py +414 -0
- package/artemis-main/artemis/mcp/actuators/base.py +113 -0
- package/artemis-main/artemis/mcp/actuators/mock.py +70 -0
- package/artemis-main/artemis/mcp/adb_server.py +490 -0
- package/artemis-main/artemis/mcp/observation.py +133 -0
- package/artemis-main/artemis/mcp/xml_search_server.py +297 -0
- package/artemis-main/artemis/memory/__init__.py +90 -0
- package/artemis-main/artemis/memory/chunking.py +1843 -0
- package/artemis-main/artemis/memory/context_policy.py +226 -0
- package/artemis-main/artemis/memory/step_capsule.md +73 -0
- package/artemis-main/artemis/memory/step_memory.py +281 -0
- package/artemis-main/artemis/memory/transcript.py +798 -0
- package/artemis-main/artemis/platform/__init__.py +61 -0
- package/artemis-main/artemis/platform/base.py +104 -0
- package/artemis-main/artemis/platform/darwin.py +184 -0
- package/artemis-main/artemis/platform/linux.py +207 -0
- package/artemis-main/artemis/platform/windows.py +193 -0
- package/artemis-main/artemis/resources/__init__.py +46 -0
- package/artemis-main/artemis/runtime/__init__.py +108 -0
- package/artemis-main/artemis/runtime/adb_endpoint.py +173 -0
- package/artemis-main/artemis/runtime/awake_lease.py +283 -0
- package/artemis-main/artemis/runtime/awake_service.py +343 -0
- package/artemis-main/artemis/runtime/cancel_requests.py +254 -0
- package/artemis-main/artemis/runtime/daemon_client.py +422 -0
- package/artemis-main/artemis/runtime/device_lock.py +1013 -0
- package/artemis-main/artemis/runtime/device_pool.py +478 -0
- package/artemis-main/artemis/runtime/helper_manager.py +1030 -0
- package/artemis-main/artemis/runtime/process_probe.py +98 -0
- package/artemis-main/artemis/runtime/server_lifecycle.py +435 -0
- package/artemis-main/artemis/runtime/supervisor.py +121 -0
- package/artemis-main/artemis/runtime/trace_store.py +375 -0
- package/artemis-main/artemis/sdk/__init__.py +26 -0
- package/artemis-main/artemis/sdk/agent.py +485 -0
- package/artemis-main/artemis/sdk/builders/__init__.py +29 -0
- package/artemis-main/artemis/sdk/builders/agent_config_builder.py +474 -0
- package/artemis-main/artemis/sdk/constants.py +15 -0
- package/artemis-main/artemis/sdk/run_outcome.py +58 -0
- package/artemis-main/artemis/sdk/types/__init__.py +61 -0
- package/artemis-main/artemis/sdk/types/agent.py +89 -0
- package/artemis-main/artemis/sdk/utils.py +28 -0
- package/artemis-main/artemis/services/llm.py +1040 -0
- package/artemis-main/artemis/services/token_meter.py +267 -0
- package/artemis-main/artemis/telemetry/__init__.py +35 -0
- package/artemis-main/artemis/telemetry/models.py +64 -0
- package/artemis-main/artemis/telemetry/replay.py +34 -0
- package/artemis-main/artemis/telemetry/storage/json_storage.py +47 -0
- package/artemis-main/artemis/telemetry/tracer.py +85 -0
- package/artemis-main/artemis/toolchain/__init__.py +52 -0
- package/artemis-main/artemis/toolchain/descriptors.py +92 -0
- package/artemis-main/artemis/toolchain/resolver.py +185 -0
- package/artemis-main/artemis/tools/base.py +232 -0
- package/artemis-main/artemis/tools/command_tool.py +1240 -0
- package/artemis-main/artemis/tools/committee_prompts.json +5 -0
- package/artemis-main/artemis/tools/committee_tool.py +422 -0
- package/artemis-main/artemis/tools/diagnoser_submit_answer_tool.py +87 -0
- package/artemis-main/artemis/tools/diagnostic_tool.py +120 -0
- package/artemis-main/artemis/tools/explorer_tool.py +581 -0
- package/artemis-main/artemis/tools/history/__init__.py +398 -0
- package/artemis-main/artemis/tools/history/replay.py +120 -0
- package/artemis-main/artemis/tools/history/screenshot.py +139 -0
- package/artemis-main/artemis/tools/history/search.py +563 -0
- package/artemis-main/artemis/tools/image_processor_tool.py +124 -0
- package/artemis-main/artemis/tools/index.py +47 -0
- package/artemis-main/artemis/tools/log_tool.py +108 -0
- package/artemis-main/artemis/tools/mobile/launch_app.py +132 -0
- package/artemis-main/artemis/tools/mobile/log_utils.py +138 -0
- package/artemis-main/artemis/tools/mobile/ocr.py +202 -0
- package/artemis-main/artemis/tools/mobile/read_hierarchy.py +108 -0
- package/artemis-main/artemis/tools/mobile/read_logs.py +116 -0
- package/artemis-main/artemis/tools/mobile/search_logs.py +260 -0
- package/artemis-main/artemis/tools/object_detection_tool.py +244 -0
- package/artemis-main/artemis/tools/scratchpad.py +819 -0
- package/artemis-main/artemis/tools/system/adb_shell.py +31 -0
- package/artemis-main/artemis/tools/tool_wrapper.py +270 -0
- package/artemis-main/artemis/tools/video_tool.py +261 -0
- package/artemis-main/artemis/tools/wait_tool.py +97 -0
- package/artemis-main/artemis/utils/app_launch_utils.py +207 -0
- package/artemis-main/artemis/utils/coordinates.py +606 -0
- package/artemis-main/artemis/utils/credentials_validator.py +201 -0
- package/artemis-main/artemis/utils/cv_canvas.py +189 -0
- package/artemis-main/artemis/utils/cython_compat.py +45 -0
- package/artemis-main/artemis/utils/element_hit_test.py +80 -0
- package/artemis-main/artemis/utils/image_diff.py +335 -0
- package/artemis-main/artemis/utils/image_hash.py +80 -0
- package/artemis-main/artemis/utils/notes.py +470 -0
- package/artemis-main/artemis/utils/ocr_api.py +105 -0
- package/artemis-main/artemis/utils/ocr_xml_fusion.py +318 -0
- package/artemis-main/artemis/utils/plan_grammar.py +513 -0
- package/artemis-main/artemis/utils/python_executor.py +213 -0
- package/artemis-main/artemis/utils/startup_progress.py +60 -0
- package/artemis-main/artemis/utils/task_tree.py +1174 -0
- package/artemis-main/artemis/utils/text.py +37 -0
- package/artemis-main/artemis/utils/ui_filter.py +651 -0
- package/artemis-main/artemis/utils/video.py +685 -0
- package/artemis-main/artemis/utils/visualization.py +802 -0
- package/artemis-main/config/artemis.example.jsonc +12 -0
- package/artemis-main/mcp_server/README.md +111 -0
- package/artemis-main/mcp_server/__init__.py +21 -0
- package/artemis-main/mcp_server/__main__.py +20 -0
- package/artemis-main/mcp_server/background/__init__.py +15 -0
- package/artemis-main/mcp_server/background/task_runner.py +453 -0
- package/artemis-main/mcp_server/base.py +49 -0
- package/artemis-main/mcp_server/notifiers/__init__.py +74 -0
- package/artemis-main/mcp_server/notifiers/agentapi.py +289 -0
- package/artemis-main/mcp_server/notifiers/base.py +45 -0
- package/artemis-main/mcp_server/notifiers/composite.py +87 -0
- package/artemis-main/mcp_server/notifiers/desktop.py +108 -0
- package/artemis-main/mcp_server/notifiers/file.py +69 -0
- package/artemis-main/mcp_server/notifiers/script.py +90 -0
- package/artemis-main/mcp_server/notifiers/webhook.py +87 -0
- package/artemis-main/mcp_server/rules.md +68 -0
- package/artemis-main/mcp_server/server.py +106 -0
- package/artemis-main/mcp_server/tools/__init__.py +29 -0
- package/artemis-main/mcp_server/tools/device_state.py +107 -0
- package/artemis-main/mcp_server/tools/diagnose.py +1249 -0
- package/artemis-main/mcp_server/tools/inspect_trace.py +394 -0
- package/artemis-main/mcp_server/tools/task_manager.py +627 -0
- package/artemis-main/mcp_server/tools/task_runner.py +548 -0
- package/artemis-main/mcp_server/utils/__init__.py +15 -0
- package/artemis-main/mcp_server/utils/device_utils.py +180 -0
- package/artemis-main/mcp_server/utils/env_utils.py +65 -0
- package/artemis-main/packages/artemis-accessibility-helper/ArtemisAccessibilityHelper.apk +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/build.gradle.kts +35 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/AndroidManifest.xml +49 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/A11yNode.java +274 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/ArtemisAccessibilityService.java +227 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/CommandServer.java +463 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/DisplayUtils.java +111 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/GestureController.java +232 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/HierarchyDumper.java +832 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenReceiver.java +36 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/TokenStore.java +38 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/java/com/artemis/helper/XmlUtils.java +102 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/values/strings.xml +4 -0
- package/artemis-main/packages/artemis-accessibility-helper/app/src/main/res/xml/accessibility_service_config.xml +10 -0
- package/artemis-main/packages/artemis-accessibility-helper/build.gradle.kts +3 -0
- package/artemis-main/packages/artemis-accessibility-helper/build_apk.sh +215 -0
- package/artemis-main/packages/artemis-accessibility-helper/debug.keystore +0 -0
- package/artemis-main/packages/artemis-accessibility-helper/gradle.properties +2 -0
- package/artemis-main/packages/artemis-accessibility-helper/helper_manifest.json +7 -0
- package/artemis-main/packages/artemis-accessibility-helper/settings.gradle.kts +17 -0
- package/artemis-main/packages/artemis-client/DESIGN.md +89 -0
- package/artemis-main/packages/artemis-client/README.md +112 -0
- package/artemis-main/packages/artemis-client/examples/basic.py +30 -0
- package/artemis-main/packages/artemis-client/pyproject.toml +36 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/__init__.py +45 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/client.py +401 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/errors.py +66 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/models.py +212 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/py.typed +1 -0
- package/artemis-main/packages/artemis-client/src/artemis_client/transport.py +134 -0
- package/artemis-main/packages/artemis-client/tests/test_client.py +289 -0
- package/artemis-main/packages/artemis-client/tests/test_transport.py +85 -0
- package/artemis-main/pyproject.toml +259 -0
- package/artemis-main/pyright-core.json +17 -0
- package/artemis-main/scripts/check_dependency_sources.py +68 -0
- package/artemis-main/scripts/install_deps.ps1 +520 -0
- package/artemis-main/scripts/install_deps.sh +533 -0
- package/artemis-main/scripts/quality_ratchet.py +97 -0
- package/artemis-main/scripts/real_device_video_acceptance.py +483 -0
- package/artemis-main/scripts/run_edge_case_part.py +327 -0
- package/artemis-main/scripts/run_ui.py +242 -0
- package/artemis-main/scripts/start.ps1 +500 -0
- package/artemis-main/scripts/test_all_edge_cases.py +269 -0
- package/artemis-main/scripts/verify_concurrency_and_devices.py +385 -0
- package/artemis-main/setup.py +164 -0
- package/artemis-main/start.bat +35 -0
- package/artemis-main/start.sh +423 -0
- package/artemis-main/third_party/mobile_use/LICENSE +190 -0
- package/artemis-main/third_party/mobile_use/METADATA +27 -0
- package/artemis-main/third_party/mobile_use/NOTICE +12 -0
- package/artemis-main/third_party/mobile_use/__init__.py +19 -0
- package/artemis-main/third_party/mobile_use/agents/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.md +22 -0
- package/artemis-main/third_party/mobile_use/agents/hopper/hopper.py +80 -0
- package/artemis-main/third_party/mobile_use/clients/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/clients/ui_automator_client.py +482 -0
- package/artemis-main/third_party/mobile_use/config/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/config/llm.py +183 -0
- package/artemis-main/third_party/mobile_use/config/output.py +184 -0
- package/artemis-main/third_party/mobile_use/context.py +119 -0
- package/artemis-main/third_party/mobile_use/controllers/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/controllers/platform_specific_commands_controller.py +151 -0
- package/artemis-main/third_party/mobile_use/controllers/types.py +92 -0
- package/artemis-main/third_party/mobile_use/controllers/unified_controller.py +183 -0
- package/artemis-main/third_party/mobile_use/graph.py +54 -0
- package/artemis-main/third_party/mobile_use/llm_service.py +193 -0
- package/artemis-main/third_party/mobile_use/main.py +130 -0
- package/artemis-main/third_party/mobile_use/sdk/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/agent.py +1060 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/agent_config_builder.py +225 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/index.py +33 -0
- package/artemis-main/third_party/mobile_use/sdk/builders/task_request_builder.py +243 -0
- package/artemis-main/third_party/mobile_use/sdk/types/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/sdk/types/agent.py +98 -0
- package/artemis-main/third_party/mobile_use/sdk/types/exceptions.py +109 -0
- package/artemis-main/third_party/mobile_use/sdk/types/task.py +234 -0
- package/artemis-main/third_party/mobile_use/testing.py +59 -0
- package/artemis-main/third_party/mobile_use/tools/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/index.py +44 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/tools/mobile/launch_app.py +89 -0
- package/artemis-main/third_party/mobile_use/tools/tool_wrapper.py +52 -0
- package/artemis-main/third_party/mobile_use/utils/__init__.py +13 -0
- package/artemis-main/third_party/mobile_use/utils/app_launch_utils.py +217 -0
- package/artemis-main/third_party/mobile_use/utils/cli_helpers.py +60 -0
- package/artemis-main/third_party/mobile_use/utils/decorators.py +158 -0
- package/artemis-main/third_party/mobile_use/utils/file.py +31 -0
- package/artemis-main/third_party/mobile_use/utils/logger.py +216 -0
- package/artemis-main/third_party/mobile_use/utils/media.py +213 -0
- package/artemis-main/third_party/mobile_use/utils/shell_utils.py +39 -0
- package/artemis-main/third_party/mobile_use/utils/video.py +398 -0
- package/artemis-main/uv.lock +4908 -0
- package/bin/android-midscene-automation.js +1 -5
- package/bin/artemis-config.mjs +15 -0
- package/bin/artemis-embedded.py +56 -0
- package/bin/start-artemis.mjs +30 -0
- package/bin/start-dev.mjs +112 -0
- package/bin/test_artemis_embedded.py +44 -0
- package/package.json +4 -2
- package/server/artemis.ts +34 -0
- package/server/http-api.ts +12 -0
- package/src/App.vue +10 -5
- package/src/api.ts +5 -0
- package/src/appium-recorder/AppiumPage.vue +1 -1
- package/src/appium-recorder/components/ComponentInspector.vue +1 -1
- package/src/appium-recorder/components/FlowNodeSearch.vue +3 -3
- package/src/appium-recorder/components/ImageCheckDialog.vue +3 -3
- package/src/appium-recorder/components/RecorderWorkspace.vue +4 -4
- package/src/appium-recorder/flow-appearance.ts +4 -4
- package/src/components/AppSidebar.vue +8 -7
- package/src/components/MidsceneMaintenanceNotice.vue +17 -0
- package/src/components/analytics/AnalysisCharts.vue +4 -4
- package/src/components/analytics/ReportAnalysis.vue +1 -1
- package/src/components/generator/TestCaseFileUpload.vue +1 -1
- package/src/components/help/HelpCenter.vue +56 -37
- package/src/pages/AiGeneratorPage.vue +1 -1
- package/src/pages/ArtemisPage.vue +72 -0
- package/src/pages/ConfigPage.vue +18 -19
- package/src/style.css +340 -121
- package/src/types.ts +1 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
"""Utilities for handling app locking and initial app launch logic."""
|
|
20
|
+
|
|
21
|
+
import asyncio
|
|
22
|
+
|
|
23
|
+
from artemis.context import AppLaunchResult, ArtemisContext
|
|
24
|
+
from artemis.controllers.unified_controller import UnifiedMobileController
|
|
25
|
+
from artemis.data_engine.trace import TraceSpan
|
|
26
|
+
from artemis.utils.app_launch_utils import current_foreground, observe_foreground
|
|
27
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
28
|
+
|
|
29
|
+
logger = get_logger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def _poll_for_app_ready(
|
|
33
|
+
ctx: ArtemisContext,
|
|
34
|
+
app_package: str,
|
|
35
|
+
max_poll_seconds: int = 15,
|
|
36
|
+
poll_interval: float = 1.0,
|
|
37
|
+
) -> tuple[bool, str | None]:
|
|
38
|
+
"""Poll for app to be ready after launch.
|
|
39
|
+
|
|
40
|
+
Treats mCurrentFocus=null as a loading state and keeps polling.
|
|
41
|
+
Only fails if we get a different (non-null) package or timeout.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
ctx: Mobile use context
|
|
45
|
+
app_package: Expected package name
|
|
46
|
+
max_poll_seconds: Maximum time to poll (default: 15s)
|
|
47
|
+
poll_interval: Time between polls (default: 1s)
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
Tuple of (success: bool, error_message: str | None)
|
|
51
|
+
"""
|
|
52
|
+
polls = int(max_poll_seconds / poll_interval)
|
|
53
|
+
|
|
54
|
+
for i in range(polls):
|
|
55
|
+
ready, current_package, task = await observe_foreground(ctx, app_package)
|
|
56
|
+
|
|
57
|
+
if ready:
|
|
58
|
+
if current_package == app_package:
|
|
59
|
+
logger.success(f"App {app_package} is ready (took ~{i * poll_interval:.1f}s)")
|
|
60
|
+
else:
|
|
61
|
+
logger.success(
|
|
62
|
+
f"App {app_package} is ready (focused window belongs to"
|
|
63
|
+
f" '{current_package}', but the foreground {task.describe()} is owned"
|
|
64
|
+
f" by {app_package}, took ~{i * poll_interval:.1f}s)"
|
|
65
|
+
)
|
|
66
|
+
return True, None
|
|
67
|
+
|
|
68
|
+
if current_package is None:
|
|
69
|
+
logger.debug(f"Poll {i + 1}/{polls}: App loading (mCurrentFocus=null)...")
|
|
70
|
+
else:
|
|
71
|
+
logger.debug(
|
|
72
|
+
f"Poll {i + 1}/{polls}: Wrong app in foreground (expected"
|
|
73
|
+
f" '{app_package}', got '{current_package}', foreground"
|
|
74
|
+
f" {task.describe() if task else 'task unknown'}). Still waiting..."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
if i < polls - 1:
|
|
78
|
+
await asyncio.sleep(poll_interval)
|
|
79
|
+
|
|
80
|
+
current_package, task = await current_foreground(ctx)
|
|
81
|
+
error_msg = (
|
|
82
|
+
f"Timeout waiting for {app_package} to load after {max_poll_seconds}s. "
|
|
83
|
+
f"Current foreground: {current_package}; foreground"
|
|
84
|
+
f" {task.describe() if task else 'task unknown'}"
|
|
85
|
+
)
|
|
86
|
+
logger.error(error_msg)
|
|
87
|
+
return False, error_msg
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
async def launch_app_with_retries(
|
|
91
|
+
ctx: ArtemisContext,
|
|
92
|
+
app_package: str,
|
|
93
|
+
max_retries: int = 3,
|
|
94
|
+
max_poll_seconds: int = 15,
|
|
95
|
+
) -> tuple[bool, str | None]:
|
|
96
|
+
"""Launch an app with retry logic and smart polling.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
ctx: Mobile use context
|
|
100
|
+
app_package: Package name (Android) to launch
|
|
101
|
+
max_retries: Maximum number of launch attempts (default: 3)
|
|
102
|
+
max_poll_seconds: Maximum time to wait for app to load per attempt
|
|
103
|
+
(default: 15s)
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Tuple of (success: bool, error_message: str | None)
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
for attempt in range(1, max_retries + 1):
|
|
110
|
+
logger.info(f"Launch attempt {attempt}/{max_retries} for app {app_package}")
|
|
111
|
+
|
|
112
|
+
with TraceSpan(
|
|
113
|
+
name=f"Launch Attempt {attempt}",
|
|
114
|
+
trace_type="span",
|
|
115
|
+
ctx=ctx,
|
|
116
|
+
) as span:
|
|
117
|
+
span.payload = {"attempt": attempt, "app_package": app_package}
|
|
118
|
+
|
|
119
|
+
controller = UnifiedMobileController(ctx)
|
|
120
|
+
if attempt > 1:
|
|
121
|
+
logger.warning(
|
|
122
|
+
f"Attempt {attempt - 1} failed. Force stopping"
|
|
123
|
+
f" '{app_package}' to clear frozen state before retrying..."
|
|
124
|
+
)
|
|
125
|
+
await controller.terminate_app(app_package)
|
|
126
|
+
await asyncio.sleep(1.0)
|
|
127
|
+
|
|
128
|
+
launch_success = await controller.launch_app(app_package)
|
|
129
|
+
if not launch_success:
|
|
130
|
+
error_msg = f"Failed to execute launch command for {app_package}"
|
|
131
|
+
logger.error(error_msg)
|
|
132
|
+
span.status = "failed"
|
|
133
|
+
span.error = error_msg
|
|
134
|
+
if attempt == max_retries:
|
|
135
|
+
return False, error_msg
|
|
136
|
+
await asyncio.sleep(2)
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
await asyncio.sleep(1)
|
|
140
|
+
|
|
141
|
+
success, error_msg = await _poll_for_app_ready(ctx, app_package, max_poll_seconds)
|
|
142
|
+
|
|
143
|
+
if success:
|
|
144
|
+
span.status = "success"
|
|
145
|
+
span.result = "App is ready"
|
|
146
|
+
return True, None
|
|
147
|
+
|
|
148
|
+
span.status = "failed"
|
|
149
|
+
span.error = error_msg
|
|
150
|
+
|
|
151
|
+
if attempt < max_retries:
|
|
152
|
+
logger.warning(f"Attempt {attempt} failed: {error_msg}. Retrying...")
|
|
153
|
+
await asyncio.sleep(1)
|
|
154
|
+
|
|
155
|
+
error_msg = f"Failed to launch {app_package} after {max_retries} attempts"
|
|
156
|
+
logger.error(error_msg)
|
|
157
|
+
return False, error_msg
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
async def _handle_initial_app_launch(
|
|
161
|
+
ctx: ArtemisContext,
|
|
162
|
+
locked_app_package: str,
|
|
163
|
+
) -> AppLaunchResult:
|
|
164
|
+
"""Handle initial app launch verification and launching if needed.
|
|
165
|
+
|
|
166
|
+
If locked_app_package is set:
|
|
167
|
+
1. Check if the app is already in the foreground
|
|
168
|
+
2. If not, attempt to launch it (with retries)
|
|
169
|
+
3. Return status with success/error information
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
ctx: Mobile use context
|
|
173
|
+
locked_app_package: Package name (Android) to lock to
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
AppLaunchResult with launch status and error information
|
|
177
|
+
"""
|
|
178
|
+
if not locked_app_package:
|
|
179
|
+
error_msg = f"Invalid locked_app_package: '{locked_app_package}'"
|
|
180
|
+
logger.error(error_msg)
|
|
181
|
+
return AppLaunchResult(
|
|
182
|
+
locked_app_package=locked_app_package,
|
|
183
|
+
locked_app_initial_launch_success=False,
|
|
184
|
+
locked_app_initial_launch_error=error_msg,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
logger.info(f"Starting initial app launch for package: {locked_app_package}")
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
already_foreground, current_package, _ = await observe_foreground(ctx, locked_app_package)
|
|
191
|
+
logger.info(f"Current foreground app: {current_package}")
|
|
192
|
+
|
|
193
|
+
if already_foreground:
|
|
194
|
+
logger.info(f"App {locked_app_package} is already in foreground")
|
|
195
|
+
return AppLaunchResult(
|
|
196
|
+
locked_app_package=locked_app_package,
|
|
197
|
+
locked_app_initial_launch_success=True,
|
|
198
|
+
locked_app_initial_launch_error=None,
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
logger.info(f"App {locked_app_package} not in foreground, attempting to launch")
|
|
202
|
+
success, error_msg = await launch_app_with_retries(ctx, locked_app_package)
|
|
203
|
+
|
|
204
|
+
return AppLaunchResult(
|
|
205
|
+
locked_app_package=locked_app_package,
|
|
206
|
+
locked_app_initial_launch_success=success,
|
|
207
|
+
locked_app_initial_launch_error=error_msg,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
error_msg = f"Exception during initial app launch: {str(e)}"
|
|
212
|
+
logger.error(error_msg)
|
|
213
|
+
return AppLaunchResult(
|
|
214
|
+
locked_app_package=locked_app_package,
|
|
215
|
+
locked_app_initial_launch_success=False,
|
|
216
|
+
locked_app_initial_launch_error=error_msg,
|
|
217
|
+
)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
from shutil import which
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
from adbutils import AdbClient
|
|
23
|
+
from rich.console import Console
|
|
24
|
+
|
|
25
|
+
from third_party.mobile_use.utils.logger import get_logger
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def display_device_status(console: Console, adb_client: AdbClient | None = None):
|
|
31
|
+
"""Checks for connected devices and displays the status."""
|
|
32
|
+
console.print("\n[bold]📱 Device Status[/bold]")
|
|
33
|
+
devices = None
|
|
34
|
+
if adb_client is not None:
|
|
35
|
+
devices = adb_client.device_list()
|
|
36
|
+
if devices:
|
|
37
|
+
console.print("✅ [bold green]Android device(s) connected:[/bold green]")
|
|
38
|
+
for device in devices:
|
|
39
|
+
console.print(f" - {device.serial}")
|
|
40
|
+
else:
|
|
41
|
+
console.print("❌ [bold red]No Android device found.[/bold red]")
|
|
42
|
+
command = "emulator -avd <avd_name>"
|
|
43
|
+
if sys.platform not in ["win32", "darwin"]:
|
|
44
|
+
command = f"./{command}"
|
|
45
|
+
console.print(
|
|
46
|
+
f"You can start an emulator using a command like: [bold]'{command}'[/bold]"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def display_local_device_status(console: Console, host: str | None, port: int | None) -> None:
|
|
51
|
+
"""Display connected Android devices, using a local ADB client when ``adb`` is available."""
|
|
52
|
+
adb_client = None
|
|
53
|
+
try:
|
|
54
|
+
if which("adb"):
|
|
55
|
+
adb_client = AdbClient(host=host or "localhost", port=port or 5037)
|
|
56
|
+
except Exception as exc:
|
|
57
|
+
# Optional cosmetic device-status display; run continues without it.
|
|
58
|
+
logger.debug(f"Could not create ADB client for device status display: {exc}")
|
|
59
|
+
|
|
60
|
+
display_device_status(console, adb_client=adb_client)
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
import asyncio
|
|
20
|
+
from collections.abc import Awaitable, Callable
|
|
21
|
+
from functools import wraps
|
|
22
|
+
import inspect
|
|
23
|
+
from typing import Any, TypeVar, cast, overload
|
|
24
|
+
|
|
25
|
+
R = TypeVar("R")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def wrap_with_callbacks_sync(
|
|
29
|
+
fn: Callable[..., R],
|
|
30
|
+
*,
|
|
31
|
+
before: Callable[..., None] | None = None,
|
|
32
|
+
on_success: Callable[[R], None] | None = None,
|
|
33
|
+
on_failure: Callable[[Exception], None] | None = None,
|
|
34
|
+
suppress_exceptions: bool = False,
|
|
35
|
+
) -> Callable[..., R]:
|
|
36
|
+
@wraps(fn)
|
|
37
|
+
def wrapper(*args: Any, **kwargs: Any) -> R:
|
|
38
|
+
if before:
|
|
39
|
+
before()
|
|
40
|
+
try:
|
|
41
|
+
result = fn(*args, **kwargs)
|
|
42
|
+
if on_success:
|
|
43
|
+
on_success(result)
|
|
44
|
+
return result
|
|
45
|
+
except Exception as e:
|
|
46
|
+
if on_failure:
|
|
47
|
+
on_failure(e)
|
|
48
|
+
if suppress_exceptions:
|
|
49
|
+
return None # type: ignore
|
|
50
|
+
raise
|
|
51
|
+
|
|
52
|
+
return wrapper
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def wrap_with_callbacks_async(
|
|
56
|
+
fn: Callable[..., Awaitable[R]],
|
|
57
|
+
*,
|
|
58
|
+
before: Callable[..., None] | None = None,
|
|
59
|
+
on_success: Callable[[R], None] | None = None,
|
|
60
|
+
on_failure: Callable[[Exception], None] | None = None,
|
|
61
|
+
suppress_exceptions: bool = False,
|
|
62
|
+
) -> Callable[..., Awaitable[R]]:
|
|
63
|
+
@wraps(fn)
|
|
64
|
+
async def wrapper(*args: Any, **kwargs: Any) -> R:
|
|
65
|
+
if before:
|
|
66
|
+
before()
|
|
67
|
+
try:
|
|
68
|
+
result = await fn(*args, **kwargs)
|
|
69
|
+
if on_success:
|
|
70
|
+
on_success(result)
|
|
71
|
+
return result
|
|
72
|
+
except Exception as e:
|
|
73
|
+
if on_failure:
|
|
74
|
+
on_failure(e)
|
|
75
|
+
if suppress_exceptions:
|
|
76
|
+
return None # type: ignore
|
|
77
|
+
raise
|
|
78
|
+
|
|
79
|
+
return wrapper
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@overload
|
|
83
|
+
def wrap_with_callbacks(
|
|
84
|
+
fn: Callable[..., Awaitable[R]],
|
|
85
|
+
*,
|
|
86
|
+
before: Callable[[], None] | None = ...,
|
|
87
|
+
on_success: Callable[[R], None] | None = ...,
|
|
88
|
+
on_failure: Callable[[Exception], None] | None = ...,
|
|
89
|
+
suppress_exceptions: bool = ...,
|
|
90
|
+
) -> Callable[..., Awaitable[R]]: ...
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@overload
|
|
94
|
+
def wrap_with_callbacks(
|
|
95
|
+
*,
|
|
96
|
+
before: Callable[..., None] | None = ...,
|
|
97
|
+
on_success: Callable[[Any], None] | None = ...,
|
|
98
|
+
on_failure: Callable[[Exception], None] | None = ...,
|
|
99
|
+
suppress_exceptions: bool = ...,
|
|
100
|
+
) -> Callable[[Callable[..., R]], Callable[..., R]]: ...
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@overload
|
|
104
|
+
def wrap_with_callbacks(
|
|
105
|
+
fn: Callable[..., R],
|
|
106
|
+
*,
|
|
107
|
+
before: Callable[[], None] | None = ...,
|
|
108
|
+
on_success: Callable[[R], None] | None = ...,
|
|
109
|
+
on_failure: Callable[[Exception], None] | None = ...,
|
|
110
|
+
suppress_exceptions: bool = ...,
|
|
111
|
+
) -> Callable[..., R]: ...
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def wrap_with_callbacks(
|
|
115
|
+
fn: Callable[..., Any] | None = None,
|
|
116
|
+
*,
|
|
117
|
+
before: Callable[[], None] | None = None,
|
|
118
|
+
on_success: Callable[[Any], None] | None = None,
|
|
119
|
+
on_failure: Callable[[Exception], None] | None = None,
|
|
120
|
+
suppress_exceptions: bool = False,
|
|
121
|
+
) -> Any:
|
|
122
|
+
def decorator(func: Callable[..., Any]) -> Any:
|
|
123
|
+
if inspect.iscoroutinefunction(func):
|
|
124
|
+
return wrap_with_callbacks_async(
|
|
125
|
+
cast(Callable[..., Awaitable[Any]], func),
|
|
126
|
+
before=before,
|
|
127
|
+
on_success=on_success,
|
|
128
|
+
on_failure=on_failure,
|
|
129
|
+
suppress_exceptions=suppress_exceptions,
|
|
130
|
+
)
|
|
131
|
+
else:
|
|
132
|
+
return wrap_with_callbacks_sync(
|
|
133
|
+
cast(Callable[..., Any], func),
|
|
134
|
+
before=before,
|
|
135
|
+
on_success=on_success,
|
|
136
|
+
on_failure=on_failure,
|
|
137
|
+
suppress_exceptions=suppress_exceptions,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
if fn is None:
|
|
141
|
+
return decorator
|
|
142
|
+
else:
|
|
143
|
+
return decorator(fn)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def agent_lifecycle_logging(
|
|
147
|
+
agent_name: str, logger: Any
|
|
148
|
+
) -> Callable[[Callable[..., R]], Callable[..., R]]:
|
|
149
|
+
"""Log the start, success and failure of an agent node call.
|
|
150
|
+
|
|
151
|
+
``agent_name`` is the display name, e.g. ``"Planner"``.
|
|
152
|
+
"""
|
|
153
|
+
label = f"{agent_name} Agent"
|
|
154
|
+
return wrap_with_callbacks(
|
|
155
|
+
before=lambda: logger.info(f"Starting {label}..."),
|
|
156
|
+
on_success=lambda _: logger.success(label),
|
|
157
|
+
on_failure=lambda _: logger.error(label),
|
|
158
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import re
|
|
21
|
+
from typing import IO
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def strip_json_comments(text: str) -> str:
|
|
25
|
+
text = re.sub(r"//.*?$", "", text, flags=re.MULTILINE)
|
|
26
|
+
text = re.sub(r"/\*.*?\*/", "", text, flags=re.DOTALL)
|
|
27
|
+
return text
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_jsonc(file: IO) -> dict:
|
|
31
|
+
return json.loads(strip_json_comments(file.read()))
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Copyright 2025-2026 Minitap, Inc.
|
|
2
|
+
# Modifications Copyright 2026 Google LLC
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
#
|
|
16
|
+
# Originally from mobile-use (https://github.com/minitap-ai/mobile-use).
|
|
17
|
+
# See third_party/mobile_use/METADATA for the upstream source and local modifications.
|
|
18
|
+
|
|
19
|
+
from enum import Enum
|
|
20
|
+
import logging
|
|
21
|
+
import os
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
import queue
|
|
24
|
+
import sys
|
|
25
|
+
import threading
|
|
26
|
+
|
|
27
|
+
from colorama import Fore, Style, init
|
|
28
|
+
|
|
29
|
+
from artemis.data_engine.context_vars import CURRENT_TRACE_ID
|
|
30
|
+
|
|
31
|
+
init(autoreset=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LogLevel(Enum):
|
|
35
|
+
DEBUG = ("DEBUG", Fore.MAGENTA, "🔍")
|
|
36
|
+
INFO = ("INFO", Fore.WHITE, "ℹ")
|
|
37
|
+
SUCCESS = ("SUCCESS", Fore.GREEN, "✓")
|
|
38
|
+
WARNING = ("WARNING", Fore.YELLOW, "⚠")
|
|
39
|
+
ERROR = ("ERROR", Fore.RED, "❌")
|
|
40
|
+
CRITICAL = ("CRITICAL", Fore.RED + Style.BRIGHT, "💥")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class DataEngineHandler(logging.Handler):
|
|
44
|
+
def __init__(self, *args, **kwargs):
|
|
45
|
+
super().__init__(*args, **kwargs)
|
|
46
|
+
self._log_queue = queue.Queue(maxsize=5000)
|
|
47
|
+
self._worker_thread = threading.Thread(target=self._drain_queue, daemon=True)
|
|
48
|
+
self._worker_thread.start()
|
|
49
|
+
|
|
50
|
+
def _drain_queue(self):
|
|
51
|
+
while True:
|
|
52
|
+
item = self._log_queue.get()
|
|
53
|
+
if item is None:
|
|
54
|
+
break
|
|
55
|
+
engine_mod = sys.modules.get("artemis.data_engine.engine")
|
|
56
|
+
if engine_mod and getattr(engine_mod, "_CURRENT_DATA_ENGINE", None):
|
|
57
|
+
_CURRENT_DATA_ENGINE = engine_mod._CURRENT_DATA_ENGINE
|
|
58
|
+
if _CURRENT_DATA_ENGINE and _CURRENT_DATA_ENGINE.current_session_id:
|
|
59
|
+
record_name, message, level_name, formatted, parent_id = item
|
|
60
|
+
_CURRENT_DATA_ENGINE.record_trace(
|
|
61
|
+
type="log",
|
|
62
|
+
name=record_name,
|
|
63
|
+
payload={
|
|
64
|
+
"message": message,
|
|
65
|
+
"level": level_name,
|
|
66
|
+
"formatted": formatted,
|
|
67
|
+
},
|
|
68
|
+
status="success",
|
|
69
|
+
parent_trace_id=parent_id,
|
|
70
|
+
)
|
|
71
|
+
self._log_queue.task_done()
|
|
72
|
+
|
|
73
|
+
def emit(self, record):
|
|
74
|
+
engine_mod = sys.modules.get("artemis.data_engine.engine")
|
|
75
|
+
if not engine_mod or not getattr(engine_mod, "_CURRENT_DATA_ENGINE", None):
|
|
76
|
+
return
|
|
77
|
+
_CURRENT_DATA_ENGINE = engine_mod._CURRENT_DATA_ENGINE
|
|
78
|
+
|
|
79
|
+
if _CURRENT_DATA_ENGINE and _CURRENT_DATA_ENGINE.current_session_id:
|
|
80
|
+
log_entry = self.format(record)
|
|
81
|
+
log_level = getattr(record, "log_level", None)
|
|
82
|
+
if log_level and hasattr(log_level, "value"):
|
|
83
|
+
level_name, _color, _symbol = log_level.value
|
|
84
|
+
else:
|
|
85
|
+
level_name = record.levelname
|
|
86
|
+
|
|
87
|
+
parent_id = CURRENT_TRACE_ID.get()
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
self._log_queue.put_nowait(
|
|
91
|
+
(
|
|
92
|
+
record.name,
|
|
93
|
+
record.getMessage(),
|
|
94
|
+
level_name,
|
|
95
|
+
log_entry,
|
|
96
|
+
parent_id,
|
|
97
|
+
)
|
|
98
|
+
)
|
|
99
|
+
except queue.Full as e:
|
|
100
|
+
raise RuntimeError(
|
|
101
|
+
"DataEngine log queue is full - cannot accept more log"
|
|
102
|
+
" entries without dropping records"
|
|
103
|
+
) from e
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ArtemisLogger:
|
|
107
|
+
def __init__(
|
|
108
|
+
self,
|
|
109
|
+
name: str,
|
|
110
|
+
console_level: str = "INFO",
|
|
111
|
+
):
|
|
112
|
+
"""Initialize the ARTEMIS logger.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
name: Logger name (usually __name__)
|
|
116
|
+
console_level: Minimum level for console output
|
|
117
|
+
"""
|
|
118
|
+
self.name = name
|
|
119
|
+
self.logger = logging.getLogger(name)
|
|
120
|
+
self.logger.setLevel(logging.DEBUG)
|
|
121
|
+
|
|
122
|
+
self.logger.handlers.clear()
|
|
123
|
+
self.logger.propagate = False
|
|
124
|
+
|
|
125
|
+
self._setup_console_handler(console_level)
|
|
126
|
+
|
|
127
|
+
# Add DataEngineHandler
|
|
128
|
+
data_engine_handler = DataEngineHandler()
|
|
129
|
+
data_engine_handler.setLevel(logging.DEBUG)
|
|
130
|
+
formatter = logging.Formatter(fmt="%(asctime)s | %(name)s | %(levelname)s | %(message)s")
|
|
131
|
+
data_engine_handler.setFormatter(formatter)
|
|
132
|
+
self.logger.addHandler(data_engine_handler)
|
|
133
|
+
|
|
134
|
+
if os.environ.get("ARTEMIS_MCP_SERVER") == "true":
|
|
135
|
+
traces_dir = os.environ.get("ARTEMIS_TRACES_PATH", "traces")
|
|
136
|
+
log_path = Path(traces_dir) / "mcp_server.log"
|
|
137
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
138
|
+
file_handler = logging.FileHandler(log_path, encoding="utf-8")
|
|
139
|
+
file_handler.setLevel(logging.DEBUG)
|
|
140
|
+
file_handler.setFormatter(formatter)
|
|
141
|
+
self.logger.addHandler(file_handler)
|
|
142
|
+
|
|
143
|
+
def _setup_console_handler(self, level: str):
|
|
144
|
+
console_handler = logging.StreamHandler(sys.stderr)
|
|
145
|
+
console_handler.setLevel(getattr(logging, level.upper()))
|
|
146
|
+
|
|
147
|
+
console_formatter = ColoredFormatter()
|
|
148
|
+
console_handler.setFormatter(console_formatter)
|
|
149
|
+
|
|
150
|
+
self.logger.addHandler(console_handler)
|
|
151
|
+
|
|
152
|
+
def debug(self, message: str, **kwargs):
|
|
153
|
+
self.logger.debug(message, extra={"log_level": LogLevel.DEBUG}, **kwargs)
|
|
154
|
+
|
|
155
|
+
def info(self, message: str, **kwargs):
|
|
156
|
+
self.logger.info(message, extra={"log_level": LogLevel.INFO}, **kwargs)
|
|
157
|
+
|
|
158
|
+
def success(self, message: str, **kwargs):
|
|
159
|
+
self.logger.info(message, extra={"log_level": LogLevel.SUCCESS}, **kwargs)
|
|
160
|
+
|
|
161
|
+
def warning(self, message: str, **kwargs):
|
|
162
|
+
self.logger.warning(message, extra={"log_level": LogLevel.WARNING}, **kwargs)
|
|
163
|
+
|
|
164
|
+
def error(self, message: str, **kwargs):
|
|
165
|
+
self.logger.error(message, extra={"log_level": LogLevel.ERROR}, **kwargs)
|
|
166
|
+
|
|
167
|
+
def critical(self, message: str, **kwargs):
|
|
168
|
+
self.logger.critical(message, extra={"log_level": LogLevel.CRITICAL}, **kwargs)
|
|
169
|
+
|
|
170
|
+
def header(self, message: str, **_kwargs):
|
|
171
|
+
separator = "=" * 60
|
|
172
|
+
colored_separator = f"{Fore.CYAN}{separator}{Style.RESET_ALL}"
|
|
173
|
+
colored_message = f"{Fore.CYAN}{message}{Style.RESET_ALL}"
|
|
174
|
+
|
|
175
|
+
print(colored_separator, file=sys.stderr)
|
|
176
|
+
print(colored_message, file=sys.stderr)
|
|
177
|
+
print(colored_separator, file=sys.stderr)
|
|
178
|
+
self.logger.info(f"\n{separator}\n{message}\n{separator}")
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ColoredFormatter(logging.Formatter):
|
|
182
|
+
def format(self, record):
|
|
183
|
+
log_level = getattr(record, "log_level", LogLevel.INFO)
|
|
184
|
+
_level_name, color, symbol = log_level.value
|
|
185
|
+
|
|
186
|
+
colored_message = f"{color}{symbol} {record.getMessage()}{Style.RESET_ALL}"
|
|
187
|
+
|
|
188
|
+
return colored_message
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
_loggers = {}
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def get_logger(
|
|
195
|
+
name: str,
|
|
196
|
+
console_level: str = "INFO",
|
|
197
|
+
) -> ArtemisLogger:
|
|
198
|
+
"""Get or create a logger instance.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
name: Logger name (usually __name__)
|
|
202
|
+
log_file: Path to log file (defaults to logs/{name}.log)
|
|
203
|
+
console_level: Minimum level for console output
|
|
204
|
+
file_level: Minimum level for file output
|
|
205
|
+
enable_file_logging: Whether to enable file logging
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
ArtemisLogger instance
|
|
209
|
+
"""
|
|
210
|
+
if name not in _loggers:
|
|
211
|
+
_loggers[name] = ArtemisLogger(
|
|
212
|
+
name=name,
|
|
213
|
+
console_level=console_level,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
return _loggers[name]
|