@changfenhuang/dsh-genui 0.11.2 → 0.11.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +13 -4
- package/README.zh-CN.md +13 -4
- package/SKILL.md +16 -4
- package/lib/assets/standalone-runtime.js +7 -6
- package/lib/client.js +15 -17
- package/lib/index.js +956 -354
- package/lib/types/client/blocks/forms.d.ts +5 -6
- package/lib/types/client/blocks/state.d.ts +2 -11
- package/lib/types/client/spec.d.ts +8 -11
- package/lib/types/client/submission-registry.d.ts +51 -0
- package/lib/types/client/table-details.d.ts +20 -0
- package/lib/types/client/walk-spec.d.ts +3 -0
- package/lib/types/plugin/fence-feedback.d.ts +50 -11
- package/lib/types/plugin/index.d.ts +3 -3
- package/lib/types/shared/fence-repair.d.ts +18 -7
- package/package.json +20 -19
- package/src/client/GenuiBlock.module.css +65 -1
- package/src/client/GenuiBlock.tsx +7 -14
- package/src/client/blocks/charts.tsx +41 -8
- package/src/client/blocks/forms.tsx +26 -48
- package/src/client/blocks/render-node.tsx +1 -1
- package/src/client/blocks/state.ts +2 -12
- package/src/client/dom-fence.tsx +94 -21
- package/src/client/genui-runtime/diagnostics.ts +20 -0
- package/src/client/genui-runtime/normalize.ts +28 -9
- package/src/client/guard.ts +93 -151
- package/src/client/inline.ts +11 -4
- package/src/client/spec.ts +8 -11
- package/src/client/submission-registry.ts +150 -0
- package/src/client/table-details.ts +151 -0
- package/src/client/walk-spec.ts +43 -0
- package/src/plugin/fence-feedback.ts +372 -53
- package/src/plugin/index.ts +7 -6
- package/src/shared/fence-repair.ts +309 -128
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.11.4] - 2026-10-08
|
|
6
|
+
|
|
7
|
+
### 修复
|
|
8
|
+
|
|
9
|
+
- DSH Desktop 0.11.0 将 `dsh-ui` 围栏呈现为 `<dsh-ui>…</dsh-ui>` 纯文本标签时,DOM 通道现可识别助手消息中的完整标签文本,渲染卡片并在卸载时恢复原文(#268)。
|
|
10
|
+
- `list` 项支持 `label`/`name` 标题字段与 `description`/`content`/`text`/`body`/`detail` 正文字段;只有正文的记录也能显示,无法渲染的记录字段会给出位置明确的警告(#265)。
|
|
11
|
+
- **GenUI reasoning-only 回合进入宿主重试**:当前回合调用 `validate_dsh_ui` 后,普通顶层会话若以 `stop` 结束且完整内容块只有 reasoning,插件会将终止结果改写为 `EMPTY_RESPONSE`,交由宿主现有重试策略处理;普通会话、子代理、辅助请求及含正文或工具调用的结果保持原行为。`fenceFeedback: false` 仍只关闭同回合围栏修正(#259)。
|
|
12
|
+
- **完善 GenUI 交付判定与 fence-feedback 重载恢复**:`render_ui` 只有明确返回 `status=rendered` 才计为正式交付,正常返回的 `status=invalid` 会继续进入零交付修正;plugin reload 时主动读取历史会话,恢复当前 turn 的 render fingerprint、delivery turn 和已消费的 correction budget,避免重复发送 delivery reminder 或重新获得修正次数;render fingerprint 去重仅限当前 turn,新 turn 可重新修正相同错误。每 turn 最多两条 correction 的上限由 render failure 与 nothing-delivered reminder 共同使用(#263、#267)。
|
|
13
|
+
- **非英文宿主 locale 下围栏永远不渲染**:通用代码块的判定依赖硬编码标题白名单(`Code` / `Code block` / `代码块`),宿主 locale 为其它语言时(如俄语包的 «Код»),本地化标题被当成真实语言,同一份 `dsh-ui` 围栏既不走带标签路径也不进内容识别——界面永远停在代码块。现在判定反转:banner 标签命中**已知真实语言闭集**(语言 id 有限、由插件维护)才视为带语言,空标签与任何本地化通用词一律视为无语言、交给内容校验把关——同一份正文的渲染结果不再因宿主语言而不同(#258)。
|
|
14
|
+
- **多行文本/代码的复制不再丢换行**:行内渲染曾把每个 `\n` 变成 `<br>`,而 `<br>` 对 `textContent` 与 `Selection.toString()` **零贡献**——用户选中表格单元格复制出来的是一整行,`python - <<'PY' … PY` 这类 heredoc 结构被毁、必须手工拼回。现在换行在 DOM 里保持为**真实换行符**(选中/复制原样),由容器的 `white-space` 负责呈现:代码形单元格 `.tdCode` 用 `pre-wrap`(换行 + 行首缩进都保留),散文单元格 `.tdMultiline` 用 `pre-line`(换行呈现、空格照常折叠),单行单元格仍是 `nowrap`;`calloutBody`/`liTitle`/`liDesc`/`kvValue`/`tlDesc`/`detailBody`/`accBody` 同步声明 `pre-line`,因为它们此前正是靠 `<br>` 硬断行(#233)。
|
|
15
|
+
- **多行表格单元格的缩进**:`.table td` 的 `nowrap`(表格的数据语气)会折叠单元格里的空白,模型写进单元格的代码缩进会消失;行内 `` `code` `` 同步改为 `pre-wrap`,不再吞掉自身空格(#233)。
|
|
16
|
+
- **「未转义引号 + 尾部杂字符」叠在一起时围栏不再整条失守**:模型常在 JSON 根值之后追加 `</p>` 或一句解释(真实会话 539 条围栏里 240 条有尾随 `</p>`),这类正文本来靠 `parsePartialGenuiSpec` 的平衡前缀就能渲染;但一旦**同时**还有字符串内未转义的半角引号,tier-1 因为「必须整体 parse 通过」而放弃、tier-2 同样放弃——两个缺陷叠加就把整条围栏变成代码块 + 红横幅。现在 tier-2 在整体 parse 失败时回退到**平衡前缀**(根值结束处)并采用它;tier-1 保持严格(它也在流式期运行,采用前缀可能发布半截正文)。真实语料 37 条不可渲染 → 22 条(救回 15 条,占失败数 40%)。
|
|
17
|
+
- SKILL.md 使用规则第 3 条补充:`}` 之后不要再写任何字符(常见错误是追加 `</p>` 或解释)。
|
|
18
|
+
- **通用代码块的内容识别接受标点级修复**:宿主会隐去它不认识的语言(高亮器不支持 `dsh-ui`),此时同一份围栏在 DOM 里是一个「代码块 / Code block / Code」标签的普通代码块;当 ChatSnapshot 的语言来源在那一行上不可用时,内容识别是唯一出路。此前它要求 `JSON.parse(raw)` **直接**通过,于是「只差一个未转义引号(tier-1 可修)」的围栏永远不会被接管——真实会话(seq 40530)里模型终于把围栏写进正文、正文经 tier-1 修 14 处后可正常渲染,界面却始终停在代码块。现在内容识别跑与带标签路径**相同的 tier-1 修复**,并且在整体 parse 失败时**裁到第一个平衡根值**(`trimToBalancedRoot`,只裁剪、不补全结构)——真实样本里模型把工具调用模板泄漏在 JSON 之后且围栏没闭合,正是这一种;结构级 tier-2 刻意不参与(兜底不该接管只是"长得像 JSON"的普通代码),canonical 规范 / 未知字段 / 归一化等价的检查保持不变。
|
|
19
|
+
- **回合状态的两处漏补救**:① `render_ui` 是否交付由 `tool/result` 决定,只有无内部错误、结果块非 `isError` 且协议明确返回 `status=rendered` 才算成功;结果未到达时回合边界保持静默,不与晚到结果赛跑。② 同回合的合成上下文消息(`agent.inject()` 通知、成员消息等)不会清掉本回合的验证/交付状态;重置只发生在正式的 `turn/start` 边界与直接用户提示(`source.kind === 'user'`)上(#236、#263 review)。
|
|
20
|
+
|
|
21
|
+
## [0.11.3] - 2026-09-29
|
|
22
|
+
|
|
23
|
+
### 兼容性
|
|
24
|
+
|
|
25
|
+
- 支持 DSH `0.2.0-rc.1` / `0.2.x` 宿主(#227)。
|
|
26
|
+
- 扩展 `@deepseek-ai/dsh-*` peerDependencies 至 `0.2.x`(#227)。
|
|
27
|
+
- 将 CI 宿主兼容目标整理为 minimum / current / next(#227)。
|
|
28
|
+
- 将 `dsh-v0.2.0-rc.1` 纳入 host API 与 packed host smoke 验证(#227)。
|
|
29
|
+
|
|
5
30
|
## [0.11.2] - 2026-09-28
|
|
6
31
|
|
|
7
32
|
### 新增
|
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ The repository ships both renderer channels, the host plugin, and the built brow
|
|
|
96
96
|
|
|
97
97
|
Prerequisites — all required:
|
|
98
98
|
|
|
99
|
-
1. **dsh `^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1`** (verified host
|
|
99
|
+
1. **dsh `^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0`** (DSH `0.2.0-rc.1` is currently a prerelease; verified host roles: minimum `dsh-v0.1.2-rc.1`, current `dsh-v0.1.7-rc.2`, next `dsh-v0.2.0-rc.2`; users on DSH `<=0.1.1-rc.x` should use dsh-genui `0.9.8`)
|
|
100
100
|
2. **`pnpm` on your PATH**: the `dsh plugin` command depends on it. If missing: `corepack enable` (or `npm i -g pnpm`), then **open a new terminal** and confirm `pnpm -v` prints a version
|
|
101
101
|
|
|
102
102
|
Install and activate in DSH (one command, all dependencies included):
|
|
@@ -116,6 +116,12 @@ npm install @changfenhuang/dsh-genui
|
|
|
116
116
|
|
|
117
117
|
> ⚠️ **Don't use `link:` on a freshly cloned directory** — `link:` does not install the plugin's dependencies (mermaid / three / react), so the renderer will break. Use the npm command above for normal installation; reserve `link:` for local development iteration (see below).
|
|
118
118
|
|
|
119
|
+
### Package name and version matter
|
|
120
|
+
|
|
121
|
+
- **The scope matters**: npm also hosts a same-named, unscoped [`dsh-genui`](https://www.npmjs.com/package/dsh-genui) (a Vue/OpenTiny implementation by a different maintainer, unrelated to this repo). If an install listing describes "interactive charts, forms, calculators, dashboards, and mini apps", that is the other project — the host will reject it over incompatible peers. This plugin is always **`@changfenhuang/dsh-genui`**.
|
|
122
|
+
- **When the host rejects the install**: the supported host range is declared in peerDependencies and enforced by the host — upgrade to the `latest` plugin version on npm and reinstall; no other config changes are needed.
|
|
123
|
+
- **When the resolved version is older than `latest`**: this is usually pnpm's release-age policy (versions published within the last 24 hours are silently skipped). Wait a day and retry, or add the target version to `minimumReleaseAgeExclude` in the profile's `pnpm-workspace.yaml`; if the profile exact-pins an old version, update the declaration first, then reinstall.
|
|
124
|
+
|
|
119
125
|
### Migrating from the old `@omdsh-dev` package name
|
|
120
126
|
|
|
121
127
|
If you installed from `github:omdsh-dev/dsh-genui` before v0.9.2, pnpm may keep the dependency under the old `@omdsh-dev/dsh-genui` key even though the repository now declares `@changfenhuang/dsh-genui`. The loader resolves plugins from the profile's dependency keys, so a later reinstall can then fail with `Cannot find package '@changfenhuang/dsh-genui'`. Re-add the plugin under its current package name:
|
|
@@ -163,7 +169,8 @@ The following is the detailed capability reference. Every behavior is constraine
|
|
|
163
169
|
- **ECharts integration**: the `echart` node renders full ECharts charts with theme-aware colors, tooltips, and legends. Two modes: **preset shorthand** (`preset: 'bar' | 'line' | 'area' | 'pie' | 'scatter'` + `data`/`series`) for quick upgrade from the `chart` node, or **full option** (`option` field) for custom chart types, dataZoom, visualMap, and other advanced ECharts features. The echarts engine (~1 MB) is lazy-loaded on demand — the main bundle never carries it, and conversations without `echart` nodes never download it- **Function plots**: `plot` draws curves; parameter sliders redraw in real time, with optional auto-animation
|
|
164
170
|
|
|
165
171
|
- **Quiz**: `quiz` grades on click with explanation and retry; with `action`, the answer is also sent back to the model (grading stays local and instant)
|
|
166
|
-
- **Local grading (submit)**: a multiple-choice set
|
|
172
|
+
- **Local grading (submit)**: a multiple-choice set uses one `radio` per question with `group` + `answer` + `explanation` and a `submit` button. Once the required radio members are answered, grading appears in the UI when no other collected block state needs delivery. An aggregated action sends `answers`, optional `fields`, `total`, and `answered` in other cases.
|
|
173
|
+
- **Submit groups**: `groups` names submission member keys from `radio.group`, `checkbox.group`, `input.id`, `textarea.id`, `select.id`, and `slider.id`. A radio needs a selection, a checkbox group needs at least one selection, and a field needs a value that remains after trimming. `groups` controls required members and progress; the payload still collects filled form state across the block. Without `groups`, any answered member enables submission.
|
|
167
174
|
- **State persistence**: answers, submission locks, and input values are saved per "session + content fingerprint" — refresh or reopen restores everything; re-rendering identical content keeps user state; new content starts fresh; LRU cap of 200 blocks
|
|
168
175
|
- **Form semantics**: `input` Enter / `textarea` Ctrl+Enter submits immediately (`submit:true`), no blur needed; fields with an `id` are collected into the submit's `fields`
|
|
169
176
|
- **Secrets ban**: GenUI must never ask for passwords, API keys, access tokens, recovery codes, or other secrets; even if a password input appears, it stays masked, is never persisted, and never enters form collection
|
|
@@ -172,7 +179,7 @@ The following is the detailed capability reference. Every behavior is constraine
|
|
|
172
179
|
- **Event loop**: buttons, checkboxes, radio buttons, switches, selects, inputs, textareas, submits, and quizzes send one event immediately per gesture; slider drags keep trailing-edge debounce, sending only the final value per slider, with different `id`s handled independently
|
|
173
180
|
- **Tool channel**: the `render_ui` tool renders the same spec as a card in the tool row (deliverable-style UI goes through the tool, answer-style UI through the fence)
|
|
174
181
|
- **Session panel**: a persistent dock above the composer; `render_ui` / `panel: true` fences update the same surface in place; `/panel` opens it from the client (`/panel <instruction>` customizes via the model, `/panel clear` clears); the top border is draggable to resize; `append: true` merges incrementally — same-named tabs append content, new tabs get added; the whole panel caps at 200 nodes / 200 appends, after which the model should send `replace` to rebuild
|
|
175
|
-
- **Fence auto-repair**: enabled by default; set `fenceFeedback: false` in this plugin's config (under the plugin entry's `config:` in your profile's cordis.patch.yml) to disable
|
|
182
|
+
- **Fence auto-repair**: enabled by default; set `fenceFeedback: false` in this plugin's config (under the plugin entry's `config:` in your profile's cordis.patch.yml) to disable same-turn fence corrections. A validated GenUI turn that ends with reasoning only still becomes `EMPTY_RESPONSE` and uses the host's retry policy. When a reply's final dsh-ui fence fails to render, the plugin steers the SAME turn with the per-node diagnosis so the model can resend a fixed fence. At most two corrections are sent per turn, shared by render failures and validated turns with no formal delivery; each fence fingerprint is corrected once per turn, and a nothing-delivered reminder is sent once per turn. A new turn can correct the same failed fence again. Plugin reload restores the current turn's fence fingerprints and correction count, along with delivery reminders, from persisted correction messages. Corrections are never sent in subagent sessions.
|
|
176
183
|
- **Self-healing & limits**: every fence passes a spec guard — bad nodes are silently dropped (the surviving siblings keep rendering: one bad component no longer degrades the whole fence), numbers clamped, strings truncated; the whole tree is capped at 200 nodes / 8 nesting levels; pathological specs never crash the UI
|
|
177
184
|
- **Canonical component protocol**: native field aliases such as `card.label` → `title`, `table.data`/`table.items` → `rows`, `callout.kind`/`callout.desc` → `tone`/`content` (tone value `danger` → `error`), `steps.items` → `steps`, `keyvalue.items` → `pairs` (record `label` → `key`), and `file-tree.nodes` → `items` (record `label` → `name`, `type` defaults to `dir` when children exist) are normalized deterministically before validation and rendering. A root-level component array is adopted as `items`, and a double-encoded JSON string is decoded once. `validate_dsh_ui` reports these normalizations and warns about unknown native fields without blocking custom renderer nodes.
|
|
178
185
|
- **Chart error self-healing**: mermaid failures auto-retry with repairs (strip backticks, quote Chinese/space labels, remove `<br/>`) before degrading to source; a broken chart never hits the screen
|
|
@@ -239,7 +246,9 @@ pnpm run check # type check + full tests + build
|
|
|
239
246
|
|
|
240
247
|
With the locked dependencies installed, the check script (`pnpm run check` or `npm run check`) uses the pinned DSH `0.1.2-rc.1` release packages.
|
|
241
248
|
|
|
242
|
-
|
|
249
|
+
The verified host roles are minimum `dsh-v0.1.2-rc.1`, current `dsh-v0.1.7-rc.2`, and next `dsh-v0.2.0-rc.1`. DSH `0.2.0-rc.1` is currently a prerelease. CI keeps four primary lanes: Node 22 + current, and Node 24 + minimum/current/next. Replace these pinned tags as DSH publishes newer versions.
|
|
250
|
+
|
|
251
|
+
`pnpm run check:host-api dsh-v0.1.7-rc.2` and `pnpm run check:host-api dsh-v0.2.0-rc.1` install the corresponding published DSH packages and run TypeScript typecheck plus tsdown build. Each CI host lane then installs the generated tarball in its pinned DSH host and runs the packed smoke.
|
|
243
252
|
|
|
244
253
|
Run `node scripts/verify-pack.mjs --keep` to retain the verified tarball for inspection or e2e use. The default `node scripts/verify-pack.mjs` removes its temporary directory after verification.
|
|
245
254
|
|
package/README.zh-CN.md
CHANGED
|
@@ -96,7 +96,7 @@ CI 的 packed host smoke 会把实际生成的 npm tarball 安装到真实 DSH
|
|
|
96
96
|
|
|
97
97
|
前置条件,缺一不可:
|
|
98
98
|
|
|
99
|
-
1. **dsh `^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1
|
|
99
|
+
1. **dsh `^0.1.2-rc.1 || ^0.1.5-alpha.1 || ^0.1.6-alpha.1 || ^0.1.7-alpha.1 || >=0.2.0-rc.1 <0.3.0-0`**(DSH `0.2.0-rc.1` 当前属于预发布版本;已验证宿主角色:minimum `dsh-v0.1.2-rc.1`、current `dsh-v0.1.7-rc.2`、next `dsh-v0.2.0-rc.2`;使用 DSH `<=0.1.1-rc.x` 的用户请使用 dsh-genui `0.9.8`)
|
|
100
100
|
2. **`pnpm` 在 PATH 上**:`dsh plugin` 命令依赖它。没有就 `corepack enable`(或 `npm i -g pnpm`),然后**新开一个终端**,确认 `pnpm -v` 有输出
|
|
101
101
|
|
|
102
102
|
安装并在 DSH 中激活(一行命令,自动带上全部依赖):
|
|
@@ -116,6 +116,12 @@ npm install @changfenhuang/dsh-genui
|
|
|
116
116
|
|
|
117
117
|
> ⚠️ **别用 `link:` 装一个刚 clone 的目录**——`link:` 不会安装插件的依赖(mermaid / three / react),装完渲染器会挂。正常安装请使用上面的 npm 命令;只有本地开发迭代才用 `link:`(见下文)。
|
|
118
118
|
|
|
119
|
+
### 认准包名与版本
|
|
120
|
+
|
|
121
|
+
- **包名必须带 scope**:npm 上另有一个同名但不带 scope 的 [`dsh-genui`](https://www.npmjs.com/package/dsh-genui)(Vue/OpenTiny 实现,与本仓库无关,维护者不同)。如果安装列表里描述是 "interactive charts, forms, calculators, dashboards, and mini apps",那是那个项目——装上会因 peer 不兼容被宿主拒绝。本插件始终是 **`@changfenhuang/dsh-genui`**。
|
|
122
|
+
- **宿主拒绝安装时**:支持的宿主范围由 peerDependencies 声明、宿主据此检查,升级到 npm 上 `latest` 的插件版本重装即可,无需改其他配置。
|
|
123
|
+
- **解析到的版本低于 `latest`**:多半是 pnpm 的发布冷静期在起作用(默认跳过发布不满 24 小时的版本,且不提示)。等一天重试,或在 profile 的 `pnpm-workspace.yaml` 的 `minimumReleaseAgeExclude` 里加入目标版本;profile 里精确 pin 过旧版本的,先改依赖声明再重装。
|
|
124
|
+
|
|
119
125
|
### 从旧 `@omdsh-dev` 包名迁移
|
|
120
126
|
|
|
121
127
|
如果你在 v0.9.2 之前通过 `github:omdsh-dev/dsh-genui` 安装过,pnpm 可能仍把依赖保存在旧的 `@omdsh-dev/dsh-genui` 键下,但仓库当前声明的包名已经是 `@changfenhuang/dsh-genui`。加载器按 profile 的依赖键解析插件,后续重装时就可能报 `Cannot find package '@changfenhuang/dsh-genui'`。请用当前包名重新添加一次:
|
|
@@ -163,7 +169,8 @@ dsh plugin --profile web add link:$PWD
|
|
|
163
169
|
- **ECharts 集成**:`echart` 节点渲染完整的 ECharts 图表,自动适配主题色、提示框和图例。两种模式:**预设简写**(`preset: 'bar' | 'line' | 'area' | 'pie' | 'scatter'` + `data`/`series`)可从 `chart` 节点快速升级;**完整选项**(`option` 字段)支持自定义图表类型、dataZoom、visualMap 等高级 ECharts 功能。echarts 引擎(~1 MB)按需懒加载——主包不含引擎,没有 `echart` 节点的对话不会下载它- **函数图**:`plot` 画曲线,参数滑块拖动实时重绘,支持自动动画
|
|
164
170
|
|
|
165
171
|
- **测验**:`quiz` 点选判题 + 解析 + 重试;带 `action` 时答案同时回传模型(判题仍本地即时)
|
|
166
|
-
-
|
|
172
|
+
- **本地判卷(交卷)**:多道选择题的每个 `radio` 设置 `group`、`answer` 和 `explanation`,再添加 `submit` 按钮。所需 radio 均已选择且当前 block 没有其他需要发送的表单状态时,分数、每题对错和解析直接显示在界面中;其他情况通过聚合 action 发送 `answers`、可选的 `fields`、`total` 和 `answered`。
|
|
173
|
+
- **提交分组**:`groups` 引用当前 block 中的 submission member key,来源为 `radio.group`、`checkbox.group`、`input.id`、`textarea.id`、`select.id`、`slider.id`。radio 需已选择,checkbox 组需至少选择一项,普通字段需在 trim 后非空。`groups` 控制提交所需成员与完成进度;payload 继续收集当前 block 中已填写的表单状态。未设置 `groups` 时,至少有一个已完成成员即可提交。
|
|
167
174
|
- **状态持久化**:答案、交卷锁定、输入值按「会话 + 内容指纹」自动保存——刷新页面/重开会话原样恢复,重渲染相同内容保留用户状态,新内容自动从头开始;上限 200 块 LRU 淘汰
|
|
168
175
|
- **表单语义**:`input` 回车 / `textarea` Ctrl+Enter 即时提交(`submit:true`),不用等失焦;带 `id` 的字段值进 submit 的 `fields` 收集
|
|
169
176
|
- **秘密禁令**:GenUI 不得索取密码、API Key、访问令牌、恢复码或其他秘密;密码输入即使出现也保持打码、不持久化、不进表单收集
|
|
@@ -172,7 +179,7 @@ dsh plugin --profile web add link:$PWD
|
|
|
172
179
|
- **事件循环**:按钮、复选框、单选、开关、下拉、输入、文本域、提交和测验等离散交互按一次手势一次事件立即回传模型;`slider` 连续拖动保留尾沿防抖,同一滑块只回传最终值,不同 `id` 的滑块互不合并。
|
|
173
180
|
- **工具通道**:`render_ui` 工具把同一份 spec 渲染成工具行卡片(交付物型 UI 走工具、回答型 UI 走围栏)
|
|
174
181
|
- **会话面板**:composer 上方常驻 dock,`render_ui` / `panel: true` 围栏原地更新同一块界面;`/panel` 命令客户端直开(`/panel <指令>` 转模型定制、`/panel clear` 清空);顶边框可拖拽调高;`append: true` 增量合并——同名标签页追加内容、新标签页新增;整面板默认最多 200 节点 / 200 条追加,达到上限后模型应发送 `replace` 重建
|
|
175
|
-
- **围栏自修**:默认开启;在 profile 的 cordis.patch.yml 中本插件条目的 `config:` 下设置 `fenceFeedback: false`
|
|
182
|
+
- **围栏自修**:默认开启;在 profile 的 cordis.patch.yml 中本插件条目的 `config:` 下设置 `fenceFeedback: false` 可以关闭同回合围栏修正。已调用 `validate_dsh_ui` 的 GenUI 回合若只输出 reasoning 并以 `stop` 结束,仍会转换为 `EMPTY_RESPONSE`,交给宿主重试策略处理。回答最终的 dsh-ui 围栏无法渲染时,插件借宿主的轮内转向(steer)把逐节点诊断送回**同一轮**,模型重发修好的围栏。每个 turn 最多发送两条 correction,渲染失败与“已验证但没有正式交付”共用该上限;同一 turn 内相同围栏 fingerprint 只修正一次,新 turn 可以重新修正相同错误;同一 turn 的零交付提醒只发送一次。plugin reload 后会从持久化的 correction message 恢复当前 turn 的围栏去重记录、已消费的 correction 次数以及零交付提醒记录。子代理回合不会收到围栏修正。
|
|
176
183
|
- **自愈与上限**:每个围栏过规格守卫——坏节点静默丢弃(同围栏其余组件照常渲染,单个坏组件不再拖垮整条围栏)、数值钳位、字符串截断,整树 ≤200 节点 / 8 层嵌套,病态 spec 不会拖垮界面
|
|
177
184
|
- **统一组件协议**:`card.label` → `title`、`table.data`/`table.items` → `rows`、`callout.kind`/`callout.desc` → `tone`/`content`(tone 值 `danger` → `error`)、`steps.items` → `steps`、`keyvalue.items` → `pairs`(记录内 `label` → `key`)、`file-tree.nodes` → `items`(记录内 `label` → `name`、有 children 时缺省 `dir`)等原生字段别名会在校验和渲染前确定性归一化;根级组件数组视为 `items`、双重编码的 JSON 字符串解一层;`validate_dsh_ui` 会报告归一化结果,并对原生组件未知字段给出警告,同时保持自定义 renderer 节点的透明兼容。
|
|
178
185
|
- **图错误自愈**:mermaid 渲染失败自动修复重试(剥反引号、引号化中文/空格标签、去 `<br/>`),仍失败才降级源码;错误图永不直接上屏
|
|
@@ -253,7 +260,9 @@ pnpm run check # 类型检查 + 全量测试 + 构建
|
|
|
253
260
|
|
|
254
261
|
安装锁定依赖后,检查脚本(`pnpm run check` 或 `npm run check`)使用固定的 DSH `0.1.2-rc.1` 发布包。
|
|
255
262
|
|
|
256
|
-
|
|
263
|
+
已验证宿主角色为 minimum `dsh-v0.1.2-rc.1`、current `dsh-v0.1.7-rc.2`、next `dsh-v0.2.0-rc.1`。DSH `0.2.0-rc.1` 当前属于预发布版本。CI 保持四条主要 lane:Node 22 + current,以及 Node 24 + minimum/current/next。DSH 发布新版本后,替换对应固定标签。
|
|
264
|
+
|
|
265
|
+
`pnpm run check:host-api dsh-v0.1.7-rc.2` 与 `pnpm run check:host-api dsh-v0.2.0-rc.1` 会安装对应 DSH 发布包,运行 TypeScript 类型检查与 tsdown 构建。每条 CI 宿主 lane 随后会把生成的 tarball 安装到固定版本的 DSH 宿主并运行 packed smoke。
|
|
257
266
|
|
|
258
267
|
运行 `node scripts/verify-pack.mjs --keep` 可保留已验收的 tarball,便于检查或运行 e2e;默认的 `node scripts/verify-pack.mjs` 会在验收后清理临时目录。
|
|
259
268
|
|
package/SKILL.md
CHANGED
|
@@ -40,7 +40,7 @@ description: "Render structured interactive UI inline through the dsh-ui fence.
|
|
|
40
40
|
- image: `{"type":"image","src":"/mmx-files/result.png","alt":"<user-language description>"}` — 展示浏览器可访问的 http(s) 或同源相对图片地址;懒加载;不支持 `file:`/`data:` 等本地或主动协议
|
|
41
41
|
- audio: `{"type":"audio","src":"/mmx-files/result.mp3","alt":"<user-language description>","loop":true?}` — 原生控制条;用户主动播放,不自动播放;仅 http(s) 或同源相对地址
|
|
42
42
|
- video: `{"type":"video","src":"/mmx-files/result.mp4","alt":"<user-language description>","poster":"/mmx-files/poster.jpg"?,"loop":true?,"muted":true?,"aspectRatio":"16:9|4:3|1:1|9:16"?}` — 原生播放/音量/全屏控制;不自动播放
|
|
43
|
-
- list: `{"type":"list","items":["..."] 或 [{"title":"...","desc":"..."}] 或嵌套节点(如 {"type":"badge","label":"TS"})}` —
|
|
43
|
+
- list: `{"type":"list","items":["..."] 或 [{"title":"...","desc":"..."}] 或嵌套节点(如 {"type":"badge","label":"TS"})}` — 行内可嵌节点(计入节点预算);记录中的 `label`/`name` 归一化为 `title`,`description`/`content`/`text`/`body`/`detail` 归一化为 `desc`;只有正文时将正文用作标题,未知字段会收到警告
|
|
44
44
|
- table: `{"type":"table","columns":["..."],"rows":[["...","..."]],"types":["text|num|delta|bar|badge"]?,"details":[[...]]?,"total":true?}` — 表头点击本地排序(升/降/还原,零往返);数值感知:千分位(`1,234`)、`k/m/b`、`万/亿`、`%`、货币符号都能按真实数值比较,纯数值列自动右对齐;**带符号单元格自动着色**(`+12.4%` 绿、`-3` 红,无需额外字段);`types` 可按列指定 `bar`(0-100 内联进度条)、`ring`(0-100 小环)、`spark`(单元格写 `"3,5,4,8"` 画微趋势线)、`badge`(胶囊标签)、`delta`(强制涨跌色)、`num`(强制右对齐)、`index`(行号)、`group`(首列当分组标题:该行只有第一格有内容时渲染成跨列小标题);`"total":true` 追加合计行(数值列自动求和);**`"export":true`**:表格上方出现「复制 Markdown / 复制 CSV」两个小按钮(纯本地剪贴板,不发请求);**`"filter":"输入框id"`**:把表格和某个 input/select 绑定,读者输入即时过滤(`filterColumn` 可限定列)——数据多时**默认就该配一个**;**`"sortField":"下拉id"`** 用下拉的值(列名)排序;**`"details"` 与 rows 同序**,第 i 项是该行展开后的内容(可放任意组件,`null` = 该行不可展开)——首列出现 chevron,点开在整行下方展开明细,适合「主表 + 明细」
|
|
45
45
|
- keyvalue: `{"type":"keyvalue","pairs":[{"key":"...","value":"..."}]}`
|
|
46
46
|
- timeline: `{"type":"timeline","items":[{"title":"...","desc":"...","time":"..."}]}`
|
|
@@ -67,9 +67,9 @@ description: "Render structured interactive UI inline through the dsh-ui fence.
|
|
|
67
67
|
- select: `{"type":"select","label":"...","options":["...","..."],"selected":下标?,"action":"pick"?,"id":"field-id"?}` — `selected` 预选某选项(缺省显示「请选择…」占位,不静默预选第一项);带 `id` 的选择跨刷新保留并进 submit 的 `fields`
|
|
68
68
|
- checkbox: `{"type":"checkbox","label":"<user-language option>","checked":true?,"action":"toggle"?,"group":"group-id"?}` — 默认保持逐次 `action` 行为;**加 `group` 进入多选聚合模式**:同组 checkbox 可反复勾选/取消,变化只在本地记录、不发逐次 action,兄弟 `submit` 一次性把该组已选 label 作为字符串数组放进 `answers`(例如 `{"styles":["<user-language option>","<user-language option>"]}`)
|
|
69
69
|
- slider: `{"type":"slider","label":"...","min":0,"max":100,"step":1,"value":n?,"action":"name"?,"id":"field-id"?}` — 数值表单滑块:实时显示数值;带 `id` 跨刷新保留并进 submit 的 `fields`(拖拽经防抖合并成一次 action)
|
|
70
|
-
- radio: `{"type":"radio","label":"<user-language label>","options":["<user-language option>","<user-language option>"],"selected":n?,"action":"pick"?}` — 单选;**加 `"group":"group-id"`
|
|
70
|
+
- radio: `{"type":"radio","label":"<user-language label>","options":["<user-language option>","<user-language option>"],"selected":n?,"action":"pick"?}` — 单选;**加 `"group":"group-id"` 进入聚合模式**:选择只本地记录、不发往返;可设置 `"answer":正确下标或标签` 和 `"explanation":"<user-language explanation>"`,在纯 radio 且没有其他待发送表单状态时本地判卷
|
|
71
71
|
- link: `{"type":"link","label":"...","href":"https://..."?}` — 仅 http(s)/mailto 协议被接受;无 `href` 时渲染为纯文本样式(不会假装可点)
|
|
72
|
-
- submit: `{"type":"submit","label":"<user-language action>","action":"grade","groups":["q1","styles"],"resetAction":"redo"?}` —
|
|
72
|
+
- submit: `{"type":"submit","label":"<user-language action>","action":"grade","groups":["q1","styles"],"resetAction":"redo"?}` — 聚合按钮:`groups` 引用当前 block 中的 submission member key,来源为 `radio.group`、`checkbox.group`、`input.id`、`textarea.id`、`select.id`、`slider.id`;radio 需已选择,checkbox 组需至少选择一项,普通 field 的值需在 trim 后非空。`groups` 控制提交所需成员与完成进度;未设置时,至少有一个已完成成员即可提交。纯 radio 范围含 `answer` 且本次没有范围外 payload 时可本地判卷;其余场景一次发送 `[genui-action]`,payload 继续收集当前 block 中已填写的表单状态,字段名称保持 `answers`、`fields`、`total`、`answered`
|
|
73
73
|
- switch: `{"type":"switch","label":"...","checked":true?,"action":"toggle"?}`
|
|
74
74
|
- textarea: `{"type":"textarea","label":"...","placeholder":"...","rows":n?,"value":"...","action":"save"?,"id":"field-id"?}` — action 在失焦和 **Ctrl/Cmd+Enter** 时触发;blur 仅值有变化才发送;带 `id` 的值刷新后保留
|
|
75
75
|
- tabs: `{"type":"tabs","tabs":[{"label":"...","items":[...]}]}` — 空 tab 可以省略 `items`,会按空数组处理
|
|
@@ -237,7 +237,7 @@ description: "Render structured interactive UI inline through the dsh-ui fence.
|
|
|
237
237
|
|
|
238
238
|
1. **围栏放哪,组件就出现在哪** —— 文字在前后自然流动,不要用工具、不要解释"这是一个围栏"。**围栏一闭合就立即渲染**(不等整条回答结束),所以可以边写文字边出组件
|
|
239
239
|
2. **组合优先**:复杂界面用 `grid`+`card`+`stat`+`table` 拼,不要追求单一巨型组件
|
|
240
|
-
3. **JSON 必须严格合法,发出前完成 4 步自检**:插件**只**修标点级小错(字符串内半角引号、尾随逗号);**缺括号/错括号等结构错误一律不修**,直接红横幅退化成代码块——写错就重发,别指望兜底。**最容易犯的错:字符串值里用了半角引号 `"`**——中文引语一律写 `“”` 或 `「」`。发出围栏前自检 4 条:① 括号配对:`{` 与 `}`、`[` 与 `]` 数量相等,**收尾序列逐个核对**(长表格最易在最后几行错位:把 `]]}]}` 写成 `]}]}]}`)② 无尾随逗号 ③ 值内引号用中文引号 ④ 最后一个字符必须是 `}
|
|
240
|
+
3. **JSON 必须严格合法,发出前完成 4 步自检**:插件**只**修标点级小错(字符串内半角引号、尾随逗号);**缺括号/错括号等结构错误一律不修**,直接红横幅退化成代码块——写错就重发,别指望兜底。**最容易犯的错:字符串值里用了半角引号 `"`**——中文引语一律写 `“”` 或 `「」`。发出围栏前自检 4 条:① 括号配对:`{` 与 `}`、`[` 与 `]` 数量相等,**收尾序列逐个核对**(长表格最易在最后几行错位:把 `]]}]}` 写成 `]}]}]}`)② 无尾随逗号 ③ 值内引号用中文引号 ④ 最后一个字符必须是 `}`(**`}` 之后不要再写任何字符**——实测常见错误是习惯性追加 `</p>` 或一句解释,整条围栏就解析不了)。不要在 JSON 字符串里放 markdown;超长表格/列表拆成多个组件分开发,宁短勿长
|
|
241
241
|
3.5. **字段名逐个核对(写错一个 = 整条围栏降级为代码块)**:某个组件的必填字段写错 / 缺失 → 该组件被丢弃 → 整份 spec 判定不可渲染 → 用户只看到一段裸 JSON。高频误写:`callout` 正文是 `content`(不是 text/body);`table` 要 `columns` + `rows`(不是 data,只给二维 rows 时首行会当表头);`keyvalue` 要 `pairs:[{key,value}]`(不是 items);`diff` 是 `diffs`;图片/音视频是 `src`;`code` 是 `code`、`copy` 是 `text`。拿不准就调 `validate_dsh_ui`,不要凭直觉命名
|
|
242
242
|
4. **不要嵌套围栏**:dsh-ui 里不要再包 ``` 代码围栏
|
|
243
243
|
5. **深色主题友好**:配色选深底亮色;UI 主题跟随应用
|
|
@@ -246,4 +246,16 @@ description: "Render structured interactive UI inline through the dsh-ui fence.
|
|
|
246
246
|
8. **规格要紧凑**:整棵组件树 ≤200 节点、≤8 层嵌套(超出部分会被渲染器裁掉),避免巨型 spec
|
|
247
247
|
9. **一个主题选一个主组件**:命中映射表后选**一种**组件承载,同一信息不要用两种组件重复表达(同一批数据又画 bars 又画 donut = 冗余)
|
|
248
248
|
10. **数量纪律**:一条回答 3–8 个组件为宜,宁缺毋滥。反例:该用 `table` 对比时写三段 `text`;一个 `stat` 能说清的事套 `card`+`grid`;与内容无关的 `scene3d` 炫技——3D 只在内容本身就是几何/空间时才用
|
|
249
|
+
12. **给命令就给能直接粘的**:多行 python 一律写成 heredoc 包装的**一整段** shell 代码块(`python - <<'PY'` … `PY`),不要用 `python -c "…"` 配反斜杠续行——续行在复制/粘贴里最容易碎成多行,用户还得自己拼回 heredoc。单行表达式才用 `-c`,且必须真的在一行内写完。示例:
|
|
250
|
+
|
|
251
|
+
````text
|
|
252
|
+
cd /path/to/repo
|
|
253
|
+
python - <<'PY'
|
|
254
|
+
import json
|
|
255
|
+
d = json.load(open('report.json'))
|
|
256
|
+
print(d.get('status'))
|
|
257
|
+
PY
|
|
258
|
+
````
|
|
259
|
+
|
|
260
|
+
同理:需要用户执行的脚本放进 `code` 节点(`lang`: `bash`/`python`)或正文围栏,并保证**从第一行到最后一行一次粘进终端就能跑**。
|
|
249
261
|
11. **先验后发(复杂 UI)**:发出 ```dsh-ui 围栏前,若 spec ≥3 个组件或含 `table`(长表格最易括号错位),先调用 `validate_dsh_ui` 工具(参数 `spec` 传围栏内的 JSON 文本)验证;返回 `next=fix_and_revalidate` 就按诊断修正后重新验证;返回 `next=emit_fence` 再发出;返回 `next=emit_repaired_fence` 时直接照抄 `repaired_json` 发出,无需再次验证;简单 UI(≤2 个组件)不必验证,渲染器会自动修复大部分标点/括号错误
|