dsh-better-ask-user 0.1.6 → 0.1.7

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/lib/client.js CHANGED
@@ -580,7 +580,7 @@ function AskHistoryRow({
580
580
  revealPanel(callId);
581
581
  } : void 0;
582
582
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: AskHistoryRow_default.row, children: [
583
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: AskHistoryRow_default.icon, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconQuestionOutline14, {}) }),
583
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: AskHistoryRow_default.icon, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconQuestionOutlineRegular, {}) }),
584
584
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: AskHistoryRow_default.title, children: t("history.title") }),
585
585
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: AskHistoryRow_default.summary, title: summary, children: summary }),
586
586
  action !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -2017,6 +2017,11 @@ function apply(ctx) {
2017
2017
  name: "tool.call.toolview",
2018
2018
  key: "ask_user_question",
2019
2019
  locale: NS,
2020
+ // The shipped `@deepseek-ai/dsh-client-ui-tool` already registers
2021
+ // `ask-question-toolview` for this key at priority 0, and the slot registry
2022
+ // rejects a second entry at the same priority (which failed the whole client
2023
+ // boot). A lower priority shadows it: lowest renders.
2024
+ priority: -1,
2020
2025
  inject: (sessionId) => ({
2021
2026
  revealPanel: (callId) => ctx.get("userQuestionPanels")?.reveal(sessionId, callId) ?? false,
2022
2027
  reviewPanel: (callId, record) => ctx.get("userQuestionPanels")?.review(sessionId, callId, record) ?? false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-ask-user",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Independent ask_user_question plugin: configurable wait, input-extended countdown, markdown/mobile card, native or default timeout behavior, and PTC deadline exemption.",
5
5
  "license": "GPL-3.0",
6
6
  "repository": {
package/src/ask-tool.js CHANGED
@@ -5,12 +5,16 @@ import { buildDefaultAnswer } from './default-answer.js'
5
5
  /** Model-visible notice for the native pending result. */
6
6
  export const PENDING_QUESTION_NOTICE = 'No answer batch arrived before the timeout. This is pending, not a skipped answer. Continue useful independent work. The user can still answer; their reply will be a user message identified as answer_to_pending_question with this callId and the original questions. Do not treat this as permission.'
7
7
 
8
+ /** Model-visible warning for a host auto-selected default batch. */
9
+ export const AUTO_SELECTED_WARNING = 'These answers were auto-selected by the host timeout behavior, not submitted by the user. Do not treat them as user confirmation.'
10
+
8
11
  function questionRequest(questions, exec) {
9
12
  return {
10
13
  questions: questions.map(question => ({
11
14
  id: question.id,
12
15
  question: question.question,
13
16
  ...(question.header !== undefined ? { header: question.header } : {}),
17
+ ...(question.detail !== undefined ? { detail: question.detail } : {}),
14
18
  ...(question.options !== undefined ? {
15
19
  options: question.options.map(option => ({ ...option })),
16
20
  } : {}),
@@ -36,14 +40,51 @@ function answerResult(result) {
36
40
  selected: [...answer.selected],
37
41
  ...(answer.custom !== undefined ? { custom: answer.custom } : {}),
38
42
  })),
43
+ ...(result.autoSelected === true ? { autoSelected: true, warning: AUTO_SELECTED_WARNING } : {}),
44
+ }
45
+ }
46
+
47
+ /** Add the optional detail field to the model-visible question schema. */
48
+ function parametersWithDetail(parameters) {
49
+ if (parameters === undefined) return parameters
50
+ const next = structuredClone(parameters)
51
+ const properties = next?.properties?.questions?.items?.properties
52
+ if (properties !== undefined && properties.detail === undefined) {
53
+ properties.detail = {
54
+ type: 'string',
55
+ description: 'Optional longer detail or context rendered as sanitized markdown below the question.',
56
+ }
57
+ }
58
+ return next
59
+ }
60
+
61
+ /** Allow the additive auto-selected warning fields on the normal answer branch. */
62
+ function extendAutoSelectedSchema(schema) {
63
+ const branches = Array.isArray(schema?.oneOf) ? schema.oneOf : [schema]
64
+ for (const branch of branches) {
65
+ const properties = branch?.properties
66
+ if (properties?.answers === undefined) continue
67
+ if (properties.autoSelected === undefined) {
68
+ properties.autoSelected = {
69
+ type: 'boolean',
70
+ description: 'True when the host auto-selected the default batch after the timeout; not a user answer.',
71
+ }
72
+ }
73
+ if (properties.warning === undefined) {
74
+ properties.warning = { type: 'string', description: 'Model-visible warning for an auto-selected batch.' }
75
+ }
39
76
  }
77
+ return schema
40
78
  }
41
79
 
42
80
  /** The timed path can return the official pending shape; keep it valid. */
43
81
  function outputWithPending(base) {
44
82
  const output = { ...base.output }
45
- const schema = structuredClone(base.output.schema)
46
- if (Array.isArray(schema?.oneOf) && schema.oneOf.some(branch => branch?.properties?.pending !== undefined)) return output
83
+ const schema = extendAutoSelectedSchema(structuredClone(base.output.schema))
84
+ if (Array.isArray(schema?.oneOf) && schema.oneOf.some(branch => branch?.properties?.pending !== undefined)) {
85
+ output.schema = schema
86
+ return output
87
+ }
47
88
  output.schema = {
48
89
  oneOf: [
49
90
  schema,
@@ -71,9 +112,10 @@ function outputWithPending(base) {
71
112
  */
72
113
  export function createBetterAskTool(ctx, base, readConfig) {
73
114
  const output = outputWithPending(base)
115
+ const parameters = parametersWithDetail(base.parameters)
74
116
 
75
117
  const execute = async (args, exec) => {
76
- const violations = validateJsonSchemaValue(base.parameters, args, '')
118
+ const violations = validateJsonSchemaValue(parameters, args, '')
77
119
  if (violations.length > 0) throw new ToolArgsError(violations)
78
120
  validateQuestionIds(args.questions)
79
121
 
@@ -113,5 +155,5 @@ export function createBetterAskTool(ctx, base, readConfig) {
113
155
  return answerResult(result)
114
156
  }
115
157
 
116
- return { ...base, output, execute }
117
- }
158
+ return { ...base, parameters, output, execute }
159
+ }
@@ -1,5 +1,5 @@
1
1
  /** History row for ask_user_question: compact summary plus a replay button. */
2
- import { IconQuestionOutline14 } from '@deepseek-ai/dsh-client-ui-primitives'
2
+ import { IconQuestionOutlineRegular } from '@deepseek-ai/dsh-client-ui-primitives'
3
3
  import type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'
4
4
  import type { ToolCallViewProps, UserQuestionRecord } from '@deepseek-ai/dsh-client-ui-tool/client'
5
5
  import type { AskUserQuestionItem } from '@deepseek-ai/dsh-user-questions/types'
@@ -116,7 +116,7 @@ export function AskHistoryRow({
116
116
 
117
117
  return (
118
118
  <div className={css.row}>
119
- <span className={css.icon} aria-hidden="true"><IconQuestionOutline14 /></span>
119
+ <span className={css.icon} aria-hidden="true"><IconQuestionOutlineRegular /></span>
120
120
  <span className={css.title}>{t('history.title')}</span>
121
121
  <span className={css.summary} title={summary}>{summary}</span>
122
122
  {action !== undefined && (
@@ -131,4 +131,4 @@ export function AskHistoryRow({
131
131
  )}
132
132
  </div>
133
133
  )
134
- }
134
+ }
@@ -419,6 +419,11 @@ export function apply(ctx: ClientContext): void {
419
419
  name: 'tool.call.toolview',
420
420
  key: 'ask_user_question',
421
421
  locale: NS,
422
+ // The shipped `@deepseek-ai/dsh-client-ui-tool` already registers
423
+ // `ask-question-toolview` for this key at priority 0, and the slot registry
424
+ // rejects a second entry at the same priority (which failed the whole client
425
+ // boot). A lower priority shadows it: lowest renders.
426
+ priority: -1,
422
427
  inject: (sessionId: SessionId) => ({
423
428
  revealPanel: (callId: string) => ctx.get('userQuestionPanels')?.reveal(sessionId, callId) ?? false,
424
429
  reviewPanel: (callId: string, record: UserQuestionRecord) => ctx.get('userQuestionPanels')?.review(sessionId, callId, record) ?? false,
package/src/settings.js CHANGED
@@ -12,9 +12,11 @@ function hasOwn(value, key) {
12
12
  return value !== null && typeof value === 'object' && Object.hasOwn(value, key)
13
13
  }
14
14
 
15
- function descriptor(service, ns) {
15
+ async function descriptor(service, ns) {
16
16
  try {
17
- return service.describe(ns)
17
+ const rows = await service.describe()
18
+ if (!Array.isArray(rows)) return undefined
19
+ return rows.find(row => row?.ns === ns)
18
20
  } catch {
19
21
  return undefined
20
22
  }
@@ -36,25 +38,25 @@ export async function migrateLegacyQuestionTimeout(service) {
36
38
  return { status: 'unavailable' }
37
39
  }
38
40
  const deadline = Date.now() + WAIT_MS
39
- let source = descriptor(service, LEGACY_GOAL_NS)
40
- let target = descriptor(service, ASK_NS)
41
+ let source = await descriptor(service, LEGACY_GOAL_NS)
42
+ let target = await descriptor(service, ASK_NS)
41
43
  while ((source === undefined || target === undefined) && Date.now() < deadline) {
42
44
  await sleep(WAIT_INTERVAL_MS)
43
- source = descriptor(service, LEGACY_GOAL_NS)
44
- target = descriptor(service, ASK_NS)
45
+ source = await descriptor(service, LEGACY_GOAL_NS)
46
+ target = await descriptor(service, ASK_NS)
45
47
  }
46
48
  if (source === undefined || target === undefined) return { status: 'pending' }
47
49
  if (!hasOwn(source.user, 'questionTimeoutSeconds')) return { status: 'no-change' }
48
50
  if (hasOwn(target.user, 'questionTimeoutSeconds')) return { status: 'no-change' }
49
51
  const value = source.value?.questionTimeoutSeconds
50
52
  if (!validSeconds(value)) return { status: 'no-change' }
51
- if (target.writable !== true) return { status: 'pending' }
53
+ if (service.writable === false) return { status: 'pending' }
52
54
  try {
53
55
  await service.update(ASK_NS, { questionTimeoutSeconds: value }, target.revision)
54
56
  } catch (error) {
55
57
  return { status: 'error', error: error instanceof Error ? error.message : String(error) }
56
58
  }
57
- const after = descriptor(service, ASK_NS)
59
+ const after = await descriptor(service, ASK_NS)
58
60
  return after?.user?.questionTimeoutSeconds === value
59
61
  ? { status: 'complete', value }
60
62
  : { status: 'pending', value }
@@ -76,4 +78,4 @@ export function mountLegacyMigration(ctx) {
76
78
  inner.logger?.warn?.('better-ask-user: legacy question wait migration failed: ' + String(error))
77
79
  })
78
80
  })
79
- }
81
+ }