内置技能包与技能检索

内置技能怎样用 Bun 宏打进产物并在首次运行时解压,同名覆盖与禁用开关,技能目录、skill_search 的 BM25 检索与中文别名,以及斜杠调用的注入。

作者 David更新于 第 25 篇(共 33 篇)

上游 OpenCode 的 skill 机制很朴素:到 ~/.claude/skills 等目录找 SKILL.md,把清单放进系统提示词,模型再用 skill 工具按名字加载(见 Agent、命令与 Skills)。MiMo Code 在这套机制上加了三样东西:随二进制分发的技能包、按相关度找技能的 skill_search 工具,以及给中文界面准备的斜杠别名。

技能包分两份:packages/opencode/src/skill/builtin/.bundle/ 下 27 个面向用户的内置技能;packages/opencode/src/skill/compose/.bundle/ 下 14 个 compose:* 技能,只供 legacy Compose agent 使用(见 Compose)。各技能的内容本篇不展开。

怎么用

  • TUI 里输入 / 看补全,/skills 打开选择框,/<技能名> 直接调用。界面语言是简体或繁体中文时,内置技能还有中文别名,例如 /深度研究 在提交前被解析成 /deep-research(packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx:1244)。
  • 一条消息里用 /名称 提到多个技能,前 3 个的正文自动注入,并附一段多技能编排提醒。
  • 消息附带 PDF、Word、Excel(含 CSV、TSV)或 PPT 文件时,会插入一段提醒,列出对应 official 技能的 SKILL.md 路径。
  • 模型这一侧:系统提示词尾部有技能目录,skill_search 按描述检索,skill 按名加载。GPT/Codex 工具集下这两个工具不在顶层,要在 exec 里以 tools.skill(...) 形式调用(见 exec 与 QuickJS 沙箱)。
  • 覆盖内置技能:在 .mimocode/skills/<name>/SKILL.md、~/.claude/skills/ 等位置放一个同名技能。
  • frontmatter 比上游多两个可选键:aliases(字符串数组,参与检索)和 disable-model-invocation(为真时模型看不见也加载不了,用户仍可 /名称)。v0.1.14 的内置技能都没有用到这两个键。

环境变量(取值 true 或 1):

变量效果
MIMOCODE_DISABLE_BUILTIN_SKILLS不解压、不加载整个内置包,附件触发的文档技能提醒也随之关闭
MIMOCODE_DISABLE_OFFICIAL_SKILLS只去掉 docx-official、pdf-official、pptx-official、xlsx-official、html-to-video-pipeline
MIMOCODE_DISABLE_COMPOSE_SKILLS不加载 compose:* 技能(README 没有列出)
MIMOCODE_DISABLE_SLASH_SKILLS只从 / 补全里隐藏技能,模型照常可用
MIMOCODE_DISABLE_EXTERNAL_SKILLS不扫描 .claude、.agents、.codex、.opencode 下的技能

最后一项还能逐个关:MIMOCODE_DISABLE_CLAUDE_CODE_SKILLS、MIMOCODE_DISABLE_AGENTS_SKILLS、MIMOCODE_DISABLE_CODEX_SKILLS、MIMOCODE_DISABLE_OPENCODE_SKILLS;MIMOCODE_MIMO_ONLY 与 MIMOCODE_DISABLE_CLAUDE_CODE 会连带关掉 .claude 下的技能(packages/opencode/src/flag/flag.ts:50)。

图表加载中…

打包:Bun 宏把目录编进产物

extract.ts 用两种方式导入同一个函数(packages/opencode/src/skill/builtin/extract.ts:9):

import { loadBuiltinBundle } from "./bundle.macro" with { type: "macro" }
import { loadBuiltinBundle as loadBuiltinBundleDev } from "./bundle.macro"

带 with { type: "macro" } 的是 Bun 宏:构建时执行 loadBuiltinBundle()(packages/opencode/src/skill/builtin/bundle.macro.ts:19),递归读出 .bundle 下每个技能目录的全部文件(跳过 .DS_Store、Thumbs.db、__pycache__ 与 .pyc),把“技能名、相对路径、文件内容”组成的对象作为字面量内联进产物。compose 包的同名文件解释了为什么还要第二个导入:

