oh-my-pi /omfg 深度解析:从一句抱怨到 TTSR 流式拦截规则的生成提示词与实现
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
omfg-user.md是 oh-my-pi("⌥ Coding agent with the IDE wired in")中/omfg斜杠命令背后的用户提示词模板:当用户对 agent 反复出现的不良行为(例如总是写any、总是在 Ruby 里生成不安全的 eval)感到厌烦时,只需输入一句抱怨,这个模板就会驱动模型把抱怨"锻造"成一条 Time Traveling Stream Rule(TTSR,时间旅行流式规则)——一条带 YAML frontmatter 的 Markdown 规则文件,之后每当 agent 的流式输出再次匹配该规则时,流会被中断并注入纠正指导。读完本文,你将理解该提示词的 TTSR 机制约束、JSON 输出契约、占位符与再生成路径,以及从/omfg命令注册、模型调用、规则解析、历史校验到落盘生效的完整源码链路。
提示词定位:它是谁、何时被渲染
omfg-user.md位于 系统提示词目录,全文包裹在<omfg>...</omfg>标签内,整体结构为:角色设定(用户不满于反复出现的 agent 行为)→ 任务(撰写一条本应更早捕获该行为的 TTSR 规则)→TTSR 机制说明→输出契约(Output contract)→示例形态(Example shape)→ 用户抱怨占位符{{complaint}}→ 可选的失败反馈/修订区块。
从源码看,该模板被 OmfgController 以文本方式导入并在每轮生成前渲染:
import omfgUserPrompt from "../../prompts/system/omfg-user.md" with { type: "text" }; // omfg-controller.ts#L4 ... const promptText = prompt.render(omfgUserPrompt, { complaint: request.complaint, feedback: failedAttempts.length > 0 ? failedAttempts.join("\n\n") : undefined, previousRule, });即模板中的{{complaint}}被替换为用户输入抱怨,而{{#if feedback}} ... {{/if}}区块只在存在失败尝试或用户修订时才展开,携带"失败尝试记录"与"最近一次候选 JSON"({{previousRule}}),要求模型"重新生成一条修正后的规则,修复列出的校验失败或用户修订,绝不重复失败的 scope 或 condition"。这是一个典型的带反馈的自修复循环提示词。
TTSR 机制:提示词教给模型的规则模型
提示词" TTSR mechanics"一节定义了模型必须遵守的规则模型,逐条继承如下(这是理解后续所有输出约束的前提):
- 规则即文件:一条 TTSR 规则是一个带 YAML frontmatter 的 Markdown 文件。
condition:一个或多个 JavaScript 正则模式,用于测试 assistant 的流式输出。scope:逗号分隔的允许列表;若存在,则只检查列出的流。- 流类型语义:
text= 仅 assistant 散文;thinking= 隐藏推理摘要;tool= 所有工具的所有参数。 - 文件级工具 scope:
tool:<name>(<glob>)表示只监控一个工具、且仅当 path 类参数匹配 glob 时。例如tool:write(*.rb)、tool:edit(*.ts)。提示词明确要求:针对代码抱怨 SHOULD 使用文件级工具 scope——"Ruby 代码通过write生成 → 用tool:write(*.rb),而不是裸的tool或text"。 - JSON 转义容忍:工具参数在流式期间可能被序列化,因此包含引号的代码的 condition 应当容忍 JSON 转义(例如参数里的
"会出现为\")。 - 命中效果:当
condition在scope内匹配时,流被中断,Markdown 正文作为纠正指导被注入。
仓库文档 docs/ttsr-injection-lifecycle.md 印证了这一机制的运行时事实:condition匹配后触发agent.abort(),随后按contextMode丢弃或保留部分输出,并用ttsr-interrupt.md模板渲染注入内容重试生成;默认 scope(无显式scope时)监控 assistant text 与所有工具参数,但不监控 thinking——与提示词中"scope 是允许列表"的表述一致。
输出契约:模型必须只输出一个 JSON 对象
提示词最核心的部分是Output contract,它规定了模型响应必须满足的硬性格式,逐条如下:
- 只输出恰好一个 JSON 对象,不输出任何其他内容;
- JSON 字段:
name、description、condition、scope、body; name必须是 kebab-case;description必须是一行摘要;condition必须是字符串或字符串数组(JavaScript 正则模式);condition必须匹配本会话更早可见的具体不良 assistant 输出;- 正则反斜杠为 JSON只转义一次:写
"\\beval\\s*\\(",绝不要写"\\\\beval\\\\s*\\("; - 保持
condition精确,绝不使用宽泛的 catch-all; scope必须是字符串或字符串数组;scope在抱怨允许的范围内尽量窄,绝不使用tool, text——除非同一不良行为同时出现在工具参数和 assistant 散文中;body必须是简洁解释正确行为的 Markdown 指导;- 调用方负责组装 YAML frontmatter,模型绝不输出 Markdown frontmatter 或用代码围栏包裹 JSON。
示例形态(提示词原文)
提示词给出的标准示例如下,它同时演示了"精确 condition + 文件级工具 scope + 可执行 body"三要素:
{ "name": "ts-no-any", "description": "Never use `any` in TypeScript — use `unknown`, a generic, or the real type", "condition": ": any|as any", "scope": ["tool:edit(*.ts)", "tool:edit(*.tsx)", "tool:write(*.ts)", "tool:write(*.tsx)"], "body": "Never use `: any` or `as any`. Use `unknown`, a domain type, a generic, or a type guard." }注意condition: ": any|as any"是一个不带转义符的精确正则(as any与: any二选一匹配),scope 同时覆盖edit与write两个会产出代码的工具且限定.ts/.tsx后缀——正是"mechanics"一节要求的文件级工具 scope 写法。
从源码看:契约为何如此苛刻
提示词里的每一条约束,都能在 omfg-rule.ts 的解析/校验代码中找到对应的"防御点"。理解这些代码,能解释契约存在的理由:
1. JSON 提取要抗脏输出
extractGeneratedRuleJson()(omfg-rule.ts#L29-L37)先尝试用正则 ````(?:json)?\s*([\s\S]*?)```从响应中抠出围栏内的 JSON,再用extractBalancedJsonObject()(#L164-L205,一个逐字符跟踪字符串/转义/括号深度的平衡提取器)从第一个{` 提取完整对象;无围栏时直接对全文做平衡提取。这解释了契约中"绝不输出代码围栏"——解析器两者都容忍,但纯 JSON 是最可靠的形态。
2. name 的 kebab-case 要求由 sanitizer 兜底
sanitizeRuleName()(omfg-rule.ts#L39-L47)会把原始 name 转小写、去引号、非[a-z0-9_-]字符折叠为-、压缩连续连字符并去首尾符号。测试证实" Caps & Spaces!! "→caps-spaces、"already_ok-123"保持不变(见 omfg-rule.test.ts#L105-L109)。
3. "只转义一次"的契约有自动修复兜底
针对模型最爱犯的"二次转义"错误,normalizeConditionRegex()(#L74-L89)先用compileRuleCondition试编译,失败则调用unescapeRegexConditionOnce()(把\\\\折叠为\\)再试编译;若修复后仍无法编译,才报Invalid condition regex ...错误。parseGeneratedRule()末尾还会用new TtsrManager().addRule(rule)做最终体检,规则若无有效 condition 或无可达 scope 则报"Rule has no valid condition or reachable scope"(omfg-rule.ts#L148-L151)。
4. frontmatter 由调用方组装
assembleRuleMarkdown()(omfg-rule.ts#L280-L291)把 JSON 五个字段拼装成最终规则文件:
--- name: <name> description: <JSON 序列化的一行字符串> condition: <JSON 字符串或 ["a", "b"] 数组> scope: <同上> --- <body 正文,CRLF 统一为 LF>这正是契约中"调用方组装 YAML frontmatter"的落实——模型永远不该自己写---块。
历史校验:condition 必须真的抓得住那次"犯案现场"
契约中"conditionMUST match the specific offending assistant output visible earlier in this conversation"在代码里对应回放校验:validateRuleAgainstAssistantHistory()(omfg-rule.ts#L346-L377)会:
- 用独立的
TtsrManager注册候选规则(注册失败即判不匹配并给出反馈); collectAssistantSurfaces()(#L306-L344)遍历会话消息,把每条 assistant 消息拆成三类校验面(surface):text块、thinking块、toolCall块(工具参数经JSON.stringify序列化,并抽取path/paths类字段作为文件路径,stream key 形如toolcall:<id>);- 对每个 surface 重置缓冲后调用
manager.checkDelta(surface.text, surface.context),任何一次命中即视为 condition 在历史中真实可抓。
不匹配时的反馈并非一句"没匹配上",而是构造了极具指导性的两段反馈文本,会原样喂回给模型(对应提示词{{feedback}}占位符的内容):
- 无匹配反馈
buildNoMatchFeedback()(#L451-L475):列出已检查的 surface 标签与节选(最多 5 条,超长文本围绕 condition 中的关键词截取 120+140 字符窗口),并提醒"如果可见的坏代码含引号,记得工具参数按序列化 JSON 检查,引号可能以\"形式出现;如果 condition 看起来没问题,就去修 scope 使其覆盖到犯案的工具与文件 glob"; - scope 过宽反馈
buildScopeFeedback()(#L477-L513):当 condition 命中了一个带文件后缀的工具调用、而 scope 却是裸tool/toolcall(宽)或包含text(内容明明在工具参数里)时,直接给出推荐 scope,例如根据犯案路径后缀生成tool:edit(*.ts),并要求"不要重复失败的 scope"。
这条反馈链路正是提示词 mechanics 中"SHOULD 使用文件级工具 scope"的运行时强制执行者。
端到端流程:/omfg 命令如何驱动整条链路
命令注册与入口
/omfg在 builtin-lifecycle.ts#L493-L504 注册:
{ name: "omfg", icon: "rule", description: "Forge a TTSR rule from a complaint to stop a recurring behavior", inlineHint: "<complaint>", allowArgs: true, handleTui: async (command, runtime) => { const complaint = command.text.slice(`/${command.name}`.length).trim(); runtime.ctx.editor.setText(""); await runtime.ctx.handleOmfgCommand(complaint); }, },命令后的整段文本(可含多词)被原样截取为complaint。slash 命令测试验证了三点:完整抱怨透传(/omfg This guy used any again....)、多余空白保留在参数内(/omfg stop making unchecked casts...)、空参调用不报错而是提示用法。
生成—校验—重试循环(最多 3 次)
OmfgController.start()在抱怨为空时提示Usage: /omfg <complaint>,随后挂载一个OmfgPanelComponent面板并异步执行#runRequest()。核心循环在#generateCandidate()(omfg-controller.ts#L131-L191):
const MAX_ATTEMPTS = 3; // omfg-controller.ts#L34每一轮:
- 用
prompt.render(omfgUserPrompt, { complaint, feedback, previousRule })渲染模板——首轮feedback为空、previousRule为空;后续轮feedback累积所有失败原因,previousRule是上一轮候选的完整文件内容; - 通过
session.runEphemeralTurn({ promptText, dedupeReply: false, onTextDelta, signal })发起一个临时(ephemeral)回合——生成内容流式显示在面板上但不污染主会话历史; parseGeneratedRule(replyText)解析:提取失败(如"no object"→Missing generated rule JSON object)、name 为空、缺 condition/scope/body、正则无法编译,都会成为带错误码的失败反馈;validateParsedRuleAgainstAssistantHistory()做历史回放校验;若校验失败但repairEscapedConditions()(把\\\\折叠为\\后重建规则)能让规则通过,则直接采纳修复后的候选(repairedCondition: true);- 校验通过(condition 真的命中过历史输出)即返回
validated: true的候选。
3 轮全部失败后,返回最后一个未验证候选;若历史校验仍不匹配,#runRequest()会弹确认框"Couldn't confirm this rule matches the conversation. Save anyway?",允许用户强制保存——这是一个有意识的人机协作逃逸阀。
保存:项目级 vs 全局,并即时热注册
#saveCandidate()(omfg-controller.ts#L193-L249)提供三个选项:
| 选项 | 落盘路径 | source level |
|---|---|---|
| This project (.omp/rules) | <cwd>/.omp/rules/<name>.md(CONFIG_DIR_NAME=.omp) | project |
| Global — all projects (~/.omp/agent/rules) | <agentDir>/rules/<name>.md | user |
| Amend with feedback… | 不保存,收集用户修订意见进入下一轮生成 | — |
目标文件已存在时弹覆盖确认;保存后经buildOmfgRuleForPath()构建Rule对象并调用#registerLive():
#registerLive(rule: Rule): void { this.ctx.session.ttsrManager?.addRule(rule); }即规则在当前会话内立即生效,无需重启。这与 TTSR 生命周期文档描述的注册行为一致:TtsrManager.addRule()在ttsr.enabled === false、condition 与 astCondition 皆空、同名规则已注册、或 scope 排除所有受监控流时会跳过注册,无效正则以警告记日志而不中断会话。
与 TTSR 运行时的衔接
规则文件保存后即成为标准 TTSR 资源,进入 docs/ttsr-injection-lifecycle.md 描述的运行时管线:会话创建时loadCapability("rules")发现规则并按rule.name首胜去重,bucketRules(...)剔除ttsr.disabledRules列出的规则、在ttsr.builtinRules === false时剔除内建默认规则;流式期间TtsrCoordinator监控text_delta/thinking_delta/toolcall_delta,对命中且interruptMode允许中断的规则立即agent.abort()、延迟 50ms 后按contextMode(默认discard)处理部分输出并注入纠正。换言之,/omfg产出的每一行condition/scope,最终都由TtsrManager.checkDelta()/checkSnapshot()逐字执行——提示词中的"精确、不宽泛"要求直接关系到规则的误报率。
测试覆盖:契约每条都有断言
- omfg.test.ts:命令路由、多词抱怨透传、空参处理;
- omfg-rule.test.ts:JSON 提取(含 body 内嵌代码围栏不误伤)、fenced JSON 接受、畸形输出错误码(缺对象/空 name/缺 condition/缺 scope/非法正则
[)、内联正则标志(?i)支持、name slug 化、以及"edit 工具参数在tool:edit(*.ts)scope 下被历史校验命中"等回放断言; - omfg-controller.test.ts:控制器层流程。
小结
omfg-user.md看似只是一段提示词,实则是 oh-my-pi "抱怨 → 规则 → 流式拦截"治理闭环的规格说明书:它把 TTSR 的规则模型(condition/scope/文件级 glob/JSON 转义)、严格的单 JSON 输出契约、以及面向失败重试的反馈占位符全部前置编码进模型上下文;而 omfg-controller.ts 与 omfg-rule.ts 则用平衡 JSON 提取、正则修复、scope 收窄建议和历史回放校验,把这条契约变成了可执行、可验证、可回滚(Escape 中止、拒绝保存)的工程流程。对希望深入 TTSR 运行时的读者,docs/ttsr-injection-lifecycle.md 覆盖了从规则发现、流式监控、触发中断到持久化恢复的完整实现细节。
【免费下载链接】oh-my-pi⌥ Coding agent with the IDE wired in项目地址: https://gitcode.com/GitHub_Trending/oh/oh-my-pi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考