@genee/omp-opsx-addon

Pi Extension: OpenSpec workflow orchestration - coder/reviewer/planner agents, session title & progress

Packages

Package details

extension

Install @genee/omp-opsx-addon from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@genee/omp-opsx-addon
Package
@genee/omp-opsx-addon
Version
0.9.0
Published
Sep 16, 2026
Downloads
1,108/mo · 519/wk
Author
iamfat
License
MIT
Types
extension
Size
899.2 KB
Dependencies
0 dependencies · 4 peers
Pi manifest JSON
{
  "extensions": [
    "./index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

omp-opsx-addon

Pi Extension: OpenSpec workflow orchestration — coder/reviewer/planner agents, session title & progress.


架构

┌─────────────────────────────────────────────────────────┐
│  主 agent (Orchestrator)                                 │
│  system prompt 由 plugin 注入: model 选择决策 + 路由表    │
│                                                          │
│  用户说 "@coder 实现 X" → transform 成 task 委派指令      │
└───────────────────────┬─────────────────────────────────┘
                        │
                ┌───────▼────────┐
                │  omp 内置 task  │
                │ (所有 role)     │
                │  原生 subagent  │
                └───────┬────────┘
                        │
               ┌────────┴──────────┐
               │ .omp/agents/      │
               │ coder / planner   │
               │ code-reviewer /   │
               │ proposal-reviewer │
               └─────────┬─────────┘
                         │ autoloadSkills
               ┌─────────▼─────────┐
               │ skills/           │
               │ opsx-orchestration│
               │ -protocol(随包) │
               └───────────────────┘

核心机制:主 agent 用 omp 内置 task 工具委派所有 4 个角色。model 选择由插件自动完成,不再有 acpx(外部 agent)路径。每个角色的有效指令集 = .omp/agents/*.md(角色专属模板)+ 随包技能 skills/opsx-orchestration-protocol(四角色共享编排协议,经 frontmatter autoloadSkills 注入)——详见下文「Agent 指令集」。


Agent 指令集:模板 + 协议技能

四个 dispatch agent 的有效指令集 = 角色专属模板.omp/agents/*.md)+ 共享协议技能(随包):

角色 模板承载的角色专属内容 autoloadSkills 注入的技能
coder 工作边界(不改提案文件 / 不二次委派 task / 通用编码请求免 scratchpad)、scoped 自验证门槛、ACTION 报告块 opsx-orchestration-protocol + openspec-apply-change两个
planner 提案拆分规则、Budget 估算、输出说明 opsx-orchestration-protocol + openspec-propose两个
code-reviewer 审查维度、全局验证三级定级(微改级 / 全流程级,Tier 1/2/3)、审查输出块 opsx-orchestration-protocol一个
proposal-reviewer 审查维度、提案审查输出块 opsx-orchestration-protocol一个

共享协议技能 skills/opsx-orchestration-protocol/SKILL.md 是四角色共享编排协议的单一真源:scratchpad 四区共享缓存与角色读写矩阵(append-only、planner 建+写 / coder 读+写 / 两个 reviewer 只读)、supersede 权威语义与两档客观判据(档一继续 + supersede 修正,档二 STATUS: blocked 交主 agent 裁决)、无关状态硬栅栏(节内首行角色门——仅 coder 适用,reviewer / planner / proposal-reviewer MUST NOT 受本节限制)、P0/P1 评审闭环与轮次上限(Loop 1 ≤2 轮、Loop 2 最多 3 轮含首次实现与修复)、报告契约、生效与兜底。模板不再复述这些正文,每处只留指向技能的一行指针。

  • 位置与发现:技能在包根 skills/,由宿主对扩展根的技能发现通道自动收录,无需安装动作——installAgents() 的写入集合仍只有 4 个 .omp/agents/*.md.addon-version,不落项目目录、不建 .omp/skills/,技能也不参与 MARKER / 版本 / conflict 状态机。
  • 生效方式:frontmatter autoloadSkills 在子 agent 首个 prompt 之前把技能正文注入其上下文,等价于 /skill:opsx-orchestration-protocol(coder / planner 另各注入一个官方技能)。
  • frontmattername / description / tools / autoloadSkills(+ 可选 model)。原 skill: 字段是宿主 agent frontmatter 白名单不解析的死配置,已移除。
  • 兜底autoloadSkills 解析失败是静默的,故每份模板对每个技能各留一行 skill://<name> 指针(Skill tool 手动读取,逐行同构、不合并);技能名不可解析且 skill://<name> 亦不可读时视为前提缺失,子 agent 输出 STATUS: blocked(无 SESSION)并报缺失技能名交由主 agent 决策,不凭记忆复述其流程。
  • 官方技能的边界openspec-apply-change / openspec-propose 正文面向主会话撰写;对子 agent 只有流程与产物定义适用(选变更、读 tasks/proposal/design/specs、逐 task 实现、- [ ]- [x]、阻塞时不猜测),announce 进度播报、「ask the user / 询问用户」、「pause / 等待用户输入」、progress / pause 报告块不适用(子 agent 无用户);需用户决策时走 STATUS: blocked 上报主 agent。该边界由协议技能单点声明,官方技能文件本身不改动。
  • 升级路径:技能文件与生成模板的代码同处一个 npm 包版本(.npmignore 未排除 skills/),升级插件即同时在场,不存在「模板已瘦身而技能未就位」的中间态;存量 .omp/agents/*.md 仍由既有 installAgents 状态机处理——含 MARKER 的判 updated 并被覆盖,不含 MARKER 的判 conflict 且不覆盖。

Dispatch 配置 (opsx.yml)

两层 opsx.yml(全局 ~/.omp/agent/opsx.yml + 项目 <cwd>/.omp/opsx.yml,项目逐键覆盖全局)。

# agents.* 的值可以是:
#   'auto'                       — 插件按 tier + usage 自动选 model
#   'smol' / 'slow' / 'default'  — OMP role alias(由 OMP modelRoles 解析)
#   'gpt-5-pro'                  — 明确 model id
#   'anthropic/claude-opus-4-6'  — provider/model 完全限定

agents:
  coder: auto
  reviewer: slow
  planner: slow
  proposal-reviewer: default

# 可选:限制自动选择时使用的 model 池
model_allowlist:
  - "anthropic/claude-opus-*"
  - "openai/gpt-5*"

# 可选:自定义 tier 评分
tiers:
  - pattern: "*opus*"
    tier: top
  - pattern: "openai/gpt-4o-mini"
    tier: low

# 可选:dispatch agent → tier 期望
agent_tiers:
  planner: top

# 可选:OMP model role → tier 重定向(或 skip 排除)。
# 写入作用域 = 最小写入集 {smol, default, slow, vision}:合法 tier 值重定向该 role 的
# 写入档位(如 slow: high);skip 使该 role 不写覆盖。写入集之外 role 的条目对写入
# 惰化(保留解析校验);skip 在非写入 role 上等价 no-op。
model_role_tiers:
  slow: high     # 例:slow 改写 high 档选中(默认 top 档补选)
  vision: skip   # 不覆盖 vision(vision 只经能力门控写入,仅 skip 有效)

# 可选:月计划覆盖的 provider(追加到内置默认集;条目规范化、去重)
plan_providers:
  - ArkCodingPlan

# 可选:从生效 plan 集合剔除((默认集 ∪ plan_providers) − plan_providers_remove)
plan_providers_remove:
  - cursor

# 可选:PAYG 兜底末位(缺省 true);false 折叠覆盖类别键,PAYG 回到常规排序
payg_last_resort: true

# 可选:会话启动自动应用一次选择(缺省 true);false 时新会话不自动应用,
# 行为与本键引入前一致(选择待手动 /pick-model 应用)
autoselect_on_start: true

旧键已废弃(仍生效):顶层的 coder: / reviewer: / planner: / proposal-reviewer:role_tiers: 是上面的旧写法,仍照常生效,但读到时会按层文件各 warn 一条(单文件多个旧键合并为 一条、列出全部旧键名),并计划一个版本周期后移除。迁移:顶层 role 键挪进 agents: mapping、 role_tiers: 改名 agent_tiers:(schema 不变)。同一文件内新旧同现时新键胜出agents.* > 顶层旧键、agent_tiers > role_tiers);跨层合并语义不变(agent 值逐键覆盖、tier 表整键覆盖)。

agents / agent_tiersmodel_role_tiers 消歧义

两者都是 opsx.yml 里的映射键、形似「role → tier」,但管理的对象与生效层完全不同:

agents / agent_tiers model_role_tiers
管的对象 opsx 插件的 4 个 dispatch agent(coder / reviewer / planner / proposal-reviewer,落 .omp/agents/*.md、经 task 工具委派) OMP 宿主的 model role(10 个内置 default/smol/slow/vision/plan/designer/commit/tiny/task/advisor + 自定义 role)
键空间 固定 4 个 agent 名(未知子键 warn 丢弃) 任意 OMP role 名(内置 + 自定义,宽松键空间)
agents.*:role 值(auto / OMP alias / model id / provider-model);agent_tiers:tier 名 tier 名或 skip
生效层 插件委派 dispatch agent 时的 model 决策(agent → 用哪个 model) /pick-model 后写入 session 覆盖(overrideModelRoles)的 role → model 映射

model_role_tiersagents / agent_tiers 互不兜底、互不影响:前者不参与 dispatch agent 的 model 选择,后者也不影响 session 覆盖写入哪些 role。

覆盖类别与 PAYG 兜底(自动选择排序首位)

自动选择先按覆盖类别硬分区、再比 tier:① 月计划覆盖 → ② 其它可用 → ③ 按量计费(PAYG)兜底。 ③类仅在①②类无可用候选时参与选择(有别的用,就不用 PAYG)。/pick-model <selector> 收窄池内仍按此 排序、仅剩③类时正常选中;pinned role 不经选择器,分区不影响显式钉值。

  • 归类通用、代码零写死plan_providers 成员(规范化后)→ ①类;quota policy 为 balanceREQUIRED_WINDOWS 注册表,如 deepseek)→ ③类;其余 → ②类。未在 REQUIRED_WINDOWS 登记 的按量计费 provider 落②类,登记即入③类——注册表是按量计费的既有登记扩展点。
  • plan_providers:追加语义,条目规范化(ArkCodingPlan / arkcodingplan 同源到 ark-coding-plan)、去重,非法条目 warn 丢弃。
  • plan_providers_remove:从生效集合剔除默认或追加的成员,条目同样规范化。
  • payg_last_resort:缺省 true,③类保持兜底末位;设为 false 折叠整个覆盖类别键—— ③类回到常规排序(可按 tierGap 胜出),同时取消①类 plan 相对②类 standard 的位次偏好 (类别键整体失效,选择回到 tier / 用量 / 价格轴)。

Model 选择

插件启动时从 ModelRegistry.getAvailable() 拿所有有鉴权的 model,从 authStorage.fetchUsageReports() 拉用量报告,然后按 role → tier 期望 + 用量健康度自动选最优 model。

决策日志打印到 stderr:[omp-opsx-addon] planner → anthropic/claude-opus-4-6 (tier=top, gap=0, remaining=0.80)

会话启动自动应用一次选择(autoselect_on_start,缺省 true

配好 china / model_allowlist / tier 期望后,新会话不再等手动命令:session_start 时插件自动应用一次当前 选择——经 ensureSelection 计算(china/allowlist 约束照常生效),按与 /pick-model 相同的应用路径写入 modelRoles(最小写入集 {smol, default, slow, vision} + per-agent 运行时中和),并把主会话模型设为 reviewer pick(high 档选中模型;与当前模型一致则不调用 setModel)。应用在首 turn 前 await 完成、每个会话 实例只应用一次,之后用户显式 /model 切换保持权威——启动路径不会再次触发、不回写主会话模型。pinned role (agents.* 显式钉值)照常优先于自动 pick(钉值旁路选择器,不被自动写入覆盖)。启动应用路径的任何错误 (选择计算抛错、settings 不可用、setModel 失败)只 warn([omp-opsx-addon] session-start autoselect error: ...),不阻塞会话启动。autoselect_on_start: false(任一层 opsx.yml)完全退出,行为与本键引入前一致。 手动 /pick-model 命令照常可用、立即生效;task 子 agent 不触发启动应用。

/pick-model:selector 约束与 role 定向([roleList:]

语法:/pick-model [roleList:]<selector> [--china];子命令 choices / update / refresh / reset 不变。 无前缀 = 全局约束(全部 role 一起收窄,行为与引入 role 定向之前完全一致);/pick-model reset 清除约束(含 role 定向),一切重算路径(refresh / 约束期 tick / ensureSelection)都遵守当前约束。

roleList 词表(逗号分隔、大小写不敏感,与宿主 MODEL_ROLE_IDS 同源、零硬编码):

  • 内置 model role 10 个:default / smol / slow / vision / plan / designer / commit / tiny / task / advisor
  • opsx agent 名作糖:coder→smolreviewer / code-reviewer / planner / proposal-reviewerdefault(解析期映射为 canonical model role);
  • all:等价无前缀的全局约束。

示例:/pick-model smol:glm*(只收窄 smol)、/pick-model smol,default:zhipu*/glm* --china/pick-model coder:glm qwen --china

粒度 = tier 槽位:约束把 scope role 集映射到的每个 tier 槽位(mid/high 槽位、top 补选与 vision 能力候选集)切到约束池,其余槽位照常走全池。映射到同槽位的其它 role 自然随动——默认映射下 tasksmol 同为 mid,/pick-model smol:glm* 换掉 smol 写入后 task 经其枚举回退链 (["tiny","smol"] 等)跟着解析到新 mid 模型;这不是 bug,是 OMP 原生链的必然结果,命令输出会标注 scope(如 scope=smol)使其可见。model_role_tiers 参与 scope→槽位解析(与写入集同一张有效映射表 OMP_ROLE_TO_TIER);有效档位解析为 skip 的 scope role(如配了 model_role_tiers: { task: skip } 再执行 /pick-model task:glm*)直接报用法错误并指名该 role——skip 的 role 不落任何槽位,池约束对它 不可表达。vision 只出现在 scope 时仅约束其图像能力候选集,不收窄 high 槽位。

scoped pinned 交互:定向约束只把 scope 内 role 置 auto(显式覆盖 pinned);scope 外 role 保留 agents: 配置值(pinned 继续生效、omp-alias 语义不变),不经任何重算路径进入约束池,也不触发空选 回滚;reset 后全部恢复 opsx.yml 语义。agents: / agent_tiers:agent-config-rename 改名后的 键名(改名前为顶层 coder: / code-reviewer: / planner: / proposal-reviewer: 四键与 role_tiers:,语义相同,旧键仍被兼容读取)。

冒号是保留字符:裸词匹配按非字母数字分词,含冒号的 token 若回落 selector 会被拆成多个子词按 OR 静默施加全局约束。因此含 : 但不是 roleList 前缀的 token(如 zhipu:glmzhipu:glm*、拼错的 smlo:glm,不论是否带 *、位于命令中何处)一律报用法错误,并把合法词表全文内联在错误信息里—— 不会静默施加任何约束。

choices 展示:紧凑行式输出(见下文「/pick-model 输出格式」),状态行携带约束段,如 当前选择 · constraint: selectors=glm* · scope=smol,default(scope 段为解析后的 canonical model role;全局约束无 scope 段)。

china 配置键(缺省 false:opsx.yml 顶层布尔键,为 trueCHINA_EXCLUDE_FAMILIES (gpt/claude/openai/anthropic 家族,裸词 token 双侧匹配,语义与 --china 一致)成为一切选择 路径的缺省排除——无约束的默认选择(ensureSelection)、refreshreset 之后的重选、selector 约束池全部生效,无需每次敲 --china。优先级为命令显式 > 配置缺省:显式 --china 写入 session 约束(约束期内持续,fingerprint 化),省略旗标时配置缺省层兜底;china: false 或缺省键 = 与引入前 完全一致(仅显式 --china 生效)。关断手段 = 改配置(无 --no-china 旗标);改 opsx.yml 后新会话 生效(配置 session 启动读一次,无热加载)。scoped(role 定向)约束下缺省排除只作用于约束池 (scope 内槽位);scope 外槽位保持不含 china 排除的全池(显式 scope 划分 > 缺省叠加)。 配置为 true 时 choices/报告头部显示 china=on(无 selector 约束时也显示,如 constraint: china=on);reset 只清 session 约束、不清配置缺省。两层合并(project 逐键覆盖 global):project 显式 china: false 压过 global china: true;非法值 warn 后回落 false

同账号区域变体去重(zai ↔ zhipu-coding-plan)

zaizhipu-coding-plan 是智谱同一账号的两个区域入口。两者同时在候选池且两扇门解析出的 API key 严格相等(同账号凭据证明)时,插件按区域偏好收敛到一侧,池与展示都不再重复;key 不同、缺失 或解析失败则保守并存(两扇门都保留、usage 栏两行都显示,靠「智谱 / Z.AI」标签区分)。同账号 (key 相等)才收窄;多 key 同账号场景请保持单一 key(两扇门配同一把 key)以获得收窄,或接受双行 显示。两扇门共享同一份额度,收窄只影响走哪扇门,不损失额度:

  • 常量表:内置单行 [{ domestic: 'zhipu-coding-plan', intl: 'zai' }]lib/provider-variants.tsREGION_VARIANT_GROUPS)。刻意不建通用配置表;未来出现新的区域入口对时扩展该常量即可。
  • 判定与前提:双侧在池且 key 严格相等才收窄(key 相等 = 同账号证明;key 不等/缺失/解析失败 = 保守并存);池中只剩单侧(allowlist/reachability/静态排除/provider_models 已滤掉一侧)= 零 行为。收窄方向由 china 驱动:china: true 保留 zhipu-coding-plan、收起 zaichina: false/缺省保留 zai、收起 zhipu-coding-plan
  • provider 级整体收窄:被收侧整个 provider 退出候选池(不参与评分),其独有模型一并让位—— 同账号一个入口足够。收窄作用于账号级基础池(与 selector 约束正交,scoped 约束的 scope 外槽位同样 去重);池构成或 key 状态变化(可达性/allowlist/静态排除翻转、重新登录换发 key 使收窄集翻转)纳入 selectionHealthKey 指纹,正确失效重算缓存。
  • 旁路:pinned role(agents: 钉值)与显式 selector 不受去重影响——显式指令 > 去重。显式 selector 指定被收侧 provider 时该侧已不在池中,走既有「无命中」错误路径(provider 计数中不含被收 侧);出路 = 改 china 或调整 allowlist。
  • 诊断:去重激活时 choices 附录追加标注行,如 **区域变体** zai → 已收起(同账号区域变体,已按区域偏好收起;保留 zhipu-coding-plan)zai 的 family 标签显示为 Z.AI(显示名常量映射),与 zhipu-coding-plan 恒可区分。
  • usage 栏同判定收窄:usage 栏的 provider 行应用同一收窄判定(含 key 严格相等门;在场的判定 = 有 usage 数据或凭据即可,不要求模型候选)——收窄激活时 china: true 不渲染 zai 行;缺省 /china: false 不渲染 zhipu-coding-plan 行、zai 行显示 Z.AI。key 不等/缺失时不收窄,两行 都渲染、以「智谱 / Z.AI」标签区分。两扇门共享同一份额度,保留侧行即权威展示,信息不丢;单侧在场 照常渲染。后台状态探测不停探被收侧(可达性记录保持新鲜)。
  • 与 model_allowlist 的叠加:allowlist 是 provider 全串 glob 白名单(未列出即排除),china 是 家族黑名单,两者叠加生效。注意区域偏好切换时 allowlist 不会自动映射——想让两个区域入口都 可用就把两区域条目都写上(如 zhipu-coding-plan/* 之外补 zai/glm-5.3);若 allowlist 只列了 单侧条目,另一侧根本不进池、去重不触发(只写 zhipu-coding-plan 条目的现有配置即此形态,行为 不变)。国内用户「不要家族排除但要国内门」(china=false 偏好国内入口)也可用单侧 allowlist 化解。
  • provider_models(逐 provider 模型约束)的关系:两者同为池过滤阶段的合取输入、互不替代; 变体去重收窄整个 provider,provider_models 收窄 provider 内的模型集合,同配时按交集生效(见下节)。

per-provider 模型目标(provider_auto / provider_auto_tiers / provider_models

三个 provider 键控的顶层键(键均经规范化,ArkCodingPlan / arkcodingplanark-coding-plan 同源收敛),共同表达「每个 provider 的模型走什么」:

# 服务端路由哨兵:provider → 该家「Auto」目录模型 id(须与 catalog id 一致,
# 大小写不敏感)。命中的候选豁免本地启发式评分,按声明档位参选。
provider_auto:
  ark-coding-plan: ark-code-latest
  cursor: default

# 哨兵声明档位(tiny|low|mid|high|top);非法值 warn 丢弃,未声明的哨兵按 mid。
provider_auto_tiers:
  ark-coding-plan: top
  cursor: top

# 逐 provider 模型约束:列出的 provider 收窄到命中模型,未列出的 provider 不受限。
# 裸词按 id 全串锚定(`deepseek-flash` 精确命中,不会过匹配 deepseek-v4-flash);
# `*` 通配族(`glm-5.3*` 命中 5.3 族);含 `/` 的模式按 selector 三级语法求值。
provider_models:
  deepseek: [deepseek-flash]
  zhipu-coding-plan: [glm-5.3, glm-5.3-flash]
  • 哨兵语义:catalog 的全 0 成本不构成比价优势——哨兵在同覆盖类、同档内让位于一切有真实 成本的候选,仅在对手耗尽或无竞争时胜出(备份定位);声明档位是哨兵的合法竞争力(如 cursor/default 声明 top 后,对启发式落档更低的 cursor 具体模型形成 tierGap 优势)。哨兵豁免 per-(provider, tier) 去重(无版本号 id 否则必被驱逐),但不豁免覆盖类别分区、配额(含 cursor bucket)、可达性排除与 allowlist。选中哨兵时 pickedReason / decision log 标注 auto 与 declared tier。role pin 到哨兵(如 agents: {coder: ark-coding-plan/ark-code-latest}) 走既有 pinned 路径直接生效。
  • **「provider 只走哨兵」**用两键组合表达:provider_models: {ark-coding-plan: [ark-code-latest], cursor: [default]}——收窄后该 provider 池内只剩哨兵。没有「provider 内哨兵优先于具体模型」的 隐式规则;想要这个效果就显式收窄。
  • provider_models vs model_allowlistprovider_models追加快照式收窄——只约束列出 的 provider,未列出的全量参选;model_allowlist全局白名单——未列出全排除。2026-09-12 撤销全局 model_allowlist 的反例:白名单无法表达「deepseek 只用 flash、zhipu 只用 5.3 族、 其余 provider 不受限」,会误伤 opencode-go 等未列 provider。两者可并存(合取)。
  • 两层合并陷阱:三键与 concurrency_ceiling_by_provider 同为整键覆盖——项目层 .omp/opsx.yml 声明任一键即完整替换全局同名键(不做 per-key 叠加);只想在项目层追加 provider 时,把全局条目一并抄过来。
  • cursor 区域风险:cursor 区域外会返回 Model not available in your region(不可重试,可达性 探测发现不了);excluded_providers 仍是唯一的用户级排除开关,撞墙自行加回 (如 excluded_providers: [cursor])。
  • 哨兵 id 漂移:provider 改名其 Auto 目录条目后声明静默失效(哨兵回落普通启发式评分,无崩溃); 失效时启动/重算日志会 warn 一行指明该 provider 的哨兵 id 不在存活候选中。

速度感知选择(speed_aware

自动选择排序中预留的 speed 槽位由此键驱动。数据源是宿主 ~/.omp/agent/agent.dbmodel_perf 表(只读:readonly 打开、整表一次 SELECT、零写入、零后台任务——仅在选择路径事件驱动读取, 模块级 TTL 缓存 300s;读失败静默回退空索引并以同 TTL 负缓存,不污染选择路径日志)。

# 速度感知选择。enabled 缺省 true(缺数据时零漂移,开启无风险面);
# mode 缺省 tie-break;min_samples 缺省 20(低于门槛的行视为未测度)。
speed_aware:
  enabled: true
  mode: tie-break   # tie-break | aggressive
  min_samples: 20
  • 排序语义(tie-break,缺省):cost band 量化进既有的 cost 槽位(槽位次序不动)—— band(cost) = max(0, floor(log2(cost)) + 1)(cost > 0),边界为 2 的幂(band 1 = [$1,$2)…); 免费与缺价、以及 clamp 收编的 <$1 正价同落 band 0,带内按 cost 原值升序(免费先于一切正价)。 band 对 cost 全域单调,故跨带候选的相对序与纯 cost 序一致(quota-aware 基线零回归)。 同带候选再比实测 tok/s(sum(output_tokens)/sum(gen_ms)×1000)——band 即「同样经费」的 操作化定义(ratio-2 ≈ 同一价格档);speed 相等回落 cost 原值。

  • aggressive(opt-in):speedKey 提到 band 之前——同覆盖类别、同 tier 轴内速度越过价带 (快而贵胜过慢而便宜)。类别分区与 tier 位次仍然不动。风险:系统性偏向高价快模型,故缺省关闭。

  • 未测度候选(无数据 / 样本 < min_samples / provider_auto 哨兵)speedKey 恒为 -Infinity: 带内排在已测度候选之后,其相互之间仍按 band/cost 定序。选择器不做探索——想试新模型走 显式 role pin 或 /pick-model <selector>;20 样本门槛在正常流量下数小时即达成。

  • 零漂移保证enabled: false 时装配层零 IO(不打开 agent.db),速度索引为空时 band 退化 为 cost 原值序、speed 恒等——全序与引入本特性之前逐字节一致。

  • 可观测:选中候选已测度时,其 pickedReason / decision log 追加 speed=<tok/s>tok/s 段 (数据层标注;/pick-model 渲染层不展示)。手工核查数据源:

    sqlite3 ~/.omp/agent/agent.db 'SELECT model_key, samples,
           output_tokens / gen_ms * 1000 AS tok_s,
           CASE WHEN ttft_samples > 0 THEN ttft_ms / ttft_samples END AS ttft_ms
           FROM model_perf ORDER BY tok_s DESC LIMIT 10;'
    
  • 两层合并speed_aware 为对象键,与 provider_* 键同款整键覆盖——项目层声明即整份 替换全局,不做字段级叠加。

难度路由与质量带(difficulty_routing / stall_escalation / continuity_guard / quality_preference

「分类在前、选择在后」的结构改造(纲领 W4,全键 opt-in):硬约束过滤(既有池过滤,零改动)→ prompt 难度分档 → 带内选择(W1-W3 冻结全序在偏移后目标下照常运行)→ stall 升档与连续性守门 → 质量带旋钮。

# 三开关全部缺省 false(结构改造 opt-in,W1-W3 的收益不依赖它们);
# quality_preference 缺省 balanced(独立于三开关的顶层旋钮)。
difficulty_routing:
  enabled: false
stall_escalation:
  enabled: false
  window: 6        # 滑窗大小(LiteLLM 同款)
  threshold: 3     # 同签名重复阈值(锚定最新一次调用)
continuity_guard:
  enabled: false
  weight: 0.6      # 换模所需最低分类置信,(0,1],越高越保守
quality_preference: balanced   # balanced(缺省,逐字节现状)| cost | quality
  • 难度分档 = 写入集 tier 目标偏移:纯函数 classifyTask(确定性、无 LLM、无状态)按启发式 信号族加权打分(推理标记 / 代码存在 / 技术术语 / 简单指令负向 / 多步模式 / 疑问复杂度 / 长度对数项),边界 0.15 / 0.35 / 0.60 映射 simple / standard / complex / reasoning;偏移表 simple −1 / standard 0 / complex +1 / reasoning +2(clamp [tiny, top]),只作用于写入集 {smol, default, slow, vision}——非写入集 role 的目标恒不触碰(不复活全量覆盖)。分档是 选择输入的预修正:偏移后目标的 gap-0 组即「带内」,组内仍由冻结链既有键(成本/速度)决胜。
  • 失败语义 = 安全侧强档:分类器异常或置信低于地板(0.35)一律落 complex——绝不因分类失败 或歧义静默降到最便宜档(反向规避 LiteLLM「未命中得 0.0 → 默认最便宜」的已证失效模式)。 plan-mode 感知基于 prompt 文本标记(宿主 plan-mode 状态对插件不可见)。
  • stall 升档:会话域滑窗记录工具调用签名(toolName + 键排序稳定序列化(args),长串截断、 序列化失败回退裸签名);锚定最新一次调用的签名在窗内重复 ≥ threshold → stall 生效, default role 目标 +1(clamp top),单 episode 只升一档、绝不自动降档;签名变化结束 episode (其后目标由分类常规接管,非降档动作)。数据源 = tool_execution_start 事件(主源)+ sessionManager.getEntries() 只读转录回溯(降级源);两源皆不可得 → 惰化(无 stall 状态、 不升档、零 warn),维持现状即安全侧。采样只在主实例生效,子 agent binding 不采样。
  • 连续性守门:自动重算点上,写入集 role 的在选模型(incumbent)仍在候选池时钉住不复换guard=pin 标注),速度/价格 tie-break 压不过钉住。换模仅当:①新 band 严格升级且分类置信 ≥ weight;②stall 升档生效;③incumbent 被硬过滤出局(可用性优先)。incumbent band 未知时以 standard 中性档参与比较(simple 不触发升级换模)。显式命令(/pick-model refresh / reset / selector / choices、role-scope)绕过守门且不做新分类——显式指令 > 守门;重算沿用当前会话级 band。
  • quality_preference 三态(顶层独立键,路由关/开均可独立生效):
    • balanced(缺省):既有冻结全序逐字节执行——零漂移的结构保证是「不进入」旋钮分支;
    • cost:tier 轴槽位内部容差带量化(槽位不移动)——同覆盖类内以最小有效 tier 轴值为锚, relGap ≤ 1(容差常量 T=1,不可配置)为带 0;带 0 内去轴化(tierGap/surge/rank 不参与, 成本/速度决胜,Azure「带内选最便宜」的字面落地,代价是带内放弃档位/声誉区分——知情选择); 带 1 内全链自洽,带 0 恒先于带 1(跨带质量序保持);provider_auto 哨兵按声明档参与量化(无特判);
    • quality:质量分前缀分层 (覆盖类, tier 轴, rank, reputation)——成本/速度无法越过一个轴位或 声誉差距(Azure Quality「无视成本」);哨兵 reputation=0 自然让位已知声誉模型(无特判)。
  • T4 边界:路由重算只更新选择缓存(choices / dispatch 上下文 / decision 消费面),不新增 applyRoleModel / setModel 调用点——无任何会话中途模型身份切换;role 模型应用仍只发生在既有 应用点(显式命令、autoselect 会话启动)。stall 采样与守门均为只读观测/选择后处理。
  • 零漂移与关闭保证:三开关全 false + balanced 时,路由段整体跳过、偏移不施加、守门不进入、 比较器走既有分支——选择输出、事件订阅副作用与 W3 落地态逐字节一致(回归用例逐字节断言)。 逐键开启独立生效、互不依赖;关闭任一开关即回到关闭前行为。
  • 可观测(数据层):路由启用且命中时 pickedReason / decision 追加 band=<band>stall+1guard=pin 标注段;未命中/关闭时与既有格式逐字节一致。choices/widget 渲染层零编辑(标注仅 数据层可得,经 dispatch 提示的「原因」行可见)。
  • 两层合并difficulty_routing / stall_escalation / continuity_guard 为对象键,与 speed_aware 同款整键覆盖quality_preference 为标量键同 ?? 惯例。

子 agent 模型落点:session-scoped modelRoles(最小写入)

/pick-model <selector> / refresh / reset 应用选择结果时,插件写 OMP session 运行时 覆盖 settings.overrideModelRoles,但只写最小写入集 {smol, default, slow, vision}。 本节取代已归档 pick-model-batch-all-roles 的「覆盖宿主全部已知 role(10 内置 + 自定义)」 语义——其动机(scout 等内置 agent 跟随切换)经宿主源码实证由 OMP 原生解析链覆盖,全量写入 属过度供给:

  • smol ← mid 档选中(coder pick)、default ← high 档选中(reviewer pick)、 slow ← top 档补选(唯一保留的 supplemental 补选)、vision ← 能力门控 pick(见下)。

  • 写入集之外的 role 一律不写,由 OMP 原生解析链接管:

    role 不写时的解析去向
    designer 经 default 继承(继承集合 {smol, slow, designer})→ high 档 pick(与原显式写入等价)
    task @task 未配置时回落主会话模型(主会话已被设为 high pick)
    commit / tiny 消费方走枚举回退链 ["commit","smol",…] / ["tiny","commit","smol"] / ["tiny","smol"],终止于 smol 写入(mid 档)
    plan 未配置时 plan mode 优雅保持当前模型(no-op)
    advisor 静态 slow priority 链(固定候选表,不读写入、不继承 default)
    自定义 role config 层原样保留,插件不再接管(也不再产生兜底告警)
  • PAYG 红线(收窄口径):存在①类(月计划)候选时,写入集四 role 以及经写入/继承/枚举 终止的原生链(designer/task/commit/tiny)的最终解析不会落入 PAYG——四 role 的 pick 全部经覆盖类别分区排序产出,vision 能力门控写入同时防住 inspect_image 原生「任一图像模型」 兜底打到 PAYG 图像模型。advisor 静态链是明确的 opt-in 豁免(OMP 原生行为)。如需让 advisor 恢复跟随选择,在 OMP config 配 modelRoles.advisor: "@slow" 自钉(复用写入集 slow 链随切换;model_role_tiers 只能重定向所复用写入 role 的档位,无法让 advisor 本身 被写入);plan 同理可经 modelRoles.plan 自钉恢复跟随。

  • model_role_tiers(opsx.yml)作用域:写入集四 role 的档位重定向(如 slow: high) 与 skip 哨兵(该 role 不写覆盖、保留你的 config 值、不产生告警)。写入集之外 role 的 条目对写入惰化(解析校验与非法值告警保留;skip 在非写入 role 上等价 no-op)。 例外:vision role 固定按 high 档在图像模型中挑选model_role_tiers.visionskip 生效——其他 tier 值被静默接受但不改选档。

  • vision 能力门控:vision 候选只从声明 inputimage 的模型中挑选;当前选择里没有 图像模型时,vision 不写覆盖(warn 汇总为 vision(capability),绝不写入纯文本模型), vision 功能回落 config/default。恢复手段:换一个含图像模型的 selector 或用 model_allowlist 纳入图像模型;改 model_role_tiers.vision 的 tier 绕不过门控、也不改选档。

  • auto role 的 agent 定义.omp/agents/*.md frontmatter 注入带引号的 tier alias (coder model: "@smol",其余三者 model: "@default");task 工具经 modelRoles 解析。 frontmatter 另含 autoloadSkills(见「Agent 指令集」)——原 skill: 字段是宿主不解析的 死配置,已移除。

  • 因此所有经 role 解析的 agent 都跟随切换——包括插件未枚举的 OMP 内置/bundled/ project agent:scout(@smol)直读 smol 写入;commit/title/classifier 经枚举链落 smol; designer/@slow 经 slow 写入或 default 继承;task 继承主会话。

  • auto role 与 omp-alias(如 slow,值无 /)的 dispatch 提示词不传 model=, 由 frontmatter alias 声明式接管;pinned 具体 provider/model 仍显式传 model,行为不变。

  • top 补选没有可用模型(候选耗尽/allowlist 全过滤)时,slow 不写覆盖、warn 汇总 (no-pick),命令不中断;无任何模型时计划为空,与既有「模型注册表不可用」分支一致。

  • 覆盖为 session 作用域、不写盘overrideModelRoles 只写 runtime overlay, session 结束即还原;不会修改你的 ~/.omp/agent/config.yml。每次应用前先 clearOverride('modelRoles'),避免上一 selector 的旧 role 值残留;reset 重算无约束 默认并覆盖(不清空,否则回落 config 硬钉)。

手动清理建议:若全局 ~/.omp/agent/config.yml 里有 modelRoles.smol 钉在会耗尽的 套餐模型(scout 会一直打它直到被 session 覆盖压过),或 task.agentModelOverrides 残留 已删除的 tester 等 key,可手动删除这些钉值;插件运行期间会以 session 空串中和 per-agent 钉值,但不改动你的配置文件。


/pick-model 输出格式(pick-model-ux)

四种命令(choices / selector 切换 / refresh / reset)与错误路径共用一套紧凑行式渲染: 状态行(符号 + 动作短语 + 约束段)→ 明细区(变更行,或 role 行 + agent 清单)→ 候选概览 → 脚注。无 markdown 表格、无 emoji、无分割线。符号每次渲染时读宿主 symbolPreset 设置 (unicode/nerd/ascii,缺省或非法回退 unicode),从 OMP SYMBOL_PRESETS 字形表按 SymbolKey 解析,插件零硬编码字形。内部量在展示层翻译为人话:覆盖类别 → 计划内 / 按量直连 / 按量兜底;tier → 轻量/低档/中档/高档/旗舰;role 配置 → 已指定/自动/ role 链tier=/gap=/class= 等原始字段不再出现在输出中(数据层 pickedReason/ decision 原样保留)。

unicode preset 下 choices 实拍(合成数据):

ⓘ 当前选择 · constraint: selectors=glm* · scope=smol

主会话 zhipu-coding-plan/glm-5.3 — 计划内 · 旗舰
coder → zhipu-coding-plan/glm-5.3-flash — 计划内 · 中档 · 自动
code-reviewer → zhipu-coding-plan/glm-5.2 — 计划内 · 高档 · 自动
planner → zhipu-coding-plan/glm-5.2 — 计划内 · 高档 · 自动
proposal-reviewer → zhipu-coding-plan/glm-5.2 — 计划内 · 高档 · 自动

── agent 清单(10)──
项目
  ⏳ code-reviewer — 继承主会话
  ⏳ coder — 跟随切换 · role 链 @smol
  ⏳ designer — 跟随切换 · role 链 @designer(经 default 继承)
  ⏳ task — 继承主会话(@task 未配置回落主会话)
  ○ researcher — 固定 · 钉值 anthropic/claude-opus-4-6
内置
  ○ commit-title — OMP 自动链 @tiny(静态 priority 模式)
  ⏳ scout — 跟随切换 · role 链 @smol

── 候选 ──
deepseek [按量兜底]: 中档→deepseek-v4-flash
zhipu-coding-plan [计划内]: 旗舰→glm-5.3  高档→glm-5.2  中档→glm-5.3-flash

4 个候选参与评分 · 分区: 计划内 3 · 按量直连 0 · 按量兜底 1(兜底未参与)
/pick-model refresh 切换耗尽模型 · /pick-model update 更新 tier 数据

symbolPreset: nerd 时同一结构,状态符号换 Nerd Font 字形(如 status.info → U+F129 等 PUA 码位,普通日志里显示为空白)。切换 / 刷新 / 重置输出同构,状态行分别为 ⓘ 已切换 selector 约束 (selectors=… · scope=…) / ⓘ 已刷新 / ⓘ 已清除 selector 约束,恢复默认选择,变更行形如 主会话 → provider/id(计划内 · 高档)coder → provider/id(计划内 · 中档 · 自动); 跨 provider 歧义提示以脚注保留(提示: glm* 命中 zhipu-coding-plan(12) deepseek(3);用 zhipu-coding-plan*/… 可锁定单一 provider)。

全量 agent 清单:数据源为宿主 discoverAgents(project .omp/agents(含本插件 installAgents 落盘的 4 个 agent)→ user → 扩展/插件 → bundled 全量合并,沿用宿主优先级, 插件不自扫目录),按 项目 / 用户 / 内置 分组、组内按名排序。每行标注模型解析来源: 缺省 / @default / @task(未配置时)→ 继承主会话;@smol / @slow → role 链(跟随 切换);@designer → role 链(经 default 继承);@tiny / @advisor(未配置时)→ OMP 自动链(静态 priority 模式);字面 pattern → 钉值;其他自定义 @role → 自定义 role。 config modelRoles.* 自钉优先于清单标注(「未配置时」的分类才适用)。discoverAgents 失败 时降级为省略该段并 warn,不阻断命令。

开发

npm run typecheck
bun test