Bun macros only resolve in the static import graph of an entry point. In dynamic import() chains (e.g. plugin tests), the macro is unavailable — fall back to a normal runtime import of the same function.

(packages/opencode/src/skill/compose/extract.ts:10)

所以 safeLoadBuiltinBundle 捕获 ReferenceError 后改走普通调用(builtin/extract.ts:91)。文件一律按 UTF-8 文本读取(builtin/bundle.macro.ts:14),包里唯一的二进制文件 playwright/assets/playwright.png 经过一读一写,字节不会与原图一致;它只被该技能的 agents/openai.yaml 当图标引用。

首次运行:解压到数据目录

extractBuiltinBundle(builtin/extract.ts:105)在 Skill 服务第一次发现技能时运行:

  • 目标是 <data>/builtin_skills/<版本号>/skills/<技能名>(builtin/extract.ts:71)。<data> 默认为 XDG 数据目录下的 mimocode(Linux 上是 ~/.local/share/mimocode),设置 MIMOCODE_HOME 后为 $MIMOCODE_HOME/data(packages/shared/src/global.ts:26)。compose 包解到 <data>/compose/<版本号>/skills(compose/extract.ts:32)。
  • 标记文件 .extracted 存的是版本号加本次启用技能名列表的 JSON,内容完全一致才跳过(builtin/extract.ts:118)。因此切换 MIMOCODE_DISABLE_OFFICIAL_SKILLS、新装了 claude 命令,都会触发重新解压;compose 包只检查标记文件是否存在。开发构建(渠道为 local)每次启动都重写。
  • claude-code、codex 两个技能要求 PATH 上分别有 claude、codex(builtin/extract.ts:20),否则不解压,已有目录会被删除;被禁用的 official 技能同样删目录,index.ts 扫描之后还会按名字再滤一遍(packages/opencode/src/skill/index.ts:190)。
  • 版本号是路径的一部分,升级后旧版本目录留在磁盘上,代码里没有找到清理逻辑。

README 的数据目录表把“技能”写在 cache 目录下(README.zh.md:387),而内置技能实际在 data 目录;cache 下的 skills/ 放的是配置 skills.urls 拉取的远程技能(packages/opencode/src/skill/discovery.ts:35)。

README 的内置技能表列了 23 个(README.zh.md:217),.bundle 里实际有 27 个,下面 4 个没写进 README:

技能frontmatter 描述大意
grok-buildGrok Build CLI(grok)的参考与工作流,只在用户明确点名时使用
mate为 MiMo Desktop 的桌面宠物 Mate 生成精灵图与 manifest
memory-search内置 memory、history 工具不够用时,直接查询轨迹 SQLite 数据库(见历史轨迹)
playwright用 playwright-cli 在终端驱动真实浏览器

compose 包的 14 个目录与 README 说的“十四个内置技能”一致(README.zh.md:192)。

发现顺序与同名覆盖

发现分两段。与项目无关的部分由 discoverStableSkills(index.ts:176)完成,用 Effect.cachedInvalidateWithTTL 以无限期 TTL 缓存(index.ts:317);与目录相关的部分由 discoverSkills(index.ts:239)按实例计算:

顺序来源模式
1解压后的内置包skills/*/SKILL.md
2解压后的 compose 包**/SKILL.md
3~/.claude、~/.agents、~/.codex、~/.opencodeskills/**/SKILL.md
4从工作目录逐级向上直到工作树根的同名目录skills/**/SKILL.md
5各配置目录(全局配置目录、各级 .mimocode、~/.mimocode){skill,skills}/**/SKILL.md
6配置 skills.paths**/SKILL.md
7配置 skills.urls下载到 cache 后 **/SKILL.md

同名时的处理(packages/opencode/src/skill/index.ts:117):

  const isBundled = bundledRoots.some((root) => match.startsWith(root))
  const existing = state.skills[parsed.data.name]

  if (existing) {
    // User overrides always win: bundled must not overwrite non-bundled
    if (isBundled && !existing.bundled) return
    if (!isBundled && existing.bundled) {
      log.info("user skill overrides bundled", { name: parsed.data.name, location: match })
    } else {
      log.warn("duplicate skill name", {
        name: parsed.data.name,
        existing: existing.location,
        duplicate: match,
      })
    }
  }

