i-have-adhd TypeScript扩展架构深度解析:从loadRules到syncContext的完整调用链
2026/8/30 12:54:51 网站建设 项目流程

i-have-adhd TypeScript扩展架构深度解析:从loadRules到syncContext的完整调用链

【免费下载链接】i-have-adhdA skill to stop your coding agent from burying the answer. ADHD-friendly output.项目地址: https://gitcode.com/GitHub_Trending/ih/i-have-adhd

i-have-adhd 是一个让 AI 编码助手输出 ADHD 友好内容的技能插件:答案先行、步骤编号、零客套。本文以它面向 Pi / OMP 的 TypeScript 扩展为例,完整拆解从loadRules读取规则文件、到syncContext把规则注入对话的调用链,带你弄清这个扩展如何在整个会话期间让规则"常驻"在 Agent 上下文中。

1️⃣ i-have-adhd 是什么:一个 ADHD 友好输出技能

普通编码 Agent 回答"认证怎么修",可能先铺垫三句再给答案。i-have-adhd 做的事情很简单:别让 Agent 把答案埋在话里

它的规则集共 10 条,全部定义在 skills/i-have-adhd/SKILL.md 中:

规则一句话说明
1. 答案先行第一行就是能执行的命令或路径
2. 步骤编号多步任务用编号列表,一步一个动作
3. 以单一动作收尾结尾只留一个两分钟内能做的下一步
4. 抑制跑题当前问题没解决完,不谈第二个问题
5. 每轮复述状态"第 3 步/共 5 步完成",不依赖读者记忆
6. 具体耗时估计说"约 15 分钟",不说"一点工作量"
7. 让进展可见明确说出"现在能跑通了"
8. 平静陈述错误位置、原因、修复,不用"Oh no"
9. 列表不超过 5 项超出就拆成"现在做"和"以后做"
10. 无开场、无总结、无客套从答案开始,到答案结束

规则本体是"单一事实来源"(single source of truth),扩展代码只负责把这份规则文件送进模型上下文,两者职责分离——这正是理解其 TypeScript 架构的钥匙。

核心文件一览:

文件职责
extensions/i-have-adhd.ts扩展入口:加载规则、管理状态、订阅事件
extensions/context-compat.ts运行时兼容层:从 Pi / OMP 读取上下文消息
skills/i-have-adhd/SKILL.md10 条规则的权威定义
package.jsonpi/omp字段中声明扩展入口
scripts/check_context_compat.ts兼容性自检脚本

2️⃣ 启动第一步:loadRules 读取规则文件

Pi 或 OMP 启动时会加载 extensions/i-have-adhd.ts 导出的工厂函数iHaveAdhdExtension(pi),它做的第一件事就是调用loadRules()(见 extensions/i-have-adhd.ts):

  1. 定位文件:模块顶层用自己的文件位置(fileURLToPath(import.meta.url))拼出SKILL.md的绝对路径,不依赖工作目录。
  2. 读取全文:用readFileSync读文件;读不到就抛一个带路径的错误,让问题在启动时暴露。
  3. 剥离 frontmatterstripFrontmatter()用正则删掉文件头部的 YAML 元数据块(namedescription等),只保留规则正文。
  4. 空内容即失败:规则文件是空的会直接抛错,避免"静默无规则"的会话。

💡 关键设计:loadRules()每个扩展生命周期只执行一次,结果存入闭包变量rules。之后无论注入多少次上下文,都不再重复读盘。

3️⃣ 三种打开方式:启动标志、斜杠命令、自然语言

扩展注册了三类用户入口(extensions/i-have-adhd.ts):

  • 启动标志pi --adhd:通过pi.registerFlag("adhd", ...)注册,让新会话默认开启;
  • 斜杠命令/i-have-adhd [on|off]:会话内切换,不带参数则翻转当前状态;
  • 自然语言input事件监听器拦截stop adhd modenormal mode两个短语,随时关闭。

另外还有一个细节:/skill:i-have-adhd这个内置技能命令会被扩展接管(extensions/i-have-adhd.ts),直接等价于开启模式并返回handled,防止 Pi 把同一份规则再展开第二遍。

状态变更统一走setEnabled():它把开关写入一条会话条目(类型为i-have-adhd-state),刷新底部状态栏的● ADHD ON标记,再调用syncContext()同步上下文。

4️⃣ 会话开始:restoreState 决定开关初值

session_startsession_tree事件都会触发restoreState(ctx)(extensions/i-have-adhd.ts),它的决策逻辑是:

  1. 查历史getSavedState()扫描当前会话分支的所有条目,取最近一条i-have-adhd-state——用户在上一段会话里说过"stop adhd mode",恢复后仍保持关闭;
  2. 定默认值pi.getFlag("adhd") === true(即pi --adhd启动)或~/.pi/agent/.i-have-adhd-always标志文件存在,则默认开启;
  3. 合并enabled = savedState ?? enabledByDefault——用户的显式选择永远压过默认值
  4. 落地:先updateStatus()刷新界面状态,再syncContext()把规则同步进对话。

