npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@mortiseai/stem

v0.0.21

Published

MortiseAI Stem CLI, built on MSC Engine

Readme

@mortiseai/stem

Stem — MortiseAI 构建的 Self-Evolving AI Agent,基于 MSCE(MSC Engine)的自我进化智能体

核心三种业务模式(详见「业务模式」):

  • Agent 模式 — 面向通用智能体形态,默认模式,当前唯一开放。启动:stem / stem --agent;核心能力:业务图谱记忆
  • Coding 模式 — 面向编码工作流的形态(暂未开放)。启动:stem --coding;核心能力:业务图谱记忆,基于 MortiseAI Spec Code Engine 编码
  • FDE 模式 — 面向企业交付场景的形态(暂未开放)。启动:stem --fde;核心能力:业务图谱记忆,私有图谱记忆,基于 MortiseAI Spec Code Engine 编码

快速开始

前置依赖

  • Node.js >= 22(源码开发/构建另需 Bun,见 MSTEM.md)

npm 快速安装和启动(发布后)

npm install -g @mortiseai/stem
stem

macOS / Linux / Windows 均可,仅需 Node >= 22,无需 bun。

退出

在 Welcome 屏按 q · Esc · 或 Ctrl+C


启动方式与命令行参数

启动范式(npm 全局安装)

npm install -g @mortiseai/stem
stem                                    # 默认:agent 模式 · llms.json default 模型 · 系统语言
stem --lang en                          # 英文界面
stem --model BaiLian:glm-5.2            # 会话默认模型(platform:model 完整引用)
stem --model glm-5.2                    # 裸模型 id(目录唯一命中,否则落默认平台)
stem --lang ja --model BaiLian:glm-5.2  # 组合使用
stem --resume <sessionId>               # 恢复历史会话(续写同一份会话日志)
echo "一句话问答" | stem --bare          # SIMPLE 模式:stdin → 单次回答(non-TTY)

开发启动(仓库源码 bun run dev,含 --tenant/--user 的 npm/bun flag 透传规则)与构建产物启动见 MSTEM.md

tenant / user 隔离启动

stem                                      # platform 作用域:落盘在 .mstem/platform 与 mstem-storage/platform
stem --user <用户ID>                      # platform + 用户隔离:用户空间 mstem-storage/platform/<用户ID>/
stem --tenant <租户代码>                  # 租户模式:全部落盘状态按 tenant/<租户代码> 隔离
stem --tenant <租户代码> --user <用户ID>  # 租户内再按用户隔离:用户空间 mstem-storage/tenant/<租户代码>/<用户ID>/

四个作用域(platform / platform+user / tenant / tenant+user)两两绝对隔离;租户码/用户码只认启动 flag,不认 env。细则见下文「多租户隔离 · --tenant」与「用户隔离 · --user」两节。

空间初始化

一条命令升级全局 CLI 并补齐 .mstem / mstem-storage 目录骨架(platform 空间 + 已存在或 -t 指定的 tenant 空间;幂等,绝不覆盖已有文件;租户配置不播种,各租户的 llms.json / settings*.json / mcp.json 需手工放入 .mstem/tenant/<code>/,见「多租户隔离」节):

./update_pro.sh [目标项目目录] [-u <name>]... [-t <code>]... [--version <ver>] [--skip-upgrade|--skip-space]

运行形态总览

| 形态 | 启动方式 | 说明 | |---|---|---| | TUI 交互(默认) | stem | Welcome + Repl 全功能终端界面 | | SIMPLE | stem --bare | TTY 下裸 prompt 简化 REPL;non-TTY + stdin pipe 走单次问答链路 | | 无头(SDK 桥) | stem --sdk-url ws://… | 不渲染 TUI,WebSocket NDJSON 全双工驱动,见下节 | | WS telemetry tap | stem --telemetry-url ws://… | TUI 正常交互,遥测帧单向推给外部 WS 观察端,见下节 | | MCP stdio server | ./dist/mcp.mjs | 把 stem 工具集暴露为 MCP server(stdio JSON-RPC),供其他 LLM 客户端接入 | | 后台 daemon | ./dist/daemon.mjs [run\|status] | 无 UI 常驻容器;status 输出一行 JSON 状态;SIGTERM/SIGINT 优雅退出 |

MCP / daemon 是独立构建产物,与 TUI 共用同一 MSC 容器逻辑(构建方式见 MSTEM.md「构建与发布」节)。

无头模式(headless SDK 桥)· --sdk-url

被 sidecar server(FastAPI / desktop server 等)spawn,协议为 WebSocket 上的 NDJSON(每帧单行 JSON),与 Claude Code SDK stream-json 消息同构;内部与 TUI 走完全相同的 MSC agent loop:

stem --sdk-url ws://127.0.0.1:8123/ws/<sid>                  # headless 全双工
stem --sdk-url ws://127.0.0.1:8123/ws/<sid> --sdk-telemetry  # 追加 telemetry 观测帧(默认关)
  • 下行帧:system(init:model / tools / slash_commands / workflows / permissionMode)、stream_event(Anthropic streaming 同构)、resultcontrol_request(权限卡)、command_result / workflow_result / telemetry_event
  • 上行帧:user(消息,入队串行)、control_request(interrupt / set_permission_mode)、control_response(权限裁决)、command(slash 命令)、workflow(直接触发 workflow,不经 LLM)
  • 会话生命周期 = 连接生命周期:ws 断开 → 容器销毁 + exit 0
  • --sdk-telemetry(或 env STEM_TELEMETRY=1)开启八源观测帧(tool/hook/mcp/subagent/workflow/skill/command/plugin)

人工驱动与协议细节:bun scripts/ws-manual-server.ts --spawn(交互式驱动器)· 协议文档 docs/sdk-telemetry-protocol.md · 全量测试手册 stem_cli_test/ws/Stem-WS.md

WebSocket telemetry tap · --telemetry-url

与无头模式不同,tap 是 TUI 共存的单向观测通道:TUI 正常渲染交互,telemetry 帧单向推给外部 WS 观察端;忽略一切下行帧,断开只记日志、不影响 TUI、不重连。env STEM_TELEMETRY_WS 兜底。

