dsh-plugin-choice-refresh 0.1.4 → 0.1.6
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 +14 -0
- package/README.en.md +1 -1
- package/README.md +4 -1
- package/lib/client.js +29 -24
- package/package.json +5 -1
- package/scripts/selfcheck.mjs +29 -17
- package/scripts/smoke-client.mjs +35 -39
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.1.6] - 2026-09-04
|
|
4
|
+
|
|
5
|
+
- 文档:中英 README「兼容性」节补全 dsh 0.1.2-rc.1 适配细节(`pendingInteraction` /
|
|
6
|
+
`PendingQuestion` 协议、应答走 `.answer({answers})` / `.cancel()`);测试基建自带
|
|
7
|
+
react/react-dom@19 devDependencies,`npm run smoke` 不再依赖宿主安装布局。
|
|
8
|
+
# Changelog
|
|
9
|
+
|
|
10
|
+
## [0.1.5] - 2026-09-04
|
|
11
|
+
|
|
12
|
+
- 适配 dsh 0.1.2-rc.1:conversation.composer 链条目 select 入参由 `{interactions}` 改为 `{pendingInteraction}`(原生 PendingQuestion 单对象),按 `kind`/questions 鸭子类型认领,plan-review 继续放行;
|
|
13
|
+
- 待答载体 PendingChoice 改为包装 PendingQuestion(`.answer({answers})` / `.cancel()` 直调,替代旧 `wait.respond` 协议);
|
|
14
|
+
- steer 兜底路径改为 `ctx.remote.session.prompt`(0.1.2 起 remote 命名空间为单数且要求 requestId);主路径 sessions.binding().session.prompt 不变。
|
|
15
|
+
# Changelog
|
|
16
|
+
|
|
3
17
|
## [0.1.4] - 2026-08-29
|
|
4
18
|
|
|
5
19
|
- 文档:兼容性补实测版本声明(DSH 0.1.1-rc.2);
|
package/README.en.md
CHANGED
|
@@ -87,7 +87,7 @@ npm run smoke # selfcheck + smoke-client
|
|
|
87
87
|
|
|
88
88
|
## Compatibility
|
|
89
89
|
|
|
90
|
-
- Tested on DSH `0.1.
|
|
90
|
+
- Tested on DSH `0.1.2-rc.1` (since 0.1.6 this README documents the adaptation; 0.1.5 migrated the card entry to the new `pendingInteraction` / `PendingQuestion` protocol — select receives a single `PendingQuestion`, and answers go through `.answer({answers})` / `.cancel()`); requires the DSH web GUI (an entry in the `conversation.composer` chain, priority -300).
|
|
91
91
|
- Directly compatible with the image cards of a co-installed `dsh-plugin-image-tools`; without it, plain text options work as usual.
|
|
92
92
|
- `plan-review` intents (plan review cards) are passed through to the native `PlanReviewPanel`, unaffected.
|
|
93
93
|
- Depends on the client services `slots` / `locale` / `sessions` / `remote`.
|
package/README.md
CHANGED
|
@@ -92,7 +92,10 @@ npm run smoke # selfcheck + smoke-client
|
|
|
92
92
|
|
|
93
93
|
## 兼容性
|
|
94
94
|
|
|
95
|
-
- 实测于 DSH `0.1.
|
|
95
|
+
- 实测于 DSH `0.1.2-rc.1`(0.1.5 起适配该版选择卡新协议:`conversation.composer` 链的
|
|
96
|
+
select 入参改为单对象 `pendingInteraction`(原生 `PendingQuestion`),应答走
|
|
97
|
+
`.answer({answers})` / `.cancel()`;0.1.6 起测试自带 react/react-dom,不再依赖宿主安装布局);
|
|
98
|
+
需要 DSH web GUI(`conversation.composer` 链条目,priority -300)。
|
|
96
99
|
- 与 `dsh-plugin-image-tools` 共存的图片卡直接兼容;未安装时纯文字选项照常。
|
|
97
100
|
- `plan-review` 意图(计划审阅卡)放行给原生 `PlanReviewPanel`,不受影响。
|
|
98
101
|
- 依赖客户端服务:`slots` / `locale` / `sessions` / `remote`。
|
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
3
|
"description": "DSH 选择增强插件(生态唯一):给 ask_user_question / ask_user_choice 的选择卡加「重新生成选项 / 更多选项」两个按钮——选项都不满意一键换一批(换角度/维度,5~8 个),选项太少一键补充到 6~10 个。纯前端交互实现,不改核心包、不新增工具,原生文字卡与图片选择卡通吃。",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.6",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -36,6 +36,10 @@
|
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"@deepseek-ai/cordis": "^4.0.1"
|
|
38
38
|
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"react": "^19.0.0",
|
|
41
|
+
"react-dom": "^19.0.0"
|
|
42
|
+
},
|
|
39
43
|
"engines": {
|
|
40
44
|
"node": ">=18"
|
|
41
45
|
},
|
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, {
|