docs(skill): document the user-explicit invocation path

Bilingual README updates for the four touched packages (ui-skill's claim
flow and deterministic-injection model experience, the apiproxy skills
domain, the shared renderSkillContent seam export, the catalog stitch
sentence), the implemented Agent Note triplet recording the decision and
its peer-product evidence, and the regenerated catalogs/graphs.
This commit is contained in:
Yichen Jiang
2026-08-08 01:52:43 +08:00
parent db146f0eba
commit 3584d8e088
19 changed files with 121 additions and 31 deletions

View File

@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write packages/client/ui-skill/README.md
README.md: f70bd2780f255cd8e0c64acb3da3863e10c4fa9d
README.zh.md: 6eb6cbd3ae196a540e161a3a23f9df2136824f2e
README.md: c888622bc92b038413c7d0ebf63abb61b483f6f5
README.zh.md: 3bbbc90186726356c53f375bb664d678c4926988

View File

@@ -2,7 +2,9 @@
English | [中文](README.zh.md)
Skill reference source, browser half: registers the `/`-trigger `skill` source into `ctx.slash`. Ordinary-session candidates come from the `skill.list` RPC addressed by the per-call `ClientSessionContext` projection's `{sessionId}`, with the host resolving `cwd` from the session header. The host returns the intersection of model-invocable and user-invocable skills because this browser path inserts a model reference rather than loading the body directly. Catalog-addressed continuable children resolve no skill candidates locally because the existing skill RPC requires an attached session; viewing their persisted history must not activate them. Catalogs cache per ordinary session with a single-flight fetch; the scope-birth `warm` hook prewarms the session's entry and `connection/reset` clears everything. Results filter by `startsWith(query)`; picking a candidate lands the literal `/name ` text through the slash pipeline (decision 21 plain-text reference), and the source `codec` owns the reference's two projections: `clipboardText``/name`, `serialize` → the model form `<skill>name</skill>` invoked at submit time. The RPC rides the plugin's root-context connection captured at registration — the source never reads services off a per-call argument. The source implements no `matchSpace`/`matchEnter` hooks — skill references never enter command adjudication and ride ordinary prompts into the default sink.
Skill invocation source, browser half: registers the `/`-trigger `skill` source into `ctx.slash`. Ordinary-session candidates come from the `skill.list` RPC addressed by the per-call `ClientSessionContext` projection's `{sessionId}`, with the host resolving `cwd` from the session header. The host serves every user-invocable skill; a `modelInvocable: false` entry (a `disable-model-invocation` skill, whose only entry point is this path) wears the user-only marker as a description prefix in the active language. Catalog-addressed continuable children resolve no skill candidates locally because the existing skill RPC requires an attached session; viewing their persisted history must not activate them. Catalogs cache per ordinary session with a single-flight fetch; the scope-birth `warm` hook prewarms the session's entry and `connection/reset` clears everything. Results filter by `startsWith(query)`.
A menu pick or an entered `/name [args]` line claims the composer into an args-tolerant `skill.invoke` transaction (`matchEnter` strong-waits the catalog; an unknown name answers undefined and stays a plain prompt). Submit trims the args, keeps blank args off the wire, and folds an RPC refusal into the composer's error outcome; the host renders the skill body and injects it as a user message before starting the turn, so invocation is deterministic for every user-invocable skill. The RPC rides the plugin's root-context connection captured at registration — the source never reads services off a per-call argument. Draft chip visuals still derive from the `lexicon` scan; the legacy `<skill>name</skill>` reference codec is gone (decision 21 removal cut) and `matchSpace` stays unimplemented — menu and enter own the skill flows.
A failed `skill.list` throws from `candidates`, which the slash shell logs and folds into a silent menu-group drop — the menu shows only pending/ready states.
@@ -14,23 +16,22 @@ The browser plugin also registers a keyed `skill` toolview in `conversation.chat
## Model Experience
### Skill reference text in the user prompt
### User-explicit skill invocation
#### What the model sees
A picked candidate lands the literal `/name ` in the draft (decision 21: plain text, no `<skill>` tag); the text reaches the model verbatim inside the ordinary user message (`session.prompt`), with no dedicated content block, prompt section, or host-side expansion. The association with the actual skill is model-side and non-deterministic: the session prefix already carries the skill catalog (rendered by `dsh-tool-skill`), and the reference's name matching a catalog entry is what invites the model to load it.
A claimed invocation never ships the `/name` literal. The host (`skill.invoke`) renders the canonical `<skill_content>` block — the same `renderSkillContent` output the `skill` tool returns — appends the user's trailing text after a blank line, and injects the whole as one user-role message carrying the `skill-invocation` source, immediately starting a turn. Loading is deterministic: the model receives the full body without being asked to call the `skill` tool, and the catalog (rendered by `dsh-tool-skill`) tells it not to re-load an inline-injected skill.
#### Token effect
Conditional and tiny: only a pick (or hand-typing the same text) adds the reference's characters to that one user message. Menu browsing and the candidate fetch add zero model tokens.
One invocation adds the rendered skill body plus the trailing text to that turn's user message — the same cost as the model loading the skill through the tool, paid unconditionally instead of at the model's discretion. Menu browsing and the candidate fetch add zero model tokens.
#### KV Cache effect
Append-only: the reference is part of a new user message appended after the reusable history prefix. This package never edits earlier request tokens.
Append-only: the injected message lands after the reusable history prefix. This package never edits earlier request tokens.
## Known Limitations and Deferred Work
- **Result-only history pages use the generic row** — keyed dispatch needs the paired call in the runtime window; pagination that leaves the call outside has no tool identity. This client presentation feature does not extend the history wire contract to recover it.
- **Non-deterministic skill loading** — the reference is a collaboration cue, not a guarantee; the model may ignore it. The rework path when hit rate proves insufficient (a host-side `context/skill-reference` guidance package, or full-text injection) sits in the design ledger; the wire text shape would not change.
- **First keystroke may race the prewarm** — the scope-birth warm launches the catalog fetch, but a menu opened before it settles shows no skill candidates for that keystroke. Accepted by design: skill references do not participate in enter adjudication, so nothing correctness-bearing waits on the catalog.
- **Enter waits on the catalog once** — `matchEnter` strong-waits the session's first catalog fetch before answering, so an enter racing a cold cache resolves against the settled catalog rather than silently missing. A menu opened before the prewarm settles still shows no skill candidates for that keystroke.
- **Text is the truth** — the reference is plain draft text; a hand-typed identical token is the same reference. Chip visuals derive from the lexicon scan; no occurrence identity or position tracking (componentized chips are a ledger item).

View File

@@ -2,7 +2,9 @@
[English](README.md) | 中文
skill技能用 source 的浏览器端:把 `/` 触发的 `skill` source 注册进 `ctx.slash`。普通会话的候选来自 `skill.list` RPC以每次调用的 `ClientSessionContext` 投影中的 `{sessionId}` 寻址host 从会话 header 解析 `cwd`。宿主返回模型可调用与用户可调用 skill 的交集,因为该浏览器路径插入的是模型引用,而不是直接加载正文。由目录寻址的可继续 subagent 在客户端解析为没有 skill 候选,因为现有 skill RPC 要求会话已挂载;查看其持久化历史不得激活它。目录按普通会话缓存,拉取走 single-flightscope 创建时的 `warm` 钩子预热该会话的缓存项,`connection/reset` 清空全部缓存。结果按 `startsWith(query)` 过滤pick 一个候选会把字面文本 `/name ` 经 slash 流水线落进草稿(决策 21 的纯文本引用source 的 `codec` 拥有该引用的两种投影:`clipboardText``/name``serialize` → 提交时生成的模型形式 `<skill>name</skill>`。RPC 使用插件注册时捕获的根上下文连接——source 绝不从每次调用的参数上读取服务。source 不实现 `matchSpace``matchEnter` 钩子——skill 引用永不进入命令裁决,随普通提示词落入 default sink
skill技能用 source 的浏览器端:把 `/` 触发的 `skill` source 注册进 `ctx.slash`。普通会话的候选来自 `skill.list` RPC以每次调用的 `ClientSessionContext` 投影中的 `{sessionId}` 寻址host 从会话 header 解析 `cwd`。宿主提供每一个用户可调用 skill`modelInvocable: false` 的条目(即 `disable-model-invocation` skill此路径是其唯一入口会以当前语言把仅限用户标记作为描述前缀带上。由目录寻址的可继续 subagent 在客户端解析为没有 skill 候选,因为现有 skill RPC 要求会话已挂载;查看其持久化历史不得激活它。目录按普通会话缓存,拉取走 single-flightscope 创建时的 `warm` 钩子预热该会话的缓存项,`connection/reset` 清空全部缓存。结果按 `startsWith(query)` 过滤。
菜单 pick 或回车提交的一行 `/name [args]` 会把 composer 认领进一个容忍参数的 `skill.invoke` 事务(`matchEnter` 强等目录;未知名称应答 undefined保持为普通提示词。提交时会修剪参数、让空白参数不上协议并把 RPC 拒绝折叠进 composer 的错误结局;宿主在开启轮次之前渲染 skill 正文并将其作为用户消息注入,因此对每一个用户可调用的 skill调用都是确定性的。RPC 使用插件注册时捕获的根上下文连接——source 绝不从每次调用的参数上读取服务。草稿 chip 视觉仍由 `lexicon` 扫描派生;旧的 `<skill>name</skill>` 引用 codec 已经移除(决策 21 的移除裁定),`matchSpace` 保持不实现——skill 流程归菜单与回车所有。
`skill.list` 失败时 `candidates` 抛出异常slash 壳层记录日志并折叠为静默的菜单组丢弃——菜单只显示 pendingready 状态。
@@ -14,23 +16,22 @@ skill技能引用 source 的浏览器端:把 `/` 触发的 `skill` sourc
## 模型体验
### 用户提示词中的 skill 引用文本
### 用户显式 skill 调用
#### 模型看到的内容
pick 的候选会把字面文本 `/name ` 落进草稿(决策 21纯文本`<skill>` 标签);该文本原样进入普通用户消息(`session.prompt`)到达模型,没有专用内容块、提示词 section 或 host 侧展开。与实际 skill 的关联在模型侧建立且具有非确定性:会话前缀已携带 skill 目录(由 `dsh-tool-skill` 渲染),引用名称与目录条目匹配,正是这一点引导模型去加载它
认领的调用绝不会把字面文本 `/name` 发出去。宿主(`skill.invoke`)渲染规范的 `<skill_content>` 块——与 `skill` 工具返回的 `renderSkillContent` 输出相同——在一个空行之后追加用户的尾随文本,并把整体作为一条携带 `skill-invocation` 来源的 user 角色消息注入,随即开启一个轮次。加载是确定性的:模型无需被要求调用 `skill` 工具就能收到完整正文,目录(由 `dsh-tool-skill` 渲染)也会告诉它不要重新加载已内联注入的 skill
#### Token 影响
有条件且极小:只有 pick或手动键入相同文本会把引用的字符加进那一条用户消息。浏览菜单和拉取候选不会增加任何模型 token。
一次调用会把渲染后的 skill 正文连同尾随文本加进该轮次的用户消息——成本与模型经由工具加载该 skill 相同,只是无条件支付,而非由模型自行裁量。浏览菜单和拉取候选不会增加任何模型 token。
#### KV Cache 影响
仅追加:引用是追加在可复用历史前缀之后的新用户消息的一部分。该包绝不改写较早的请求 token。
仅追加:注入的消息落在可复用历史前缀之后。该包绝不改写较早的请求 token。
## 已知限制与暂缓事项
- **仅含结果的 history 页使用通用行**:键控分派要求配对调用位于 runtime 窗口内;分页将调用留在窗口外时,结果没有工具身份。这项客户端呈现功能不会为了恢复该身份而扩展 history 协议契约。
- **skill 加载具有非确定性**引用是协作线索不是保证模型可能忽略它。针对命中率不足情况的返工路径host 侧 `context/skill-reference` 引导包,或全文注入)记录在设计台账中;协议中的文本形态不会改变
- **首次击键可能与预热竞速**scope 创建时的预热会启动目录拉取,但目录落定之前打开的菜单,在那次击键下不会显示 skill 候选。这是设计上接受的取舍skill 引用不参与回车裁决,因此没有任何攸关正确性的环节等待目录。
- **回车对目录只等待一次**`matchEnter` 在应答之前强等该会话的首次目录拉取,因此与冷缓存竞速的回车会对照已落定的目录解析,而不是静默错过。预热落定之前打开的菜单,在那次击键下仍不会显示 skill 候选
- **文本是唯一依据**:引用是普通的草稿文本;手动键入的相同 token 就是同一个引用。chip 视觉由 lexicon 扫描派生;没有 occurrence 身份或位置跟踪(组件化 chip 是台账事项)。