android-midscene-automation 0.2.4 → 1.1.0
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 +7 -0
- package/README.md +35 -0
- 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,49 @@
|
|
|
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
|
+
"""Shared FastMCP instance definition for the mobile automation MCP server."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
from mcp.server.fastmcp import FastMCP
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
from mcp.server.fastmcp.server import Settings as FastMCPSettings
|
|
25
|
+
|
|
26
|
+
FastMCPSettings.model_rebuild()
|
|
27
|
+
except Exception as exc:
|
|
28
|
+
# Version-compat shim: older/newer FastMCP releases may not expose
|
|
29
|
+
# Settings or need the rebuild; the server works without it.
|
|
30
|
+
logger.debug("FastMCP Settings.model_rebuild skipped: %s", exc, exc_info=True)
|
|
31
|
+
|
|
32
|
+
# Define the shared FastMCP instance for external IDE and agent clients.
|
|
33
|
+
mcp = FastMCP(
|
|
34
|
+
"artemis",
|
|
35
|
+
instructions=(
|
|
36
|
+
"ARTEMIS is an autonomous mobile AI agent and Android UI automation engine. "
|
|
37
|
+
"Use mobile_run_task to launch autonomous UI workflows on connected Android devices or emulators, "
|
|
38
|
+
"mobile_manage_task to check status or steer execution, "
|
|
39
|
+
"mobile_get_device_state to inspect real-time device screen/hierarchy, "
|
|
40
|
+
"mobile_inspect_trace to inspect detailed execution steps and visual action overlays, "
|
|
41
|
+
"and mobile_diagnose whenever a tool errors, no device is found, or the user says "
|
|
42
|
+
"ARTEMIS is not working: it checks the environment and returns ordered fix steps."
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Tool registration is intentionally NOT done here: mcp_server/__init__.py
|
|
47
|
+
# imports mcp_server.tools (which imports this module), so importing this
|
|
48
|
+
# module from anywhere already registers every tool. Importing tools here as
|
|
49
|
+
# well would create a circular import (base -> tools -> base).
|
|
@@ -0,0 +1,74 @@
|
|
|
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
|
+
"""Universal MCP Multi-Environment Notification System."""
|
|
16
|
+
|
|
17
|
+
from typing import Any, Dict, Optional
|
|
18
|
+
|
|
19
|
+
from mcp_server.notifiers.agentapi import AgentApiNotifier
|
|
20
|
+
from mcp_server.notifiers.base import BaseNotifier
|
|
21
|
+
from mcp_server.notifiers.composite import CompositeNotifier
|
|
22
|
+
from mcp_server.notifiers.desktop import DesktopNotifier
|
|
23
|
+
from mcp_server.notifiers.file import FileNotifier
|
|
24
|
+
from mcp_server.notifiers.script import ScriptNotifier
|
|
25
|
+
from mcp_server.notifiers.webhook import WebhookNotifier
|
|
26
|
+
|
|
27
|
+
_default_notifier = CompositeNotifier()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_default_notifier() -> CompositeNotifier:
|
|
31
|
+
"""Returns the global default composite notifier instance."""
|
|
32
|
+
return _default_notifier
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def notify(
|
|
36
|
+
conversation_id: str,
|
|
37
|
+
message: str,
|
|
38
|
+
title: str | None = None,
|
|
39
|
+
event_type: str = "completed",
|
|
40
|
+
payload: dict[str, Any] | None = None,
|
|
41
|
+
) -> bool:
|
|
42
|
+
"""Dispatches a notification across all available channels (AgentAPI, Webhook, Desktop, Script, File)."""
|
|
43
|
+
try:
|
|
44
|
+
return _default_notifier.notify(
|
|
45
|
+
conversation_id=conversation_id,
|
|
46
|
+
message=message,
|
|
47
|
+
title=title,
|
|
48
|
+
event_type=event_type,
|
|
49
|
+
payload=payload,
|
|
50
|
+
)
|
|
51
|
+
except Exception as e:
|
|
52
|
+
import logging
|
|
53
|
+
|
|
54
|
+
logging.getLogger("mcp_server.notifiers").debug(f"Global notify error: {e}")
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def notify_jetski(conversation_id: str, message: str) -> bool:
|
|
59
|
+
"""Backward-compatible alias for notify()."""
|
|
60
|
+
return notify(conversation_id=conversation_id, message=message, event_type="completed")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"BaseNotifier",
|
|
65
|
+
"AgentApiNotifier",
|
|
66
|
+
"WebhookNotifier",
|
|
67
|
+
"DesktopNotifier",
|
|
68
|
+
"ScriptNotifier",
|
|
69
|
+
"FileNotifier",
|
|
70
|
+
"CompositeNotifier",
|
|
71
|
+
"get_default_notifier",
|
|
72
|
+
"notify",
|
|
73
|
+
"notify_jetski",
|
|
74
|
+
]
|
|
@@ -0,0 +1,289 @@
|
|
|
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
|
+
"""AgentAPI Notifier for Jetski / Antigravity environments."""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import shutil
|
|
21
|
+
import subprocess
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from mcp_server.notifiers.base import BaseNotifier
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("mcp_server.notifiers.agentapi")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AgentApiNotifier(BaseNotifier):
|
|
30
|
+
"""Notifier implementation for environments supporting the agentapi CLI (e.g. Jetski / Antigravity)."""
|
|
31
|
+
|
|
32
|
+
def __init__(self):
|
|
33
|
+
super().__init__()
|
|
34
|
+
try:
|
|
35
|
+
addr = os.environ.get("ANTIGRAVITY_LS_ADDRESS")
|
|
36
|
+
token = os.environ.get("ANTIGRAVITY_CSRF_TOKEN")
|
|
37
|
+
if addr and token:
|
|
38
|
+
self._save_shared_env(addr, token)
|
|
39
|
+
except Exception as exc:
|
|
40
|
+
# Environment recovery is opportunistic; notifier construction
|
|
41
|
+
# must never fail because of it.
|
|
42
|
+
logger.debug("[EnvRecovery] Skipped saving shared env: %s", exc, exc_info=True)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return "agentapi"
|
|
47
|
+
|
|
48
|
+
def _find_agentapi_path(self) -> str | None:
|
|
49
|
+
"""Locates the agentapi executable."""
|
|
50
|
+
try:
|
|
51
|
+
which_path = shutil.which("agentapi")
|
|
52
|
+
if which_path:
|
|
53
|
+
return which_path
|
|
54
|
+
|
|
55
|
+
candidates = [
|
|
56
|
+
os.path.expanduser("~/.gemini/jetski/bin/agentapi"),
|
|
57
|
+
os.path.expanduser("~/.artemis/bin/agentapi"),
|
|
58
|
+
os.path.expanduser("~/bin/agentapi"),
|
|
59
|
+
"/usr/local/bin/agentapi",
|
|
60
|
+
]
|
|
61
|
+
for cand in candidates:
|
|
62
|
+
if os.path.exists(cand) and os.access(cand, os.X_OK):
|
|
63
|
+
return cand
|
|
64
|
+
except OSError:
|
|
65
|
+
# Filesystem probe failed: report the binary as absent.
|
|
66
|
+
pass
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
def is_available(self) -> bool:
|
|
70
|
+
"""Returns True if agentapi binary exists or LS address is detected."""
|
|
71
|
+
try:
|
|
72
|
+
if self._find_agentapi_path():
|
|
73
|
+
return True
|
|
74
|
+
if "ANTIGRAVITY_LS_ADDRESS" in os.environ:
|
|
75
|
+
return True
|
|
76
|
+
except OSError:
|
|
77
|
+
# Filesystem probe failed: report the notifier as unavailable.
|
|
78
|
+
pass
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
def _save_shared_env(self, addr: str, token: str) -> None:
|
|
82
|
+
"""Persists recovered environment to shared files for faster subsequent access."""
|
|
83
|
+
shared_candidates = [
|
|
84
|
+
os.path.expanduser("~/.gemini/jetski/.jetski_env"),
|
|
85
|
+
os.path.expanduser("~/.artemis/.artemis_env"),
|
|
86
|
+
]
|
|
87
|
+
try:
|
|
88
|
+
parent_dir = os.path.dirname(
|
|
89
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
90
|
+
)
|
|
91
|
+
shared_candidates.append(os.path.join(parent_dir, ".jetski_env"))
|
|
92
|
+
except OSError:
|
|
93
|
+
# Repo-relative candidate is optional; the home-dir ones remain.
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
data = {
|
|
97
|
+
"ANTIGRAVITY_LS_ADDRESS": addr,
|
|
98
|
+
"ANTIGRAVITY_CSRF_TOKEN": token,
|
|
99
|
+
}
|
|
100
|
+
for shared_file in shared_candidates:
|
|
101
|
+
try:
|
|
102
|
+
os.makedirs(os.path.dirname(shared_file), exist_ok=True)
|
|
103
|
+
with open(shared_file, "w", encoding="utf-8") as f:
|
|
104
|
+
json.dump(data, f, indent=2)
|
|
105
|
+
logger.debug(f"[EnvRecovery] Saved session to {shared_file}.")
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(f"[EnvRecovery] Error writing shared env file {shared_file}: {e}")
|
|
108
|
+
|
|
109
|
+
def _get_candidate_envs(self, force_proc_scan: bool = False) -> list[tuple[str, str]]:
|
|
110
|
+
"""Returns unique candidate (address, token) pairs ordered by priority and recency."""
|
|
111
|
+
candidates: list[tuple[str, str]] = []
|
|
112
|
+
seen: set[tuple[str, str]] = set()
|
|
113
|
+
|
|
114
|
+
def _add(addr: str | None, token: str | None) -> None:
|
|
115
|
+
if addr and token and (addr, token) not in seen:
|
|
116
|
+
seen.add((addr, token))
|
|
117
|
+
candidates.append((addr, token))
|
|
118
|
+
|
|
119
|
+
# 1. Current environment variables
|
|
120
|
+
_add(
|
|
121
|
+
os.environ.get("ANTIGRAVITY_LS_ADDRESS"),
|
|
122
|
+
os.environ.get("ANTIGRAVITY_CSRF_TOKEN"),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# 2. Shared env files (if not forcing fresh process scan)
|
|
126
|
+
if not force_proc_scan:
|
|
127
|
+
shared_candidates = [
|
|
128
|
+
os.path.expanduser("~/.gemini/jetski/.jetski_env"),
|
|
129
|
+
os.path.expanduser("~/.artemis/.artemis_env"),
|
|
130
|
+
]
|
|
131
|
+
try:
|
|
132
|
+
parent_dir = os.path.dirname(
|
|
133
|
+
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
134
|
+
)
|
|
135
|
+
shared_candidates.append(os.path.join(parent_dir, ".jetski_env"))
|
|
136
|
+
except OSError:
|
|
137
|
+
# Repo-relative candidate is optional; the home-dir ones remain.
|
|
138
|
+
pass
|
|
139
|
+
|
|
140
|
+
for shared_file in shared_candidates:
|
|
141
|
+
if os.path.exists(shared_file):
|
|
142
|
+
try:
|
|
143
|
+
with open(shared_file, encoding="utf-8") as f:
|
|
144
|
+
env_data = json.load(f)
|
|
145
|
+
_add(
|
|
146
|
+
env_data.get("ANTIGRAVITY_LS_ADDRESS"),
|
|
147
|
+
env_data.get("ANTIGRAVITY_CSRF_TOKEN"),
|
|
148
|
+
)
|
|
149
|
+
except Exception as e:
|
|
150
|
+
logger.debug(
|
|
151
|
+
f"[EnvRecovery] Error reading shared env file {shared_file}: {e}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# 3. Scan process table for active agent sessions, sorted by newest creation time first
|
|
155
|
+
proc_matches: list[tuple[float, str, str]] = []
|
|
156
|
+
try:
|
|
157
|
+
import psutil
|
|
158
|
+
|
|
159
|
+
for proc in psutil.process_iter(["create_time", "environ"]):
|
|
160
|
+
try:
|
|
161
|
+
env = proc.info.get("environ")
|
|
162
|
+
if env and "ANTIGRAVITY_LS_ADDRESS" in env and "ANTIGRAVITY_CSRF_TOKEN" in env:
|
|
163
|
+
ctime = proc.info.get("create_time", 0.0) or 0.0
|
|
164
|
+
proc_matches.append(
|
|
165
|
+
(
|
|
166
|
+
ctime,
|
|
167
|
+
env["ANTIGRAVITY_LS_ADDRESS"],
|
|
168
|
+
env["ANTIGRAVITY_CSRF_TOKEN"],
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
except psutil.Error:
|
|
172
|
+
# This process vanished or denies access: scan the rest.
|
|
173
|
+
continue
|
|
174
|
+
except ImportError:
|
|
175
|
+
import glob
|
|
176
|
+
|
|
177
|
+
for env_path in glob.glob("/proc/[0-9]*/environ"):
|
|
178
|
+
try:
|
|
179
|
+
mtime = os.path.getmtime(env_path)
|
|
180
|
+
with open(env_path, "rb") as f:
|
|
181
|
+
content = f.read()
|
|
182
|
+
env_vars = content.split(b"\x00")
|
|
183
|
+
local_addr = None
|
|
184
|
+
local_token = None
|
|
185
|
+
for var in env_vars:
|
|
186
|
+
if var.startswith(b"ANTIGRAVITY_LS_ADDRESS="):
|
|
187
|
+
local_addr = var.split(b"=", 1)[1].decode("utf-8", errors="ignore")
|
|
188
|
+
elif var.startswith(b"ANTIGRAVITY_CSRF_TOKEN="):
|
|
189
|
+
local_token = var.split(b"=", 1)[1].decode("utf-8", errors="ignore")
|
|
190
|
+
if local_addr and local_token:
|
|
191
|
+
proc_matches.append((mtime, local_addr, local_token))
|
|
192
|
+
except OSError:
|
|
193
|
+
# /proc entries vanish or deny access as processes exit.
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
# Sort matches descending by creation timestamp so active/newest sessions come first
|
|
197
|
+
proc_matches.sort(key=lambda x: x[0], reverse=True)
|
|
198
|
+
for _, addr, token in proc_matches:
|
|
199
|
+
_add(addr, token)
|
|
200
|
+
|
|
201
|
+
return candidates
|
|
202
|
+
|
|
203
|
+
def _load_shared_env(self, force_proc_scan: bool = False) -> None:
|
|
204
|
+
"""Recovers and loads the newest candidate ANTIGRAVITY_LS_ADDRESS and ANTIGRAVITY_CSRF_TOKEN."""
|
|
205
|
+
candidates = self._get_candidate_envs(force_proc_scan=force_proc_scan)
|
|
206
|
+
if candidates:
|
|
207
|
+
addr, token = candidates[0]
|
|
208
|
+
os.environ["ANTIGRAVITY_LS_ADDRESS"] = addr
|
|
209
|
+
os.environ["ANTIGRAVITY_CSRF_TOKEN"] = token
|
|
210
|
+
logger.debug(f"[EnvRecovery] Loaded session candidate: {addr}")
|
|
211
|
+
|
|
212
|
+
def notify(
|
|
213
|
+
self,
|
|
214
|
+
conversation_id: str,
|
|
215
|
+
message: str,
|
|
216
|
+
title: str | None = None,
|
|
217
|
+
event_type: str = "completed",
|
|
218
|
+
payload: dict[str, Any] | None = None,
|
|
219
|
+
) -> bool:
|
|
220
|
+
"""Dispatches a notification via the agentapi send-message command."""
|
|
221
|
+
try:
|
|
222
|
+
if not conversation_id or conversation_id.lower() in ("default", "none", ""):
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
agentapi_path = self._find_agentapi_path()
|
|
226
|
+
if not agentapi_path:
|
|
227
|
+
return False
|
|
228
|
+
|
|
229
|
+
cmd = [agentapi_path, "send-message"]
|
|
230
|
+
if title:
|
|
231
|
+
cmd.append(f"--title={title}")
|
|
232
|
+
cmd.extend([conversation_id, message])
|
|
233
|
+
|
|
234
|
+
candidates = self._get_candidate_envs(force_proc_scan=False)
|
|
235
|
+
if not candidates:
|
|
236
|
+
candidates = self._get_candidate_envs(force_proc_scan=True)
|
|
237
|
+
|
|
238
|
+
for attempt, (addr, token) in enumerate(candidates):
|
|
239
|
+
env = os.environ.copy()
|
|
240
|
+
env["ANTIGRAVITY_LS_ADDRESS"] = addr
|
|
241
|
+
env["ANTIGRAVITY_CSRF_TOKEN"] = token
|
|
242
|
+
try:
|
|
243
|
+
subprocess.run(
|
|
244
|
+
cmd,
|
|
245
|
+
capture_output=True,
|
|
246
|
+
text=True,
|
|
247
|
+
check=True,
|
|
248
|
+
timeout=10,
|
|
249
|
+
env=env,
|
|
250
|
+
)
|
|
251
|
+
os.environ["ANTIGRAVITY_LS_ADDRESS"] = addr
|
|
252
|
+
os.environ["ANTIGRAVITY_CSRF_TOKEN"] = token
|
|
253
|
+
self._save_shared_env(addr, token)
|
|
254
|
+
logger.info(f"AgentAPI notification sent successfully via {addr}.")
|
|
255
|
+
return True
|
|
256
|
+
except Exception as err:
|
|
257
|
+
logger.debug(f"Candidate session {addr} failed (attempt {attempt + 1}): {err}")
|
|
258
|
+
|
|
259
|
+
# If all initial candidates failed, force a fresh process scan in case a new session started
|
|
260
|
+
fresh_candidates = self._get_candidate_envs(force_proc_scan=True)
|
|
261
|
+
for addr, token in fresh_candidates:
|
|
262
|
+
if (addr, token) in candidates:
|
|
263
|
+
continue
|
|
264
|
+
env = os.environ.copy()
|
|
265
|
+
env["ANTIGRAVITY_LS_ADDRESS"] = addr
|
|
266
|
+
env["ANTIGRAVITY_CSRF_TOKEN"] = token
|
|
267
|
+
try:
|
|
268
|
+
subprocess.run(
|
|
269
|
+
cmd,
|
|
270
|
+
capture_output=True,
|
|
271
|
+
text=True,
|
|
272
|
+
check=True,
|
|
273
|
+
timeout=10,
|
|
274
|
+
env=env,
|
|
275
|
+
)
|
|
276
|
+
os.environ["ANTIGRAVITY_LS_ADDRESS"] = addr
|
|
277
|
+
os.environ["ANTIGRAVITY_CSRF_TOKEN"] = token
|
|
278
|
+
self._save_shared_env(addr, token)
|
|
279
|
+
logger.info(
|
|
280
|
+
f"AgentAPI notification sent successfully on fresh scan via {addr}."
|
|
281
|
+
)
|
|
282
|
+
return True
|
|
283
|
+
except Exception as err:
|
|
284
|
+
logger.debug(f"Fresh candidate session {addr} failed: {err}")
|
|
285
|
+
|
|
286
|
+
return False
|
|
287
|
+
except Exception as e:
|
|
288
|
+
logger.debug(f"AgentApiNotifier encountered unhandled error: {e}")
|
|
289
|
+
return False
|
|
@@ -0,0 +1,45 @@
|
|
|
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
|
+
"""Base interface for MCP notification adapters."""
|
|
16
|
+
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class BaseNotifier(ABC):
|
|
22
|
+
"""Abstract base class for environment notification providers."""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
"""Name of the notifier adapter."""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def is_available(self) -> bool:
|
|
32
|
+
"""Checks if the notifier is configured and available in the current runtime environment."""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def notify(
|
|
37
|
+
self,
|
|
38
|
+
conversation_id: str,
|
|
39
|
+
message: str,
|
|
40
|
+
title: str | None = None,
|
|
41
|
+
event_type: str = "completed",
|
|
42
|
+
payload: dict[str, Any] | None = None,
|
|
43
|
+
) -> bool:
|
|
44
|
+
"""Sends a notification to the target conversation / client."""
|
|
45
|
+
pass
|
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
"""Composite Notifier coordinating multi-channel notifications."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from mcp_server.notifiers.agentapi import AgentApiNotifier
|
|
21
|
+
from mcp_server.notifiers.base import BaseNotifier
|
|
22
|
+
from mcp_server.notifiers.desktop import DesktopNotifier
|
|
23
|
+
from mcp_server.notifiers.file import FileNotifier
|
|
24
|
+
from mcp_server.notifiers.script import ScriptNotifier
|
|
25
|
+
from mcp_server.notifiers.webhook import WebhookNotifier
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("mcp_server.notifiers.composite")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CompositeNotifier(BaseNotifier):
|
|
31
|
+
"""Aggregates multiple notifiers and broadcasts events across all available channels."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, notifiers: list[BaseNotifier] | None = None):
|
|
34
|
+
if notifiers is not None:
|
|
35
|
+
self._notifiers = notifiers
|
|
36
|
+
else:
|
|
37
|
+
self._notifiers = [
|
|
38
|
+
FileNotifier(),
|
|
39
|
+
AgentApiNotifier(),
|
|
40
|
+
WebhookNotifier(),
|
|
41
|
+
ScriptNotifier(),
|
|
42
|
+
DesktopNotifier(),
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def name(self) -> str:
|
|
47
|
+
return "composite"
|
|
48
|
+
|
|
49
|
+
def is_available(self) -> bool:
|
|
50
|
+
for n in self._notifiers:
|
|
51
|
+
try:
|
|
52
|
+
if n.is_available():
|
|
53
|
+
return True
|
|
54
|
+
except Exception:
|
|
55
|
+
continue
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def register_notifier(self, notifier: BaseNotifier) -> None:
|
|
59
|
+
"""Registers an additional custom notifier."""
|
|
60
|
+
self._notifiers.append(notifier)
|
|
61
|
+
|
|
62
|
+
def notify(
|
|
63
|
+
self,
|
|
64
|
+
conversation_id: str,
|
|
65
|
+
message: str,
|
|
66
|
+
title: str | None = None,
|
|
67
|
+
event_type: str = "completed",
|
|
68
|
+
payload: dict[str, Any] | None = None,
|
|
69
|
+
) -> bool:
|
|
70
|
+
any_success = False
|
|
71
|
+
for notifier in self._notifiers:
|
|
72
|
+
try:
|
|
73
|
+
if not notifier.is_available():
|
|
74
|
+
continue
|
|
75
|
+
success = notifier.notify(
|
|
76
|
+
conversation_id=conversation_id,
|
|
77
|
+
message=message,
|
|
78
|
+
title=title,
|
|
79
|
+
event_type=event_type,
|
|
80
|
+
payload=payload,
|
|
81
|
+
)
|
|
82
|
+
if success:
|
|
83
|
+
any_success = True
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.debug(f"Notifier '{notifier.name}' encountered error: {e}")
|
|
86
|
+
|
|
87
|
+
return any_success
|
|
@@ -0,0 +1,108 @@
|
|
|
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
|
+
"""Desktop Toast / OS notification adapter."""
|
|
16
|
+
|
|
17
|
+
import logging
|
|
18
|
+
import os
|
|
19
|
+
import shutil
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from mcp_server.notifiers.base import BaseNotifier
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger("mcp_server.notifiers.desktop")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class DesktopNotifier(BaseNotifier):
|
|
30
|
+
"""Notifier that displays native desktop notifications across macOS, Linux, and Windows."""
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def name(self) -> str:
|
|
34
|
+
return "desktop"
|
|
35
|
+
|
|
36
|
+
def is_available(self) -> bool:
|
|
37
|
+
try:
|
|
38
|
+
val = os.getenv("ARTEMIS_DESKTOP_NOTIFY", "").lower()
|
|
39
|
+
if val in ("0", "false", "no", "off"):
|
|
40
|
+
return False
|
|
41
|
+
if os.getenv("CI", "").lower() in ("1", "true", "yes") and val not in (
|
|
42
|
+
"1",
|
|
43
|
+
"true",
|
|
44
|
+
"yes",
|
|
45
|
+
):
|
|
46
|
+
return False
|
|
47
|
+
if sys.platform == "linux" and not shutil.which("notify-send"):
|
|
48
|
+
return False
|
|
49
|
+
if sys.platform == "darwin" and not shutil.which("osascript"):
|
|
50
|
+
return False
|
|
51
|
+
return True
|
|
52
|
+
except Exception:
|
|
53
|
+
return False
|
|
54
|
+
|
|
55
|
+
def notify(
|
|
56
|
+
self,
|
|
57
|
+
conversation_id: str,
|
|
58
|
+
message: str,
|
|
59
|
+
title: str | None = None,
|
|
60
|
+
event_type: str = "completed",
|
|
61
|
+
payload: dict[str, Any] | None = None,
|
|
62
|
+
) -> bool:
|
|
63
|
+
if not self.is_available():
|
|
64
|
+
return False
|
|
65
|
+
|
|
66
|
+
header = title or f"☕ Artemis Task {event_type.capitalize()}"
|
|
67
|
+
clean_body = message.split("\n\n")[0][:120]
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
if sys.platform == "linux":
|
|
71
|
+
if shutil.which("notify-send"):
|
|
72
|
+
subprocess.run(
|
|
73
|
+
["notify-send", header, clean_body],
|
|
74
|
+
stdout=subprocess.DEVNULL,
|
|
75
|
+
stderr=subprocess.DEVNULL,
|
|
76
|
+
timeout=3,
|
|
77
|
+
)
|
|
78
|
+
return True
|
|
79
|
+
elif sys.platform == "darwin":
|
|
80
|
+
if shutil.which("osascript"):
|
|
81
|
+
script = f'display notification "{clean_body}" with title "{header}"'
|
|
82
|
+
subprocess.run(
|
|
83
|
+
["osascript", "-e", script],
|
|
84
|
+
stdout=subprocess.DEVNULL,
|
|
85
|
+
stderr=subprocess.DEVNULL,
|
|
86
|
+
timeout=3,
|
|
87
|
+
)
|
|
88
|
+
return True
|
|
89
|
+
elif sys.platform == "win32":
|
|
90
|
+
ps_cmd = (
|
|
91
|
+
f"[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null; "
|
|
92
|
+
f"$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02); "
|
|
93
|
+
f'$textNodes = $template.GetElementsByTagName("text"); '
|
|
94
|
+
f'$textNodes.Item(0).AppendChild($template.CreateTextNode("{header}")) > $null; '
|
|
95
|
+
f'$textNodes.Item(1).AppendChild($template.CreateTextNode("{clean_body}")) > $null; '
|
|
96
|
+
f"$toast = [Windows.UI.Notifications.ToastNotification]::new($template); "
|
|
97
|
+
f'[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Artemis").Show($toast);'
|
|
98
|
+
)
|
|
99
|
+
subprocess.run(
|
|
100
|
+
["powershell", "-NoProfile", "-Command", ps_cmd],
|
|
101
|
+
stdout=subprocess.DEVNULL,
|
|
102
|
+
stderr=subprocess.DEVNULL,
|
|
103
|
+
timeout=5,
|
|
104
|
+
)
|
|
105
|
+
return True
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logger.debug(f"Desktop notification failed: {e}")
|
|
108
|
+
return False
|