forgexa-cli 1.48.4__tar.gz → 1.49.0__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.
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/PKG-INFO +1 -1
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/__init__.py +1 -1
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/agent_core.py +96 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/daemon.py +91 -19
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/PKG-INFO +1 -1
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/pyproject.toml +1 -1
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/README.md +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/_build_config.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/_local_bind.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/autoupgrade.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/main.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/py.typed +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli/runtime_evidence.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/SOURCES.txt +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/dependency_links.txt +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/entry_points.txt +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/requires.txt +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/forgexa_cli.egg-info/top_level.txt +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/setup.cfg +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_auth_and_runtime_commands.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_autoupgrade.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_check_command.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_expiry_warnings_and_revoke.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_local_bind_commands.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_runtime_credentials.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_session_credentials.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_silent_install.py +0 -0
- {forgexa_cli-1.48.4 → forgexa_cli-1.49.0}/tests/test_upgrade_observability.py +0 -0
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
"""forgexa-cli — Forgexa command-line client."""
|
|
2
|
-
__version__ = "1.
|
|
2
|
+
__version__ = "1.49.0"
|
|
@@ -319,6 +319,21 @@ class AgentDiscovery:
|
|
|
319
319
|
),
|
|
320
320
|
"upgrade_hint": "opencode upgrade",
|
|
321
321
|
},
|
|
322
|
+
"mimo": {
|
|
323
|
+
"commands": ["mimo"],
|
|
324
|
+
"detect": "mimo --version",
|
|
325
|
+
"invoke_modes": ["cli"],
|
|
326
|
+
"env_path_override": "FACTORY_MIMO_PATH",
|
|
327
|
+
"env_model_override": "FACTORY_MIMO_MODEL",
|
|
328
|
+
"compatibility_level": "L2",
|
|
329
|
+
"min_version": (0, 1, 14),
|
|
330
|
+
"min_version_reason": (
|
|
331
|
+
"The Runtime Daemon relies on the verified `mimo run --format json` "
|
|
332
|
+
"protocol and non-interactive permission flag introduced by MiMo Code "
|
|
333
|
+
"v0.1.14."
|
|
334
|
+
),
|
|
335
|
+
"upgrade_hint": "mimo upgrade",
|
|
336
|
+
},
|
|
322
337
|
"gemini": {
|
|
323
338
|
"commands": ["gemini"],
|
|
324
339
|
"detect": "gemini --version",
|
|
@@ -380,6 +395,7 @@ class AgentDiscovery:
|
|
|
380
395
|
extra_dirs += [
|
|
381
396
|
appdata / "npm", # npm -g installs
|
|
382
397
|
home / ".opencode" / "bin",
|
|
398
|
+
home / ".mimocode" / "bin",
|
|
383
399
|
home / ".cargo" / "bin",
|
|
384
400
|
home / ".bun" / "bin",
|
|
385
401
|
home / "scoop" / "shims", # scoop package manager
|
|
@@ -422,6 +438,7 @@ class AgentDiscovery:
|
|
|
422
438
|
extra_dirs += [
|
|
423
439
|
home / ".local" / "bin", # pip / pipx installs, Claude Code
|
|
424
440
|
home / ".opencode" / "bin", # opencode
|
|
441
|
+
home / ".mimocode" / "bin", # MiMo Code
|
|
425
442
|
home / ".cargo" / "bin", # Rust / cargo installs
|
|
426
443
|
home / ".bun" / "bin", # bun
|
|
427
444
|
home / ".volta" / "bin", # volta (node version manager)
|
|
@@ -658,6 +675,14 @@ class AgentDiscovery:
|
|
|
658
675
|
# ── Advisory file locks ──
|
|
659
676
|
|
|
660
677
|
|
|
678
|
+
def mimo_profile_lock_path() -> Path:
|
|
679
|
+
"""Return the machine-local lock shared by all MiMo Code processes."""
|
|
680
|
+
override = os.environ.get("FORGEXA_MIMO_PROFILE_LOCK", "").strip()
|
|
681
|
+
if override:
|
|
682
|
+
return Path(override)
|
|
683
|
+
return Path.home() / ".forgexa" / "locks" / "mimo-profile.lock"
|
|
684
|
+
|
|
685
|
+
|
|
661
686
|
def acquire_file_lock(
|
|
662
687
|
path: str | Path,
|
|
663
688
|
*,
|
|
@@ -1616,6 +1641,77 @@ def build_opencode_command(
|
|
|
1616
1641
|
return cmd
|
|
1617
1642
|
|
|
1618
1643
|
|
|
1644
|
+
def build_mimo_command(
|
|
1645
|
+
binary: str,
|
|
1646
|
+
*,
|
|
1647
|
+
cwd: str | Path,
|
|
1648
|
+
model: str | None = None,
|
|
1649
|
+
prompt_file: str | Path | None = None,
|
|
1650
|
+
session_id: str | None = None,
|
|
1651
|
+
prompt: str | None = None,
|
|
1652
|
+
) -> list[str]:
|
|
1653
|
+
"""MiMo Code CLI non-interactive invocation for the local AgentHost."""
|
|
1654
|
+
cmd = [
|
|
1655
|
+
binary, "run",
|
|
1656
|
+
"--format", "json",
|
|
1657
|
+
"--dangerously-skip-permissions",
|
|
1658
|
+
"--dir", str(cwd),
|
|
1659
|
+
]
|
|
1660
|
+
if session_id:
|
|
1661
|
+
cmd += ["--session", session_id]
|
|
1662
|
+
if model:
|
|
1663
|
+
cmd += ["--model", model]
|
|
1664
|
+
if prompt_file is not None:
|
|
1665
|
+
cmd += [
|
|
1666
|
+
"--file", str(prompt_file),
|
|
1667
|
+
"--", "Read and follow the attached instructions.",
|
|
1668
|
+
]
|
|
1669
|
+
elif prompt is not None:
|
|
1670
|
+
cmd += ["--", prompt]
|
|
1671
|
+
return cmd
|
|
1672
|
+
|
|
1673
|
+
|
|
1674
|
+
def mimo_base_env(env: dict[str, str]) -> dict[str, str]:
|
|
1675
|
+
"""Return the constrained MiMo Code environment used by AgentHost turns."""
|
|
1676
|
+
env = dict(env)
|
|
1677
|
+
env.update({
|
|
1678
|
+
"MIMOCODE_DISABLE_SHARE": "true",
|
|
1679
|
+
"MIMOCODE_ENABLE_ANALYSIS": "false",
|
|
1680
|
+
"MIMOCODE_PURE": "true",
|
|
1681
|
+
"MIMOCODE_DISABLE_DEFAULT_PLUGINS": "true",
|
|
1682
|
+
"MIMOCODE_DISABLE_PROJECT_CONFIG": "true",
|
|
1683
|
+
"MIMOCODE_DISABLE_EXTERNAL_SKILLS": "true",
|
|
1684
|
+
"MIMOCODE_DISABLE_LSP_DOWNLOAD": "true",
|
|
1685
|
+
"MIMOCODE_DISABLE_CLAUDE_CODE_MCP": "true",
|
|
1686
|
+
"MIMOCODE_DISABLE_AUTOUPDATE": "true",
|
|
1687
|
+
"MIMOCODE_MIMO_ONLY": "true",
|
|
1688
|
+
"MIMOCODE_PERMISSION": json.dumps({
|
|
1689
|
+
"*": "deny",
|
|
1690
|
+
"read": "allow",
|
|
1691
|
+
"edit": "allow",
|
|
1692
|
+
"glob": "allow",
|
|
1693
|
+
"grep": "allow",
|
|
1694
|
+
"lsp": "allow",
|
|
1695
|
+
"bash": {
|
|
1696
|
+
"*": "allow",
|
|
1697
|
+
"git commit*": "deny",
|
|
1698
|
+
"git * commit*": "deny",
|
|
1699
|
+
"git push*": "deny",
|
|
1700
|
+
"git * push*": "deny",
|
|
1701
|
+
"git reset --hard*": "deny",
|
|
1702
|
+
"git * reset --hard*": "deny",
|
|
1703
|
+
},
|
|
1704
|
+
"external_directory": "deny",
|
|
1705
|
+
"task": "deny",
|
|
1706
|
+
"skill": "deny",
|
|
1707
|
+
"webfetch": "deny",
|
|
1708
|
+
"websearch": "deny",
|
|
1709
|
+
"codesearch": "deny",
|
|
1710
|
+
}, separators=(",", ":")),
|
|
1711
|
+
})
|
|
1712
|
+
return env
|
|
1713
|
+
|
|
1714
|
+
|
|
1619
1715
|
def opencode_base_env(
|
|
1620
1716
|
env: dict[str, str], *, data_home: str | Path, require_windows_shell: bool = False,
|
|
1621
1717
|
) -> dict[str, str]:
|
|
@@ -1120,7 +1120,7 @@ except (ImportError, ModuleNotFoundError):
|
|
|
1120
1120
|
# DAEMON_VERSION is the protocol/logic version of the daemon code.
|
|
1121
1121
|
# Kept in sync with pyproject.toml version via bump-version.sh.
|
|
1122
1122
|
# CLIENT_TYPE identifies which packaging/distribution this daemon runs in.
|
|
1123
|
-
DAEMON_VERSION = "1.
|
|
1123
|
+
DAEMON_VERSION = "1.49.0"
|
|
1124
1124
|
|
|
1125
1125
|
|
|
1126
1126
|
def _detect_client_type() -> str:
|
|
@@ -1934,7 +1934,7 @@ def _daemon_extract_agent_output(agent_id: str, raw: str) -> str:
|
|
|
1934
1934
|
return _daemon_extract_copilot_output(raw)
|
|
1935
1935
|
if agent_id == "codex":
|
|
1936
1936
|
return _daemon_extract_codex_output(raw)
|
|
1937
|
-
if agent_id
|
|
1937
|
+
if agent_id in ("opencode", "mimo"):
|
|
1938
1938
|
return _daemon_extract_opencode_output(raw)
|
|
1939
1939
|
return raw
|
|
1940
1940
|
|
|
@@ -5640,6 +5640,10 @@ class ProcessManager:
|
|
|
5640
5640
|
result = await self._run_codex(agent, prompt, workspace_path, timeout, task.task_id, on_chunk)
|
|
5641
5641
|
elif agent.agent_id == "opencode":
|
|
5642
5642
|
result = await self._run_opencode(agent, prompt, workspace_path, timeout, task.task_id, on_chunk)
|
|
5643
|
+
elif agent.agent_id == "mimo":
|
|
5644
|
+
result = await self._run_mimo(
|
|
5645
|
+
agent, prompt, workspace_path, timeout, task.task_id, on_chunk,
|
|
5646
|
+
)
|
|
5643
5647
|
elif agent.agent_id == "gemini":
|
|
5644
5648
|
result = await self._run_gemini(agent, prompt, workspace_path, timeout, task.task_id, on_chunk)
|
|
5645
5649
|
elif agent.agent_id == "kimi":
|
|
@@ -6379,6 +6383,53 @@ class ProcessManager:
|
|
|
6379
6383
|
result.metrics.update(parsed_metrics)
|
|
6380
6384
|
return result
|
|
6381
6385
|
|
|
6386
|
+
async def _run_mimo(
|
|
6387
|
+
self, agent: DiscoveredAgent, prompt: str, cwd: Path, timeout: int, task_id: str,
|
|
6388
|
+
on_chunk: Any = None,
|
|
6389
|
+
) -> TaskResult:
|
|
6390
|
+
"""Run MiMo Code with its user-managed profile serialized machine-wide.
|
|
6391
|
+
|
|
6392
|
+
MiMo's native sessions, memory and credentials belong to its own profile.
|
|
6393
|
+
Unlike OpenCode, do not redirect XDG/MIMOCODE_HOME or copy auth files per
|
|
6394
|
+
task: that would break its state model and risk credential handling bugs.
|
|
6395
|
+
The shared lock covers AgentHost and Daemon processes using that profile.
|
|
6396
|
+
"""
|
|
6397
|
+
profile_lock = agent_core.acquire_file_lock(agent_core.mimo_profile_lock_path())
|
|
6398
|
+
if profile_lock is None:
|
|
6399
|
+
return TaskResult(
|
|
6400
|
+
status="failed",
|
|
6401
|
+
exit_code=-1,
|
|
6402
|
+
stdout="",
|
|
6403
|
+
stderr="",
|
|
6404
|
+
error=(
|
|
6405
|
+
"MiMo Code profile is busy with another local turn; retry after it "
|
|
6406
|
+
"finishes"
|
|
6407
|
+
),
|
|
6408
|
+
failure_code="mimo_profile_busy",
|
|
6409
|
+
)
|
|
6410
|
+
|
|
6411
|
+
tmp_root = tempfile.mkdtemp(prefix=f"mimo-{task_id[:8]}-")
|
|
6412
|
+
try:
|
|
6413
|
+
prompt_file = Path(tmp_root) / "instructions.md"
|
|
6414
|
+
prompt_file.write_text(prompt, encoding="utf-8")
|
|
6415
|
+
cmd = agent_core.build_mimo_command(
|
|
6416
|
+
agent.command,
|
|
6417
|
+
cwd=cwd,
|
|
6418
|
+
model=os.environ.get("FACTORY_MIMO_MODEL", "").strip() or None,
|
|
6419
|
+
prompt_file=prompt_file,
|
|
6420
|
+
)
|
|
6421
|
+
env = agent_core.mimo_base_env(os.environ.copy())
|
|
6422
|
+
result = await self._run_cli(
|
|
6423
|
+
cmd, cwd, timeout, task_id, on_chunk=on_chunk, env=env,
|
|
6424
|
+
)
|
|
6425
|
+
finally:
|
|
6426
|
+
agent_core.release_file_lock(profile_lock)
|
|
6427
|
+
shutil.rmtree(tmp_root, ignore_errors=True)
|
|
6428
|
+
|
|
6429
|
+
parsed_metrics = self._parse_agent_jsonl_output(result.stdout)
|
|
6430
|
+
result.metrics.update(parsed_metrics)
|
|
6431
|
+
return result
|
|
6432
|
+
|
|
6382
6433
|
async def _run_gemini(
|
|
6383
6434
|
self, agent: DiscoveredAgent, prompt: str, cwd: Path, timeout: int, task_id: str,
|
|
6384
6435
|
on_chunk: Any = None,
|
|
@@ -7830,7 +7881,7 @@ class HeartbeatService:
|
|
|
7830
7881
|
|
|
7831
7882
|
def update(self, active_tasks: int, agents: list[dict]):
|
|
7832
7883
|
self._active_tasks = active_tasks
|
|
7833
|
-
self._agents = agents
|
|
7884
|
+
self._agents = [dict(agent) for agent in agents]
|
|
7834
7885
|
|
|
7835
7886
|
async def _sync_local_workspace_bindings(self) -> None:
|
|
7836
7887
|
try:
|
|
@@ -8143,6 +8194,8 @@ class ServerConnection:
|
|
|
8143
8194
|
self.poller: TaskPoller | None = None
|
|
8144
8195
|
self.reporter: ProgressReporter | None = None
|
|
8145
8196
|
self.log_uploader: LogUploader | None = None
|
|
8197
|
+
self._agents: list[DiscoveredAgent] = []
|
|
8198
|
+
self._max_concurrent = 5
|
|
8146
8199
|
self._auth_failures = 0 # Consecutive auth failure count
|
|
8147
8200
|
self._max_auth_failures = 3 # Trigger re-registration after this many
|
|
8148
8201
|
# Set when the server demands a runtime credential (compat window
|
|
@@ -8568,7 +8621,19 @@ class ServerConnection:
|
|
|
8568
8621
|
self._re_register_lock = asyncio.Lock()
|
|
8569
8622
|
return self._re_register_lock
|
|
8570
8623
|
|
|
8571
|
-
|
|
8624
|
+
def update_agent_inventory(
|
|
8625
|
+
self, active_tasks: int, agents: list[DiscoveredAgent],
|
|
8626
|
+
) -> None:
|
|
8627
|
+
"""Keep heartbeat and re-registration on one current agent snapshot."""
|
|
8628
|
+
self._agents = list(agents)
|
|
8629
|
+
if self.heartbeat:
|
|
8630
|
+
self.heartbeat.update(active_tasks, _agent_dicts_for_wire(self._agents))
|
|
8631
|
+
|
|
8632
|
+
async def re_register(
|
|
8633
|
+
self,
|
|
8634
|
+
agents: list[DiscoveredAgent] | None = None,
|
|
8635
|
+
max_concurrent: int | None = None,
|
|
8636
|
+
):
|
|
8572
8637
|
"""Refresh token and re-register with the server.
|
|
8573
8638
|
|
|
8574
8639
|
Called when persistent auth failures indicate stale token/hash.
|
|
@@ -8578,6 +8643,10 @@ class ServerConnection:
|
|
|
8578
8643
|
Single-flight: concurrent triggers (heartbeat/poller/poll_ai_jobs all
|
|
8579
8644
|
share the auth-failure callback) collapse into one execution.
|
|
8580
8645
|
"""
|
|
8646
|
+
if agents is not None:
|
|
8647
|
+
self._agents = list(agents)
|
|
8648
|
+
if max_concurrent is not None:
|
|
8649
|
+
self._max_concurrent = max_concurrent
|
|
8581
8650
|
lock = self._get_re_register_lock()
|
|
8582
8651
|
if lock.locked():
|
|
8583
8652
|
logger.debug(
|
|
@@ -8588,7 +8657,7 @@ class ServerConnection:
|
|
|
8588
8657
|
async with lock:
|
|
8589
8658
|
await self.refresh_access_token()
|
|
8590
8659
|
try:
|
|
8591
|
-
await self.register(
|
|
8660
|
+
await self.register(self._agents, self._max_concurrent)
|
|
8592
8661
|
# Sync runtime_id to all services (may change after re-registration)
|
|
8593
8662
|
if self.heartbeat and self.runtime_id:
|
|
8594
8663
|
self.heartbeat.runtime_id = self.runtime_id
|
|
@@ -8610,9 +8679,10 @@ class ServerConnection:
|
|
|
8610
8679
|
|
|
8611
8680
|
async def register(self, agents: list[DiscoveredAgent], max_concurrent: int):
|
|
8612
8681
|
"""Register this daemon with the server."""
|
|
8613
|
-
self._agents = agents
|
|
8682
|
+
self._agents = list(agents)
|
|
8683
|
+
self._max_concurrent = max_concurrent
|
|
8614
8684
|
self._max_concurrent_cache = max_concurrent
|
|
8615
|
-
agent_dicts = _agent_dicts_for_wire(
|
|
8685
|
+
agent_dicts = _agent_dicts_for_wire(self._agents)
|
|
8616
8686
|
async def _register_once():
|
|
8617
8687
|
payload = {
|
|
8618
8688
|
"daemon_id": self.daemon_id,
|
|
@@ -8685,7 +8755,7 @@ class ServerConnection:
|
|
|
8685
8755
|
preflight_snapshot_provider: Callable[[], dict | None] | None = None,
|
|
8686
8756
|
):
|
|
8687
8757
|
"""Initialize heartbeat, poller, and reporter services."""
|
|
8688
|
-
self._agents = agents
|
|
8758
|
+
self._agents = list(agents)
|
|
8689
8759
|
self._max_concurrent = getattr(self, '_max_concurrent_cache', 5)
|
|
8690
8760
|
|
|
8691
8761
|
def _on_auth_success():
|
|
@@ -8708,7 +8778,7 @@ class ServerConnection:
|
|
|
8708
8778
|
try:
|
|
8709
8779
|
loop = asyncio.get_event_loop()
|
|
8710
8780
|
if loop.is_running():
|
|
8711
|
-
loop.create_task(self.re_register(
|
|
8781
|
+
loop.create_task(self.re_register())
|
|
8712
8782
|
except Exception:
|
|
8713
8783
|
pass
|
|
8714
8784
|
|
|
@@ -8719,7 +8789,7 @@ class ServerConnection:
|
|
|
8719
8789
|
)
|
|
8720
8790
|
self.heartbeat._on_success = _on_heartbeat_success
|
|
8721
8791
|
self.heartbeat._on_auth_failure = _on_auth_failure
|
|
8722
|
-
self.
|
|
8792
|
+
self.update_agent_inventory(0, self._agents)
|
|
8723
8793
|
self.poller = TaskPoller(
|
|
8724
8794
|
self.client, self.server_url, self.runtime_id, poll_interval,
|
|
8725
8795
|
)
|
|
@@ -9354,10 +9424,8 @@ class RuntimeDaemon:
|
|
|
9354
9424
|
logger.info("Refreshed agent inventory: %s -> %s", previous_agents, current_agents)
|
|
9355
9425
|
|
|
9356
9426
|
active_tasks = len(self.active_tasks)
|
|
9357
|
-
agent_dicts = self._agents_as_dicts()
|
|
9358
9427
|
for connection in self.connections:
|
|
9359
|
-
|
|
9360
|
-
connection.heartbeat.update(active_tasks, agent_dicts)
|
|
9428
|
+
connection.update_agent_inventory(active_tasks, refreshed_agents)
|
|
9361
9429
|
|
|
9362
9430
|
async def _agent_refresh_loop(self) -> None:
|
|
9363
9431
|
while not self._shutdown:
|
|
@@ -10238,8 +10306,7 @@ class RuntimeDaemon:
|
|
|
10238
10306
|
# Update heartbeat on all connections with total active count
|
|
10239
10307
|
total_active = len(self.active_tasks)
|
|
10240
10308
|
for conn in self.connections:
|
|
10241
|
-
|
|
10242
|
-
conn.heartbeat.update(total_active, self._agents_as_dicts())
|
|
10309
|
+
conn.update_agent_inventory(total_active, self.agents)
|
|
10243
10310
|
|
|
10244
10311
|
# Poll from all connected servers
|
|
10245
10312
|
for conn in self.connections:
|
|
@@ -10637,6 +10704,7 @@ class RuntimeDaemon:
|
|
|
10637
10704
|
"claude": "npm install -g @anthropic-ai/claude-code",
|
|
10638
10705
|
"codex": "npm install -g @openai/codex",
|
|
10639
10706
|
"opencode": "curl -sSL https://opencode.ai/install | sh",
|
|
10707
|
+
"mimo": "curl -fsSL https://mimo.xiaomi.com/install | bash",
|
|
10640
10708
|
"gemini": "npm install -g @google/gemini-cli",
|
|
10641
10709
|
"kimi": "curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash",
|
|
10642
10710
|
"copilot": (
|
|
@@ -11683,9 +11751,11 @@ class RuntimeDaemon:
|
|
|
11683
11751
|
for agent in self.agents:
|
|
11684
11752
|
if agent.agent_id == preferred_id:
|
|
11685
11753
|
return agent
|
|
11686
|
-
# 3. Any remaining agent
|
|
11754
|
+
# 3. Any remaining agent. MiMo Code requires explicit selection or an
|
|
11755
|
+
# explicit policy fallback entry; it must never become an implicit
|
|
11756
|
+
# catch-all because its profile state is shared machine-wide.
|
|
11687
11757
|
for agent in self.agents:
|
|
11688
|
-
if agent.agent_id not in tried:
|
|
11758
|
+
if agent.agent_id not in tried and agent.agent_id != "mimo":
|
|
11689
11759
|
return agent
|
|
11690
11760
|
|
|
11691
11761
|
return None
|
|
@@ -11728,8 +11798,9 @@ class RuntimeDaemon:
|
|
|
11728
11798
|
for agent in self.agents:
|
|
11729
11799
|
if agent.compatibility_level == "L3":
|
|
11730
11800
|
return agent
|
|
11731
|
-
# 5. Any agent (last resort for unrecognized types)
|
|
11732
|
-
|
|
11801
|
+
# 5. Any agent (last resort for unrecognized types), excluding the
|
|
11802
|
+
# explicit-only MiMo Code driver.
|
|
11803
|
+
return next((agent for agent in self.agents if agent.agent_id != "mimo"), None)
|
|
11733
11804
|
|
|
11734
11805
|
# ── Layer 2: Validation Gate ──
|
|
11735
11806
|
|
|
@@ -13043,6 +13114,7 @@ class RuntimeDaemon:
|
|
|
13043
13114
|
"claude": "npm install -g @anthropic-ai/claude-code",
|
|
13044
13115
|
"codex": "npm install -g @openai/codex",
|
|
13045
13116
|
"opencode": "curl -sSL https://opencode.ai/install | sh",
|
|
13117
|
+
"mimo": "curl -fsSL https://mimo.xiaomi.com/install | bash",
|
|
13046
13118
|
"gemini": "npm install -g @google/gemini-cli",
|
|
13047
13119
|
"kimi": "curl -fsSL https://code.kimi.com/kimi-code/install.sh | bash",
|
|
13048
13120
|
"copilot": (
|
|
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
|