Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@

steps:
- name: Check out repository
uses: actions/checkout@v4

Check failure on line 33 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 33 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:33: unpinned action reference: action is not pinned to a hash (required by blanket policy)

- name: Set up Python 3.12
uses: actions/setup-python@v5

Check failure on line 36 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 36 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:36: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
python-version: "3.12"

Expand All @@ -41,7 +41,7 @@
run: python scripts/check_dependency_sources.py

- name: Set up uv
uses: astral-sh/setup-uv@v6

Check failure on line 44 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 44 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:44: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
enable-cache: true
cache-dependency-glob: uv.lock
Expand Down Expand Up @@ -74,6 +74,7 @@
--cov=artemis
--cov=mcp_server
--cov=apps/admin_console
--cov=third_party
--cov-fail-under=60

frontend:
Expand All @@ -86,10 +87,10 @@

steps:
- name: Check out repository
uses: actions/checkout@v4

Check failure on line 90 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 90 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:90: unpinned action reference: action is not pinned to a hash (required by blanket policy)

- name: Set up Node.js
uses: actions/setup-node@v4

Check failure on line 93 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 93 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:93: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
node-version: "22.22.3"
cache: npm
Expand All @@ -111,21 +112,21 @@

steps:
- name: Check out repository
uses: actions/checkout@v4

Check failure on line 115 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 115 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:115: unpinned action reference: action is not pinned to a hash (required by blanket policy)

- name: Set up Python 3.12
uses: actions/setup-python@v5

Check failure on line 118 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 118 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:118: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v6

Check failure on line 123 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 123 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:123: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
enable-cache: true
cache-dependency-glob: uv.lock

- name: Set up Node.js
uses: actions/setup-node@v4

Check failure on line 129 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

zizmor/unpinned-uses

unpinned action reference: action is not pinned to a hash (required by blanket policy)

Check failure on line 129 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:129: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
node-version: "22.22.3"
cache: npm
Expand Down Expand Up @@ -171,7 +172,7 @@
artemis --help

- name: Upload distributions
uses: actions/upload-artifact@v4

Check failure on line 175 in .github/workflows/ci.yml

View workflow job for this annotation

GitHub Actions / zizmor-output

unpinned-uses

ci.yml:175: unpinned action reference: action is not pinned to a hash (required by blanket policy)
with:
name: python-distributions
path: dist/*
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ WORKDIR /build
COPY artemis/ ./artemis/
COPY apps/ ./apps/
COPY mcp_server/ ./mcp_server/
COPY third_party/ ./third_party/
COPY packages/artemis-client/ ./packages/artemis-client/
COPY config/ ./config/
COPY pyproject.toml setup.py README.md LICENSE ./
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -315,4 +315,8 @@ Contributions are warmly welcomed!

This project is licensed under the [Apache License 2.0](LICENSE).

This project includes source code developed by [Minitap, Inc.](https://github.com/minitap-ai/mobile-use).
### Third-party code

Code not owned by Google lives under [`third_party/`](third_party/), one directory per upstream project, each with its own `LICENSE` and `METADATA`.

- [`third_party/mobile_use`](third_party/mobile_use/) – portions of [mobile-use](https://github.com/minitap-ai/mobile-use) by Minitap, Inc., Apache License 2.0.
6 changes: 5 additions & 1 deletion README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,8 @@ ARTEMIS 提供两种运行模式以适应不同的自动化需求:

本项目基于 [Apache License 2.0](LICENSE) 协议开源。

本项目包含由 [Minitap, Inc.](https://github.com/minitap-ai/mobile-use) 开发的源代码。
### 第三方代码

非 Google 所有的代码位于 [`third_party/`](third_party/) 目录下,每个上游项目一个子目录,并各自附带 `LICENSE` 与 `METADATA`。

- [`third_party/mobile_use`](third_party/mobile_use/) – 来自 Minitap, Inc. 的 [mobile-use](https://github.com/minitap-ai/mobile-use) 的部分代码,Apache License 2.0。
12 changes: 4 additions & 8 deletions apps/admin_console/replay_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def _init_device(self, device_id: str = None):
from adbutils import AdbClient

try:
from artemis.clients.ui_automator_client import UIAutomatorClient
from third_party.mobile_use.clients.ui_automator_client import UIAutomatorClient
except ImportError:
raise ImportError(
"Failed to import UIAutomatorClient. Ensure artemis package is installed in path."
Expand Down Expand Up @@ -1766,7 +1766,8 @@ def instantiate_state(

initial_goal = self.load_session_goal(session_id, step_dir, str(self.db_path))

ui_hier, decisions, app_info, dev_date = self.extract_state_prepopulation_data(
# State has no fields for the app info or device date (extra="forbid").
ui_hier, decisions, _app_info, _dev_date = self.extract_state_prepopulation_data(
step_dir, step_data, pre_image_meta
)

Expand All @@ -1776,19 +1777,14 @@ def instantiate_state(
raise ImportError(f"Failed to import State module: {import_err}")

state = State(
messages=[],
initial_goal=initial_goal,
latest_screenshot=str(step_dir / "pre.jpg")
if (step_dir / "pre.jpg").exists()
else str(step_dir / "post.jpg"),
operator_raw_data={"width": self.w, "height": self.h},
current_step_id=step_data["step_id"],
complete_subgoals_by_ids=[],
validator_messages=[],
subagent_calls=step_data.get("subagent_calls", []),
subagent_calls=step_data.get("subagent_calls") or [],
latest_ui_hierarchy=ui_hier,
focused_app_info=app_info,
device_date=dev_date,
structured_decisions=decisions,
)
return state
Expand Down
52 changes: 40 additions & 12 deletions apps/admin_console/routers/system.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ async def get_model_config_and_env():
import os
from artemis.config.paths import get_config_path, get_env_file
from artemis.config import settings
from artemis.utils.file import load_jsonc
from third_party.mobile_use.utils.file import load_jsonc

from artemis.config.settings import is_placeholder_key

Expand Down Expand Up @@ -495,28 +495,56 @@ def mask_key(k: str | None) -> str | None:

@router.get("/server-status")
async def get_server_runtime_status():
"""Retrieve runtime status, PID, port, and uptime of the Artemis server."""
"""Retrieve runtime status, PID, port, and uptime of the Artemis server.

Answers from in-process state. ``server_lifecycle.get_server_status`` is
not used here: it runs ``lsof``/``fuser``, which can take over a second and
is too slow for the ``is_artemis_daemon`` probe.
"""
import os
from artemis.runtime.server_lifecycle import get_server_status
import time

from artemis.runtime.process_probe import pid_is_alive
from artemis.runtime.server_lifecycle import read_server_info

try:
from apps.admin_console.core.state import state
except ImportError:
from admin_console.core.state import state

port = getattr(state, "port", 8000)
status = get_server_status(port=port)
current_pid = os.getpid()
pids = {current_pid}
started_at = None

info = read_server_info()
if info and info.get("port") == port:
saved_pid = info.get("pid")
if isinstance(saved_pid, int) and saved_pid != current_pid and pid_is_alive(saved_pid):
pids.add(saved_pid)
if isinstance(info.get("started_at"), (int, float)):
started_at = float(info["started_at"])
if started_at is None:
try:
import psutil

started_at = psutil.Process(current_pid).create_time()
except Exception: # pylint: disable=broad-exception-caught
# psutil is optional; uptime is best-effort.
started_at = None

uptime_seconds = max(0.0, time.time() - started_at) if started_at is not None else None
# Explicit DTO: the raw metadata file additionally holds the lifecycle
# token, cmdline, and filesystem paths, none of which belong on the wire.
return {
"running": status["running"],
"port": status["port"],
"pids": status["pids"],
"active_pid": status["active_pid"],
"uptime_seconds": status["uptime_seconds"],
"url": status["url"],
"admin_url": status["admin_url"],
"current_pid": os.getpid(),
"running": True,
"port": port,
"pids": sorted(pids),
"active_pid": current_pid,
"uptime_seconds": uptime_seconds,
"url": f"http://localhost:{port}",
"admin_url": f"http://localhost:{port}/admin",
"current_pid": current_pid,
}


Expand Down
4 changes: 4 additions & 0 deletions artemis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
"""☕ ARTEMIS: Autonomous Multimodal Android Agent & Testing Framework."""

from artemis._version import __version__
from artemis import _legacy_imports

# Old import paths of the modules moved to third_party/ (deprecated).
_legacy_imports.install()

from artemis.interfaces.sdk.client import ArtemisClient, ConcurrencyMode
from artemis.interfaces.sdk.task import StreamEvent, StreamEventType, Task
Expand Down
94 changes: 94 additions & 0 deletions artemis/_legacy_imports.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Old import paths of the modules moved to ``third_party/mobile_use``.
An old path resolves to the new module object itself, so isinstance checks,
module-level state and ``patch("old.path.x")`` still work. The first import of
an old path emits a ``DeprecationWarning``.
This is a meta path finder rather than one stub file per path because
``artemis.agents.hopper`` is a package; a stub file would load
``artemis.agents.hopper.hopper`` a second time.
"""

import importlib
import importlib.abc
import importlib.machinery
import sys
from types import ModuleType
import warnings

_NEW_ROOT = "third_party.mobile_use"

# Old module path -> new module path.
LEGACY_MODULES: dict[str, str] = {
"artemis.agents.hopper": f"{_NEW_ROOT}.agents.hopper",
"artemis.agents.hopper.hopper": f"{_NEW_ROOT}.agents.hopper.hopper",
"artemis.clients.ui_automator_client": f"{_NEW_ROOT}.clients.ui_automator_client",
"artemis.config.output": f"{_NEW_ROOT}.config.output",
"artemis.controllers.platform_specific_commands_controller": (
f"{_NEW_ROOT}.controllers.platform_specific_commands_controller"
),
"artemis.sdk.builders.index": f"{_NEW_ROOT}.sdk.builders.index",
"artemis.sdk.builders.task_request_builder": f"{_NEW_ROOT}.sdk.builders.task_request_builder",
"artemis.sdk.types.exceptions": f"{_NEW_ROOT}.sdk.types.exceptions",
"artemis.sdk.types.task": f"{_NEW_ROOT}.sdk.types.task",
"artemis.utils.cli_helpers": f"{_NEW_ROOT}.utils.cli_helpers",
"artemis.utils.decorators": f"{_NEW_ROOT}.utils.decorators",
"artemis.utils.file": f"{_NEW_ROOT}.utils.file",
"artemis.utils.logger": f"{_NEW_ROOT}.utils.logger",
"artemis.utils.media": f"{_NEW_ROOT}.utils.media",
"artemis.utils.shell_utils": f"{_NEW_ROOT}.utils.shell_utils",
}


class _AliasLoader(importlib.abc.Loader):
"""Loader that hands back an already-importable module under another name."""

def __init__(self, target: str) -> None:
self._target = target
self._target_spec: importlib.machinery.ModuleSpec | None = None

def create_module(self, spec: importlib.machinery.ModuleSpec) -> ModuleType:
warnings.warn(
f"'{spec.name}' is deprecated; import '{self._target}' instead.",
DeprecationWarning,
stacklevel=2,
)
module = importlib.import_module(self._target)
self._target_spec = module.__spec__
return module

def exec_module(self, module: ModuleType) -> None:
# Nothing to execute. The import system has overwritten __spec__ with the
# old name; put the real spec back so reload() and pickling still work.
if self._target_spec is not None:
module.__spec__ = self._target_spec


class _LegacyModuleFinder(importlib.abc.MetaPathFinder):
"""Resolve entries of :data:`LEGACY_MODULES` to their new modules."""

def find_spec(self, fullname, path=None, target=None):
new_name = LEGACY_MODULES.get(fullname)
if new_name is None:
return None
return importlib.machinery.ModuleSpec(fullname, _AliasLoader(new_name))


def install() -> None:
"""Register the finder once, ahead of the default path-based finders."""
if not any(isinstance(finder, _LegacyModuleFinder) for finder in sys.meta_path):
sys.meta_path.insert(0, _LegacyModuleFinder())
2 changes: 1 addition & 1 deletion artemis/agents/checker/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@
split_multimodal_result,
tool_result_messages,
)
from artemis.utils.logger import get_logger
from artemis.utils.task_tree import format_actions_clean
from artemis.utils.ocr_api import is_ocr_configured, perform_ocr
from artemis.utils.ocr_xml_fusion import (
Expand All @@ -67,6 +66,7 @@
fuse_ocr_with_xml,
)
from artemis.utils.visualization import format_minimal_list_with_elements
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/diagnoser/diagnoser.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@
)
from artemis.tools.video_tool import get_video_analyzer_tool
from artemis.tools.wait_tool import get_wait_tool
from artemis.utils.logger import get_logger
from artemis.memory.context_policy import build_history_for
from artemis.utils.task_tree import (
get_active_subgoal_hashes,
get_recent_subgoal_hashes,
)
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/explorer/explorer.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
from artemis.data_engine.trace import trace
from artemis.graph.state import State
from artemis.llm.google import is_gemini_model, strip_provider_prefix
from artemis.utils.logger import get_logger
from artemis.utils.ocr_api import is_ocr_configured
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/explorer/native_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
retry_policy_for,
)
from artemis.services.llm import _record_llm_event, _record_llm_retry
from artemis.utils.logger import get_logger
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/explorer/perception_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@
from artemis.agents.object_detector.object_detector import _run_object_detection
from artemis.config import settings
from artemis.data_engine.trace import trace
from artemis.utils.logger import get_logger
from artemis.utils.visualization import draw_dots
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/explorer/run_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
from artemis.config import settings
from artemis.data_engine.storage import StorageManager
from artemis.graph.state import State
from artemis.utils.logger import get_logger
from artemis.utils.ocr_api import is_ocr_configured, perform_ocr
from artemis.utils.ocr_xml_fusion import fuse_ocr_with_xml
from artemis.utils.visualization import draw_dots, format_minimal_list_with_elements
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/explorer/universal_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
from artemis.agents.explorer.tool_declarations import UNIVERSAL_EXPLORER_TOOLS
from artemis.graph.state import State
from artemis.services.llm import get_llm
from artemis.utils.logger import get_logger
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/flash/context_compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@

from artemis.memory.step_memory import StepMemoryService
from artemis.memory.transcript import EPHEMERAL_BLOCKS_KEY
from artemis.utils.logger import get_logger
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/flash/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
COORDINATE_SPACE_NORMALIZED,
parse_swipe_parameters,
)
from artemis.utils.logger import get_logger
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/flash/summarizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@
from artemis.memory.step_memory import JobKey, StepMemoryService
from artemis.services.llm import RobustChatModelWrapper, get_google_llm, get_llm
from artemis.services.token_meter import record_llm_usage
from artemis.utils.logger import get_logger
from artemis.utils.task_tree import format_actions_clean
from artemis.utils.visualization import draw_action_overlay_on_image
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/history_analyzer/history_analyzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@
split_multimodal_result,
tool_result_messages,
)
from artemis.utils.logger import get_logger
from artemis.utils.notes import get_note_file_path
from artemis.utils.task_tree import get_active_subgoal_hashes
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion artemis/agents/image_processor/image_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
from artemis.core.tool_declaration import ToolDeclaration
from artemis.data_engine.trace import TraceSpan, trace
from artemis.services.llm import get_llm
from artemis.utils.logger import get_logger
from artemis.utils.python_executor import PythonExecutor
from third_party.mobile_use.utils.logger import get_logger

logger = get_logger(__name__)

Expand Down
Loading
Loading