@deepseek-ai/dsh-tool-ask-user 0.2.0-rc.1 → 0.2.0-rc.2

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.i18n.yaml CHANGED
@@ -9,32 +9,32 @@
9
9
  en: 2a561a70c4a900d7
10
10
  zh: 3d12372992cf2634
11
11
  /deepseek-ai-dsh-tool-ask-user/summary:
12
- en: 45bb29d463d1e8d2
13
- zh: a462610ab6e8e61b
12
+ en: 1a9a38698a6cb1f3
13
+ zh: 35540765df5648e7
14
14
  /deepseek-ai-dsh-tool-ask-user/table-of-contents:
15
15
  en: d152484eb41ac6b4
16
16
  zh: 09388d293f9be9cb
17
17
  /deepseek-ai-dsh-tool-ask-user/use-this-package:
18
- en: ed8ec46772509d66
19
- zh: b06aad135f7a4fa3
18
+ en: 547a3ea61708bc3e
19
+ zh: 5e251ea392940c1a
20
20
  /deepseek-ai-dsh-tool-ask-user/use-this-package/when-to-call-the-tool:
21
- en: 5a142d88def1a4fd
22
- zh: 2ae5573abf23d76a
21
+ en: 7c70d43d4cd9aa19
22
+ zh: 7597e972067ec5fe
23
23
  /deepseek-ai-dsh-tool-ask-user/use-this-package/what-the-model-gets-back:
24
- en: d0047cb9dda88606
25
- zh: d3432561db23f65a
24
+ en: c30ba8cefa64a287
25
+ zh: cc8bc285ff859983
26
26
  /deepseek-ai-dsh-tool-ask-user/use-this-package/when-the-call-fails:
27
- en: dd379ed6282761b6
28
- zh: fa7da19a52091403
27
+ en: c4ac3c584f900230
28
+ zh: bd8d227f0637b8f1
29
29
  /deepseek-ai-dsh-tool-ask-user/understand-the-implementation:
30
30
  en: e9b9d46be0b45ca2
31
31
  zh: c02403486473cd80
32
32
  /deepseek-ai-dsh-tool-ask-user/understand-the-implementation/source-map:
33
- en: 8ce163d8203be215
34
- zh: 6fd9db8f061b8198
33
+ en: 95a6f15bb08e239b
34
+ zh: 9def25d23f0e131d
35
35
  /deepseek-ai-dsh-tool-ask-user/understand-the-implementation/consumer-role:
36
- en: 30c2b751e1b04fe4
37
- zh: 862c1087ad9c8f66
36
+ en: 7de88d25287b969f
37
+ zh: be36dc5a39aeac3b
38
38
  /deepseek-ai-dsh-tool-ask-user/understand-the-implementation/result-rendering:
39
39
  en: 686176b2703dfa08
40
40
  zh: 4ea4dab0da7f79f9
@@ -48,8 +48,8 @@
48
48
  en: e4c7376d4f29dbd5
49
49
  zh: 6b7a9eaf69c9ea53
50
50
  /deepseek-ai-dsh-tool-ask-user/model-experience/tool-schema/what-the-model-sees:
51
- en: e043611dc8d99927
52
- zh: 2e4b0e6548f2d64c
51
+ en: 577c8609a8bb915d
52
+ zh: 8607e0d39be0e492
53
53
  /deepseek-ai-dsh-tool-ask-user/model-experience/tool-schema/token-effect:
54
54
  en: 97cce067584eadff
55
55
  zh: c376cab39ea9df68
@@ -60,8 +60,8 @@
60
60
  en: 9717b983b6a857e3
61
61
  zh: 611a2dadf7c0c706
62
62
  /deepseek-ai-dsh-tool-ask-user/model-experience/tool-call-history-and-result/what-the-model-sees:
63
- en: d5ba86e2dbdb5b4b
64
- zh: f5a2d6de0d985eed
63
+ en: c2ceed515656e10b
64
+ zh: 4e9b6768e4914718
65
65
  /deepseek-ai-dsh-tool-ask-user/model-experience/tool-call-history-and-result/token-effect:
66
66
  en: 3092d4afd4ba8678
67
67
  zh: cb7a7bbefdbc3a1e
@@ -69,8 +69,8 @@
69
69
  en: f5d5f97a153f7c70
70
70
  zh: 4db81881ebdcfcc4
71
71
  /deepseek-ai-dsh-tool-ask-user/known-limitations-and-deferred-work:
72
- en: a0f0f15f51d5affb
73
- zh: 41f11351e852968c
72
+ en: c8df1368c2959465
73
+ zh: b14fb07a60e75ebf
74
74
  /deepseek-ai-dsh-tool-ask-user/known-limitations-and-deferred-work/dev-note:
