diff --git a/crates/aionui-ai-agent/src/registry.rs b/crates/aionui-ai-agent/src/registry.rs
index 1d3d70d22..16d5fe07f 100644
--- a/crates/aionui-ai-agent/src/registry.rs
+++ b/crates/aionui-ai-agent/src/registry.rs
@@ -1644,7 +1644,7 @@ mod tests {
// when none of the CLIs are installed on the test host.
let reg = registry().await;
let all = reg.list_all_including_hidden().await;
- assert_eq!(all.len(), 43, "seed rows: 42 pre-existing + antigravity");
+ assert_eq!(all.len(), 44, "seed rows: 42 pre-existing + antigravity + minimax-code");
}
#[tokio::test]
@@ -1772,7 +1772,7 @@ mod tests {
.unwrap_or_else(|error| panic!("missing release lock for {backend}: {error}"));
locked += 1;
}
- assert_eq!(locked, 12);
+ assert_eq!(locked, 13);
}
/// On a host that has *none* of the seeded CLIs installed, the
@@ -1807,7 +1807,7 @@ mod tests {
let reg = registry().await;
let all = reg.list_all_including_hidden().await;
let count = |t: AgentType| all.iter().filter(|m| m.agent_type == t).count();
- assert_eq!(count(AgentType::Acp), 39);
+ assert_eq!(count(AgentType::Acp), 40);
assert_eq!(count(AgentType::Nanobot), 1);
assert_eq!(count(AgentType::OpenclawGateway), 1);
assert_eq!(count(AgentType::Aionrs), 1);
@@ -2029,7 +2029,7 @@ mod tests {
async fn diagnostic_snapshot_pairs_rows_with_reasons() {
let reg = registry().await;
let snapshot = reg.diagnostic_snapshot().await;
- assert_eq!(snapshot.len(), 43, "every row appears once");
+ assert_eq!(snapshot.len(), 44, "every row appears once");
for (meta, reason) in &snapshot {
match (meta.available, reason) {
diff --git a/crates/aionui-assets/assets/logos/acp-registry/minimax-code.svg b/crates/aionui-assets/assets/logos/acp-registry/minimax-code.svg
new file mode 100644
index 000000000..8b33c7709
--- /dev/null
+++ b/crates/aionui-assets/assets/logos/acp-registry/minimax-code.svg
@@ -0,0 +1,3 @@
+
diff --git a/crates/aionui-assets/src/service.rs b/crates/aionui-assets/src/service.rs
index afa5363cf..a56ce7b12 100644
--- a/crates/aionui-assets/src/service.rs
+++ b/crates/aionui-assets/src/service.rs
@@ -128,6 +128,7 @@ mod tests {
"grok",
"kilo",
"mimo-code",
+ "minimax-code",
"nova",
"omp",
"sigit",
diff --git a/crates/aionui-db/migrations/044_add_minimax_code_builtin_agent.sql b/crates/aionui-db/migrations/044_add_minimax_code_builtin_agent.sql
new file mode 100644
index 000000000..ff9af5d3c
--- /dev/null
+++ b/crates/aionui-db/migrations/044_add_minimax_code_builtin_agent.sql
@@ -0,0 +1,70 @@
+-- Add MiniMax Code as a builtin ACP agent (Registry-listed, npx distribution).
+--
+-- Identity: the public Registry card is "MiniMax Code" (https://agent.minimax.io);
+-- the npm package is @minimax-ai/code (bin `mcode`); a live ACP `initialize`
+-- reports agentInfo {name:"minimax-code", title:"MiniMax Code"}. `backend` is
+-- taken from the product name plus the agent's own reported name, following the
+-- `mimo-code` precedent (029) for " Code" products. It is not a copy of
+-- the CDN JSON lookup id, which merely coincides.
+--
+-- `binary_name` is `mcode`: the official README installs the product with
+-- `npm install -g @minimax-ai/code`, which places `mcode` on PATH, and
+-- `mcode --version` is documented, so the default PATH probe applies.
+-- `mcode acp` is the vendor's documented ACP server (README "ACP clients");
+-- the Registry declares the same `acp` argument. AionCore launches the
+-- Registry npx distribution; the exact version is pinned in
+-- crates/aionui-runtime/resources/acp-registry-npx-lock.json, never here.
+--
+-- Probe evidence (2026-09-14, @minimax-ai/code@0.2.7, clean temporary HOME):
+-- initialize ok, protocolVersion 1; session/new -> -32000
+-- "Authentication required: Run `mcode login` and try again."
+-- agent_capabilities is the snake_case form of what initialize returned
+-- (contract: migration 003 header). auth_methods stays NULL: initialize
+-- advertised none, and a blob must not be synthesized.
+--
+-- yolo_id stays NULL. The 0.2.7 ACP server exposes exactly two session modes,
+-- `default` and `plan` (source: run-acp-command chunk, availableModes). The
+-- Ask / Auto / Full access permission levels are a `_permission`-category
+-- CONFIG OPTION (`permissionMode` = default | auto | bypassPermissions), not a
+-- session mode, and AionCore resolves yolo_id through `session/set_mode`, so
+-- storing `bypassPermissions` here would send an unsupported mode id.
+--
+-- native_skills_dirs stays NULL. The 0.2.7 source scans only
+-- `$MINIMAX_DATA_DIR/skills` (a user data directory) and the bundled
+-- `assets/skills`; there is no project-relative skills directory to declare.
+--
+-- behavior_policy omits `supports_team`: migration 033 retired the negative
+-- form, and team capability is derived from backend + probed capabilities.
+-- Post-030 seed shape: builtin rows use agent_id = id and user_id NULL.
+INSERT INTO agent_metadata
+ (id, agent_id, icon, name, backend, agent_type, agent_source, agent_source_info,
+ enabled, command, args, env, native_skills_dirs, behavior_policy, yolo_id,
+ agent_capabilities, sort_order, created_at, updated_at)
+VALUES
+ ('ec619063', 'ec619063', '/api/assets/logos/acp-registry/minimax-code.svg', 'MiniMax Code',
+ 'minimax-code', 'acp', 'builtin', '{"binary_name":"mcode","bridge_binary":"npx"}',
+ 1, 'npx', '["-y","@minimax-ai/code","acp"]', '[]',
+ NULL,
+ '{"supports_side_question":false}',
+ NULL,
+ '{"load_session":true,"mcp_capabilities":{"http":true,"sse":true},"prompt_capabilities":{"image":false,"audio":false,"embedded_context":false},"session_capabilities":{"list":{},"fork":{},"resume":{},"close":{}}}',
+ 3340,
+ unixepoch('now','subsec')*1000, unixepoch('now','subsec')*1000)
+ON CONFLICT(id) DO UPDATE SET
+ agent_id = excluded.agent_id,
+ icon = excluded.icon,
+ name = excluded.name,
+ description = NULL,
+ backend = excluded.backend,
+ agent_type = excluded.agent_type,
+ agent_source = excluded.agent_source,
+ agent_source_info = excluded.agent_source_info,
+ enabled = excluded.enabled,
+ command = excluded.command,
+ args = excluded.args,
+ env = excluded.env,
+ native_skills_dirs = excluded.native_skills_dirs,
+ behavior_policy = excluded.behavior_policy,
+ yolo_id = excluded.yolo_id,
+ sort_order = excluded.sort_order,
+ updated_at = unixepoch('now','subsec')*1000;
diff --git a/crates/aionui-db/src/repository/sqlite_agent_metadata.rs b/crates/aionui-db/src/repository/sqlite_agent_metadata.rs
index a1f4715e0..7e3e90fa0 100644
--- a/crates/aionui-db/src/repository/sqlite_agent_metadata.rs
+++ b/crates/aionui-db/src/repository/sqlite_agent_metadata.rs
@@ -863,7 +863,11 @@ mod tests {
let (repo, _db) = setup().await;
let rows = repo.list_all().await.unwrap();
// 39 ACP vendors + 2 non-ACP builtins + 1 internal = 42.
- assert_eq!(rows.len(), 43, "seed rows: 42 pre-existing + antigravity");
+ assert_eq!(
+ rows.len(),
+ 44,
+ "seed rows: 42 pre-existing + antigravity + minimax-code"
+ );
assert!(
rows.iter()
.any(|r| r.name == "Claude Code" && r.agent_source == "builtin")
diff --git a/crates/aionui-db/tests/minimax_code_builtin_agent_migration.rs b/crates/aionui-db/tests/minimax_code_builtin_agent_migration.rs
new file mode 100644
index 000000000..de67c53cb
--- /dev/null
+++ b/crates/aionui-db/tests/minimax_code_builtin_agent_migration.rs
@@ -0,0 +1,153 @@
+//! Migration 044 seeds MiniMax Code as a Registry-listed builtin ACP agent.
+//!
+//! The assertions pin the fields that silently change behaviour if wrong: the
+//! launch argv (the npx bridge pins its exact version from the release lock at
+//! spawn time), the product CLI that availability detection looks for, and the
+//! two handshake columns that a re-seed must never clobber.
+
+use aionui_db::{IAgentMetadataRepository, SqliteAgentMetadataRepository, init_database_memory};
+
+const BACKEND: &str = "minimax-code";
+
+#[tokio::test]
+async fn seeds_minimax_code_as_a_bridged_registry_builtin() {
+ let db = init_database_memory().await.unwrap();
+ let repo = SqliteAgentMetadataRepository::new(db.pool().clone());
+
+ let row = repo
+ .find_builtin_by_backend(BACKEND)
+ .await
+ .unwrap()
+ .expect("minimax-code is seeded by migration 044");
+
+ assert_eq!(row.id, "ec619063");
+ assert_eq!(row.user_id, None, "builtin rows are machine-level, user_id stays NULL");
+ assert_eq!(row.name, "MiniMax Code");
+ assert_eq!(row.agent_type, "acp");
+ assert_eq!(row.agent_source, "builtin");
+ assert!(row.enabled);
+ assert_eq!(
+ row.icon.as_deref(),
+ Some("/api/assets/logos/acp-registry/minimax-code.svg"),
+ "icon is keyed by the local backend, not the Registry lookup id"
+ );
+
+ // The Registry npx distribution is the ACP connection; the release lock
+ // pins its exact version at launch, so the stored argv carries none.
+ assert_eq!(row.command.as_deref(), Some("npx"));
+ assert_eq!(row.args.as_deref(), Some(r#"["-y","@minimax-ai/code","acp"]"#));
+ assert_eq!(row.env.as_deref(), Some("[]"));
+
+ // PATH detection looks for the product CLI the vendor installs (`mcode`),
+ // while the bridge that speaks ACP is npx.
+ let source: serde_json::Value =
+ serde_json::from_str(row.agent_source_info.as_deref().expect("agent_source_info")).unwrap();
+ assert_eq!(source["binary_name"], "mcode");
+ assert_eq!(source["bridge_binary"], "npx");
+ assert!(
+ source.get("registry_json_id").is_none() && source.get("package_name").is_none(),
+ "agent_source_info must not carry Registry identity fields: {source}"
+ );
+}
+
+/// `initialize` was probed at integration time, so `agent_capabilities` is
+/// seeded; `initialize` advertised no auth methods, so `auth_methods` stays NULL
+/// rather than carrying a synthesized blob. Neither column may be listed in the
+/// migration's `ON CONFLICT DO UPDATE` set — a re-seed must not reset what a
+/// live handshake later teaches this install.
+#[tokio::test]
+async fn seeds_probed_capabilities_and_leaves_unprobed_fields_null() {
+ let db = init_database_memory().await.unwrap();
+ let repo = SqliteAgentMetadataRepository::new(db.pool().clone());
+
+ let row = repo
+ .find_builtin_by_backend(BACKEND)
+ .await
+ .unwrap()
+ .expect("minimax-code is seeded");
+
+ let caps: serde_json::Value =
+ serde_json::from_str(row.agent_capabilities.as_deref().expect("agent_capabilities seeded")).unwrap();
+ assert_eq!(caps["load_session"], true);
+ assert_eq!(
+ caps["mcp_capabilities"]["http"], true,
+ "Team must route it to the MCP transport"
+ );
+ assert_eq!(caps["mcp_capabilities"]["sse"], true);
+ assert_eq!(caps["prompt_capabilities"]["image"], false);
+ assert!(caps["session_capabilities"].get("resume").is_some());
+ assert!(
+ row.agent_capabilities.as_deref().unwrap().contains("load_session")
+ && !row.agent_capabilities.as_deref().unwrap().contains("loadSession"),
+ "handshake columns are stored snake_case (migration 003 contract)"
+ );
+
+ assert_eq!(
+ row.auth_methods, None,
+ "initialize advertised no auth methods; nothing is synthesized"
+ );
+ assert_eq!(
+ row.yolo_id, None,
+ "its ACP session modes are default/plan only; bypassPermissions is a config option"
+ );
+ assert_eq!(
+ row.native_skills_dirs, None,
+ "no project-relative skills directory is documented or in source"
+ );
+
+ let policy: serde_json::Value =
+ serde_json::from_str(row.behavior_policy.as_deref().expect("behavior_policy")).unwrap();
+ assert_eq!(policy["supports_side_question"], false);
+ assert!(
+ policy.get("supports_team").is_none(),
+ "no negative team flag (retired by 033)"
+ );
+ assert!(
+ policy.get("team_capable_override").is_none(),
+ "team_capable_override was retired by 033"
+ );
+}
+
+/// The lock manifest is the only place the Registry version lives. The entry
+/// must exist under the local backend key, name the stable package, and pin an
+/// exact semver — a tag such as `latest` would defeat the release lock.
+#[test]
+fn minimax_code_is_pinned_in_the_npx_release_lock() {
+ let lock = include_str!("../../aionui-runtime/resources/acp-registry-npx-lock.json");
+ let parsed: serde_json::Value = serde_json::from_str(lock).unwrap();
+
+ let entry = parsed["agents"]
+ .get(BACKEND)
+ .unwrap_or_else(|| panic!("{BACKEND} must be pinned in the npx release lock"));
+ assert_eq!(entry["package"], "@minimax-ai/code");
+ assert_eq!(
+ entry["registry_json_id"], "minimax-code",
+ "lookup alias lives in the lock, not in metadata"
+ );
+
+ let version = entry["version"].as_str().expect("version is a string");
+ let parts: Vec<&str> = version.split('.').collect();
+ assert!(
+ parts.len() == 3
+ && parts
+ .iter()
+ .all(|p| !p.is_empty() && p.chars().all(|c| c.is_ascii_digit())),
+ "version must be an exact semver, got {version:?}"
+ );
+}
+
+/// Bad path: the product CLI name and the npm scope are not backends. A lookup
+/// by either must miss, so nothing can accidentally seed a second row under an
+/// alias and split the agent's identity.
+#[tokio::test]
+async fn aliases_are_not_registered_as_backends() {
+ let db = init_database_memory().await.unwrap();
+ let repo = SqliteAgentMetadataRepository::new(db.pool().clone());
+
+ for alias in ["mcode", "minimax", "minimax-ai", "@minimax-ai/code"] {
+ assert!(
+ repo.find_builtin_by_backend(alias).await.unwrap().is_none(),
+ "{alias} must not resolve to a builtin row"
+ );
+ }
+}
diff --git a/crates/aionui-db/tests/team_capability_criteria_migration.rs b/crates/aionui-db/tests/team_capability_criteria_migration.rs
index 30fa30d0f..97b1fc558 100644
--- a/crates/aionui-db/tests/team_capability_criteria_migration.rs
+++ b/crates/aionui-db/tests/team_capability_criteria_migration.rs
@@ -111,7 +111,7 @@ async fn probed_registry_agents_carry_seeded_auth_methods() {
assert!(!raw.contains("/home/"), "{backend} auth_methods leaks a host path");
}
- for backend in ["cortex-code", "dimcode", "poolside", "vtcode", "junie"] {
+ for backend in ["cortex-code", "dimcode", "poolside", "vtcode", "junie", "minimax-code"] {
let row = repo
.find_builtin_by_backend(backend)
.await
@@ -137,7 +137,7 @@ async fn probed_registry_agents_carry_seeded_mcp_capabilities() {
// (backend, http, sse) — `None` means the agent advertises no usable
// mcp_capabilities (absent or empty object), which keeps Team on the CLI
// transport for it. Covers EVERY agent added by migrations 025/029/031.
- let cases: [(&str, Option<(bool, bool)>); 20] = [
+ let cases: [(&str, Option<(bool, bool)>); 21] = [
// npx distributions (025 + 029 + 031)
("autohand", Some((true, true))),
("deepagents", Some((false, false))),
@@ -147,6 +147,8 @@ async fn probed_registry_agents_carry_seeded_mcp_capabilities() {
("grok", Some((true, true))),
("kilo", Some((true, true))),
("mimo-code", Some((true, true))),
+ // 044 (Registry npx, probed 2026-09-14 at @minimax-ai/code@0.2.7)
+ ("minimax-code", Some((true, true))),
("nova", Some((true, true))),
("sigit", Some((false, false))),
// direct CLI launch (031 seeded it on npx; 039 moved it off the bridge)
diff --git a/crates/aionui-runtime/resources/acp-registry-npx-lock.json b/crates/aionui-runtime/resources/acp-registry-npx-lock.json
index f68a0f54c..f11653e61 100644
--- a/crates/aionui-runtime/resources/acp-registry-npx-lock.json
+++ b/crates/aionui-runtime/resources/acp-registry-npx-lock.json
@@ -45,6 +45,11 @@
"package": "@mimo-ai/cli",
"version": "0.1.9"
},
+ "minimax-code": {
+ "registry_json_id": "minimax-code",
+ "package": "@minimax-ai/code",
+ "version": "0.2.7"
+ },
"nova": {
"registry_json_id": "nova",
"package": "@compass-ai/nova",
diff --git a/crates/aionui-runtime/src/registry_npx_lock.rs b/crates/aionui-runtime/src/registry_npx_lock.rs
index d3d782978..f0f78be41 100644
--- a/crates/aionui-runtime/src/registry_npx_lock.rs
+++ b/crates/aionui-runtime/src/registry_npx_lock.rs
@@ -130,7 +130,7 @@ mod tests {
#[test]
fn every_lock_entry_has_an_exact_version() {
let lock = registry_npx_lock().unwrap();
- assert_eq!(lock.agents.len(), 12);
+ assert_eq!(lock.agents.len(), 13);
for package in lock.agents.values() {
assert!(semver::Version::parse(&package.version).is_ok());
}