@lynn123411/dsh-ask-user-grilling 0.2.7 → 0.6.0

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/README.md CHANGED
@@ -1,16 +1,35 @@
1
1
  # @lynn123411/dsh-ask-user-grilling
2
2
 
3
- DSH 原生 `ask_user_question`([`@deepseek-ai/dsh-tool-ask-user`](https://www.npmjs.com/package/@deepseek-ai/dsh-tool-ask-user))的**表单呈现变体**:与原生共用同一条 `ctx.userQuestions` seam,工具描述与全部参数描述**与原生逐字相同**,只改表单本身的呈现——强制多选、并自动追加一道轮末补充题。因此它可以直接顶替原生工具而不带来任何提示词漂移,本身不施加任何额外纪律或约束。
3
+ DSH 原生 `ask_user_question`([`@deepseek-ai/dsh-tool-ask-user`](https://www.npmjs.com/package/@deepseek-ai/dsh-tool-ask-user))的**表单呈现变体**:与原生共用同一条 `ctx.userQuestions` seam,工具描述与共有参数描述**与原生逐字相同**,差异在工具名、参数集(本插件没有 `multi_select`,一律强制多选;另多出可选参数 `number`(题号)、`detail`(正文)与 `recommended`(推荐))与输出 schema(多出 `rejected`、`violations`、`error` 三个字段)。答题界面仍是官方那一套——composer takeover 由 `ctx.userQuestions` seam 驱动、与工具名无关;transcript 里那一行由本插件的浏览器半边自己画,所以官方卡画不出来的东西(轮末补充题、`detail` 正文、被拒的违规清单)都在卡上。
4
4
 
5
- > ⚠️ **安装约束:本插件是 preset 工具行消费的 Cordis 插件,严禁加入 profile `package.json` 的 `dsh.profile.bundles`**(bundle 层必须在包内声明 `dsh.bundle`,本插件没有,加入会导致启动报错)。只需通过 `pnpm add` / `dsh plugin --profile web add` 进入 dependencies 即可,preset 的工具行会直接从 node_modules 解析本包。
5
+ > ⚠️ **装配是一个包两条行**:preset 工具行注册工具;profile `package.json` 的 `dsh.profile.bundles` 里还要有本包(包内 `cordis.patch.yml` 插入一条 `config.carrier: true` 的载体行,宿主半边在那条行下什么都不注册),浏览器才拿得到卡片。少了后一条,工具照常可用、transcript 那一行退回原始 JSON。
6
6
 
7
7
  ## 特性
8
8
 
9
- - **ask_user_grilling**:与原生 `ask_user_question` 同形同名同描述,只改呈现。
9
+ - **ask_user_grilling**:与原生 `ask_user_question` 共用同一条 seam,工具描述与共有参数描述逐字相同,工具名与参数集不同。
10
10
  - **强制多选**:所有问题一律多选——schema 不提供 `multi_select` 参数,也就没有关闭开关。此行为刻意**不写入工具描述**(描述与原生逐字一致):模型不知道只能多选,才不会为避免互斥选项而影响出题质量;
11
+ - **题号并入标题**:可选参数 `number`(如 `"Q2"`)由代码并进 `header`,格式 `<number> · <header>`,只给一个时就是那一个值——散文预告里的 Q 编号与表单页标题因此能对上。`header` 已经以该题号开头时原样送出,不出现「`Q2 · Q2 · …`」这种重复前缀。界面半边画卡片时用的是同一份拼法(`src/contract.js`),两处不会各拼一套;
12
+ - **正文交给 `detail`**:可选参数 `detail` 透传给 seam。界面侧把 `question` 渲染成无 markdown、不换行的标题,`detail` 才走 markdown 正文块——多段正文塞在 `question` 里会挤成一坨大标题并把选项区往下顶,所以题干留一句、正文进 `detail`;
13
+ - **推荐标记归一化**:模型把推荐写成 `description` 末尾的「;推荐」、`label` 里的无括号写法,或直接给 `recommended: true`,都由本插件在投递前统一搬到 `label` 末尾的「(推荐)」。界面只按 `label` 末尾的括号写法判断,其他写法既不渲染徽标、标记还会原样显示成文字——搬到这里之后徽标才出现;回传答案时再把 `label` 还原成模型原本写的那一个,模型收到的仍是自己写的内容;
11
14
  - **轮末补充题**:每次调用由代码自动追加一道「这轮还有什么要补充或调整的吗?(无需补充)」,每题末尾的补充输入框则由 UI 自动渲染——两者都不依赖模型、也不写入描述,模型自加补充项只会与它们重复;
12
- - **id 前缀保护**:`__grill_` 开头的 id 会被拒绝(该前缀归自动追加的轮末补充题所有),保证两者不会撞 id;模型侧的 id 描述与原生一致,正常出题不会触发;
15
+ - **送不进表单的入参就地拒绝**:`__grill_` 前缀的 id(该前缀归轮末补充题)、同一轮里重复的 id、空白题干、空白选项 label、同一题内归一化后同名的两个选项——这几种都在投递前返回 `rejected` + `violations`,让模型改完重调,而不是把空选项、歧义选项画到界面上。界面按 label 认选项,重名的两个选项用户根本分不开,还原答案时也只会留下最后一个;题干与选项的分工仍是引导,不做校验;
13
16
  - **同一条 seam**:直接调 `ctx.userQuestions.ask`,与原生工具走同一通道、行为一致。
17
+ - **transcript 卡片(本插件的浏览器半边)**:按 wire 工具名注册 `tool.call.toolview` 的自有 key,官方的 `ask_user_question` 那张卡不受影响。
18
+ - 折叠行是「提问 · N/N 已回答」(等答时是「等待回答」,被拒/取消/中断各有自己的说法),展开是问答列表:题号与标题、题干、`detail` 走 markdown(与 composer 用同一个渲染器)、选中项与自由文本逐行、没答的标「未回答」;
19
+ - 轮末补充题**不在调用参数里**(它是宿主追加的),所以官方卡的「问题↔答案逐 id 等长配对」必然失败、退回原始 JSON;这里按「结果里 argsRaw 没有的 id」把它单独列出来,题干取自 `src/contract.js` 的同一份常量;
20
+ - `rejected` 的轮次画违规清单,取消与中断画说明加题目;调用失败画失败摘要;参数还在流式传输(`argsRaw` 解析不出来)时退回原始文本,不假装读懂了这一轮。
21
+
22
+ ## 自检
23
+
24
+ `pnpm check`(= `pnpm build && pnpm test && pnpm assert:lib-untracked`):
25
+
26
+ - `scripts/test-recommendation.mjs`:推荐标记的归一化;
27
+ - `scripts/test-card.mjs`:卡片模型(轮末补充题、`detail`、被拒、取消/中断、结果里多出来的 id、流式半截 JSON);
28
+ - `scripts/smoke-host.mjs`:用真 `defineTool` + 假 seam 跑 `execute()`,逐条断言上面的转换与拒绝,另断言载体行的 config 门禁与 config 校验;
29
+ - `scripts/test-render.mjs`:用真 react 渲染构建出来的行组件,断言卡片的结构与文案(取词走真字典,缺键或缺参数即失败);`ui-primitives` 是替身,真组件的外观在浏览器里看;
30
+ - `scripts/smoke-client.mjs`:按 Web shell 的方式加载构建出来的 `lib/client.js`(`window.__ModuleLoader__.load` 桩件),用带内核 inject 守卫的假上下文断言 `tool.call.toolview` 的 keyed 注册与字典,并守住 `dsg-*` class 名在 JSX 与 CSS 之间不单边漂移。
31
+
32
+ 内核包(`@deepseek-ai/dsh-tools` / `@deepseek-ai/dsh-user-questions`)与界面侧副本从 DSH 运行副本 `~/.dsh/profiles/node_modules/` 解析(可用 `DSH_HOME` 改主目录);`scripts/recommended-label-rule.mjs` 抄着界面侧「哪个 label 算推荐」那条正则,`smoke-host.mjs` 断言它仍与安装副本逐字相同,上游一改就当场失败。`src/` 是源码,`lib/` 是构建镜像、不入库(`pnpm build` 现打,`prepack` 在发布前打一次)。
14
33
 
15
34
  ## 安装
16
35
 
@@ -18,6 +37,13 @@ DSH 原生 `ask_user_question`([`@deepseek-ai/dsh-tool-ask-user`](https://www.
18
37
  dsh plugin --profile web add @lynn123411/dsh-ask-user-grilling
19
38
  ```
20
39
 
21
- 已发布至 npm,供三个 matt 预设(`matt-standard` / `matt-ptc` / `matt-cordis`)使用(preset 的工具行直接消费本包)。
40
+ 已发布至 npm,供三个 matt 预设(`matt-standard` / `matt-ptc` / `matt-cordis`)使用:preset 的工具行直接消费本包,官方 `tool-ask-user` 行原位换成它。
41
+
42
+ **本地开发**:用 `link:`,不要拷贝——运行副本软链接到本仓库目录后,仓库里的文件就是“已安装内容”:`cd ~/.dsh/profiles/web && pnpm add "link:<my-dsh 绝对路径>/plugins/dsh-ask-user-grilling" --offline`,再在插件目录 `pnpm install && pnpm build`。本插件宿主半边 `import` 了 `@deepseek-ai/dsh-tools` 与 `@deepseek-ai/dsh-user-questions`;`link:` 插件的这些内核包由 DSH 自身路由供给(前提是 DSH 带 `--preserve-symlinks` 启动,启动器已默认带上——见仓库 `docs/rules/dev-copy.md`「内核包路由与 `--preserve-symlinks`」)。**不要**手工拷贝进 `node_modules/`:裸拷贝是未注册状态,`pnpm install` / `dsh plugin add|remove` 等任何 profile 同步都会把它当 extraneous 清掉,而 roster 对每份 preset 做行可解析性健康检查——插件一旦被剪,引用它的三份 matt preset 会整体从模式选择里消失。
43
+
44
+ **profile 侧两处登记,缺一不可**:
45
+
46
+ 1. `dsh.profile.bundles` 里有 `@lynn123411/dsh-ask-user-grilling`。包内 `cordis.patch.yml` 会在 bundle 层插入一条 `id: lynn-ask-user-grilling-carrier`、`config: { carrier: true }` 的行;宿主半边在那条行下什么都不注册。这一条只为让 Web shell 服务本包的浏览器半边:客户端 bundle 只对 root loader 的 **enabled entry** 服务,而 preset 工具行是 `ctx.plugin` 直接挂的子树、不是 Loader entry。位置决定功能——少了它工具照常可用,只是 transcript 那一行退回原始 JSON;`carrier` 之外的 config 键或错类型当场抛,免得拼错的载体行静默把工具注册成 root layer 的全局工具。
47
+ 2. preset 的 `agent.cordis.yml` 里有工具行(`- id: tool-ask-user-grilling` / `name: '@lynn123411/dsh-ask-user-grilling'`)。工具与它的作用域由这一行注册。
22
48
 
23
- **本地开发**:用 `link:`,不要拷贝——运行副本软链接到本仓库目录后,仓库里的文件就是“已安装内容”,改完即生效、不必发版:`cd ~/.dsh/profiles/web && pnpm add "link:<my-dsh 绝对路径>/plugins/dsh-ask-user-grilling" --offline`。本插件宿主半边 `import` 了 `@deepseek-ai/dsh-tools` 与 `@deepseek-ai/dsh-user-questions`;`link:` 插件的这些内核包由 DSH 自身路由供给,**无需在仓库里备 `node_modules`**(前提是 DSH 带 `--preserve-symlinks` 启动,启动器已默认带上——见仓库 `docs/rules/plugins.md` 第 4 条)。**不要**手工拷贝进 `node_modules/`:裸拷贝是未注册状态,`pnpm install` / `dsh plugin add|remove` 等任何 profile 同步都会把它当 extraneous 清掉,而 roster 对每份 preset 做行可解析性健康检查——插件一旦被剪,引用它的三份 matt preset 会整体从模式选择里消失。也**不要**把它加进 profile `package.json` 的 `dsh.profile.bundles`(bundle 层必须在包内声明 `dsh.bundle`,否则启动报错)。
49
+ 改完 `src/` 必须重新 `pnpm build`:宿主半边与**新加入 profile bundle 层的这一行**要重启 DSH 实例(bundle 层在启动时组装);已经在图里的客户端 bundle 由 `dsh-client-hmr` 轮询到改动后热换,页面自行重载,必要时硬刷新一次。
@@ -0,0 +1,11 @@
1
+ # 本包出现在 profile 的 dsh.profile.bundles 里时应用。
2
+ #
3
+ # 这一行只为让 Web shell 服务本包的浏览器半边:客户端 bundle 只对 root loader 的 enabled
4
+ # entry 服务,而 preset 工具行是直接挂的子树、不是 Loader entry(见 dsh-agent-presets 的
5
+ # mount 说明与 client-modules 的 processOne)。所以它必须什么都不注册——工具仍由 preset 行
6
+ # 注册,否则工具会以 root layer 的身份对每一个 preset 的 agent 可见。
7
+ - insert:
8
+ - id: lynn-ask-user-grilling-carrier
9
+ name: '@lynn123411/dsh-ask-user-grilling'
10
+ config:
11
+ carrier: true
package/lib/client.js ADDED
@@ -0,0 +1,371 @@
1
+ window.__ModuleLoader__.load({ id: "@lynn123411/dsh-ask-user-grilling", factory: (require) => { var module = { exports: {} }; var exports = module.exports;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/client/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ apply: () => apply,
24
+ inject: () => inject
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/client/AskGrillingRow.jsx
29
+ var import_react = require("react");
30
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
31
+
32
+ // src/contract.js
33
+ var RESERVED_ID_PREFIX = "__grill_";
34
+ var ROUND_END_QUESTION = Object.freeze({
35
+ id: `${RESERVED_ID_PREFIX}round_supplement__`,
36
+ question: "\u8FD9\u8F6E\u8FD8\u6709\u4EC0\u4E48\u8981\u8865\u5145\u6216\u8C03\u6574\u7684\u5417\uFF1F",
37
+ header: "\u8F6E\u672B\u8865\u5145",
38
+ options: Object.freeze([Object.freeze({ label: "\u65E0\u9700\u8865\u5145" })])
39
+ });
40
+ function isBlank(value) {
41
+ return typeof value !== "string" || value.trim() === "";
42
+ }
43
+ function mergeNumberIntoHeader(number, header) {
44
+ const num = isBlank(number) ? void 0 : number;
45
+ const head = isBlank(header) ? void 0 : header;
46
+ if (num === void 0) return head ?? "";
47
+ if (head === void 0) return num;
48
+ return head.startsWith(num) ? head : `${num} \xB7 ${head}`;
49
+ }
50
+
51
+ // src/card.js
52
+ var CANCELLED_CODE = "ASK_CANCELLED";
53
+ var INTERRUPTED_CODE = "ASK_ABORTED";
54
+ function isRecord(value) {
55
+ return typeof value === "object" && value !== null && !Array.isArray(value);
56
+ }
57
+ function parseJson(text) {
58
+ if (typeof text !== "string" || text === "") return void 0;
59
+ try {
60
+ return JSON.parse(text);
61
+ } catch {
62
+ return void 0;
63
+ }
64
+ }
65
+ function stringList(value) {
66
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
67
+ }
68
+ function readQuestions(argsRaw) {
69
+ const parsed = parseJson(argsRaw);
70
+ if (!isRecord(parsed) || !Array.isArray(parsed.questions)) return [];
71
+ const questions = [];
72
+ const ids = /* @__PURE__ */ new Set();
73
+ for (const question of parsed.questions) {
74
+ if (!isRecord(question) || typeof question.id !== "string" || ids.has(question.id)) return [];
75
+ ids.add(question.id);
76
+ questions.push({
77
+ id: question.id,
78
+ header: mergeNumberIntoHeader(question.number, question.header),
79
+ question: typeof question.question === "string" ? question.question : "",
80
+ detail: isBlank(question.detail) ? void 0 : question.detail,
81
+ answers: [],
82
+ supplement: false
83
+ });
84
+ }
85
+ return questions;
86
+ }
87
+ function readResult(resultText) {
88
+ const parsed = parseJson(resultText);
89
+ if (!isRecord(parsed)) return void 0;
90
+ if (parsed.rejected === true) {
91
+ return {
92
+ rejected: true,
93
+ violations: stringList(parsed.violations),
94
+ error: typeof parsed.error === "string" ? parsed.error : void 0,
95
+ answers: []
96
+ };
97
+ }
98
+ if (!Array.isArray(parsed.answers)) return void 0;
99
+ const answers = [];
100
+ for (const entry of parsed.answers) {
101
+ if (!isRecord(entry) || typeof entry.id !== "string") return void 0;
102
+ answers.push({
103
+ id: entry.id,
104
+ selected: stringList(entry.selected),
105
+ custom: typeof entry.custom === "string" ? entry.custom : void 0
106
+ });
107
+ }
108
+ return { rejected: false, violations: [], error: void 0, answers };
109
+ }
110
+ function readAskCard({ argsRaw, resultText, errorCode, failed = false }) {
111
+ const questions = readQuestions(argsRaw);
112
+ const known = new Set(questions.map((question) => question.id));
113
+ const result = readResult(resultText);
114
+ let state;
115
+ if (errorCode === CANCELLED_CODE) state = "cancelled";
116
+ else if (errorCode === INTERRUPTED_CODE) state = "interrupted";
117
+ else if (result !== void 0) state = result.rejected ? "rejected" : "answered";
118
+ else if (failed) state = "error";
119
+ else if (typeof resultText === "string" && resultText !== "") state = "error";
120
+ else state = "running";
121
+ if (state === "answered") {
122
+ for (const answer of result.answers) {
123
+ const lines = [
124
+ ...answer.selected,
125
+ ...answer.custom === void 0 || answer.custom === "" ? [] : [answer.custom]
126
+ ];
127
+ const entry = questions.find((question) => question.id === answer.id);
128
+ if (entry !== void 0) {
129
+ entry.answers = lines;
130
+ continue;
131
+ }
132
+ const supplement = answer.id === ROUND_END_QUESTION.id;
133
+ questions.push({
134
+ id: answer.id,
135
+ header: supplement ? ROUND_END_QUESTION.header : "",
136
+ question: supplement ? ROUND_END_QUESTION.question : answer.id,
137
+ detail: void 0,
138
+ answers: lines,
139
+ supplement: true
140
+ });
141
+ }
142
+ } else if ((state === "running" || state === "cancelled" || state === "interrupted") && questions.length > 0) {
143
+ if (!known.has(ROUND_END_QUESTION.id)) {
144
+ questions.push({
145
+ id: ROUND_END_QUESTION.id,
146
+ header: ROUND_END_QUESTION.header,
147
+ question: ROUND_END_QUESTION.question,
148
+ detail: void 0,
149
+ answers: [],
150
+ supplement: true
151
+ });
152
+ }
153
+ }
154
+ return finish(state, questions, result);
155
+ }
156
+ function finish(state, questions, result) {
157
+ return {
158
+ state,
159
+ questions,
160
+ answered: questions.filter((question) => question.answers.length > 0).length,
161
+ total: questions.length,
162
+ violations: result === void 0 ? [] : result.violations,
163
+ error: result === void 0 ? void 0 : result.error
164
+ };
165
+ }
166
+
167
+ // src/client/AskGrillingRow.jsx
168
+ var import_jsx_runtime = require("react/jsx-runtime");
169
+ function AskGrillingRow({ block, toolName, inspect, t }) {
170
+ const [expanded, setExpanded] = (0, import_react.useState)(false);
171
+ const settled = "kind" in block;
172
+ const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? "";
173
+ const resultText = settled ? firstText(block.content) : void 0;
174
+ const model = readAskCard({
175
+ argsRaw,
176
+ resultText,
177
+ errorCode: settled ? block.error?.code : void 0,
178
+ failed: settled && block.isError === true
179
+ });
180
+ const failure = settled && block.isError === true ? failureText(resultText, block.error) : void 0;
181
+ const expandable = argsRaw !== "" || resultText !== void 0 || model.questions.length > 0;
182
+ const open = expanded && expandable;
183
+ const status = statusLabel(model.state, t);
184
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-root", "data-tool": toolName, "data-state": model.state, children: [
185
+ status === null ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-visuallyHidden", children: status }),
186
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
187
+ import_dsh_client_ui_primitives.DisclosureRow,
188
+ {
189
+ icon: leadingFor(model.state),
190
+ title: t("row.title"),
191
+ open,
192
+ expandable,
193
+ expandOnRowClick: true,
194
+ keepContentWhenOpen: true,
195
+ rowClassName: "dsg-row",
196
+ leadingClassName: "dsg-leading",
197
+ chevronClassName: "dsg-chevron",
198
+ titleClassName: "dsg-title",
199
+ collapsedContent: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
200
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-sep", "aria-hidden": "true" }),
201
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-summary", children: summaryFor(model, t) })
202
+ ] }),
203
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-bodyWrap", children: [
204
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardBody, { model, argsRaw, resultText, failure, t }),
205
+ inspect === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", { type: "button", className: "dsg-inspectButton", onClick: inspect, children: [
206
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconInspectOutline12, {}),
207
+ t("row.inspect")
208
+ ] })
209
+ ] })
210
+ }
211
+ )
212
+ ] });
213
+ }
214
+ function firstText(content) {
215
+ if (!Array.isArray(content)) return void 0;
216
+ const text = content.find((block) => block?.type === "text");
217
+ return typeof text?.text === "string" ? text.text : void 0;
218
+ }
219
+ function failureText(resultText, error) {
220
+ if (resultText !== void 0 && resultText !== "") return resultText;
221
+ const parts = [error?.name, error?.code].filter((part) => typeof part === "string" && part !== "");
222
+ return parts.length === 0 ? void 0 : parts.join(": ");
223
+ }
224
+ function leadingFor(state) {
225
+ if (state === "error" || state === "rejected") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: "error" });
226
+ if (state === "cancelled" || state === "interrupted") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.StateDot, { state: "warning" });
227
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconQuestionOutline14, {});
228
+ }
229
+ function statusLabel(state, t) {
230
+ switch (state) {
231
+ case "running":
232
+ return t("row.waiting");
233
+ case "error":
234
+ return t("row.failed");
235
+ case "cancelled":
236
+ return t("row.cancelled");
237
+ case "interrupted":
238
+ return t("row.interrupted");
239
+ default:
240
+ return null;
241
+ }
242
+ }
243
+ function summaryFor(model, t) {
244
+ switch (model.state) {
245
+ case "running":
246
+ return t("row.waiting");
247
+ case "answered":
248
+ return t("row.answered", { answered: model.answered, total: model.total });
249
+ case "rejected":
250
+ return t("row.rejected");
251
+ case "cancelled":
252
+ return t("row.cancelled");
253
+ case "interrupted":
254
+ return t("row.interrupted");
255
+ default:
256
+ return t("row.failed");
257
+ }
258
+ }
259
+ function CardBody({ model, argsRaw, resultText, failure, t }) {
260
+ if (model.state === "rejected") {
261
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-card", role: "group", "aria-label": t("row.violations"), children: [
262
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsg-verdict", children: model.error ?? t("row.rejected") }),
263
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "dsg-violations", children: model.violations.map((violation, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("li", { children: violation }, String(index))) }),
264
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QuestionEntries, { questions: model.questions, showAnswers: false, t })
265
+ ] });
266
+ }
267
+ if (model.questions.length === 0) {
268
+ const raw = model.state === "running" ? argsRaw : resultText;
269
+ return raw === void 0 || raw === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("pre", { className: "dsg-raw", children: raw });
270
+ }
271
+ if (model.state === "cancelled" || model.state === "interrupted") {
272
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-card", children: [
273
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsg-verdict", children: model.state === "cancelled" ? t("row.cancelledDetail") : t("row.interruptedDetail") }),
274
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QuestionEntries, { questions: model.questions, showAnswers: false, t })
275
+ ] });
276
+ }
277
+ if (model.state === "error") {
278
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-card", children: [
279
+ failure === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsg-failure", children: failure }),
280
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QuestionEntries, { questions: model.questions, showAnswers: false, t })
281
+ ] });
282
+ }
283
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsg-card", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(QuestionEntries, { questions: model.questions, showAnswers: model.state === "answered", t }) });
284
+ }
285
+ function QuestionEntries({ questions, showAnswers, t }) {
286
+ const labels = (0, import_react.useMemo)(() => markdownLabels(t), [t]);
287
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dl", { className: "dsg-list", children: questions.map((question) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsg-item", children: [
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("dt", { className: "dsg-question", children: [
289
+ question.header === "" ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-header", children: question.header }),
290
+ question.question
291
+ ] }),
292
+ question.detail === void 0 ? null : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsg-detail", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.MarkdownText, { text: question.detail, labels }) }),
293
+ showAnswers ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AnswerLine, { question, t }) : null
294
+ ] }, question.id)) });
295
+ }
296
+ function AnswerLine({ question, t }) {
297
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("dd", { className: "dsg-answer", children: question.answers.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-skipped", children: t("row.skipped") }) : question.answers.map((answer, index) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsg-answerLine", children: answer }, `${question.id}-${String(index)}`)) });
298
+ }
299
+ function markdownLabels(t) {
300
+ return {
301
+ code: { copyLabel: t("copy"), copiedLabel: t("copied") },
302
+ footnotes: t("markdown.footnotes")
303
+ };
304
+ }
305
+
306
+ // src/client/AskGrillingRow.css
307
+ var AskGrillingRow_default = "/* ask_user_grilling \u7684 transcript \u5361\u7247\u3002\n *\n * \u4E0E\u5B98\u65B9 @deepseek-ai/dsh-client-ui-tool \u7684 ToolRow / AskQuestionCard \u540C\u4E00\u5957 token \u4E0E\u6570\u503C\uFF1A\n * \u8FD9\u91CC\u53EA\u662F\u628A\u7C7B\u540D\u6362\u6210 dsg- \u524D\u7F00\uFF08\u8DE8\u63D2\u4EF6\u503C\u5BFC\u5165\u88AB\u5BA2\u6237\u7AEF bundle \u7684\u7EAF\u51C0\u5EA6\u95E8\u7981\u6321\u4F4F\uFF0C\u6240\u4EE5\u884C\u5916\u58F3\n * \u53EA\u80FD\u81EA\u5DF1\u62FC\uFF09\uFF0C\u5F62\u72B6\u3001\u5B57\u53F7\u3001\u884C\u9AD8\u30010.5px \u53D1\u4E1D\u7EBF\u90FD\u7167\u6284\uFF0C\u907F\u514D\u540C\u4E00\u9875\u51FA\u73B0\u4E24\u5957\u89C6\u89C9\u3002 */\n\n.dsg-root {\n display: flex;\n flex-direction: column;\n}\n\n.dsg-row {\n position: relative;\n overflow: hidden;\n}\n\n/* \u8FD0\u884C\u4E2D\uFF1A\u4E0E\u5B98\u65B9\u884C\u540C\u4E00\u6761\u626B\u5149\u3002 */\n.dsg-root[data-state='running'] .dsg-row::after {\n content: '';\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 300px;\n pointer-events: none;\n background: linear-gradient(\n 90deg,\n transparent 0%,\n color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%,\n transparent 100%\n );\n animation: 2.6s ease-out infinite dsg-tool-row-sweep;\n}\n\n@keyframes dsg-tool-row-sweep {\n 0% {\n left: -300px;\n }\n 90%,\n 100% {\n left: 100%;\n }\n}\n\n.dsg-leading {\n flex-shrink: 0;\n}\n\n.dsg-chevron {\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsg-title {\n font-weight: 400;\n}\n\n.dsg-sep {\n flex: none;\n width: 2px;\n height: 2px;\n margin: 0 8px;\n border-radius: 1px;\n background: var(--dsw-alias-label-caption);\n}\n\n.dsg-summary {\n flex: auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dsg-bodyWrap {\n display: flex;\n flex-direction: column;\n}\n\n.dsg-card {\n display: flex;\n flex-direction: column;\n gap: 16px;\n max-height: 360px;\n margin: 4px 0 4px 4px;\n padding: 16px 20px;\n overflow-y: auto;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-bg-base);\n}\n\n.dsg-list {\n display: flex;\n flex-direction: column;\n gap: 16px;\n margin: 0;\n}\n\n.dsg-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.dsg-question,\n.dsg-answer,\n.dsg-verdict {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: var(--dsh-content-font-size, 14px);\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\n}\n\n.dsg-question {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dsg-answer,\n.dsg-verdict {\n color: var(--dsw-alias-label-primary);\n}\n\n.dsg-failure {\n margin: 0;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n font-size: var(--dsh-content-font-size, 14px);\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dsg-answerLine {\n display: block;\n}\n\n.dsg-skipped {\n color: var(--dsw-alias-label-tertiary);\n}\n\n.dsg-header {\n display: block;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n color: var(--dsw-alias-label-caption);\n}\n\n.dsg-detail {\n margin: 2px 0 0;\n font-size: var(--dsh-content-font-size-secondary, 13px);\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsg-violations {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin: 0;\n padding-left: 20px;\n font-size: var(--dsh-content-font-size, 14px);\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\n color: var(--dsw-alias-state-error-primary);\n}\n\n.dsg-raw {\n max-height: 360px;\n margin: 4px 0 4px 4px;\n padding: 12px 16px;\n overflow: auto;\n border: 0.5px solid var(--dsw-alias-border-l1);\n border-radius: 12px;\n background: var(--dsw-alias-markdown-code-block);\n font: var(--dsw-font-markdown-code-block-small);\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n color: var(--dsw-alias-label-secondary);\n}\n\n.dsg-inspectButton {\n display: inline-flex;\n align-self: flex-start;\n align-items: center;\n gap: 4px;\n margin: 4px 0 2px 4px;\n padding: 2px 8px;\n border: 0.5px solid var(--dsw-alias-border-l4);\n border-radius: 999px;\n corner-shape: round;\n background: var(--dsw-alias-bg-base);\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 16px;\n opacity: 0;\n cursor: pointer;\n transition: opacity 100ms ease;\n}\n\n.dsg-bodyWrap:hover .dsg-inspectButton,\n.dsg-inspectButton:focus-visible {\n opacity: 1;\n}\n\n.dsg-inspectButton:hover {\n background: var(--dsw-alias-interactive-bg-hover-solid);\n color: var(--dsw-alias-label-primary);\n}\n\n.dsg-visuallyHidden {\n position: absolute;\n width: 1px;\n height: 1px;\n overflow: hidden;\n clip: rect(0 0 0 0);\n white-space: nowrap;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dsg-root[data-state='running'] .dsg-row::after {\n animation: none;\n }\n\n .dsg-inspectButton {\n transition: none;\n }\n}\n";
308
+
309
+ // src/client/locales.js
310
+ var NS = "askGrilling";
311
+ var zh = {
312
+ "row.title": "\u63D0\u95EE",
313
+ "row.waiting": "\u7B49\u5F85\u56DE\u7B54",
314
+ "row.answered": "{answered}/{total} \u5DF2\u56DE\u7B54",
315
+ "row.skipped": "\u672A\u56DE\u7B54",
316
+ "row.rejected": "\u5165\u53C2\u88AB\u62D2\u7EDD",
317
+ "row.cancelled": "\u5DF2\u53D6\u6D88",
318
+ "row.cancelledDetail": "\u672C\u8F6E\u5DF2\u53D6\u6D88\uFF0C\u672A\u63D0\u4EA4\u56DE\u7B54\u3002",
319
+ "row.interrupted": "\u5DF2\u4E2D\u65AD",
320
+ "row.interruptedDetail": "\u672C\u8F6E\u5DF2\u4E2D\u65AD\uFF0C\u672A\u63D0\u4EA4\u56DE\u7B54\u3002",
321
+ "row.failed": "\u8C03\u7528\u5931\u8D25",
322
+ "row.violations": "\u6821\u9A8C\u5931\u8D25",
323
+ "row.inspect": "\u67E5\u770B",
324
+ "copy": "\u590D\u5236",
325
+ "copied": "\u5DF2\u590D\u5236",
326
+ "markdown.footnotes": "\u811A\u6CE8"
327
+ };
328
+ var en = {
329
+ "row.title": "Ask question",
330
+ "row.waiting": "waiting for the answer",
331
+ "row.answered": "{answered}/{total} answered",
332
+ "row.skipped": "Not answered",
333
+ "row.rejected": "Rejected before asking",
334
+ "row.cancelled": "cancelled",
335
+ "row.cancelledDetail": "This round was cancelled before answers were submitted.",
336
+ "row.interrupted": "interrupted",
337
+ "row.interruptedDetail": "This round was interrupted before answers were submitted.",
338
+ "row.failed": "Tool call failed",
339
+ "row.violations": "Rejected input",
340
+ "row.inspect": "Inspect",
341
+ "copy": "Copy",
342
+ "copied": "Copied",
343
+ "markdown.footnotes": "Footnotes"
344
+ };
345
+
346
+ // src/client/index.js
347
+ var PACKAGE_ID = "@lynn123411/dsh-ask-user-grilling";
348
+ var CSS_TAG = `${PACKAGE_ID}/AskGrillingRow.css`;
349
+ var inject = ["locale", "slots"];
350
+ function injectStyles() {
351
+ if (typeof document === "undefined") return;
352
+ let tag = document.querySelector("style[data-plugin-css=" + JSON.stringify(CSS_TAG) + "]");
353
+ if (tag === null) {
354
+ tag = document.createElement("style");
355
+ tag.dataset.plugin = PACKAGE_ID;
356
+ tag.dataset.pluginCss = CSS_TAG;
357
+ document.head.appendChild(tag);
358
+ }
359
+ tag.textContent = AskGrillingRow_default;
360
+ }
361
+ function apply(ctx) {
362
+ injectStyles();
363
+ ctx.effect(() => ctx.locale.register(NS, { zh, en }), "dsh-ask-user-grilling: dictionaries");
364
+ ctx.slots.inject("tool.call.toolview", () => ctx.slots.register({
365
+ name: "tool.call.toolview",
366
+ key: "ask_user_grilling",
367
+ locale: NS
368
+ }, AskGrillingRow));
369
+ }
370
+ return module.exports; } });
371
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/client/index.js", "../src/client/AskGrillingRow.jsx", "../src/contract.js", "../src/card.js", "../src/client/AskGrillingRow.css", "../src/client/locales.js"],
4
+ "sourcesContent": ["import { AskGrillingRow } from './AskGrillingRow.jsx';\nimport css from './AskGrillingRow.css';\nimport { NS, en, zh } from './locales.js';\n\n/** \u6D4F\u89C8\u5668\u534A\u8FB9\u7684\u5305\u8EAB\u4EFD\uFF0C\u4E5F\u662F bundle \u91CC <style> \u7684\u5F52\u5C5E\u6807\u8BB0\u3002 */\nconst PACKAGE_ID = '@lynn123411/dsh-ask-user-grilling';\nconst CSS_TAG = `${PACKAGE_ID}/AskGrillingRow.css`;\n\n/**\n * \u6D4F\u89C8\u5668\u534A\u8FB9\u8981\u7684 cordis \u670D\u52A1\u3002`locale` \u88C5\u5B57\u5178\uFF0C`slots` \u6CE8\u518C\u884C\uFF1B\n * \u5C11\u58F0\u660E\u4E00\u4E2A\u4E0D\u4F1A\u5F53\u573A\u62A5\u9519\uFF0C\u63D2\u4EF6\u4F1A\u6574\u6761 entry \u53D8 failed\u3001\u4ECE\u9875\u9762\u4E0A\u6D88\u5931\uFF08\u89C1 docs/rules/client-wiring.md\uFF09\u3002\n */\nexport const inject = ['locale', 'slots'];\n\n/** \u6CE8\u5165\u672C\u63D2\u4EF6\u7684\u6837\u5F0F\u8868\uFF1A\u6309\u6807\u7B7E\u952E\u590D\u7528\u540C\u4E00\u4E2A <style>\uFF0CHMR \u91CD\u8DD1\u65F6\u66FF\u6362\u5185\u5BB9\u800C\u4E0D\u662F\u53E0\u4E00\u4EFD\u3002 */\nfunction injectStyles() {\n if (typeof document === 'undefined') return;\n let tag = document.querySelector('style[data-plugin-css=' + JSON.stringify(CSS_TAG) + ']');\n if (tag === null) {\n tag = document.createElement('style');\n tag.dataset.plugin = PACKAGE_ID;\n tag.dataset.pluginCss = CSS_TAG;\n document.head.appendChild(tag);\n }\n tag.textContent = css;\n}\n\n/**\n * \u6D4F\u89C8\u5668\u534A\u8FB9\uFF1A\u88C5\u672C\u63D2\u4EF6\u7684\u5B57\u5178\uFF0C\u7136\u540E\u6309 wire \u5DE5\u5177\u540D\u6CE8\u518C `ask_user_grilling` \u7684\u884C\u3002\n * \u6CE8\u518C\u7684\u662F\u81EA\u6709 key\uFF0C\u5B98\u65B9 `ask_user_question` \u90A3\u5F20\u5361\u4E0D\u53D7\u5F71\u54CD\u3002\n * @param {object} ctx - \u5BA2\u6237\u7AEF\u6839\u4E0A\u4E0B\u6587\u3002\n */\nexport function apply(ctx) {\n injectStyles();\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-ask-user-grilling: dictionaries');\n ctx.slots.inject('tool.call.toolview', () => ctx.slots.register({\n name: 'tool.call.toolview',\n key: 'ask_user_grilling',\n locale: NS,\n }, AskGrillingRow));\n}\n", "import { useMemo, useState } from 'react';\nimport {\n DisclosureRow,\n IconInspectOutline12,\n IconQuestionOutline14,\n MarkdownText,\n StateDot,\n} from '@deepseek-ai/dsh-client-ui-primitives';\nimport { readAskCard } from '../card.js';\n\n/**\n * `ask_user_grilling` \u7684 transcript \u884C\uFF1A\u6309 wire \u5DE5\u5177\u540D\u6CE8\u518C\u8FDB `tool.call.toolview`\uFF0C\n * \u4E0E\u5B98\u65B9 `ask_user_question` \u90A3\u5F20\u5361\u5E76\u5217\uFF08\u6CE8\u518C\u81EA\u6709\u7684 key\uFF0C\u4E0D\u63A5\u7BA1\u5B98\u65B9\u90A3\u5F20\uFF09\u3002\n *\n * \u5B98\u65B9\u5361\u7684\u53D6\u6570\u6B62\u4E8E argsRaw \u7684 id/question \u4E0E\u7ED3\u679C\u7684 answers\uFF0C\u8F6E\u672B\u8865\u5145\u9898\u4E0D\u5728 argsRaw \u91CC\u3001\n * detail \u4E5F\u4E0D\u5728\u8303\u56F4\u5185\uFF0C\u4E8E\u662F\u914D\u4E0D\u4E0A\u5BF9\u5C31\u9000\u56DE\u539F\u59CB JSON\u3002\u8FD9\u91CC\u6362\u6210\u8BFB\u5361\u6A21\u578B\uFF1AargsRaw \u91CC\u6CA1\u6709\u7684\n * \u7B54\u6848\u6309\u8865\u5145\u9898\u53E6\u5217\uFF0Cdetail \u8D70 markdown\u3002\n *\n * @param {{ block: object, toolName: string, inspect?: () => void, t: Function }} props - \u69FD\u4F4D\u7ED9\u7684\u884C\u6570\u636E\u3002\n * @returns {import('react').JSX.Element} \u8FD9\u4E00\u884C\u7684\u5361\u7247\u3002\n */\nexport function AskGrillingRow({ block, toolName, inspect, t }) {\n const [expanded, setExpanded] = useState(false);\n const settled = 'kind' in block;\n const argsRaw = (settled ? block.call?.argsRaw : block.argsRaw) ?? '';\n const resultText = settled ? firstText(block.content) : undefined;\n const model = readAskCard({\n argsRaw,\n resultText,\n errorCode: settled ? block.error?.code : undefined,\n failed: settled && block.isError === true,\n });\n const failure = settled && block.isError === true ? failureText(resultText, block.error) : undefined;\n const expandable = argsRaw !== '' || resultText !== undefined || model.questions.length > 0;\n const open = expanded && expandable;\n const status = statusLabel(model.state, t);\n\n return (\n <div className=\"dsg-root\" data-tool={toolName} data-state={model.state}>\n {status === null ? null : <span className=\"dsg-visuallyHidden\">{status}</span>}\n <DisclosureRow\n icon={leadingFor(model.state)}\n title={t('row.title')}\n open={open}\n expandable={expandable}\n expandOnRowClick\n keepContentWhenOpen\n rowClassName=\"dsg-row\"\n leadingClassName=\"dsg-leading\"\n chevronClassName=\"dsg-chevron\"\n titleClassName=\"dsg-title\"\n collapsedContent={\n <>\n <span className=\"dsg-sep\" aria-hidden=\"true\" />\n <span className=\"dsg-summary\">{summaryFor(model, t)}</span>\n </>\n }\n >\n <div className=\"dsg-bodyWrap\">\n <CardBody model={model} argsRaw={argsRaw} resultText={resultText} failure={failure} t={t} />\n {inspect === undefined ? null : (\n <button type=\"button\" className=\"dsg-inspectButton\" onClick={inspect}>\n <IconInspectOutline12 />\n {t('row.inspect')}\n </button>\n )}\n </div>\n </DisclosureRow>\n </div>\n );\n}\n\n/**\n * \u7ED3\u679C\u91CC\u7684\u7B2C\u4E00\u6BB5\u6587\u672C\u3002\u672C\u5DE5\u5177\u53EA\u6E32\u67D3\u4E00\u6BB5\uFF0C\u5F62\u72B6\u4E0D\u5BF9\u5C31\u5F53\u6CA1\u6709\u7ED3\u679C\u3002\n * @param {unknown} content - \u7ED3\u679C content \u6570\u7EC4\u3002\n * @returns {string|undefined} \u6587\u672C\u3002\n */\nfunction firstText(content) {\n if (!Array.isArray(content)) return undefined;\n const text = content.find((block) => block?.type === 'text');\n return typeof text?.text === 'string' ? text.text : undefined;\n}\n\n/**\n * \u5931\u8D25\u884C\u8981\u663E\u793A\u7684\u6587\u5B57\uFF1A\u7ED3\u679C\u91CC\u90A3\u6BB5\u6587\u672C\u4F18\u5148\uFF0C\u6CA1\u6709\u5C31\u7528\u9519\u8BEF\u7684 name/code \u62FC\u4E00\u884C\u3002\n * @param {string|undefined} resultText - \u7ED3\u679C\u6587\u672C\u3002\n * @param {{name?: string, code?: string}|undefined} error - \u7ED3\u679C\u8282\u70B9\u4E0A\u7684\u9519\u8BEF\u3002\n * @returns {string|undefined} \u5931\u8D25\u8BF4\u660E\u3002\n */\nfunction failureText(resultText, error) {\n if (resultText !== undefined && resultText !== '') return resultText;\n const parts = [error?.name, error?.code].filter((part) => typeof part === 'string' && part !== '');\n return parts.length === 0 ? undefined : parts.join(': ');\n}\n\n/**\n * \u884C\u7684\u524D\u5BFC\u56FE\u6807\uFF1A\u5931\u8D25\u4E0E\u62D2\u7EDD\u7528\u72B6\u6001\u70B9\uFF0C\u5176\u4F59\u7528\u95EE\u53F7\u56FE\u6807\uFF08\u4E0E\u5B98\u65B9\u884C\u540C\u4E00\u5957\u8BED\u4E49\uFF09\u3002\n * @param {string} state - \u5361\u7247\u72B6\u6001\u3002\n * @returns {import('react').ReactNode} \u524D\u5BFC\u8282\u70B9\u3002\n */\nfunction leadingFor(state) {\n if (state === 'error' || state === 'rejected') return <StateDot state=\"error\" />;\n if (state === 'cancelled' || state === 'interrupted') return <StateDot state=\"warning\" />;\n return <IconQuestionOutline14 />;\n}\n\n/**\n * \u5C4F\u5E55\u9605\u8BFB\u5668\u7528\u7684\u72B6\u6001\u6587\u5B57\u3002\u626B\u63CF\u5149\u4E0E\u72B6\u6001\u70B9\u90FD\u662F\u7EAF\u89C6\u89C9\u7684\u3002\n * @param {string} state - \u5361\u7247\u72B6\u6001\u3002\n * @param {Function} t - \u672C\u63D2\u4EF6\u7684\u53D6\u8BCD\u51FD\u6570\u3002\n * @returns {string|null} \u72B6\u6001\u6587\u5B57\uFF0C\u6B63\u5E38\u6001\u4E3A null\u3002\n */\nfunction statusLabel(state, t) {\n switch (state) {\n case 'running':\n return t('row.waiting');\n case 'error':\n return t('row.failed');\n case 'cancelled':\n return t('row.cancelled');\n case 'interrupted':\n return t('row.interrupted');\n default:\n return null;\n }\n}\n\n/**\n * \u6298\u53E0\u884C\u53F3\u4FA7\u7684\u6458\u8981\u3002\n * @param {object} model - \u5361\u7247\u6A21\u578B\u3002\n * @param {Function} t - \u672C\u63D2\u4EF6\u7684\u53D6\u8BCD\u51FD\u6570\u3002\n * @returns {string} \u6458\u8981\u6587\u5B57\u3002\n */\nfunction summaryFor(model, t) {\n switch (model.state) {\n case 'running':\n return t('row.waiting');\n case 'answered':\n return t('row.answered', { answered: model.answered, total: model.total });\n case 'rejected':\n return t('row.rejected');\n case 'cancelled':\n return t('row.cancelled');\n case 'interrupted':\n return t('row.interrupted');\n default:\n return t('row.failed');\n }\n}\n\n/**\n * \u6B63\u6587\uFF1A\u62D2\u7EDD\u65F6\u5148\u7ED9\u8FDD\u89C4\u6E05\u5355\uFF0C\u53D6\u6D88/\u4E2D\u65AD\u65F6\u7ED9\u8BF4\u660E\u52A0\u9898\u76EE\uFF0C\u5931\u8D25\u7ED9\u5931\u8D25\u8BF4\u660E\uFF0C\u5176\u4F59\u6309\u95EE\u7B54\u5217\u8868\u753B\u3002\n * @param {{ model: object, argsRaw: string, resultText: string|undefined, failure: string|undefined, t: Function }} props - \u5361\u7247\u6A21\u578B\u4E0E\u539F\u59CB\u6587\u672C\u3002\n * @returns {import('react').ReactNode} \u6B63\u6587\u3002\n */\nfunction CardBody({ model, argsRaw, resultText, failure, t }) {\n if (model.state === 'rejected') {\n return (\n <div className=\"dsg-card\" role=\"group\" aria-label={t('row.violations')}>\n <p className=\"dsg-verdict\">{model.error ?? t('row.rejected')}</p>\n <ul className=\"dsg-violations\">\n {model.violations.map((violation, index) => (\n <li key={String(index)}>{violation}</li>\n ))}\n </ul>\n <QuestionEntries questions={model.questions} showAnswers={false} t={t} />\n </div>\n );\n }\n if (model.questions.length === 0) {\n // \u8FD0\u884C\u4E2D\u7684\u534A\u622A JSON\u3001\u6216\u4E0D\u662F\u672C\u63D2\u4EF6\u5F62\u72B6\u7684\u7ED3\u679C\uFF1A\u539F\u6837\u7ED9\u51FA\uFF0C\u522B\u88C5\u4F5C\u8BFB\u61C2\u4E86\n const raw = model.state === 'running' ? argsRaw : resultText;\n return raw === undefined || raw === '' ? null : <pre className=\"dsg-raw\">{raw}</pre>;\n }\n if (model.state === 'cancelled' || model.state === 'interrupted') {\n return (\n <div className=\"dsg-card\">\n <p className=\"dsg-verdict\">\n {model.state === 'cancelled' ? t('row.cancelledDetail') : t('row.interruptedDetail')}\n </p>\n <QuestionEntries questions={model.questions} showAnswers={false} t={t} />\n </div>\n );\n }\n if (model.state === 'error') {\n return (\n <div className=\"dsg-card\">\n {failure === undefined ? null : <p className=\"dsg-failure\">{failure}</p>}\n <QuestionEntries questions={model.questions} showAnswers={false} t={t} />\n </div>\n );\n }\n return (\n <div className=\"dsg-card\">\n <QuestionEntries questions={model.questions} showAnswers={model.state === 'answered'} t={t} />\n </div>\n );\n}\n\n/**\n * \u9898\u76EE\u5217\u8868\uFF1A\u6709\u7B54\u6848\u7684\u753B dt/dd\uFF0C\u6CA1\u7B54\u6848\u7684\u53EA\u753B\u9898\u5E72\uFF1Bdetail \u8D70 markdown\u3002\n * @param {{ questions: object[], showAnswers: boolean, t: Function }} props - \u9898\u76EE\u4E0E\u662F\u5426\u753B\u7B54\u6848\u3002\n * @returns {import('react').JSX.Element} \u5217\u8868\u3002\n */\nfunction QuestionEntries({ questions, showAnswers, t }) {\n const labels = useMemo(() => markdownLabels(t), [t]);\n return (\n <dl className=\"dsg-list\">\n {questions.map((question) => (\n <div className=\"dsg-item\" key={question.id}>\n <dt className=\"dsg-question\">\n {question.header === '' ? null : <span className=\"dsg-header\">{question.header}</span>}\n {question.question}\n </dt>\n {question.detail === undefined ? null : (\n <div className=\"dsg-detail\">\n <MarkdownText text={question.detail} labels={labels} />\n </div>\n )}\n {showAnswers ? <AnswerLine question={question} t={t} /> : null}\n </div>\n ))}\n </dl>\n );\n}\n\n/**\n * \u4E00\u9053\u9898\u7684\u7B54\u6848\u884C\uFF1A\u9009\u4E2D\u9879\u9010\u884C\u5217\u51FA\uFF0C\u81EA\u7531\u6587\u672C\u63A5\u5728\u540E\u9762\uFF0C\u4E00\u6761\u90FD\u6CA1\u6709\u5C31\u662F\u300C\u672A\u56DE\u7B54\u300D\u3002\n * @param {{ question: object, t: Function }} props - \u9898\u76EE\u4E0E\u53D6\u8BCD\u51FD\u6570\u3002\n * @returns {import('react').JSX.Element} \u7B54\u6848\u884C\u3002\n */\nfunction AnswerLine({ question, t }) {\n return (\n <dd className=\"dsg-answer\">\n {question.answers.length === 0 ? (\n <span className=\"dsg-skipped\">{t('row.skipped')}</span>\n ) : (\n question.answers.map((answer, index) => (\n <span className=\"dsg-answerLine\" key={`${question.id}-${String(index)}`}>\n {answer}\n </span>\n ))\n )}\n </dd>\n );\n}\n\n/**\n * `MarkdownText` \u8981\u7684\u90A3\u4EFD chrome \u6807\u7B7E\uFF0C\u53D6\u81EA\u672C\u63D2\u4EF6\u7684\u5B57\u5178\u3002\n * @param {Function} t - \u672C\u63D2\u4EF6\u7684\u53D6\u8BCD\u51FD\u6570\u3002\n * @returns {{ code: { copyLabel: string, copiedLabel: string }, footnotes: string }} \u6807\u7B7E\u3002\n */\nfunction markdownLabels(t) {\n return {\n code: { copyLabel: t('copy'), copiedLabel: t('copied') },\n footnotes: t('markdown.footnotes'),\n };\n}\n", "// \u5BBF\u4E3B\u534A\u8FB9\u4E0E\u754C\u9762\u534A\u8FB9\u5171\u7528\u7684\u7EAF\u5951\u7EA6\uFF1A\u9898\u76EE\u5B57\u6BB5\u7684\u62FC\u6CD5\uFF0C\u52A0\u8F6E\u672B\u8865\u5145\u9898\u8FD9\u4E00\u9053\u7531\u4EE3\u7801\u8FFD\u52A0\u7684\u9898\u3002\n//\n// \u4E24\u5904\u5FC5\u987B\u4E00\u81F4\uFF0C\u6240\u4EE5\u53EA\u6709\u8FD9\u4E00\u4EFD\uFF1A\u8868\u5355\u9875\u6807\u9898\u4E0E transcript \u5361\u7247\u6807\u9898\u90FD\u6309\n// mergeNumberIntoHeader \u62FC\uFF1B\u754C\u9762\u6309 id \u8BA4\u51FA\u8F6E\u672B\u8865\u5145\u9898\uFF0C\u4E5F\u6309 ROUND_END_QUESTION \u62FF\u5B83\u7684\u9898\u5E72\u3002\n// \u5404\u5199\u4E00\u4EFD\u5FC5\u7136\u6F02\u3002\n\n/** \u8F6E\u672B\u8865\u5145\u9898\u7684 id \u524D\u7F00\uFF0C\u540C\u65F6\u662F\u6A21\u578B\u4E0D\u5F97\u5360\u7528\u7684\u4FDD\u7559\u524D\u7F00\u3002 */\nexport const RESERVED_ID_PREFIX = '__grill_';\n\n/**\n * \u6BCF\u6B21\u8C03\u7528\u7531\u4EE3\u7801\u8FFD\u52A0\u5728\u672B\u5C3E\u7684\u8865\u5145\u9898\u3002\u754C\u9762\u534A\u8FB9\u6309 id \u8BA4\u51FA\u5B83\uFF0C\u5BBF\u4E3B\u534A\u8FB9\u628A\u5B83\u9001\u8FDB seam\u3002\n * \u9009\u9879\u6570\u7EC4\u7684\u6D45\u62F7\u8D1D\u7531\u5BBF\u4E3B\u5728\u6295\u9012\u524D\u505A\uFF08seam \u4FA7\u4E0D\u518D\u6539\u5B83\uFF0C\u4F46\u522B\u628A\u8FD9\u4EFD\u5E38\u91CF\u4EA4\u51FA\u53BB\uFF09\u3002\n */\nexport const ROUND_END_QUESTION = Object.freeze({\n id: `${RESERVED_ID_PREFIX}round_supplement__`,\n question: '\u8FD9\u8F6E\u8FD8\u6709\u4EC0\u4E48\u8981\u8865\u5145\u6216\u8C03\u6574\u7684\u5417\uFF1F',\n header: '\u8F6E\u672B\u8865\u5145',\n options: Object.freeze([Object.freeze({ label: '\u65E0\u9700\u8865\u5145' })]),\n});\n\n/**\n * \u53BB\u6389\u9996\u5C3E\u7A7A\u767D\u540E\u662F\u5426\u4E3A\u7A7A\u3002\u7A7A\u4E32\u4E0E\u7EAF\u7A7A\u767D\u90FD\u4E0D\u503C\u5F97\u9001\u8FDB\u8868\u5355\u3002\n * @param {unknown} value - \u5F85\u5224\u5B9A\u7684\u503C\u3002\n * @returns {boolean} \u975E\u5B57\u7B26\u4E32\u6216\u53BB\u7A7A\u767D\u540E\u4E3A\u7A7A\u65F6\u4E3A true\u3002\n */\nexport function isBlank(value) {\n return typeof value !== 'string' || value.trim() === '';\n}\n\n/**\n * \u628A\u9898\u53F7\u5E76\u8FDB\u6807\u9898\uFF1A\u4E24\u8005\u90FD\u7ED9\u5199\u4F5C `<number> \u00B7 <header>`\uFF0C\u53EA\u7ED9\u4E00\u4E2A\u5C31\u662F\u90A3\u4E00\u4E2A\uFF1B\n * \u6807\u9898\u5DF2\u7ECF\u4EE5\u8BE5\u9898\u53F7\u5F00\u5934\u65F6\u539F\u6837\u9001\u51FA\u53BB\uFF08\u6A21\u578B\u5E38\u81EA\u5DF1\u5199\u6210 \"Q2 \u00B7 Deadline\"\uFF09\u3002\n * @param {string} [number] - \u9898\u53F7\u3002\n * @param {string} [header] - \u6807\u9898\u3002\n * @returns {string} \u4EA4\u7ED9\u754C\u9762\u7684\u6807\u9898\uFF1B\u4E24\u8005\u90FD\u7F3A\u65F6\u4E3A\u7A7A\u4E32\uFF08\u8C03\u7528\u65B9\u636E\u6B64\u7701\u7565\u8BE5\u5B57\u6BB5\uFF09\u3002\n */\nexport function mergeNumberIntoHeader(number, header) {\n const num = isBlank(number) ? undefined : number;\n const head = isBlank(header) ? undefined : header;\n if (num === undefined) return head ?? '';\n if (head === undefined) return num;\n return head.startsWith(num) ? head : `${num} \u00B7 ${head}`;\n}\n", "// \u4E00\u6B21 ask_user_grilling \u8C03\u7528\u7684\u5361\u7247\u6A21\u578B\uFF1A\u628A\u7EBF\u4E0A\u90A3\u4E24\u4EFD JSON\uFF08\u8C03\u7528\u7684 argsRaw\u3001\u7ED3\u679C\u7684\u6587\u672C\uFF09\n// \u7FFB\u6210\u754C\u9762\u76F4\u63A5\u80FD\u753B\u7684\u7ED3\u6784\u3002\u7EAF\u51FD\u6570\uFF1A\u4E0D\u78B0 React\u3001\u4E0D\u8BFB\u65F6\u949F\u3001\u4E0D\u8BFB\u4F1A\u8BDD\uFF0C\u5B9E\u65F6\u6E32\u67D3\u4E0E\u65E5\u5FD7\u91CD\u653E\u540C\u4E00\u6761\u8DEF\u5F84\u3002\n//\n// \u4E0E\u5B98\u65B9\u5361\uFF08@deepseek-ai/dsh-client-ui-tool \u7684 AskQuestionRow\uFF09\u7684\u53D6\u6570\u5DEE\u522B\u6709\u4E24\u5904\uFF0C\u90FD\u662F\u672C\u63D2\u4EF6\n// \u81EA\u5DF1\u9020\u6210\u7684\uFF1A\u8F6E\u672B\u8865\u5145\u9898\u662F\u5BBF\u4E3B\u8FFD\u52A0\u7684\uFF0C\u4E0D\u5728 argsRaw \u91CC\uFF1Bdetail \u4E5F\u4E0D\u5728\u5B98\u65B9\u5361\u7684\u53D6\u6570\u8303\u56F4\u5185\u3002\n// \u6240\u4EE5\u8FD9\u91CC\u6309 id \u914D\u5BF9\u4E4B\u540E\uFF0C\u628A argsRaw \u91CC\u6CA1\u6709\u7684\u7B54\u6848\u6309\u8F6E\u672B\u8865\u5145\u9898\u53E6\u884C\u5217\u51FA\uFF0C\u5E76\u628A detail \u4E00\u8D77\u5E26\u51FA\u53BB\u3002\n\nimport { ROUND_END_QUESTION, isBlank, mergeNumberIntoHeader } from './contract.js';\n\n/** \u7528\u6237\u53D6\u6D88 / \u4E2D\u65AD\u65F6\u5BBF\u4E3B\u5DE5\u5177\u6536\u5230\u7684\u9519\u8BEF\u7801\uFF0C\u754C\u9762\u636E\u6B64\u7ED9\u51FA\u672A\u4F5C\u7B54\u7684\u8BF4\u660E\u3002 */\nconst CANCELLED_CODE = 'ASK_CANCELLED';\nconst INTERRUPTED_CODE = 'ASK_ABORTED';\n\nfunction isRecord(value) {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/**\n * JSON.parse\uFF0C\u5931\u8D25\u8FD4\u56DE undefined\u3002running \u65F6 argsRaw \u662F\u534A\u622A JSON\uFF0C\u89E3\u6790\u5931\u8D25\u662F\u6B63\u5E38\u6001\u3002\n * @param {unknown} text - \u539F\u59CB\u6587\u672C\u3002\n * @returns {unknown} \u89E3\u6790\u7ED3\u679C\uFF0C\u6216 undefined\u3002\n */\nfunction parseJson(text) {\n if (typeof text !== 'string' || text === '') return undefined;\n try {\n return JSON.parse(text);\n } catch {\n return undefined;\n }\n}\n\nfunction stringList(value) {\n return Array.isArray(value) ? value.filter((item) => typeof item === 'string') : [];\n}\n\n/**\n * argsRaw \u2192 \u6A21\u578B\u51FA\u7684\u9898\u3002\u89E3\u6790\u5931\u8D25\u3001\u6CA1\u6709 questions\u3001\u6216 id \u91CD\u590D\uFF08\u754C\u9762\u6309 id \u914D\u5BF9\uFF0C\u91CD\u590D\u5373\u65E0\u4ECE\u914D\u5BF9\uFF09\u65F6\u8FD4\u56DE\u7A7A\u6570\u7EC4\u3002\n * @param {unknown} argsRaw - \u8C03\u7528\u7684\u539F\u59CB\u53C2\u6570\u6587\u672C\u3002\n * @returns {object[]} \u9898\u76EE\u6761\u76EE\u3002\n */\nfunction readQuestions(argsRaw) {\n const parsed = parseJson(argsRaw);\n if (!isRecord(parsed) || !Array.isArray(parsed.questions)) return [];\n const questions = [];\n const ids = new Set();\n for (const question of parsed.questions) {\n if (!isRecord(question) || typeof question.id !== 'string' || ids.has(question.id)) return [];\n ids.add(question.id);\n questions.push({\n id: question.id,\n header: mergeNumberIntoHeader(question.number, question.header),\n question: typeof question.question === 'string' ? question.question : '',\n detail: isBlank(question.detail) ? undefined : question.detail,\n answers: [],\n supplement: false,\n });\n }\n return questions;\n}\n\n/**\n * \u7ED3\u679C\u6587\u672C \u2192 \u7B54\u6848\u8BB0\u5F55\u3002\u5F62\u72B6\u4E0D\u8BA4\u8BC6\u65F6\u8FD4\u56DE undefined\uFF08\u8C03\u7528\u65B9\u6309\u8C03\u7528\u5931\u8D25\u5904\u7406\uFF09\u3002\n * @param {unknown} resultText - \u5DE5\u5177\u7ED3\u679C\u91CC\u7684\u6587\u672C\u3002\n * @returns {{rejected: boolean, violations: string[], error: string|undefined, answers: object[]}|undefined}\n */\nfunction readResult(resultText) {\n const parsed = parseJson(resultText);\n if (!isRecord(parsed)) return undefined;\n if (parsed.rejected === true) {\n return {\n rejected: true,\n violations: stringList(parsed.violations),\n error: typeof parsed.error === 'string' ? parsed.error : undefined,\n answers: [],\n };\n }\n if (!Array.isArray(parsed.answers)) return undefined;\n const answers = [];\n for (const entry of parsed.answers) {\n if (!isRecord(entry) || typeof entry.id !== 'string') return undefined;\n answers.push({\n id: entry.id,\n selected: stringList(entry.selected),\n custom: typeof entry.custom === 'string' ? entry.custom : undefined,\n });\n }\n return { rejected: false, violations: [], error: undefined, answers };\n}\n\n/**\n * \u8BFB\u51FA\u4E00\u5F20\u5361\u7684\u6A21\u578B\u3002\n * @param {{ argsRaw?: unknown, resultText?: unknown, errorCode?: unknown, failed?: boolean }} input\n * argsRaw\uFF1A\u8C03\u7528\u7684\u539F\u59CB\u53C2\u6570\uFF1BresultText\uFF1A\u7ED3\u679C\u6587\u672C\uFF08running \u65F6\u4E3A\u7A7A\uFF09\uFF1BerrorCode\uFF1A\u5931\u8D25\u7684 code\uFF1B\n * failed\uFF1A\u8FD9\u6B21\u8C03\u7528\u662F\u5426\u4EE5\u5931\u8D25\u6536\u573A\uFF08\u7ED3\u679C\u65E2\u6CA1\u6709\u7B54\u6848\u4E5F\u4E0D\u662F\u62D2\u7EDD\u65F6\u7528\u6765\u533A\u5206\u300C\u8FD0\u884C\u4E2D\u300D\u4E0E\u300C\u5931\u8D25\u300D\uFF09\u3002\n * @returns {{\n * state: 'running'|'answered'|'rejected'|'cancelled'|'interrupted'|'error',\n * questions: object[], answered: number, total: number,\n * violations: string[], error: string|undefined,\n * }} \u5361\u7247\u6A21\u578B\uFF1Bquestions \u4E3A\u7A7A\u8868\u793A\u53C2\u6570\u8FD8\u89E3\u6790\u4E0D\u51FA\u6765\uFF08\u8FD0\u884C\u4E2D\u6216\u4E0D\u662F\u672C\u63D2\u4EF6\u7684\u5F62\u72B6\uFF09\u3002\n */\nexport function readAskCard({ argsRaw, resultText, errorCode, failed = false }) {\n const questions = readQuestions(argsRaw);\n const known = new Set(questions.map((question) => question.id));\n const result = readResult(resultText);\n\n let state;\n if (errorCode === CANCELLED_CODE) state = 'cancelled';\n else if (errorCode === INTERRUPTED_CODE) state = 'interrupted';\n else if (result !== undefined) state = result.rejected ? 'rejected' : 'answered';\n else if (failed) state = 'error';\n else if (typeof resultText === 'string' && resultText !== '') state = 'error';\n else state = 'running';\n\n if (state === 'answered') {\n for (const answer of result.answers) {\n const lines = [\n ...answer.selected,\n ...answer.custom === undefined || answer.custom === '' ? [] : [answer.custom],\n ];\n const entry = questions.find((question) => question.id === answer.id);\n if (entry !== undefined) {\n entry.answers = lines;\n continue;\n }\n const supplement = answer.id === ROUND_END_QUESTION.id;\n questions.push({\n id: answer.id,\n header: supplement ? ROUND_END_QUESTION.header : '',\n question: supplement ? ROUND_END_QUESTION.question : answer.id,\n detail: undefined,\n answers: lines,\n supplement: true,\n });\n }\n } else if ((state === 'running' || state === 'cancelled' || state === 'interrupted') && questions.length > 0) {\n // \u8FD9\u4E00\u8F6E\u771F\u7684\u8FDB\u8FC7\u8868\u5355\uFF08\u6B63\u5728\u56DE\u7B54 / \u88AB\u53D6\u6D88 / \u88AB\u4E2D\u65AD\uFF09\uFF1A\u8865\u5145\u9898\u4E5F\u5728\u8868\u5355\u91CC\uFF0C\u6309\u5E38\u91CF\u8865\u4E00\u6761\u51FA\u6765\u3002\n // \u88AB\u62D2\u4E0E\u8C03\u7528\u5931\u8D25\u90FD\u6CA1\u8D70\u5230\u8868\u5355\uFF0C\u8865\u4E0A\u53BB\u7B49\u4E8E\u62A5\u544A\u4E00\u9053\u6CA1\u95EE\u8FC7\u7684\u9898\uFF1B\u53C2\u6570\u8FD8\u89E3\u6790\u4E0D\u51FA\u6765\u65F6\u540C\u7406\u2014\u2014\n // \u90A3\u662F argsRaw \u8FD8\u5728\u6D41\u5F0F\u4F20\u8F93\uFF0C\u754C\u9762\u9000\u56DE\u539F\u59CB\u6587\u672C\uFF0C\u4E0D\u5047\u88C5\u8BFB\u61C2\u4E86\u8FD9\u4E00\u8F6E\u3002\n if (!known.has(ROUND_END_QUESTION.id)) {\n questions.push({\n id: ROUND_END_QUESTION.id,\n header: ROUND_END_QUESTION.header,\n question: ROUND_END_QUESTION.question,\n detail: undefined,\n answers: [],\n supplement: true,\n });\n }\n }\n\n return finish(state, questions, result);\n}\n\n/**\n * \u6536\u53E3\uFF1A\u7B97\u5DF2\u7B54\u6570\u5E76\u5E26\u4E0A\u62D2\u7EDD\u4FE1\u606F\u3002\n * @param {string} state - \u5361\u7247\u72B6\u6001\u3002\n * @param {object[]} questions - \u6761\u76EE\u5217\u8868\u3002\n * @param {object|undefined} result - \u89E3\u6790\u51FA\u7684\u7ED3\u679C\u8BB0\u5F55\u3002\n * @returns {object} \u5361\u7247\u6A21\u578B\u3002\n */\nfunction finish(state, questions, result) {\n return {\n state,\n questions,\n answered: questions.filter((question) => question.answers.length > 0).length,\n total: questions.length,\n violations: result === undefined ? [] : result.violations,\n error: result === undefined ? undefined : result.error,\n };\n}\n", "export default \"/* ask_user_grilling \u7684 transcript \u5361\u7247\u3002\\n *\\n * \u4E0E\u5B98\u65B9 @deepseek-ai/dsh-client-ui-tool \u7684 ToolRow / AskQuestionCard \u540C\u4E00\u5957 token \u4E0E\u6570\u503C\uFF1A\\n * \u8FD9\u91CC\u53EA\u662F\u628A\u7C7B\u540D\u6362\u6210 dsg- \u524D\u7F00\uFF08\u8DE8\u63D2\u4EF6\u503C\u5BFC\u5165\u88AB\u5BA2\u6237\u7AEF bundle \u7684\u7EAF\u51C0\u5EA6\u95E8\u7981\u6321\u4F4F\uFF0C\u6240\u4EE5\u884C\u5916\u58F3\\n * \u53EA\u80FD\u81EA\u5DF1\u62FC\uFF09\uFF0C\u5F62\u72B6\u3001\u5B57\u53F7\u3001\u884C\u9AD8\u30010.5px \u53D1\u4E1D\u7EBF\u90FD\u7167\u6284\uFF0C\u907F\u514D\u540C\u4E00\u9875\u51FA\u73B0\u4E24\u5957\u89C6\u89C9\u3002 */\\n\\n.dsg-root {\\n display: flex;\\n flex-direction: column;\\n}\\n\\n.dsg-row {\\n position: relative;\\n overflow: hidden;\\n}\\n\\n/* \u8FD0\u884C\u4E2D\uFF1A\u4E0E\u5B98\u65B9\u884C\u540C\u4E00\u6761\u626B\u5149\u3002 */\\n.dsg-root[data-state='running'] .dsg-row::after {\\n content: '';\\n position: absolute;\\n top: 0;\\n bottom: 0;\\n left: 0;\\n width: 300px;\\n pointer-events: none;\\n background: linear-gradient(\\n 90deg,\\n transparent 0%,\\n color-mix(in srgb, var(--dsw-alias-bg-base) 60%, transparent) 55%,\\n transparent 100%\\n );\\n animation: 2.6s ease-out infinite dsg-tool-row-sweep;\\n}\\n\\n@keyframes dsg-tool-row-sweep {\\n 0% {\\n left: -300px;\\n }\\n 90%,\\n 100% {\\n left: 100%;\\n }\\n}\\n\\n.dsg-leading {\\n flex-shrink: 0;\\n}\\n\\n.dsg-chevron {\\n color: var(--dsw-alias-label-secondary);\\n}\\n\\n.dsg-title {\\n font-weight: 400;\\n}\\n\\n.dsg-sep {\\n flex: none;\\n width: 2px;\\n height: 2px;\\n margin: 0 8px;\\n border-radius: 1px;\\n background: var(--dsw-alias-label-caption);\\n}\\n\\n.dsg-summary {\\n flex: auto;\\n min-width: 0;\\n overflow: hidden;\\n text-overflow: ellipsis;\\n white-space: nowrap;\\n font-size: var(--dsh-content-font-size-secondary, 13px);\\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\\n color: var(--dsw-alias-label-tertiary);\\n}\\n\\n.dsg-bodyWrap {\\n display: flex;\\n flex-direction: column;\\n}\\n\\n.dsg-card {\\n display: flex;\\n flex-direction: column;\\n gap: 16px;\\n max-height: 360px;\\n margin: 4px 0 4px 4px;\\n padding: 16px 20px;\\n overflow-y: auto;\\n border: 0.5px solid var(--dsw-alias-border-l1);\\n border-radius: 12px;\\n background: var(--dsw-alias-bg-base);\\n}\\n\\n.dsg-list {\\n display: flex;\\n flex-direction: column;\\n gap: 16px;\\n margin: 0;\\n}\\n\\n.dsg-item {\\n display: flex;\\n flex-direction: column;\\n gap: 2px;\\n min-width: 0;\\n}\\n\\n.dsg-question,\\n.dsg-answer,\\n.dsg-verdict {\\n margin: 0;\\n white-space: pre-wrap;\\n overflow-wrap: anywhere;\\n font-size: var(--dsh-content-font-size, 14px);\\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\\n}\\n\\n.dsg-question {\\n color: var(--dsw-alias-label-tertiary);\\n}\\n\\n.dsg-answer,\\n.dsg-verdict {\\n color: var(--dsw-alias-label-primary);\\n}\\n\\n.dsg-failure {\\n margin: 0;\\n white-space: pre-wrap;\\n overflow-wrap: anywhere;\\n font-size: var(--dsh-content-font-size, 14px);\\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\\n color: var(--dsw-alias-state-error-primary);\\n}\\n\\n.dsg-answerLine {\\n display: block;\\n}\\n\\n.dsg-skipped {\\n color: var(--dsw-alias-label-tertiary);\\n}\\n\\n.dsg-header {\\n display: block;\\n font-size: var(--dsh-content-font-size-secondary, 13px);\\n color: var(--dsw-alias-label-caption);\\n}\\n\\n.dsg-detail {\\n margin: 2px 0 0;\\n font-size: var(--dsh-content-font-size-secondary, 13px);\\n color: var(--dsw-alias-label-secondary);\\n}\\n\\n.dsg-violations {\\n display: flex;\\n flex-direction: column;\\n gap: 8px;\\n margin: 0;\\n padding-left: 20px;\\n font-size: var(--dsh-content-font-size, 14px);\\n line-height: calc(24px + var(--dsh-content-font-delta, 0px));\\n color: var(--dsw-alias-state-error-primary);\\n}\\n\\n.dsg-raw {\\n max-height: 360px;\\n margin: 4px 0 4px 4px;\\n padding: 12px 16px;\\n overflow: auto;\\n border: 0.5px solid var(--dsw-alias-border-l1);\\n border-radius: 12px;\\n background: var(--dsw-alias-markdown-code-block);\\n font: var(--dsw-font-markdown-code-block-small);\\n white-space: pre-wrap;\\n overflow-wrap: anywhere;\\n color: var(--dsw-alias-label-secondary);\\n}\\n\\n.dsg-inspectButton {\\n display: inline-flex;\\n align-self: flex-start;\\n align-items: center;\\n gap: 4px;\\n margin: 4px 0 2px 4px;\\n padding: 2px 8px;\\n border: 0.5px solid var(--dsw-alias-border-l4);\\n border-radius: 999px;\\n corner-shape: round;\\n background: var(--dsw-alias-bg-base);\\n color: var(--dsw-alias-label-secondary);\\n font-size: 11px;\\n line-height: 16px;\\n opacity: 0;\\n cursor: pointer;\\n transition: opacity 100ms ease;\\n}\\n\\n.dsg-bodyWrap:hover .dsg-inspectButton,\\n.dsg-inspectButton:focus-visible {\\n opacity: 1;\\n}\\n\\n.dsg-inspectButton:hover {\\n background: var(--dsw-alias-interactive-bg-hover-solid);\\n color: var(--dsw-alias-label-primary);\\n}\\n\\n.dsg-visuallyHidden {\\n position: absolute;\\n width: 1px;\\n height: 1px;\\n overflow: hidden;\\n clip: rect(0 0 0 0);\\n white-space: nowrap;\\n}\\n\\n@media (prefers-reduced-motion: reduce) {\\n .dsg-root[data-state='running'] .dsg-row::after {\\n animation: none;\\n }\\n\\n .dsg-inspectButton {\\n transition: none;\\n }\\n}\\n\";", "/** \u672C\u63D2\u4EF6\u5361\u7247\u81EA\u5DF1\u7684\u6587\u6848\u5B57\u5178\u3002\u540D\u5B57\u7A7A\u95F4\u5F52\u672C\u63D2\u4EF6\uFF0C\u590D\u7528\u522B\u4EBA\u7684\u540D\u5B57\u7A7A\u95F4\u4F1A\u5728\u5BF9\u65B9\u6539\u952E\u65F6\u9759\u9ED8\u5931\u6548\u3002 */\n\n/** \u5B57\u5178\u540D\u5B57\u7A7A\u95F4\u3002 */\nexport const NS = 'askGrilling';\n\n/** \u7B80\u4F53\u4E2D\u6587\uFF08\u952E\u96C6\u7684\u4E8B\u5B9E\u6765\u6E90\uFF09\u3002 */\nexport const zh = {\n 'row.title': '\u63D0\u95EE',\n 'row.waiting': '\u7B49\u5F85\u56DE\u7B54',\n 'row.answered': '{answered}/{total} \u5DF2\u56DE\u7B54',\n 'row.skipped': '\u672A\u56DE\u7B54',\n 'row.rejected': '\u5165\u53C2\u88AB\u62D2\u7EDD',\n 'row.cancelled': '\u5DF2\u53D6\u6D88',\n 'row.cancelledDetail': '\u672C\u8F6E\u5DF2\u53D6\u6D88\uFF0C\u672A\u63D0\u4EA4\u56DE\u7B54\u3002',\n 'row.interrupted': '\u5DF2\u4E2D\u65AD',\n 'row.interruptedDetail': '\u672C\u8F6E\u5DF2\u4E2D\u65AD\uFF0C\u672A\u63D0\u4EA4\u56DE\u7B54\u3002',\n 'row.failed': '\u8C03\u7528\u5931\u8D25',\n 'row.violations': '\u6821\u9A8C\u5931\u8D25',\n 'row.inspect': '\u67E5\u770B',\n 'copy': '\u590D\u5236',\n 'copied': '\u5DF2\u590D\u5236',\n 'markdown.footnotes': '\u811A\u6CE8',\n};\n\n/** \u82F1\u6587\uFF0C\u952E\u96C6\u4E0E\u4E2D\u6587\u9010\u6761\u5BF9\u9F50\u3002 */\nexport const en = {\n 'row.title': 'Ask question',\n 'row.waiting': 'waiting for the answer',\n 'row.answered': '{answered}/{total} answered',\n 'row.skipped': 'Not answered',\n 'row.rejected': 'Rejected before asking',\n 'row.cancelled': 'cancelled',\n 'row.cancelledDetail': 'This round was cancelled before answers were submitted.',\n 'row.interrupted': 'interrupted',\n 'row.interruptedDetail': 'This round was interrupted before answers were submitted.',\n 'row.failed': 'Tool call failed',\n 'row.violations': 'Rejected input',\n 'row.inspect': 'Inspect',\n 'copy': 'Copy',\n 'copied': 'Copied',\n 'markdown.footnotes': 'Footnotes',\n};\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/lib/index.js CHANGED
@@ -1,34 +1,73 @@
1
+ // src/index.js
1
2
  import { defineTool } from "@deepseek-ai/dsh-tools";
2
3
  import "@deepseek-ai/dsh-user-questions";
3
4
 
4
- /**
5
- * @lynn123411/dsh-ask-user-grilling — a presentation variant of the native
6
- * `ask_user_question` (`@deepseek-ai/dsh-tool-ask-user`). Same capability seam
7
- * (ctx.userQuestions) and the same tool/parameter descriptions verbatim; only
8
- * the rendered form differs.
9
- *
10
- * ask_user_grilling:
11
- * - forces multi-select on every question — the schema offers no opt-out
12
- * - appends a round-end supplement question; per-question supplement goes
13
- * through the built-in custom input ("Type your answer" / "输入你的答案"),
14
- * so no extra per-question option is added (it would duplicate that field)
15
- * - rejects question ids using the reserved `__grill_` prefix, so the
16
- * auto-appended question can never be shadowed
17
- */
18
- const name = "tool-ask-user-grilling";
19
- const inject = ["tools", "userQuestions"];
5
+ // src/contract.js
6
+ var RESERVED_ID_PREFIX = "__grill_";
7
+ var ROUND_END_QUESTION = Object.freeze({
8
+ id: `${RESERVED_ID_PREFIX}round_supplement__`,
9
+ question: "\u8FD9\u8F6E\u8FD8\u6709\u4EC0\u4E48\u8981\u8865\u5145\u6216\u8C03\u6574\u7684\u5417\uFF1F",
10
+ header: "\u8F6E\u672B\u8865\u5145",
11
+ options: Object.freeze([Object.freeze({ label: "\u65E0\u9700\u8865\u5145" })])
12
+ });
13
+ function isBlank(value) {
14
+ return typeof value !== "string" || value.trim() === "";
15
+ }
16
+ function mergeNumberIntoHeader(number, header) {
17
+ const num = isBlank(number) ? void 0 : number;
18
+ const head = isBlank(header) ? void 0 : header;
19
+ if (num === void 0) return head ?? "";
20
+ if (head === void 0) return num;
21
+ return head.startsWith(num) ? head : `${num} \xB7 ${head}`;
22
+ }
20
23
 
21
- const ROUND_END_QUESTION = {
22
- id: "__grill_round_supplement__",
23
- question: "这轮还有什么要补充或调整的吗?",
24
- header: "轮末补充",
25
- options: [
26
- { label: "无需补充" },
27
- ],
28
- multiSelect: true,
29
- };
24
+ // src/recommendation.js
25
+ var MARKER = "recommended|\u63A8\u8350";
26
+ var BRACKETED_MARKER = `[\uFF08(\u3010\\[]\\s*(?:${MARKER})\\s*[\uFF09)\u3011\\]]`;
27
+ var SEPARATOR = "\uFF1B;\uFF0C,\u3001:\uFF1A\\-\u2014";
28
+ var TRAILING_PUNCTUATION = "\u3002.\uFF01!\uFF1F?\uFF1B;\uFF0C,\u3001~\uFF5E\\s";
29
+ var BRACKETED_SUFFIX = new RegExp(`[\\s]*[${SEPARATOR}]?\\s*${BRACKETED_MARKER}[${TRAILING_PUNCTUATION}]*$`, "i");
30
+ var BARE_SUFFIX = new RegExp(`[\\s]*[${SEPARATOR}]\\s*(?:${MARKER})[${TRAILING_PUNCTUATION}]*$`, "i");
31
+ var RECOMMENDED_LABEL_SUFFIX = "\uFF08\u63A8\u8350\uFF09";
32
+ function stripRecommendationMarker(text) {
33
+ if (typeof text !== "string") return { text, found: false };
34
+ const match = BRACKETED_SUFFIX.exec(text) ?? BARE_SUFFIX.exec(text);
35
+ if (match === null) return { text, found: false };
36
+ return { text: text.slice(0, match.index).replace(/\s+$/, ""), found: true };
37
+ }
38
+ function normalizeOption(option) {
39
+ const label = stripRecommendationMarker(option.label);
40
+ const description = option.description === void 0 ? void 0 : stripRecommendationMarker(option.description);
41
+ const recommended = option.recommended === true || label.found || description !== void 0 && description.found;
42
+ const cleanLabel = label.text;
43
+ return {
44
+ originalLabel: option.label,
45
+ label: recommended ? `${cleanLabel}${RECOMMENDED_LABEL_SUFFIX}` : cleanLabel,
46
+ description: description === void 0 ? void 0 : description.text,
47
+ recommended
48
+ };
49
+ }
30
50
 
31
- function apply(ctx) {
51
+ // src/index.js
52
+ var name = "tool-ask-user-grilling";
53
+ var inject = ["tools", "userQuestions"];
54
+ var CONFIG_KEY = "carrier";
55
+ function readConfig(config) {
56
+ if (config === void 0 || config === null) return { carrier: false };
57
+ if (typeof config !== "object" || Array.isArray(config)) {
58
+ throw new TypeError(`${name}: config must be an object with an optional boolean "${CONFIG_KEY}"`);
59
+ }
60
+ const unknown = Object.keys(config).filter((key) => key !== CONFIG_KEY);
61
+ if (unknown.length > 0) {
62
+ throw new TypeError(`${name}: unknown config key(s) ${unknown.map((key) => JSON.stringify(key)).join(", ")}; the only accepted key is "${CONFIG_KEY}"`);
63
+ }
64
+ if (config[CONFIG_KEY] !== void 0 && typeof config[CONFIG_KEY] !== "boolean") {
65
+ throw new TypeError(`${name}: config.${CONFIG_KEY} must be a boolean`);
66
+ }
67
+ return { carrier: config[CONFIG_KEY] === true };
68
+ }
69
+ function apply(ctx, config) {
70
+ if (readConfig(config).carrier) return;
32
71
  ctx.tools.register(defineTool({
33
72
  name: "ask_user_grilling",
34
73
  description: "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.",
@@ -44,20 +83,28 @@ function apply(ctx) {
44
83
  id: {
45
84
  type: "string",
46
85
  required: true,
47
- description: "Stable id for this question; echoed in the answer.",
86
+ description: "Stable id for this question; echoed in the answer."
48
87
  },
49
88
  question: {
50
89
  type: "string",
51
90
  required: true,
52
- description: "The specific question to ask the user.",
91
+ description: "The specific question to ask the user."
53
92
  },
54
93
  header: {
55
94
  type: "string",
56
- description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\".",
95
+ description: 'Optional short heading for the question, such as "Confirm" or "Choose Mode".'
96
+ },
97
+ number: {
98
+ type: "string",
99
+ description: 'Optional question number, e.g. "Q2". Rendered before the header as "Q2 \xB7 <header>"; use the same Q-number you announced in the message text.'
100
+ },
101
+ detail: {
102
+ type: "string",
103
+ description: "Optional supporting body for this question. Rendered as markdown under the question, so put multi-paragraph context here and keep question to the one line that asks."
57
104
  },
58
105
  options: {
59
106
  type: "array",
60
- description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
107
+ description: 'Optional choices to show the user. If you recommend one, put it first and append "(Recommended)" to that label.',
61
108
  items: {
62
109
  type: "object",
63
110
  additionalProperties: true,
@@ -65,18 +112,22 @@ function apply(ctx) {
65
112
  label: {
66
113
  type: "string",
67
114
  required: true,
68
- description: "Short user-facing option label.",
115
+ description: "Short user-facing option label."
69
116
  },
70
117
  description: {
71
118
  type: "string",
72
- description: "One sentence explaining the tradeoff or impact.",
119
+ description: "One sentence explaining the tradeoff or impact."
73
120
  },
74
- },
75
- },
76
- },
77
- },
78
- },
79
- },
121
+ recommended: {
122
+ type: "boolean",
123
+ description: 'Set true on the option you recommend (alternative to the "(Recommended)" label suffix).'
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
80
131
  },
81
132
  output: {
82
133
  schema: {
@@ -85,16 +136,16 @@ function apply(ctx) {
85
136
  properties: {
86
137
  rejected: {
87
138
  type: "boolean",
88
- description: "True when input validation failed and the round was not asked.",
139
+ description: "True when input validation failed and the round was not asked."
89
140
  },
90
141
  violations: {
91
142
  type: "array",
92
143
  items: { type: "string" },
93
- description: "Validation violations when rejected (reserved id prefix only).",
144
+ description: "Validation violations when rejected."
94
145
  },
95
146
  error: {
96
147
  type: "string",
97
- description: "Human-readable error when rejected.",
148
+ description: "Human-readable error when rejected."
98
149
  },
99
150
  answers: {
100
151
  type: "array",
@@ -107,80 +158,112 @@ function apply(ctx) {
107
158
  id: {
108
159
  type: "string",
109
160
  required: true,
110
- description: "The question id you supplied.",
161
+ description: "The question id you supplied."
111
162
  },
112
163
  selected: {
113
164
  type: "array",
114
165
  required: true,
115
166
  items: { type: "string" },
116
- description: "Labels of the options the user picked (may be empty if skipped).",
167
+ description: "Labels of the options the user picked (may be empty if skipped)."
117
168
  },
118
169
  custom: {
119
170
  type: "string",
120
- description: "User-typed free text for this question, if any.",
121
- },
122
- },
123
- },
124
- },
125
- },
171
+ description: "User-typed free text for this question, if any."
172
+ }
173
+ }
174
+ }
175
+ }
176
+ }
126
177
  },
127
- render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }],
178
+ render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
128
179
  },
129
180
  async execute(args, exec) {
130
- // 1. input validation: reserved id prefix guard only (the round-end
131
- // question owns __grill_). Stem/option separation is guidance, NOT
132
- // enforced: substring matching would reject legitimate stems (e.g. a
133
- // stem that naturally mentions an option name), so no stem check may
134
- // refuse a round — a bad stem is preferable to a false rejection.
135
181
  const violations = [];
136
- for (const question of args.questions) {
137
- if (typeof question.id === "string" && question.id.startsWith("__grill_")) {
138
- violations.push(`Question id "${question.id}" uses the reserved prefix __grill_`);
182
+ const seenIds = /* @__PURE__ */ new Set();
183
+ args.questions.forEach((question, questionIndex) => {
184
+ const where = `Question ${questionIndex + 1} (id ${JSON.stringify(question.id)})`;
185
+ if (typeof question.id === "string" && question.id.startsWith(RESERVED_ID_PREFIX)) {
186
+ violations.push(`${where} uses the reserved prefix ${RESERVED_ID_PREFIX} (owned by the round-end supplement question)`);
139
187
  }
140
- }
188
+ if (typeof question.id === "string") {
189
+ if (seenIds.has(question.id)) violations.push(`${where} repeats an id already used in this round; ids must be unique because the answer is keyed by id`);
190
+ seenIds.add(question.id);
191
+ }
192
+ if (isBlank(question.question)) {
193
+ violations.push(`${where} has an empty question text; write the question itself in question and its body in detail`);
194
+ }
195
+ const seenLabels = /* @__PURE__ */ new Set();
196
+ (question.options ?? []).forEach((option, optionIndex) => {
197
+ const label = normalizeOption(option).label;
198
+ if (isBlank(label)) {
199
+ violations.push(`${where}, option ${optionIndex + 1} has an empty label; every option needs a label the user can read and pick`);
200
+ return;
201
+ }
202
+ if (seenLabels.has(label)) {
203
+ violations.push(`${where} has two options that render with the same label ${JSON.stringify(label)}; the form identifies a choice by its label, so make them differ`);
204
+ return;
205
+ }
206
+ seenLabels.add(label);
207
+ });
208
+ });
141
209
  if (violations.length > 0) {
142
210
  return {
143
211
  rejected: true,
144
212
  violations,
145
- error: "Question ids must not use the reserved prefix __grill_ (reserved for the round-end supplement question). Fix the ids and call this tool again.",
213
+ error: `${violations.length} violation(s) in this round: ids must be unique and must not use the reserved ${RESERVED_ID_PREFIX} prefix, question text must not be blank, and the options of one question must not share a label. Fix them and call this tool again.`,
214
+ answers: []
146
215
  };
147
216
  }
148
-
149
- // 2. transform: force multi-select; per-question supplement is via the built-in custom input ("Type your answer"/"输入你的答案") — no extra option is added to avoid duplication with that field
150
- const questions = args.questions.map((question) => ({
151
- id: question.id,
152
- question: question.question,
153
- ...(question.header !== undefined ? { header: question.header } : {}),
154
- options: [
155
- ...(question.options ?? []).map((option) => ({
156
- label: option.label,
157
- ...(option.description !== undefined ? { description: option.description } : {}),
158
- })),
159
- ],
160
- multiSelect: true,
161
- }));
162
-
163
- // 3. round-end supplement question — single "无需补充" option; supplement is via custom input, so no "I have something to add" option (duplicates that field)
217
+ const labelRestore = /* @__PURE__ */ new Map();
218
+ const questions = args.questions.map((question) => {
219
+ const header = mergeNumberIntoHeader(question.number, question.header);
220
+ const restore = /* @__PURE__ */ new Map();
221
+ const options = (question.options ?? []).map((option) => {
222
+ const normalized = normalizeOption(option);
223
+ if (normalized.label !== normalized.originalLabel) {
224
+ restore.set(normalized.label, normalized.originalLabel);
225
+ }
226
+ return {
227
+ label: normalized.label,
228
+ ...normalized.description !== void 0 ? { description: normalized.description } : {}
229
+ };
230
+ });
231
+ labelRestore.set(question.id, restore);
232
+ return {
233
+ id: question.id,
234
+ question: question.question,
235
+ ...header !== "" ? { header } : {},
236
+ ...isBlank(question.detail) ? {} : { detail: question.detail },
237
+ options,
238
+ multiSelect: true
239
+ };
240
+ });
164
241
  questions.push({
165
242
  ...ROUND_END_QUESTION,
166
243
  options: ROUND_END_QUESTION.options.map((option) => ({ ...option })),
244
+ multiSelect: true
167
245
  });
168
-
169
- // 4. ask through the userQuestions seam (UI renders from the service)
170
246
  const answer = await ctx.userQuestions.ask({
171
247
  questions,
172
- ...(exec.agent !== undefined ? { agent: exec.agent } : {}),
173
- signal: exec.signal,
248
+ ...exec.agent !== void 0 ? { agent: exec.agent } : {},
249
+ signal: exec.signal
174
250
  });
175
251
  return {
176
- answers: answer.answers.map((entry) => ({
177
- id: entry.id,
178
- selected: [...entry.selected],
179
- ...(entry.custom !== undefined ? { custom: entry.custom } : {}),
180
- })),
252
+ answers: answer.answers.map((entry) => {
253
+ const restore = labelRestore.get(entry.id);
254
+ return {
255
+ id: entry.id,
256
+ selected: entry.selected.map((label) => restore?.get(label) ?? label),
257
+ ...entry.custom !== void 0 ? { custom: entry.custom } : {}
258
+ };
259
+ })
181
260
  };
182
- },
261
+ }
183
262
  }));
184
263
  }
185
-
186
- export { apply, inject, name };
264
+ export {
265
+ apply,
266
+ inject,
267
+ name
268
+ };
269
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/index.js", "../src/contract.js", "../src/recommendation.js"],
4
+ "sourcesContent": ["import { defineTool } from \"@deepseek-ai/dsh-tools\";\nimport \"@deepseek-ai/dsh-user-questions\";\nimport { RESERVED_ID_PREFIX, ROUND_END_QUESTION, isBlank, mergeNumberIntoHeader } from \"./contract.js\";\nimport { normalizeOption } from \"./recommendation.js\";\n\n/**\n * @lynn123411/dsh-ask-user-grilling \u2014 a presentation variant of the native\n * `ask_user_question` (`@deepseek-ai/dsh-tool-ask-user`). Same capability seam\n * (ctx.userQuestions), the same tool description and the same descriptions for\n * the shared parameters verbatim; only the rendered form differs.\n *\n * ask_user_grilling:\n * - forces multi-select on every question \u2014 the schema offers no opt-out\n * - merges the optional `number` into the header as \"<number> \u00B7 <header>\",\n * so a form page carries the same Q-number the round announced in prose;\n * a header that already opens with that number is sent as it is\n * - passes the optional `detail` through to the seam: the client renders it as\n * markdown under the question, while the question itself is a plain heading\n * with no markdown and no line breaks, so a multi-paragraph body belongs in\n * `detail`\n * - normalizes the recommendation marker into the one form the client renders,\n * a trailing \"\uFF08\u63A8\u8350\uFF09\" on the label: an explicit `recommended` flag, or a\n * loose marker at the end of the label or the description, is moved there \u2014\n * the client reads only the label and accepts only the bracketed suffix\n * - appends a round-end supplement question; per-question supplement goes\n * through the built-in custom input (\"Type your answer\" / \"\u8F93\u5165\u4F60\u7684\u7B54\u6848\"),\n * so no extra per-question option is added (it would duplicate that field)\n * - rejects what the form cannot carry: a question id under the reserved\n * `__grill_` prefix, a duplicate question id, a blank question text, a blank\n * option label, or two options of one question that end up with the same\n * label (the client keys selection by label, so a collision cannot be\n * restored or even clicked apart)\n *\n * Two rows mount this module. The preset tool row registers the tool (the\n * default). The profile bundle row, inserted by this package's\n * `cordis.patch.yml`, exists only so the Web client serves `lib/client.js` \u2014\n * the browser bundle is served for an enabled Loader entry, and a preset row is\n * a directly plugged subtree rather than a Loader entry, so the package must be\n * in `dsh.profile.bundles` for its card to reach the page. That row passes\n * `config.carrier: true` and registers nothing; without it the tool would also\n * become a root-layer tool, visible to agents on every other preset.\n */\nconst name = \"tool-ask-user-grilling\";\nconst inject = [\"tools\", \"userQuestions\"];\n\n/** \u8F7D\u4F53\u884C\u8BA4\u8FD9\u4E00\u4E2A\u952E\uFF0C\u8BA4\u5230\u5C31\u6574\u884C\u4EC0\u4E48\u90FD\u4E0D\u6CE8\u518C\uFF08\u6D4F\u89C8\u5668\u534A\u8FB9\u7684 servable entry\uFF0C\u89C1\u6587\u4EF6\u5934\uFF09\u3002 */\nconst CONFIG_KEY = \"carrier\";\n\n/**\n * \u8BFB\u8FD9\u4E00\u884C\u7684 config\u3002\u672A\u77E5\u952E\u4E0E\u9519\u7C7B\u578B\u5F53\u573A\u629B\uFF1A\u62FC\u9519\u7684\u8F7D\u4F53\u884C\u4F1A\u9759\u9ED8\u591A\u6CE8\u518C\u4E00\u4E2A\u5168\u5C40\u5DE5\u5177\uFF0C\n * \u90A3\u6BD4\u542F\u52A8\u5931\u8D25\u96BE\u67E5\u5F97\u591A\u3002\n * @param {unknown} config - loader \u4EA4\u7ED9\u63D2\u4EF6\u884C\u7684 config\u3002\n * @returns {{ carrier: boolean }} \u5F52\u4E00\u5316\u540E\u7684\u914D\u7F6E\u3002\n */\nfunction readConfig(config) {\n if (config === undefined || config === null) return { carrier: false };\n if (typeof config !== \"object\" || Array.isArray(config)) {\n throw new TypeError(`${name}: config must be an object with an optional boolean \"${CONFIG_KEY}\"`);\n }\n const unknown = Object.keys(config).filter((key) => key !== CONFIG_KEY);\n if (unknown.length > 0) {\n throw new TypeError(`${name}: unknown config key(s) ${unknown.map((key) => JSON.stringify(key)).join(\", \")}; the only accepted key is \"${CONFIG_KEY}\"`);\n }\n if (config[CONFIG_KEY] !== undefined && typeof config[CONFIG_KEY] !== \"boolean\") {\n throw new TypeError(`${name}: config.${CONFIG_KEY} must be a boolean`);\n }\n return { carrier: config[CONFIG_KEY] === true };\n}\n\nfunction apply(ctx, config) {\n if (readConfig(config).carrier) return;\n ctx.tools.register(defineTool({\n name: \"ask_user_grilling\",\n description: \"Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.\",\n parameters: {\n questions: {\n type: \"array\",\n required: true,\n description: \"Questions to ask the user before continuing.\",\n items: {\n type: \"object\",\n additionalProperties: true,\n properties: {\n id: {\n type: \"string\",\n required: true,\n description: \"Stable id for this question; echoed in the answer.\",\n },\n question: {\n type: \"string\",\n required: true,\n description: \"The specific question to ask the user.\",\n },\n header: {\n type: \"string\",\n description: \"Optional short heading for the question, such as \\\"Confirm\\\" or \\\"Choose Mode\\\".\",\n },\n number: {\n type: \"string\",\n description: \"Optional question number, e.g. \\\"Q2\\\". Rendered before the header as \\\"Q2 \u00B7 <header>\\\"; use the same Q-number you announced in the message text.\",\n },\n detail: {\n type: \"string\",\n description: \"Optional supporting body for this question. Rendered as markdown under the question, so put multi-paragraph context here and keep question to the one line that asks.\",\n },\n options: {\n type: \"array\",\n description: \"Optional choices to show the user. If you recommend one, put it first and append \\\"(Recommended)\\\" to that label.\",\n items: {\n type: \"object\",\n additionalProperties: true,\n properties: {\n label: {\n type: \"string\",\n required: true,\n description: \"Short user-facing option label.\",\n },\n description: {\n type: \"string\",\n description: \"One sentence explaining the tradeoff or impact.\",\n },\n recommended: {\n type: \"boolean\",\n description: \"Set true on the option you recommend (alternative to the \\\"(Recommended)\\\" label suffix).\",\n },\n },\n },\n },\n },\n },\n },\n },\n output: {\n schema: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n rejected: {\n type: \"boolean\",\n description: \"True when input validation failed and the round was not asked.\",\n },\n violations: {\n type: \"array\",\n items: { type: \"string\" },\n description: \"Validation violations when rejected.\",\n },\n error: {\n type: \"string\",\n description: \"Human-readable error when rejected.\",\n },\n answers: {\n type: \"array\",\n required: true,\n description: \"One entry per question, in the order asked.\",\n items: {\n type: \"object\",\n additionalProperties: false,\n properties: {\n id: {\n type: \"string\",\n required: true,\n description: \"The question id you supplied.\",\n },\n selected: {\n type: \"array\",\n required: true,\n items: { type: \"string\" },\n description: \"Labels of the options the user picked (may be empty if skipped).\",\n },\n custom: {\n type: \"string\",\n description: \"User-typed free text for this question, if any.\",\n },\n },\n },\n },\n },\n },\n render: (_args, value) => [{ type: \"text\", text: JSON.stringify(value) }],\n },\n async execute(args, exec) {\n // 1. input validation: only what the form cannot represent at all. Stem /\n // option separation stays guidance, NOT enforced: substring matching\n // would reject legitimate stems (e.g. a stem that naturally mentions an\n // option name), so no stem check may refuse a round \u2014 a bad stem is\n // preferable to a false rejection.\n const violations = [];\n const seenIds = new Set();\n args.questions.forEach((question, questionIndex) => {\n const where = `Question ${questionIndex + 1} (id ${JSON.stringify(question.id)})`;\n if (typeof question.id === \"string\" && question.id.startsWith(RESERVED_ID_PREFIX)) {\n violations.push(`${where} uses the reserved prefix ${RESERVED_ID_PREFIX} (owned by the round-end supplement question)`);\n }\n if (typeof question.id === \"string\") {\n if (seenIds.has(question.id)) violations.push(`${where} repeats an id already used in this round; ids must be unique because the answer is keyed by id`);\n seenIds.add(question.id);\n }\n if (isBlank(question.question)) {\n violations.push(`${where} has an empty question text; write the question itself in question and its body in detail`);\n }\n const seenLabels = new Set();\n (question.options ?? []).forEach((option, optionIndex) => {\n const label = normalizeOption(option).label;\n if (isBlank(label)) {\n violations.push(`${where}, option ${optionIndex + 1} has an empty label; every option needs a label the user can read and pick`);\n return;\n }\n if (seenLabels.has(label)) {\n violations.push(`${where} has two options that render with the same label ${JSON.stringify(label)}; the form identifies a choice by its label, so make them differ`);\n return;\n }\n seenLabels.add(label);\n });\n });\n if (violations.length > 0) {\n return {\n rejected: true,\n violations,\n error: `${violations.length} violation(s) in this round: ids must be unique and must not use the reserved ${RESERVED_ID_PREFIX} prefix, question text must not be blank, and the options of one question must not share a label. Fix them and call this tool again.`,\n answers: [],\n };\n }\n\n // 2. transform: force multi-select; merge the optional number into the\n // header; hand detail to the field the client renders as markdown; move\n // the recommendation marker to the label suffix the client renders;\n // per-question supplement is via the built-in custom input (\"Type your\n // answer\"/\"\u8F93\u5165\u4F60\u7684\u7B54\u6848\") \u2014 no extra option is added to avoid\n // duplication with that field\n const labelRestore = new Map();\n const questions = args.questions.map((question) => {\n const header = mergeNumberIntoHeader(question.number, question.header);\n const restore = new Map();\n const options = (question.options ?? []).map((option) => {\n const normalized = normalizeOption(option);\n if (normalized.label !== normalized.originalLabel) {\n restore.set(normalized.label, normalized.originalLabel);\n }\n return {\n label: normalized.label,\n ...(normalized.description !== undefined ? { description: normalized.description } : {}),\n };\n });\n labelRestore.set(question.id, restore);\n return {\n id: question.id,\n question: question.question,\n ...(header !== \"\" ? { header } : {}),\n ...(isBlank(question.detail) ? {} : { detail: question.detail }),\n options,\n multiSelect: true,\n };\n });\n\n // 3. round-end supplement question \u2014 single \"\u65E0\u9700\u8865\u5145\" option; supplement is via custom input, so no \"I have something to add\" option (duplicates that field)\n questions.push({\n ...ROUND_END_QUESTION,\n options: ROUND_END_QUESTION.options.map((option) => ({ ...option })),\n multiSelect: true,\n });\n\n // 4. ask through the userQuestions seam (UI renders from the service)\n const answer = await ctx.userQuestions.ask({\n questions,\n ...(exec.agent !== undefined ? { agent: exec.agent } : {}),\n signal: exec.signal,\n });\n return {\n answers: answer.answers.map((entry) => {\n const restore = labelRestore.get(entry.id);\n return {\n id: entry.id,\n selected: entry.selected.map((label) => restore?.get(label) ?? label),\n ...(entry.custom !== undefined ? { custom: entry.custom } : {}),\n };\n }),\n };\n },\n }));\n}\n\nexport { apply, inject, name };\n", "// \u5BBF\u4E3B\u534A\u8FB9\u4E0E\u754C\u9762\u534A\u8FB9\u5171\u7528\u7684\u7EAF\u5951\u7EA6\uFF1A\u9898\u76EE\u5B57\u6BB5\u7684\u62FC\u6CD5\uFF0C\u52A0\u8F6E\u672B\u8865\u5145\u9898\u8FD9\u4E00\u9053\u7531\u4EE3\u7801\u8FFD\u52A0\u7684\u9898\u3002\n//\n// \u4E24\u5904\u5FC5\u987B\u4E00\u81F4\uFF0C\u6240\u4EE5\u53EA\u6709\u8FD9\u4E00\u4EFD\uFF1A\u8868\u5355\u9875\u6807\u9898\u4E0E transcript \u5361\u7247\u6807\u9898\u90FD\u6309\n// mergeNumberIntoHeader \u62FC\uFF1B\u754C\u9762\u6309 id \u8BA4\u51FA\u8F6E\u672B\u8865\u5145\u9898\uFF0C\u4E5F\u6309 ROUND_END_QUESTION \u62FF\u5B83\u7684\u9898\u5E72\u3002\n// \u5404\u5199\u4E00\u4EFD\u5FC5\u7136\u6F02\u3002\n\n/** \u8F6E\u672B\u8865\u5145\u9898\u7684 id \u524D\u7F00\uFF0C\u540C\u65F6\u662F\u6A21\u578B\u4E0D\u5F97\u5360\u7528\u7684\u4FDD\u7559\u524D\u7F00\u3002 */\nexport const RESERVED_ID_PREFIX = '__grill_';\n\n/**\n * \u6BCF\u6B21\u8C03\u7528\u7531\u4EE3\u7801\u8FFD\u52A0\u5728\u672B\u5C3E\u7684\u8865\u5145\u9898\u3002\u754C\u9762\u534A\u8FB9\u6309 id \u8BA4\u51FA\u5B83\uFF0C\u5BBF\u4E3B\u534A\u8FB9\u628A\u5B83\u9001\u8FDB seam\u3002\n * \u9009\u9879\u6570\u7EC4\u7684\u6D45\u62F7\u8D1D\u7531\u5BBF\u4E3B\u5728\u6295\u9012\u524D\u505A\uFF08seam \u4FA7\u4E0D\u518D\u6539\u5B83\uFF0C\u4F46\u522B\u628A\u8FD9\u4EFD\u5E38\u91CF\u4EA4\u51FA\u53BB\uFF09\u3002\n */\nexport const ROUND_END_QUESTION = Object.freeze({\n id: `${RESERVED_ID_PREFIX}round_supplement__`,\n question: '\u8FD9\u8F6E\u8FD8\u6709\u4EC0\u4E48\u8981\u8865\u5145\u6216\u8C03\u6574\u7684\u5417\uFF1F',\n header: '\u8F6E\u672B\u8865\u5145',\n options: Object.freeze([Object.freeze({ label: '\u65E0\u9700\u8865\u5145' })]),\n});\n\n/**\n * \u53BB\u6389\u9996\u5C3E\u7A7A\u767D\u540E\u662F\u5426\u4E3A\u7A7A\u3002\u7A7A\u4E32\u4E0E\u7EAF\u7A7A\u767D\u90FD\u4E0D\u503C\u5F97\u9001\u8FDB\u8868\u5355\u3002\n * @param {unknown} value - \u5F85\u5224\u5B9A\u7684\u503C\u3002\n * @returns {boolean} \u975E\u5B57\u7B26\u4E32\u6216\u53BB\u7A7A\u767D\u540E\u4E3A\u7A7A\u65F6\u4E3A true\u3002\n */\nexport function isBlank(value) {\n return typeof value !== 'string' || value.trim() === '';\n}\n\n/**\n * \u628A\u9898\u53F7\u5E76\u8FDB\u6807\u9898\uFF1A\u4E24\u8005\u90FD\u7ED9\u5199\u4F5C `<number> \u00B7 <header>`\uFF0C\u53EA\u7ED9\u4E00\u4E2A\u5C31\u662F\u90A3\u4E00\u4E2A\uFF1B\n * \u6807\u9898\u5DF2\u7ECF\u4EE5\u8BE5\u9898\u53F7\u5F00\u5934\u65F6\u539F\u6837\u9001\u51FA\u53BB\uFF08\u6A21\u578B\u5E38\u81EA\u5DF1\u5199\u6210 \"Q2 \u00B7 Deadline\"\uFF09\u3002\n * @param {string} [number] - \u9898\u53F7\u3002\n * @param {string} [header] - \u6807\u9898\u3002\n * @returns {string} \u4EA4\u7ED9\u754C\u9762\u7684\u6807\u9898\uFF1B\u4E24\u8005\u90FD\u7F3A\u65F6\u4E3A\u7A7A\u4E32\uFF08\u8C03\u7528\u65B9\u636E\u6B64\u7701\u7565\u8BE5\u5B57\u6BB5\uFF09\u3002\n */\nexport function mergeNumberIntoHeader(number, header) {\n const num = isBlank(number) ? undefined : number;\n const head = isBlank(header) ? undefined : header;\n if (num === undefined) return head ?? '';\n if (head === undefined) return num;\n return head.startsWith(num) ? head : `${num} \u00B7 ${head}`;\n}\n", "// \u63A8\u8350\u6807\u8BB0\u7684\u5F52\u4E00\u5316\u3002\n//\n// \u5BA2\u6237\u7AEF\uFF08@deepseek-ai/dsh-client-ui-user-questions \u7684 parseRecommendedLabel\uFF09\n// \u53EA\u6709\u4E00\u4E2A\u5224\u5B9A\u4F9D\u636E\uFF1Aoption.label \u5FC5\u987B\u4EE5\u534A\u89D2\u6216\u5168\u89D2\u7684\u62EC\u53F7\u63A8\u8350\u5B57\u6837\u6536\u5C3E\uFF1Boption.description\n// \u5B8C\u5168\u4E0D\u53C2\u4E0E\u3002\u6A21\u578B\u7ECF\u5E38\u628A\u63A8\u8350\u5199\u6210 description \u672B\u5C3E\u7684\u300C\uFF1B\u63A8\u8350\u300D\uFF0C\u6216\u8005\u5199\u6210 label \u91CC\u4E0D\u5E26\n// \u62EC\u53F7\u7684\u5F62\u5F0F\uFF0C\u4E8E\u662F\u5FBD\u6807\u4E0D\u6E32\u67D3\u3001\u6807\u8BB0\u539F\u6837\u663E\u793A\u6210\u6587\u5B57\u3002\n//\n// \u8FD9\u91CC\u5728\u9898\u76EE\u4EA4\u7ED9 ctx.userQuestions.ask \u4E4B\u524D\u628A\u6807\u8BB0\u7EDF\u4E00\u642C\u5230 label \u672B\u5C3E\u7684\u300C\uFF08\u63A8\u8350\uFF09\u300D\uFF0C\n// \u5BA2\u6237\u7AEF\u5C31\u80FD\u8BA4\u51FA\u5E76\u6E32\u67D3\u5FBD\u6807\u3002\u663E\u5F0F\u5B57\u6BB5 recommended \u662F\u53E6\u4E00\u6761\u66F4\u660E\u786E\u7684\u4FE1\u53F7\uFF0C\u4F18\u5148\u4E8E\u6587\u5B57\u3002\n\nconst MARKER = 'recommended|\u63A8\u8350';\n// \u534A\u89D2/\u5168\u89D2\u5706\u62EC\u53F7\u4E0E\u65B9\u62EC\u53F7/\u65B9\u5934\u62EC\u53F7\nconst BRACKETED_MARKER = `[\uFF08(\u3010\\\\[]\\\\s*(?:${MARKER})\\\\s*[\uFF09)\u3011\\\\]]`;\n// \u65E0\u62EC\u53F7\u5199\u6CD5\u5FC5\u987B\u7531\u5206\u9694\u7B26\u5F15\u51FA\uFF0C\u5426\u5219\u300C\u67E5\u770B\u63A8\u8350\u300D\u8FD9\u79CD\u6B63\u5E38\u6587\u5B57\u4F1A\u88AB\u8BEF\u5224\nconst SEPARATOR = '\uFF1B;\uFF0C,\u3001:\uFF1A\\\\-\u2014';\nconst TRAILING_PUNCTUATION = '\u3002.\uFF01!\uFF1F?\uFF1B;\uFF0C,\u3001~\uFF5E\\\\s';\n\nconst BRACKETED_SUFFIX = new RegExp(`[\\\\s]*[${SEPARATOR}]?\\\\s*${BRACKETED_MARKER}[${TRAILING_PUNCTUATION}]*$`, 'i');\nconst BARE_SUFFIX = new RegExp(`[\\\\s]*[${SEPARATOR}]\\\\s*(?:${MARKER})[${TRAILING_PUNCTUATION}]*$`, 'i');\n\n/** \u4F9B\u6D4B\u8BD5\u6838\u5BF9\u5BA2\u6237\u7AEF\u89C4\u5219\u7684\u63A8\u8350\u6807\u8BB0\u540E\u7F00\uFF1B\u5BA2\u6237\u7AEF\u53EA\u8BA4 label \u672B\u5C3E\u7684\u8FD9\u4E24\u79CD\u62EC\u53F7\u5199\u6CD5\u3002 */\nexport const RECOMMENDED_LABEL_SUFFIX = '\uFF08\u63A8\u8350\uFF09';\n\n/**\n * \u53BB\u6389\u6587\u672C\u672B\u5C3E\u7684\u63A8\u8350\u6807\u8BB0\u3002\n * @param {string} text - label \u6216 description \u539F\u6587\u3002\n * @returns {{ text: string, found: boolean }} \u53BB\u6389\u6807\u8BB0\u540E\u7684\u6587\u672C\uFF0C\u4EE5\u53CA\u662F\u5426\u627E\u5230\u6807\u8BB0\u3002\n */\nexport function stripRecommendationMarker(text) {\n if (typeof text !== 'string') return { text, found: false };\n const match = BRACKETED_SUFFIX.exec(text) ?? BARE_SUFFIX.exec(text);\n if (match === null) return { text, found: false };\n return { text: text.slice(0, match.index).replace(/\\s+$/, ''), found: true };\n}\n\n/**\n * \u628A\u4E00\u4E2A option \u5F52\u4E00\u5316\u6210\u5BA2\u6237\u7AEF\u80FD\u6E32\u67D3\u7684\u5F62\u5F0F\u3002\n * @param {{ label: string, description?: string, recommended?: boolean }} option - \u6A21\u578B\u7ED9\u7684\u9009\u9879\u3002\n * @returns {{ originalLabel: string, label: string, description?: string, recommended: boolean }}\n * \u4EA4\u7ED9\u754C\u9762\u65F6\u7528 label/description\uFF1B\u56DE\u4F20\u7B54\u6848\u65F6\u7528 originalLabel \u8FD8\u539F\u6A21\u578B\u539F\u672C\u5199\u7684 label\u3002\n */\nexport function normalizeOption(option) {\n const label = stripRecommendationMarker(option.label);\n const description = option.description === undefined ? undefined : stripRecommendationMarker(option.description);\n const recommended = option.recommended === true || label.found || (description !== undefined && description.found);\n const cleanLabel = label.text;\n return {\n originalLabel: option.label,\n label: recommended ? `${cleanLabel}${RECOMMENDED_LABEL_SUFFIX}` : cleanLabel,\n description: description === undefined ? undefined : description.text,\n recommended,\n };\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
package/package.json CHANGED
@@ -1,24 +1,51 @@
1
1
  {
2
2
  "name": "@lynn123411/dsh-ask-user-grilling",
3
- "version": "0.2.7",
4
- "description": "Form-presentation variant of the native ask_user_question: same tool and parameter descriptions verbatim, forced multi-select, plus an auto-appended round-end supplement question.",
3
+ "version": "0.6.0",
4
+ "description": "Form-presentation variant of the native ask_user_question: same tool description and shared-parameter descriptions verbatim, forced multi-select, an optional question number merged into the header, an optional markdown detail body, a recommendation marker normalized into the one label suffix the client renders, plus an auto-appended round-end supplement question. Its own browser half renders the transcript card for ask_user_grilling (question list, detail markdown, round-end supplement, rejected-round violations).",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "exports": {
8
8
  ".": "./lib/index.js",
9
+ "./client": "./lib/client.js",
10
+ "./cordis.patch.yml": "./cordis.patch.yml",
9
11
  "./package.json": "./package.json"
10
12
  },
11
13
  "files": [
12
14
  "lib",
15
+ "cordis.patch.yml",
13
16
  "README.md"
14
17
  ],
15
18
  "license": "MIT",
19
+ "dsh": {
20
+ "bundle": {
21
+ "patch": "./cordis.patch.yml"
22
+ },
23
+ "client": {
24
+ "platform": "web",
25
+ "inject": [
26
+ "@deepseek-ai/dsh-client-ui-primitives"
27
+ ]
28
+ }
29
+ },
30
+ "scripts": {
31
+ "build": "node build.mjs",
32
+ "prepack": "pnpm run build",
33
+ "test": "node scripts/test-recommendation.mjs && node scripts/test-card.mjs && node scripts/smoke-host.mjs && node scripts/test-render.mjs && node scripts/smoke-client.mjs",
34
+ "assert:lib-untracked": "node scripts/assert-lib-untracked.mjs",
35
+ "check": "pnpm build && pnpm test && pnpm assert:lib-untracked"
36
+ },
16
37
  "peerDependencies": {
17
38
  "@deepseek-ai/cordis": "^4.0.2",
18
- "@deepseek-ai/dsh-tools": "^0.1.5-rc.1",
19
- "@deepseek-ai/dsh-user-questions": "^0.1.5-rc.1"
39
+ "@deepseek-ai/dsh-tools": "0.1.6-alpha.2",
40
+ "@deepseek-ai/dsh-user-questions": "0.1.6-alpha.2"
41
+ },
42
+ "devDependencies": {
43
+ "esbuild": "^0.25.0",
44
+ "react": "^18.3.1",
45
+ "react-dom": "^18.3.1"
20
46
  },
21
47
  "engines": {
48
+ "dsh": "0.1.6-alpha.2",
22
49
  "node": "^22.19 || >=24"
23
50
  },
24
51
  "publishConfig": {