5️⃣ syncContext 核心:规则只注入一次,而非每次重写

syncContext()(extensions/i-have-adhd.ts)是整个调用链的心脏,决策表非常克制:

模式 enabled规则已在上下文中动作
注入规则消息(隐藏)
什么都不做
注入"已禁用"通知
什么都不做

注入用的是pi.sendMessage({ customType: "i-have-adhd-rules", display: false }, { triggerTurn: false })——一条不显示、不触发模型回合的隐藏消息,安静地留在对话里,模型在之后的每个请求中都能"看到"它。

为什么"只注入一次"?代码注释里写得很直白:这与 Claude Code 的 SessionStart 钩子行为保持一致——注入一次规则集,而不是在每个请求前重写系统提示。省 token,也避免规则反复刷新干扰对话节奏。

关闭时也不是删消息,而是追加一条i-have-adhd-disabled通知:"忽略之前注入的 ADHD 规则,恢复默认风格"——用最新的标记覆盖旧规则,后文细讲。

6️⃣ context-compat 兼容层:一份代码跑两种运行时

"规则还在不在上下文里?"需要读取会话消息列表,但 Pi 和 OMP 的 sessionManager API 并不相同。兼容层 extensions/context-compat.ts 解决了这个问题:

  • OMP的 API 是buildSessionContext(),返回{ messages }
  • Pi的 API 是buildContextEntries(),直接返回条目数组。

contextMessages()(extensions/context-compat.ts)按顺序尝试两个 API,并且是故障放行(fail open)设计:sessionManager 缺失、API 不存在、甚至调用抛异常,一律返回空数组。后果是调用方认为"规则不在上下文",于是重新注入一次——重复注入无害(见下节),而让会话启动崩溃才是真事故。

真正裁决规则是否"生效"的是latestMarkerIsActive()(extensions/context-compat.ts):它按时间顺序扫描消息,只看最后一个标记

  • 先注入规则、后追加"禁用"通知 → 规则失效;
  • 先禁用、后重新开启(再注入规则)→ 规则生效;
  • 普通消息(非 custom 标记)一律忽略,不会误激活规则。

这套逻辑有专门的自检脚本 scripts/check_context_compat.ts 验证,用bun scripts/check_context_compat.ts运行即可。

7️⃣ session_compact:压缩之后规则"复活"

Agent 会话过长时会触发压缩(compaction),被总结掉的旧消息会从上下文里物理移除——之前注入的规则消息也可能就此消失。

扩展对session_compact事件的响应只有一行:再跑一次syncContext(ctx)。结合第 6 节的判断逻辑:规则没了就重新注入,还在就什么都不做。配合latestMarkerIsActive只看最新标记的特性,整条链对压缩、分支、会话恢复都是安全的。

8️⃣ 完整调用链总览

把上面的环节串起来,整条链是:

Pi / OMP 启动 └─► iHaveAdhdExtension(pi) ├─► loadRules() ← 读取 SKILL.md,剥离 frontmatter(一次性) └─► 注册 adhd 标志、/i-have-adhd 命令、input 事件监听 会话开始(session_start / session_tree) └─► restoreState(ctx) ├─► getSavedState() 从会话条目还原用户上次的开关选择 ├─► updateStatus() 底部状态栏显示 ● ADHD ON └─► syncContext(ctx) └─► rulesAreInContext(ctx) ├─► contextMessages() OMP: buildSessionContext / Pi: buildContextEntries(fail open) └─► latestMarkerIsActive() 只看最新标记裁决生效状态 └─► 未注入 → sendMessage(规则, 隐藏, 不触发回合) 用户切换(命令 / 自然语言) └─► setEnabled() → appendEntry(持久化) → updateStatus() → syncContext() 会话压缩(session_compact) └─► syncContext() 规则被压缩掉了?重新注入

三个值得借鉴的架构要点:

  1. 规则与代码分离——改规则只需编辑 skills/i-have-adhd/SKILL.md,扩展代码零改动;
  2. 一次性注入 + 标记裁决——省 token 且对压缩、分支天然安全;
  3. 故障放行兼容层——运行时 API 变了也不崩,最多重复注入一次。

📚 延伸阅读:各运行时安装与激活方式见 INSTALL.md,仓库整体地图见 AGENTS.md,always-on 钩子声明在 hooks/hooks.json。

【免费下载链接】i-have-adhdA skill to stop your coding agent from burying the answer. ADHD-friendly output.项目地址: https://gitcode.com/GitHub_Trending/ih/i-have-adhd

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询