Files
deepseek-harness/packages/client/ui-command/README.zh.md
imccyu 83c2115de8 refactor(client): command decorations replace the hostBacked contribution mode
A popup on a host command is not a second command — it is what that
command's BARE invocation does on this client. CommandContribution loses
hostBacked (contributions are pure client commands again; a host-name
collision fails loud, unchanged for /model), and the contract gains
CommandDecoration + command.decorate(): key = the HOST command name, no
catalog row, no claim participation. Dispatch consults decorations only on
the bare paths (menu pick / bare enter) after the host row resolves; space
and argued enter never see them — the two edges hostBacked had to guard
explicitly hold by construction in the decoration model. A decorated name
with no host row in the session's directory never fires (a decoration
cannot manufacture a command).

ui-permission switches register→decorate with zero behavior change
(options still read the permissions projection; a pick still submits
'/permission <preset>'). Specs rewrite to the decoration semantics: no
catalog row, bare-enter popup vs argued-enter host claim, space host
claim, no-host-row miss, unavailable fall-through, duplicate fail-loud.
2026-07-29 12:01:36 +08:00

3.9 KiB
Raw Blame History

@deepseek-ai/dsh-client-ui-command

English | 中文

客户端命令业务面(ctx.command):以会话为 key 的命令目录缓存、带 matchSpacematchEnter 裁决钩子的 / 命令 source、三型派发executepopupSelectleadingInput以及面向业务包的 popupSelect 注册面。契约:Web 命令业务面 Agent Noteagent 决策记录)

src/client/contract.ts 是冻结的业务表层:CommandServiceContract.register(name, spec)decorate(name, spec) 是业务包消费的全部内容;CommandUiSpec{options, onSelect} 让 popup 数据自给自足——壳组件归本包所有业务永远见不到它。contribution 是 client 自有命令(与 host 同名碰撞即 fail-louddecoration装饰则把裸调用 popup 挂在已存在的 host 命令上——host 保留目录行、带参 claimspace / 带参 enter与生命周期记账被装饰的名字若在会话目录中无 host 行则装饰永不触发。命令三型按每次派发派生,绝不在注册时定型:带 input 的 host descriptor 是 leadingInput注册了 CommandUiSpec 的是 popupSelect其余全部是 execute。

CommandDirectorysrc/client/directory.ts)是唯一的 wire 派生缓存,以会话为 key每个会话恒为 agent-backed因此 command.list({sessionId}) 是唯一的寻址形状source 的 scope 出生 warm 钩子会预热该会话的缓存项。缓存项由 commands/changed 类型化事件软失效(重拉在途期间旧快照继续服务),由 connection/reset 硬失效,并以 epoch 把关,被取代的旧拉取永远无法覆盖更新的结果。matchSpace 只凭该缓存同步应答;matchEnter 在 SubmitAttempt 信号上强等缓存,预热失败即拒绝——/ 开头的一行绝不会被静默降级为普通提示词。

PopupSelectControllersrc/client/popup.ts)是无头的壳状态:PopupSelectView 自行注册进 conversation.input.overlaySlotMap key 归 ui-conversation 所有;本包只以 type-only 导入引入该声明——没有运行时依赖边。壳是打开期间持有焦点的瞬态层onSelect 之后的 token 片段消费在两条分支上都经 consumeTokenSegment 执行(菜单路径做 span CAS回车路径做裸 token 相等比较),作用于接线层经 bindDraft 绑定的草稿表层。

/client 导出表层是插件主体(applyinject)、CommandService、目录类和 popup 类及其状态类型,以及冻结的契约类型;壳组件本身是 overlay 注册的内部实现。

模型体验

间接影响,途径是本包的派发与 claim.submit 路径触发的 host command.execute RPC匹配命中的命令其 handler 会修改 host 领域状态,其他包再把该状态投影进下一个请求(/plan 的 handler 翻转 plan 模式,其归属包注入 plan:policy 系统提示词 section而命令行本身、detached result 与所有菜单notice 渲染都留在客户端,永不进入会话日志。

KV Cache 影响

无直接影响;该包既不组装也不发送提供方请求。它触发的命令 handler 可能改变归属 host 包对下一个请求系统提示词的贡献(某个 section 的出现或消失会替换较早的请求 token并使提供方前缀从该点起失效但这一影响由各命令的 host 包拥有并记录。

已知限制与暂缓事项

  • popupSelect 壳还没有已上架的业务消费者模型选择host selectModel)是设计的参照用例,将随其自身的功能工作落地;在此之前,壳只由包测试演练。
  • 脱离会话后detached result 的 notice 回退到 consolefire-and-forget 路径经 SessionInput.notify 把结果送到触发会话的编辑器会话拆除后console 输出行是仅剩的呈现面。