stem --telemetry-url ws://127.0.0.1:8123/ws/manual

语言 · --lang

--lang <zh|en|ja|ko>(或 --lang=zh)。缺省时按优先级回退:

  1. --lang flag
  2. env STEM_DEF_LANGUAGE(shell export 或 .mstem/settings.local.jsonenv 段,见「运行时开关」)
  3. POSIX env LC_ALL / LC_MESSAGES / LANG(前缀匹配)
  4. 默认 zh

模型 · --model

--model <ref>(或 --model=<ref>)设置会话级默认模型,不落盘:

  • ref 形式:platform:model(如 BaiLian:glm-5.2,平台名做存在性校验,打错立即报错退出并列出已知平台)或裸模型 id(目录唯一命中,否则落默认平台,不做白名单)

  • 平台与模型目录来自 .mstem/llms.json(见下节)

  • 解析优先级(llms.jsondefault 是唯一的持久来源,settings 不参与模型选择):

    /fast 开启 > 会话级覆盖(--model 启动参数 / 会话内 /model,同一个槽)> llms.jsondefault(工程层 .mstem/<scope>/ > 用户层 ~/.mstem[/tenant/<code>]/)> env STEM_DEF_LLM_MODEL > 首平台首模型

  • /model <ref>会话级的:与 --model 写同一个槽,不落盘,退出即失效,后写胜出

  • /model default 清掉该槽(连 --model 启动参数一起撤销),回落 llms.jsondefault

  • 要改持久默认模型,就改 llms.jsondefault —— 没有别的持久化入口

  • /model current 可查看当前模型与来源

模型目录 · .mstem/llms.json

多平台 LLM 目录,搜索顺序:$STEM_LLMS_PATH<cwd>/.mstem/llms.json~/.mstem/llms.json

  • protocol:anthropic(/v1/messages)或 openai(/chat/completions)
  • apiKeyEnv:密钥所在 env 变量名,缺省 STEM_LLM_<平台大写>_API_KEY;也可写 apiKey 字段(值为全大写下划线视为 env 变量名,否则视为密钥字面量)
  • authHeader:缺省 anthropic → x-api-key、openai → bearer
  • context:支持 '200k' / '1M' / 数字;pricing 单位 USD/1M tokens(供 /cost 分账)
  • vision:该模型是否支持图片理解(布尔);未声明视为能力未知
  • default:全局默认模型;roles.fast:/fast 快速模式用的模型

per-model 深度思考开关(thinking)—— 控制这个模型默认思不思考:

| 字段 | 必填 | 说明 | |---|---|---| | thinking | 否 | 布尔。true = 默认开启深度思考,false = 默认关闭。不写 = 一个字段都不发,由网关自己的默认决定 | | thinking_flag | 否 | 开关字段名,缺省 enable_thinkingprotocol: "openai" 有效(见下)。写坏了整条开关不发 | | thinking_budget | 否 | 思考预算 tokens,仅在 thinking: true 时同发;不写则不发 |

两条协议发的形状不同(2026-08-08 实测,别改回统一形状):

| 协议 | 关 | 开 | |---|---|---| | openai | enable_thinking: false | enable_thinking: true + thinking_budget | | anthropic | thinking: {"type":"disabled"} | thinking: {"type":"enabled","budget_tokens":N} |

anthropic 侧必须用原生对象:百炼 /apps/anthropic 对顶层 enable_thinking: false 静默忽略(照样回 thinking 块),换成 {"type":"disabled"} 才真关掉(同一问题 output_tokens 121 → 4);minimax /anthropic 同样认这个形状。budget_tokens 受网关硬校验 max_tokens > budget_tokens(不满足直接 400),本端会自动压到 max_tokens - 1 以内。

thinking: false会发关闭指令 —— 这一档的主要用途就是关掉 qwen3 / glm 这类默认开思考的模型,不发字段就关不掉。开关按当前模型的目录条目取,子 agent 带自己的 model 时各取各的。

例外:api.anthropic.com(及 *.anthropic.com)不发。官方端点上 budget_tokens 必填且有下限、还与 temperature/max_tokens 互相牵制,这些约束当前配置表达不了;官方端点的推理深度由 /effort 的 system prompt 注入承担。

与内置搜索同样是静默生效的能力:网关不认时不报错也不降级,配完请实测一次(响应里有没有 thinking 块 / reasoning_content / usage.completion_tokens_details.reasoning_tokens)。写错的值一律静默忽略,/doctor 会列出具体问题。

per-model 联网搜索(web_search 工具)—— 各家模型的搜索形态不同,故按模型配置。不写 search = 该模型没有搜索能力,工具压根不上送给模型(保持"出网搜索由用户显式选择"的默认):

| 字段 | 适用档 | 必填 | 说明 | |---|---|---|---| | search | — | — | model / mcp / api | | search_flag | model | 否 | 请求体上那路布尔开关的字段名,缺省 enable_search(百炼 / DashScope 口径);写 false 显式关掉这一路(只留工具形状) | | search_model_url | model | 否 | 写了就走独立请求档:搜索单独打这个地址(OpenAI Chat Completions 形状),不再挂在主对话那次请求上。收 baseUrl 或完整 /chat/completions 两种写法 | | search_model_key / search_model | model | 否 | 独立请求档的密钥与模型;key 省略回落平台 apiKey,model 省略 = 该条目自己的模型 id | | search_model_options | model | 否 | 独立请求档的 search_options 子配置(enable_source / forced_search / search_strategy …),外加 enable_thinking(见下) | | search_mcp | mcp | 是 | "<server>""<server>/<tool>";server 名取自 mcp.json。只给 server 时自动挑搜索工具,有歧义会报错并列出候选,请按提示补 /<tool> | | search_mcp_key | mcp | 否 | 该 MCP server 的鉴权密钥。与平台 apiKey 可以不是同一把 —— 托管搜索服务常另发一把(百炼:对话走 Token Plan 的 sk-sp-,联网搜索认另一把)。这里只是声明,真正送进请求头/环境变量要在 mcp.json 里用 $<平台>$<模型>$search_mcp_key 引用,见下方「MCP 配置」节。值为全大写下划线时视为 env 变量名 | | search_api_url | api | 是 | 三方搜索 API 端点。按 host 自动识别 api.tavily.com / google.serper.dev / api.search.brave.com;其它 host 走通用约定形状:POST {query, max_results}{results:[{title,url,snippet}]} | | search_api_key | api | 否 | 可不设(自建 / 免 key 端点不会带 Authorization)。上面三家已知 vendor 没有免 key 模式,缺 key 会直接报错。值为全大写下划线时视为 env 变量名——llms.json 随仓库提交,密钥请优先用 env 变量名或写在 ~/.mstem/llms.json |

