@mortiseai/stem
v0.0.39
Published
MortiseAI Stem CLI, built on MSC Engine
Downloads
2,404
Maintainers
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 编码 - EVO 模式 — 独立洞察 Session,固定简化 Welcome。启动或重连:
stem --evo;显示Stem EVO.或Stem EVO. Code
EVO 进化模式:/stem-evo on --stem 生成自我进化方案,/stem-evo on --stem code 生成方案并执行源码进化;/stem-evo on --biz <空间名> 生成同名业务空间的方案,/stem-evo on --biz --use <空间名> 将方案转为可供场景应用的 Wiki/Case/Onto 资产。在来源 CLI 选择目标和策略,stem --evo 打开独立视图,窗口内用 /evolve <新要求> 更新当前方向并同步 evo.directions.stem/biz。方案写入 .mstem/platform/evo/,业务方案按空间隔离。详见 EVO 运行说明。
快速开始
前置依赖
- Node.js >= 22(源码开发/构建另需 Bun,见
MSTEM.md)
npm 快速安装和启动(发布后)
npm install -g @mortiseai/stem
stemmacOS / Linux / Windows 均可,仅需 Node >= 22,无需 bun。
通过 OSS 配置包初始化当前项目(将 <密码> 替换为发布方提供的 6~128 位字母或数字,去掉尖括号):
stem init --platform --<密码>
stem init --p --<密码> # 与 --platform 等效
stem init --8ycwl8 --<密码>--platform 是平台参数,可简写为 --p(例如 /init --p --<密码>);其他作用域参数按租户码处理。下载版本与当前安装的 CLI 一致,分别备份并替换 .mstem/platform/ 或 .mstem/tenant/8ycwl8/。租户包未发布时自动改用 common 通用包,安装目录仍与租户码对齐。客户端无需 .env 或 OSS 工具。密码按 MD5 → scrypt → AES-256-GCM 使用,命令行密码优先于环境密钥。密码可能留在终端历史和进程参数中;程序日志不记录密码。完整说明见 初始化与升级。
在交互界面中使用 /init --platform --<密码>(简写 /init --p --<密码>)安装平台配置,或 /init --<租户号> --<密码> 安装租户配置;安装成功后在仓库根目录生成基础 STEM.md,已有文件保留。安装和文件创建均在本地执行,不调用模型或上下文压缩。参数回显隐藏,安装成功后 CLI 自动重启并加载配置,保留当前会话,不触发模型续跑。无参数 /init 仅生成或完善项目指南 STEM.md;/init --help 查看两种用法。
升级
在交互界面执行 /upgrade 检查并升级 Stem。当前版本与最新版本相同或更高时不做变更。有新版时先升级 CLI,再询问是否更新当前平台和租户配置;选择更新后输入隐藏的配置密码。租户包未发布时自动改用 common 通用包(目录名仍为租户码);平台包未发布则在结果中列出并保留原配置,其余项目继续更新。完成后自动重启并恢复当前会话。详见 初始化与升级。
重启 EVO 服务
stem evo restart重启当前用户的 EVO 后台服务,保留队列、报告、会话、暂停状态及服务运行配置。服务未启动时会直接启动;有任务正在执行时会提示稍后重试。stem evo restart --help 查看用法。在普通 Stem 会话内也可使用 /stem-evo restart。
退出
在 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 同构)、result、control_request(权限卡)、command_result/workflow_result/telemetry_event - 上行帧:
user(消息,入队串行)、control_request(interrupt / set_permission_mode)、control_response(权限裁决)、command(slash 命令)、workflow(直接触发 workflow,不经 LLM) --sdk-url默认由 Stem supervisor 看护(非 TTY 也启用):异常退出自动恢复原会话并重连同一 WS 地址,待执行消息按序续跑;STEM_SUPERVISOR_OFF=1关闭。宿主在 WS 断开时等待重连,以启动器最终退出作为会话终点。宿主主动关闭 WS → 正常exit 0,不自动重启。- 宿主给
user帧附稳定的request_id,以system/user-accepted为接收回执;重连时只重发尚未收到回执的消息,Stem 会按落盘 ID 去重。 - 无头
/restart:发送{ "type":"command", "name":"restart", "request_id":"..." };空闲时回restarting:true,下一条连接的init保持同一session_id且supervisor_generation加一。 --sdk-interactive启用 AskUserQuestion / AskUserText 控制请求;未启用时保持自动代答,供旧宿主兼容。Web 验证等待可由宿主通过control_response恢复。--sdk-telemetry(或 envSTEM_TELEMETRY=1)开启八源观测帧(tool/hook/mcp/subagent/workflow/skill/command/plugin)- 无头
/compact:发送{ "type":"command", "name":"compact", "args":"可选摘要要求", "request_id":"..." }。command_result.ok=true表示 SessionLogic 已替换模型上下文并将摘要写入会话历史;回执的summary是恢复时使用的完整续接文本,另含preTokensEst/postTokensEst。失败返回ok=false、errorCode、error,不会把仅有的命令指令当成压缩成功。
人工驱动与协议细节: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)。缺省时按优先级回退:
--langflag- env
STEM_DEF_LANGUAGE(shell export 或.mstem/settings.local.json的env段,见「运行时开关」) - POSIX env
LC_ALL/LC_MESSAGES/LANG(前缀匹配) - 默认
zh
模型 · --model
--model <ref>(或 --model=<ref>)设置会话级默认模型,不落盘:
ref 形式:
platform:model(如BaiLian:glm-5.2,平台名做存在性校验,打错立即报错退出并列出已知平台)或裸模型 id(目录唯一命中,否则落默认平台,不做白名单)平台与模型目录来自
.mstem/llms.json(见下节)解析优先级(
llms.json的default是唯一的持久来源,settings 不参与模型选择):/fast开启且选中模型与roles.fast目标均声明能力 > 会话级覆盖(--model启动参数 / 会话内/model,同一个槽)>llms.json的default(工程层.mstem/<scope>/> 用户层~/.mstem[/tenant/<code>]/)> envSTEM_DEF_LLM_MODEL> 首平台首模型/model <ref>是会话级的:与--model写同一个槽,不落盘,退出即失效,后写胜出/model default清掉该槽(连--model启动参数一起撤销),回落llms.json的default要改持久默认模型,就改
llms.json的default—— 没有别的持久化入口/model current可查看当前模型与来源/fast [on|off]默认关闭,仅当前选中模型在llms.json声明有效fast_service_tier时可用;不带参数时切换。该字段只声明能力,不会自动开启 fast/effort和/fast只修改当前会话,并更新未来新建会话的默认值;其他已有会话保留自己的设置。/resume、--resume和崩溃恢复读取该会话保存的值,/clear、/branch新建会话时使用最新默认值。旧版本未保存独立偏好的会话在首次恢复时按当时默认值初始化。能力配置按模型级 > 平台级解析:模型未配置则继承平台,模型显式
false禁用。未声明能力时,包括 OpenAI 官方端点,均不默认申请priority;手动输入/fast会提示不可用且不修改开关OpenAI 协议请求在 fast 生效后发送配置的
service_tier。如果配置了roles.fast,只有选中模型和目标模型均声明能力时才切换;目标无能力时保持当前模型。/fast off同时撤销服务档位和模型切换Footer 始终显示快速模式状态:支持时为
快速模式 : 关闭/快速模式 : 开启,当前模型未声明能力时为快速模式 : 不支持;无能力时补全和/help仍隐藏/fast连接 Cockpit Tools API 服务时,Cockpit 的「标准」会强制撤销上游快速档位,「快速」会强制上游使用快速档位;Footer 标记「Cockpit 强制」。Cockpit 设为「Auto」时才按当前会话的
/fast设置执行。被强制期间/fast仍保存偏好,切回 Auto 后恢复生效。保存的
fast.enabled: true只是一项偏好:切到无能力模型或删除能力声明后不生效;切回支持的模型后恢复。roles.fast不能绕过此检查
模型目录 · .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 →bearercontext:支持'200k'/'1M'/ 数字;pricing单位 USD/1M tokens(供/cost分账)vision:该模型是否支持图片理解(布尔);未声明视为能力未知default:全局默认模型;roles.fast:/fast可选的快速模型(不配时已声明的fast_service_tier仍可独立生效)
per-model 深度思考开关与强度—— 控制这个模型默认是否思考,以及 OpenAI 推理模型的原生强度:
| 字段 | 必填 | 说明 |
|---|---|---|
| thinking | 否 | 布尔。true = 默认开启深度思考,false = 默认关闭。不写 = 一个字段都不发,由网关自己的默认决定 |
| thinking_flag | 否 | 开关字段名,缺省 enable_thinking。仅 protocol: "openai" 有效(见下)。写坏了整条开关不发 |
| thinking_budget | 否 | 思考预算 tokens,仅在 thinking: true 时同发;不写则不发 |
| reasoning_effort_levels | 否 | 该模型支持的推理强度数组,可用值:none / minimal / low / medium / high / xhigh / max / ultra。/effort 只接受当前模型列出的档位;不写则保持原方案 low / medium / high / max |
| reasoning_effort | 否 | 推理策略:auto 按任务和执行反馈自动分配;具体档位表示固定默认值;provider 关闭应用层分配、完全跟随供应商。不写等同 auto |
Advisor 顾问复核统一默认使用 max / 120秒,普通和复杂复核仅在证据包容量上区分,可通过运行项目的 advisor.effort、advisor.timeoutMs 覆盖。可选复核失败保留主任务验证状态,必需复核失败标记未完成。详见 Advisor 复核策略。
auto是 Stem 策略状态,不会原样发给 API。主会话会把它解析成具体档位:严格的上一轮增量调整使用低档,普通任务使用中档,规划/跨模块复杂任务使用高档;工具失败或连续空搜索会升档,修改校验通过后的收尾会降档。用户显式/effort <档位>始终优先,/effort auto清除会话覆写并恢复模型策略。协议投递按能力降级:OpenAI 模型显式配置
reasoning_effort或reasoning_effort_levels时发送具体原生档位;Anthropic/兼容网关已配置thinking时按档位调整开关和预算;能力未知时仅注入 system prompt,不盲发未知字段。即使完全未配置而默认进入 auto,也不会因此让兼容网关收到不支持的参数。
"gpt-5.6-sol": {
"reasoning_effort_levels": ["low", "medium", "high", "xhigh", "max", "ultra"],
"reasoning_effort": "auto"
}如需保留某个特殊网关的原始默认行为,可显式写 "reasoning_effort": "provider"。
两条协议发的形状不同(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"= 这个模型自己会搜,把它打开。各家开法不同,所以这一档两路都发,网关各取所需、忽略不认识的那一路:
- 以
web_search_20250305工具形状上送(Anthropic 直连及兼容该 type 的网关)- 请求体加
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/<scope>/mcp.json
MCP server 清单只从当前工程读取:<cwd>/.mstem/platform/mcp.json;租户模式读取 <cwd>/.mstem/tenant/<code>/mcp.json。$STEM_MCP_CONFIG_PATH 是测试/显式覆写入口,设置后改为读取该文件。MCP 不读取 ~/.mstem 下的配置。顶层键 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又无url、url不是 http(s)、type: "ws")会被跳过并在日志里记一条mcp:configwarn ——不再静默丢弃。
env 与 headers 的值都支持引用 llms.json,两种语法:
| 语法 | 取什么 |
|---|---|
| $<平台>:apiKey / $<平台>:baseUrl | 平台级 —— 该平台的对话密钥 / baseUrl |
| $<平台>$<模型>$search_mcp_key | 模型级 —— 该模型条目上的 MCP 专用密钥(另有 search_api_key / search_model_key) |
模型级用 $ 而非 : 作分隔,是因为模型 id 里 . / - 很常见(MiniMax-M3、qwen3.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 消费方一致生效:
MCPTool、web_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.browser → mcp__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 回归测试默认关闭。要让当前工程每次启动 Stem 后都允许按需使用浏览器,在 .mstem/<scope>/settings.local.json 写入:
{
"chrome": { "enabled": true }
}缺失 chrome.enabled 或值不是布尔 true 时均按关闭处理。完成配置后:
/chrome setup # 写入当前工程 .mstem/<scope>/mcp.json
/chrome on # 本次运行立即开启并连接(首次会用 npx 拉包,约 30–60 秒)然后直接说「打开 example.com,告诉我页面标题」即可。/chrome on 会立即刷新
动态工具目录,下一条普通请求生效,无需退出或重启 CLI。
当请求包含打开页面、点击、截图、控制台或 DOM 复测等浏览器意图时,Stem 会先检查
本轮刷新后的工具池:Chrome DevTools MCP 可用就要求走 snapshot → action → 再检查
页面/console 的闭环;不可用则只允许静态诊断并提示 /chrome connect,不会悄悄
退回 AppleScript、手写 CDP、remote-debugging 端口或临时 Puppeteer/Playwright 驱动。
前置条件:Node ≥ 20.19(^20.19 || ^22.12 || >=23)、已安装 Chrome、首次运行需联网。
子命令
| 命令 | 说明 |
|---|---|
| /chrome | 状态卡:配置来源、命令行、连接状态、本机 Chrome、Node 版本。不发起连接 |
| /chrome setup [flags] | 幂等合并写入 mcp.json(保留同级其他 server) |
| /chrome on / /chrome connect | 覆盖默认状态,在本次 Stem 运行中开启、断开重连并刷新工具目录 |
| /chrome off / /chrome close | 覆盖默认状态,在本次 Stem 运行中关闭浏览器并隐藏工具 |
| /chrome tools [过滤词] | 列出工具(人读版) |
| /chrome remove | 从所属配置层删除条目 |
setup 的可选参数:
| 参数 | 作用 |
|---|---|
| --headless | 无头模式。默认不开 —— 参考体验是用户看着 Chrome 自己动 |
| --isolated | 每次用全新临时 profile;这是默认值,可显式写出 |
| --persistent | 复用上游持久 profile 以保留登录态;同一时间只能由一个 Chrome MCP 会话使用 |
| --channel <c> | stable / beta / canary / dev |
| --executable-path <p> | 指定 Chrome 可执行文件 |
| --browser-url <url> | attach 到已在运行的 Chrome(需其带 --remote-debugging-port 启动) |
| --force | 条目已存在且与默认不同时才需要 |
写入的配置
{
"servers": {
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest",
"--screenshotFormat", "webp",
"--screenshotMaxWidth", "1568",
"--isolated"]
}
}
}几个参数是踩过坑才定下来的,手改时别删:
-y—— MCP 的 stdio transport 以shell: falsespawn 子进程。没有-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--isolated—— 每个 MCP server 使用自己的临时 profile,多个 Stem CLI 并行时不会争用SingletonLock。需要登录态时再显式选择--persistent,并确保只运行一个持久 profile 会话- 不设置
--viewport—— 上游在最大化窗口上执行Browser.setContentsSize会失败,并可能留下占用 profile 的 Chrome;需要调整尺寸时在页面连接成功后调用resize_page
配置改动无需重启 —— MCP 配置按 mtime/size 指纹热更新。
权限
动态注册后每个浏览器动作都是独立工具名,可精确配规则(写在当前项目 .mstem/platform/settings.local.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 |
| Browser.setContentsSize / Restore window to normal state | 去掉配置中的 --viewport 后 /chrome connect;新版 /chrome setup 会自动迁移旧默认配置 |
| ECONNREFUSED(用了 --browser-url) | 目标端口没有 Chrome 在监听,先带 --remote-debugging-port 启动 Chrome |
| 会话中途杀掉 Chrome 后一直报错 | 等 60 秒(连接失败缓存 TTL)或直接 /chrome connect |
| 模型说「我截图了」但描述不出画面 | 图没送到。查日志 mcpTool:result 的 images 字段是否为 0 |
实现要点
改动集中在这几处,排查问题时按图索骥(均在 src/stem-msce/msc-project/mai-cli-server/ 下):
logic/services/mcp-logic/source-stem/clientManager.ts—McpContentBlock类型、tool annotations、失败态 TTL、连接/调用超时、reconnectServer/disconnectServerlogic/tools/mcp-tool-logic/source/mcpToolCore.ts—splitContent()把 MCP image 块抬成ToolInvokeResult.imageBlocks(此前一律降级成字面量[image],截图直接蒸发)logic/tools/_shared/mcpToolCatalog.ts— 动态注册目录:名称清洗、readOnlyHint→isReadOnly、描述截断、数量上限、总开关logic/tools/registry.ts—ToolDescriptor.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.ts—buildBrowserPromptSection(),仅在工具池真有浏览器工具时注入「先取快照拿 uid、再按 uid 操作」的用法约定
chrome.enabled 是未执行运行期命令时的基准状态。运行中可用 /chrome off 主动关闭 Stem 托管的浏览器并暂停浏览器工具,配置不会删除;用 /chrome on 即可重新连接,无需重启 Stem。一旦执行 on/off,其进程内覆盖会保持到本次 Stem 退出,下次启动重新读取 settings。/chrome close、/chrome disconnect 与 off 等价,原有 /chrome connect 与 on 等价。
自检脚本 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)、--force(覆盖已有条目)。配置始终写入当前工程的 .mstem/<scope>/mcp.json。
端点用
/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只列工具不计费,是验证连通性最便宜的一步。
业务模式
Stem 的四种启动模式:
| 模式 | 启动命令 | 状态 | 说明 | 核心能力 |
|---|---|---|---|---|
| Agent | stem / stem --agent | ✅ 开放(默认) | 面向通用智能体形态 | 业务图谱记忆 |
| Coding | stem --coding | ✅ 开放 | 面向编码工作流的形态 | 业务图谱记忆 · 基于 MortiseAI Spec Code Engine 编码 |
| FDE | stem --fde | ✅ 开放 | 面向企业交付场景的形态 | 业务图谱记忆 · 私有图谱记忆 · 基于 MortiseAI Spec Code Engine 编码 |
| EVO | stem --evo / stem evo attach | ✅ 开放 | 持续显示来源 Session 的进化任务 | Plan 方案;Code 源码;Use 业务资产应用 |
缺省读取 env STEM_DEF_MODE,默认 agent。--evo 与其他启动模式互斥。EVO 是独立进化视图;来源 Session 使用 /stem-evo on --stem [code] 或 /stem-evo on --biz [--use] <空间名> 选择目标与策略。
会话 · --resume
--resume <id> / -r <id> / --resume=<id>:恢复指定会话(退出提示里有 --resume <id> 可直接复制),复用原会话日志文件续写。
底部耗时按会话累计各轮执行时间,手动恢复和 OOM 自动重启均接续原值;退出期间和轮次之间的空闲时间不计入。执行中每秒保存计时快照,轮次结束、切换会话及正常退出时立即保存;强制终止时恢复最近一次快照。旧会话首次恢复会从已有日志补回已记录的轮次耗时。/clear 新建会话从零计时,/branch 继承分叉时的累计值。
恢复采用严格模式:历史不存在、为空、损坏或读取失败时停止启动并记录原因,不会降级为新会话。内存保护和异常崩溃的自动重启必须取得有效会话通告;会话历史或续接队列保存失败时停止自动重启,保留原会话供排查。
内存告警在下一次模型请求或任务完成时执行回收,无需手动输入“继续”触发。回收后余量仍不足时自动重启:中断中的任务继续执行,已完成任务只恢复会话,不再注入续跑指令;原有待执行队列保持顺序。最终回复与会话记录在退出前保存。
多租户隔离 · --tenant
--tenant <code>(或 --tenant=<code>)以租户模式启动:全部落盘状态按租户
完全隔离,近似独立实例;不带 flag 时行为与以往完全一致。
- flag-only:租户码只认启动参数。shell
export STEM_TENANT/.env/ settingsenv段里的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/memory;输入历史为进程内内存,不落盘) | ~/.mstem/... | ~/.mstem/H/... |
| 项目级共享产物(plan 与 wiki 同级、不插作用域段) | <存储根>/plans/ | <存储根>/plans/ |
| 项目配置(settings/keybindings/skills/plugins/agents/workflows/hooks/mcp/llms/memory) | <cwd>/.mstem/platform/... | <cwd>/.mstem/H/... |
| debug 日志 | <cwd>/logs/<YYYY-MM-DD>/<HH-MM-SS-sessionId>/ | 同左(统一按日期/会话归档;sessionId 唯一) |
- env 覆盖 × 租户:目录形根只有两个 env(
STEM_STORAGE_DIR重定向存储根、STEM_DIR重定向项目配置根;不再有逐 kind 的独立重定向)。即使显式 设置,租户段照插(如STEM_STORAGE_DIR=/data+--tenant acme→/data/tenant/acme/...)— 隔离是边界,不随根重定向失效;独立作用域的wiki/与plans/例外,始终直接位于存储根。文件形显式指针 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>/)。会话数据的口径是完整的:会话/洞察四目录、 task 快照与 workflow 日志(projects/)、洞察报告与洞察记忆 —— 用户会话的全部落盘都在自己空间内,Group 与其他用户不可见。 (debug 日志例外:统一落<cwd>/logs/<YYYY-MM-DD>/<HH-MM-SS-sessionId>/, 按日期与唯一 sessionId 区分会话。) - plan 与 wiki 是项目级独立作用域,不属于会话用户数据:分别落
<存储根>/plans/、<存储根>/wiki/,不插 platform/tenant/user 段。 - 五类资源(workflows / skills / plugins / memory / agents)同样绝对隔离:
--user会话只读自己用户空间下的对应子目录(如mstem-storage/platform/<id>/skills/),不读 Group 的.mstem/家目录层; Group 会话也看不到用户空间的资源。 llms.json、settings.json、settings.local.json(以及 mcp/hooks 等配置 文件)仅存在于平台/租户目录 —— 模型与 settings 只能按 Group(平台/租户) 设置,用户维度没有自己的配置副本,--user会话读到的是所属 Group 的配置。- Group 配置对用户会话只读:
--user会话里的一切设置写入(/model、/config、技能/插件开关等)都落进程内的 session 临时层 —— 会话内立即生效、 优先级最高,但不落盘,退出即消失,Group 的配置文件一个字节都不会被改。 - 用户码格式与租户码相同(1–64 字符,小写字母/数字开头,允许
._-); 非法直接报错退出,与--tenant是否合法/是否传入无关。 - 不做 npm 中继(与
--tenant的npm_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。 - 图片在进入模型前保存到当前会话存储目录的
<sessionId>/images/,按1.png、2.jpg、3.webp…累计编号;重启后继续编号,相同内容复用缓存。用户附件与工具返回的图片都走这条路径。 - 内存回收只卸载图片载荷,保留可读取的文件引用;
--resume后会提供图片索引,模型需要时通过Read指定缓存文件恢复看图能力,不会一次性把全部图片重新载入内存。images/index.jsonl保存编号、来源和文件信息;升级前未缓存的历史图片仍需重新提供。 - 图片索引元数据按会话与文件版本缓存,新增图片或索引变化时刷新;注入模型的最近图片目录限制为 4000 字符,较早图片仍可通过完整索引查找。
运行中继续输入:排队与直接插入
当前任务执行时仍可继续输入,但默认不会改变正在执行的轮次:
- Enter:加入等待队列,当前任务完成后作为独立新轮次按顺序执行。
- Tab:显式直接插入当前运行轮,在下一个合法消息边界交给模型;仅在确实要修正当前任务方向时使用。
- ⌥/Alt+↑:取回最后一条排队消息继续编辑,不中断当前任务。
- Esc:中断当前任务,并把尚未执行的排队消息取回输入框。
直接插入遇到无活动轮、slash 命令、服务端拒绝或超时,会自动回退到等待队列,确保输入不丢失。补全浮层打开时,Tab 仍优先接受补全。
Ctrl+I 引用剪贴板来源 · @path#Lx-y
在编辑器里复制一段代码/文档后按 Ctrl+I(macOS 也可 ⌘I,需终端把 Cmd 组合键透传给程序,如 iTerm2/kitty/WezTerm/ghostty 开启 kitty 键盘协议),stem 用剪贴板文本在当前目录反查来源文件与行号区间,输入框插入 @mind/Wiki_Manual_Test_Cases.md#L228-236 这样的引用而不是原文;发送时把对应行以 <file path lines> 块附给模型,模型无需再 Read。
- 反查规则:首条非空行为锚,逐行比对(容忍缩进差异;首/尾行允许从行中间开始/结束选中;两侧剥掉
#/反引号/**/表格竖线等 Markdown 语法并跳过空行,因此从 Markdown 预览/渲染视图复制的文本也能对回原始.md)。多处匹配取匹配行数最多、路径最短者并提示「存在多处匹配」;找不到则退化为普通粘贴文本。 - 手写
@path#L10-20/@path#L10同样在发送时展开;裸@path(无#L)保持原状,模型自行 Read。 - 终端差异:只有 iTerm2 / kitty / WezTerm / ghostty / tmux / Windows Terminal 能区分 Ctrl+I 与 Tab;其它终端(macOS Terminal.app、VS Code 内置终端等)在无补全浮层、不忙时按裸 Tab 触发同一功能(补全浮层打开时 Tab 仍是接受补全,运行中 Tab 为显式直接插入当前轮)。
- 跳过 node_modules/.git 等目录、所有隐藏目录(含
.mstem会话日志)、.jsonl/.log、二进制与 >2MB 文件;反查上限 2s。
其他参数
| 参数 | 说明 |
|---|---|
| --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 的 env 段
CLI 子进程的运行时开关统一放 settings 级联的 env 段(不再使用 .env):
入口启动时把 env 对象逐 key 注入 process.env,仅补缺不覆盖 — shell
显式 export 的同名变量仍最优先(CI/测试钩子用,如 STEM_LLMS_PATH /
STEM_SETTINGS_PATH)。
层级(高层同 key 覆盖低层):
<cwd>/.mstem/platform/settings.local.json— 个人项目级(应 gitignore)<cwd>/.mstem/platform/settings.json— 团队项目级(随仓库提交)
租户模式改用 .mstem/tenant/<code>/,--user 会话覆盖只保存在进程内。
不读取或重建 ~/.mstem.json,也不回落到 ~/.mstem/ 的配置与扩展。
新增设置默认写当前作用域的 settings.local.json;已有设置写回所属项目层。
STEM_SETTINGS_PATH 可显式替换 local 文件,STEM_DIR 可重定向项目配置根。
示例 .mstem/platform/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.json 的 default —— 只在没配 default 时兜底 |
| STEM_DEBUG | 1 = 渲染 tool-use/tool-result 调试卡片 + Debug banner |
| STEM_ACTIVITY_MODE | 任务过程密度:normal(默认,紧凑语义步骤、错误/小 diff/验证结果智能展开)|quiet(只显示状态 spinner)|detailed(同时预览全部工具结果);也可运行时使用 /statusline activity quiet\|normal\|detailed,STEM_DEBUG=1 仍使用原始调试卡 |
| STEM_API_RETRY_INTERVAL_MS | LLM 网络错误重试间隔(默认 60000,最多 10 次) |
| STEM_API_TIMEOUT_MS | JSON 请求总时限及 SSE 首次有效输出时限(平台未配 timeoutMs 时生效,默认 180000);SSE 开始输出后由空闲时限管理 |
| STEM_API_HEADERS_TIMEOUT_MS | 等待 HTTP 响应头的时限(默认 180000,且不超过请求时限) |
| STEM_API_FIRST_EVENT_TIMEOUT_MS | 等待首个 SSE 数据事件的时限(默认 180000);SSE 心跳不续期,首次有效输出仍受请求时限约束 |
| STREAM_IDLE_TIMEOUT_MS | SSE 有效输出之间的空闲时限(默认 180000,且不超过请求时限);正文、思考、工具参数增量续期,空帧及 SSE 心跳不续期 |
| STEM_API_RECOVERY_WINDOW_MS | 首次可恢复失败后共享的重试恢复窗口(默认 180000);退避和后续尝试共同消耗此窗口,设为 0 禁用扩展恢复 |
| STEM_API_REASONING_MAX_DURATION_MS | 无正文或工具动作时,有效思考增量之间的空闲时限(默认 120000) |
| STEM_API_REASONING_ABSOLUTE_MAX_DURATION_MS | 可选的单次纯思考绝对时限;默认不限制,只有显式设置时持续思考也会受限 |
| STEM_API_STREAM_MAX_DURATION_MS | 可选的单次 SSE 请求总时限;默认不限制,只有显式设置时有效输出也不能延长 |
| STEM_AUTO_COMPACT_TIMEOUT_MS | 自动压缩摘要的独立硬截止(默认 15000);到时中止辅助摘要并继续主任务 |
| STEM_ADAPTIVE_TASK_CONTRACT_MODE | 复杂/批量改动任务的自适应执行合同:enforce(默认,深度理解后硬门控并逐条绑定用户明确约束)、observe(同样规划但只审计不阻断)、off(关闭);未完成时回复独立的“继续/continue/resume”会恢复 scope、约束、步骤和原任务预算,版本化快照可随同一 workspace/session 的 --resume 恢复;磁盘恢复始终要求重新验收,若目标文件指纹漂移则退回深度理解并重提合同。合同验收命令失败时会在原回合提供有上限的自动修复机会并把推理升到 xhigh/max,成功重跑后自动清账,连续失败达到上限才允许收口为阻塞。运行中的 Bash 会对合同目标做前后指纹检查:发生修改即清除旧验收并要求干净重跑,后台启动的命令不会直接形成验收证据;验收还绑定 workspace 根目录、拒绝未写进 verifier 的隐藏 cwd/env 覆写,并只接受与 verifier 完整一致的 Bash 命令,需要子目录、环境变量或复合步骤时须原样写为 cd path && ... / VAR=value ...。Bash、PowerShell 与未声明只读的 MCP 工具还会经过有界 Git mutation manifest:既有脏文件在调用期间再次变化也可识别;发现合同范围外路径时不自动回滚或归因,而是冻结旧合同、升到 max 重新理解并要求带 revisionReason 重提合同。普通新请求会丢弃旧快照;旧开关 STEM_ADAPTIVE_TASK_CONTRACT=0 仍等价于 off。可运行 bun run smoke:task-contract 做无网络状态机检查 |
| 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>;项目级独立作用域 wiki/、plans/ 直接位于根下。session 自动追加 platform/<user或common>/session 或 tenant/<code>/<user或common>/session;Group 洞察数据(causal/distill/evolve)落 STEM_DIR 项目配置根 |
| STEM_DIR | 项目配置根(缺省 <cwd>/.mstem;取值形态与作用域追加规则同 STEM_STORAGE_DIR) |
| STEM_TENANT | 内部通道,勿手工设置 — 只由 --tenant flag 写入,env 段里的值会被入口忽略 |
| STEM_USER | 内部通道,勿手工设置 — 只由 --user flag 写入,env 段里的值会被入口忽略 |
启动器例外:
STEM_SUPERVISOR_OFF必须在 CLI 子进程读取settings.local.json之前生效,因此只从 shell 环境或 Stem 安装/源码根的.env读取。缺省或0开启 supervisor,1关闭。bun run dev也经过 supervisor;直接执行 Bun 源码入口才会绕过。
STEM_STORAGE_DIR / STEM_DIR 的取值支持两种形态:绝对路径、相对路径
(相对当前工程路径 cwd 解析)——不做 ~ home 展开,~/... 不会被特殊
处理,直接按相对路径拼接。会话聊天文件默认落
<cwd>/mstem-storage/platform/common/session/<sessionId>.jsonl(--user 时落用户空间
<cwd>/mstem-storage/platform/<user>/session/);升级前写在
.mstem/<scope>/session/ 或 ~/.mstem/projects/<项目>/ 的历史会话
仍可被 /resume 找到并续写原文件;新会话统一写入存储根。
关闭文件日志时,工具调用跳过输入和结果预览;开启时对预览的层级、条数及字符数设限,图片载荷不进入预览。内存日志包含 heapTotalMB,流式采样使用正在执行的会话;contextTextChars、imageBase64Chars 和 estimatedImageBytes 分别表示文本长度、图片编码长度和估算的解码大小,不等同于实际 RSS。回收日志记录这些指标的前后变化。
模型调用前及完成一轮后,历史的文本与图片编码载荷估算超过 8 MiB 时会清理较早的工具回执和图片,保留最近的工具结果及对话;每轮记录 transcriptMemoryTrim:sample 的 RSS、堆、历史载荷和清理数量。STEM_TRANSCRIPT_TRIM_BYTES 可调整阈值,设为 0 可关闭这项清理。无头 SDK 的发送缓冲在 8 MiB 后丢弃可丢弃的过程帧,关键帧将缓冲推过 16 MiB 时关闭拥塞连接。
目录级 Grep content 最多返回 24 条、约 4000 字符的匹配记录(含路径);指定单个文件时保留 80 条、16000 字符预算。截断结果会提示按已找到的文件和行号收窄或批量读取。最终回复中的校验成功声明会检查本轮已修改文件的版本:后续修改、外部改动或失败的命令会使旧证据失效,需要重跑相关检查或明确披露未重新校验;这一检查也适用于未激活 TaskContract 的轮次。
LLM Wiki · /stem-wiki
三层知识体系的完整指南与使用用例:
MSTEM-WIKI.md(沉淀)·MSTEM-ONTO.md(治理)·MSTEM-CASE.md(度量)。本节及后两节为速查。
参考 nashsu/llm_wiki(Karpathy「LLM Wiki」模式):与 RAG 每次从零检索不同,agent 增量构建并维护一份持久化、互链的 Markdown 知识库 —— 你负责策展(purpose.md 写目标、schema.md 定结构、丢源文件),agent 负责维护(ingest / query / lint)。
- 独立作用域:wiki 不挂 platform/tenant/user。一份 wiki = 一个业务场景,可有多份;统一注册在存储根
mstem-storage/wiki/wikis.json(与platform/、tenant/同级;随STEM_STORAGE_DIR/storage.dir走),缺省每份落mstem-storage/wiki/<name>/,--dir可指到任意目录(如项目内)。 - 会话挂载:
/stem-wiki <name>只按已注册名称把 wiki 挂进当前会话(不接受路径);之后 import/query/lint 都作用于它。wiki / onto / case 各有独立挂载槽,底栏和 session meta 同时保存三者,--resume//resume分别恢复。 - 目录布局与 llm_wiki 桌面端一致,可用 Obsidian 直接打开:
purpose.md·schema.md·raw/sources/·wiki/{index,log,overview}.md + entities/ concepts/ sources/ queries/ comparisons/ synthesis/(+模板目录)·.llm-wiki/{reviews,converted,analysis,ingest-cache.json,search-index.json}。
/stem-wiki create research-a research # 建 wiki(模板 general|research|reading|personal|business)并挂载
/stem-wiki create biz-b business --dir ./kb # 放到项目内
/stem-wiki list · /stem-wiki <name> · /stem-wiki detach · /stem-wiki remove <name> [--purge] · /stem-wiki status · /stem-wiki templates
/stem-wiki import ./papers/ [--force] [--max N] # 归档 → 转 md → SHA256 增量 → agent 分析 → 生成合规页面(ingest 为兼容别名)
/stem-wiki query 这个领域的核心争议是什么? [--save] # 关键词 BM25 + [[wikilink]] 图扩展预检索 → agent 引用作答;--save 存为 query 页
/stem-wiki lint [--fix | --semantic] # 结构检查(零 LLM)/ agent 修复 / 内容语义检查(产出审阅项)
/stem-wiki review [<id> [skip|create]] # 人工审阅队列:矛盾 / 重复 / 缺页 / 建议 / 过期
/stem-wiki overview # 重生成 overview.md(唯一允许 agent 整体重写的聚合页)
/stem-wiki search <关键词> # 纯检索,调试召回V2:/stem-wiki search|query --mode hybrid [--explain](BM25 + 语义 + 图 + 本体的 RRF 融合;需配置 wiki.embedding.url/model 或 STEM_EMBEDDING_URL,否则显式 DEGRADED)· 页面 ## Fact Assertions 表(事实级证据 / 有效期)· entityId / externalIds / canonical 实体归一 + review <id> canonicalize <target> · /stem-wiki snapshot create|list|show 发布快照。详见 MSTEM-WIKI.md §8。
agent 侧配套 5 件工具(MCP server 同样暴露为 tool_wiki_*,外部调用须显式传 name 或 dir):WikiSearch(检索)· WikiIndex(commit:归一 frontmatter、重建 index.md、追加 log.md、审阅项入库)· WikiLint(orphan / broken-link / no-outlinks / schema-routing / frontmatter / index-drift)· WikiIngestPrep(源转换与分块读取)· WikiReview。index.md 是派生物、log.md 只追加,LLM 误写会在下次 commit 时被还原。
提示:ingest 一次会写多个文件,默认权限模式会反复弹确认卡 —— 可 Shift+Tab 切到 acceptEdits,或在 settings 权限段放行 Write(mstem-storage/wiki/**)。prompt 模板可用 .mstem/skills/wiki-<ingest|query|lint-fix|lint-semantic|review|overview>/SKILL.md 覆盖指令块。PDF/DOCX 解析依赖 pdfjs-dist / mammoth(随包安装;缺失时仅对应格式不可用)。
Stem-Ontology · /stem-onto
独立的正式语义空间(可选读取会话当前 Wiki):Wiki 是 bottom-up 的知识沉淀,本体是 top-down 的语义治理,Wiki ≠ Ontology。本体本身也是 Markdown(一类 / 一属性 / 一规则各一页,Obsidian 可开),ontology/** 只由人或经审核的 proposal 合并写入;Agent 只能通过工具提议变更。
- 独立空间:
<存储根>/onto/<name>/ontology/{ontology.md, classes/, properties/, rules/}+.llm-wiki/ontology/{ontology.json, graph.json, proposals/, export/};注册表onto/ontos.json,用/stem-onto <name>挂载。未挂 Wiki 时 build/query 仍可用,页面实例按空集处理。 - 实例标注(不改 frontmatter 解析器,Obsidian Dataview 原生兼容):页面 frontmatter 加
class: Customer;正文用行内字段写类型化关系与属性值 ——industry:: 制造业、- hasContact:: [[entities/zhang-san]]。[[链接]]仍进 outlinks,现有图扩展 / broken-link lint 直接生效;类与属性可用别名(含中文)书写,编译时归一。 - 实例图:
WikiIndex commit末尾自动把标注编译成类型化三元组(type/object/datatype,含inverseOf反向补边),未知类 / 未知属性 / 悬空链接分别记账而非报错。
/stem-onto list · /stem-onto create <name> [--dir p] · /stem-onto <name> · /stem-onto detach · /stem-onto remove <name> [--purge]
/stem-onto init [--name <name>] [--base <iri>] # 未挂自身空间时自动创建;缺 Wiki 时建同名协作 Wiki
/stem-onto status # 类 / 属性 / 规则数、实例覆盖率、三元组数、类树、是否 stale
/stem-onto show <Class|property> # 类:祖先 / 子类 / 可用属性(含继承与槽覆盖)/ 实例;属性:domain / range / 使用样本
/stem-onto tree · /stem-onto build # 类树 / 由 ontology/** 与页面重编译,报告解析问题、继承环、unknown、unresolved
/stem-onto query "class:Customer industry=制造业" # 实例图 DSL:class:X · key=value · A -pred-> B(* 任意)
/stem-onto query "entities/acme -hasOpportunity-> *"/stem-onto proposals [--all] # 本体变更提案队列(.llm-wiki/ontology/proposals/*.json,p-YYYYMMDD-xxxx)
/stem-onto propose [提示] # 让 agent 调研 wiki 后经 OntologyPropose 提交提案(addClass / addProperty / addRule / mergeAlias / reparent / deprecate)
/stem-onto review <id> [--explain] # 按当前磁盘本体校验提案;--explain 让 agent 评估影响面并给出 merge / amend / reject 建议
/stem-onto merge <id> · /stem-onto reject <id> [原因] # merge 是唯一写 ontology/** 的路径:校验 → 落盘 → 重编译 → ontology.md Changelog + log.md
/stem-onto lint [--fix] # 约束校验:unknownClass / deprecatedUse / unknownProperty / domainMismatch / rangeMismatch / cardinality / requiredMissing / unclassified;--fix 只改页面治理不变量:本体只增不破(没有 delete,deprecate 保留页面且有实例时必须给 replacedBy)、LLM 无本体写权(工具只写 proposals;合并按磁盘现状重新校验,人手改过导致冲突会拒绝)。/stem-wiki lint 默认也带本体 finding(WikiLint 的 checks 加了 ontology),但 --fix 分流:结构问题走 /stem-wiki lint --fix,标注问题走 /stem-onto lint --fix。
agent 侧配套工具(MCP 同样暴露为 tool_ontology_*):OntologySchema(tree / classes / class / property / lookup —— 写标注前先查 id,别发明新词)· OntologyGraph(facts / neighbors / instances / path / query —— 结构性、关系性问题走图而不是关键词)· OntologyPropose(唯一的 Agent 侧写入口,只进队列)· OntologyValidate(标注校验;给 proposalId 则校验提案可否合并)。prompt 模板可用 .mstem/skills/onto-<propose|lint-fix|explain|mine|classify>/SKILL.md 覆盖指令块。
/stem-onto mine [--min N] [--scope 目录] [--dry] # 阶段②③:确定性统计候选(未知 class 值 / 高频 tag / 高频 key:: 及其主宾类分布 / 标题词 / 未类型化链接的类对)→ agent 聚类消歧后经 OntologyPropose 提案;--dry 只看统计
/stem-onto classify [--scope 目录] [--max N] # 阶段⑥:让 agent 给未分类页面补 class: 与 prop::(只用已有 id;新词留给 propose)
/stem-onto export [--format jsonld|ttl|shacl|instances|all] # 阶段⑦导出:ontology.jsonld / ontology.ttl(OWL-Lite 子集)/ shapes.ttl(SHACL)/ instances.jsonld本体存在时,WikiSearch 多两个参数:class(只保留该类及子类的实例)与 predicate(沿类型化边扩展种子);system prompt 的 # Wiki 区块追加 ## Ontology 段(类树 ≤ 40 行 + 标注写法硬规则 + 最近黄金集分数),让 ingest 天然产出实例标注。
黄金用例集 · /stem-case
三条命令的职责边界:/stem-wiki 沉淀 · /stem-onto 治理 · /stem-case 度量。三者拥有独立目录、注册表、会话挂载槽与写门;跨空间读取只使用会话当前挂载的协作者。黄金用例集(Golden Use Cases)默认 10 条,配比正例 50% / 错例 30% / 边界 20%,只依赖人的 KnowHow,可以先于任何页面与本体存在;缺 Wiki / Ontology 时解析与结构分明确降级为 n/a。
- 独立空间:
<存储根>/case/<name>/case/{README.md, cases/<g-NNNN>.md}+.llm-wiki/case/{runs/, derived/, latest.json};注册表case/cases.json,用/stem-case <name>挂载。 - 用例:
kind(positive: lookup / relation / aggregate;negative: false-premise / stale / misattribution;edge: multi-hop / alias / cross-class / ambiguous)、question、mode(answer / refute / abstain / list)、mustContain/mustNotContain、entities/relations/classes、weight,正文## Rationale。 - 打分(确定性优先):字面分(mustContain 命中 ×6 + mustNotContain 无违反 ×4)+ 模式一致性(从回答检测 refute / abstain / list;不符封顶 3 分)+ 结构分(已解析的实体 / 关系在 KB 中命中率;无本体时报告标
n/a,不给假低分);open 题可用--judge让 agent 自评。负例通过率单独列,防止"全答对但都顺着说"。每条失败带归因码:missingPage / missingClass / missingRelation / wrongAnswer / followedFalsePremise / overAbstain—— 前三种直接就是建设清单。
/stem-case list · /stem-case create <name> [--dir p] · /stem-case <name> · /stem-case detach · /stem-case remove <name> [--purge]
/stem-case init [--name 名称] [--n 10] [--ratio 5/3/2] [--wiki 名称] [--no-interview] # 未挂自身空间时自动创建;缺 Wiki 时建同名协作 Wiki
/stem-case import <文件|目录> [--analyze] [--max N] # 合规 md/json/csv 直接导入;普通外部资料自动分析后生成 case
/stem-case cases [--kind k] · /stem-case show <id> · /stem-case add [提示] · /stem-case status
/stem-case check # 配比 + 期望解析:resolved / unresolved 两栏,unresolved 即待建清单
/stem-case derive [--apply] # 待建清单 → 本体提案草案 + 页面待办;--apply 让 agent 经 OntologyPropose / WikiReview 落成正式项
/stem-case run [--judge] [--kind k] # agent 逐题用 WikiSearch / OntologyGraph 作答 → CaseScore score(确定性打分 + 归因)→ 全部打完自动收尾
/stem-case report [runId] [--finish] # 套件分 / 按 kind 通过率 / 归因分布 / 逐题明细 / 与上次 run 对比 / 是否已过期(页面或本体变了)三种起步顺序都支持:用例先行(init/import → derive → /stem-onto init + merge → /stem-wiki import → run)、知识先行(基于类树 + 访谈生成 → run)、交替演化(evolve)。/stem-onto status 与 system prompt 会显示最近分数与最弱 kind,引导 import / classify 优先补短板。agent 工具:CaseScore(list / get / add / score / status;MCP tool_case_score)。prompt 指令块可用 .mstem/skills/case-<init|import|add|run|derive|evolve>/SKILL.md 覆盖。
独立面板:/stem-case run 发起后显示逐题进度、分数与归因、套件分和分类汇总;CaseScore score 即时刷新,Ctrl+C / Esc 中断并保留快照,/stem-case hide 关闭。
配置结构说明
两个配置根均落在目标项目目录下,可用 env STEM_DIR / STEM_STORAGE_DIR 重定向(见「运行时开关」):
.mstem/— 项目配置根:配置文件 + Group 洞察数据(causal/distill/evolve 等)mstem-storage/— 存储根:承载所有作用域的 session、--user用户隔离数据,以及独立作用域的 wiki/plan 项目产物;不放配置文件
.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/ skills/ workflows/
└── tenant/<租户代码>/ # tenant 作用域:结构同 platform,首启自动建目录
└── … # 但配置文件不播种,需手工放置
mstem-storage/ # 存储根(STEM_STORAGE_DIR)
├── wiki/ # 独立作用域的 Wiki(不插 platform/tenant/user)
├── plans/ # plan file,与 wiki 同级
├── platform/
│ ├── common/ # 未指定 --user 时的公共用户空间,session/ 位于其下
│ └── <用户ID>/ # stem --user <用户ID> 的用户空间
│ └── agents/ causal/ … workflows/ session/(含用户会话目录)
└── tenant/<租户代码>/
├── common/ # 租户内公共用户空间,session/ 位于其下
└── <用户ID>/ # stem --tenant <code> --user <id> 的用户空间- 四个作用域(platform / platform+user / tenant / tenant+user)两两绝对隔离,详见「多租户隔离」「用户隔离」两节
- 会话聊天文件默认落
mstem-storage/platform/common/session/<sessionId>.jsonl;--user时落mstem-storage/platform/<用户ID>/session/ - settings 级联:会话覆盖 > 当前
.mstem/<scope>/settings.local.json>settings.json;不读取家目录配置。 - 新增设置写当前项目
settings.local.json,已有设置写回所属项目层;取消设置会清除项目两层中的该项。 - 骨架补齐与配置播种统一走
./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 会往你看不见的列里画。两种解法:
- PowerShell 窗口 → 右键标题栏 → 属性 → 布局 → 把「屏幕缓冲区大小」的宽度改成与 「窗口大小」宽度一致(推荐,一劳永逸);
- 或用
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/<YYYY-MM-DD>/<HH-MM-SS-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 |
