dsh-better-ask-user 0.1.3 → 0.1.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/ask-tool.js +108 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-better-ask-user",
3
- "version": "0.1.3",
3
+ "version": "0.1.4",
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
@@ -1,16 +1,113 @@
1
+ /** ask_user_question scope shadow: official contract plus the plugin-managed wait. */
2
+ import { ToolArgsError, validateJsonSchemaValue } from '@deepseek-ai/dsh-tools'
3
+ import { buildDefaultAnswer } from './default-answer.js'
4
+
5
+ /** Model-visible notice for the native pending result. */
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
+
8
+ function questionRequest(questions, exec) {
9
+ return {
10
+ questions: questions.map(question => ({
11
+ id: question.id,
12
+ question: question.question,
13
+ ...(question.header !== undefined ? { header: question.header } : {}),
14
+ ...(question.options !== undefined ? {
15
+ options: question.options.map(option => ({ ...option })),
16
+ } : {}),
17
+ ...(question.multi_select !== undefined ? { multiSelect: question.multi_select } : {}),
18
+ })),
19
+ ...(exec.agent !== undefined ? { agent: exec.agent } : {}),
20
+ signal: exec.signal,
21
+ }
22
+ }
23
+
24
+ function validateQuestionIds(questions) {
25
+ const ids = new Set()
26
+ for (const question of questions) {
27
+ if (ids.has(question.id)) throw new Error('question id ' + JSON.stringify(question.id) + ' must be unique within this call')
28
+ ids.add(question.id)
29
+ }
30
+ }
31
+
32
+ function answerResult(result) {
33
+ return {
34
+ answers: result.answers.map(answer => ({
35
+ id: answer.id,
36
+ selected: [...answer.selected],
37
+ ...(answer.custom !== undefined ? { custom: answer.custom } : {}),
38
+ })),
39
+ }
40
+ }
41
+
42
+ /** The timed path can return the official pending shape; keep it valid. */
43
+ function outputWithPending(base) {
44
+ 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
47
+ output.schema = {
48
+ oneOf: [
49
+ schema,
50
+ {
51
+ type: 'object',
52
+ additionalProperties: false,
53
+ properties: {
54
+ pending: { type: 'boolean' },
55
+ callId: { type: 'string' },
56
+ message: { type: 'string' },
57
+ },
58
+ required: ['pending', 'callId', 'message'],
59
+ },
60
+ ],
61
+ }
62
+ return output
63
+ }
64
+
1
65
  /**
2
- * ask_user_question scope shadow: the official tool definition, schema, output
3
- * and execute path verbatim. The ask-user row only supplies the default wait
4
- * when the model omits the timeout argument; explicit model timeouts and every
5
- * validation or error stay exactly those of @deepseek-ai/dsh-tool-ask-user.
66
+ * Build the ask_user_question shadow from the currently registered base tool.
67
+ * @param ctx - root host context with userQuestions and optional goals.
68
+ * @param base - existing official ask_user_question definition.
69
+ * @param readConfig - live config reader.
70
+ * @returns a ToolDefinition with the official contract and the plugin wait.
6
71
  */
7
- export function createBetterAskTool(_ctx, base, readConfig) {
72
+ export function createBetterAskTool(ctx, base, readConfig) {
73
+ const output = outputWithPending(base)
74
+
8
75
  const execute = async (args, exec) => {
9
- const { questionTimeoutSeconds } = readConfig()
10
- const withDefault = args.timeout === undefined
11
- ? { ...args, timeout: questionTimeoutSeconds }
12
- : args
13
- return base.execute(withDefault, exec)
76
+ const violations = validateJsonSchemaValue(base.parameters, args, '')
77
+ if (violations.length > 0) throw new ToolArgsError(violations)
78
+ validateQuestionIds(args.questions)
79
+
80
+ const userQuestions = ctx.get('userQuestions')
81
+ if (userQuestions === undefined) throw new Error('ask_user_question requires the userQuestions service')
82
+
83
+ const config = readConfig()
84
+ const goal = exec.agent === undefined ? undefined : ctx.get('goals')?.get?.(exec.agent)
85
+ const goalActive = goal?.phase === 'active' && goal.activation === 'armed'
86
+ const mode = goalActive ? config.goalTimeoutBehavior : config.timeoutBehavior
87
+ const seconds = config.questionTimeoutSeconds
88
+ const questions = questionRequest(args.questions, exec)
89
+ const defaultAnswer = mode === 'default' && seconds !== -1
90
+ ? answerResult(buildDefaultAnswer(args.questions))
91
+ : undefined
92
+
93
+ if (seconds === -1) {
94
+ const result = await userQuestions.ask({ ...questions, wait: { callId: exec.callId } })
95
+ return answerResult(result)
96
+ }
97
+ if (exec.agent === undefined) throw new Error('timed questions require a live agent')
98
+
99
+ const waitMs = seconds * 1000
100
+ const result = await userQuestions.askTimed({
101
+ ...questions,
102
+ agent: exec.agent,
103
+ better: { waitMs, timeoutMode: mode, defaultAnswer },
104
+ }, exec.callId, waitMs)
105
+ if ('pending' in result) {
106
+ if (mode === 'default' && defaultAnswer !== undefined) return answerResult(defaultAnswer)
107
+ return { ...result, message: PENDING_QUESTION_NOTICE }
108
+ }
109
+ return answerResult(result)
14
110
  }
15
- return { ...base, execute }
111
+
112
+ return { ...base, output, execute }
16
113
  }