OpenProgram Docs

Slash Commands — Unified Design#

设计目标:把 OpenProgram 现在散落在 CLI 写死表、Web composer 写死表、/api/plugins/commands、MCP prompts、skills 这几条互不相通的"指令源",合并成一份统一的 slash 命令登记表,五个层级、一份格式、一套渲染、一个 UI。

这些选择所参照的实现见 slash-commands-references.zh.md。本文档凡是写某项设计取自某个项目的地方,意思是直接复用那个项目的设计选择。


1. 来源层级#

加载顺序由低到高,高优先级覆盖同名低优先级;被覆盖的不丢失,仍可通过 /source:name 显式调用。

Layer 来源 目录 / 接口 谁写 热重载
L0 built-in 源码硬编码 OpenProgram 自己 否(要重启)
L1 plugins ~/.openprogram/plugins/<pkg>/...entrypoints.commands 插件作者 插件 reload 时
L2 mcp-prompts 已连接 MCP 服务的 list_prompts() MCP 服务 session 重连时
L3 skills ~/.openprogram/skills/<name>/SKILL.md 技能作者 / 用户 watcher 监听
L4 user ~/.openprogram/commands/**/*.md 当前用户 watcher 监听
L5 project <cwd>/.openprogram/commands/**/*.md 项目维护者 watcher 监听

覆盖规则照搬 claude-code:后加载者赢;同来源内同名按 realpath 去重防 symlink 重复加载。

显式命名空间格式:/(plugin)name/(mcp:linear)name/(skill)name/(user)name/(project)name。括号内是 source label。

冲突时菜单展示主条目 + 「这个名字还有 N 个其它来源,按 ⇥ 切换」的 hint,照搬 claude-code 的 disambiguation UI 思路。


2. 文件格式#

照搬 claude-code 的 markdown + YAML frontmatter,字段集合并入 claude-code + opencode + openclaw + hermes 的并集,再裁掉对我们没意义的(i18n、provider routing、platform filter)。

---
# 标识 ----------------------------------------------------------
name: review              # 可省,默认取文件名
aliases: [r, rev]         # 可选;同名表里也走覆盖规则
description: 按团队规范 review 当前 diff
when-to-use: |            # 长描述;进入 picker 详情面板
  当用户希望对未提交改动做一轮风格 + bug review 时用。
hidden: false             # true 则不出现在补全菜单,但仍可显式触发

# 参数 ----------------------------------------------------------
arguments:                # 位置参数声明(opencode 风格)
  - name: target
    description: 文件路径或目录,默认当前 diff
    required: false
argument-hint: "[target]"  # 菜单里灰字提示(claude-code)

# 执行 ----------------------------------------------------------
type: prompt              # prompt | local | local-jsx,默认 prompt
context: inline           # inline | fork,默认 inline
agent: general-purpose    # 仅 context: fork 时生效
model: inherit            # inherit | opus | sonnet | haiku | <full id>
effort: medium            # low | medium | high | max | <int>
allowed-tools:            # fork 模式下传给子 agent 的工具白名单
  - Read
  - Grep

# 触发条件(claude-code 独有)-----------------------------------
paths:                    # 通配;命中时该命令才出现在补全里
  - "src/**/*.{ts,tsx}"
  - "**/*.py"
requires:                 # openclaw 风格的前置依赖检查
  any-bins: [git]
  config: [openai_api_key]

# 钩子 ----------------------------------------------------------
hooks:                    # 与 plugins/hooks 共用事件名
  PreToolUse: ...

# 元 -----------------------------------------------------------
version: 1.0.0
---
请审查 {{target}} 这段代码:
- 找潜在 bug
- 检查是否符合 CONVENTIONS.md
- 输出 patch 建议

附加上下文:
$ARGUMENTS

最近 commit:
!`git log -5 --oneline`

当前 diff:
@`git diff --staged`

字段权威表见第 7 节。


3. 命令体模板语法#

照搬 claude-code 的全部,加 opencode 的 $0..$9,加 hermes 的 timeout-bounded shell。

