Skip to content

[REQ-125][CODE] 斜杠命令 chip 增加技能/MCP 类型分型(橙技能/紫 MCP 变体) #582

Description

@jinjunnn

⚠️ 2026-08-12 订正块(只读对帧勘破) —— 本票正文写于 2026-07-24,以下带 划掉 的部分
已被推翻或已过期,以本块与下方「对帧订正」评论为准;未划掉的部分仍然成立。
推翻它的事实:活稿 2026-08-10 改过(新增已批基线 docs/design/2026-08-08-req125-tool-card-provenance/),
且 #879/#587 已合入 —— 工具卡落点从「按 kind 分支」变为「按 dispatch 分支」
(唯一入口 toolCardDispatchOf → resolveToolCardDispatch,新分支必须写在 metadataOnly 短路之后)。
7-24 的截图证据不能再当 before/after(#587 给每张卡加了常驻来源徽标与折叠开发者详情)。

本票特有:缺的不是样式,是数据。TimelineSlashOrigin(timeline-model.ts:118-123)
只有 {assistantMessageID?, command, arguments?},没有任何类型或来源字段;而 2026-08-09 基线 §6/T3
明令来源标记只读持久化的 identity 与 authority、禁止从名字反推 ⇒ 不能靠解析命令字符串猜。
本票的锚仍权威、E3/E4 旧图仍可用,但开工前须先定「捕获侧持久化类型/来源」的归属。
↑ 这一段(我 08-12 写的)被 08-13 的数据面勘破推翻了,见下一块。锚仍权威、E3/E4 旧图仍可用那两句仍成立。

⚠️ 2026-08-13 订正块(数据面归属勘破) —— 「缺数据」不成立,数据面已经存在而且就在手里。
这是 #620 的同一形态:洞其实不存在,上游早给了能力,只是我们拿到之后扔了。

· 引擎的 Command.Info(packages/opencode/src/command/index.ts:27)带
source: "command" | "mcp" | "skill",由注册表三段循环各自钉死(:96/:108/:140)——
是注册方的声明,不是名字反推,正是 2026-08-09 基线要的 authority。
· alpha-composer.tsx:1488 已经在调 c.command.list({directory}),却只做
.some(x => x.name === slash.name),把整条记录连同 source 丢掉;:1507 只往下传
{command, arguments, assistantMessageID}。类型信息就丢在这两行之间。
· v2 client 的类型(packages/sdk/js/src/v2/gen/types.gen.ts:2383)已经声明了 source?,
今天写就编译得过,不用 regen、不用 as any。(⚠️ packages/sdk/js/src/gen/types.gen.ts
那份 v1 类型是陈旧的、没有 source —— 但桌面端用的不是它。)

归属:一个上游文件都不用碰。 要动的 8 个文件全在 packages/ui-mac;
scripts/north-star-guard.sh 实跑 ✓ zero upstream package edits。橙紫 token 在
tokens.css:67-70(亮)/:217-220(暗)已存在。⇒ 本票是 CODE 票、S 级、单 PR,不是 DECIDE 票。

唯一真缺口是 E4 的 server 名:稿子要 context7 + resolve-library-id 两段,而引擎给 MCP prompt
注册的是合成键 sanitize(server) + ":" + sanitize(prompt)(mcp/catalog.ts:108),Command 里没有
单独的 server 字段,线上也没有列 MCP prompt 的路由。E3(技能)完全不受影响。
处置:不切、整串显示,先落地 —— 为最后 5% 的排版去碰基线原则不划算。

开工第一步是硬要求:curl 真引擎的 /command 看真实响应体,确认 source 真的在 JSON 里
(勘破只验到契约层,没起一个装着真 MCP server + 真技能的引擎)。

Parent requirement: #538

负责 AC:#538 AC1/AC3(视觉一致)—— 矩阵行 E3(技能 chip)/E4(MCP chip)双主题与稿一致。
边界:packages/ui-mac/src/renderer/alpha-ui/session-timeline/session-timeline-view.tsx(cmd-chip 渲染分型)、session-timeline.css;如需类型数据面,timeline-model.ts(订正:那里只是消费端,数据面缺在捕获侧)。
Out-of-scope:G15 技能执行卡形态(另票)、E1 展开体口径冲突(owner 裁决项)、E8 连接器 chip(另票)。
退出条件:E3/E4 按 CT #user 帧1 形态(橙色技能变体、紫色 MCP 变体 name+args)+ 明暗双主题一致 + 测试绿。


现状(矩阵批2 发现6,基点 alpha@7281627ed):斜杠 chip 无类型分型 —— 技能/MCP 均渲染为通用「运行命令」chip;设计有橙色技能、紫色 MCP 变体(TL-05 分类)。

  • 对照稿锚点:docs/design/current/conversation-timeline/design.html #user 帧1(矩阵行 E3/E4)
  • 证据:docs/verification/2026-07-24-req125-session-visual/shots/E3-{light,dark}-harness.png、E4-{light,dark}-harness.png
  • 矩阵:docs/verification/2026-07-24-req125-session-visual/matrix.md 批2 发现6
  • 处置:owner 裁决 —— 收窄简化升级为按稿实现(2026-07-24)

Activity

  1. jinjunnn commented on Aug 12, 2026

    @jinjunnn
    OwnerAuthor

    对帧订正(主 session,2026-08-12)—— 缺的不是样式,是数据

    只读对帧勘破结论(全文在 #538 的评论里)。缺口仍在,而且比票面深;锚仍权威(8-10 那次只碰了活稿的
    ③ 段,#user 的 data-k="skill"/data-k="mcp" 两帧与 design.css 的橙紫两条都在),
    7-24 的 E3/E4 旧图仍可用 —— 这一票是五张里唯一证据能复用的。

    但它今天做不了,因为数据面不存在:

    • 今天只有一个 chip 样式(session-timeline-view.tsx:564,567、session-timeline.css:487),没有变体;
    • 真正的堵点在上游:timeline-model.ts:118-123 的 TimelineSlashOrigin 只有
      {assistantMessageID?, command, arguments?} —— 没有任何类型或来源字段,
      slashOriginForTurn(同文件 585-604)也只透传这三个。票面写「如需类型数据面,timeline-model.ts」,
      而那里只是消费端。
    • 2026-08-09 已批基线新立了一条原则(§6/T3):来源标记只读持久化的 identity 与 authority,
      禁止从名字反推
      。橙/紫也是一次来源声明 ⇒ 不能靠解析命令字符串猜。
      这条原则在 7-24 定票时还不存在。

    ⇒ 本票要么先补数据面(捕获侧把类型/来源随 slash origin 一起持久化),要么等那件事另有归属。
    开工前先把这一段的归属定下来,否则实现方会走上「解析命令字符串」那条被基线明令禁止的路。

  2. jinjunnn commented on Aug 13, 2026

    @jinjunnn
    OwnerAuthor

    归属勘破(只读,2026-08-13)—— 票面前提被推翻:数据面已经存在,而且已经在手里了

    上一条订正说「缺的不是样式,是数据」。「缺样式」那半句成立;「缺数据」那半句不成立。
    把「用户敲 /xxx → chip 出现」整条链逐跳读完并跑了一次真实产物之后,结论是:

    引擎早就把「这是技能 / 这是 MCP / 这是普通命令」明明白白告诉了桌面端,
    而且就在 composer 已经发出的那一次请求的返回值里。我们拿到之后直接扔了。

    这和 #620 是同一个形态(洞其实不存在,上游早给了能力只是没人用)。


    1. 今天这条链长什么样

    1. 用户在输入框敲 /review pr 12 → routeSlash(composer-state.ts:263)拆成 {name, args}。
    2. alpha-composer.tsx:1488 先调 c.command.list({directory}) 把命令清单拉下来,
      用 cmds.some(x => x.name === slash.name) 确认这条命令存在。
    3. 存在 → c.session.command({...}) 发给引擎;拿到返回的 assistant messageID。
    4. alpha-composer.tsx:1507 回调 onSlashCommand({sessionID, directory, command, arguments, assistantMessageID})。
    5. session-composer-dock.tsx:184 → recordSessionSlashOrigin(...) 记进
      session-slash-origin.ts 的一个 solid signal。
    6. 时间线 slashOriginForTurn(timeline-model.ts:585)按 assistantMessageID 对齐回合 → 出 chip。

    类型信息就是在第 2 步和第 4 步之间丢的。 第 2 步那个清单里每一条命令都带着类型,
    第 4 步只挑了 name 和 args 往下传。

    2. 引擎侧到底知道多少(实读 + 实跑)

    packages/opencode/src/command/index.ts:22-32 的命令清单结构里有这一行:

    source: Schema.optional(Schema.Literals(["command", "mcp", "skill"])),

    而且它不是名字反推出来的 —— 注册表分三段循环各自钉死:配置命令写 "command"(:96)、
    MCP prompt 写 "mcp"(:108)、技能写 "skill"(:140)。是注册方自己的声明,正是基线要的那种 authority。

    三条实测(不是读代码推断的):

    • 实跑当前代码生成一次 v1 契约(bun run dev generate,2026-08-13):
      Command 的 schema 里确实有 "source": {"type":"string","enum":["command","mcp","skill"]}。
      仓里 checked-in 的 packages/sdk/openapi.json 与实跑结果逐字节一致。
    • 桌面端用的那个 SDK 类型里也已经有了:renderer 走
      @opencode-ai/sdk/v2/client,它 re-export packages/sdk/js/src/v2/gen/types.gen.ts,
      第 2378 行 export type Command = { ... source?: "command" | "mcp" | "skill" ... }。
      ⇒ 不需要 regen、不需要 as any、不需要补类型,今天写 entry.source 就编译得过。
    • c.command.list 走的是 v1 的 GET /command(v2 sdk sdk.gen.ts:2184 明写),
      返回 Array<Command>,不是缺字段的 CommandV2Info。这一跳我特意查了,因为两条命令面
      长得很像而其中一条确实没有 source。

    分三种问的答案:

    • 技能 —— 引擎知道,形状是 source: "skill",name 就是技能名(cloudflare),干净可用。
    • MCP —— 引擎知道它是 MCP(source: "mcp"),但不知道是哪个 server(见下面第 4 节)。
    • #878 那套 identity 快照 —— 用不上。 它是 ToolPart 的东西,
      ToolIdentitySource 枚举是 builtin|builtin-v2|plugin|mcp|host,里面根本没有 skill
      (模型自己调技能走的是内置 skill 工具,技能名是参数不是身份)。斜杠 chip 是用户消息行,
      不是工具卡,两套词汇不通用。别去接它。

    3. 归属:一个上游文件都不用碰

    按 scripts/north-star-guard.sh 的口径(UPSTREAM_PATHS = packages/{opencode,core,server,tui,sdk,protocol,schema,client}):

    要动的文件 在上游吗
    alpha-composer.tsx(:512 类型、:1488 .some→.find、:1507 多传一个字段) 否
    session-composer-dock.tsx(:184 透传) 否
    session-slash-origin.ts(记录里多一个字段) 否
    timeline-model.ts(:118 类型、:585 校验后透传) 否
    session-timeline-view.tsx(:493 chipInner 分型、:564/567 data-k) 否
    session-timeline.css(:487 起两段变体) 否
    i18n/{zh,en}.ts(两条新文案) 否
    对应测试 否

    全部落在 packages/ui-mac,守卫零风险(刚跑过 north-star-guard.sh:
    ✓ zero upstream package edits)。配色 token --a-orange(-subtle) / --a-purple(-subtle)
    在 tokens.css 明暗两套都已存在(:67-70 / :217-220),不用新造。

    ⇒ 这是一张 CODE 票,不是 DECIDE 票,而且是 S。 唯一需要 owner 点头的只有下面第 4 节那一件事。

    4. 唯一真的缺口:E4 那张帧要「server + 远端名」两段,而线上只给了一段

    稿子 #user 帧1 的 MCP chip 是这样画的:图标 + MCP · + name=context7 + args=resolve-library-id。
    但引擎给 MCP prompt 注册的命令名是一个合成键:
    sanitize(server) + ":" + sanitize(promptName)(packages/opencode/src/mcp/catalog.ts:108),
    也就是 context7:resolve-library-id 一整串,Command 里没有单独的 server 字段,
    线上也没有任何一条路由列 MCP prompt 清单(GET /mcp 只给 server 状态)。

    技能那半张(E3)完全不受影响,今天就能按稿做。 需要裁的只有 E4 的这一刀:

    • 4a 按 : 切开(零上游、结构上安全 —— sanitize 把两侧的 : 都洗掉了,分隔符不会歧义)。
      代价:①这确实是「从名字拆」,和基线那条原则擦边;②拿到的是洗过的 server 名
      (配置里叫 my server 会显示成 my_server)。
    • 4b 切开 + 拿 GET /mcp 的 server 键集合对一次(把「拆」变成「在权威集合里认」)。
      代价:多一次请求;两个配置键 a-b 和 a_b 洗完撞车时仍分不开。
    • 4c 不切,整串当 name 显示(MCP · context7:resolve-library-id)。零推断、零风险,
      但与稿有肉眼可见的差异 —— 需要改稿或接受偏差。

    我的建议:4c 先落地、4a 作为可选增量。理由是这一票的价值 95% 在「橙 / 紫一眼分得清」,
    而 4c 已经百分之百交付了那件事;为最后 5% 的排版去碰一条基线原则不划算。

    5. 顺带勘破出来的一件事(不属于本票,但请知悉)

    session-slash-origin.ts 是一个 renderer 内存 signal,不落盘。也就是说
    今天这个 chip 本来就活不过一次重启 —— 重开会话根本看不到它。
    所以订正块里说的「捕获侧持久化」在现状里并不成立;好消息是这反而让本票更安全:
    数据缺席即零渲染,回放永远不会显示一个错的来源(基线 T4 天然满足)。
    坏消息是「chip 重启后消失」是一个今天就存在、且比本票大得多的问题,
    要修得让引擎把来源写进消息或另建持久层(会碰 schema/src/v1/session.ts 与
    session/prompt.ts —— 那两个文件 ADR-041 已经收编,但仍是 L 级)。建议另开票,不要塞进 #582。

    6. 四条路的对照

    # 做什么 动哪些文件 上游 fork-sync 代价 先例
    P1 ✅推荐 消费已有的 source,E3 按稿、E4 取 4c 只有 packages/ui-mac 那 8 个 零 零 #620(洞不存在,上游早给了)
    P2 P1 + E4 按 : 切开还原稿子 同上 零 零 无;需 owner 对「拆合成键算不算反推」表态
    P3 让引擎在 Command.Info 里多给一个 server/origin opencode/src/command/index.ts(未被任何 exclude 覆盖)+ 重生 sdk 类型 + openapi 是 新 ADR + 守卫和 alpha-ci.yml 两处同步加 exclude(local-gate-parity.test.ts 会判)+ 每次 fork-sync 都要重解冲突 ADR-041 收编那 20 个文件
    P4 把来源持久化进消息,让 chip 活过重启 schema/src/v1/session.ts、session/prompt.ts(两者已被 ADR-041 收编)+ ui-mac 是(已登记) 中 ADR-041 / #878

    建议:走 P1,本票即刻可开工,S 级,单 PR。 P4 另开票。P3 只有在 owner 认为
    E4 必须逐像素对稿、且 4a/4b 都不可接受时才值得。

    7. 实现方开工第一件事(别跳)

    我验的是契约(实跑生成的 v1 OpenAPI + 桌面端在用的那份 SDK 类型 + 注册表三处赋值),
    没有起一个装着真 MCP server 和真技能的引擎去看那个 JSON body。
    所以第一步请花五分钟:对着真在跑的引擎 curl /command,确认返回体里
    技能条目真的带 "source":"skill"、MCP 条目真的带 "source":"mcp",再动手。
    判据必须是看见真实响应,不是「类型里有」。

  3. self-assigned this
    on Aug 13, 2026
  4. added a commit that references this issue on Aug 13, 2026
  5. added 2 commits that reference this issue on Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

type:featureNew user or system capability

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions