mirrorneuron-cli 1.2.7__tar.gz → 1.2.8__tar.gz
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.
- {mirrorneuron_cli-1.2.7/mirrorneuron_cli.egg-info → mirrorneuron_cli-1.2.8}/PKG-INFO +1 -1
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8/mirrorneuron_cli.egg-info}/PKG-INFO +1 -1
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/model_cmds.py +17 -1
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/server_cmds.py +77 -10
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_model_cmds.py +34 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_server_cmds.py +33 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/.github/workflows/ci.yml +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/.github/workflows/release.yml +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/.gitignore +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/.python-version +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/AGENTS.md +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/LICENSE +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/README.md +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/RELEASE.md +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/SOURCES.txt +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/dependency_links.txt +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/entry_points.txt +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/requires.txt +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/top_level.txt +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/__init__.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/banner.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/config.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/error_handler.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/__init__.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/artifacts.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/backup_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/blueprint_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/blueprint_models.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/blueprint_observability.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/blueprint_repository.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/blueprint_resources.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/bundles.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/deployment_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/event_relay.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/job_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/resource_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/run_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/run_logs.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/run_manifest.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/runtime_health.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/schedule_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/service_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/skill_dependencies.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/skill_runtime.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/sys_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/ui.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/workflow_progress.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/libs/workflow_validation.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/logging_config.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/main.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/runtime_mode.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/runtime_state.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/schemas/workflow_manifest.schema.json +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/sdk_path.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/shared.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/terminal.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/update_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/pyproject.toml +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/scripts/check-release-artifacts.sh +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/scripts/make-release-zip.sh +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/scripts/validate-version-tag.sh +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/setup.cfg +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/conftest.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_backup_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_blueprint_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_blueprint_repository.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_blueprint_resources.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_deployment_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_docker_network_integration.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_job_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_main.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_repo_hygiene.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_resource_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_run_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_run_helpers.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_runtime_health.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_runtime_mode.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_runtime_state.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_schedule_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_service_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_shared.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_sys_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_terminal.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_ui.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_update_cmds.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/tests/test_workflow_validation.py +0 -0
- {mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/uv.lock +0 -0
|
@@ -250,7 +250,7 @@ def install_model_entry(
|
|
|
250
250
|
target = docker_model_name(entry)
|
|
251
251
|
if _docker_model_cli_available():
|
|
252
252
|
_ensure_runner(compatibility.backend, compatibility.accelerator)
|
|
253
|
-
|
|
253
|
+
_docker_model_pull(target)
|
|
254
254
|
run_command = ["model", "run", "--detach"]
|
|
255
255
|
resolved_context = context_size or entry.get("context_size")
|
|
256
256
|
if resolved_context and _docker_model_run_supports_context_size():
|
|
@@ -269,6 +269,22 @@ def install_model_entry(
|
|
|
269
269
|
}
|
|
270
270
|
|
|
271
271
|
|
|
272
|
+
def _docker_model_pull(target: str, *, attempts: int = 2) -> None:
|
|
273
|
+
last_error: RuntimeError | None = None
|
|
274
|
+
for attempt in range(1, attempts + 1):
|
|
275
|
+
try:
|
|
276
|
+
_docker(["model", "pull", target], timeout=900, stream=True)
|
|
277
|
+
return
|
|
278
|
+
except RuntimeError as exc:
|
|
279
|
+
if _model_installed(target):
|
|
280
|
+
return
|
|
281
|
+
last_error = exc
|
|
282
|
+
if attempt < attempts:
|
|
283
|
+
console.print("[yellow]Docker model pull failed; retrying once...[/yellow]")
|
|
284
|
+
if last_error is not None:
|
|
285
|
+
raise last_error
|
|
286
|
+
|
|
287
|
+
|
|
272
288
|
def remove_model_ref(model: str, *, force: bool = False) -> None:
|
|
273
289
|
if _docker_model_cli_available():
|
|
274
290
|
command = ["model", "rm"]
|
|
@@ -112,6 +112,10 @@ RUNTIME_MODELS_OVERRIDE_FILE = "docker-compose.models.yml"
|
|
|
112
112
|
DEFAULT_LLM_MODEL_RUNNER_MODEL = "ai/gemma4:E2B"
|
|
113
113
|
DEFAULT_CONTEXT_MODEL_RUNNER_MODEL = "hf.co/homerquan/mn-context-engine-model-v-Q4_K_M"
|
|
114
114
|
DEFAULT_MEMBRANE_REPO = "MirrorNeuronLab/Membrane"
|
|
115
|
+
DEFAULT_MEMBRANE_ENGINE_IMAGE_REPOSITORY = (
|
|
116
|
+
"us-central1-docker.pkg.dev/mirrorneuron-public-packages/"
|
|
117
|
+
"mirrorneuron-runtime/membrane-context-engine"
|
|
118
|
+
)
|
|
115
119
|
CONTEXT_ENGINE_SERVICE = "membrane-context-engine"
|
|
116
120
|
CONTEXT_ENGINE_CONTAINER = "mirror-neuron-context-engine"
|
|
117
121
|
CONTEXT_ENGINE_MODEL_CONTAINER = "mirror-neuron-context-engine-model"
|
|
@@ -2000,14 +2004,23 @@ def ensure_context_engine_runtime(*, force: bool = False) -> dict[str, str]:
|
|
|
2000
2004
|
)
|
|
2001
2005
|
|
|
2002
2006
|
env = _runtime_base_env(True)
|
|
2003
|
-
source_dir = _ensure_context_engine_source(env)
|
|
2004
2007
|
profiles = _compose_profiles_with(env.get("COMPOSE_PROFILES"), "context")
|
|
2005
2008
|
model = str(env.get("MN_CONTEXT_MODEL_RUNNER_MODEL") or DEFAULT_CONTEXT_MODEL_RUNNER_MODEL)
|
|
2009
|
+
engine_image = _context_engine_release_image(env)
|
|
2010
|
+
use_engine_image = _context_engine_image_mode_enabled(env, engine_image)
|
|
2006
2011
|
updates = {
|
|
2007
2012
|
"COMPOSE_PROFILES": profiles,
|
|
2008
|
-
"MEMBRANE_DIR": str(source_dir),
|
|
2009
2013
|
"MN_CONTEXT_MODEL_RUNNER_MODEL": model,
|
|
2010
2014
|
}
|
|
2015
|
+
source_dir: Path | None = None
|
|
2016
|
+
if use_engine_image:
|
|
2017
|
+
updates["ENGINE_IMAGE"] = engine_image
|
|
2018
|
+
updates["MN_MEMBRANE_ENGINE_IMAGE"] = engine_image
|
|
2019
|
+
_remove_env_file_keys(RUNTIME_COMPOSE_ENV, {"MEMBRANE_DIR"})
|
|
2020
|
+
env.pop("MEMBRANE_DIR", None)
|
|
2021
|
+
else:
|
|
2022
|
+
source_dir = _ensure_context_engine_source(env)
|
|
2023
|
+
updates["MEMBRANE_DIR"] = str(source_dir)
|
|
2011
2024
|
_write_env_file_values(RUNTIME_COMPOSE_ENV, updates)
|
|
2012
2025
|
env.update(updates)
|
|
2013
2026
|
|
|
@@ -2017,14 +2030,24 @@ def ensure_context_engine_runtime(*, force: bool = False) -> dict[str, str]:
|
|
|
2017
2030
|
|
|
2018
2031
|
already_running = _docker_container_running(CONTEXT_ENGINE_CONTAINER)
|
|
2019
2032
|
if force or not already_running:
|
|
2033
|
+
if use_engine_image:
|
|
2034
|
+
subprocess.run(
|
|
2035
|
+
runtime_compose_cmd("pull", CONTEXT_ENGINE_SERVICE),
|
|
2036
|
+
check=True,
|
|
2037
|
+
stdout=subprocess.DEVNULL,
|
|
2038
|
+
env=env,
|
|
2039
|
+
)
|
|
2040
|
+
up_args = ("up", "-d", "--no-build", CONTEXT_ENGINE_SERVICE)
|
|
2041
|
+
else:
|
|
2042
|
+
subprocess.run(
|
|
2043
|
+
runtime_compose_cmd("build", CONTEXT_ENGINE_SERVICE),
|
|
2044
|
+
check=True,
|
|
2045
|
+
stdout=subprocess.DEVNULL,
|
|
2046
|
+
env=env,
|
|
2047
|
+
)
|
|
2048
|
+
up_args = ("up", "-d", CONTEXT_ENGINE_SERVICE)
|
|
2020
2049
|
subprocess.run(
|
|
2021
|
-
runtime_compose_cmd(
|
|
2022
|
-
check=True,
|
|
2023
|
-
stdout=subprocess.DEVNULL,
|
|
2024
|
-
env=env,
|
|
2025
|
-
)
|
|
2026
|
-
subprocess.run(
|
|
2027
|
-
runtime_compose_cmd("up", "-d", CONTEXT_ENGINE_SERVICE),
|
|
2050
|
+
runtime_compose_cmd(*up_args),
|
|
2028
2051
|
check=True,
|
|
2029
2052
|
stdout=subprocess.DEVNULL,
|
|
2030
2053
|
env=env,
|
|
@@ -2038,8 +2061,8 @@ def ensure_context_engine_runtime(*, force: bool = False) -> dict[str, str]:
|
|
|
2038
2061
|
"service": CONTEXT_ENGINE_SERVICE,
|
|
2039
2062
|
"container": CONTEXT_ENGINE_CONTAINER,
|
|
2040
2063
|
"model": model,
|
|
2041
|
-
"membrane_dir": str(source_dir),
|
|
2042
2064
|
"compose_profiles": profiles,
|
|
2065
|
+
**({"engine_image": engine_image} if use_engine_image else {"membrane_dir": str(source_dir)}),
|
|
2043
2066
|
}
|
|
2044
2067
|
|
|
2045
2068
|
def _compose_profiles_with(value: object, required_profile: str) -> str:
|
|
@@ -2058,6 +2081,50 @@ def _compose_profiles_with(value: object, required_profile: str) -> str:
|
|
|
2058
2081
|
profiles.append(required_profile)
|
|
2059
2082
|
return ",".join(profiles)
|
|
2060
2083
|
|
|
2084
|
+
def _normalized_release_image_tag(value: object) -> str:
|
|
2085
|
+
tag = str(value or "").strip()
|
|
2086
|
+
if not tag:
|
|
2087
|
+
return ""
|
|
2088
|
+
return tag if tag.startswith("v") else f"v{tag}"
|
|
2089
|
+
|
|
2090
|
+
def _context_engine_release_image(env: dict[str, str]) -> str:
|
|
2091
|
+
explicit = str(
|
|
2092
|
+
os.getenv("MN_MEMBRANE_ENGINE_IMAGE")
|
|
2093
|
+
or env.get("MN_MEMBRANE_ENGINE_IMAGE")
|
|
2094
|
+
or os.getenv("MN_CONTEXT_ENGINE_IMAGE")
|
|
2095
|
+
or env.get("MN_CONTEXT_ENGINE_IMAGE")
|
|
2096
|
+
or os.getenv("ENGINE_IMAGE")
|
|
2097
|
+
or env.get("ENGINE_IMAGE")
|
|
2098
|
+
or ""
|
|
2099
|
+
).strip()
|
|
2100
|
+
if explicit:
|
|
2101
|
+
return explicit
|
|
2102
|
+
|
|
2103
|
+
tag = _normalized_release_image_tag(
|
|
2104
|
+
os.getenv("MN_MEMBRANE_ENGINE_IMAGE_TAG")
|
|
2105
|
+
or env.get("MN_MEMBRANE_ENGINE_IMAGE_TAG")
|
|
2106
|
+
or os.getenv("MN_RUNTIME_MODULE_VERSION")
|
|
2107
|
+
or env.get("MN_RUNTIME_MODULE_VERSION")
|
|
2108
|
+
or os.getenv("MN_PACKAGE_VERSION")
|
|
2109
|
+
or env.get("MN_PACKAGE_VERSION")
|
|
2110
|
+
)
|
|
2111
|
+
if not tag:
|
|
2112
|
+
return ""
|
|
2113
|
+
repository = str(
|
|
2114
|
+
os.getenv("MN_MEMBRANE_ENGINE_IMAGE_REPOSITORY")
|
|
2115
|
+
or env.get("MN_MEMBRANE_ENGINE_IMAGE_REPOSITORY")
|
|
2116
|
+
or DEFAULT_MEMBRANE_ENGINE_IMAGE_REPOSITORY
|
|
2117
|
+
).strip().rstrip("/")
|
|
2118
|
+
return f"{repository}:{tag}" if repository else ""
|
|
2119
|
+
|
|
2120
|
+
def _context_engine_image_mode_enabled(env: dict[str, str], image: str) -> bool:
|
|
2121
|
+
mode = str(os.getenv("MN_MEMBRANE_SOURCE_MODE") or env.get("MN_MEMBRANE_SOURCE_MODE") or "").strip().lower()
|
|
2122
|
+
if mode in {"source", "git", "checkout", "local"}:
|
|
2123
|
+
return False
|
|
2124
|
+
if mode in {"image", "docker", "gar", "release"}:
|
|
2125
|
+
return bool(image)
|
|
2126
|
+
return bool(image and image != "mirror-neuron-memory-engine:latest")
|
|
2127
|
+
|
|
2061
2128
|
def _context_engine_git_url(env: dict[str, str]) -> str:
|
|
2062
2129
|
explicit = str(os.getenv("MN_MEMBRANE_GIT_URL") or env.get("MN_MEMBRANE_GIT_URL") or "").strip()
|
|
2063
2130
|
if explicit:
|
|
@@ -128,6 +128,38 @@ def test_model_install_streams_pull_progress(mocker):
|
|
|
128
128
|
assert pull_kwargs["capture_output"] is False
|
|
129
129
|
|
|
130
130
|
|
|
131
|
+
def test_model_install_retries_transient_pull_failure(mocker):
|
|
132
|
+
calls = []
|
|
133
|
+
pull_attempts = 0
|
|
134
|
+
|
|
135
|
+
def fake_run(command, **kwargs):
|
|
136
|
+
nonlocal pull_attempts
|
|
137
|
+
calls.append(command)
|
|
138
|
+
if command[:4] == ["docker", "model", "status", "--json"]:
|
|
139
|
+
return _completed(command, stdout=json.dumps({"running": True, "backends": {"llama.cpp": "Running"}}))
|
|
140
|
+
if command[:4] == ["docker", "model", "run", "--help"]:
|
|
141
|
+
return _completed(command, stdout="Options:\n")
|
|
142
|
+
if command == ["docker", "model", "pull", "ai/gemma4:E2B"]:
|
|
143
|
+
pull_attempts += 1
|
|
144
|
+
if pull_attempts == 1:
|
|
145
|
+
return _completed(command, returncode=1, stderr="writing blob: blob digest mismatch")
|
|
146
|
+
if command == ["docker", "model", "inspect", "ai/gemma4:E2B"]:
|
|
147
|
+
return _completed(command, returncode=1)
|
|
148
|
+
return _completed(command)
|
|
149
|
+
|
|
150
|
+
mocker.patch("subprocess.run", side_effect=fake_run)
|
|
151
|
+
mocker.patch(
|
|
152
|
+
"mn_sdk.model_runtime.detect_host_hardware",
|
|
153
|
+
return_value=HostHardwareProfile("darwin", "arm64", total_memory_gb=16, unified_memory_gb=16, has_apple_silicon=True),
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
result = runner.invoke(app, ["model", "install", "gemma4:e2b"])
|
|
157
|
+
|
|
158
|
+
assert result.exit_code == 0
|
|
159
|
+
assert calls.count(["docker", "model", "pull", "ai/gemma4:E2B"]) == 2
|
|
160
|
+
assert ["docker", "model", "run", "--detach", "ai/gemma4:E2B"] in calls
|
|
161
|
+
|
|
162
|
+
|
|
131
163
|
def test_model_install_persists_manual_ownership_record(mocker):
|
|
132
164
|
def fake_run(command, **kwargs):
|
|
133
165
|
if command[:4] == ["docker", "model", "status", "--json"]:
|
|
@@ -293,6 +325,8 @@ def test_model_install_failure_does_not_record_manual_ownership(mocker, tmp_path
|
|
|
293
325
|
return _completed(command, stdout=json.dumps({"running": True, "backends": {"llama.cpp": "Running"}}))
|
|
294
326
|
if command[:3] == ["docker", "model", "pull"]:
|
|
295
327
|
return _completed(command, returncode=1, stderr="pull failed")
|
|
328
|
+
if command[:3] == ["docker", "model", "inspect"]:
|
|
329
|
+
return _completed(command, returncode=1)
|
|
296
330
|
return _completed(command)
|
|
297
331
|
|
|
298
332
|
mocker.patch("subprocess.run", side_effect=fake_run)
|
|
@@ -588,6 +588,39 @@ def test_ensure_context_engine_runtime_persists_profile_and_starts_compose(mocke
|
|
|
588
588
|
assert run.call_args_list[0].args[0] == runtime_compose_cmd("build", "membrane-context-engine")
|
|
589
589
|
assert run.call_args_list[1].args[0] == runtime_compose_cmd("up", "-d", "membrane-context-engine")
|
|
590
590
|
|
|
591
|
+
def test_ensure_context_engine_runtime_uses_release_image_without_source_clone(mocker):
|
|
592
|
+
server_cmds.RUNTIME_COMPOSE_ENV.parent.mkdir(parents=True, exist_ok=True)
|
|
593
|
+
server_cmds.RUNTIME_COMPOSE_ENV.write_text(
|
|
594
|
+
"COMPOSE_PROJECT_NAME=mirror-neuron\n"
|
|
595
|
+
"COMPOSE_PROFILES=openshell\n"
|
|
596
|
+
"MN_RUNTIME_MODULE_VERSION=1.2.7\n"
|
|
597
|
+
"MEMBRANE_DIR=/private/membrane\n",
|
|
598
|
+
encoding="utf-8",
|
|
599
|
+
)
|
|
600
|
+
server_cmds.RUNTIME_COMPOSE_FILE.write_text("services: {}\n", encoding="utf-8")
|
|
601
|
+
ensure_source = mocker.patch("mn_cli.server_cmds._ensure_context_engine_source")
|
|
602
|
+
mocker.patch("mn_cli.server_cmds._ensure_docker_model_runner")
|
|
603
|
+
mocker.patch("mn_cli.server_cmds._remove_non_mirror_neuron_container")
|
|
604
|
+
mocker.patch("mn_cli.server_cmds._docker_container_running", return_value=False)
|
|
605
|
+
run = mocker.patch("mn_cli.server_cmds.subprocess.run")
|
|
606
|
+
|
|
607
|
+
result = server_cmds.ensure_context_engine_runtime()
|
|
608
|
+
|
|
609
|
+
env = server_cmds._read_env_file(server_cmds.RUNTIME_COMPOSE_ENV)
|
|
610
|
+
expected_image = (
|
|
611
|
+
"us-central1-docker.pkg.dev/mirrorneuron-public-packages/"
|
|
612
|
+
"mirrorneuron-runtime/membrane-context-engine:v1.2.7"
|
|
613
|
+
)
|
|
614
|
+
assert env["COMPOSE_PROFILES"] == "openshell,context"
|
|
615
|
+
assert env["ENGINE_IMAGE"] == expected_image
|
|
616
|
+
assert env["MN_MEMBRANE_ENGINE_IMAGE"] == expected_image
|
|
617
|
+
assert "MEMBRANE_DIR" not in env
|
|
618
|
+
assert result["status"] == "started"
|
|
619
|
+
assert result["engine_image"] == expected_image
|
|
620
|
+
ensure_source.assert_not_called()
|
|
621
|
+
assert run.call_args_list[0].args[0] == runtime_compose_cmd("pull", "membrane-context-engine")
|
|
622
|
+
assert run.call_args_list[1].args[0] == runtime_compose_cmd("up", "-d", "--no-build", "membrane-context-engine")
|
|
623
|
+
|
|
591
624
|
def test_ensure_context_engine_runtime_skips_compose_when_already_running(mocker, tmp_path):
|
|
592
625
|
membrane_dir = tmp_path / "Membrane"
|
|
593
626
|
membrane_dir.mkdir()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mirrorneuron_cli.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mirrorneuron_cli-1.2.7 → mirrorneuron_cli-1.2.8}/mn_cli/schemas/workflow_manifest.schema.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|