dsh-plugin-choice-refresh 0.1.3 → 0.1.5
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 +27 -0
- package/README.en.md +117 -0
- package/README.md +16 -2
- package/docs/choice-buttons.png +0 -0
- package/docs/choice-more-options.png +0 -0
- package/lib/client.js +29 -24
- package/package.json +16 -3
- package/scripts/selfcheck.mjs +29 -17
- package/scripts/smoke-client.mjs +35 -39
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.1.5] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
- 适配 dsh 0.1.2-rc.1:conversation.composer 链条目 select 入参由 `{interactions}` 改为 `{pendingInteraction}`(原生 PendingQuestion 单对象),按 `kind`/questions 鸭子类型认领,plan-review 继续放行;
|
|
6
|
+
- 待答载体 PendingChoice 改为包装 PendingQuestion(`.answer({answers})` / `.cancel()` 直调,替代旧 `wait.respond` 协议);
|
|
7
|
+
- steer 兜底路径改为 `ctx.remote.session.prompt`(0.1.2 起 remote 命名空间为单数且要求 requestId);主路径 sessions.binding().session.prompt 不变。
|
|
8
|
+
# Changelog
|
|
9
|
+
|
|
10
|
+
## [0.1.4] - 2026-08-29
|
|
11
|
+
|
|
12
|
+
- 文档:兼容性补实测版本声明(DSH 0.1.1-rc.2);
|
|
13
|
+
- 元数据:description 加「生态唯一」钩子,keywords 10 → 16;
|
|
14
|
+
- 新增英文版 README.en.md 与双语切换行;CHANGELOG.md 纳入 npm files。
|
|
15
|
+
## [0.1.3] - 2026-08-25
|
|
16
|
+
|
|
17
|
+
- 相关插件段新增 dsh-plugin-windows-guard(Windows 环境防坑守则 skill 插件,互相引流)。
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## [0.1.2] - 2026-08-21
|
|
21
|
+
|
|
22
|
+
- README 新增「相关插件」互相引用段(列出同系列已发布插件,npm / GitHub 链接 + 一句话说明),互相引流。
|
|
23
|
+
|
|
24
|
+
## [0.1.1] - 2026-08-19
|
|
25
|
+
|
|
26
|
+
- 首发:选择增强(「重新生成选项」/「更多选项」按钮),纯前端实现,支持文字与图片选项卡。
|
|
27
|
+
- 发布 npm、GitHub topics、dsh-market 收录(issue #40)。
|
package/README.en.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
[中文](./README.md) | **English**
|
|
2
|
+
|
|
3
|
+
# dsh-plugin-choice-refresh
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
**The only choice-enhancement plugin in the dsh plugin market**: it adds two buttons to the choice cards of `ask_user_question` / `ask_user_choice` —
|
|
10
|
+
not happy with any of the options? One click has the model come back with a completely different batch. Too few options? One click has the model add more.
|
|
11
|
+
|
|
12
|
+
| Button | Scenario | Effect |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| 🔄 **Regenerate options** (「重新生成选项」) | None of the offered options works for you | One click has the model re-ask with a completely different batch of options (new angle / style / dimension, 5–8 options) |
|
|
15
|
+
| ➕ **More options** (「更多选项」) | Too few options to choose from | One click has the model re-ask while keeping the original options and adding new ones (6–10 options in total) |
|
|
16
|
+
|
|
17
|
+
Purely client-side interaction, **registers no new tools and modifies no core packages**; works on both native text option cards and the
|
|
18
|
+
[dsh-plugin-image-tools](https://github.com/Pasumao/dsh-plugin-image-tools) image option cards (image cards are rendered directly by this plugin, reusing its image routes).
|
|
19
|
+
|
|
20
|
+
## Screenshot
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
Two extra buttons appear automatically at the bottom of the card:
|
|
25
|
+
|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
After clicking "➕ More options": the model keeps the original options, adds new ones up to 6–10 total, and re-asks.
|
|
29
|
+
|
|
30
|
+
> Real Web GUI screenshots.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- 🔄 **Regenerate options**: when none of the options offered by `ask_user_question` / `ask_user_choice` works for you, one click cancels the current question and has the model re-ask with a completely different batch of options (new angle / style / dimension, 5–8 options);
|
|
35
|
+
- ➕ **More options**: when the options are too few to choose from, one click has the model keep the original options and add new ones (6–10 in total);
|
|
36
|
+
- Purely client-side interaction, **registers no new tools and modifies no core packages**;
|
|
37
|
+
- Works on native text option cards and `dsh-plugin-image-tools` image option cards alike;
|
|
38
|
+
- Compatible with the `plan-review` plan review card, without affecting the native flow.
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
No configuration needed — install and it works:
|
|
43
|
+
|
|
44
|
+
- Reads no environment variables, needs no API key / token, writes no config files;
|
|
45
|
+
- Button labels follow `ctx.locale` (zh / en bilingual), with no extra setup;
|
|
46
|
+
- Behavior is driven by the `【系统 · 选项刷新】` (system · option refresh) / `【系统 · 更多选项】` (system · more options) messages injected by the buttons; the model re-asks according to those instructions.
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```powershell
|
|
51
|
+
# npm (recommended)
|
|
52
|
+
dsh plugin --profile web add dsh-plugin-choice-refresh
|
|
53
|
+
# or GitHub
|
|
54
|
+
dsh plugin --profile web add github:Pasumao/dsh-plugin-choice-refresh
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Install from source (local development / debugging):
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
git clone https://github.com/Pasumao/dsh-plugin-choice-refresh.git
|
|
61
|
+
cd dsh-plugin-choice-refresh
|
|
62
|
+
npm install # or pnpm install
|
|
63
|
+
# Mount into the profile via link:, see 设计说明.md (design notes) for details
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Once installed, refresh the browser and it takes effect (hot-reloads automatically when the profile already has dsh-client-hmr — no restart needed).
|
|
67
|
+
For local development, you can mount the plugin via `link:` instead — see `设计说明.md` (design notes) for details.
|
|
68
|
+
|
|
69
|
+
## Usage
|
|
70
|
+
|
|
71
|
+
When the model asks a question as usual via `ask_user_question` / `ask_user_choice` (e.g. following the novel-* skills), two extra buttons — **Regenerate options** and **More options** — appear at the bottom of the choice card:
|
|
72
|
+
|
|
73
|
+
- Click **Regenerate options**: the current question is cancelled, and a `【系统 · 选项刷新】` user message is injected for the model, which immediately re-asks with a fresh set of options;
|
|
74
|
+
- Click **More options**: same as above — a `【系统 · 更多选项】` message is injected, and the model keeps the original options and adds more.
|
|
75
|
+
|
|
76
|
+
> The injected message stays short (one or two lines) and remains in the session as a user message so the model can understand the instruction;
|
|
77
|
+
> the UI displays it verbatim.
|
|
78
|
+
|
|
79
|
+
> Note: the refresh / top-up is done by the model, which re-invokes the question tool after reading the instruction — this is a "soft" enhancement, and the model may occasionally adjust the question itself. If you are still not satisfied with the result, click the button again or simply type what you want.
|
|
80
|
+
|
|
81
|
+
## Self-check
|
|
82
|
+
|
|
83
|
+
```powershell
|
|
84
|
+
# In the plugin directory (local dev clone / link mount)
|
|
85
|
+
npm run smoke # selfcheck + smoke-client
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Compatibility
|
|
89
|
+
|
|
90
|
+
- Tested on DSH `0.1.1-rc.2`; requires the DSH web GUI (an entry in the `conversation.composer` chain, priority -300).
|
|
91
|
+
- Directly compatible with the image cards of a co-installed `dsh-plugin-image-tools`; without it, plain text options work as usual.
|
|
92
|
+
- `plan-review` intents (plan review cards) are passed through to the native `PlanReviewPanel`, unaffected.
|
|
93
|
+
- Depends on the client services `slots` / `locale` / `sessions` / `remote`.
|
|
94
|
+
|
|
95
|
+
## Related plugins
|
|
96
|
+
|
|
97
|
+
This plugin is part of **Pasumao's dsh plugin ecosystem**; the other published plugins in the series pair well with it:
|
|
98
|
+
|
|
99
|
+
| Plugin (npm) | GitHub | Description |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| [dsh-notify](https://www.npmjs.com/package/dsh-notify) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-notify) | Native Windows notifications + system tray |
|
|
102
|
+
| [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-dev-kb) | Plugin development knowledge base (full mirror of the official docs + skill) |
|
|
103
|
+
| [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-image-tools) | Image choice cards + inline images in replies + image handoff for text-only models |
|
|
104
|
+
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-table-zoom) | Floating-window viewer for long chat tables + one-click Markdown copy |
|
|
105
|
+
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows environment guard: rule skills + mojibake detection / dangerous-write blocking / encoding diagnostics & repair |
|
|
106
|
+
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub repo](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code-style file explorer + editable preview |
|
|
107
|
+
|
|
108
|
+
> For the rest of the series, see [Pasumao · dsh plugins](https://github.com/Pasumao); if you find them useful, a ⭐ on GitHub is always appreciated.
|
|
109
|
+
|
|
110
|
+
## AI generation disclosure
|
|
111
|
+
|
|
112
|
+
Code and documentation are AI-assisted (DeepSeek Harness), all human-reviewed and verified on a live install
|
|
113
|
+
(`npm run smoke`: selfcheck + fake-client smoke test).
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
[MIT](./LICENSE)
|
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|

|
|
5
5
|

|
|
6
6
|
|
|
7
|
+
[**中文**](./README.md) | [English](./README.en.md)
|
|
8
|
+
|
|
7
9
|
**dsh 插件市场里唯一的选择增强插件**:给 `ask_user_question` / `ask_user_choice` 的选择卡加上两个按钮——
|
|
8
10
|
选项都不满意?一键让模型换一批完全不同的。选项太少?一键让模型补充。
|
|
9
11
|
|
|
@@ -16,6 +18,18 @@
|
|
|
16
18
|
[dsh-plugin-image-tools](https://github.com/Pasumao/dsh-plugin-image-tools) 的
|
|
17
19
|
图片选项卡都生效(图片卡由本插件直接渲染,复用其图片路由)。
|
|
18
20
|
|
|
21
|
+
## 效果图
|
|
22
|
+
|
|
23
|
+

|
|
24
|
+
|
|
25
|
+
选择卡底部自动多出两个按钮:
|
|
26
|
+
|
|
27
|
+

|
|
28
|
+
|
|
29
|
+
点「➕ 更多选项」后:模型保留原选项、补充到 6~10 个并重新提问。
|
|
30
|
+
|
|
31
|
+
> 真实 Web GUI 截图。
|
|
32
|
+
|
|
19
33
|
## 功能
|
|
20
34
|
|
|
21
35
|
- 🔄 **重新生成选项**:`ask_user_question` / `ask_user_choice` 给出的选项都不满意时,
|
|
@@ -78,7 +92,7 @@ npm run smoke # selfcheck + smoke-client
|
|
|
78
92
|
|
|
79
93
|
## 兼容性
|
|
80
94
|
|
|
81
|
-
-
|
|
95
|
+
- 实测于 DSH `0.1.2-rc.1`(0.1.5 起适配该版的 `pendingInteraction` / `PendingQuestion` 新协议);需要 DSH web GUI(`conversation.composer` 链条目,priority -300)。
|
|
82
96
|
- 与 `dsh-plugin-image-tools` 共存的图片卡直接兼容;未安装时纯文字选项照常。
|
|
83
97
|
- `plan-review` 意图(计划审阅卡)放行给原生 `PlanReviewPanel`,不受影响。
|
|
84
98
|
- 依赖客户端服务:`slots` / `locale` / `sessions` / `remote`。
|
|
@@ -93,7 +107,7 @@ npm run smoke # selfcheck + smoke-client
|
|
|
93
107
|
| [dsh-plugin-dev-kb](https://www.npmjs.com/package/dsh-plugin-dev-kb) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-dev-kb) | 插件开发知识库(官方文档完整镜像 + 技能) |
|
|
94
108
|
| [dsh-plugin-image-tools](https://www.npmjs.com/package/dsh-plugin-image-tools) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-image-tools) | 图片选择卡 + 回复内嵌图片 + 盲模型收图 |
|
|
95
109
|
| [dsh-plugin-table-zoom](https://www.npmjs.com/package/dsh-plugin-table-zoom) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-table-zoom) | 聊天长表格浮窗查看 + 一键复制 Markdown |
|
|
96
|
-
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows
|
|
110
|
+
| [dsh-plugin-windows-guard](https://www.npmjs.com/package/dsh-plugin-windows-guard) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-windows-guard) | Windows 环境防坑:守则技能 + 乱码检测 / 危险写拦截 / 编码诊断修复 |
|
|
97
111
|
| [dsh-plugin-workbench](https://www.npmjs.com/package/dsh-plugin-workbench) | [GitHub 仓库](https://github.com/Pasumao/dsh-plugin-workbench) | VS Code 风格文件浏览器 + 可编辑预览 |
|
|
98
112
|
|
|
99
113
|
> 本系列其余插件见 [Pasumao · dsh 插件](https://github.com/Pasumao);觉得好用欢迎到 GitHub 点 ⭐。
|
|
Binary file
|
|
Binary file
|
package/lib/client.js
CHANGED
|
@@ -178,23 +178,20 @@ window.__ModuleLoader__.load({
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
// ------------------------------------------------------------------
|
|
181
|
-
//
|
|
181
|
+
// 待答载体(dsh 0.1.2 起 composer 链的 matched 即原生 PendingQuestion:
|
|
182
|
+
// .answer({answers}) / .cancel() / .key / .sessionId / .questions,
|
|
183
|
+
// 这里包一层维持组件既有协议)
|
|
182
184
|
// ------------------------------------------------------------------
|
|
183
185
|
var PendingChoice = class {
|
|
184
|
-
constructor(
|
|
185
|
-
get key() { return this.
|
|
186
|
-
get sessionId() { return this.
|
|
187
|
-
get questions() { return this.
|
|
186
|
+
constructor(matched) { this.matched = matched; }
|
|
187
|
+
get key() { return this.matched.key; }
|
|
188
|
+
get sessionId() { return this.matched.sessionId; }
|
|
189
|
+
get questions() { return this.matched.questions; }
|
|
188
190
|
async answer(answer) {
|
|
189
|
-
|
|
190
|
-
if (!receipt.accepted) throw new Error('question response rejected: ' + receipt.reason);
|
|
191
|
+
await this.matched.answer(answer);
|
|
191
192
|
}
|
|
192
193
|
async cancel() {
|
|
193
|
-
|
|
194
|
-
ok: false,
|
|
195
|
-
error: { code: 'cancelled', message: 'the user closed this question request', details: {} }
|
|
196
|
-
});
|
|
197
|
-
if (!receipt.accepted) throw new Error('question cancellation rejected: ' + receipt.reason);
|
|
194
|
+
await this.matched.cancel();
|
|
198
195
|
}
|
|
199
196
|
};
|
|
200
197
|
|
|
@@ -420,8 +417,12 @@ window.__ModuleLoader__.load({
|
|
|
420
417
|
if (binding !== void 0 && binding.session !== void 0 && typeof binding.session.prompt === 'function') {
|
|
421
418
|
return binding.session.prompt([{ type: 'text', text: text }], 'steer');
|
|
422
419
|
}
|
|
423
|
-
if (ctx.remote !== void 0 && ctx.remote.
|
|
424
|
-
|
|
420
|
+
if (ctx.remote !== void 0 && ctx.remote.session !== void 0 && typeof ctx.remote.session.prompt === 'function') {
|
|
421
|
+
// dsh 0.1.2 起 remote 命名空间为 session(单数)且要求 requestId。
|
|
422
|
+
var requestId = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
|
423
|
+
? crypto.randomUUID()
|
|
424
|
+
: 'dsr-' + Date.now() + '-' + Math.random().toString(36).slice(2);
|
|
425
|
+
return ctx.remote.session.prompt({ requestId: requestId, sessionId: sessionId, mode: 'steer', content: [{ type: 'text', text: text }] });
|
|
425
426
|
}
|
|
426
427
|
throw new Error('choice-refresh: no session prompt transport available');
|
|
427
428
|
};
|
|
@@ -758,18 +759,22 @@ window.__ModuleLoader__.load({
|
|
|
758
759
|
* 认领所有「非 plan-review」的 question 交互(priority -300,先于
|
|
759
760
|
* image-tools 的 -100 与原生 UI 的 0)。plan-review 意图放行给原生
|
|
760
761
|
* PlanReviewPanel;纯文字 / 带图片标记的问题都归本插件渲染。
|
|
762
|
+
* dsh 0.1.2 起 select 入参为 { pendingInteraction }(单对象,即原生
|
|
763
|
+
* PendingQuestion 实例;无交互时为 null),不再是 { interactions } 数组。
|
|
761
764
|
*/
|
|
765
|
+
function isPendingQuestion(value) {
|
|
766
|
+
return value !== null && typeof value === 'object'
|
|
767
|
+
&& Array.isArray(value.questions)
|
|
768
|
+
&& typeof value.answer === 'function'
|
|
769
|
+
&& typeof value.cancel === 'function';
|
|
770
|
+
}
|
|
762
771
|
function selectChoice(_ref) {
|
|
763
|
-
var
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
if (isPlanReviewBatch(questions)) continue;
|
|
770
|
-
return item;
|
|
771
|
-
}
|
|
772
|
-
return null;
|
|
772
|
+
var pendingInteraction = _ref.pendingInteraction;
|
|
773
|
+
if (!isPendingQuestion(pendingInteraction)) return null;
|
|
774
|
+
if (pendingInteraction.kind === 'plan-review') return null;
|
|
775
|
+
// 兜底再判一次 plan-review 意图(老版本宿主无 kind 字段时仍能放行)。
|
|
776
|
+
if (isPlanReviewBatch(pendingInteraction.questions)) return null;
|
|
777
|
+
return pendingInteraction;
|
|
773
778
|
}
|
|
774
779
|
|
|
775
780
|
// ------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-choice-refresh",
|
|
3
|
-
"description": "DSH
|
|
4
|
-
"version": "0.1.
|
|
3
|
+
"description": "DSH 选择增强插件(生态唯一):给 ask_user_question / ask_user_choice 的选择卡加「重新生成选项 / 更多选项」两个按钮——选项都不满意一键换一批(换角度/维度,5~8 个),选项太少一键补充到 6~10 个。纯前端交互实现,不改核心包、不新增工具,原生文字卡与图片选择卡通吃。",
|
|
4
|
+
"version": "0.1.5",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -12,8 +12,11 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"lib",
|
|
14
14
|
"scripts",
|
|
15
|
+
"docs",
|
|
15
16
|
"cordis.patch.yml",
|
|
16
17
|
"README.md",
|
|
18
|
+
"README.en.md",
|
|
19
|
+
"CHANGELOG.md",
|
|
17
20
|
"LICENSE",
|
|
18
21
|
"设计说明.md"
|
|
19
22
|
],
|
|
@@ -33,6 +36,10 @@
|
|
|
33
36
|
"peerDependencies": {
|
|
34
37
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
35
38
|
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0"
|
|
42
|
+
},
|
|
36
43
|
"engines": {
|
|
37
44
|
"node": ">=18"
|
|
38
45
|
},
|
|
@@ -45,7 +52,13 @@
|
|
|
45
52
|
"plugin",
|
|
46
53
|
"choice",
|
|
47
54
|
"refresh",
|
|
48
|
-
"regenerate",
|
|
55
|
+
"regenerate-options",
|
|
56
|
+
"more-options",
|
|
57
|
+
"ask-user",
|
|
58
|
+
"question",
|
|
59
|
+
"ux",
|
|
60
|
+
"chat-ui",
|
|
61
|
+
"frontend",
|
|
49
62
|
"options"
|
|
50
63
|
],
|
|
51
64
|
"license": "MIT",
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -10,9 +10,11 @@ import { strict as assert } from 'node:assert'
|
|
|
10
10
|
|
|
11
11
|
const require = createRequire(import.meta.url)
|
|
12
12
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
13
|
+
// 优先用插件自带的 devDependencies react(0.1.5 起 dsh-app 根部的 react 不再保证
|
|
14
|
+
// 是 17+、无 jsx-runtime);本地没有时退回 profile 共享树。
|
|
15
|
+
const localRequire = createRequire(join(ROOT, 'noop.cjs'))
|
|
16
|
+
const reactRoot = dirname(localRequire.resolve('react'))
|
|
17
|
+
const reactModule = localRequire('react')
|
|
16
18
|
|
|
17
19
|
// 1) 模拟浏览器模块加载器并物化 factory
|
|
18
20
|
let spec = null
|
|
@@ -107,26 +109,36 @@ ok('parseRecommendedLabel strips suffix', () => {
|
|
|
107
109
|
assert.deepEqual(mod.parseRecommendedLabel('A'), { label: 'A', recommended: false })
|
|
108
110
|
})
|
|
109
111
|
|
|
110
|
-
// --- selectChoice
|
|
112
|
+
// --- selectChoice(dsh 0.1.2 协议:入参 { pendingInteraction },载体即原生
|
|
113
|
+
// PendingQuestion——questions 直挂、带 answer()/cancel()) ---
|
|
114
|
+
function fakePending(questions, extra) {
|
|
115
|
+
return {
|
|
116
|
+
kind: 'question',
|
|
117
|
+
key: 'q:test',
|
|
118
|
+
sessionId: 's1',
|
|
119
|
+
questions,
|
|
120
|
+
answer: async () => {},
|
|
121
|
+
cancel: async () => {},
|
|
122
|
+
...extra,
|
|
123
|
+
}
|
|
124
|
+
}
|
|
111
125
|
ok('selectChoice claims normal question', () => {
|
|
112
|
-
const
|
|
113
|
-
assert.equal(mod.selectChoice({
|
|
126
|
+
const pending = fakePending([{ id: 'a', question: 'q', options: [{ label: 'A' }] }])
|
|
127
|
+
assert.equal(mod.selectChoice({ pendingInteraction: pending }), pending)
|
|
114
128
|
})
|
|
115
|
-
ok('selectChoice
|
|
116
|
-
const plan =
|
|
117
|
-
|
|
118
|
-
assert.equal(mod.selectChoice({ interactions: [approval, plan] }), null, 'plan-review 应放行给原生 PlanReviewPanel')
|
|
129
|
+
ok('selectChoice passes plan-review to native', () => {
|
|
130
|
+
const plan = fakePending([{ id: 'a', question: 'q', detail: 'plan', intent: { kind: 'plan-review', approve: '执行' }, options: [{ label: '执行' }] }], { kind: 'plan-review' })
|
|
131
|
+
assert.equal(mod.selectChoice({ pendingInteraction: plan }), null, 'plan-review 应放行给原生 PlanReviewPanel')
|
|
119
132
|
})
|
|
120
|
-
ok('selectChoice
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
assert.equal(mod.selectChoice({
|
|
124
|
-
assert.equal(mod.selectChoice({ interactions: [approval] }), null)
|
|
133
|
+
ok('selectChoice rejects non-question values', () => {
|
|
134
|
+
assert.equal(mod.selectChoice({ pendingInteraction: null }), null)
|
|
135
|
+
assert.equal(mod.selectChoice({ pendingInteraction: { questions: [] } }), null)
|
|
136
|
+
assert.equal(mod.selectChoice({}), null)
|
|
125
137
|
})
|
|
126
138
|
ok('selectChoice claims image-marked question too', () => {
|
|
127
139
|
const marker = '<!--dsh-pick:v1:' + encodeBase64Url(JSON.stringify({ pickId: 'p1', images: [0] })) + '-->'
|
|
128
|
-
const
|
|
129
|
-
assert.equal(mod.selectChoice({
|
|
140
|
+
const pending = fakePending([{ id: 'c', question: '选图', detail: marker, options: [{ label: 'A' }] }])
|
|
141
|
+
assert.equal(mod.selectChoice({ pendingInteraction: pending }), pending, '带图问题应由本插件渲染(含刷新/更多按钮)')
|
|
130
142
|
})
|
|
131
143
|
|
|
132
144
|
console.log(`\n[dsh-plugin-choice-refresh] ${passed} checks passed`)
|
package/scripts/smoke-client.mjs
CHANGED
|
@@ -18,11 +18,13 @@ import { strict as assert } from 'node:assert'
|
|
|
18
18
|
|
|
19
19
|
const require = createRequire(import.meta.url)
|
|
20
20
|
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
// 优先用插件自带的 devDependencies react(0.1.5 起 dsh-app 根部 react 可能被降级
|
|
22
|
+
// 到 16.8、无 jsx-runtime,与 react-dom 19 不同源);本地没有时退回 profile 共享树。
|
|
23
|
+
const localRequire = createRequire(join(ROOT, 'noop.cjs'))
|
|
24
|
+
const reactRoot = dirname(localRequire.resolve('react'))
|
|
23
25
|
const jsxRuntime = join(reactRoot, 'jsx-runtime.js')
|
|
24
|
-
const reactModule =
|
|
25
|
-
const reactDomServer =
|
|
26
|
+
const reactModule = localRequire('react')
|
|
27
|
+
const reactDomServer = localRequire('react-dom/server')
|
|
26
28
|
|
|
27
29
|
// 1) 模拟浏览器模块加载器
|
|
28
30
|
let spec = null
|
|
@@ -70,21 +72,20 @@ function encodeBase64Url(json) {
|
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
// 2) 纯文字问题:渲染卡片 + 两个增强按钮
|
|
75
|
+
// (dsh 0.1.2 起 matched 即原生 PendingQuestion:questions 直挂、answer()/cancel())
|
|
73
76
|
const plainWait = {
|
|
77
|
+
kind: 'question',
|
|
74
78
|
key: 'q:1',
|
|
75
79
|
sessionId: 's1',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
{
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
],
|
|
86
|
-
},
|
|
87
|
-
respond: async (r) => ({ accepted: true }),
|
|
80
|
+
questions: [
|
|
81
|
+
{ id: 'a', question: '选一个封面风格', header: '封面', detail: '请选择你喜欢的风格', multiSelect: false, options: [
|
|
82
|
+
{ label: '赛博 (Recommended)', description: '霓虹质感' },
|
|
83
|
+
{ label: '水墨' },
|
|
84
|
+
{ label: '极简' },
|
|
85
|
+
] },
|
|
86
|
+
],
|
|
87
|
+
answer: async () => {},
|
|
88
|
+
cancel: async () => {},
|
|
88
89
|
}
|
|
89
90
|
let html = renderToString(reactModule.createElement(mod.ChoiceComposer, { matched: plainWait, t: fakeT, ctx: fakeCtx }))
|
|
90
91
|
assert.ok(html.includes('dsr-card'), '卡片未渲染')
|
|
@@ -98,20 +99,18 @@ assert.ok(!html.includes('<!--dsh-pick:v1:'), '图片标记不应出现')
|
|
|
98
99
|
// 3) 带图片标记的问题:渲染图片卡片(image-tools 路由)
|
|
99
100
|
const marker = '<!--dsh-pick:v1:' + encodeBase64Url(JSON.stringify({ pickId: 'pick-7', images: [0, 2] })) + '-->'
|
|
100
101
|
const imageWait = {
|
|
102
|
+
kind: 'question',
|
|
101
103
|
key: 'q:2',
|
|
102
104
|
sessionId: 's2',
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
],
|
|
113
|
-
},
|
|
114
|
-
respond: async (r) => ({ accepted: true }),
|
|
105
|
+
questions: [
|
|
106
|
+
{ id: 'b', question: '选一张封面图', detail: marker, multiSelect: false, options: [
|
|
107
|
+
{ label: '深海' },
|
|
108
|
+
{ label: '极光' },
|
|
109
|
+
{ label: '沙漠' },
|
|
110
|
+
] },
|
|
111
|
+
],
|
|
112
|
+
answer: async () => {},
|
|
113
|
+
cancel: async () => {},
|
|
115
114
|
}
|
|
116
115
|
html = renderToString(reactModule.createElement(mod.ChoiceComposer, { matched: imageWait, t: fakeT, ctx: fakeCtx }))
|
|
117
116
|
assert.ok(html.includes('/dsh-plugin-image-tools/pick-7/0'), '第一张图片 URL 缺失')
|
|
@@ -121,21 +120,18 @@ assert.ok(html.includes('放大查看'), '放大触发按钮缺失')
|
|
|
121
120
|
assert.ok(html.includes('重新生成选项'), '带图问题的刷新按钮缺失')
|
|
122
121
|
assert.ok(!html.includes('<!--dsh-pick:v1:'), '图片标记不应泄漏到界面')
|
|
123
122
|
|
|
124
|
-
// 4) plan-review
|
|
123
|
+
// 4) plan-review 批放行(dsh 0.1.2:载体 kind 即为 'plan-review')
|
|
125
124
|
const planWait = {
|
|
126
|
-
kind: '
|
|
125
|
+
kind: 'plan-review',
|
|
127
126
|
key: 'q:3',
|
|
128
127
|
sessionId: 's3',
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
],
|
|
135
|
-
},
|
|
136
|
-
respond: async (r) => ({ accepted: true }),
|
|
128
|
+
questions: [
|
|
129
|
+
{ id: 'c', question: '确认执行计划', detail: '# 计划\n1. 做 A', intent: { kind: 'plan-review', approve: '确认执行' }, options: [{ label: '确认执行' }, { label: '拒绝' }] },
|
|
130
|
+
],
|
|
131
|
+
answer: async () => {},
|
|
132
|
+
cancel: async () => {},
|
|
137
133
|
}
|
|
138
|
-
assert.equal(mod.selectChoice({
|
|
134
|
+
assert.equal(mod.selectChoice({ pendingInteraction: planWait }), null, 'plan-review 不应被本插件认领')
|
|
139
135
|
|
|
140
136
|
// 5) Lightbox 单独渲染
|
|
141
137
|
const lightboxHtml = renderToString(reactModule.createElement(mod.Lightbox, {
|