search: "model" = 这个模型自己会搜,把它打开。各家开法不同,所以这一档两路都发,网关各取所需、忽略不认识的那一路:

  1. web_search_20250305 工具形状上送(Anthropic 直连及兼容该 type 的网关)
  2. 请求体加 enable_search: true(百炼 qwen 口径;字段名用 search_flag 改,search_flag: false 关掉这一路)

唯一例外:api.anthropic.com(及 *.anthropic.com)不加第 2 路 —— 官方端点对未知顶层字段直接 400。搜与不搜由模型自己按问题判断,结果内联在回复里,客户端不派发 web_search。两路都不被网关认时表现为静默无搜索——不报错、也不降级,配完请实测一次。

搜索请求的深度思考 = 独立一档,由 search_model_options.enable_thinking,与模型条目上那个管主对话的 thinking 互不相干 —— 独立请求档打的是 search_model_url,可能是另一家网关、另一个模型,没理由跟着对话走。不写 = 不发这个字段,跟搜索端点自己的默认。

注意配置位置与线上位置不一致:配置写在 search_model_options 里(它属于搜索端点这一摊),本端在拼请求体时会把它提到顶层 —— 网关不读 search_options 子对象里的开关。自己手写 curl 时别搞错层级。

建议配 false:这一轮的任务只是把网关注入的「参考资料」整理成一段带来源的摘要,推理链对结果没有贡献,只是让用户干等。2026-08-08 同一条搜索实测 —— 开:84.9s / completion 4040 tokens(其中 reasoning 3853);关:3.9s / 178 tokens,正文长度与注入的 prompt_tokens 都没变。排查「搜索怎么这么慢」先看日志 modelSearch:done 那行的 thinking 字段。

反过来,写进内联档search_options.enable_thinking 是无效的:那个对象进的是主对话那次请求,那里的思考由模型条目上的 thinking 管。/doctor 会点名这种层级错。

mcp 档的密钥不写进 mcp.json —— 用 $<平台>:apiKey(平台那把)或 $<平台>$<模型>$search_mcp_key(这个 server 专用的那把)引用回 llms.json,见下方「MCP 配置」节。

配置写错(如 search 拼错、mcp 档漏了 search_mcp)一律静默降级为"该模型无搜索能力",/doctor 会列出具体问题。

免 key 的 DuckDuckGo / Brave SERP 抓取已于 2026-08-08 移除,STEM_WEBSEARCH_ENABLED / STEM_WEBSEARCH_PROVIDER 两个 env 及同名 settings 一并删除。此前开启过 STEM_WEBSEARCH_ENABLED=1 的用户升级后会失去搜索能力,需改配 search

完整示例:

{
  "platforms": {
    "anthropic": {
      "protocol": "anthropic",
      "baseUrl": "https://api.anthropic.com",
      "apiKeyEnv": "STEM_LLM_ANTHROPIC_API_KEY",
      "models": {
        "claude-sonnet-4-6": { "context": "200k", "search": "model" },
        "claude-haiku-4-5": { "context": "200k" }
      }
    },
    "bailian": {
      "protocol": "anthropic",
      "baseUrl": "https://token-plan.cn-beijing.maas.aliyuncs.com/apps/anthropic",
      "apiKeyEnv": "STEM_LLM_BAILIAN_API_KEY",
      "authHeader": "bearer",
      "models": {
        "qwen3.8-max-preview": {
          "context": "1M",
          "vision": true,
          "pricing": { "input": 1.3, "output": 7.8, "cacheRead": 0.26, "cacheWrite": 0 },
          "search": "model"
        },
        "glm-5.2": {
          "context": "1M",
          "search": "api",
          "search_api_url": "https://api.tavily.com/search",
          "search_api_key": "STEM_TAVILY_API_KEY"
        }
      }
    },
    "minimax": {
      "protocol": "anthropic",
      "baseUrl": "https://api.minimax.io/anthropic",
      "apiKeyEnv": "STEM_LLM_MINIMAX_API_KEY",
      "authHeader": "bearer",
      "models": {
        "MiniMax-M2.7": { "context": "200k", "search": "mcp", "search_mcp": "brave/brave_web_search" },
        "MiniMax-M2.7-highspeed": { "context": "200k" }
      }
    },
    "deepseek": {
      "protocol": "openai",
      "baseUrl": "https://api.deepseek.com",
      "apiKeyEnv": "STEM_LLM_DEEPSEEK_API_KEY",
      "models": {
        "deepseek-chat": { "context": "128k" }
      }
    }
  },
  "default": "bailian:qwen3.8-max-preview",
  "roles": {
    "fast": "minimax:MiniMax-M2.7-highspeed"
  }
}

MCP 配置 · .mstem/mcp.json

MCP server 清单,搜索顺序:$STEM_MCP_CONFIG_PATH~/.mstem/mcp.json(用户层)→ <cwd>/.mstem/<scope>/mcp.json(工程层,高优先)。顶层键 servers(也兼容 CC 布局的 mcpServers)。

每项是本地 stdio远程 HTTP 二选一:

| 形态 | 写法 | 说明 | |---|---|---| | stdio | { command, args?, env? } | spawn 子进程 | | 远程 | { type: "http", url, headers? } | Streamable HTTP(现行 spec);"streamable-http" 同义 | | 远程 | { type: "sse", url, headers? } | 旧的 HTTP+SSE 传输。服务方尚未全部迁完,百炼联网搜索目前只能走这一档 |

