@qilin-agent/tool-ask-user 0.0.0-stage → 3.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 kkutysllb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,76 @@
1
+ # Bilingual-pair consistency record for README.md (docs/i18n/README.md): per heading
2
+ # section, a hash of its English and Chinese blocks outside code blocks and generated regions.
3
+ # After editing either side, bring the other along and re-record with:
4
+ # pnpm run verify-translation-pairing --write packages/interaction/tool-ask-user/README.md
5
+ /:
6
+ en: fd744e4beac0ee66
7
+ zh: 27288856740f5dbf
8
+ /qilin-agent-tool-ask-user:
9
+ en: a11fb3b8b82faa5c
10
+ zh: dac91e479f678f66
11
+ /qilin-agent-tool-ask-user/summary:
12
+ en: 45bb29d463d1e8d2
13
+ zh: a462610ab6e8e61b
14
+ /qilin-agent-tool-ask-user/table-of-contents:
15
+ en: d152484eb41ac6b4
16
+ zh: 09388d293f9be9cb
17
+ /qilin-agent-tool-ask-user/use-this-package:
18
+ en: ed8ec46772509d66
19
+ zh: b06aad135f7a4fa3
20
+ /qilin-agent-tool-ask-user/use-this-package/when-to-call-the-tool:
21
+ en: 5a142d88def1a4fd
22
+ zh: 2ae5573abf23d76a
23
+ /qilin-agent-tool-ask-user/use-this-package/what-the-model-gets-back:
24
+ en: d0047cb9dda88606
25
+ zh: d3432561db23f65a
26
+ /qilin-agent-tool-ask-user/use-this-package/when-the-call-fails:
27
+ en: dd379ed6282761b6
28
+ zh: fa7da19a52091403
29
+ /qilin-agent-tool-ask-user/understand-the-implementation:
30
+ en: e9b9d46be0b45ca2
31
+ zh: c02403486473cd80
32
+ /qilin-agent-tool-ask-user/understand-the-implementation/source-map:
33
+ en: 4533d1e8efbd438f
34
+ zh: 69a265056b455b96
35
+ /qilin-agent-tool-ask-user/understand-the-implementation/consumer-role:
36
+ en: 30c2b751e1b04fe4
37
+ zh: 862c1087ad9c8f66
38
+ /qilin-agent-tool-ask-user/understand-the-implementation/result-rendering:
39
+ en: 686176b2703dfa08
40
+ zh: 4ea4dab0da7f79f9
41
+ /qilin-agent-tool-ask-user/further-exploration:
42
+ en: 1bfef98f70694d16
43
+ zh: 77f0d493c56c0d0e
44
+ /qilin-agent-tool-ask-user/model-experience:
45
+ en: 215e7ba838619b7b
46
+ zh: a311da3843709f90
47
+ /qilin-agent-tool-ask-user/model-experience/tool-schema:
48
+ en: e4c7376d4f29dbd5
49
+ zh: 6b7a9eaf69c9ea53
50
+ /qilin-agent-tool-ask-user/model-experience/tool-schema/what-the-model-sees:
51
+ en: cec230e2fcc87c40
52
+ zh: 4766d0efb3804f66
53
+ /qilin-agent-tool-ask-user/model-experience/tool-schema/token-effect:
54
+ en: 97cce067584eadff
55
+ zh: c376cab39ea9df68
56
+ /qilin-agent-tool-ask-user/model-experience/tool-schema/kv-cache-effect:
57
+ en: b385b04c94496e5b
58
+ zh: 6bd8f2bc39e6f8c0
59
+ /qilin-agent-tool-ask-user/model-experience/tool-call-history-and-result:
60
+ en: 9717b983b6a857e3
61
+ zh: 611a2dadf7c0c706
62
+ /qilin-agent-tool-ask-user/model-experience/tool-call-history-and-result/what-the-model-sees:
63
+ en: d5ba86e2dbdb5b4b
64
+ zh: f5a2d6de0d985eed
65
+ /qilin-agent-tool-ask-user/model-experience/tool-call-history-and-result/token-effect:
66
+ en: 3092d4afd4ba8678
67
+ zh: cb7a7bbefdbc3a1e
68
+ /qilin-agent-tool-ask-user/model-experience/tool-call-history-and-result/kv-cache-effect:
69
+ en: f5d5f97a153f7c70
70
+ zh: 4db81881ebdcfcc4
71
+ /qilin-agent-tool-ask-user/known-limitations-and-deferred-work:
72
+ en: a0f0f15f51d5affb
73
+ zh: 41f11351e852968c
74
+ /qilin-agent-tool-ask-user/known-limitations-and-deferred-work/dev-note:
75
+ en: e67f633dc6fe7773
76
+ zh: 3bb0c20c93f16f98
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ ---
2
+ description: "The model-facing ask_user_question tool over the user-questions seam, for users and maintainers composing or debugging interactive agent surfaces."
3
+ kind: "package-reference"
4
+ ---
2
5
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
6
+ # @qilin-agent/tool-ask-user
7
+
8
+ English | [中文](README.zh.md)
9
+
10
+ ## Summary
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.
13
+
14
+ ## Table of Contents
15
+
16
+ - [Use this package](#use-this-package)
17
+ - [Understand the implementation](#understand-the-implementation)
18
+ - [Further Exploration](#further-exploration)
19
+ - [Model Experience](#model-experience)
20
+ - [Known Limitations and Deferred Work](#known-limitations-and-deferred-work)
21
+ - [Dev Note](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## Use this package
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.
29
+
30
+ ### When to call the tool
31
+
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.
33
+
34
+ ```json
35
+ {
36
+ "questions": [
37
+ {
38
+ "id": "cleanup",
39
+ "question": "Proceed with the destructive cleanup?",
40
+ "header": "Confirm",
41
+ "options": [
42
+ { "label": "Yes, delete them (Recommended)", "description": "Removes the three stale files." },
43
+ { "label": "No, keep them", "description": "Aborts the cleanup." }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ ```
49
+
50
+ ### What the model gets back
51
+
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.
53
+
54
+ ```json
55
+ { "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
56
+ ```
57
+
58
+ ### When the call fails
59
+
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.
61
+
62
+ -----
63
+
64
+ <a id="understand-the-implementation"></a>
65
+ ## Understand the implementation
66
+
67
+ <details>
68
+ <summary>Implementation internals — click to expand</summary>
69
+
70
+ The observable behavior is covered in [Use this package](#use-this-package); this section explains the tool definition and its relationship to the seam.
71
+
72
+ ### Source map
73
+
74
+ | File | Role |
75
+ |---|---|
76
+ | [`src/index.ts`](src/index.ts) | Tool registration: `ask_user_question` schema, execute path, result render |
77
+
78
+ ### Consumer role
79
+
80
+ 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.
81
+
82
+ ### Result rendering
83
+
84
+ The `render` output projects the structured value to a single text block via `JSON.stringify`, which is why the model-facing result is compact JSON rather than a richer content-block vocabulary.
85
+
86
+ </details>
87
+
88
+ -----
89
+
90
+ <a id="further-exploration"></a>
91
+ ## Further Exploration
92
+
93
+ Read these pages when the package-level contract is not enough. They move from the tool surface to the seam contract and its answerer waterfall.
94
+
95
+ - [User interaction subsystem reference](../../../docs/subsystems/user-questions.md) — the service contract, question vocabulary, and answerer waterfall behind this tool.
96
+ - [Tool catalog](../../../docs/tool-catalog.md#qilin-agenttool-ask-user) — the generated `ask_user_question` schema.
97
+ - [user-questions package](../user-questions/README.md) — the seam this tool consumes.
98
+ - [Interaction group map](../README.md) — adjacent approval and command surfaces.
99
+
100
+ -----
101
+
102
+ <a id="model-experience"></a>
103
+ ## Model Experience
104
+
105
+ ### Tool schema
106
+
107
+ #### What the model sees
108
+
109
+ The model sees the generated [`ask_user_question` schema](../../../docs/tool-catalog.md#qilin-agenttool-ask-user), including question ids, prompts, headings, options, and multi-select flags.
110
+
111
+ #### Token effect
112
+
113
+ Fixed schema cost on every request where the tool is visible.
114
+
115
+ #### KV Cache effect
116
+
117
+ Prefix-stable while the definition and visibility are unchanged. Plugin lifecycle or scoped restrictions may invalidate reuse from this schema.
118
+
119
+ ### Tool-call history and result
120
+
121
+ #### What the model sees
122
+
123
+ 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.
124
+
125
+ #### Token effect
126
+
127
+ Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human.
128
+
129
+ #### KV Cache effect
130
+
131
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
132
+
133
+ ## Known Limitations and Deferred Work
134
+
135
+ <a id="known-limitations-and-deferred-work"></a>
136
+
137
+
138
+ These limits define when the tool is a poor fit. They are current package constraints, not a UI backlog.
139
+
140
+ - **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.
141
+ - **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.
142
+ - **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.
143
+
144
+ <a id="dev-note"></a>
145
+ ### Dev Note
146
+
147
+ <details>
148
+ <summary>Working context for maintainers — click to expand</summary>
149
+
150
+ None.
151
+
152
+ </details>
package/README.zh.md ADDED
@@ -0,0 +1,152 @@
1
+ ---
2
+ description: "基于 user-questions seam 的模型侧 ask_user_question 工具;供组合或排查交互式 agent(智能体)表面的用户与维护者阅读。"
3
+ kind: "package-reference"
4
+ ---
5
+
6
+ # @qilin-agent/tool-ask-user
7
+
8
+ [English](README.md) | 中文
9
+
10
+ ## 概述
11
+
12
+ `ask_user_question` 让模型暂停工作,向用户请求确认、选择或缺失的信息。它接受一个或多个问题,并以紧凑 JSON 返回回答。调用会等待回答被接受或当前轮次被取消;如果没有回答处理器接受请求,模型会收到错误。归属于运行时其他 agent 的子级不能调用此工具,必须在最终结果中报告尚未解决的问题。本包不渲染界面或收集输入,因此调用方必须提供兼容的用户交互表面。
13
+
14
+ ## 目录
15
+
16
+ - [使用本包](#use-this-package)
17
+ - [理解实现](#understand-the-implementation)
18
+ - [进一步探索](#further-exploration)
19
+ - [模型体验](#model-experience)
20
+ - [已知限制与延期工作](#known-limitations-and-deferred-work)
21
+ - [开发备注](#dev-note)
22
+
23
+ -----
24
+
25
+ <a id="use-this-package"></a>
26
+ ## 使用本包
27
+
28
+ 凡模型应当能够暂停等待人类决定的场景,都可组合此插件:它提供 `ask_user_question` 工具,并且需要带有接受作用域请求的 answerer 的 `ctx.userQuestions` seam。没有 answerer 接受时,工具调用会以错误失败,而不是降级。
29
+
30
+ ### 何时调用该工具
31
+
32
+ 当模型需要确认、选择结果或缺失的信息才能继续时,调用 `ask_user_question`。发送一个或多个问题,每个问题携带稳定的 `id`(回答中会原样包含);推荐选项放在首位,并在标签末尾追加 `(Recommended)`。
33
+
34
+ ```json
35
+ {
36
+ "questions": [
37
+ {
38
+ "id": "cleanup",
39
+ "question": "Proceed with the destructive cleanup?",
40
+ "header": "Confirm",
41
+ "options": [
42
+ { "label": "Yes, delete them (Recommended)", "description": "Removes the three stale files." },
43
+ { "label": "No, keep them", "description": "Aborts the cleanup." }
44
+ ]
45
+ }
46
+ ]
47
+ }
48
+ ```
49
+
50
+ ### 模型得到什么
51
+
52
+ 工具为每个问题返回一个回答对象:`selected` 保存选中的选项标签,`custom` 携带自由填写的回答——对多选题补充 `selected`,对单选题覆盖它。Native 渲染器保留紧凑的 JSON 文本形式。
53
+
54
+ ```json
55
+ { "answers": [{ "id": "cleanup", "selected": ["Yes, delete them (Recommended)"] }] }
56
+ ```
57
+
58
+ ### 调用何时失败
59
+
60
+ 工具调用会阻塞到用户作答,并且只能通过当前轮次的信号取消。没有 answerer 接受、调用被中止、或调用方不是确切的存活运行时根,都会以模型在工具结果中看到的错误结算——最值得注意的是,归属于另一个 agent 的存活子级会被拒绝(`DELEGATED_CALLER`),必须在最终结果中包含尚未解决的问题或决定。
61
+
62
+ -----
63
+
64
+ <a id="understand-the-implementation"></a>
65
+ ## 理解实现
66
+
67
+ <details>
68
+ <summary>实现细节——点击展开</summary>
69
+
70
+ 可观察行为已在[使用本包](#use-this-package)中说明;本节解释工具定义及其与 seam 的关系。
71
+
72
+ ### 源码地图
73
+
74
+ | 文件 | 职责 |
75
+ |---|---|
76
+ | [`src/index.ts`](src/index.ts) | 工具注册:`ask_user_question` schema、执行路径、结果渲染 |
77
+
78
+ ### 消费方角色
79
+
80
+ 该插件以 `['tools', 'userQuestions']` 注入,在 `ctx.tools` 上注册一个 `defineTool` 条目。`execute` 把模型参数映射为 `AskUserQuestionRequest`,转发确切的调用 agent 与当前轮次的信号,并把接受的回答映射回规范的 `answers` 数组。身份检查、意图校验、waterfall(瀑布式事件)分派与错误分类由 seam 拥有;本包只做转换。
81
+
82
+ ### 结果渲染
83
+
84
+ `render` 输出把结构化值经 `JSON.stringify` 投影为单个文本块,因此模型侧结果是紧凑 JSON,而非更丰富的内容块词汇。
85
+
86
+ </details>
87
+
88
+ -----
89
+
90
+ <a id="further-exploration"></a>
91
+ ## 进一步探索
92
+
93
+ 当包级约定不够用时阅读以下页面。它们从工具表面逐步进入 seam 约定及其 answerer waterfall。
94
+
95
+ - [用户交互子系统参考](../../../docs/subsystems/user-questions.zh.md)——此工具背后的服务约定、问题词汇与 answerer waterfall。
96
+ - [工具目录](../../../docs/tool-catalog.zh.md#qilin-agenttool-ask-user)——生成的 `ask_user_question` schema。
97
+ - [user-questions 包](../user-questions/README.zh.md)——本工具消费的 seam。
98
+ - [交互组映射](../README.zh.md)——相邻的审批与命令表面。
99
+
100
+ -----
101
+
102
+ <a id="model-experience"></a>
103
+ ## 模型体验
104
+
105
+ ### 工具 schema
106
+
107
+ #### 模型看到的内容
108
+
109
+ 模型会看到生成的 [`ask_user_question` schema](../../../docs/tool-catalog.zh.md#qilin-agenttool-ask-user),其中包含问题 id、提示语、标题、选项与多选标志。
110
+
111
+ #### Token 影响
112
+
113
+ 工具可见时,每个请求都会产生固定的 schema token 开销。
114
+
115
+ #### KV Cache 影响
116
+
117
+ 只要定义和可见性保持不变,前缀即可稳定复用。插件生命周期变化或作用域限制可能会使从此 schema 起的缓存复用失效。
118
+
119
+ ### 工具调用历史与结果
120
+
121
+ #### 模型看到的内容
122
+
123
+ 模型提出的完整问题保留在 assistant 工具调用参数中。用户回答后,下一步会看到精确采用 `{"answers":[{"id":"<id>","selected":["<label>"],"custom":"<text>"}]}` 形式的紧凑 JSON;不使用 `custom` 时会省略该字段,`selected` 可以包含零个、一个或多个标签。调用等待期间的 UI 交互不属于模型上下文。
124
+
125
+ #### Token 影响
126
+
127
+ 参数和回答 JSON 是依数据而定的保留 token;等待用户时不会产生 token 开销。
128
+
129
+ #### KV Cache 影响
130
+
131
+ 仅追加;新出现的可见内容位于可复用请求前缀之后,不会使现有 KV Cache 条目失效。
132
+
133
+ ## 已知限制与延期工作
134
+
135
+ <a id="known-limitations-and-deferred-work"></a>
136
+
137
+
138
+ 这些限制说明该工具何时不合适。它们是当前包约束,不是 UI 积压事项。
139
+
140
+ - **待处理问题会阻塞工具调用,直至用户作答**:该工具未声明 `timeout-policy` 预算;取消仅沿用当前轮次的 `exec.signal`。
141
+ - **运行时中归属于其他 agent 的 subagent 不能向用户提问**:`ask_user_question` 会以 `DELEGATED_CALLER` 拒绝归属于另一个 agent 的存活子级;该子级必须在最终结果中包含尚未解决的问题或决定。持久谱系不能决定这一边界,因此带有谱系的会话恢复为运行时根后可以正常提问。
142
+ - **Native 回答渲染为 JSON 文本**:规范值仍为结构化数据,但模型侧结果使用紧凑 JSON,而非更丰富的内容块词汇。
143
+
144
+ <a id="dev-note"></a>
145
+ ### 开发备注
146
+
147
+ <details>
148
+ <summary>维护者的工作上下文——点击展开</summary>
149
+
150
+ 无。
151
+
152
+ </details>
package/lib/index.js ADDED
@@ -0,0 +1,324 @@
1
+ import { defineTool } from "@qilin-agent/tools";
2
+ import z from "@qilin-agent/schemastery";
3
+ import { TIMED_WAIT_PARAMETER } from "@qilin-agent/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
203
+ //#region lib/types/index.js
204
+ /**
205
+ * Model-facing Consumer of the `ctx.userQuestions` capability seam.
206
+ * The tool pauses until a UI provider returns a human answer, then feeds that
207
+ * answer back into the agent loop as an ordinary tool result.
208
+ *
209
+ * @module @qilin-agent/tool-ask-user
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
+ });
215
+ const name = "tool-ask-user";
216
+ const inject = ["tools", "userQuestions"];
217
+ const description = "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding.";
218
+ function apply(ctx, config = {}) {
219
+ if (config.mode === "timed") {
220
+ registerTimedAskUser(ctx, config.timeout);
221
+ return;
222
+ }
223
+ ctx.tools.register(defineTool({
224
+ name: "ask_user_question",
225
+ description,
226
+ parameters: { questions: {
227
+ type: "array",
228
+ required: true,
229
+ description: "Questions to ask the user before continuing.",
230
+ items: {
231
+ type: "object",
232
+ additionalProperties: true,
233
+ properties: {
234
+ id: {
235
+ type: "string",
236
+ required: true,
237
+ description: "Stable id for this question; echoed in the answer."
238
+ },
239
+ question: {
240
+ type: "string",
241
+ required: true,
242
+ description: "The specific question to ask the user."
243
+ },
244
+ header: {
245
+ type: "string",
246
+ description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
247
+ },
248
+ options: {
249
+ type: "array",
250
+ description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
251
+ items: {
252
+ type: "object",
253
+ additionalProperties: true,
254
+ properties: {
255
+ label: {
256
+ type: "string",
257
+ required: true,
258
+ description: "Short user-facing option label."
259
+ },
260
+ description: {
261
+ type: "string",
262
+ description: "One sentence explaining the tradeoff or impact."
263
+ }
264
+ }
265
+ }
266
+ },
267
+ multi_select: {
268
+ type: "boolean",
269
+ description: "Whether the user may select more than one option. Defaults to false."
270
+ }
271
+ }
272
+ }
273
+ } },
274
+ output: {
275
+ schema: {
276
+ type: "object",
277
+ additionalProperties: false,
278
+ properties: { answers: {
279
+ type: "array",
280
+ required: true,
281
+ items: {
282
+ type: "object",
283
+ additionalProperties: false,
284
+ properties: {
285
+ id: {
286
+ type: "string",
287
+ required: true
288
+ },
289
+ selected: {
290
+ type: "array",
291
+ required: true,
292
+ items: { type: "string" }
293
+ },
294
+ custom: { type: "string" }
295
+ }
296
+ }
297
+ } }
298
+ },
299
+ render: (_args, value) => [{
300
+ type: "text",
301
+ text: JSON.stringify(value)
302
+ }]
303
+ },
304
+ async execute(args, exec) {
305
+ return { answers: (await ctx.userQuestions.ask({
306
+ questions: args.questions.map((question) => ({
307
+ id: question.id,
308
+ question: question.question,
309
+ ...question.header !== void 0 ? { header: question.header } : {},
310
+ ...question.options !== void 0 ? { options: question.options } : {},
311
+ ...question.multi_select !== void 0 ? { multiSelect: question.multi_select } : {}
312
+ })),
313
+ ...exec.agent !== void 0 ? { agent: exec.agent } : {},
314
+ signal: exec.signal
315
+ })).answers.map((answer) => ({
316
+ id: answer.id,
317
+ selected: [...answer.selected],
318
+ ...answer.custom !== void 0 ? { custom: answer.custom } : {}
319
+ })) };
320
+ }
321
+ }));
322
+ }
323
+ //#endregion
324
+ export { Config, apply, inject, name };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Model-facing Consumer of the `ctx.userQuestions` capability seam.
3
+ * The tool pauses until a UI provider returns a human answer, then feeds that
4
+ * answer back into the agent loop as an ordinary tool result.
5
+ *
6
+ * @module @qilin-agent/tool-ask-user
7
+ */
8
+ import type { Context } from '@qilin-agent/kylin';
9
+ import z from '@qilin-agent/schemastery';
10
+ import '@qilin-agent/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>;
19
+ export declare const name = "tool-ask-user";
20
+ export declare const inject: string[];
21
+ export declare function apply(ctx: Context, config?: Config): void;
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 '@qilin-agent/kylin';
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,6 +1,49 @@
1
1
  {
2
2
  "name": "@qilin-agent/tool-ask-user",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "description": "Model-facing ask_user_question tool over the ctx.userQuestions seam",
4
+ "version": "3.1.4",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/kkutysllb/QiLin.git",
11
+ "directory": "packages/interaction/tool-ask-user"
12
+ },
13
+ "type": "module",
14
+ "engines": {
15
+ "node": "^22.19.0 || >=24.0.0"
16
+ },
17
+ "main": "lib/index.js",
18
+ "types": "lib/types/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./lib/types/index.d.ts",
22
+ "default": "./lib/index.js"
23
+ },
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib/index.js",
29
+ "lib/types/**/*.d.ts"
30
+ ],
31
+ "license": "MIT",
32
+ "peerDependencies": {
33
+ "@qilin-agent/agent": "3.1.4",
34
+ "@qilin-agent/tools": "3.1.4",
35
+ "@qilin-agent/user-questions": "3.1.4",
36
+ "@qilin-agent/kylin": "~4.0.3"
37
+ },
38
+ "devDependencies": {
39
+ "@qilin-agent/agent": "3.1.4",
40
+ "@qilin-agent/tools": "3.1.4",
41
+ "@qilin-agent/system-prompt": "3.1.4",
42
+ "@qilin-agent/user-questions": "3.1.4",
43
+ "@qilin-agent/llm": "3.1.4",
44
+ "@qilin-agent/kylin": "~4.0.3"
45
+ },
46
+ "dependencies": {
47
+ "@qilin-agent/schemastery": "~3.18.3"
48
+ }
6
49
  }