尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

oh-my-pi coding-agent 斜杠命令深度解析:从多源发现到模板展开

oh-my-pi coding-agent 斜杠命令深度解析:从多源发现到模板展开 oh-my-pi coding-agent 斜杠命令深度解析从多源发现到模板展开【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pioh-my-pi 的coding-agent支持来自多个外部工具Claude Code、OpenAI Codex、OpenCode 等以及自身原生目录的 Markdown 斜杠命令。本文基于仓库内 slash-command-internals.md 的设计文档结合 capability 注册表、命令展开工具 与 交互式模式实现 逐层剖析命令如何被发现、按什么优先级去重、在交互界面里如何呈现以及提交 prompt 时如何被展开并路由到 LLM。读完你能掌握「命令从磁盘文件到实际 prompt」的完整链路并能准确解释同名命令为何某个赢、某个被标记为 shadowed。一、发现模型capability 注册表与 first-wins 去重斜杠命令在coding-agent中被建模为一个 capability能力其定义位于 slash-command.tsexport const slashCommandCapability defineCapabilitySlashCommand({ id: slash-commands, displayName: Slash Commands, description: Custom slash commands defined as markdown files, key: cmd cmd.name, toExtensionId: cmd slash-command:${cmd.name}, validate: cmd { /* name/path/content/level 校验 */ }, });核心契约id 为slash-commands所有 provider 通过这个 id 注册自己注册入口是 builtin.ts 等文件里的registerProviderSlashCommand(slashCommandCapability.id, {...})调用。key 是命令名cmd.name去重完全以名字为准。validate 校验四要素name非空、path非空、content非 undefined、level必须是user/project/native三者之一。校验失败的条目会被注册表丢弃并记录警告。优先级排序与 first-wins 语义capability/index.ts 中的registerProvider会把 provider 按 priority降序插入数组findIndex(p p.priority provider.priority)加载时loadImpl并行调用所有 provider然后按注册顺序遍历条目执行去重。源码中的去重逻辑for (const item of allItems) { const key capability.key(item); if (key undefined) { deduped.push(item); continue; } const keySeen seen.has(key); seen.add(key); // ... if (keySeen || aliasSeen) { item._shadowed true; } else { deduped.push(item); } }即先出现优先级最高的条目赢得 key 所有权后来同名者被标记_shadowed true。这个行为跨 provider 生效同一 provider 内部返回重名也会触发。当前 provider 优先级优先级provider说明100nativeOMP 原生.omp目录见 builtin.ts 中PRIORITY 10090omp-plugins扩展包 / npm / link 插件见 omp-plugins.ts80claudeClaude Code.claude见 claude.ts70claude-pluginsClaude 插件市场/本地插件见 claude-plugins.ts70agents.agent/.agents标准目录见 agents.ts70codexOpenAI Codex见 codex.ts55opencodeOpenCode见 opencode.ts平级tie行为优先级相同的 provider 保持注册顺序。当前导入顺序是claude-plugins→agents→codex因此三者同名碰撞时插件命令胜出。这一点可以从 capability/index.ts 的插入策略推断只有priority 才插到前面相等时追加到队尾。内建命令不走这个 capability注意一个关键边界内建命令built-ins不是slash-commandscapability 的条目。它们住在统一的内建注册表 builtin-registry.ts在 TUI 和 ACP/RPC 模式下先于会话级扩展/自定义/文件命令被分派自动补全与 ACP 可用性也优先预留内建名字和别名。这一设计让/help、/model等系统命令永远不会被用户文件命令覆盖。文件扫描行为各 provider 大多复用 helpers.ts 中的loadFilesFromDir(...)。该函数默认非递归匹配*.mdrecursive参数默认falsepattern 为*.md而非**/*.md使用原生 globgitignore: true、hidden: false、fileType: FileType.File并行读取匹配文件并转换transform成SlashCommand条目。源码中有一段注释解释了为什么必须显式传递recursive原生 glob 默认recursive为 true 并会把*.{ts,js}重写为**/*.{ts,js}否则会遍历整棵子树例如把~/.codex/tools下 venv 的 site-packages 当成工具导入#8552。因此隐藏文件/目录不会被加载、被 git 忽略的路径会被跳过文件顺序遵循原生 glob 返回顺序除非 provider 自己追加排序。二、各 provider 的源路径与本地优先级nativeproviderbuiltin.ts搜索根来自.omp目录getConfigDirs()返回项目优先项目级cwd/.omp/commands/*.md用户级当前激活 profile 的 agent 目录commands/*.md默认 profile 是~/.omp/agent/commands/*.md命名 profile 是~/.omp/profiles/name/agent/commands/*.md源码确认const projectDir await ifNonEmptyDir(ctx.cwd, PATHS.projectDir); if (projectDir) result.push({ dir: projectDir, level: project }); const userDir await ifNonEmptyDir(getAgentDir()); if (userDir) result.push({ dir: userDir, level: user });所以项目级 native 命令在同名时压过用户级 native 命令。native 的 slash 命令 loader 只匹配md扩展名命令名是文件名去掉.md后缀。omp-pluginsprovideromp-plugins.ts扫描已配置扩展包根目录以及已启用 npm/link 插件中的commands/*.md。根优先级依次为调用/CLI → 项目 settings → 用户 settings → 已安装插件。市场marketplace根在此处被刻意排除避免重复发现改由claude-plugins处理。claudeproviderclaude.ts受commands.enableClaudeUser与commands.enableClaudeProject两个 settings 门控用户级~/.claude/commands/**/*.md递归项目级cwd/.claude/commands/**/*.md递归递归扫描后有一个 Claude 特有的处理子目录中的命令额外获得一个命名空间别名。addClaudeCommandNamespaceAliases把foo/bar.md同时注册为bar原始名和foo:bar别名nestedCommands.push(command); aliases.push({ ...command, name: relativeName.replace(/[\\/]/g, :) });注意该 provider 先 push 用户条目再 push 项目条目所以在此 provider 内部同名碰撞时用户 Claude 命令压过项目 Claude 命令跨 provider 仍由全局优先级裁决。codexprovidercodex.ts加载用户级~/.codex/commands/*.md项目级cwd/.codex/commands/*.md两侧加载后以用户优先顺序展平故同 provider 内用户命令压过项目命令。Codex 命令内容经过parseFrontmatter剥离frontmatter 中的name可以覆盖命令名否则使用文件名。opencodeprovideropencode.ts受commands.enableOpencodeUser与commands.enableOpencodeProject门控用户级~/.config/opencode/commands/*.md项目级cwd/.opencode/commands/*.md同样用户优先展平frontmattername可覆盖文件名。claude-pluginsproviderclaude-plugins.ts通过listClaudePluginRoots(...)读取三份注册表~/.claude/plugins/installed_plugins.json、~/.omp/plugins/installed_plugins.json以及从 cwd 解析出的最近项目级注册表。对每个插件根扫描pluginRoot/commands/*.md目录可被插件配置的commands/slash-commands键重映射命令名加插件名前缀plugin:command。跨三份注册表时按优先级合并而非排序--plugin-dir注入的根最先项目级条目次之对同 plugin id 遮蔽用户级条目用户级条目最后对同一 plugin idOMP 注册表优先于 Claude 注册表。每份注册表内部保留 JSON 数据的逐插件顺序没有额外排序步骤。agentsprovideragents.ts从 cwd 向上走到仓库根扫描.agent/与.agents/下的非递归commands/*.md然后扫描~/.agent/commands与~/.agents/commands。provider 内部顺序最近的项目根第一.agent先于.agents项目条目先于用户条目。三、物化为运行时FileSlashCommandextensibility/slash-commands.ts 中的loadSlashCommands()把 capability 条目转换为 prompt 时刻使用的FileSlashCommand对象export interface FileSlashCommand { name: string; description: string; content: string; source: string; // 例如 via Claude Code (User) _source?: { providerName: string; level: user | project | native }; }转换步骤对每个命令解析 frontmatter/bodyparseFrontmatterdescription 来源有frontmatter.description则取它否则取正文第一行非空内容超过 60 字符截断并追加...保留解析后的 body 作为可执行模板内容计算展示用 source 字符串例如via Claude Code Projectvia ${providerName} ${首字母大写的 level}。frontmatter 解析的严重级别与条目 level 相关发现来的 user/project 命令使用warn级别解析解析失败时回退到 key/value 宽松解析显式标记native的 capability 条目使用fatal解析打包的 fallback 模板同样fatal。源码对应行const { description, body } parseCommandTemplate(cmd.content, { source: cmd.path ?? slash-command:${cmd.name}, level: cmd.level native ? fatal : warn, });打包的 fallback 命令文件命令加载完成后task/commands.ts 中嵌入的EMBEDDED_COMMAND_TEMPLATES会以source: bundled追加——仅当同名命令尚未出现时。当前嵌入集合来自构建期以{ type: text }内联的 Markdown如init命令import initMd from ../prompts/agents/init.md with { type: text }; const EMBEDDED_COMMANDS [{ name: init.md, content: prompt.render(initMd) }]; export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray{ name: string; content: string } EMBEDDED_COMMANDS;这意味着打包命令永远垫底任何用户/项目/插件命令都可以按名字覆盖它们。四、交互模式命令列表从哪来交互模式interactive-mode.ts把多个来源合并用于自动补全与命令路由。构造时先构建 pending 命令列表内建命令BUILTIN_SLASH_COMMANDS含参数补全与选中命令的内联提示扩展注册的命令extensionRunner.getRegisteredCommands(...)TypeScript 自定义命令session.customCommands映射为斜杠命令标签可选的skill 命令/skill:name受skills.enableSkillCommands门控随后init()调用refreshSlashCommandState(...)加载文件命令并安装一个自动补全 providercreatePromptActionAutocompleteProvider一个PromptActionAutocompleteProvider内部包着CombinedAutocompleteProvider其候选集包含上述 pending 命令发现的基于文件的命令名字未被内建/hook/自定义/skill/文件命令占用的 prompt-template 命令。refreshSlashCommandState还会同步session.setSlashCommands(...)保证prompt 展开与自动补全看到同一份文件命令集合agent-session.ts 的setSlashCommands。交互模式中对 prompt-template 的排除逻辑值得注意interactive-mode.tsconst reservedNames new Setstring(); for (const command of this.#pendingSlashCommands) { reservedNames.add(command.name); for (const alias of command.aliases ?? []) reservedNames.add(alias); } for (const command of fileSlashCommands) { reservedNames.add(command.name); for (const alias of command.aliases ?? []) reservedNames.add(alias); } const promptTemplateCommands this.session.promptTemplates .filter(template !reservedNames.has(template.name)) .map(...);注释解释了原因AgentSession.prompt()先expandSlashCommand再expandPromptTemplate内建命令执行会先解析别名所以补全候选要镜像这个解析顺序避免同一个 token 既指向文件命令又指向 prompt 模板。刷新生命周期斜杠命令状态在这些时机被刷新交互模式初始化时/move切换工作目录后applyCwdChange重置 capability 并对新 cwd 重新发现interactive-mode.ts 中可见对refreshSlashCommandState(newCwd)的调用且支持切回原目录时回滚编辑器组件被替换时selector-controller.ts显式插件重载流程如/reload-plugins。没有持续的文件 watcher——命令目录变化不会实时反映必须依赖上述刷新点。其他展示面Extensions 仪表盘也加载slash-commandscapability展示 active/shadowed 命令条目包括带_shadowed标记的重复项——这是诊断「为什么我的命令没生效」的直接入口。五、路由与 prompt 管线中的位置统一内建注册表在 TUI 与 ACP/RPC 模式下先于AgentSession.prompt(...)被检查。一个内建命令可以消费输入也可以返回残余的 prompt 文本。TUI-only 内建命令不出现在 ACP 可用列表与分派中ACP 可见的内建命令是有 text-modehandle的条目见 acp-builtins.ts。越过内建边界后AgentSession.prompt(...)在expandPromptTemplates ! false时按以下顺序处理斜杠输入agent-session.ts扩展命令#tryExecuteExtensionCommand/name命中扩展注册命令时立即执行 handlerprompt 返回false已本地消费不发给 LLMTypeScript 自定义命令与 MCP prompt 命令#tryExecuteCustomCommand命中时可能返回string→ 用该字符串替换 prompt 文本若为空串则表示已处理不再发 LLMvoid/undefined→ 视为已处理不产生 LLM prompt基于文件的斜杠命令expandSlashCommand文本仍以/开头时尝试 Markdown 命令展开Prompt 模板expandPromptTemplate在斜杠/自定义处理之后应用投递idleprompt 直接发给 agentstreaming按streamingBehavior排队为 steer/follow-up。源码中这段管线一目了然if (expandPromptTemplates text.startsWith(/)) { const handled await this.#tryExecuteExtensionCommand(text); if (handled) return false; const customResult await this.#tryExecuteCustomCommand(text); if (customResult ! null) { if (customResult ) return false; text customResult; } // Only if text still starts with / (wasnt transformed by custom command) if (text.startsWith(/)) { text expandSlashCommand(text, this.#slashCommands); } } const expandedText expandPromptTemplates ? expandPromptTemplate(text, [...this.#promptTemplates]) : text;这也解释了两个设计决策内建名字在文件命令之前被预留文件命令展开先于 prompt-template 展开自定义命令可以「抹掉」开头的/从而阻止后续文件命令匹配。六、基于文件的斜杠命令展开语义expandSlashCommand(text, fileCommands)extensibility/slash-commands.ts的行为仅当文本以/开头时运行从/后的第一个 token 解析命令名空格前剩余文本经parseCommandArgs解析为参数在已加载的fileCommands中做精确名字匹配不模糊、不补全命中后依次应用位置替换$1、$2…1-based越界得到空串切片替换$[start]/$[start:length]1-basedstart 越界或 length 非正得到空串聚合替换$ARGUMENTS与$prompt.render渲染上下文为{ args, ARGUMENTS, arguments }当模板没有使用任何内联参数占位符时把参数追加在末尾inline-argument fallbackappendInlineArgsFallback。$替换的正则command-args.tscontent.replace( /\$\[(\d)(?::(\d*)?)?\]|\$ARGUMENTS|\$|\$(\d)/g, (match, startRaw?, lengthRaw?, positionalNum?) { ... } );文档特别强调替换只发生在模板字符串上。参数值里包含$1、$等模式不会被递归替换——这避免了二次注入式展开。parseCommandArgs的注意点该解析器是简单的引号感知分词command-args.ts支持单引号与双引号保留空格剥离引号定界符不实现反斜杠转义规则未闭合引号不是错误解析器会消费到字符串末尾。例如/commit fix: handle edge raw $1会得到两个参数fix: handle edge与raw $1后者原样保留。七、未知/...输入的行为核心斜杠逻辑不会拒绝未知斜杠输入。当内建、扩展、自定义、文件命令都没有命中时expandSlashCommand返回原文本字面上的/...prompt 会继续走 prompt-template 展开并投递给 LLM。TUI 与 ACP/RPC 都在session.prompt(...)之前分派共享内建注册表TUI-only 内建命令在 ACP 侧既不广播也不处理因此某个拼写在 ACP 里可能作为普通 prompt 文本落下去。八、ACP/RPC 可用性available-commands.ts 中的buildAvailableSlashCommands(...)以 first-wins 顺序发布命令具备文本能力的内建命令可选 skill 命令扩展命令TypeScript/MCP 自定义命令发现的基于文件的命令。内建主名与别名被预留名字前缀会被解析成内建命令的扩展名例如model:foo其前缀model是内建命令会从 ACP 可用性中过滤掉防止歧义。同一份文件命令加载会同步更新会话的展开集合保证 ACP 广播与展开一致性。九、Streaming 与 idle 的差异idle 路径session.prompt(/x ...)跑完整命令管线要么立即执行命令要么把展开后的文本直接发送。streaming 路径session.isStreaming trueprompt(...)仍然先跑扩展/自定义/文件/模板变换随后要求显式streamingBehaviorsteer→ 排队中断消息agent.steerfollowUp→ 排队回合后消息agent.followUp缺省streamingBehavior时 prompt 抛出AgentBusyErroragent-session.tsif (this.isStreaming) { const streamingBehavior options?.streamingBehavior; if (!streamingBehavior) throw new AgentBusyError(); ... await this.#queueUserMessage(expandedText, options?.images, streamingBehavior, submittedAt); return true; }命令在 streaming 下的特殊行为扩展命令即使 streaming 中也立即执行不排队成文本steer(...)/followUp(...)辅助方法通过#throwIfExtensionCommand拒绝扩展命令文本避免把「必须同步执行的 handler」排成普通文本消息compaction 队列重放使用isKnownSlashCommand(...)判断已知斜杠命令走session.prompt(...)保证命令管线重跑否则走裸 steer/follow-up 方法ui-helpers.ts 中可见该判断被用于消息队列重放。十、错误处理与失败面provider 加载失败相互隔离loadImpl用Promise.all包裹每个 provider 的load单个 provider 抛错只在该 provider 上记[{displayName}] Failed to load: ...警告其余 provider 继续capability/index.ts无效斜杠命令条目被 capability 校验丢弃缺 name/path/content 或 level 非法的条目被validate剔除并警告frontmatter 解析失败分级native 命令fatal 解析错误向外冒泡非 native 命令warning 回退 key/value 宽松解析扩展/自定义命令 handler 异常被捕获并通过扩展错误通道报告无扩展 runner 的自定义命令回退到 logger且视为已处理——不会意外落回文件命令或模板展开继续执行。十一、内建命令注记/pause/pause仅在交互式 TUI 可用。它为主 agent、进程内子 agent 与 advisor 开启一个进程级闸门每个 agent 在下一个安全边界停泊在途调用会跑完不中止任何工作闸门释放前不启动新工作。从暂停屏幕按 Esc、Enter、Space 或 CtrlC 恢复——注意CtrlC 是恢复而非中止任何 agent。十二、实操要点速查把上文收敛成可操作的清单想让命令对所有项目生效放到~/.omp/agent/commands/*.mdnative优先级 100项目级.omp/commands可覆盖它想复用 Claude Code 生态命令保持.claude/commands/递归即可被发现子目录命令额外获得dir:cmd别名用户级需commands.enableClaudeUser: true~/.claude属于外部工具用户源默认关闭、按能力 opt-in见 capability/index.ts 的FOREIGN_USER_PROVIDERS想避免与内建命令撞名内建名字全局预留同名文件命令永远不会执行——可用 Extensions 仪表盘查看_shadowed条目定位冲突模板参数约定$1…位置参数、$[2:3]从第 2 个取 3 个、$ARGUMENTS/$全部参数模板未引用任何占位符时参数自动追加在末尾改完命令文件后无需 watcher/move、/reload-plugins或重开会话都会触发refreshSlashCommandStateACP/RPC 场景只广播有文本能力的内建 非内建名冲突的扩展/自定义/文件命令model:foo这类「前缀即内建名」的扩展命令会被过滤。参考文件主题文件设计文档docs/slash-command-internals.mdcapability 定义与校验packages/coding-agent/src/capability/slash-command.ts注册表、优先级与去重packages/coding-agent/src/capability/index.ts文件扫描公共工具packages/coding-agent/src/discovery/helpers.tsnative 发现packages/coding-agent/src/discovery/builtin.ts运行时物化与展开packages/coding-agent/src/extensibility/slash-commands.ts参数解析与替换packages/coding-agent/src/utils/command-args.ts打包 fallback 命令packages/coding-agent/src/task/commands.ts内建注册表 / ACP 可用性packages/coding-agent/src/slash-commands/builtin-registry.ts、packages/coding-agent/src/slash-commands/available-commands.ts会话 prompt 管线packages/coding-agent/src/session/agent-session.ts交互模式刷新与补全packages/coding-agent/src/modes/interactive-mode.ts、packages/coding-agent/src/modes/controllers/input-controller.ts【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表