不写 type 但有 url 时按路径推断:/sse 结尾当 SSE,其余当 Streamable HTTP。认不出来的条目(既无 command 又无 urlurl 不是 http(s)、type: "ws")会被跳过并在日志里记一条 mcp:config warn ——不再静默丢弃

envheaders 的值都支持引用 llms.json,两种语法:

| 语法 | 取什么 | |---|---| | $<平台>:apiKey / $<平台>:baseUrl | 平台级 —— 该平台的对话密钥 / baseUrl | | $<平台>$<模型>$search_mcp_key | 模型级 —— 该模型条目上的 MCP 专用密钥(另有 search_api_key / search_model_key) |

模型级用 $ 而非 : 作分隔,是因为模型 id 里 . / - 很常见(MiniMax-M3qwen3.8-max),再用 : 会与平台级写法撞上。引用可以内嵌在字符串中间,所以 "Bearer $x:apiKey" 这种写法是合法的。

{
  "servers": {
    "minimax": {
      "command": "uvx",
      "args": ["minimax-coding-plan-mcp", "-y"],
      "env": {
        "MINIMAX_API_KEY": "$minimax$MiniMax-M3$search_mcp_key",
        "MINIMAX_API_HOST": "https://api.minimaxi.com"
      }
    },
    "bailian-websearch": {
      "type": "sse",
      "url": "https://dashscope.aliyuncs.com/api/v1/mcps/WebSearch/sse",
      "headers": {
        "Authorization": "Bearer $bailian$qwen3.8-max$search_mcp_key"
      }
    }
  }
}
  • 引用在建连之前解析,所以对所有 MCP 消费方一致生效:MCPToolweb_search 的 mcp 档、/mcp 面板
  • 平台 / 模型的密钥本身可以是字面量或 env 变量名,引用侧不用关心 —— 走的是同一套凭据解析。平台名与模型名都大小写不敏感
  • 平台名未知、模型名未知、字段与该模型的 search 档不匹配、或拿不到可用密钥 → 不建连,直接报错点名问题(带着一个字面量 "$minimax:apiKey" 去连,只会换来上游一句语义不明的鉴权错)。这类失败不落连接失败缓存,改完 llms.json 下次调用即可重试
  • 报错文案里不会出现密钥本身,/mcp 面板也只展示 url / command,不展示 headers / env
  • 要写以 $ 开头的字面量值,用 $$ 转义("$$FOO"$FOO)

MCP 工具动态注册:连接成功的 server,其工具会自动注册成 mcp__<server>__<tool> 形态直接进工具池(与 ClaudeCode 命名约定一致),模型可直接调用、权限规则可精确到单个工具。启动时连不上的 server 不注册,其工具仍可经 MCPTool({server, tool, input}) 元工具兜底调用。

| 配置项 | 默认 | 说明 | |---|---|---| | mcp.dynamicTools | true | 关掉则不做动态注册,退回纯 MCPTool 元工具 | | mcp.dynamicToolsMaxTools | 64 | 注册总数上限,溢出的工具仍可经元工具调用 |

工具名里的非法字符会被清洗成 _(server 名 my.browsermcp__my_browser__*),实际名字可经 /mcp 面板查看。

浏览器自动化 · /chrome

让模型能启动 Chrome、导航、点击、填表、截图、读控制台与网络请求。

实现走 MCP 路线:把 Google 官方的 chrome-devtools-mcp 作为 stdio MCP server 接入,其工具按上节规则动态注册成 mcp__chrome-devtools__<tool>。stem 不自己驱动浏览器 —— 可执行文件发现、profile 管理、attach 与退出清理都由 chrome-devtools-mcp 用 puppeteer-core 完成,因此 stem 侧零新依赖

快速开始

/chrome setup      # 写入 ~/.mstem/mcp.json
/chrome connect    # 连接(首次会用 npx 拉包,约 30–60 秒)

然后直接说「打开 example.com,告诉我页面标题」即可。

前置条件:Node ≥ 20.19(^20.19 || ^22.12 || >=23)、已安装 Chrome、首次运行需联网。

子命令

| 命令 | 说明 | |---|---| | /chrome | 状态卡:配置来源、命令行、连接状态、本机 Chrome、Node 版本。不发起连接 | | /chrome setup [flags] | 幂等合并写入 mcp.json(保留同级其他 server) | | /chrome connect | 断开重连并刷新动态工具目录 | | /chrome tools [过滤词] | 列出工具(人读版) | | /chrome remove | 从所属配置层删除条目 |

setup 的可选参数:

| 参数 | 作用 | |---|---| | --headless | 无头模式。默认不开 —— 参考体验是用户看着 Chrome 自己动 | | --isolated | 每次用全新临时 profile。默认不开 —— 默认持久 profile 保留登录态,「在我账号里填这个表」才可用 | | --channel <c> | stable / beta / canary / dev | | --executable-path <p> | 指定 Chrome 可执行文件 | | --browser-url <url> | attach 到已在运行的 Chrome(需其带 --remote-debugging-port 启动) | | --project | 写到 <cwd>/.mstem/mcp.json 而非用户级 | | --force | 条目已存在且与默认不同时才需要 |

写入的配置

{
  "servers": {
    "chrome-devtools": {
      "command": "npx",
      "args": ["-y", "chrome-devtools-mcp@latest",
               "--viewport", "1280x720",
               "--screenshotFormat", "webp",
               "--screenshotMaxWidth", "1568"]
    }
  }
}

几个参数是踩过坑才定下来的,手改时别删:

  • -y —— MCP 的 stdio transport 以 shell: false spawn 子进程。没有 -y 时 npx 会把 "Ok to proceed?" 提示写进握手用的同一条 stdio,连接挂死直到超时,然后落成 failed
  • Windows 上是 npx.cmd —— 同样因为 shell: false,"npx" 在 Windows 会 ENOENT。/chrome setup 会按平台自动写对
  • --screenshotFormat webp + --screenshotMaxWidth 1568 —— 截图直接决定 token 成本,这是主要的尺寸控制手段。stem 侧还有一道 5MB 兜底,超限会丢图并提示改用 webp

配置改动无需重启 —— MCP 配置按 mtime/size 指纹热更新。

权限