75
- en: e67f633dc6fe7773
76
- zh: 3bb0c20c93f16f98
75
+ en: 0b06d843322610c1
76
+ zh: a1e7cadd4f03c384
package/README.md CHANGED
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
9
9
 
10
10
  ## Summary
11
11
 
12
- `ask_user_question` lets a model pause work and ask the human for confirmation, a choice, or missing information. It accepts one or more questions and returns their answers as compact JSON. The call waits until an answer is accepted or the turn is cancelled; if no answer handler accepts it, the model receives an error. A live child agent owned by another agent cannot call this tool and must report unresolved questions in its final result. The package does not render or collect input, so callers must provide a compatible user interaction surface.
12
+ `ask_user_question` asks the user for confirmation, a choice, or missing information. It waits for an answer by default. With `mode: timed`, a deadline can release the model to continue independent work while the question stays answerable; `timeout: -1` waits indefinitely. A live child agent cannot call the tool. Callers provide the answer UI.
13
13
 
14
14
  ## Table of Contents
15
15
 
@@ -25,11 +25,23 @@ English | [中文](README.zh.md)
25
25
  <a id="use-this-package"></a>
26
26
  ## Use this package
27
27
 
28
- Compose this plugin wherever the model should be able to pause for a human decision: it provides the `ask_user_question` tool and needs the `ctx.userQuestions` seam with an answerer that accepts the scoped request. Without one, the tool call fails with an error instead of degrading.
28
+ Compose this plugin with `ctx.userQuestions` when the model needs a user decision. Without an answerer, blocking calls fail; finite timed calls return pending at their deadline.
29
+
30
+ Shipped presets use blocking mode. To enable timed mode, set `mode: timed` on the `tool-ask-user` row in the active preset's `config.plugins` list:
31
+
32
+ ```yaml
33
+ - id: tool-ask-user
34
+ name: '@deepseek-ai/dsh-tool-ask-user'
35
+ config:
36
+ mode: timed
37
+ timeout: 120
38
+ ```
39
+
40
+ This is a plugin row, not a top-level `--patch` entry. In the Web profile, change the `preset-standard` plugin list or use the Agent Preset editor. `mode: legacy` or omitted config keeps the blocking schema. The row's `timeout` applies to each timed call by default; `-1` waits indefinitely unless the model supplies a positive timeout. A model-supplied `-1` applies only to that call, including all its questions.
29
41
 
30
42
  ### When to call the tool
31
43
 
32
- The model calls `ask_user_question` when it needs confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable `id` that is echoed in the answer; a recommended option goes first with `(Recommended)` appended to its label.
44
+ Send one or more questions with ids unique within the call; the answer echoes those ids. Put a recommended option first and append `(Recommended)` to its label. The optional per-call `timeout` is in seconds; use `-1` when work cannot safely continue without an answer. A timeout is never approval. In the Web card, editing or choosing Take time also holds that Client's wait until submission or cancellation.
33
45
 
34
46
  ```json
35
47
  {
@@ -49,7 +61,9 @@ The model calls `ask_user_question` when it needs confirmation, a choice, or mis
49
61
 
50
62
  ### What the model gets back
51
63
 
52
- The tool returns one answer object per question: `selected` holds the chosen option labels, and `custom` carries a free-form answer — supplementing `selected` for a multi-select question and overriding it for a single-select question. The Native renderer preserves the compact JSON text shape.
64
+ An answer returns one item per question. `selected` holds option labels; `custom` supplements a multi-select answer or replaces a single-select choice. A skipped item has empty `selected` and no `custom`.
65
+
66
+ For a finite timed call, `{ "pending": true, "callId": "…" }` means the foreground wait ended without an answer. The question remains answerable, and the model may continue independent work. A later answer arrives as a user message identified by `kind`, `tool`, and `callId`. Web chat pairs its questions and answers; other consumers receive compact JSON text.
53
67
 
54
68
  ```json
