@lynn123411/dsh-ask-user-grilling 0.5.0 → 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 +25 -6
- package/cordis.patch.yml +11 -0
- package/lib/client.js +371 -0
- package/lib/client.js.map +7 -0
- package/lib/index.js +113 -120
- package/lib/index.js.map +7 -0
- package/package.json +26 -4
- package/lib/recommendation.js +0 -53
package/README.md
CHANGED
|
@@ -1,23 +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,工具描述与共有参数描述**与原生逐字相同**,差异在工具名、参数集(本插件没有 `multi_select`,一律强制多选;另多出可选参数 `number`(题号)、`detail`(正文)与 `recommended`(推荐))与输出 schema(多出 `rejected`、`violations`、`error`
|
|
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
|
-
> ⚠️
|
|
5
|
+
> ⚠️ **装配是一个包两条行**:preset 工具行注册工具;profile `package.json` 的 `dsh.profile.bundles` 里还要有本包(包内 `cordis.patch.yml` 插入一条 `config.carrier: true` 的载体行,宿主半边在那条行下什么都不注册),浏览器才拿得到卡片。少了后一条,工具照常可用、transcript 那一行退回原始 JSON。
|
|
6
6
|
|
|
7
7
|
## 特性
|
|
8
8
|
|
|
9
9
|
- **ask_user_grilling**:与原生 `ask_user_question` 共用同一条 seam,工具描述与共有参数描述逐字相同,工具名与参数集不同。
|
|
10
10
|
- **强制多选**:所有问题一律多选——schema 不提供 `multi_select` 参数,也就没有关闭开关。此行为刻意**不写入工具描述**(描述与原生逐字一致):模型不知道只能多选,才不会为避免互斥选项而影响出题质量;
|
|
11
|
-
- **题号并入标题**:可选参数 `number`(如 `"Q2"`)由代码并进 `header`,格式 `<number> · <header>`,只给一个时就是那一个值——散文预告里的 Q 编号与表单页标题因此能对上。`header` 已经以该题号开头时原样送出,不出现「`Q2 · Q2 ·
|
|
11
|
+
- **题号并入标题**:可选参数 `number`(如 `"Q2"`)由代码并进 `header`,格式 `<number> · <header>`,只给一个时就是那一个值——散文预告里的 Q 编号与表单页标题因此能对上。`header` 已经以该题号开头时原样送出,不出现「`Q2 · Q2 · …`」这种重复前缀。界面半边画卡片时用的是同一份拼法(`src/contract.js`),两处不会各拼一套;
|
|
12
12
|
- **正文交给 `detail`**:可选参数 `detail` 透传给 seam。界面侧把 `question` 渲染成无 markdown、不换行的标题,`detail` 才走 markdown 正文块——多段正文塞在 `question` 里会挤成一坨大标题并把选项区往下顶,所以题干留一句、正文进 `detail`;
|
|
13
13
|
- **推荐标记归一化**:模型把推荐写成 `description` 末尾的「;推荐」、`label` 里的无括号写法,或直接给 `recommended: true`,都由本插件在投递前统一搬到 `label` 末尾的「(推荐)」。界面只按 `label` 末尾的括号写法判断,其他写法既不渲染徽标、标记还会原样显示成文字——搬到这里之后徽标才出现;回传答案时再把 `label` 还原成模型原本写的那一个,模型收到的仍是自己写的内容;
|
|
14
14
|
- **轮末补充题**:每次调用由代码自动追加一道「这轮还有什么要补充或调整的吗?(无需补充)」,每题末尾的补充输入框则由 UI 自动渲染——两者都不依赖模型、也不写入描述,模型自加补充项只会与它们重复;
|
|
15
15
|
- **送不进表单的入参就地拒绝**:`__grill_` 前缀的 id(该前缀归轮末补充题)、同一轮里重复的 id、空白题干、空白选项 label、同一题内归一化后同名的两个选项——这几种都在投递前返回 `rejected` + `violations`,让模型改完重调,而不是把空选项、歧义选项画到界面上。界面按 label 认选项,重名的两个选项用户根本分不开,还原答案时也只会留下最后一个;题干与选项的分工仍是引导,不做校验;
|
|
16
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` 解析不出来)时退回原始文本,不假装读懂了这一轮。
|
|
17
21
|
|
|
18
22
|
## 自检
|
|
19
23
|
|
|
20
|
-
`pnpm
|
|
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` 在发布前打一次)。
|
|
21
33
|
|
|
22
34
|
## 安装
|
|
23
35
|
|
|
@@ -25,6 +37,13 @@ DSH 原生 `ask_user_question`([`@deepseek-ai/dsh-tool-ask-user`](https://www.
|
|
|
25
37
|
dsh plugin --profile web add @lynn123411/dsh-ask-user-grilling
|
|
26
38
|
```
|
|
27
39
|
|
|
28
|
-
已发布至 npm,供三个 matt 预设(`matt-standard` / `matt-ptc` / `matt-cordis
|
|
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'`)。工具与它的作用域由这一行注册。
|
|
29
48
|
|
|
30
|
-
|
|
49
|
+
改完 `src/` 必须重新 `pnpm build`:宿主半边与**新加入 profile bundle 层的这一行**要重启 DSH 实例(bundle 层在启动时组装);已经在图里的客户端 bundle 由 `dsh-client-hmr` 轮询到改动后热换,页面自行重载,必要时硬刷新一次。
|
package/cordis.patch.yml
ADDED
|
@@ -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,69 +1,73 @@
|
|
|
1
|
+
// src/index.js
|
|
1
2
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
2
3
|
import "@deepseek-ai/dsh-user-questions";
|
|
3
|
-
import { normalizeOption } from "./recommendation.js";
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* - merges the optional `number` into the header as "<number> · <header>",
|
|
14
|
-
* so a form page carries the same Q-number the round announced in prose;
|
|
15
|
-
* a header that already opens with that number is sent as it is
|
|
16
|
-
* - passes the optional `detail` through to the seam: the client renders it as
|
|
17
|
-
* markdown under the question, while the question itself is a plain heading
|
|
18
|
-
* with no markdown and no line breaks, so a multi-paragraph body belongs in
|
|
19
|
-
* `detail`
|
|
20
|
-
* - normalizes the recommendation marker into the one form the client renders,
|
|
21
|
-
* a trailing "(推荐)" on the label: an explicit `recommended` flag, or a
|
|
22
|
-
* loose marker at the end of the label or the description, is moved there —
|
|
23
|
-
* the client reads only the label and accepts only the bracketed suffix
|
|
24
|
-
* - appends a round-end supplement question; per-question supplement goes
|
|
25
|
-
* through the built-in custom input ("Type your answer" / "输入你的答案"),
|
|
26
|
-
* so no extra per-question option is added (it would duplicate that field)
|
|
27
|
-
* - rejects what the form cannot carry: a question id under the reserved
|
|
28
|
-
* `__grill_` prefix, a duplicate question id, a blank question text, a blank
|
|
29
|
-
* option label, or two options of one question that end up with the same
|
|
30
|
-
* label (the client keys selection by label, so a collision cannot be
|
|
31
|
-
* restored or even clicked apart)
|
|
32
|
-
*/
|
|
33
|
-
const name = "tool-ask-user-grilling";
|
|
34
|
-
const inject = ["tools", "userQuestions"];
|
|
35
|
-
|
|
36
|
-
const ROUND_END_QUESTION = {
|
|
37
|
-
id: "__grill_round_supplement__",
|
|
38
|
-
question: "这轮还有什么要补充或调整的吗?",
|
|
39
|
-
header: "轮末补充",
|
|
40
|
-
options: [
|
|
41
|
-
{ label: "无需补充" },
|
|
42
|
-
],
|
|
43
|
-
multiSelect: true,
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/** 去掉首尾空白后是否为空。空串与纯空白都不值得送进表单。 */
|
|
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
|
+
});
|
|
47
13
|
function isBlank(value) {
|
|
48
14
|
return typeof value !== "string" || value.trim() === "";
|
|
49
15
|
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* 把题号并进标题:两者都给写作 `<number> · <header>`,只给一个就是那一个;
|
|
53
|
-
* 标题已经以该题号开头时原样送出去(模型常自己写成 "Q2 · Deadline")。
|
|
54
|
-
* @param {string} [number] - 题号。
|
|
55
|
-
* @param {string} [header] - 标题。
|
|
56
|
-
* @returns {string} 交给界面的标题;两者都缺时为空串(调用方据此省略该字段)。
|
|
57
|
-
*/
|
|
58
16
|
function mergeNumberIntoHeader(number, header) {
|
|
59
|
-
const num = isBlank(number) ?
|
|
60
|
-
const head = isBlank(header) ?
|
|
61
|
-
if (num ===
|
|
62
|
-
if (head ===
|
|
63
|
-
return head.startsWith(num) ? head : `${num}
|
|
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
|
+
}
|
|
23
|
+
|
|
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
|
+
};
|
|
64
49
|
}
|
|
65
50
|
|
|
66
|
-
|
|
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;
|
|
67
71
|
ctx.tools.register(defineTool({
|
|
68
72
|
name: "ask_user_grilling",
|
|
69
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.",
|
|
@@ -79,28 +83,28 @@ function apply(ctx) {
|
|
|
79
83
|
id: {
|
|
80
84
|
type: "string",
|
|
81
85
|
required: true,
|
|
82
|
-
description: "Stable id for this question; echoed in the answer."
|
|
86
|
+
description: "Stable id for this question; echoed in the answer."
|
|
83
87
|
},
|
|
84
88
|
question: {
|
|
85
89
|
type: "string",
|
|
86
90
|
required: true,
|
|
87
|
-
description: "The specific question to ask the user."
|
|
91
|
+
description: "The specific question to ask the user."
|
|
88
92
|
},
|
|
89
93
|
header: {
|
|
90
94
|
type: "string",
|
|
91
|
-
description:
|
|
95
|
+
description: 'Optional short heading for the question, such as "Confirm" or "Choose Mode".'
|
|
92
96
|
},
|
|
93
97
|
number: {
|
|
94
98
|
type: "string",
|
|
95
|
-
description:
|
|
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.'
|
|
96
100
|
},
|
|
97
101
|
detail: {
|
|
98
102
|
type: "string",
|
|
99
|
-
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."
|
|
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."
|
|
100
104
|
},
|
|
101
105
|
options: {
|
|
102
106
|
type: "array",
|
|
103
|
-
description:
|
|
107
|
+
description: 'Optional choices to show the user. If you recommend one, put it first and append "(Recommended)" to that label.',
|
|
104
108
|
items: {
|
|
105
109
|
type: "object",
|
|
106
110
|
additionalProperties: true,
|
|
@@ -108,22 +112,22 @@ function apply(ctx) {
|
|
|
108
112
|
label: {
|
|
109
113
|
type: "string",
|
|
110
114
|
required: true,
|
|
111
|
-
description: "Short user-facing option label."
|
|
115
|
+
description: "Short user-facing option label."
|
|
112
116
|
},
|
|
113
117
|
description: {
|
|
114
118
|
type: "string",
|
|
115
|
-
description: "One sentence explaining the tradeoff or impact."
|
|
119
|
+
description: "One sentence explaining the tradeoff or impact."
|
|
116
120
|
},
|
|
117
121
|
recommended: {
|
|
118
122
|
type: "boolean",
|
|
119
|
-
description:
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
123
|
+
description: 'Set true on the option you recommend (alternative to the "(Recommended)" label suffix).'
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
127
131
|
},
|
|
128
132
|
output: {
|
|
129
133
|
schema: {
|
|
@@ -132,16 +136,16 @@ function apply(ctx) {
|
|
|
132
136
|
properties: {
|
|
133
137
|
rejected: {
|
|
134
138
|
type: "boolean",
|
|
135
|
-
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."
|
|
136
140
|
},
|
|
137
141
|
violations: {
|
|
138
142
|
type: "array",
|
|
139
143
|
items: { type: "string" },
|
|
140
|
-
description: "Validation violations when rejected."
|
|
144
|
+
description: "Validation violations when rejected."
|
|
141
145
|
},
|
|
142
146
|
error: {
|
|
143
147
|
type: "string",
|
|
144
|
-
description: "Human-readable error when rejected."
|
|
148
|
+
description: "Human-readable error when rejected."
|
|
145
149
|
},
|
|
146
150
|
answers: {
|
|
147
151
|
type: "array",
|
|
@@ -154,37 +158,32 @@ function apply(ctx) {
|
|
|
154
158
|
id: {
|
|
155
159
|
type: "string",
|
|
156
160
|
required: true,
|
|
157
|
-
description: "The question id you supplied."
|
|
161
|
+
description: "The question id you supplied."
|
|
158
162
|
},
|
|
159
163
|
selected: {
|
|
160
164
|
type: "array",
|
|
161
165
|
required: true,
|
|
162
166
|
items: { type: "string" },
|
|
163
|
-
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)."
|
|
164
168
|
},
|
|
165
169
|
custom: {
|
|
166
170
|
type: "string",
|
|
167
|
-
description: "User-typed free text for this question, if any."
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
171
|
+
description: "User-typed free text for this question, if any."
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
173
177
|
},
|
|
174
|
-
render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
|
|
178
|
+
render: (_args, value) => [{ type: "text", text: JSON.stringify(value) }]
|
|
175
179
|
},
|
|
176
180
|
async execute(args, exec) {
|
|
177
|
-
// 1. input validation: only what the form cannot represent at all. Stem /
|
|
178
|
-
// option separation stays guidance, NOT enforced: substring matching
|
|
179
|
-
// would reject legitimate stems (e.g. a stem that naturally mentions an
|
|
180
|
-
// option name), so no stem check may refuse a round — a bad stem is
|
|
181
|
-
// preferable to a false rejection.
|
|
182
181
|
const violations = [];
|
|
183
|
-
const seenIds = new Set();
|
|
182
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
184
183
|
args.questions.forEach((question, questionIndex) => {
|
|
185
184
|
const where = `Question ${questionIndex + 1} (id ${JSON.stringify(question.id)})`;
|
|
186
|
-
if (typeof question.id === "string" && question.id.startsWith(
|
|
187
|
-
violations.push(`${where} uses the reserved prefix
|
|
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)`);
|
|
188
187
|
}
|
|
189
188
|
if (typeof question.id === "string") {
|
|
190
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`);
|
|
@@ -193,7 +192,7 @@ function apply(ctx) {
|
|
|
193
192
|
if (isBlank(question.question)) {
|
|
194
193
|
violations.push(`${where} has an empty question text; write the question itself in question and its body in detail`);
|
|
195
194
|
}
|
|
196
|
-
const seenLabels = new Set();
|
|
195
|
+
const seenLabels = /* @__PURE__ */ new Set();
|
|
197
196
|
(question.options ?? []).forEach((option, optionIndex) => {
|
|
198
197
|
const label = normalizeOption(option).label;
|
|
199
198
|
if (isBlank(label)) {
|
|
@@ -211,21 +210,14 @@ function apply(ctx) {
|
|
|
211
210
|
return {
|
|
212
211
|
rejected: true,
|
|
213
212
|
violations,
|
|
214
|
-
error: `${violations.length} violation(s) in this round: ids must be unique and must not use the reserved
|
|
215
|
-
answers: []
|
|
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: []
|
|
216
215
|
};
|
|
217
216
|
}
|
|
218
|
-
|
|
219
|
-
// 2. transform: force multi-select; merge the optional number into the
|
|
220
|
-
// header; hand detail to the field the client renders as markdown; move
|
|
221
|
-
// the recommendation marker to the label suffix the client renders;
|
|
222
|
-
// per-question supplement is via the built-in custom input ("Type your
|
|
223
|
-
// answer"/"输入你的答案") — no extra option is added to avoid
|
|
224
|
-
// duplication with that field
|
|
225
|
-
const labelRestore = new Map();
|
|
217
|
+
const labelRestore = /* @__PURE__ */ new Map();
|
|
226
218
|
const questions = args.questions.map((question) => {
|
|
227
219
|
const header = mergeNumberIntoHeader(question.number, question.header);
|
|
228
|
-
const restore = new Map();
|
|
220
|
+
const restore = /* @__PURE__ */ new Map();
|
|
229
221
|
const options = (question.options ?? []).map((option) => {
|
|
230
222
|
const normalized = normalizeOption(option);
|
|
231
223
|
if (normalized.label !== normalized.originalLabel) {
|
|
@@ -233,31 +225,28 @@ function apply(ctx) {
|
|
|
233
225
|
}
|
|
234
226
|
return {
|
|
235
227
|
label: normalized.label,
|
|
236
|
-
...
|
|
228
|
+
...normalized.description !== void 0 ? { description: normalized.description } : {}
|
|
237
229
|
};
|
|
238
230
|
});
|
|
239
231
|
labelRestore.set(question.id, restore);
|
|
240
232
|
return {
|
|
241
233
|
id: question.id,
|
|
242
234
|
question: question.question,
|
|
243
|
-
...
|
|
244
|
-
...
|
|
235
|
+
...header !== "" ? { header } : {},
|
|
236
|
+
...isBlank(question.detail) ? {} : { detail: question.detail },
|
|
245
237
|
options,
|
|
246
|
-
multiSelect: true
|
|
238
|
+
multiSelect: true
|
|
247
239
|
};
|
|
248
240
|
});
|
|
249
|
-
|
|
250
|
-
// 3. round-end supplement question — single "无需补充" option; supplement is via custom input, so no "I have something to add" option (duplicates that field)
|
|
251
241
|
questions.push({
|
|
252
242
|
...ROUND_END_QUESTION,
|
|
253
243
|
options: ROUND_END_QUESTION.options.map((option) => ({ ...option })),
|
|
244
|
+
multiSelect: true
|
|
254
245
|
});
|
|
255
|
-
|
|
256
|
-
// 4. ask through the userQuestions seam (UI renders from the service)
|
|
257
246
|
const answer = await ctx.userQuestions.ask({
|
|
258
247
|
questions,
|
|
259
|
-
...
|
|
260
|
-
signal: exec.signal
|
|
248
|
+
...exec.agent !== void 0 ? { agent: exec.agent } : {},
|
|
249
|
+
signal: exec.signal
|
|
261
250
|
});
|
|
262
251
|
return {
|
|
263
252
|
answers: answer.answers.map((entry) => {
|
|
@@ -265,12 +254,16 @@ function apply(ctx) {
|
|
|
265
254
|
return {
|
|
266
255
|
id: entry.id,
|
|
267
256
|
selected: entry.selected.map((label) => restore?.get(label) ?? label),
|
|
268
|
-
...
|
|
257
|
+
...entry.custom !== void 0 ? { custom: entry.custom } : {}
|
|
269
258
|
};
|
|
270
|
-
})
|
|
259
|
+
})
|
|
271
260
|
};
|
|
272
|
-
}
|
|
261
|
+
}
|
|
273
262
|
}));
|
|
274
263
|
}
|
|
275
|
-
|
|
276
|
-
|
|
264
|
+
export {
|
|
265
|
+
apply,
|
|
266
|
+
inject,
|
|
267
|
+
name
|
|
268
|
+
};
|
|
269
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -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": ";AAAA,SAAS,kBAAkB;AAC3B,OAAO;;;ACMA,IAAM,qBAAqB;AAM3B,IAAM,qBAAqB,OAAO,OAAO;AAAA,EAC9C,IAAI,GAAG,kBAAkB;AAAA,EACzB,UAAU;AAAA,EACV,QAAQ;AAAA,EACR,SAAS,OAAO,OAAO,CAAC,OAAO,OAAO,EAAE,OAAO,2BAAO,CAAC,CAAC,CAAC;AAC3D,CAAC;AAOM,SAAS,QAAQ,OAAO;AAC7B,SAAO,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM;AACvD;AASO,SAAS,sBAAsB,QAAQ,QAAQ;AACpD,QAAM,MAAM,QAAQ,MAAM,IAAI,SAAY;AAC1C,QAAM,OAAO,QAAQ,MAAM,IAAI,SAAY;AAC3C,MAAI,QAAQ,OAAW,QAAO,QAAQ;AACtC,MAAI,SAAS,OAAW,QAAO;AAC/B,SAAO,KAAK,WAAW,GAAG,IAAI,OAAO,GAAG,GAAG,SAAM,IAAI;AACvD;;;AChCA,IAAM,SAAS;AAEf,IAAM,mBAAmB,4BAAkB,MAAM;AAEjD,IAAM,YAAY;AAClB,IAAM,uBAAuB;AAE7B,IAAM,mBAAmB,IAAI,OAAO,UAAU,SAAS,SAAS,gBAAgB,IAAI,oBAAoB,OAAO,GAAG;AAClH,IAAM,cAAc,IAAI,OAAO,UAAU,SAAS,WAAW,MAAM,KAAK,oBAAoB,OAAO,GAAG;AAG/F,IAAM,2BAA2B;AAOjC,SAAS,0BAA0B,MAAM;AAC9C,MAAI,OAAO,SAAS,SAAU,QAAO,EAAE,MAAM,OAAO,MAAM;AAC1D,QAAM,QAAQ,iBAAiB,KAAK,IAAI,KAAK,YAAY,KAAK,IAAI;AAClE,MAAI,UAAU,KAAM,QAAO,EAAE,MAAM,OAAO,MAAM;AAChD,SAAO,EAAE,MAAM,KAAK,MAAM,GAAG,MAAM,KAAK,EAAE,QAAQ,QAAQ,EAAE,GAAG,OAAO,KAAK;AAC7E;AAQO,SAAS,gBAAgB,QAAQ;AACtC,QAAM,QAAQ,0BAA0B,OAAO,KAAK;AACpD,QAAM,cAAc,OAAO,gBAAgB,SAAY,SAAY,0BAA0B,OAAO,WAAW;AAC/G,QAAM,cAAc,OAAO,gBAAgB,QAAQ,MAAM,SAAU,gBAAgB,UAAa,YAAY;AAC5G,QAAM,aAAa,MAAM;AACzB,SAAO;AAAA,IACL,eAAe,OAAO;AAAA,IACtB,OAAO,cAAc,GAAG,UAAU,GAAG,wBAAwB,KAAK;AAAA,IAClE,aAAa,gBAAgB,SAAY,SAAY,YAAY;AAAA,IACjE;AAAA,EACF;AACF;;;AFVA,IAAM,OAAO;AACb,IAAM,SAAS,CAAC,SAAS,eAAe;AAGxC,IAAM,aAAa;AAQnB,SAAS,WAAW,QAAQ;AAC1B,MAAI,WAAW,UAAa,WAAW,KAAM,QAAO,EAAE,SAAS,MAAM;AACrE,MAAI,OAAO,WAAW,YAAY,MAAM,QAAQ,MAAM,GAAG;AACvD,UAAM,IAAI,UAAU,GAAG,IAAI,wDAAwD,UAAU,GAAG;AAAA,EAClG;AACA,QAAM,UAAU,OAAO,KAAK,MAAM,EAAE,OAAO,CAAC,QAAQ,QAAQ,UAAU;AACtE,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI,UAAU,GAAG,IAAI,2BAA2B,QAAQ,IAAI,CAAC,QAAQ,KAAK,UAAU,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,+BAA+B,UAAU,GAAG;AAAA,EACxJ;AACA,MAAI,OAAO,UAAU,MAAM,UAAa,OAAO,OAAO,UAAU,MAAM,WAAW;AAC/E,UAAM,IAAI,UAAU,GAAG,IAAI,YAAY,UAAU,oBAAoB;AAAA,EACvE;AACA,SAAO,EAAE,SAAS,OAAO,UAAU,MAAM,KAAK;AAChD;AAEA,SAAS,MAAM,KAAK,QAAQ;AAC1B,MAAI,WAAW,MAAM,EAAE,QAAS;AAChC,MAAI,MAAM,SAAS,WAAW;AAAA,IAC5B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,MACV,WAAW;AAAA,QACT,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,QACb,OAAO;AAAA,UACL,MAAM;AAAA,UACN,sBAAsB;AAAA,UACtB,YAAY;AAAA,YACV,IAAI;AAAA,cACF,MAAM;AAAA,cACN,UAAU;AAAA,cACV,aAAa;AAAA,YACf;AAAA,YACA,UAAU;AAAA,cACR,MAAM;AAAA,cACN,UAAU;AAAA,cACV,aAAa;AAAA,YACf;AAAA,YACA,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,SAAS;AAAA,cACP,MAAM;AAAA,cACN,aAAa;AAAA,cACb,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,sBAAsB;AAAA,gBACtB,YAAY;AAAA,kBACV,OAAO;AAAA,oBACL,MAAM;AAAA,oBACN,UAAU;AAAA,oBACV,aAAa;AAAA,kBACf;AAAA,kBACA,aAAa;AAAA,oBACX,MAAM;AAAA,oBACN,aAAa;AAAA,kBACf;AAAA,kBACA,aAAa;AAAA,oBACX,MAAM;AAAA,oBACN,aAAa;AAAA,kBACf;AAAA,gBACF;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IACA,QAAQ;AAAA,MACN,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,sBAAsB;AAAA,QACtB,YAAY;AAAA,UACV,UAAU;AAAA,YACR,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,YAAY;AAAA,YACV,MAAM;AAAA,YACN,OAAO,EAAE,MAAM,SAAS;AAAA,YACxB,aAAa;AAAA,UACf;AAAA,UACA,OAAO;AAAA,YACL,MAAM;AAAA,YACN,aAAa;AAAA,UACf;AAAA,UACA,SAAS;AAAA,YACP,MAAM;AAAA,YACN,UAAU;AAAA,YACV,aAAa;AAAA,YACb,OAAO;AAAA,cACL,MAAM;AAAA,cACN,sBAAsB;AAAA,cACtB,YAAY;AAAA,gBACV,IAAI;AAAA,kBACF,MAAM;AAAA,kBACN,UAAU;AAAA,kBACV,aAAa;AAAA,gBACf;AAAA,gBACA,UAAU;AAAA,kBACR,MAAM;AAAA,kBACN,UAAU;AAAA,kBACV,OAAO,EAAE,MAAM,SAAS;AAAA,kBACxB,aAAa;AAAA,gBACf;AAAA,gBACA,QAAQ;AAAA,kBACN,MAAM;AAAA,kBACN,aAAa;AAAA,gBACf;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,MACA,QAAQ,CAAC,OAAO,UAAU,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,KAAK,EAAE,CAAC;AAAA,IAC1E;AAAA,IACA,MAAM,QAAQ,MAAM,MAAM;AAMxB,YAAM,aAAa,CAAC;AACpB,YAAM,UAAU,oBAAI,IAAI;AACxB,WAAK,UAAU,QAAQ,CAAC,UAAU,kBAAkB;AAClD,cAAM,QAAQ,YAAY,gBAAgB,CAAC,QAAQ,KAAK,UAAU,SAAS,EAAE,CAAC;AAC9E,YAAI,OAAO,SAAS,OAAO,YAAY,SAAS,GAAG,WAAW,kBAAkB,GAAG;AACjF,qBAAW,KAAK,GAAG,KAAK,6BAA6B,kBAAkB,+CAA+C;AAAA,QACxH;AACA,YAAI,OAAO,SAAS,OAAO,UAAU;AACnC,cAAI,QAAQ,IAAI,SAAS,EAAE,EAAG,YAAW,KAAK,GAAG,KAAK,iGAAiG;AACvJ,kBAAQ,IAAI,SAAS,EAAE;AAAA,QACzB;AACA,YAAI,QAAQ,SAAS,QAAQ,GAAG;AAC9B,qBAAW,KAAK,GAAG,KAAK,2FAA2F;AAAA,QACrH;AACA,cAAM,aAAa,oBAAI,IAAI;AAC3B,SAAC,SAAS,WAAW,CAAC,GAAG,QAAQ,CAAC,QAAQ,gBAAgB;AACxD,gBAAM,QAAQ,gBAAgB,MAAM,EAAE;AACtC,cAAI,QAAQ,KAAK,GAAG;AAClB,uBAAW,KAAK,GAAG,KAAK,YAAY,cAAc,CAAC,4EAA4E;AAC/H;AAAA,UACF;AACA,cAAI,WAAW,IAAI,KAAK,GAAG;AACzB,uBAAW,KAAK,GAAG,KAAK,oDAAoD,KAAK,UAAU,KAAK,CAAC,kEAAkE;AACnK;AAAA,UACF;AACA,qBAAW,IAAI,KAAK;AAAA,QACtB,CAAC;AAAA,MACH,CAAC;AACD,UAAI,WAAW,SAAS,GAAG;AACzB,eAAO;AAAA,UACL,UAAU;AAAA,UACV;AAAA,UACA,OAAO,GAAG,WAAW,MAAM,iFAAiF,kBAAkB;AAAA,UAC9H,SAAS,CAAC;AAAA,QACZ;AAAA,MACF;AAQA,YAAM,eAAe,oBAAI,IAAI;AAC7B,YAAM,YAAY,KAAK,UAAU,IAAI,CAAC,aAAa;AACjD,cAAM,SAAS,sBAAsB,SAAS,QAAQ,SAAS,MAAM;AACrE,cAAM,UAAU,oBAAI,IAAI;AACxB,cAAM,WAAW,SAAS,WAAW,CAAC,GAAG,IAAI,CAAC,WAAW;AACvD,gBAAM,aAAa,gBAAgB,MAAM;AACzC,cAAI,WAAW,UAAU,WAAW,eAAe;AACjD,oBAAQ,IAAI,WAAW,OAAO,WAAW,aAAa;AAAA,UACxD;AACA,iBAAO;AAAA,YACL,OAAO,WAAW;AAAA,YAClB,GAAI,WAAW,gBAAgB,SAAY,EAAE,aAAa,WAAW,YAAY,IAAI,CAAC;AAAA,UACxF;AAAA,QACF,CAAC;AACD,qBAAa,IAAI,SAAS,IAAI,OAAO;AACrC,eAAO;AAAA,UACL,IAAI,SAAS;AAAA,UACb,UAAU,SAAS;AAAA,UACnB,GAAI,WAAW,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,UAClC,GAAI,QAAQ,SAAS,MAAM,IAAI,CAAC,IAAI,EAAE,QAAQ,SAAS,OAAO;AAAA,UAC9D;AAAA,UACA,aAAa;AAAA,QACf;AAAA,MACF,CAAC;AAGD,gBAAU,KAAK;AAAA,QACb,GAAG;AAAA,QACH,SAAS,mBAAmB,QAAQ,IAAI,CAAC,YAAY,EAAE,GAAG,OAAO,EAAE;AAAA,QACnE,aAAa;AAAA,MACf,CAAC;AAGD,YAAM,SAAS,MAAM,IAAI,cAAc,IAAI;AAAA,QACzC;AAAA,QACA,GAAI,KAAK,UAAU,SAAY,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;AAAA,QACxD,QAAQ,KAAK;AAAA,MACf,CAAC;AACD,aAAO;AAAA,QACL,SAAS,OAAO,QAAQ,IAAI,CAAC,UAAU;AACrC,gBAAM,UAAU,aAAa,IAAI,MAAM,EAAE;AACzC,iBAAO;AAAA,YACL,IAAI,MAAM;AAAA,YACV,UAAU,MAAM,SAAS,IAAI,CAAC,UAAU,SAAS,IAAI,KAAK,KAAK,KAAK;AAAA,YACpE,GAAI,MAAM,WAAW,SAAY,EAAE,QAAQ,MAAM,OAAO,IAAI,CAAC;AAAA,UAC/D;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,CAAC,CAAC;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/package.json
CHANGED
|
@@ -1,27 +1,49 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lynn123411/dsh-ask-user-grilling",
|
|
3
|
-
"version": "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.",
|
|
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
|
+
},
|
|
16
30
|
"scripts": {
|
|
17
|
-
"
|
|
18
|
-
"
|
|
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"
|
|
19
36
|
},
|
|
20
37
|
"peerDependencies": {
|
|
21
38
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
22
39
|
"@deepseek-ai/dsh-tools": "0.1.6-alpha.2",
|
|
23
40
|
"@deepseek-ai/dsh-user-questions": "0.1.6-alpha.2"
|
|
24
41
|
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"esbuild": "^0.25.0",
|
|
44
|
+
"react": "^18.3.1",
|
|
45
|
+
"react-dom": "^18.3.1"
|
|
46
|
+
},
|
|
25
47
|
"engines": {
|
|
26
48
|
"dsh": "0.1.6-alpha.2",
|
|
27
49
|
"node": "^22.19 || >=24"
|
package/lib/recommendation.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// 推荐标记的归一化。
|
|
2
|
-
//
|
|
3
|
-
// 客户端(@deepseek-ai/dsh-client-ui-user-questions 的 parseRecommendedLabel)
|
|
4
|
-
// 只有一个判定依据:option.label 必须以半角或全角的括号推荐字样收尾;option.description
|
|
5
|
-
// 完全不参与。模型经常把推荐写成 description 末尾的「;推荐」,或者写成 label 里不带
|
|
6
|
-
// 括号的形式,于是徽标不渲染、标记原样显示成文字。
|
|
7
|
-
//
|
|
8
|
-
// 这里在题目交给 ctx.userQuestions.ask 之前把标记统一搬到 label 末尾的「(推荐)」,
|
|
9
|
-
// 客户端就能认出并渲染徽标。显式字段 recommended 是另一条更明确的信号,优先于文字。
|
|
10
|
-
|
|
11
|
-
const MARKER = 'recommended|推荐';
|
|
12
|
-
// 半角/全角圆括号与方括号/方头括号
|
|
13
|
-
const BRACKETED_MARKER = `[((【\\[]\\s*(?:${MARKER})\\s*[))】\\]]`;
|
|
14
|
-
// 无括号写法必须由分隔符引出,否则「查看推荐」这种正常文字会被误判
|
|
15
|
-
const SEPARATOR = ';;,,、::\\-—';
|
|
16
|
-
const TRAILING_PUNCTUATION = '。.!!??;;,,、~~\\s';
|
|
17
|
-
|
|
18
|
-
const BRACKETED_SUFFIX = new RegExp(`[\\s]*[${SEPARATOR}]?\\s*${BRACKETED_MARKER}[${TRAILING_PUNCTUATION}]*$`, 'i');
|
|
19
|
-
const BARE_SUFFIX = new RegExp(`[\\s]*[${SEPARATOR}]\\s*(?:${MARKER})[${TRAILING_PUNCTUATION}]*$`, 'i');
|
|
20
|
-
|
|
21
|
-
/** 供测试核对客户端规则的推荐标记后缀;客户端只认 label 末尾的这两种括号写法。 */
|
|
22
|
-
export const RECOMMENDED_LABEL_SUFFIX = '(推荐)';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 去掉文本末尾的推荐标记。
|
|
26
|
-
* @param {string} text - label 或 description 原文。
|
|
27
|
-
* @returns {{ text: string, found: boolean }} 去掉标记后的文本,以及是否找到标记。
|
|
28
|
-
*/
|
|
29
|
-
export function stripRecommendationMarker(text) {
|
|
30
|
-
if (typeof text !== 'string') return { text, found: false };
|
|
31
|
-
const match = BRACKETED_SUFFIX.exec(text) ?? BARE_SUFFIX.exec(text);
|
|
32
|
-
if (match === null) return { text, found: false };
|
|
33
|
-
return { text: text.slice(0, match.index).replace(/\s+$/, ''), found: true };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* 把一个 option 归一化成客户端能渲染的形式。
|
|
38
|
-
* @param {{ label: string, description?: string, recommended?: boolean }} option - 模型给的选项。
|
|
39
|
-
* @returns {{ originalLabel: string, label: string, description?: string, recommended: boolean }}
|
|
40
|
-
* 交给界面时用 label/description;回传答案时用 originalLabel 还原模型原本写的 label。
|
|
41
|
-
*/
|
|
42
|
-
export function normalizeOption(option) {
|
|
43
|
-
const label = stripRecommendationMarker(option.label);
|
|
44
|
-
const description = option.description === undefined ? undefined : stripRecommendationMarker(option.description);
|
|
45
|
-
const recommended = option.recommended === true || label.found || (description !== undefined && description.found);
|
|
46
|
-
const cleanLabel = label.text;
|
|
47
|
-
return {
|
|
48
|
-
originalLabel: option.label,
|
|
49
|
-
label: recommended ? `${cleanLabel}${RECOMMENDED_LABEL_SUFFIX}` : cleanLabel,
|
|
50
|
-
description: description === undefined ? undefined : description.text,
|
|
51
|
-
recommended,
|
|
52
|
-
};
|
|
53
|
-
}
|