动态注册后每个浏览器动作都是独立工具名,可精确配规则(写在 .mstem/settings.local.json~/.mstem.json):

{
  "permissions": {
    "allow": ["mcp__chrome-devtools__*"],
    "ask": [
      "mcp__chrome-devtools__evaluate_script",
      "mcp__chrome-devtools__upload_file"
    ]
  }
}

裁决优先级 deny > ask > allow,所以上面的 ask 条目会压过通配 allow —— 常规操作免弹卡,执行任意 JS 与上传文件仍逐次确认。

工具名尾部 * 是前缀匹配,为动态 MCP 工具新增的语法;带括号的 pattern(如 Bash(npm *))语义不变。注意兜底路径:未注册成功的工具经 MCPTool 调用时,按字面名 MCPTool 鉴权,mcp__* 规则对它不生效。

故障排查

/chrome/chrome connect 会把常见错误翻成一句能照做的提示:

| 现象 | 处理 | |---|---| | spawn npx ENOENT | 装 Node.js 并确保在 PATH 上 | | 首次 connect 超时 | 正在下载包。重试,或先跑 npx -y chrome-devtools-mcp@latest --help 预热 | | Could not find Chrome | 装 Chrome,或 /chrome setup --executable-path <路径> | | SingletonLock / profile 被占用 | /chrome setup --isolated | | ECONNREFUSED(用了 --browser-url) | 目标端口没有 Chrome 在监听,先带 --remote-debugging-port 启动 Chrome | | 会话中途杀掉 Chrome 后一直报错 | 等 60 秒(连接失败缓存 TTL)或直接 /chrome connect | | 模型说「我截图了」但描述不出画面 | 图没送到。查日志 mcpTool:resultimages 字段是否为 0 |

实现要点

改动集中在这几处,排查问题时按图索骥(均在 src/stem-msce/msc-project/mai-cli-server/ 下):

  • logic/services/mcp-logic/source-stem/clientManager.tsMcpContentBlock 类型、tool annotations、失败态 TTL、连接/调用超时、reconnectServer / disconnectServer
  • logic/tools/mcp-tool-logic/source/mcpToolCore.tssplitContent() 把 MCP image 块抬成 ToolInvokeResult.imageBlocks(此前一律降级成字面量 [image],截图直接蒸发)
  • logic/tools/_shared/mcpToolCatalog.ts — 动态注册目录:名称清洗、readOnlyHintisReadOnly、描述截断、数量上限、总开关
  • logic/tools/registry.tsToolDescriptor.mcp 字段、getTool 兜底查目录、buildToolDefs 追加
  • logic/session/session-logic/MaiCliServerSessionLogic.ts — 构建 toolDefs 前刷新目录(带 3s 预算,连不上不阻塞会话);派发时把扁平入参包回 {server, tool, input}
  • logic/core/permission-logic/source/permissionRules.ts — 工具名尾 * 前缀匹配
  • logic/core/system-prompt-logic/source/systemPrompt.tsbuildBrowserPromptSection(),仅在工具池真有浏览器工具时注入「先取快照拿 uid、再按 uid 操作」的用法约定

自检脚本 bun scripts/smoke-chrome-mcp.ts(不需要 Chrome、不需要联网)覆盖截图直通、动态注册、连接失败 TTL、配置写入器与权限通配。

阿里百炼联网搜索 · /bailian

把百炼托管的联网搜索 MCP 接进来,作为 search: "mcp" 档的后端。与 /chrome 同构,差别在于它是远程 SSE server 而非本地 stdio。

快速开始

/bailian setup     # 写入 mcp.json(条目名 bailian-websearch)
/bailian connect   # 连接
/bailian tools     # 确认工具已就绪(只列工具,不计搜索次数)

再到 llms.json 给模型加上路由:

"qwen3.8-max": {
  "search": "mcp",
  "search_mcp": "bailian-websearch/bailian_web_search",
  "search_mcp_key": "sk-ws-…"
}

search_mcp_key 与平台 apiKey 不是同一把 —— 实测平台的 Token Plan key(sk-sp-)打这个端点直接 401 InvalidApiKey,要用开通联网搜索时拿到的那把。setup 默认写平台级引用,发现模型上已有 search_mcp_key 时用 --model <id> 让它改写成模型级引用。

子命令

| 命令 | 说明 | |---|---| | /bailian | 状态卡:配置来源、端点、引用能否解析、连接状态、平台密钥、当前模型的搜索是否真的指向本 server。不发起连接 | | /bailian setup [flags] | 幂等合并写入 mcp.json(保留同级其他 server) | | /bailian connect | 断开重连并刷新动态工具目录 | | /bailian tools [过滤词] | 列出工具(人读版) | | /bailian remove | 从所属配置层删除条目 |

setup 的可选参数:--platform <id>(引用哪个平台,缺省 bailian)、--model <id>(改用该模型的 search_mcp_key)、--project(写工程层)、--force(覆盖已有条目)。

端点用 /WebSearch/mcp(Streamable HTTP,与官方文档一致)。百炼在 2026-08-08 ~ 08-10 间完成了 SSE → Streamable HTTP 的服务端迁移(此前 /mcp 回 405、只有 /sse 通;迁移后倒过来,/sse 只回空流)。若连接报 Invalid content type 一类错,先用 curl 对照两个路径确认服务端协议,再改 type。套餐专属 workspace 域名(llm-<id>.<region>.maas.aliyuncs.com)下同路径也可用。

连上之后 bailian_web_search 不会出现在模型的工具池里 —— 被 search_mcp 指为搜索后端的工具由 hideSearchBackends 摘掉,搜索统一走 web_search 门面按当前模型路由。这是预期行为,不是漏注册。

计费:前 2000 次调用免费,之后按次计。/bailian tools 只列工具不计费,是验证连通性最便宜的一步。

业务模式 · --agent / --coding / --fde

Stem 的三种核心业务模式:

| 模式 | 启动命令 | 状态 | 说明 | 核心能力 | |---|---|---|---|---| | Agent | stem / stem --agent | ✅ 开放(默认) | 面向通用智能体形态 | 业务图谱记忆 | | Coding | stem --coding | ⬜ 暂未开放 | 面向编码工作流的形态 | 业务图谱记忆 · 基于 MortiseAI Spec Code Engine 编码 | | FDE | stem --fde | ⬜ 暂未开放 | 面向企业交付场景的形态 | 业务图谱记忆 · 私有图谱记忆 · 基于 MortiseAI Spec Code Engine 编码 |

三选一布尔开关,缺省回退 env STEM_DEF_MODE,默认 agent。显式请求 --coding / --fde 会拒绝启动并提示(不做静默回退)。

会话 · --resume

--resume <id> / -r <id> / --resume=<id>:恢复指定会话(退出提示里有 --resume <id> 可直接复制),复用原会话日志文件续写。

多租户隔离 · --tenant

--tenant <code>(或 --tenant=<code>)以租户模式启动:全部落盘状态按租户 完全隔离,近似独立实例;不带 flag 时行为与以往完全一致。

  • flag-only:租户码只认启动参数。shell export STEM_TENANT / .env / settings env 段里的 STEM_TENANT 一律被入口清除,不作为输入 (STEM_TENANT 是内部传播通道,不要手工设置)。自拉起 stem 子进程时须显式 转发 --tenant
  • 经 npm/bun 脚本启动(仓库源码开发):flag 透传规则见 MSTEM.md「开发启动」节。
  • 租户码:1–64 字符,小写字母/数字开头,允许 . _ -(拒绝大写与路径 分隔符);非法直接报错退出。

目录布局(H = 租户段 tenant/<code>):

| 数据 | 无租户 | 租户模式 | |---|---|---| | Group 数据(session/causal/distill/evolve) | <cwd>/.mstem/platform/<kind>/ | <cwd>/.mstem/H/<kind>/ | | 用户空间(--user 时的全部数据,见下节) | <cwd>/mstem-storage/platform/<user>/ | <cwd>/mstem-storage/H/<user>/ | | 家目录状态(projects/plans/keybindings/memory/plugins/skills/mcp/hooks/llms/workflows/agents;输入历史为进程内内存,不落盘) | ~/.mstem/... | ~/.mstem/H/... | | 用户 settings | ~/.mstem.json | ~/.mstem/H/settings.json | | 项目配置(settings/skills/plugins/agents/workflows/hooks/mcp/llms/memory) | <cwd>/.mstem/platform/... | <cwd>/.mstem/H/... | | debug 日志 | <cwd>/logs/ | <cwd>/logs/(统一,不分目录;sessionId 唯一) |

  • env 覆盖 × 租户:目录形根只有两个 env(STEM_STORAGE_DIR 重定向存储根、 STEM_DIR 重定向项目配置根;不再有逐 kind 的独立重定向)。即使显式 设置,租户段照插(如 STEM_STORAGE_DIR=/data + --tenant acme/data/tenant/acme/...)— 隔离是边界,不随根重定向失效。文件形显式指针 env(STEM_SETTINGS_PATH / STEM_CMD_LOG / STEM_LLMS_PATH / STEM_HOOKS_PATH / STEM_MCP_CONFIG_PATH / STEM_PLUGINS_DIR)按字面生效。
  • 首启为空 + 自动建空间:租户首次以正常路径启动(TUI / daemon run / mcp serve;--help 等 fast-echo 不算)时自动创建其目录骨架 (.mstem/tenant/<code>/ 及其 kind 四目录、 ~/.mstem/tenant/<code>/projects/),但不播种任何配置 — 新租户的 settings/llms/mcp/skills 全部独立,需逐租户放入(llms.json 凭据不跨租户 共享是特性,不是缺陷;想共享凭据可用字面生效的 STEM_LLMS_PATH)。
  • 租户会话与个人历史互不可见:/resume 在租户模式下只枚举该租户自己的会话。

用户隔离 · --user

--user <id>(或 --user=<id>)在 platform 或 tenant 作用域基础上再追加一层 用户隔离,--tenant 有没有传都生效。四个作用域(平台 / 平台+用户 / 租户 / 租户+用户)两两绝对隔离:每个用户拥有独立的用户空间 <存储根>/platform/<id>/<存储根>/tenant/<code>/<id>/,.mstem 配置根 本身不含任何用户段:

  • 用户数据落用户空间;不传 --user 时数据属 Group 维度,落 STEM_DIR (.mstem/<scope>/<kind>/),mstem-storage 只承载用户空间。会话数据的 口径是完整的:会话/洞察四目录、task 快照与 workflow 日志(projects/)、plan 文件(plans/)、洞察报告与洞察记忆 —— 用户会话的全部落盘都在自己空间内,Group 与其他用户不可见。 (debug 日志例外:统一落 <cwd>/logs/,按唯一 sessionId 区分文件。)
  • 五类资源(workflows / skills / plugins / memory / agents)同样绝对隔离: --user 会话只读自己用户空间下的对应子目录(如 mstem-storage/platform/<id>/skills/),不读 Group 的 .mstem/家目录层; Group 会话也看不到用户空间的资源。
  • llms.jsonsettings.jsonsettings.local.json(以及 mcp/hooks 等配置 文件)仅存在于平台/租户目录 —— 模型与 settings 只能按 Group(平台/租户) 设置,用户维度没有自己的配置副本,--user 会话读到的是所属 Group 的配置。
  • Group 配置对用户会话只读:--user 会话里的一切设置写入(/model/config、技能/插件开关等)都落进程内的 session 临时层 —— 会话内立即生效、 优先级最高,但不落盘,退出即消失,Group 的配置文件一个字节都不会被改。
  • 用户码格式与租户码相同(1–64 字符,小写字母/数字开头,允许 . _ -); 非法直接报错退出,与 --tenant 是否合法/是否传入无关。
  • 不做 npm 中继(与 --tenantnpm_config_tenant 中继不同):npm 脚本下需要 --user 时用 npm run dev -- --user <id> 透传,细则见 MSTEM.md「开发启动」节。

举例:stem --user 1000 → 用户空间 <cwd>/mstem-storage/platform/1000/ (会话/洞察/skills/agents/workflows/memory/plugins 全部在内),llms/settings 读 <cwd>/.mstem/platform/;stem --tenant acme --user 1000 → 用户空间 <cwd>/mstem-storage/tenant/acme/1000/,llms/settings 读 <cwd>/.mstem/tenant/acme/

