@deepseek-ai/dsh-client-ui-user-questions 0.0.1-rc.3
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/LICENSE +28 -0
- package/README.i18n.yaml +6 -0
- package/README.md +26 -0
- package/README.zh.md +26 -0
- package/lib/client.js +679 -0
- package/lib/index.js +16 -0
- package/lib/invariant.js +24 -0
- package/lib/types/client/PlanReviewPanel.d.ts +14 -0
- package/lib/types/client/QuestionComposer.d.ts +25 -0
- package/lib/types/client/contract/slots.d.ts +94 -0
- package/lib/types/client/index.d.ts +35 -0
- package/lib/types/client/locales.d.ts +36 -0
- package/lib/types/index.d.ts +14 -0
- package/lib/types/invariant.d.ts +16 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, DeepSeek
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.i18n.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
|
2
|
+
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
|
3
|
+
# after editing either side, bring the other along and re-record with:
|
|
4
|
+
# pnpm run verify-translation-pairing --write packages/client/ui-user-questions/README.md
|
|
5
|
+
README.md: ba4b23150a7f3a2536db97df99410cb92238210e
|
|
6
|
+
README.zh.md: 96c81800ef85514c565defecc779da7d2e6523b7
|
package/README.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-client-ui-user-questions
|
|
2
|
+
|
|
3
|
+
English | [中文](README.zh.md)
|
|
4
|
+
|
|
5
|
+
Web question feature plugin: its browser half registers the `question` entry in the conversation-owned `conversation.composer` keyed slot. Its host half is empty on purpose — mounting `dsh-tool-ask-user` there put the tool in the registry's GLOBAL layer, which merges into every agent regardless of the preset that composed it, so a two-tool benchmark preset really presented three. Rendering a question is a host UI capability; having the tool is an agent capability, so the `tool-ask-user` row belongs to the presets that want it (and to the TUI composition, which has no presets).
|
|
6
|
+
|
|
7
|
+
The component renders one question at a time with progress navigation, single- and multi-select choices, recommendation badges derived from label suffixes, and custom answers. A multi-select draft keeps its selected labels while the user opens or edits the custom answer, so its submitted item may carry both `selected` and `custom`; a single-select custom answer remains exclusive. Question detail reuses the assistant-output `MarkdownText` primitive, including its GFM rendering and untrusted-content policy. The capped card keeps its title, navigation, and submission actions fixed while long detail and choices share an internal scroll region. Single-select choices advance immediately, and Enter submits once every question is answered or skipped; Enter during IME composition confirms the input candidate without advancing. It submits one structured answer batch for the whole request: “Skip this question” retains other drafts and emits the existing blank `{ selected: [] }` shape for that item, while close rejects the whole wait as `ASK_CANCELLED`.
|
|
8
|
+
|
|
9
|
+
A request whose single question declares a presentation intent renders as that intent's own surface instead. `plan-review` — set by `dsh-plan-mode` on the `exit_plan_mode` review — takes the waiting-approval card shape: a `Plan review` strip, the plan as the scrolling markdown body, the question text as the card's accessible name, and one decision row of `Chat about it` / `Refuse` / `Approve`. Approve and Refuse answer with the asker's own option labels (the intent names which label approves, so the verdict never rides option order) and keep the asker's descriptions as tooltips; `Chat about it` rejects the wait as `ASK_CANCELLED`, returning the composer so the user can say what they want instead. The card claims a request only when it can send every answer that request allows: one question, the intent declared, the plan present as `detail`, the named approve label offered, and a binary single choice (at most one option besides approve, not multi-select). Anything else — no intent, a batch of several questions, a missing plan, an approve label naming no option, a third option, a multi-select decision — stays on the generic flow, which can express it. An intent changes the layout, never which answers are reachable.
|
|
10
|
+
|
|
11
|
+
Selection state is local to a component keyed by the request rpcId. A replay with the same id preserves a still-mounted draft, while `question/resolved` from the host removes the composer. The host remains authoritative: successful HTTP delivery does not remove pending state locally.
|
|
12
|
+
|
|
13
|
+
Composer chrome copy (pager, buttons, placeholders, validation feedback) is bilingual: the plugin registers zh/en dictionaries under the `question` namespace of `dsh-client-locale` and hands the entry its bound translator plus the locale snapshot source through the inject face, so a locale switch re-renders a mounted composer. Question and option text arrives from the model and renders verbatim; carrier failure messages also display untranslated.
|
|
14
|
+
|
|
15
|
+
## Model Experience
|
|
16
|
+
|
|
17
|
+
Indirectly, through `dsh-tool-ask-user`; that package owns the model-visible tool schema and structured result.
|
|
18
|
+
|
|
19
|
+
#### KV Cache effect
|
|
20
|
+
|
|
21
|
+
No direct invalidation; `dsh-tool-ask-user` owns the model-visible tool call and result.
|
|
22
|
+
|
|
23
|
+
## Known Limitations and Deferred Work
|
|
24
|
+
|
|
25
|
+
- **Unsubmitted drafts are not durable** — reconnect resync or a full page reload restores the host-owned pending request with the same rpcId, but a composer unmount resets local option and custom-text drafts.
|
|
26
|
+
- **One request owns the composer at a time** — later pending requests remain in the session snapshot and become visible after the earlier request resolves.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# @deepseek-ai/dsh-client-ui-user-questions
|
|
2
|
+
|
|
3
|
+
[English](README.md) | 中文
|
|
4
|
+
|
|
5
|
+
Web 提问功能插件:其浏览器侧把 `question` 条目注册到会话拥有的 `conversation.composer` 键控 slot 中。其主机侧刻意为空——在那里挂载 `dsh-tool-ask-user` 会把工具放进注册表的**全局层**,而全局层会并入每一个 agent(智能体),无论它由哪个 preset 组装,于是一个「两工具」的 benchmark preset 实际会呈现三个。渲染提问是宿主的 UI 能力,拥有该工具则是 agent 的能力,因此 `tool-ask-user` 行属于需要它的各个 preset(以及没有 preset 的 TUI 组装)。
|
|
6
|
+
|
|
7
|
+
组件每次渲染一个问题,提供进度导航、单选和多选选项、由标签后缀派生的推荐徽标,以及自定义答案。用户打开或编辑自定义答案时,多选题草稿会保留已选中的标签,因此提交项可以同时携带 `selected` 与 `custom`;单选题的自定义答案仍保持互斥。问题详情复用助手输出的 `MarkdownText` 原语,包括其 GFM 渲染与不受信任内容策略。限高卡片保持标题、导航与提交动作固定,超长的详情与选项共享内部滚动区。选择单选选项后会立即前进;所有问题均已回答或跳过后,Enter 会提交;IME 组合输入期间按 Enter 只会确认输入候选,不会前进。组件为整个请求提交一批结构化答案:「跳过此问题」会保留其他草稿,并为该项发出既有的空 `{ selected: [] }` 形状;关闭则以 `ASK_CANCELLED` 拒绝整个等待。
|
|
8
|
+
|
|
9
|
+
若某个请求的唯一问题声明了呈现意图,则改为渲染该意图自己的界面。`plan-review`——由 `dsh-plan-mode` 在 `exit_plan_mode` 审阅上设置——采用等待审批卡片的形状:一条 `Plan review` 条带、计划作为可滚动的 markdown 主体、问题文本作为卡片的无障碍名称,以及一行 `Chat about it` / `Refuse` / `Approve` 的决定操作。Approve 与 Refuse 用提问方自己的选项标签回答(意图指名哪个标签表示批准,因此裁决绝不依赖选项顺序),并把提问方的描述保留为 tooltip;`Chat about it` 以 `ASK_CANCELLED` 拒绝该等待,让编辑器归位,用户可以直接说出他想说的话。卡片只在能够发出该请求允许的每一个答案时才接管:只有一个问题、声明了意图、计划以 `detail` 存在、提供了被指名的批准标签,且是二元单选(除批准外最多一个选项,且非多选)。其他任何情形——没有意图、一批含多个问题、缺少计划、批准标签未命中任何选项、出现第三个选项、多选决定——都留在能够表达它的通用流程上。意图改变的只是布局,从不改变可达的答案。
|
|
10
|
+
|
|
11
|
+
选择状态只存在于以请求 rpcId 为 key 的组件本地。使用相同 id 回放时,只要组件仍挂载,就会保留草稿;主机发出的 `question/resolved` 则会移除编辑器。主机仍具有最终决定权:HTTP 交付成功不会在本地移除待处理状态。
|
|
12
|
+
|
|
13
|
+
编辑器外框文案(翻页器、按钮、占位符、校验提示)是双语的:插件在 `dsh-client-locale` 的 `question` 命名空间下注册 zh/en 词典,并通过 inject face 把绑定的翻译函数和 locale 快照源交给该条目,因此切换语言会重新渲染已挂载的编辑器。问题与选项文本来自模型并原样渲染;载体失败消息也不经翻译直接显示。
|
|
14
|
+
|
|
15
|
+
## 模型体验
|
|
16
|
+
|
|
17
|
+
通过 `dsh-tool-ask-user` 间接影响;该包拥有模型可见的工具 schema 和结构化结果。
|
|
18
|
+
|
|
19
|
+
#### KV Cache 影响
|
|
20
|
+
|
|
21
|
+
不会直接失效;模型可见的工具调用与结果由 `dsh-tool-ask-user` 拥有。
|
|
22
|
+
|
|
23
|
+
## 已知限制与暂缓事项
|
|
24
|
+
|
|
25
|
+
- **未提交的草稿不持久**:重新连接再同步或完整刷新页面时,会恢复主机拥有且 rpcId 相同的待处理请求,但编辑器卸载会重置本地选项和自定义文本草稿。
|
|
26
|
+
- **每次只有一个请求拥有编辑器**:后续待处理请求仍留在会话快照中,并在较早请求落定后显示。
|
package/lib/client.js
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({
|
|
2
|
+
id: "@deepseek-ai/dsh-client-ui-user-questions",
|
|
3
|
+
factory: (require) => {
|
|
4
|
+
var module = { exports: {} };
|
|
5
|
+
var exports = module.exports;
|
|
6
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
7
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
8
|
+
let react = require("react");
|
|
9
|
+
let _deepseek_ai_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
|
|
10
|
+
//#region ../../../node_modules/.pnpm/clsx@2.1.1/node_modules/clsx/dist/clsx.mjs
|
|
11
|
+
function r(e) {
|
|
12
|
+
var t, f, n = "";
|
|
13
|
+
if ("string" == typeof e || "number" == typeof e) n += e;
|
|
14
|
+
else if ("object" == typeof e) if (Array.isArray(e)) {
|
|
15
|
+
var o = e.length;
|
|
16
|
+
for (t = 0; t < o; t++) e[t] && (f = r(e[t])) && (n && (n += " "), n += f);
|
|
17
|
+
} else for (f in e) e[f] && (n && (n += " "), n += f);
|
|
18
|
+
return n;
|
|
19
|
+
}
|
|
20
|
+
function clsx() {
|
|
21
|
+
for (var e, t, f = 0, n = "", o = arguments.length; f < o; f++) (e = arguments[f]) && (t = r(e)) && (n && (n += " "), n += t);
|
|
22
|
+
return n;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region lib/types/client/contract/slots.js
|
|
26
|
+
/**
|
|
27
|
+
* Narrow a request to a renderable plan review, or return undefined to leave it
|
|
28
|
+
* to the generic question flow.
|
|
29
|
+
*
|
|
30
|
+
* The card is one decision over one plan, and it claims a request only when it
|
|
31
|
+
* can send every answer that request allows — an intent changes the layout,
|
|
32
|
+
* never which answers are reachable. So the batch must be a single question
|
|
33
|
+
* that declares the intent, carries the plan as its detail, offers the approve
|
|
34
|
+
* label the intent names, and is a binary single choice: at most one option
|
|
35
|
+
* besides approve, and not multi-select. A third option or a multi-select batch
|
|
36
|
+
* has answers two buttons cannot express, so the generic flow keeps it — as it
|
|
37
|
+
* keeps any request whose intent the asker's own service would have rejected,
|
|
38
|
+
* because the client sits downstream of a wire boundary and every request must
|
|
39
|
+
* stay answerable.
|
|
40
|
+
*
|
|
41
|
+
* @param questions - the request's whole question batch.
|
|
42
|
+
* @returns The narrowed review, or undefined when the generic flow owns it.
|
|
43
|
+
*/
|
|
44
|
+
function planReviewOf(questions) {
|
|
45
|
+
if (questions.length !== 1) return void 0;
|
|
46
|
+
const question = questions[0];
|
|
47
|
+
const intent = question.intent;
|
|
48
|
+
if (intent?.kind !== "plan-review" || question.detail === void 0) return void 0;
|
|
49
|
+
if (question.multiSelect === true) return void 0;
|
|
50
|
+
const options = question.options ?? [];
|
|
51
|
+
if (options.length > 2) return void 0;
|
|
52
|
+
const approve = options.find((option) => option.label === intent.approve);
|
|
53
|
+
if (approve === void 0) return void 0;
|
|
54
|
+
const decline = options.find((option) => option.label !== intent.approve);
|
|
55
|
+
return {
|
|
56
|
+
id: question.id,
|
|
57
|
+
question: question.question,
|
|
58
|
+
plan: question.detail,
|
|
59
|
+
approve,
|
|
60
|
+
...decline === void 0 ? {} : { decline }
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Question domain face over the carrier: render identity and questions
|
|
65
|
+
* transparently forwarded; answer/cancel own the wire encoding (the success
|
|
66
|
+
* fields and the cancelled error) and turn a rejected carrier receipt into a
|
|
67
|
+
* thrown error. Components mint one per carrier via useMemo (never inside a
|
|
68
|
+
* select — a per-dispatch mint would churn identity and break memoization).
|
|
69
|
+
*/
|
|
70
|
+
var PendingQuestion = class {
|
|
71
|
+
wait;
|
|
72
|
+
/**
|
|
73
|
+
* @param wait - the runtime carrier for one pending question request.
|
|
74
|
+
*/
|
|
75
|
+
constructor(wait) {
|
|
76
|
+
this.wait = wait;
|
|
77
|
+
}
|
|
78
|
+
/** Opaque render identity (React key / draft remount axis), forwarded from the carrier. */
|
|
79
|
+
get key() {
|
|
80
|
+
return this.wait.key;
|
|
81
|
+
}
|
|
82
|
+
/** The request's question list, forwarded from the carrier payload. */
|
|
83
|
+
get questions() {
|
|
84
|
+
return this.wait.payload.questions;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Deliver the whole answer batch; a rejected carrier receipt throws.
|
|
88
|
+
* @param answer - complete structured answer batch.
|
|
89
|
+
*/
|
|
90
|
+
async answer(answer) {
|
|
91
|
+
const receipt = await this.wait.respond({
|
|
92
|
+
ok: true,
|
|
93
|
+
value: {
|
|
94
|
+
sessionId: this.wait.sessionId,
|
|
95
|
+
answer
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
if (!receipt.accepted) throw new Error(`question response rejected: ${receipt.reason}`);
|
|
99
|
+
}
|
|
100
|
+
/** Reject the whole wait (the host resolves the tool call as cancelled); a rejected receipt throws. */
|
|
101
|
+
async cancel() {
|
|
102
|
+
const receipt = await this.wait.respond({
|
|
103
|
+
ok: false,
|
|
104
|
+
error: {
|
|
105
|
+
code: "cancelled",
|
|
106
|
+
message: "the user closed this question request",
|
|
107
|
+
details: {}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
if (!receipt.accepted) throw new Error(`question cancellation rejected: ${receipt.reason}`);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-user-questions/src/client/PlanReviewPanel.module.css.mjs
|
|
115
|
+
const css$1 = ".LVzXQa_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.LVzXQa_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-state-warn-secondary);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;display:flex;overflow:hidden}.LVzXQa_card,.LVzXQa_card *{box-sizing:border-box}.LVzXQa_strip{background:var(--dsw-alias-state-warn-tertiary);color:var(--dsw-alias-state-warn-primary);flex-shrink:0;align-items:center;gap:8px;padding:10px 16px;font-size:13px;line-height:18px;display:flex}.LVzXQa_dot{background:var(--dsw-alias-state-warn-primary);border-radius:50%;width:8px;height:8px}.LVzXQa_body{overscroll-behavior:contain;flex:auto;min-height:0;padding:12px 16px 4px;font-size:14px;line-height:22px;overflow-y:auto}.LVzXQa_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;padding:8px 16px 12px;display:flex}.LVzXQa_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);font-size:11px;line-height:16px}.LVzXQa_actions{flex-shrink:0;align-items:center;gap:8px;display:flex}.LVzXQa_discuss{color:var(--dsw-alias-label-secondary);gap:6px}.LVzXQa_discuss:hover:not(:disabled){color:var(--dsw-alias-label-primary)}@media (width<=720px){.LVzXQa_card{border-radius:16px}.LVzXQa_body{padding:10px 12px 4px}.LVzXQa_footer{align-items:flex-end;padding:8px 12px 10px}}";
|
|
116
|
+
const tagId$1 = "@deepseek-ai/dsh-client-ui-user-questions/PlanReviewPanel.module.css";
|
|
117
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId$1) + "]") === null) {
|
|
118
|
+
const tag = document.createElement("style");
|
|
119
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-user-questions";
|
|
120
|
+
tag.dataset.pluginCss = tagId$1;
|
|
121
|
+
tag.textContent = css$1;
|
|
122
|
+
document.head.appendChild(tag);
|
|
123
|
+
}
|
|
124
|
+
var PlanReviewPanel_module_css_default = {
|
|
125
|
+
"card": "LVzXQa_card",
|
|
126
|
+
"body": "LVzXQa_body",
|
|
127
|
+
"actions": "LVzXQa_actions",
|
|
128
|
+
"dot": "LVzXQa_dot",
|
|
129
|
+
"frame": "LVzXQa_frame",
|
|
130
|
+
"feedback": "LVzXQa_feedback",
|
|
131
|
+
"strip": "LVzXQa_strip",
|
|
132
|
+
"footer": "LVzXQa_footer",
|
|
133
|
+
"discuss": "LVzXQa_discuss"
|
|
134
|
+
};
|
|
135
|
+
//#endregion
|
|
136
|
+
//#region lib/types/client/PlanReviewPanel.js
|
|
137
|
+
/**
|
|
138
|
+
* Optional-prop spread for a decision button's tooltip: `title` is optional on
|
|
139
|
+
* the DOM props, and exactOptionalPropertyTypes rejects an explicit undefined.
|
|
140
|
+
*
|
|
141
|
+
* @param description - the asker's option description, when it carries one.
|
|
142
|
+
* @returns The `title` prop to spread, or nothing.
|
|
143
|
+
*/
|
|
144
|
+
function tooltip(description) {
|
|
145
|
+
return description === void 0 ? {} : { title: description };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Render a plan review as a decision card.
|
|
149
|
+
*
|
|
150
|
+
* @param props - the question domain face, the narrowed plan review, and `t`.
|
|
151
|
+
* @returns The plan-review takeover for this request.
|
|
152
|
+
*/
|
|
153
|
+
function PlanReviewPanel({ pending, review, t }) {
|
|
154
|
+
const [busy, setBusy] = (0, react.useState)(false);
|
|
155
|
+
const [error, setError] = (0, react.useState)(null);
|
|
156
|
+
const settle = (send) => {
|
|
157
|
+
setBusy(true);
|
|
158
|
+
setError(null);
|
|
159
|
+
send().catch((cause) => {
|
|
160
|
+
setBusy(false);
|
|
161
|
+
setError(cause instanceof Error ? cause.message : String(cause));
|
|
162
|
+
});
|
|
163
|
+
};
|
|
164
|
+
const decide = (label) => {
|
|
165
|
+
settle(() => pending.answer({ answers: [{
|
|
166
|
+
id: review.id,
|
|
167
|
+
selected: [label]
|
|
168
|
+
}] }));
|
|
169
|
+
};
|
|
170
|
+
const decline = review.decline;
|
|
171
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
172
|
+
className: PlanReviewPanel_module_css_default.frame,
|
|
173
|
+
"data-plan-review-key": pending.key,
|
|
174
|
+
children: (0, react_jsx_runtime.jsxs)("section", {
|
|
175
|
+
className: PlanReviewPanel_module_css_default.card,
|
|
176
|
+
"aria-label": review.question,
|
|
177
|
+
children: [
|
|
178
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
179
|
+
className: PlanReviewPanel_module_css_default.strip,
|
|
180
|
+
children: [(0, react_jsx_runtime.jsx)("span", { className: PlanReviewPanel_module_css_default.dot }), t("plan.header")]
|
|
181
|
+
}),
|
|
182
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
183
|
+
className: PlanReviewPanel_module_css_default.body,
|
|
184
|
+
"data-plan-review-scroll": true,
|
|
185
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { text: review.plan })
|
|
186
|
+
}),
|
|
187
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
188
|
+
className: PlanReviewPanel_module_css_default.footer,
|
|
189
|
+
children: [(0, react_jsx_runtime.jsx)("div", {
|
|
190
|
+
className: PlanReviewPanel_module_css_default.feedback,
|
|
191
|
+
role: "status",
|
|
192
|
+
children: error
|
|
193
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
194
|
+
className: PlanReviewPanel_module_css_default.actions,
|
|
195
|
+
children: [
|
|
196
|
+
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
197
|
+
variant: "ghost",
|
|
198
|
+
className: PlanReviewPanel_module_css_default.discuss,
|
|
199
|
+
icon: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 14 }),
|
|
200
|
+
disabled: busy,
|
|
201
|
+
onClick: () => {
|
|
202
|
+
settle(() => pending.cancel());
|
|
203
|
+
},
|
|
204
|
+
children: t("plan.discuss")
|
|
205
|
+
}),
|
|
206
|
+
decline !== void 0 && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
207
|
+
variant: "outline",
|
|
208
|
+
...tooltip(decline.description),
|
|
209
|
+
disabled: busy,
|
|
210
|
+
onClick: () => {
|
|
211
|
+
decide(decline.label);
|
|
212
|
+
},
|
|
213
|
+
children: t("plan.decline")
|
|
214
|
+
}),
|
|
215
|
+
(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
216
|
+
variant: "primary",
|
|
217
|
+
...tooltip(review.approve.description),
|
|
218
|
+
disabled: busy,
|
|
219
|
+
onClick: () => {
|
|
220
|
+
decide(review.approve.label);
|
|
221
|
+
},
|
|
222
|
+
children: t("plan.approve")
|
|
223
|
+
})
|
|
224
|
+
]
|
|
225
|
+
})]
|
|
226
|
+
})
|
|
227
|
+
]
|
|
228
|
+
})
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
//#endregion
|
|
232
|
+
//#region \0dsh-css:/home/runner/work/deepseek-harness/deepseek-harness/packages/client/ui-user-questions/src/client/QuestionComposer.module.css.mjs
|
|
233
|
+
const css = ".Mbwy4a_frame{padding:6px calc(var(--dsh-composer-side-clearance) + 16px) 10px;justify-content:center;display:flex}.Mbwy4a_card{width:100%;max-width:var(--dsh-chat-content-width);border:1px solid var(--dsw-alias-border-l2-darkmode-thin);background:var(--dsw-specific-input-major);max-height:min(60vh,520px);box-shadow:var(--dsw-shadow-lv2);color:var(--dsw-alias-label-primary);--dsh-scrollbar-thumb:var(--dsw-alias-scrollbar-bg-l2);--dsh-scrollbar-thumb-hover:var(--dsw-alias-scrollbar-hover-l2);border-radius:20px;flex-direction:column;padding:0 0 10px;display:flex;overflow:hidden}.Mbwy4a_card,.Mbwy4a_card *{box-sizing:border-box}.Mbwy4a_header{flex-shrink:0;justify-content:space-between;align-items:flex-start;gap:16px;padding:20px 16px 0 24px;display:flex}.Mbwy4a_headingBlock{min-width:0}.Mbwy4a_eyebrow{color:var(--dsw-alias-label-tertiary);margin-bottom:5px;font-size:11px;line-height:16px}.Mbwy4a_title{margin:0;font-size:16px;font-weight:500;line-height:22px}.Mbwy4a_detail{margin:0 2px 8px}.Mbwy4a_footerActions{flex-shrink:0;align-items:center;gap:12px;display:flex}.Mbwy4a_pager{flex-shrink:0;align-items:center;gap:6px;display:flex}.Mbwy4a_progress{color:var(--dsw-alias-label-secondary);white-space:nowrap;word-spacing:-2px;padding:0 4px;font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_iconButton{width:24px;height:24px;color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:none;border-radius:999px;place-items:center;padding:0;display:grid}.Mbwy4a_iconButton:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}.Mbwy4a_iconButton:disabled{color:var(--dsw-alias-label-dimmed);cursor:default}.Mbwy4a_body{overscroll-behavior:contain;flex-direction:column;flex:auto;min-height:0;display:flex;overflow-y:auto}.Mbwy4a_options{flex-direction:column;gap:1px;margin:8px 0 0;padding:4px 12px;display:flex}.Mbwy4a_option{width:100%;min-height:40px;color:inherit;text-align:left;cursor:pointer;background:0 0;border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_option:hover:not(:disabled),.Mbwy4a_optionSelected{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_optionSelected{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_option:disabled{cursor:default}.Mbwy4a_number{background:var(--dsw-alias-bg-overlay);width:20px;height:20px;color:var(--dsw-alias-label-secondary);border-radius:6px;flex:0 0 20px;place-items:center;margin-top:2px;font-size:12px;font-weight:500;line-height:18px;display:grid}.Mbwy4a_checkbox{flex:0 0 20px;place-items:center;width:20px;height:20px;margin-top:2px;display:grid}.Mbwy4a_checkbox:before{content:\"\";border:1px solid var(--dsw-alias-border-l4);border-radius:4px;grid-area:1/1;width:14px;height:14px;transition:background-color .12s,border-color .12s}.Mbwy4a_checkbox>svg{grid-area:1/1}.Mbwy4a_checkboxChecked{color:var(--dsw-alias-label-primary-foreground)}.Mbwy4a_checkboxChecked:before{border-color:var(--dsw-alias-label-primary);background:var(--dsw-alias-label-primary)}.Mbwy4a_optionCopy{flex:1;min-width:0}.Mbwy4a_optionLine{flex-wrap:wrap;align-items:baseline;gap:2px 6px;display:flex}.Mbwy4a_optionLabel{font-size:14px;font-weight:500;line-height:24px}.Mbwy4a_badge{background:var(--dsw-specific-sidebar-nav-item-active-accent);color:var(--dsw-alias-button-info-fill);border-radius:6px;padding:0 4px;font-size:11px;font-weight:600;line-height:18px}.Mbwy4a_description{color:var(--dsw-alias-label-tertiary);font-size:14px;font-weight:400;line-height:24px}.Mbwy4a_customRow{border:1px solid #0000;border-radius:12px;flex-shrink:0;align-items:flex-start;gap:8px;width:100%;min-height:40px;padding:8px 12px 8px 8px;transition:background-color .12s,border-color .12s;display:flex}.Mbwy4a_customRow:hover,.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{background:var(--dsw-alias-interactive-bg-hover)}.Mbwy4a_customRow:focus-within,.Mbwy4a_customRowActive{border-color:var(--dsw-alias-border-l2)}.Mbwy4a_customInput{min-width:0;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);font:inherit;background:0 0;border:none;outline:none;flex:1;padding:0;font-size:14px;line-height:24px}.Mbwy4a_customInput::placeholder{color:var(--dsw-alias-label-caption)}.Mbwy4a_customTextarea{resize:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-module-platform);min-height:64px;max-height:140px;color:var(--dsw-alias-label-primary);caret-color:var(--dsw-alias-state-business-primary);font:inherit;border-radius:10px;outline:none;flex-shrink:0;margin:0 12px;padding:8px 12px;font-size:14px;line-height:24px;display:block}.Mbwy4a_customTextarea:focus{border-color:var(--dsw-alias-state-business-primary)}.Mbwy4a_customTextarea::placeholder{color:var(--dsw-alias-label-caption)}.Mbwy4a_footer{flex-shrink:0;justify-content:space-between;align-items:center;gap:12px;margin-top:12px;padding:0 10px 0 18px;display:flex}.Mbwy4a_feedback{min-height:16px;color:var(--dsw-alias-state-error-primary);text-align:right;flex:1;font-size:11px;line-height:16px}@media (width<=720px){.Mbwy4a_card{border-radius:16px}.Mbwy4a_header{padding:10px 12px 0 18px}.Mbwy4a_options{padding:4px 8px}.Mbwy4a_title{font-size:15px;line-height:21px}.Mbwy4a_option,.Mbwy4a_customRow{padding:8px 6px}.Mbwy4a_footer{align-items:flex-end;padding:0 10px}.Mbwy4a_footerActions{flex-shrink:0}}@media (prefers-reduced-motion:reduce){.Mbwy4a_option,.Mbwy4a_customRow{transition:none}}";
|
|
234
|
+
const tagId = "@deepseek-ai/dsh-client-ui-user-questions/QuestionComposer.module.css";
|
|
235
|
+
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
236
|
+
const tag = document.createElement("style");
|
|
237
|
+
tag.dataset.plugin = "@deepseek-ai/dsh-client-ui-user-questions";
|
|
238
|
+
tag.dataset.pluginCss = tagId;
|
|
239
|
+
tag.textContent = css;
|
|
240
|
+
document.head.appendChild(tag);
|
|
241
|
+
}
|
|
242
|
+
var QuestionComposer_module_css_default = {
|
|
243
|
+
"body": "Mbwy4a_body",
|
|
244
|
+
"pager": "Mbwy4a_pager",
|
|
245
|
+
"optionLine": "Mbwy4a_optionLine",
|
|
246
|
+
"feedback": "Mbwy4a_feedback",
|
|
247
|
+
"customRowActive": "Mbwy4a_customRowActive",
|
|
248
|
+
"footer": "Mbwy4a_footer",
|
|
249
|
+
"customRow": "Mbwy4a_customRow",
|
|
250
|
+
"header": "Mbwy4a_header",
|
|
251
|
+
"progress": "Mbwy4a_progress",
|
|
252
|
+
"description": "Mbwy4a_description",
|
|
253
|
+
"customTextarea": "Mbwy4a_customTextarea",
|
|
254
|
+
"footerActions": "Mbwy4a_footerActions",
|
|
255
|
+
"eyebrow": "Mbwy4a_eyebrow",
|
|
256
|
+
"card": "Mbwy4a_card",
|
|
257
|
+
"headingBlock": "Mbwy4a_headingBlock",
|
|
258
|
+
"title": "Mbwy4a_title",
|
|
259
|
+
"frame": "Mbwy4a_frame",
|
|
260
|
+
"iconButton": "Mbwy4a_iconButton",
|
|
261
|
+
"optionLabel": "Mbwy4a_optionLabel",
|
|
262
|
+
"number": "Mbwy4a_number",
|
|
263
|
+
"optionCopy": "Mbwy4a_optionCopy",
|
|
264
|
+
"customInput": "Mbwy4a_customInput",
|
|
265
|
+
"options": "Mbwy4a_options",
|
|
266
|
+
"badge": "Mbwy4a_badge",
|
|
267
|
+
"checkbox": "Mbwy4a_checkbox",
|
|
268
|
+
"optionSelected": "Mbwy4a_optionSelected",
|
|
269
|
+
"detail": "Mbwy4a_detail",
|
|
270
|
+
"option": "Mbwy4a_option",
|
|
271
|
+
"checkboxChecked": "Mbwy4a_checkboxChecked"
|
|
272
|
+
};
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region lib/types/client/QuestionComposer.js
|
|
275
|
+
/**
|
|
276
|
+
* Split the conventional recommendation suffix without changing the answer value.
|
|
277
|
+
* @param label - Original option label returned if selected.
|
|
278
|
+
* @returns Display label plus recommendation state.
|
|
279
|
+
*/
|
|
280
|
+
function parseRecommendedLabel(label) {
|
|
281
|
+
const suffix = /\s*(?:\((?:recommended|推荐)\)|((?:recommended|推荐)))\s*$/i;
|
|
282
|
+
return suffix.test(label) ? {
|
|
283
|
+
label: label.replace(suffix, ""),
|
|
284
|
+
recommended: true
|
|
285
|
+
} : {
|
|
286
|
+
label,
|
|
287
|
+
recommended: false
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/** Return whether a text-field key event belongs to an active IME composition. */
|
|
291
|
+
function isComposing(event) {
|
|
292
|
+
return event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Composer takeover boundary; the carrier key keys local drafts, so a
|
|
296
|
+
* same-request replay (same key, new carrier object) preserves them.
|
|
297
|
+
*
|
|
298
|
+
* One takeover, two shapes: a request that declares a presentation intent this
|
|
299
|
+
* package renders takes that shape (a plan review is one decision over one
|
|
300
|
+
* plan, not a question set), and every other request takes the generic flow.
|
|
301
|
+
* The routing lives here, at the one entry that owns the composer seat, so
|
|
302
|
+
* neither shape can claim a request the other is already rendering.
|
|
303
|
+
*
|
|
304
|
+
* @param props - the selector-matched pending question carrier plus the framework standard kit.
|
|
305
|
+
* @returns The question flow, or the intent's own surface, for this request.
|
|
306
|
+
*/
|
|
307
|
+
function QuestionComposer(props) {
|
|
308
|
+
const question = (0, react.useMemo)(() => new PendingQuestion(props.matched), [props.matched]);
|
|
309
|
+
const review = (0, react.useMemo)(() => planReviewOf(question.questions), [question]);
|
|
310
|
+
return review === void 0 ? (0, react_jsx_runtime.jsx)(QuestionFlow, {
|
|
311
|
+
pending: question,
|
|
312
|
+
t: props.t
|
|
313
|
+
}, question.key) : (0, react_jsx_runtime.jsx)(PlanReviewPanel, {
|
|
314
|
+
pending: question,
|
|
315
|
+
review,
|
|
316
|
+
t: props.t
|
|
317
|
+
}, question.key);
|
|
318
|
+
}
|
|
319
|
+
function QuestionFlow({ pending, t }) {
|
|
320
|
+
const questions = pending.questions;
|
|
321
|
+
const [index, setIndex] = (0, react.useState)(0);
|
|
322
|
+
const [drafts, setDrafts] = (0, react.useState)(() => questions.map(() => ({
|
|
323
|
+
selected: [],
|
|
324
|
+
custom: "",
|
|
325
|
+
skipped: false
|
|
326
|
+
})));
|
|
327
|
+
const [busy, setBusy] = (0, react.useState)(null);
|
|
328
|
+
const [error, setError] = (0, react.useState)(null);
|
|
329
|
+
const question = questions[index];
|
|
330
|
+
const draft = drafts[index];
|
|
331
|
+
const hasOptions = (question.options?.length ?? 0) > 0;
|
|
332
|
+
const cancelFlow = () => {
|
|
333
|
+
setBusy("cancel");
|
|
334
|
+
setError(null);
|
|
335
|
+
pending.cancel().catch((cause) => {
|
|
336
|
+
setBusy(null);
|
|
337
|
+
setError({ text: cause instanceof Error ? cause.message : String(cause) });
|
|
338
|
+
});
|
|
339
|
+
};
|
|
340
|
+
const updateDraft = (update) => {
|
|
341
|
+
setDrafts((current) => current.map((item, itemIndex) => itemIndex === index ? update(item) : item));
|
|
342
|
+
setError(null);
|
|
343
|
+
};
|
|
344
|
+
const choose = (label) => {
|
|
345
|
+
updateDraft((current) => {
|
|
346
|
+
if (question.multiSelect === true) {
|
|
347
|
+
const selected = current.selected.includes(label) ? current.selected.filter((item) => item !== label) : [...current.selected, label];
|
|
348
|
+
return {
|
|
349
|
+
...current,
|
|
350
|
+
selected,
|
|
351
|
+
skipped: false
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
selected: [label],
|
|
356
|
+
custom: "",
|
|
357
|
+
skipped: false
|
|
358
|
+
};
|
|
359
|
+
});
|
|
360
|
+
if (question.multiSelect !== true && index < questions.length - 1) setIndex((current) => current + 1);
|
|
361
|
+
};
|
|
362
|
+
const answered = (item) => item.selected.length > 0 || item.custom.trim() !== "";
|
|
363
|
+
const completed = (item) => answered(item) || item.skipped;
|
|
364
|
+
const submitDrafts = (values) => {
|
|
365
|
+
const missing = values.findIndex((item) => !completed(item));
|
|
366
|
+
if (missing >= 0) {
|
|
367
|
+
setIndex(missing);
|
|
368
|
+
setError({ key: "error.incomplete" });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const answer = { answers: questions.map((item, itemIndex) => {
|
|
372
|
+
const value = values[itemIndex];
|
|
373
|
+
if (value.skipped) return {
|
|
374
|
+
id: item.id,
|
|
375
|
+
selected: []
|
|
376
|
+
};
|
|
377
|
+
const custom = value.custom.trim();
|
|
378
|
+
return {
|
|
379
|
+
id: item.id,
|
|
380
|
+
selected: custom === "" || item.multiSelect === true ? value.selected : [],
|
|
381
|
+
...custom === "" ? {} : { custom }
|
|
382
|
+
};
|
|
383
|
+
}) };
|
|
384
|
+
setBusy("answer");
|
|
385
|
+
setError(null);
|
|
386
|
+
pending.answer(answer).catch((cause) => {
|
|
387
|
+
setBusy(null);
|
|
388
|
+
setError({ text: cause instanceof Error ? cause.message : String(cause) });
|
|
389
|
+
});
|
|
390
|
+
};
|
|
391
|
+
const continueFlow = () => {
|
|
392
|
+
if (!answered(draft)) {
|
|
393
|
+
setError({ key: "error.unanswered" });
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (index < questions.length - 1) {
|
|
397
|
+
setIndex((current) => current + 1);
|
|
398
|
+
setError(null);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
submitDrafts(drafts);
|
|
402
|
+
};
|
|
403
|
+
const draftCustom = (event) => {
|
|
404
|
+
const value = event.target.value;
|
|
405
|
+
updateDraft((current) => ({
|
|
406
|
+
...current,
|
|
407
|
+
selected: question.multiSelect === true ? current.selected : [],
|
|
408
|
+
custom: value,
|
|
409
|
+
skipped: false
|
|
410
|
+
}));
|
|
411
|
+
};
|
|
412
|
+
const continueFromCustom = (event) => {
|
|
413
|
+
if (event.key !== "Enter" || event.shiftKey || isComposing(event)) return;
|
|
414
|
+
event.preventDefault();
|
|
415
|
+
continueFlow();
|
|
416
|
+
};
|
|
417
|
+
const skipQuestion = () => {
|
|
418
|
+
const nextDrafts = drafts.map((item, itemIndex) => itemIndex === index ? {
|
|
419
|
+
selected: [],
|
|
420
|
+
custom: "",
|
|
421
|
+
skipped: true
|
|
422
|
+
} : item);
|
|
423
|
+
setDrafts(nextDrafts);
|
|
424
|
+
setError(null);
|
|
425
|
+
if (index < questions.length - 1) {
|
|
426
|
+
setIndex((current) => current + 1);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
submitDrafts(nextDrafts);
|
|
430
|
+
};
|
|
431
|
+
return (0, react_jsx_runtime.jsx)("div", {
|
|
432
|
+
className: QuestionComposer_module_css_default.frame,
|
|
433
|
+
"data-question-key": pending.key,
|
|
434
|
+
children: (0, react_jsx_runtime.jsxs)("section", {
|
|
435
|
+
className: QuestionComposer_module_css_default.card,
|
|
436
|
+
"aria-labelledby": `question-${pending.key}-${String(index)}`,
|
|
437
|
+
children: [
|
|
438
|
+
(0, react_jsx_runtime.jsxs)("header", {
|
|
439
|
+
className: QuestionComposer_module_css_default.header,
|
|
440
|
+
children: [(0, react_jsx_runtime.jsxs)("div", {
|
|
441
|
+
className: QuestionComposer_module_css_default.headingBlock,
|
|
442
|
+
children: [question.header !== void 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
443
|
+
className: QuestionComposer_module_css_default.eyebrow,
|
|
444
|
+
children: question.header
|
|
445
|
+
}), (0, react_jsx_runtime.jsx)("h2", {
|
|
446
|
+
className: QuestionComposer_module_css_default.title,
|
|
447
|
+
id: `question-${pending.key}-${String(index)}`,
|
|
448
|
+
children: question.question
|
|
449
|
+
})]
|
|
450
|
+
}), (0, react_jsx_runtime.jsx)("button", {
|
|
451
|
+
type: "button",
|
|
452
|
+
className: QuestionComposer_module_css_default.iconButton,
|
|
453
|
+
"aria-label": t("nav.cancel"),
|
|
454
|
+
title: t("nav.cancel"),
|
|
455
|
+
disabled: busy !== null,
|
|
456
|
+
onClick: cancelFlow,
|
|
457
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCloseOutline16, {})
|
|
458
|
+
})]
|
|
459
|
+
}),
|
|
460
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
461
|
+
className: QuestionComposer_module_css_default.body,
|
|
462
|
+
"data-question-scroll": true,
|
|
463
|
+
children: [question.detail !== void 0 && (0, react_jsx_runtime.jsx)("div", {
|
|
464
|
+
className: QuestionComposer_module_css_default.detail,
|
|
465
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.MarkdownText, { text: question.detail })
|
|
466
|
+
}), (0, react_jsx_runtime.jsxs)("div", {
|
|
467
|
+
className: QuestionComposer_module_css_default.options,
|
|
468
|
+
role: question.multiSelect === true ? "group" : "radiogroup",
|
|
469
|
+
children: [(question.options ?? []).map((option, optionIndex) => {
|
|
470
|
+
const selected = draft.selected.includes(option.label);
|
|
471
|
+
const display = parseRecommendedLabel(option.label);
|
|
472
|
+
return (0, react_jsx_runtime.jsxs)("button", {
|
|
473
|
+
type: "button",
|
|
474
|
+
className: clsx(QuestionComposer_module_css_default.option, selected && question.multiSelect !== true && QuestionComposer_module_css_default.optionSelected),
|
|
475
|
+
role: question.multiSelect === true ? "checkbox" : "radio",
|
|
476
|
+
"aria-checked": selected,
|
|
477
|
+
"aria-label": display.label,
|
|
478
|
+
disabled: busy !== null,
|
|
479
|
+
onClick: () => {
|
|
480
|
+
choose(option.label);
|
|
481
|
+
},
|
|
482
|
+
onKeyDown: (event) => {
|
|
483
|
+
if (event.key !== "Enter" || !drafts.every(completed)) return;
|
|
484
|
+
event.preventDefault();
|
|
485
|
+
submitDrafts(drafts);
|
|
486
|
+
},
|
|
487
|
+
children: [question.multiSelect === true ? (0, react_jsx_runtime.jsx)("span", {
|
|
488
|
+
className: clsx(QuestionComposer_module_css_default.checkbox, selected && QuestionComposer_module_css_default.checkboxChecked),
|
|
489
|
+
"aria-hidden": "true",
|
|
490
|
+
children: selected && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14, { size: 12 })
|
|
491
|
+
}) : (0, react_jsx_runtime.jsx)("span", {
|
|
492
|
+
className: QuestionComposer_module_css_default.number,
|
|
493
|
+
children: optionIndex + 1
|
|
494
|
+
}), (0, react_jsx_runtime.jsx)("span", {
|
|
495
|
+
className: QuestionComposer_module_css_default.optionCopy,
|
|
496
|
+
children: (0, react_jsx_runtime.jsxs)("span", {
|
|
497
|
+
className: QuestionComposer_module_css_default.optionLine,
|
|
498
|
+
children: [
|
|
499
|
+
(0, react_jsx_runtime.jsx)("span", {
|
|
500
|
+
className: QuestionComposer_module_css_default.optionLabel,
|
|
501
|
+
children: display.label
|
|
502
|
+
}),
|
|
503
|
+
display.recommended && (0, react_jsx_runtime.jsx)("span", {
|
|
504
|
+
className: QuestionComposer_module_css_default.badge,
|
|
505
|
+
children: t("option.recommended")
|
|
506
|
+
}),
|
|
507
|
+
option.description !== void 0 && (0, react_jsx_runtime.jsx)("span", {
|
|
508
|
+
className: QuestionComposer_module_css_default.description,
|
|
509
|
+
children: option.description
|
|
510
|
+
})
|
|
511
|
+
]
|
|
512
|
+
})
|
|
513
|
+
})]
|
|
514
|
+
}, `${option.label}-${String(optionIndex)}`);
|
|
515
|
+
}), hasOptions ? (0, react_jsx_runtime.jsxs)("div", {
|
|
516
|
+
className: clsx(QuestionComposer_module_css_default.customRow, draft.custom !== "" && QuestionComposer_module_css_default.customRowActive),
|
|
517
|
+
children: [question.multiSelect === true ? (0, react_jsx_runtime.jsx)("span", {
|
|
518
|
+
className: clsx(QuestionComposer_module_css_default.checkbox, draft.custom !== "" && QuestionComposer_module_css_default.checkboxChecked),
|
|
519
|
+
"aria-hidden": "true",
|
|
520
|
+
children: draft.custom !== "" && (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconCheckOutline14, { size: 12 })
|
|
521
|
+
}) : (0, react_jsx_runtime.jsx)("span", {
|
|
522
|
+
className: QuestionComposer_module_css_default.number,
|
|
523
|
+
"aria-hidden": "true",
|
|
524
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconEditOutline16, { size: 12 })
|
|
525
|
+
}), (0, react_jsx_runtime.jsx)("input", {
|
|
526
|
+
type: "text",
|
|
527
|
+
className: QuestionComposer_module_css_default.customInput,
|
|
528
|
+
value: draft.custom,
|
|
529
|
+
disabled: busy !== null,
|
|
530
|
+
placeholder: t("custom.placeholder"),
|
|
531
|
+
onChange: draftCustom,
|
|
532
|
+
onKeyDown: continueFromCustom
|
|
533
|
+
})]
|
|
534
|
+
}) : (0, react_jsx_runtime.jsx)("textarea", {
|
|
535
|
+
autoFocus: true,
|
|
536
|
+
className: QuestionComposer_module_css_default.customTextarea,
|
|
537
|
+
value: draft.custom,
|
|
538
|
+
disabled: busy !== null,
|
|
539
|
+
rows: 2,
|
|
540
|
+
placeholder: t("custom.placeholder"),
|
|
541
|
+
onChange: draftCustom,
|
|
542
|
+
onKeyDown: continueFromCustom
|
|
543
|
+
})]
|
|
544
|
+
})]
|
|
545
|
+
}),
|
|
546
|
+
(0, react_jsx_runtime.jsxs)("footer", {
|
|
547
|
+
className: QuestionComposer_module_css_default.footer,
|
|
548
|
+
children: [
|
|
549
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
550
|
+
className: QuestionComposer_module_css_default.pager,
|
|
551
|
+
children: [
|
|
552
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
553
|
+
type: "button",
|
|
554
|
+
className: QuestionComposer_module_css_default.iconButton,
|
|
555
|
+
"aria-label": t("nav.prev"),
|
|
556
|
+
disabled: index === 0 || busy !== null,
|
|
557
|
+
onClick: () => {
|
|
558
|
+
setIndex(index - 1);
|
|
559
|
+
setError(null);
|
|
560
|
+
},
|
|
561
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronLeftOutline14, {})
|
|
562
|
+
}),
|
|
563
|
+
(0, react_jsx_runtime.jsxs)("span", {
|
|
564
|
+
className: QuestionComposer_module_css_default.progress,
|
|
565
|
+
children: [
|
|
566
|
+
index + 1,
|
|
567
|
+
" / ",
|
|
568
|
+
questions.length
|
|
569
|
+
]
|
|
570
|
+
}),
|
|
571
|
+
(0, react_jsx_runtime.jsx)("button", {
|
|
572
|
+
type: "button",
|
|
573
|
+
className: QuestionComposer_module_css_default.iconButton,
|
|
574
|
+
"aria-label": t("nav.next"),
|
|
575
|
+
disabled: index === questions.length - 1 || busy !== null,
|
|
576
|
+
onClick: () => {
|
|
577
|
+
setIndex(index + 1);
|
|
578
|
+
setError(null);
|
|
579
|
+
},
|
|
580
|
+
children: (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.IconChevronRightOutline14, {})
|
|
581
|
+
})
|
|
582
|
+
]
|
|
583
|
+
}),
|
|
584
|
+
(0, react_jsx_runtime.jsx)("div", {
|
|
585
|
+
className: QuestionComposer_module_css_default.feedback,
|
|
586
|
+
role: "status",
|
|
587
|
+
children: error === null ? null : "key" in error ? t(error.key) : error.text
|
|
588
|
+
}),
|
|
589
|
+
(0, react_jsx_runtime.jsxs)("div", {
|
|
590
|
+
className: QuestionComposer_module_css_default.footerActions,
|
|
591
|
+
children: [(0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
592
|
+
variant: "outline",
|
|
593
|
+
disabled: busy !== null,
|
|
594
|
+
onClick: skipQuestion,
|
|
595
|
+
children: t("action.skip")
|
|
596
|
+
}), (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
|
|
597
|
+
variant: "primary",
|
|
598
|
+
disabled: busy !== null || !answered(draft),
|
|
599
|
+
onClick: continueFlow,
|
|
600
|
+
children: busy === "answer" ? t("submitting") : index === questions.length - 1 ? t("submit") : t("action.next")
|
|
601
|
+
})]
|
|
602
|
+
})
|
|
603
|
+
]
|
|
604
|
+
})
|
|
605
|
+
]
|
|
606
|
+
})
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
//#endregion
|
|
610
|
+
//#region lib/types/client/locales.js
|
|
611
|
+
/** `question` namespace dictionaries. */
|
|
612
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
613
|
+
const zh = {
|
|
614
|
+
"error.incomplete": "请先完成这道问题。",
|
|
615
|
+
"error.unanswered": "请选择一个选项或填写自定义答案。",
|
|
616
|
+
"nav.prev": "上一题",
|
|
617
|
+
"nav.next": "下一题",
|
|
618
|
+
"nav.cancel": "放弃整组问题",
|
|
619
|
+
"option.recommended": "推荐",
|
|
620
|
+
"custom.placeholder": "输入你的答案",
|
|
621
|
+
"action.skip": "跳过本题",
|
|
622
|
+
"action.next": "下一题",
|
|
623
|
+
"plan.header": "计划待审",
|
|
624
|
+
"plan.approve": "确认执行",
|
|
625
|
+
"plan.decline": "拒绝",
|
|
626
|
+
"plan.discuss": "去聊天里说"
|
|
627
|
+
};
|
|
628
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
629
|
+
const en = {
|
|
630
|
+
"error.incomplete": "Please complete this question first.",
|
|
631
|
+
"error.unanswered": "Please select an option or enter a custom answer.",
|
|
632
|
+
"nav.prev": "Previous question",
|
|
633
|
+
"nav.next": "Next question",
|
|
634
|
+
"nav.cancel": "Dismiss all questions",
|
|
635
|
+
"option.recommended": "Recommended",
|
|
636
|
+
"custom.placeholder": "Type your answer",
|
|
637
|
+
"action.skip": "Skip this question",
|
|
638
|
+
"action.next": "Next",
|
|
639
|
+
"plan.header": "Plan review",
|
|
640
|
+
"plan.approve": "Approve",
|
|
641
|
+
"plan.decline": "Refuse",
|
|
642
|
+
"plan.discuss": "Chat about it"
|
|
643
|
+
};
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region lib/types/client/index.js
|
|
646
|
+
/** Dictionary namespace owned by this plugin. */
|
|
647
|
+
const NS = "question";
|
|
648
|
+
/** Required services: the slot registry and the question composer's copy. */
|
|
649
|
+
const inject = ["slots", "locale"];
|
|
650
|
+
/** Chain routing: claim the composer while a question wait is pending (pure — owner props only). */
|
|
651
|
+
function selectQuestion({ interactions }) {
|
|
652
|
+
return interactions.find((i) => i.kind === "question") ?? null;
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Client plugin body: register the `question` dictionaries and the question
|
|
656
|
+
* composer into the composer chain. Zero business face — data and verbs live
|
|
657
|
+
* on the matched carrier; t rides the standard locale seat.
|
|
658
|
+
* @param ctx - client root context.
|
|
659
|
+
*/
|
|
660
|
+
function apply(ctx) {
|
|
661
|
+
ctx.effect(() => ctx.locale.register(NS, {
|
|
662
|
+
zh,
|
|
663
|
+
en
|
|
664
|
+
}), "ui-user-questions: dictionaries");
|
|
665
|
+
ctx.slots.inject("conversation.composer", () => ctx.slots.register({
|
|
666
|
+
name: "conversation.composer",
|
|
667
|
+
select: selectQuestion,
|
|
668
|
+
locale: NS
|
|
669
|
+
}, QuestionComposer));
|
|
670
|
+
}
|
|
671
|
+
//#endregion
|
|
672
|
+
exports.PendingQuestion = PendingQuestion;
|
|
673
|
+
exports.apply = apply;
|
|
674
|
+
exports.inject = inject;
|
|
675
|
+
return module.exports;
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
//# sourceMappingURL=client.js.map
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region lib/types/index.js
|
|
2
|
+
/**
|
|
3
|
+
* Web question plugin, node half.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately empty. Mounting `ask_user_question` here put it in the tools
|
|
6
|
+
* registry's GLOBAL layer, so every agent saw it no matter which preset
|
|
7
|
+
* composed it — a two-tool benchmark preset actually presented three, and a
|
|
8
|
+
* locally authored `bash-only` preset presented two. Rendering a question is
|
|
9
|
+
* a host UI capability; having the tool is an agent capability, and only a
|
|
10
|
+
* preset decides that. The `tool-ask-user` row belongs in the presets that
|
|
11
|
+
* want it (and in the TUI composition, which has no presets).
|
|
12
|
+
*/
|
|
13
|
+
/** Host plugin body — the model-facing tool is composed per preset, not here. */
|
|
14
|
+
function apply() {}
|
|
15
|
+
//#endregion
|
|
16
|
+
export { apply };
|
package/lib/invariant.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region lib/types/invariant.js
|
|
2
|
+
/**
|
|
3
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-user-questions`.
|
|
4
|
+
* @module @deepseek-ai/dsh-client-ui-user-questions/invariant
|
|
5
|
+
*/
|
|
6
|
+
const PACKAGE_NAME = "@deepseek-ai/dsh-client-ui-user-questions";
|
|
7
|
+
/** Cordis companion plugin name. */
|
|
8
|
+
const name = "client-ui-user-questions-invariant";
|
|
9
|
+
/** Service required before the companion can reserve package ownership. */
|
|
10
|
+
const inject = ["invariants"];
|
|
11
|
+
/**
|
|
12
|
+
* No runtime invariant: tool and slot registrations are effects
|
|
13
|
+
* owned and observed by their respective registries; the host pending table is
|
|
14
|
+
* exercised through the public wire protocol.
|
|
15
|
+
*/
|
|
16
|
+
const install = () => {};
|
|
17
|
+
/**
|
|
18
|
+
* Register this package's invariant companion.
|
|
19
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
20
|
+
* @returns The installed registration's disposer after setup succeeds.
|
|
21
|
+
*/
|
|
22
|
+
const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
|
|
23
|
+
//#endregion
|
|
24
|
+
export { apply, inject, name };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { PendingQuestion, PlanReview, QuestionComposerProps } from './contract/slots.ts';
|
|
2
|
+
/** The panel's own props: the question domain face, the narrowed review, and the locale seat. */
|
|
3
|
+
export type PlanReviewPanelProps = {
|
|
4
|
+
pending: PendingQuestion;
|
|
5
|
+
review: PlanReview;
|
|
6
|
+
} & Pick<QuestionComposerProps, 't'>;
|
|
7
|
+
/**
|
|
8
|
+
* Render a plan review as a decision card.
|
|
9
|
+
*
|
|
10
|
+
* @param props - the question domain face, the narrowed plan review, and `t`.
|
|
11
|
+
* @returns The plan-review takeover for this request.
|
|
12
|
+
*/
|
|
13
|
+
export declare function PlanReviewPanel({ pending, review, t }: PlanReviewPanelProps): import("react").JSX.Element;
|
|
14
|
+
//# sourceMappingURL=PlanReviewPanel.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type QuestionComposerProps } from './contract/slots.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Split the conventional recommendation suffix without changing the answer value.
|
|
4
|
+
* @param label - Original option label returned if selected.
|
|
5
|
+
* @returns Display label plus recommendation state.
|
|
6
|
+
*/
|
|
7
|
+
export declare function parseRecommendedLabel(label: string): {
|
|
8
|
+
label: string;
|
|
9
|
+
recommended: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Composer takeover boundary; the carrier key keys local drafts, so a
|
|
13
|
+
* same-request replay (same key, new carrier object) preserves them.
|
|
14
|
+
*
|
|
15
|
+
* One takeover, two shapes: a request that declares a presentation intent this
|
|
16
|
+
* package renders takes that shape (a plan review is one decision over one
|
|
17
|
+
* plan, not a question set), and every other request takes the generic flow.
|
|
18
|
+
* The routing lives here, at the one entry that owns the composer seat, so
|
|
19
|
+
* neither shape can claim a request the other is already rendering.
|
|
20
|
+
*
|
|
21
|
+
* @param props - the selector-matched pending question carrier plus the framework standard kit.
|
|
22
|
+
* @returns The question flow, or the intent's own surface, for this request.
|
|
23
|
+
*/
|
|
24
|
+
export declare function QuestionComposer(props: QuestionComposerProps): import("react").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=QuestionComposer.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Question-composer slot contract: the registrant-side props composition for
|
|
3
|
+
* the conversation-owned `conversation.composer` slot, plus the question
|
|
4
|
+
* domain face over the runtime's carrier object. The carrier (PendingWait)
|
|
5
|
+
* owns envelope transport only; the question protocol — answer value shape,
|
|
6
|
+
* cancelled error encoding, receipt checks — lives HERE, with the package
|
|
7
|
+
* that consumes it.
|
|
8
|
+
*/
|
|
9
|
+
import type { PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
10
|
+
import type { PendingWait } from '@deepseek-ai/dsh-client-runtime/client';
|
|
11
|
+
import type { QuestionResponsePayload } from '@deepseek-ai/dsh-api-remotes/client';
|
|
12
|
+
/** The pending question carrier the owner dispatches into the composer slot. */
|
|
13
|
+
export type QuestionWait = PendingWait<'question'>;
|
|
14
|
+
/** One structured answer batch covering every question of the request. */
|
|
15
|
+
export type QuestionAnswer = QuestionResponsePayload['answer'];
|
|
16
|
+
/** One question of the request, as the carrier payload carries it. */
|
|
17
|
+
type QuestionItem = QuestionWait['payload']['questions'][number];
|
|
18
|
+
/** One option the asker offered on a question. */
|
|
19
|
+
type QuestionOption = NonNullable<QuestionItem['options']>[number];
|
|
20
|
+
/**
|
|
21
|
+
* A request narrowed to the `plan-review` presentation intent: everything the
|
|
22
|
+
* decision card renders and answers with, so the panel never re-reads the
|
|
23
|
+
* request shape. `approve` and `decline` are the asker's own options — an
|
|
24
|
+
* answer must carry one of those labels verbatim — and `plan` is the markdown
|
|
25
|
+
* body under review.
|
|
26
|
+
*/
|
|
27
|
+
export interface PlanReview {
|
|
28
|
+
/** The reviewed question's id, echoed in the answer. */
|
|
29
|
+
id: string;
|
|
30
|
+
/** The question text, kept as the card's accessible name. */
|
|
31
|
+
question: string;
|
|
32
|
+
/** The plan markdown under review. */
|
|
33
|
+
plan: string;
|
|
34
|
+
/** The option that approves the plan. */
|
|
35
|
+
approve: QuestionOption;
|
|
36
|
+
/** The option that declines it; absent when the asker offered no other option. */
|
|
37
|
+
decline?: QuestionOption;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Narrow a request to a renderable plan review, or return undefined to leave it
|
|
41
|
+
* to the generic question flow.
|
|
42
|
+
*
|
|
43
|
+
* The card is one decision over one plan, and it claims a request only when it
|
|
44
|
+
* can send every answer that request allows — an intent changes the layout,
|
|
45
|
+
* never which answers are reachable. So the batch must be a single question
|
|
46
|
+
* that declares the intent, carries the plan as its detail, offers the approve
|
|
47
|
+
* label the intent names, and is a binary single choice: at most one option
|
|
48
|
+
* besides approve, and not multi-select. A third option or a multi-select batch
|
|
49
|
+
* has answers two buttons cannot express, so the generic flow keeps it — as it
|
|
50
|
+
* keeps any request whose intent the asker's own service would have rejected,
|
|
51
|
+
* because the client sits downstream of a wire boundary and every request must
|
|
52
|
+
* stay answerable.
|
|
53
|
+
*
|
|
54
|
+
* @param questions - the request's whole question batch.
|
|
55
|
+
* @returns The narrowed review, or undefined when the generic flow owns it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function planReviewOf(questions: readonly QuestionItem[]): PlanReview | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* Question domain face over the carrier: render identity and questions
|
|
60
|
+
* transparently forwarded; answer/cancel own the wire encoding (the success
|
|
61
|
+
* fields and the cancelled error) and turn a rejected carrier receipt into a
|
|
62
|
+
* thrown error. Components mint one per carrier via useMemo (never inside a
|
|
63
|
+
* select — a per-dispatch mint would churn identity and break memoization).
|
|
64
|
+
*/
|
|
65
|
+
export declare class PendingQuestion {
|
|
66
|
+
private readonly wait;
|
|
67
|
+
/**
|
|
68
|
+
* @param wait - the runtime carrier for one pending question request.
|
|
69
|
+
*/
|
|
70
|
+
constructor(wait: QuestionWait);
|
|
71
|
+
/** Opaque render identity (React key / draft remount axis), forwarded from the carrier. */
|
|
72
|
+
get key(): string;
|
|
73
|
+
/** The request's question list, forwarded from the carrier payload. */
|
|
74
|
+
get questions(): QuestionWait['payload']['questions'];
|
|
75
|
+
/**
|
|
76
|
+
* Deliver the whole answer batch; a rejected carrier receipt throws.
|
|
77
|
+
* @param answer - complete structured answer batch.
|
|
78
|
+
*/
|
|
79
|
+
answer(answer: QuestionAnswer): Promise<void>;
|
|
80
|
+
/** Reject the whole wait (the host resolves the tool call as cancelled); a rejected receipt throws. */
|
|
81
|
+
cancel(): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Full component props: the framework runtime share (chain currency +
|
|
85
|
+
* session/global standard kit) plus the chain `matched` share — the entry's
|
|
86
|
+
* selector result, already narrowed to the question carrier — plus the
|
|
87
|
+
* standard locale seat; the carrier plus the domain face above carry the
|
|
88
|
+
* whole behavior surface.
|
|
89
|
+
*/
|
|
90
|
+
export type QuestionComposerProps = PropsRuntime<'conversation.composer'> & {
|
|
91
|
+
matched: QuestionWait;
|
|
92
|
+
} & PropsLocale<'question'>;
|
|
93
|
+
export {};
|
|
94
|
+
//# sourceMappingURL=slots.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web question plugin, browser half: QuestionComposer registered as a
|
|
3
|
+
* selector-routed entry of the conversation-declared composer chain, plus the
|
|
4
|
+
* `question` dictionaries. The selector narrows the owner's currency to the
|
|
5
|
+
* question carrier (matched prop), and the whole behavior surface rides the
|
|
6
|
+
* carrier (domain encoding in contract/slots.ts PendingQuestion); copy rides
|
|
7
|
+
* the standard locale seat. Export discipline: packages/client/AGENTS.md.
|
|
8
|
+
*
|
|
9
|
+
* One entry, two shapes: the composer renders a request that declares a
|
|
10
|
+
* presentation intent as that intent's own surface (`plan-review` → the plan
|
|
11
|
+
* decision card) and every other request as the generic question flow. A
|
|
12
|
+
* separate chain entry per shape would race the same carrier, so the shape
|
|
13
|
+
* choice lives inside this entry — see QuestionComposer.
|
|
14
|
+
*/
|
|
15
|
+
import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
|
|
16
|
+
import { type QuestionKey } from './locales.ts';
|
|
17
|
+
export { PendingQuestion } from './contract/slots.ts';
|
|
18
|
+
export type { PlanReview, QuestionAnswer, QuestionComposerProps, QuestionWait, } from './contract/slots.ts';
|
|
19
|
+
export type { QuestionKey } from './locales.ts';
|
|
20
|
+
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
21
|
+
interface LocaleNamespaceMap {
|
|
22
|
+
/** The question composer's copy. */
|
|
23
|
+
question: QuestionKey;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Required services: the slot registry and the question composer's copy. */
|
|
27
|
+
export declare const inject: string[];
|
|
28
|
+
/**
|
|
29
|
+
* Client plugin body: register the `question` dictionaries and the question
|
|
30
|
+
* composer into the composer chain. Zero business face — data and verbs live
|
|
31
|
+
* on the matched carrier; t rides the standard locale seat.
|
|
32
|
+
* @param ctx - client root context.
|
|
33
|
+
*/
|
|
34
|
+
export declare function apply(ctx: ClientContext): void;
|
|
35
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** `question` namespace dictionaries. */
|
|
2
|
+
/** Simplified Chinese dictionary (the key-set source of truth). */
|
|
3
|
+
export declare const zh: {
|
|
4
|
+
'error.incomplete': string;
|
|
5
|
+
'error.unanswered': string;
|
|
6
|
+
'nav.prev': string;
|
|
7
|
+
'nav.next': string;
|
|
8
|
+
'nav.cancel': string;
|
|
9
|
+
'option.recommended': string;
|
|
10
|
+
'custom.placeholder': string;
|
|
11
|
+
'action.skip': string;
|
|
12
|
+
'action.next': string;
|
|
13
|
+
'plan.header': string;
|
|
14
|
+
'plan.approve': string;
|
|
15
|
+
'plan.decline': string;
|
|
16
|
+
'plan.discuss': string;
|
|
17
|
+
};
|
|
18
|
+
/** The question namespace key union. */
|
|
19
|
+
export type QuestionKey = keyof typeof zh;
|
|
20
|
+
/** English dictionary, checked complete against the zh key set. */
|
|
21
|
+
export declare const en: {
|
|
22
|
+
'error.incomplete': string;
|
|
23
|
+
'error.unanswered': string;
|
|
24
|
+
'nav.prev': string;
|
|
25
|
+
'nav.next': string;
|
|
26
|
+
'nav.cancel': string;
|
|
27
|
+
'option.recommended': string;
|
|
28
|
+
'custom.placeholder': string;
|
|
29
|
+
'action.skip': string;
|
|
30
|
+
'action.next': string;
|
|
31
|
+
'plan.header': string;
|
|
32
|
+
'plan.approve': string;
|
|
33
|
+
'plan.decline': string;
|
|
34
|
+
'plan.discuss': string;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=locales.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web question plugin, node half.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately empty. Mounting `ask_user_question` here put it in the tools
|
|
5
|
+
* registry's GLOBAL layer, so every agent saw it no matter which preset
|
|
6
|
+
* composed it — a two-tool benchmark preset actually presented three, and a
|
|
7
|
+
* locally authored `bash-only` preset presented two. Rendering a question is
|
|
8
|
+
* a host UI capability; having the tool is an agent capability, and only a
|
|
9
|
+
* preset decides that. The `tool-ask-user` row belongs in the presets that
|
|
10
|
+
* want it (and in the TUI composition, which has no presets).
|
|
11
|
+
*/
|
|
12
|
+
/** Host plugin body — the model-facing tool is composed per preset, not here. */
|
|
13
|
+
export declare function apply(): void;
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Package-owned invariant companion for `@deepseek-ai/dsh-client-ui-user-questions`.
|
|
3
|
+
* @module @deepseek-ai/dsh-client-ui-user-questions/invariant
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
/** Cordis companion plugin name. */
|
|
7
|
+
export declare const name = "client-ui-user-questions-invariant";
|
|
8
|
+
/** Service required before the companion can reserve package ownership. */
|
|
9
|
+
export declare const inject: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Register this package's invariant companion.
|
|
12
|
+
* @param ctx - Cordis context carrying the invariant service.
|
|
13
|
+
* @returns The installed registration's disposer after setup succeeds.
|
|
14
|
+
*/
|
|
15
|
+
export declare const apply: (ctx: Context) => Promise<() => void>;
|
|
16
|
+
//# sourceMappingURL=invariant.d.ts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@deepseek-ai/dsh-client-ui-user-questions",
|
|
3
|
+
"description": "Web ask_user_question feature: host tool mount plus composer-takeover question UI",
|
|
4
|
+
"version": "0.0.1-rc.3",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "restricted"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/deepseek-ai/deepseek-harness.git",
|
|
11
|
+
"directory": "packages/client/ui-user-questions"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "lib/index.js",
|
|
15
|
+
"types": "lib/types/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./lib/types/index.d.ts",
|
|
19
|
+
"default": "./lib/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./invariant": {
|
|
22
|
+
"types": "./lib/types/invariant.d.ts",
|
|
23
|
+
"default": "./lib/invariant.js"
|
|
24
|
+
},
|
|
25
|
+
"./client": {
|
|
26
|
+
"types": "./lib/types/client/index.d.ts",
|
|
27
|
+
"default": "./lib/client.js"
|
|
28
|
+
},
|
|
29
|
+
"./src/*": "./src/*",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"dsh": {
|
|
33
|
+
"client": {
|
|
34
|
+
"inject": [
|
|
35
|
+
"@deepseek-ai/dsh-client-locale",
|
|
36
|
+
"@deepseek-ai/dsh-client-ui-conversation"
|
|
37
|
+
],
|
|
38
|
+
"platform": "web"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"license": "BSD-3-Clause",
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"clsx": "^2.0.0",
|
|
44
|
+
"react": "^18.2.0",
|
|
45
|
+
"@deepseek-ai/dsh-client-runtime": "^0.0.1-rc.3",
|
|
46
|
+
"@deepseek-ai/dsh-client-connection": "^0.0.1-rc.3",
|
|
47
|
+
"@deepseek-ai/dsh-client-ui-primitives": "^0.0.1-rc.3",
|
|
48
|
+
"@deepseek-ai/dsh-client-ui-conversation": "^0.0.1-rc.3",
|
|
49
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.0.1-rc.3"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
53
|
+
"@deepseek-ai/dsh-api-remotes": "^0.0.1-rc.3",
|
|
54
|
+
"@deepseek-ai/dsh-client-locale": "^0.0.1-rc.3",
|
|
55
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/react": "~18.3.1",
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.1-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-agent": "^0.0.1-rc.3",
|
|
61
|
+
"@deepseek-ai/dsh-api-remotes": "^0.0.1-rc.3",
|
|
62
|
+
"@deepseek-ai/dsh-client-locale": "^0.0.1-rc.3",
|
|
63
|
+
"@deepseek-ai/dsh-invariants": "^0.0.1-rc.3",
|
|
64
|
+
"@deepseek-ai/dsh-system-prompt": "^0.0.1-rc.3",
|
|
65
|
+
"@deepseek-ai/dsh-user-questions": "^0.0.1-rc.3",
|
|
66
|
+
"@deepseek-ai/dsh-tools": "^0.0.1-rc.3"
|
|
67
|
+
},
|
|
68
|
+
"files": [
|
|
69
|
+
"lib/index.js",
|
|
70
|
+
"lib/invariant.js",
|
|
71
|
+
"lib/client.js",
|
|
72
|
+
"lib/types/**/*.d.ts"
|
|
73
|
+
],
|
|
74
|
+
"scripts": {
|
|
75
|
+
"bundle": "tsdown",
|
|
76
|
+
"watch": "tsdown --watch"
|
|
77
|
+
}
|
|
78
|
+
}
|