包内技能永远写不过用户技能;两边同为用户技能(或同为包内技能)时,后加载的覆盖先加载的,只记一条警告。为了让“后加载”有确定含义,每个根目录的匹配结果先排序(index.ts:170),加载改为串行(index.ts:300)。v1.14.19 既不排序,又以 "unbounded" 并发解析,同名时谁赢取决于谁先解析完。

与上游相比还有几处不同:

  • 外部目录多了 .codex、.opencode(index.ts:23),Info 多了 aliases、disable_model_invocation、bundled 三个字段(index.ts:29)。
  • 远程技能沿用 v1.14.19 的下载逻辑:目标文件已存在就不再下载(discovery.ts:38),不会像 OpenCode 专栏所写的新版那样按 version 更新。
  • write、edit 工具改动了 .mimocode/skills/ 或 .mimocode/tools/ 下的文件后,会调用 ToolRegistry.reload,进而让两段发现缓存一起失效(packages/opencode/src/session/prompt.ts:1696、packages/opencode/src/tool/registry.ts:517)。判断用的正则只认正斜杠(prompt.ts:395)。

三个集合:谁看得见、谁调得动

Skill 服务给出三种列表(index.ts:339 起):

方法过滤条件用在哪里
all()不过滤注册为斜杠命令(packages/opencode/src/command/index.ts:264)、GET /skill
available(agent)去掉 skill 权限求值为 deny 的用户 /名称 注入
modelInvocable(agent)再去掉 disable_model_invocation 为真的技能目录、skill_search、skill 工具的报错提示

默认规则里 skill 是 "*": "allow" 加 "compose:*": "deny"(packages/opencode/src/agent/agent.ts:112),只有 compose agent 放行 compose:*(agent.ts:221)。deny 意味着谁都用不了:在 build agent 里输入 /compose:plan 不会注入任何内容。

技能目录放在 system 尾部

SystemPrompt.skills(packages/opencode/src/session/system.ts:177)在 agent 没有整体禁用 skill 时,输出一行 Skills available in this session:,后接 Skill.fmt 生成的 <available_skills> 详单,列表取自 modelInvocable。与 v1.14.19 比有两处变化:

  • 上游还把一份简版清单拼进 skill 工具描述(registry 里的 describeSkill);MiMo 删掉了这段,skill 工具描述固定为 skill.txt,不随技能集合变化。
  • 技能目录曾以 synthetic 文本部件写进 user 消息,标记常量还留在 packages/opencode/src/session/skill-catalog.ts:1。转换历史时,这类旧部件会被跳过(packages/opencode/src/session/message-v2.ts:856):

Skill catalogs moved to the system tail. Suppress snapshots persisted by older versions so resumed sessions do not receive a duplicate user-side catalog.

同一会话在模型、agent、权限等不变时,system 内容随请求前缀快照冻结(prompt.ts:4441),工具集哈希变化才重算(prompt.ts:4505),所以会话中途新增的技能不一定马上出现在目录里;skill_search 和 /名称 读的则是实时列表。前缀快照见压缩、修剪与请求前缀。

skill_search:先精确匹配,再 BM25

skill_search(packages/opencode/src/tool/skill-search.ts:19)只有一个参数 query,参数说明要求模型把请求改写成动作、输入、期望产出、受众四个要素。执行时对 modelInvocable 的结果调用 searchSkills(packages/opencode/src/skill/search.ts:68):

  1. 排除 compose:名字以 compose: 开头的不参与。
  2. 精确提及:查询里出现技能名、aliases 或中文别名即命中,得分为 1。含汉字的别名做子串匹配,其余要求前后都不是字母或数字(search.ts:20)。
  3. 分词:转小写,去掉停用词,其中包括 action、input、output、audience 这四个模板标签;连续汉字切成相邻二字组,单字保留;长度大于 3、以 s 结尾但不以 ss 结尾的词去掉末尾的 s(search.ts:49)。
  4. BM25 与混合打分:文档是名字、别名、中文别名与描述拼起来的文本。每个技能的 BM25 分除以本次最高分后乘 0.55,加上查询词覆盖率乘 0.35,保留 4 位小数。精确命中排在最前,去重后截取前 3 个。