图片粘贴 · 图文混合理解(macOS)

对齐 Claude Code:剪贴板里有图片(如 Cmd+Shift+Ctrl+4 截图)时按 Ctrl+V,输入框插入 [Image #N] 占位;提交后文本与图片按占位位置交错组成混合消息发给模型。模型也可以直接 Read 图片文件(png/jpg/gif/webp)"看到"内容。

  • 依赖模型能力位:llms.json 模型条目的 vision 字段。vision: false 的模型粘贴时直接拦截提示;粘贴后切到不支持的模型,发送前自动剥离图片并提示「已省略 N 张图片」。
  • 剪贴板读取优先用 pngpaste(brew 可选安装,更快),否则走系统 osascript — 首次调用可能弹 macOS 自动化权限框,拒绝后图片粘贴不可用(等同剪贴板无图)。
  • 超限图片(>5MB 或长边 >8000px)自动用系统 sips 缩放到长边 1568px。
  • 已知限制:--resume 恢复的历史会话中图片不回灌,模型只见 [Image #N] 字面。

其他参数

| 参数 | 说明 | |---|---| | --bare | SIMPLE 模式(见「运行形态总览」) | | --plugin-dir <dir> | 会话级插件根,可重复传入(相对路径按真实调用目录解析) | | --sdk-url / --sdk-telemetry / --telemetry-url | 无头 SDK 桥 / 观测帧 / TUI tap(见上文两节) | | --worktree/-w + --tmux | git worktree + tmux 并排会话(需 git 仓库) | | --update / --upgrade | 自更新 | | --version/-v · --help/-h | 版本 / 完整参数帮助 |

完整参数清单以 stem --help 输出为准。

运行时开关 · .mstem/settings.local.jsonenv

CLI 的全部运行时开关统一放 settings 级联的 env(不再使用 .env): 入口启动时把 env 对象逐 key 注入 process.env,仅补缺不覆盖 — shell 显式 export 的同名变量仍最优先(CI/测试钩子用,如 STEM_LLMS_PATH / STEM_SETTINGS_PATH)。

层级(高层同 key 覆盖低层):

  1. <cwd>/.mstem/settings.local.json — 个人项目级(应 gitignore)
  2. <cwd>/.mstem/settings.json — 团队项目级(随仓库提交)
  3. ~/.mstem.json — 用户全局

示例 .mstem/settings.local.json:

{
  "env": {
    "STEM_DEF_LANGUAGE": "zh",
    "STEM_API_RETRY_INTERVAL_MS": "3000",
    "STEM_DEBUG": "1",
    "STEM_LOOP_CRON_ENABLED": "1"
  }
}

常用开关一览(全部可放 env 段,也可 shell export 临时覆盖):

| 变量 | 作用 | |---|---| | STEM_DEF_LANGUAGE | 界面语言 zh\|en\|ja\|ko(--lang 的兜底) | | STEM_DEF_MODE | 业务模式(--agent 等的兜底) | | STEM_DEF_LLM_MODEL | 模型引用;优先级低于 --model / /model llms.jsondefault —— 只在没配 default 时兜底 | | STEM_DEBUG | 1 = 渲染 tool-use/tool-result 调试卡片 + Debug banner | | STEM_API_RETRY_INTERVAL_MS | LLM 网络错误重试间隔(默认 60000,最多 10 次) | | STEM_API_TIMEOUT_MS | LLM 请求全局超时(平台未配 timeoutMs 时生效,默认 600000) | | STEM_PERMISSION_MODE | 工具权限模式 default\|allow\|plan\|bypassPermissions\|... | | STEM_MCP_CONNECT_TIMEOUT_MS | MCP server 连接超时(默认 120000;首次 npx 拉包 + 浏览器冷启动可能很久) | | STEM_MCP_CALL_TIMEOUT_MS | MCP 单次工具调用超时(默认 120000;性能 trace 一类会很慢) | | STEM_MCP_FAILED_TTL_MS | MCP 连接失败后多久允许重试(默认 60000) | | STEM_LOOP_CRON_ENABLED | 1 = 开启 /loop 后台 cron(默认关,opt-in 安全门) | | STEM_HUD_SHOW_USAGE / STEM_HUD_SHOW_WEEK | HUD 用量行 / 本周行显示开关 | | STEM_TELEMETRY | 1 = 无头模式开启 telemetry 观测帧(--sdk-telemetry 的 env 形式) | | STEM_TELEMETRY_WS | telemetry WS tap 地址 | | STEM_STORAGE_DIR | 用户空间根(缺省 <cwd>/mstem-storage;优先于 settings storage.dir;自动追加作用域+用户段 —— platform/<user>,租户模式下 tenant/<code>/<user>)。只承载 --user 用户空间;Group 数据(session/causal/distill/evolve)落 STEM_DIR 项目配置根,跟随 STEM_DIR 重定向 | | STEM_DIR | 项目配置根(缺省 <cwd>/.mstem;取值形态与作用域追加规则同 STEM_STORAGE_DIR) | | STEM_TENANT | 内部通道,勿手工设置 — 只由 --tenant flag 写入,env 段里的值会被入口忽略 | | STEM_USER | 内部通道,勿手工设置 — 只由 --user flag 写入,env 段里的值会被入口忽略 |

STEM_STORAGE_DIR / STEM_DIR 的取值支持两种形态:绝对路径、相对路径 (相对当前工程路径 cwd 解析)——不做 ~ home 展开,~/... 不会被特殊 处理,直接按相对路径拼接。会话聊天文件默认落 <cwd>/.mstem/platform/session/<sessionId>.jsonl(--user 时落用户空间 <cwd>/mstem-storage/platform/<user>/session/);升级前写在 ~/.mstem/projects/<项目>/mstem-storage/<scope>/common/ 的历史会话 经惰性迁移仍可被 /resume 找到并续写原文件。


配置结构说明

两个配置根均落在目标项目目录下,可用 env STEM_DIR / STEM_STORAGE_DIR 重定向(见「运行时开关」):

  • .mstem/ — 项目配置根:配置文件 + Group 数据(session/causal/distill/evolve 等)
  • mstem-storage/ — 用户空间根:只承载 --user 用户隔离数据,不放配置文件
.mstem/                            # 项目配置根(STEM_DIR)
├── platform/                      # platform 作用域(./update_pro.sh 或 scripts/update-platform-space.sh 补齐+播种)
│   ├── settings.json              #   团队项目级配置(随仓库提交)
│   ├── settings.local.json        #   个人项目级配置(应 gitignore;env 段放运行时开关)
│   ├── llms.json                  #   多平台 LLM 目录(见「模型目录」节)
│   ├── mcp.json                   #   MCP server 配置
│   └── agents/ causal/ distill/ evolve/ memory/ plugins/ session/ skills/ workflows/
└── tenant/<租户代码>/             # tenant 作用域:结构同 platform,首启自动建目录
    └── …                          #   但配置文件不播种,需手工放置

mstem-storage/                     # 用户空间根(STEM_STORAGE_DIR)
├── platform/
│   ├── common/                    # 未指定 --user 时的公共用户空间
│   └── <用户ID>/                  # stem --user <用户ID> 的用户空间
│       └── agents/ causal/ … workflows/(九个子目录同上)
└── tenant/<租户代码>/
    ├── common/                    # 租户内公共用户空间
    └── <用户ID>/                  # stem --tenant <code> --user <id> 的用户空间
  • 四个作用域(platform / platform+user / tenant / tenant+user)两两绝对隔离,详见「多租户隔离」「用户隔离」两节
  • 会话聊天文件默认落 .mstem/platform/session/<sessionId>.jsonl;--user 时落 mstem-storage/platform/<用户ID>/session/
  • settings 级联(对全部 settings key 生效,不止 env/权限段):.mstem/settings.local.json > .mstem/settings.json > ~/.mstem.json(用户全局)——「当前工程 .mstem 配置 > ~/.mstem.json
  • 写入侧防遮蔽:设置默认仍落用户层(theme/advisor 这类跨工程偏好),但工程层已定义该 key 时就地改工程层;取消设置则两侧一起清
  • 骨架补齐与配置播种统一走 ./update_pro.sh(幂等,绝不覆盖已有文件,见「空间初始化」节)

Windows 使用说明

macOS / Linux 用户可以跳过本节。以下是 Windows(尤其中文系统 + 独立 PowerShell / cmd 窗口,即 conhost 而非 Windows Terminal)上遇到过的问题与开关。

画面串行错乱(欢迎横幅 / 思考中 / 状态栏挤在同一行互相覆盖)

原因是「东亚歧义宽度」字符 —— ● · ▁ │ ─ … → — ↑ ◇ 这些状态栏和横幅的主力符号, Unicode 规定"上下文不明时按 1 格算"(xterm / iTerm / Windows Terminal 都照办), 但中文 Windows 的 conhost 按控制台字体的全角字形画成 2 格。渲染器是单元格模型, 模型算的宽度和终端实际推进的列数一旦对不上,光标就逐列漂移。

现在会自动判定(裸 conhost + CJK 代码页 → 按 2 格),启动后还会用 DECXCPR 实测一次 校准。判定结果记在会话日志的 [termProfile] / [termProfile:probe] 里。

判错了可以手动压:

| 变量 | 作用 | | --- | --- | | STEM_AMBIGUOUS_WIDTH=wide\|narrow | 强制歧义宽度按 2 格 / 1 格算,优先级高于自动判定与探针 | | STEM_CODE_PAGE=936 | 覆盖代码页探测(否则跑 chcp 取) | | STEM_COLUMNS / STEM_ROWS | 覆盖终端列/行数,见下 |

画面右侧像被截断 / 窗口底部出现水平滚动条

conhost 的屏幕缓冲区可以比可见窗口宽,而 Node 报告的列数取的是缓冲区宽度 (libuv 用 dwSize.X),于是 UI 会往你看不见的列里画。两种解法:

  1. PowerShell 窗口 → 右键标题栏 → 属性 → 布局 → 把「屏幕缓冲区大小」的宽度改成与 「窗口大小」宽度一致(推荐,一劳永逸);
  2. 或用 STEM_COLUMNS=<实际可见列数> 压住。

刻意不认 COLUMNS / LINES:Git Bash / MSYS 导出的是启动时的静态值,窗口 放大后不更新,拿它当真相源本身就是个 bug。

中文变成 ���

工具输出(PowerShell / Bash)和 MCP server 的 stderr 现在都按正确编码处理:能控制的 子进程强制它输出 UTF-8(PowerShell 注入 [Console]::OutputEncoding,cmd 前缀 chcp 65001),控制不了的(MCP server)按 OEM 代码页解码。

如果仍有乱码,把 [termProfile] 那行里的 codePage / oemEncoding 一并反馈。

MCP server 起不来

Windows 上命令解析失败时不会报 ENOENT —— cross-spawn 会把它交给 cmd.exe, 于是你只会看到一句本地化的 "不是内部或外部命令"(还是 OEM 编码的)。现在会在 spawn 前先按 PATHEXT 沿 PATH 解析一遍,解析不到直接给 command not found on PATH: "uvx"。常见修法:装上对应工具,或把 command 写成 npx.cmd 这样带后缀的形式。

报障时请带上

  • logs/<sessionId>/*.txt(含 [termProfile],一眼能看出终端画像)
  • 复现时加 STEM_DEBUG_REPAINTS=1,日志里会多出全屏重画的归因
  • 画面问题可加 STEM_TTY_RECORD=<路径>,把原始终端字节流录下来一并发回 —— 我们能在 本地虚拟终端里按不同口径回放,把"猜"变成"量"

技术栈

| 维度 | 选择 | |---|---| | 语言 | TypeScript 5(严格模式 + 装饰器) | | 开发运行时 | Bun | | 发行格式 | ESM 单文件 bundle,Node >= 22 | | UI | Ink 6 + React 19 | | 架构引擎 | @mortiseai/mai_msc_engine_ts_module@^0.0.3 | | 许可证 | Apache-2.0 |