55
69
  { "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
@@ -57,7 +71,7 @@ The tool returns one answer object per question: `selected` holds the chosen opt
57
71
 
58
72
  ### When the call fails
59
73
 
60
- The tool call blocks until the human answers and cancels only through the turn's signal. No accepting answerer, an aborted call, or a caller that is not the exact live runtime root each settles as an error the model sees in the tool result — most notably, a live child agent owned by another agent is rejected (`DELEGATED_CALLER`) and must include the unresolved question or decision in its final result.
74
+ Legacy and `timeout: -1` calls wait for an answer or cancellation; without an accepting answerer, they return an error. A finite timed call without an answerer returns pending at its deadline. Cancellation and a caller that is not the exact live runtime root also return errors. A live child agent is rejected with `DELEGATED_CALLER` and must include the unresolved decision in its final result.
61
75
 
62
76
  -----
63
77
 
@@ -73,12 +87,13 @@ The observable behavior is covered in [Use this package](#use-this-package); thi
73
87
 
74
88
  | File | Role |
75
89
  |---|---|
76
- | [`src/index.ts`](src/index.ts) | Tool registration: `ask_user_question` schema, execute path, result render |
90
+ | [`src/index.ts`](src/index.ts) | Default blocking tool definition and mode selection |
91
+ | [`src/timed.ts`](src/timed.ts) | Opt-in timed tool definition and result rendering |
77
92
  | — | No runtime invariant companion is published; this model-facing adapter has no independent lifecycle stream; execution relations are owned by the capability seam it calls. |
78
93
 
79
94
  ### Consumer role
80
95
 
81
- The plugin registers one `defineTool` entry on `ctx.tools` with injects `['tools', 'userQuestions']`. `execute` maps model arguments into an `AskUserQuestionRequest`, forwards the exact calling agent and the turn's signal, and maps the accepted answer back into the canonical `answers` array. The seam owns identity checks, intent validation, waterfall dispatch, and the error taxonomy; this package only translates.
96
+ The plugin registers one tool definition. Legacy mode calls `ask()`; timed mode calls `askTimed()` for positive timeouts and `ask()` for `-1`. Both forward the calling agent and turn signal to `ctx.userQuestions`. Timed requests include the tool call id in `wait`, so a Client can reopen their card. The projection identifies timed native calls from the `timeout` field in the recorded tool schema, even when a call omits that argument.
82
97
 
83
98
  ### Result rendering
84
99
 
@@ -107,7 +122,7 @@ Read these pages when the package-level contract is not enough. They move from t
107
122
 
108
123
  #### What the model sees
109
124
 
110
- The model sees the generated [`ask_user_question` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-ask-user), including question ids, prompts, headings, options, and multi-select flags.
125
+ The shipped presets expose the original blocking [`ask_user_question` schema](../../../docs/tool-catalog.md#deepseek-aidsh-tool-ask-user). A custom Cordis row with `mode: timed` switches to the alternate schema, including question ids, prompts, headings, options, multi-select flags, `timeout`, and the pending result; the model sees only the selected definition.
111
126
 
112
127
  #### Token effect
113
128
 
@@ -121,7 +136,7 @@ Prefix-stable while the definition and visibility are unchanged. Plugin lifecycl
121
136
 
122
137
  #### What the model sees
123
138
 
124
- The model's full questions remain in the assistant tool-call arguments. After the human answers, the next step sees compact JSON in the exact shape `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}`; `custom` is omitted when unused and `selected` can contain zero, one, or several labels. UI interaction while the call is pending is not model context.
139
+ The assistant tool call retains the questions. An answer during the wait appears in the next step as compact `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}` JSON; unused `custom` is omitted. A timed-out call returns `{"pending":true,"callId":"<pending-call-id>","message":"<instruction>"}`. A later answer arrives as a user message with `kind: "answer_to_pending_question"`, `tool: "ask_user_question"`, the call id, the original questions, and the answers. UI activity before submission is not model context.
125
140
 
126
141
  #### Token effect
127
142
 
@@ -138,7 +153,8 @@ Append-only; newly visible content follows the reusable request prefix and does
138
153
 
139
154
  These limits define when the tool is a poor fit. They are current package constraints, not a UI backlog.
140
155
 
141
- - **A pending question blocks the tool call until the human answers** — the tool declares no `timeout-policy` budget; cancellation rides the turn's `exec.signal` only.
156
+ - **The legacy tool never reports pending** — it returns an answer or an error. Native legacy calls do not enter the `userQuestions` projection, and interrupted calls cannot take late answers.
157
+ - **An interrupted PTC wait may lose its question** — if the `run_code` process ends before an `ask_user_question` sub-call records its `tool/ptc-dispatch` result, the projection cannot reconstruct that sub-call for a late answer.
142
158
  - **Runtime-owned subagents cannot ask the user** — `ask_user_question` rejects a live child owned by another agent with `DELEGATED_CALLER`; the child must include the unresolved question or decision in its final result. Durable lineage does not decide this boundary, so a lineage-bearing session resumed as a runtime root may ask normally.
143
159
  - **Native answers render as JSON text** — the canonical value remains structured, but the model-facing result uses compact JSON rather than a richer content-block vocabulary.
144
160
 
@@ -148,6 +164,6 @@ These limits define when the tool is a poor fit. They are current package constr
148
164
  <details>
149
165
  <summary>Working context for maintainers — click to expand</summary>
150
166
 
151
- None.
167
+ The [`userQuestions` projection](../user-questions/src/projection.ts) reads each recorded tool schema rather than the call arguments: timed calls may omit `timeout`, and timed `-1` calls still use the timed schema. Changes to the projection fold require a `stateVersion` bump so stored caches refold from the log.
152
168
 
153
169
  </details>
package/README.zh.md CHANGED
@@ -9,7 +9,7 @@ kind: "package-reference"
9
9
 
10
10
  ## 概述
11
11
 
12
- `ask_user_question` 让模型暂停工作,向用户请求确认、选择或缺失的信息。它接受一个或多个问题,并以紧凑 JSON 返回回答。调用会等待回答被接受或当前轮次被取消;如果没有回答处理器接受请求,模型会收到错误。归属于运行时其他 agent 的子级不能调用此工具,必须在最终结果中报告尚未解决的问题。本包不渲染界面或收集输入,因此调用方必须提供兼容的用户交互表面。
12
+ `ask_user_question` 向用户请求确认、选择或缺失的信息。默认情况下,它会等待回答。设置 `mode: timed` 后,期限到达时模型可以继续独立工作,而问题仍可回答;`timeout: -1` 则无限期等待。存活的子 agent 不能调用此工具。调用方需提供回答界面。
13
13
 
14
14
  ## 目录
15
15
 
@@ -25,11 +25,23 @@ kind: "package-reference"
25
25
  <a id="use-this-package"></a>
26
26
  ## 使用本包
27
27
 
28
- 凡模型应当能够暂停等待人类决定的场景,都可组合此插件:它提供 `ask_user_question` 工具,并且需要带有接受作用域请求的 answerer 的 `ctx.userQuestions` seam。没有 answerer 接受时,工具调用会以错误失败,而不是降级。
28
+ 当模型需要用户决定时,将此插件与 `ctx.userQuestions` 组合。没有 answerer 时,阻塞式调用返回错误;有限时长的 timed 调用到期后返回 pending。
29
+
30
+ 随附 preset 使用阻塞模式。要启用 timed 模式,请在当前 preset 的 `config.plugins` 列表中,将 `tool-ask-user` 条目设为 `mode: timed`:
31
+
32
+ ```yaml
33
+ - id: tool-ask-user
34
+ name: '@deepseek-ai/dsh-tool-ask-user'
35
+ config:
36
+ mode: timed
37
+ timeout: 120
38
+ ```
39
+
40
+ 以上片段是插件条目,不是顶层 `--patch` 条目。在 Web profile 中,请修改 `preset-standard` 的插件列表,或使用 Agent Preset 编辑器。`mode: legacy` 或省略配置会保留阻塞式 schema。条目中的 `timeout` 默认适用于每次 timed 调用;设为 `-1` 时会无限期等待,除非模型传入正数期限。模型传入的 `-1` 只适用于该次调用,包括其中的所有问题。
29
41
 
30
42
  ### 何时调用该工具
31
43
 
32
- 当模型需要确认、选择结果或缺失的信息才能继续时,调用 `ask_user_question`。发送一个或多个问题,每个问题携带稳定的 `id`(回答中会原样包含);推荐选项放在首位,并在标签末尾追加 `(Recommended)`。
44
+ 发送一个或多个问题,每个问题的 `id` 在本次调用内必须唯一;回答会带回这些 id。推荐选项放在首位,并在标签末尾追加 `(Recommended)`。可选的单次调用 `timeout` 以秒为单位;如果没有回答就无法安全继续,请使用 `-1`。超时绝不表示批准。在 Web 卡片中,开始编辑或选择“慢慢回答”也会让该 Client 的等待持续到提交或取消。
33
45
 
34
46
  ```json
35
47
  {
@@ -49,7 +61,9 @@ kind: "package-reference"
49
61
 
50
62
  ### 模型得到什么
51
63
 
52
- 工具为每个问题返回一个回答对象:`selected` 保存选中的选项标签,`custom` 携带自由填写的回答——对多选题补充 `selected`,对单选题覆盖它。Native 渲染器保留紧凑的 JSON 文本形式。
64
+ 收到回答时,工具为每个问题返回一项。`selected` 保存选项标签;`custom` 可以补充多选答案,或替代单选选项。跳过的回答项具有空 `selected`,且没有 `custom`。
65
+
66
+ 对于有限时长的 timed 调用,`{ "pending": true, "callId": "…" }` 表示前台等待结束时仍没有回答。问题仍可回答,模型可以继续独立工作。之后的回答以用户消息送达,由 `kind`、`tool` 和 `callId` 标识。Web 对话会配对展示问题与回答;其他消费方收到紧凑 JSON 文本。
53
67
 
54
68
  ```json
55
69
  { "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
@@ -57,7 +71,7 @@ kind: "package-reference"
57
71
 
58
72
  ### 调用何时失败
59
73
 
60
- 工具调用会阻塞到用户作答,并且只能通过当前轮次的信号取消。没有 answerer 接受、调用被中止、或调用方不是确切的存活运行时根,都会以模型在工具结果中看到的错误结算——最值得注意的是,归属于另一个 agent 的存活子级会被拒绝(`DELEGATED_CALLER`),必须在最终结果中包含尚未解决的问题或决定。
74
+ Legacy 调用和 `timeout: -1` 调用会等待回答或取消;没有 answerer 接受时返回错误。有限时长的 timed 调用若没有 answerer,则在到期后返回 pending。取消调用,或调用方不是确切的存活运行时根时,也会返回错误。存活的子 agent 会以 `DELEGATED_CALLER` 被拒绝,必须在最终结果中包含尚未解决的决定。
61
75
 
62
76
  -----
63
77
 
@@ -73,12 +87,13 @@ kind: "package-reference"
73
87
 
74
88
  | 文件 | 职责 |
75
89
  |---|---|
76
- | [`src/index.ts`](src/index.ts) | 工具注册:`ask_user_question` schema、执行路径、结果渲染 |
90
+ | [`src/index.ts`](src/index.ts) | 默认阻塞式工具定义与模式选择 |
91
+ | [`src/timed.ts`](src/timed.ts) | 显式启用的计时工具定义与结果渲染 |
77
92
  | — | 不发布运行时不变式伴生入口;此模型侧适配器没有独立的生命周期流;执行关系由其调用的能力 seam 负责。 |
78
93
 
79
94
  ### 消费方角色
80
95
 
81
- 该插件以 `['tools', 'userQuestions']` 注入,在 `ctx.tools` 上注册一个 `defineTool` 条目。`execute` 把模型参数映射为 `AskUserQuestionRequest`,转发确切的调用 agent 与当前轮次的信号,并把接受的回答映射回规范的 `answers` 数组。身份检查、意图校验、waterfall(瀑布式事件)分派与错误分类由 seam 拥有;本包只做转换。
96
+ 插件只注册一个工具定义。Legacy 模式调用 `ask()`;timed 模式对正数期限调用 `askTimed()`,对 `-1` 调用 `ask()`。两者都向 `ctx.userQuestions` 传递调用方 agent 和轮次信号。Timed 请求在 `wait` 中包含工具调用 id,Client 因此可以重新打开对应卡片。投影从记录的工具 schema 的 `timeout` 字段识别 timed 原生调用,即使某次调用省略该参数也一样。
82
97
 
83
98
  ### 结果渲染
84
99
 
@@ -107,7 +122,7 @@ kind: "package-reference"
107
122
 
108
123
  #### 模型看到的内容
109
124
 
110
- 模型会看到生成的 [`ask_user_question` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-ask-user),其中包含问题 id、提示语、标题、选项与多选标志。
125
+ 随附 preset 会公开原有的阻塞式 [`ask_user_question` schema](../../../docs/tool-catalog.zh.md#deepseek-aidsh-tool-ask-user)。自定义 Cordis 行设置 `mode: timed` 后,会切换到包含问题 id、提示语、标题、选项、多选标志、`timeout` 与待处理结果的异步 schema;模型只会看到被选中的定义。
111
126
 
112
127
  #### Token 影响
113
128
 
@@ -121,7 +136,7 @@ kind: "package-reference"
121
136
 
122
137
  #### 模型看到的内容
123
138
 
124
- 模型提出的完整问题保留在 assistant 工具调用参数中。用户回答后,下一步会看到精确采用 `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}` 形式的紧凑 JSON;不使用 `custom` 时会省略该字段,`selected` 可以包含零个、一个或多个标签。调用等待期间的 UI 交互不属于模型上下文。
139
+ assistant 工具调用保留问题。等待期间收到的回答会在下一步显示为紧凑的 `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}` JSON;未使用的 `custom` 会省略。计时到期的调用返回 `{"pending":true,"callId":"<pending-call-id>","message":"<instruction>"}`。之后的回答以用户消息送达,包含 `kind: "answer_to_pending_question"`、`tool: "ask_user_question"`、调用 id、原问题和答案。提交之前的 UI 活动不属于模型上下文。
125
140
 
126
141
  #### Token 影响
127
142
 
@@ -138,7 +153,8 @@ kind: "package-reference"
138
153
 
139
154
  这些限制说明该工具何时不合适。它们是当前包约束,不是 UI 积压事项。
140
155
 
141
- - **待处理问题会阻塞工具调用,直至用户作答**:该工具未声明 `timeout-policy` 预算;取消仅沿用当前轮次的 `exec.signal`。
156
+ - **Legacy 工具不会报告 pending**:它只返回回答或错误。原生 legacy 调用不进入 `userQuestions` 投影;中断的调用不能接受迟到回答。
157
+ - **中断的 PTC 等待可能丢失问题**:若 `run_code` 进程在 `ask_user_question` 子调用记录 `tool/ptc-dispatch` 结果前结束,投影无法重建该子调用以接受迟到回答。
142
158
  - **运行时中归属于其他 agent 的 subagent 不能向用户提问**:`ask_user_question` 会以 `DELEGATED_CALLER` 拒绝归属于另一个 agent 的存活子级;该子级必须在最终结果中包含尚未解决的问题或决定。持久谱系不能决定这一边界,因此带有谱系的会话恢复为运行时根后可以正常提问。
143
159
  - **Native 回答渲染为 JSON 文本**:规范值仍为结构化数据,但模型侧结果使用紧凑 JSON,而非更丰富的内容块词汇。
144
160
 
@@ -148,6 +164,6 @@ kind: "package-reference"
148
164
  <details>
149
165
  <summary>维护者的工作上下文——点击展开</summary>
150
166
 
151
- 无。
167
+ [`userQuestions` 投影](../user-questions/src/projection.ts)读取记录的工具 schema,而不是调用参数:timed 调用可以省略 `timeout`,timed `-1` 调用也仍使用 timed schema。改变投影折叠语义时必须提高 `stateVersion`,让持久化缓存从日志重新折叠。
152
168
 
153
169
  </details>
package/lib/index.js CHANGED
@@ -1,5 +1,205 @@
1
1
  import { defineTool } from "@deepseek-ai/dsh-tools";
2
- import "@deepseek-ai/dsh-user-questions";
2
+ import z from "@deepseek-ai/schemastery";
3
+ import { TIMED_WAIT_PARAMETER } from "@deepseek-ai/dsh-user-questions";
4
+ //#region lib/types/timed.js
5
+ /**
6
+ * Opt-in timed `ask_user_question` tool definition.
7
+ */
8
+ function validateTimeout(timeout) {
9
+ if (timeout !== -1 && (!Number.isInteger(timeout) || timeout < 1 || timeout > 2147483)) throw new Error("timeout must be -1 or a positive integer up to 2147483 seconds");
10
+ return timeout;
11
+ }
12
+ function validateQuestionIds(questions) {
13
+ const ids = /* @__PURE__ */ new Set();
14
+ for (const question of questions) {
15
+ if (ids.has(question.id)) throw new Error(`question id ${JSON.stringify(question.id)} must be unique within this call`);
16
+ ids.add(question.id);
17
+ }
18
+ }
19
+ const description$1 = "Ask brief, direct, self-contained questions about missing information, preferences, or decisions. Use user-facing terms; assume no knowledge of background work or internal names. Use distinct stable question IDs. A submitted skipped question is an answer item with empty selected and no custom; pending instead means no answer batch arrived before the timeout and the user can still answer.";
20
+ /**
21
+ * Instruction the pending result carries in its `message` field. It is a field
22
+ * of the result value rather than prose beside it because the recorded result
23
+ * text is read back as one JSON object: the `userQuestions` projection decides
24
+ * from it that the call stays answerable, and the Client question row decides
25
+ * from it that the recorded result is the timeout, not an answer batch.
26
+ */
27
+ const pendingNotice = "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.";
28
+ /**
29
+ * Translate the model schema into the service request without changing ownership.
30
+ * @param questions - Model-supplied questions in tool-schema form.
31
+ * @param exec - Execution context that owns the agent and cancellation signal.
32
+ * @returns The corresponding user-question service request.
33
+ */
34
+ function questionRequest(questions, exec) {
35
+ return {
36
+ questions: questions.map((question) => ({
37
+ id: question.id,
38
+ question: question.question,
39
+ ...question.header !== void 0 ? { header: question.header } : {},
40
+ ...question.options !== void 0 ? { options: question.options.map((option) => ({ ...option })) } : {},
41
+ ...question.multi_select !== void 0 ? { multiSelect: question.multi_select } : {}
42
+ })),
43
+ ...exec.agent !== void 0 ? { agent: exec.agent } : {},
44
+ signal: exec.signal
45
+ };
46
+ }
47
+ /**
48
+ * Copy service-owned answer arrays into the model-facing result.
49
+ * @param result - Answer returned by the user-question service.
50
+ * @returns A detached model-facing answer payload.
51
+ */
52
+ function answerResult(result) {
53
+ return { answers: result.answers.map((answer) => ({
54
+ id: answer.id,
55
+ selected: [...answer.selected],
56
+ ...answer.custom !== void 0 ? { custom: answer.custom } : {}
57
+ })) };
58
+ }
59
+ /**
60
+ * Register the opt-in timed tool definition.
61
+ * @param ctx - Agent-scoped context receiving the tool.
62
+ * @param timeout - Default foreground wait in seconds.
63
+ */
64
+ function registerTimedAskUser(ctx, timeout = 120) {
65
+ const defaultTimeout = validateTimeout(timeout);
66
+ ctx.tools.register(defineTool({
67
+ name: "ask_user_question",
68
+ description: description$1,
69
+ parameters: {
70
+ questions: {
71
+ type: "array",
72
+ required: true,
73
+ description: "Questions to ask the user.",
74
+ items: {
75
+ type: "object",
76
+ additionalProperties: true,
77
+ properties: {
78
+ id: {
79
+ type: "string",
80
+ required: true,
81
+ description: "Stable id for this question; echoed in the answer."
82
+ },
83
+ question: {
84
+ type: "string",
85
+ required: true,
86
+ description: "The specific question to ask the user."
87
+ },
88
+ header: {
89
+ type: "string",
90
+ description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
91
+ },
92
+ options: {
93
+ type: "array",
94
+ description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
95
+ items: {
96
+ type: "object",
97
+ additionalProperties: true,
98
+ properties: {
99
+ label: {
100
+ type: "string",
101
+ required: true,
102
+ description: "Short user-facing option label."
103
+ },
104
+ description: {
105
+ type: "string",
106
+ description: "One sentence explaining the tradeoff or impact."
107
+ }
108
+ }
109
+ }
110
+ },
111
+ multi_select: {
112
+ type: "boolean",
113
+ description: "Whether the user may select more than one option. Defaults to false."
114
+ }
115
+ }
116
+ }
117
+ },
118
+ [TIMED_WAIT_PARAMETER]: {
119
+ type: "integer",
120
+ description: `Wait seconds for the entire batch (default ${defaultTimeout}); omit unless the user specifies a duration. Use -1 only when an answer is required before proceeding.`
121
+ }
122
+ },
123
+ output: {
124
+ schema: { oneOf: [{
125
+ type: "object",
126
+ additionalProperties: false,
127
+ properties: {
128
+ pending: {
129
+ type: "boolean",
130
+ enum: [true],
131
+ required: true,
132
+ description: "True when the foreground wait expired before the user submitted an answer batch. The questions remain answerable; this is not a skipped answer."
133
+ },
134
+ callId: {
135
+ type: "string",
136
+ required: true,
137
+ description: "Tool call whose unanswered questions remain pending."
138
+ },
139
+ message: {
140
+ type: "string",
141
+ required: true,
142
+ description: "How to continue while the questions stay answerable."
143
+ }
144
+ }
145
+ }, {
146
+ type: "object",
147
+ additionalProperties: false,
148
+ properties: { answers: {
149
+ type: "array",
150
+ required: true,
151
+ description: "Submitted answer batch with one item per question. A skipped question has empty selected and no custom; unlike pending, the user has completed the batch.",
152
+ items: {
153
+ type: "object",
154
+ additionalProperties: false,
155
+ properties: {
156
+ id: {
157
+ type: "string",
158
+ required: true,
159
+ description: "Stable question id echoed from the request."
160
+ },
161
+ selected: {
162
+ type: "array",
163
+ required: true,
164
+ items: { type: "string" },
165
+ description: "Selected option labels. Empty with no custom means the user explicitly skipped this question."
166
+ },
167
+ custom: {
168
+ type: "string",
169
+ description: "Optional free-form answer; omitted for a skipped question."
170
+ }
171
+ }
172
+ }
173
+ } }
174
+ }] },
175
+ render: (_args, value) => [{
176
+ type: "text",
177
+ text: JSON.stringify(value)
178
+ }]
179
+ },
180
+ async execute(args, exec) {
181
+ const timeout = validateTimeout(args.timeout ?? defaultTimeout);
182
+ validateQuestionIds(args.questions);
183
+ const request = questionRequest(args.questions, exec);
184
+ if (timeout !== -1) {
185
+ if (exec.agent === void 0) throw new Error("timed questions require a live agent");
186
+ const result = await ctx.userQuestions.askTimed({
187
+ ...request,
188
+ agent: exec.agent
189
+ }, exec.callId, timeout * 1e3);
190
+ return "pending" in result ? {
191
+ ...result,
192
+ message: pendingNotice
193
+ } : answerResult(result);
194
+ }
195
+ return answerResult(await ctx.userQuestions.ask({
196
+ ...request,
197
+ wait: { callId: exec.callId }
198
+ }));
199
+ }
200
+ }));
201
+ }
202
+ //#endregion
3
203
  //#region lib/types/index.js
4
204
  /**
5
205
  * Model-facing Consumer of the `ctx.userQuestions` capability seam.
@@ -8,10 +208,18 @@ import "@deepseek-ai/dsh-user-questions";
8
208
  *
9
209
  * @module @deepseek-ai/dsh-tool-ask-user
10
210
  */
211
+ const Config = z.object({
212
+ mode: z.union(["legacy", "timed"]).default("legacy"),
213
+ timeout: z.union([-1, z.number().step(1).min(1).max(2147483)]).default(120)
214
+ });
11
215
  const name = "tool-ask-user";
12
216
  const inject = ["tools", "userQuestions"];
13
217
  const description = "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding.";
14
- function apply(ctx) {
218
+ function apply(ctx, config = {}) {
219
+ if (config.mode === "timed") {
220
+ registerTimedAskUser(ctx, config.timeout);
221
+ return;
222
+ }
15
223
  ctx.tools.register(defineTool({
16
224
  name: "ask_user_question",
17
225
  description,
@@ -113,4 +321,4 @@ function apply(ctx) {
113
321
  }));
114
322
  }
115
323
  //#endregion
116
- export { apply, inject, name };
324
+ export { Config, apply, inject, name };
@@ -6,8 +6,17 @@
6
6
  * @module @deepseek-ai/dsh-tool-ask-user
7
7
  */
8
8
  import type { Context } from '@deepseek-ai/cordis';
9
+ import z from '@deepseek-ai/schemastery';
9
10
  import '@deepseek-ai/dsh-user-questions';
11
+ /** Cordis row selecting the tool schema and its default foreground wait. */
12
+ export interface Config {
13
+ /** Tool definition selected by this Cordis row. Defaults to the blocking legacy tool. */
14
+ mode?: 'legacy' | 'timed';
15
+ /** Foreground wait before automatic continuation. Defaults to 120 seconds. */
16
+ timeout?: number;
17
+ }
18
+ export declare const Config: z<Config>;
10
19
  export declare const name = "tool-ask-user";
11
20
  export declare const inject: string[];
12
- export declare function apply(ctx: Context): void;
21
+ export declare function apply(ctx: Context, config?: Config): void;
13
22
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Opt-in timed `ask_user_question` tool definition.
3
+ */
4
+ import type { Context } from '@deepseek-ai/cordis';
5
+ /**
6
+ * Register the opt-in timed tool definition.
7
+ * @param ctx - Agent-scoped context receiving the tool.
8
+ * @param timeout - Default foreground wait in seconds.
9
+ */
10
+ export declare function registerTimedAskUser(ctx: Context, timeout?: number): void;
11
+ //# sourceMappingURL=timed.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@deepseek-ai/dsh-tool-ask-user",
3
3
  "description": "Model-facing ask_user_question tool over the ctx.userQuestions seam",
4
- "version": "0.2.0-rc.1",
4
+ "version": "0.2.0-rc.2",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -27,17 +27,20 @@
27
27
  ],
28
28
  "license": "MIT",
29
29
  "peerDependencies": {
30
- "@deepseek-ai/dsh-agent": "0.2.0-rc.1",
31
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
32
- "@deepseek-ai/dsh-user-questions": "0.2.0-rc.1",
30
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
31
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
32
+ "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2",
33
33
  "@deepseek-ai/cordis": "~4.0.4"
34
34
  },
35
35
  "devDependencies": {
36
- "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
37
- "@deepseek-ai/dsh-tools": "0.2.0-rc.1",
38
- "@deepseek-ai/dsh-user-questions": "0.2.0-rc.1",
39
- "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.1",
40
- "@deepseek-ai/cordis": "~4.0.4",
41
- "@deepseek-ai/dsh-agent": "0.2.0-rc.1"
36
+ "@deepseek-ai/dsh-agent": "0.2.0-rc.2",
37
+ "@deepseek-ai/dsh-llm": "0.2.0-rc.2",
38
+ "@deepseek-ai/dsh-system-prompt": "0.2.0-rc.2",
39
+ "@deepseek-ai/dsh-tools": "0.2.0-rc.2",
40
+ "@deepseek-ai/dsh-user-questions": "0.2.0-rc.2",
41
+ "@deepseek-ai/cordis": "~4.0.4"
42
+ },
43
+ "dependencies": {
44
+ "@deepseek-ai/schemastery": "~3.18.4"
42
45
  }
43
46
  }