这些参数写死在 Flag 对象里,并不读环境变量(flag.ts:222):

  // Skill-search ranking and loading policy. Exact mentions stay above BM25;
  // the BM25/coverage blend has a 0.90 ceiling, and near-max results auto-load.
  MIMOCODE_SKILL_SEARCH_EXACT_SCORE: 1,
  MIMOCODE_SKILL_SEARCH_BM25_K1: 1.5,
  MIMOCODE_SKILL_SEARCH_BM25_LENGTH_NORMALIZATION: 0.75,
  MIMOCODE_SKILL_SEARCH_BM25_IDF_SMOOTHING: 0.5,
  MIMOCODE_SKILL_SEARCH_BM25_SCORE_WEIGHT: 0.55,
  MIMOCODE_SKILL_SEARCH_QUERY_COVERAGE_WEIGHT: 0.35,
  MIMOCODE_SKILL_SEARCH_AUTO_LOAD_THRESHOLD: 0.85,
  MIMOCODE_SKILL_SEARCH_SCORE_PRECISION: 4,
  MIMOCODE_SKILL_SEARCH_MAX_RESULTS: 3,
  MIMOCODE_SKILL_SEARCH_STEM_MIN_LENGTH: 3,
  MIMOCODE_SKILL_SEARCH_FILE_SAMPLE_LIMIT: 10,

第一名得分不低于 0.85 时自动加载(skill-search.ts:45):照常询问 skill 权限,再把正文接在 JSON 结果后面返回;否则只返回候选列表,由模型决定。按混合公式,BM25 结果要过线,得是本次最高分且覆盖约 86% 以上的查询词,或者覆盖全部查询词且分数达到最高分的约 91%。

中文别名来自 TUI 简繁中文词条里的 tui.skill.<名字>.slash,多个别名以 | 分隔,只对内置的非 compose 技能生效(packages/opencode/src/skill/localized-alias.ts:7)。于是查询里出现 Word文档 就能精确命中 docx-official。有一处对不上:mimocode-docs 的词条键写成了 tui.skill.mimocode.slash(packages/opencode/src/cli/cmd/tui/i18n/zh.ts:240),按技能名查不到,它的中文别名与中文描述都不会生效。

加载:三条注入路径

  • skill 工具(packages/opencode/src/tool/skill.ts:15):名字查不到时,如果它其实是内置 workflow,就提示改用 workflow 工具;否则只列出 modelInvocable 里的名字,避免把隐藏技能泄露给模型。技能设了 disable-model-invocation 就拒绝,并让模型转告用户输入 /名称。通过后询问 skill 权限,由 renderSkillContent(packages/opencode/src/tool/skill-content.ts:9)输出 <skill_content> 块:正文、技能目录的 file:// 地址、最多 10 个用 ripgrep 取样的附属文件路径。这段输出与上游 skill.ts 相同,只是抽成函数给 skill_search 复用。
  • 用户提及(prompt.ts:1243):去掉代码块和行内代码后用正则找 /名称,只认 available 里的技能;前 3 个(MAX_AUTOLOAD)把原始正文包进 <system-reminder> 写进本条 user 消息(不带文件清单),其余只提示通过工具加载。提到 2 个及以上时再追加编排提醒,要求先读完每个 SKILL.md,判断是流水线、并行还是约束叠加,并声明冲突时哪个技能优先。TUI 里选中技能命令,服务端只写入 /名称 参数 这段可见文本(prompt.ts:5202),正文同样由这里注入。
  • 附件触发(prompt.ts:1217):按 MIME 或扩展名匹配四个 official 文档技能(builtin/extract.ts:30),插入提醒列出各自 SKILL.md 的绝对路径。这张表不经过 available 或 modelInvocable。

下一篇:命令行式工具调用与 Token 精简——用受限的命令行语法代替 JSON 调工具,以及 bash 输出怎样被压缩。

本页目录