语法 含义 来源
$ARGUMENTS 用户输入命令后的整串文本 claude-code
$0..$9 第 N 个位置参数(shell 风格分词) opencode
{{name}} arguments: 声明的命名参数 opencode + 自创
${OPENPROGRAM_COMMAND_DIR} 命令文件所在目录绝对路径 claude-code
${OPENPROGRAM_SESSION_ID} 当前会话 id claude-code
${OPENPROGRAM_CWD} 当前工作目录 新增
!`cmd` 或代码块 `` ```! 在 host shell 执行,stdout 拼回 prompt;2s timeout claude-code + hermes
@`path` 读文件内容拼回 prompt;路径必须在 trusted_roots 内 新增
<<command-name>>name<</command-name>> 引用另一条命令并展开(递归保护,最多 3 层) 新增

参数解析照搬 claude-code tryParseShellCommand:先 shell-quote 分词,失败 fallback 到 whitespace split。空参数返回空列表,模板里的 $0..$9 解析成空字符串。

数字命名参数(name: "0")拒绝注册,与 $0 冲突。

Shell 执行的安全模型:默认禁用,需要在配置里 commands.allow_shell: true 才能跑 !`...`。MCP 上下文里禁用所有 ! 块。


4. 执行模式#

照搬 claude-code 的三态,加 opencode 的 subtask 概念。

type: prompt            (默认)
  渲染模板 → 当作用户消息塞进当前会话 → 走正常 agent loop

type: local
  调用 host 注册的 LocalCommandHandler;返回 LocalCommandResult
  保留给内置命令:/compact /clear /new /web /model 等

type: local-jsx
  暂不实现。Web UI 可以渲染 React 组件作为命令结果(claude-code 用 ink)
  我们这边走 server-pushed structured event,留接口

context: inline          (默认)
  渲染后的 prompt 进当前会话上下文

context: fork
  开 agent 子 agent 跑(已有 functions/tools/agent)
  agent 字段决定 subagent_type;allowed-tools 决定可见工具集
  子 agent 返回的最终消息以「命令结果」形式呈现,不污染主上下文

context: fork 等价于"敲 /review 自动转成调一次 agent(prompt=...)"。这一步把 claude-code 的 fork 模式直接嫁接到我们已有的 subagent 机制上。


5. 触发条件(paths / requires)#

照搬 claude-code 的 paths、openclaw 的 requires:在不满足条件时从补全菜单隐藏,但用户仍可手动敲完整命令触发——触发时再做一次硬校验,失败给清晰报错。

paths: ["src/**/*.py"]:当前会话最近 touch 过的文件(或显式 @file 引用的)命中 glob 时才显示。

requires.any-bins: [git, rg]which 检查至少一个可用。失败则 hint「需要 git / rg,请先安装」。

requires.config: [openai_api_key]:当前 profile 配过该键。

requires.platform: [darwin, linux]:从 hermes 借来的平台过滤。


6. 钩子绑定#

命令可以声明自己的临时 hook(仅在这条命令的执行期间生效)。事件名复用 openprogram/events/registry.py 里的总线事件类型(tool.beforetool.afterchat.before_send……)。

hooks:
  PreToolUse:
    - matcher: Bash
      command: !`echo "blocked by /review" >&2; exit 2`
  PostToolUse:
    - matcher: Edit
      handler: built-in:auto-stage

handler 形式两种:

  • ! 反引号块 → 跑 shell,stdout 进日志,exit code 决定 allow/deny
  • built-in:<id> → 调 host 注册的命名 handler(首发不做,留接口)

等 hooks 子系统具备拦截与改写语义后,本节才完整可用。schema 先在这里定下来,后续加入时就不构成破坏性变更。


7. Frontmatter 字段权威表#

字段 类型 默认 含义 借自
name string 文件名 stem 命令名 通用
aliases string[] [] 别名,独立走覆盖表 claude-code
description string "" 一行说明,菜单展示 通用
when-to-use string | md "" 长说明,详情面板 claude-code
hidden bool false 隐藏出补全 claude-code (isHidden)
arguments list [] 位置参数声明 opencode
argument-hint string 自动生成 菜单灰字提示 claude-code
type enum prompt 执行模式 claude-code
context enum inline inline / fork claude-code
agent string "general-purpose" fork 时的 subagent_type claude-code
model enum/string inherit 模型覆盖 claude-code
effort enum/int inherit 推理强度 claude-code
allowed-tools string[] inherit 工具白名单 claude-code
paths string[] null glob 条件激活 claude-code
requires object {} 前置依赖 openclaw
hooks object {} 临时钩子 claude-code
version semver null 升级提示用 claude-code
user-invocable bool true 是否对应 /name 触发,false 时只能模型调 claude-code
shell enum inherit bash / powershell,! 块用 claude-code

未声明的字段一律保留进 extras dict,不报错(向前兼容)。


8. UI#

补全菜单按 source 分组,组内按字母序,跨组按 source 优先级(project > user > skill > mcp > plugin > builtin)。每条展示:

/review                  按团队规范 review 当前 diff      [project]
  [target]

搜索按 fuzzy(name + description + when-to-use)。

详情面板(按 ⇥ 展开):

/review (project)
─────────────────────────────────────
按团队规范 review 当前 diff

参数:[target] (optional)
模式:inline · model: inherit · effort: medium
来源:.openprogram/commands/review.md

冲突态:菜单条目右侧标 (+2 more),⇥ 切换不同 source 的实现。

技能(L3)和 MCP prompts(L2)自动注入;技能命令默认 context: forkagent: general-purpose,MCP prompts 默认 type: prompt + inline(因为它们本来就是 prompt 模板)。


9. 安全#

  • 路径加载:realpath 解析后必须落在 trusted_roots(~/.openprogram/$cwd/.openprogram/)内,否则拒绝。
  • YAML 解析:yaml.safe_load,禁用任意类型构造。
  • Glob:fnmatch 风格,禁用 .. 与绝对路径。
  • ! shell 块:默认禁用,2s timeout,禁止 fork bomb,stdout 限制 64KB。
  • @ 文件引用:必须在 trusted_roots 内或显式被 --allow-file <abs> 授权。
  • 来源标签固定由 loader 写入,不允许 frontmatter 自报 source:

10. 工程实现#

10.1 目录布局#

openprogram/commands/
├── __init__.py             # 对外 API: list_commands / get / dispatch
├── loader.py               # 扫描 + 解析 + 合并五个 layer
├── frontmatter.py          # YAML 解析 + 字段校验
├── template.py             # $ARGUMENTS / {{name}} / !`...` / @`...` 渲染
├── conditions.py           # paths / requires 评估
├── registry.py             # 进程内合并表 + 冲突索引
├── dispatch.py             # type/context 分支
├── watcher.py              # inotify/fsevents 监听 L3-L5
└── _ref.py                 # 给 web/cli 用的轻量 view 投影

10.2 数据流#

启动 / reload
  → loader.scan_all_layers()
  → for each layer: read files / call provider (plugins, mcp.list_prompts)
  → frontmatter.parse + validate
  → registry.merge(layer, items)        覆盖 + 别名 + 冲突索引

用户敲 /review xxx
  → cli or web 转发到 dispatch.invoke(name, raw_args, session_ctx)
  → registry.resolve(name) → CommandSpec
  → conditions.check(spec, session_ctx) → ok / blocked-with-reason
  → template.render(spec.body, parsed_args, env)
  → dispatch by type:
       prompt + inline → session.append_user_message(rendered)
       prompt + fork   → task.run(agent=spec.agent, prompt=rendered, tools=allowed)
       local           → handler(session_ctx, parsed_args)

10.3 API#

后端:

GET    /api/commands                    # 合并后的统一列表(含 source、metadata)
GET    /api/commands/{name}             # 单条详情(含 body 模板预览)
POST   /api/commands/{name}/invoke      # body: {session_id, raw_args}
POST   /api/commands/reload             # 强制重扫
GET    /api/commands/conflicts          # 冲突表(同名多源)

/api/plugins/commands 保留为兼容入口,内部 redirect 到 /api/commands?source=plugin

前端:

web/components/chat/composer/slash/use-slash-menu.ts 改读 /api/commands,删掉内部硬编码列表(保留 dispatcher 兼容层,把 client 侧 /compact /clear 等映射到 builtin local 命令)。

CLI:

openprogram/_cli_chat/handlers.py:_handle_slash 的每条 /slash 都经 registry 解析——TUI 与 WebUI 共用同一张表,不再有任何硬编码命令列表。Rich REPL 的本地动作放在 builtin 层:register_repl_builtins 逐条注册,handler 填动作名字符串作标记,_LOCAL_ACTIONS 把标记映射回本地实现。命令的存在性、别名、/help 全部读自 registry(list_all())。其余层(plugin / skill / user / project)的命令经 dispatch.invoke 渲染,渲染出的正文作为本轮消息发给 agent——与 Web composer 的展开语义一致。

Ink TUI(cli/src/commands/registry.ts)只硬编码 TUI 本地动作(主题、picker、导出等),经 GET /api/commands 从 worker 拉统一 registry——并入补全、ctrl+K 面板与 /help——registry 命令经 POST /api/commands/invoke 展开,渲染正文作为聊天轮发送。

10.4 建设顺序#

各部分独立可发布,按依赖顺序落地:

1  扫描器 + frontmatter + 渲染 + registry + /api/commands   [基础]
2  L4 (~/.openprogram/commands) + L5 (.openprogram/commands)
3  L3 skills 自动注入(skills/loader 暴露 to_command_spec())
4  L2 mcp prompts 自动注入(mcp/registry 已有 list_prompts)
5  L1 plugins 接入新表(plugins/loader 已有 _commands,加一层 adapter)
6  context: fork 接 agent 工具
7  paths / requires 触发条件评估
8  watcher 热重载
9  builtin 命令迁移成 type: local + frontmatter
10 hooks 字段在 hooks 子系统升级后启用

第 1-2 项加第 5 项合起来已经交付了用户可见的大部分价值。


11. 不实现的部分(明确舍弃)#

来源 设计 不抄的原因
openclaw i18n (descriptionLocalizations) 我们是英文 + 简中两套,运行时切换没价值
openclaw provider routing (Slack vs Mattermost) 单一 host,没有多 provider 命名
hermes platforms 过滤 (darwin/linux/win32) requires.platform 替代
hermes 提示注入 134 pattern 检测 移到独立的 prompt-injection scanner 子系统
claude-code local-jsx React 组件 Web UI 用 structured event 替代,CLI 不实现
pi-mono 纯硬编码 反例

12. 版本与升级#

version: 1.0.0 字段 + 来源仓库的 git hash(如有)一起塞 registry。

L1(plugins)走插件 autoupdate 子系统(已有)。

L3(skills)走 skills discovery diff(已有)。

L4 / L5 用户写的,不自动更新。

L0(builtin)跟随 OpenProgram 版本。

slash-commands-references.zh.md 记录五家参考项目实现斜杠命令的方式,并周期性重扫。在那里发现的新设计,作为字段增补进本文档 §2/§3,不破坏既有 frontmatter(额外字段进 extras)。

Last updated · 2026-08-13