@hydraharness/harness-tool-ask-user 0.1.1-rc.6

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 DeepSeek
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.
package/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # @hydraharness/harness-tool-ask-user
2
+
3
+ Model-facing `ask_user_question` tool over `ctx.userQuestions`. It lets the model ask the human a concise question for a user-owned choice or missing information before continuing, including when approval prompts are disabled. Execution permission follows the current sandbox and approval policy through the acting tool's approval flow; a question answer grants no permission and cannot override a denial. When the request clearly implies an available tool or execution path, the model should choose it itself instead of asking the user to choose between tools or implementation options.
4
+
5
+ ## Tool
6
+
7
+ `ask_user_question` accepts:
8
+
9
+ - `questions` — required non-empty array of question objects.
10
+ - `id` — required stable id on each question, echoed in the answer.
11
+ - `question` — required question text for each question.
12
+ - `header` — optional short heading.
13
+ - `options` — optional choices with `label` and `description`. If recommending a choice, put it first and append `(Recommended)` to that label.
14
+ - `multi_select` — whether that question may return more than one selected option.
15
+
16
+ The tool calls `ctx.userQuestions.ask()` and returns canonical `{ answers: [{ id, selected, custom? }] }`. `selected` contains option labels; `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 `{ "answers": [{ "id": "...", "selected": ["..."], "custom": "..." }] }`.
17
+
18
+ ## Role
19
+
20
+ This is the Consumer package for the user-questions seam. It does not render UI and does not know how input is collected; it only translates model arguments into `AskUserQuestionRequest` and returns the human answer to the agent loop.
21
+
22
+ ## Model Experience
23
+
24
+ ### Tool schema
25
+
26
+ #### What the model sees
27
+
28
+ The model sees the generated [`ask_user_question` schema](../../../docs/tool-catalog.md#hydraharness-tool-ask-user), including question ids, prompts, headings, options, and multi-select flags. It reserves the tool for a concrete task blocked by a user-owned choice or a fact normal inspection cannot establish; execution permission requests, greetings, casual chat, vague requests, and generic action/tool menus are excluded.
29
+
30
+ #### Token effect
31
+
32
+ Fixed schema cost on every request where the tool is visible.
33
+
34
+ #### KV Cache effect
35
+
36
+ Prefix-stable while the definition and visibility are unchanged. Plugin lifecycle or scoped restrictions may invalidate reuse from this schema.
37
+
38
+ ### Tool-call history and result
39
+
40
+ #### What the model sees
41
+
42
+ 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.
43
+
44
+ #### Token effect
45
+
46
+ Arguments and answer JSON are data-dependent retained tokens; there is no token cost while waiting for the human.
47
+
48
+ #### KV Cache effect
49
+
50
+ Append-only; newly visible content follows the reusable request prefix and does not invalidate existing KV-cache entries.
51
+
52
+ ## Known Limitations and Deferred Work
53
+
54
+ - **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.
55
+ - **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.
56
+ - **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.
package/lib/index.js ADDED
@@ -0,0 +1,171 @@
1
+ import { defineTool } from "@hydraharness/harness-tools";
2
+ import "@hydraharness/harness-user-questions";
3
+ //#region lib/types/index.js
4
+ /**
5
+ * Model-facing Consumer of the `ctx.userQuestions` capability seam.
6
+ * The tool pauses until a UI provider returns a human answer, then feeds that
7
+ * answer back into the agent loop as an ordinary tool result.
8
+ *
9
+ * @module @hydraharness/harness-tool-ask-user
10
+ */
11
+ const name = "tool-ask-user";
12
+ const inject = ["tools", "userQuestions"];
13
+ const description = "Ask the user a concise question only when a genuine user-owned choice or a material fact unavailable through normal inspection blocks a concrete task. Do not use this to request permission for an action. Follow the current sandbox and approval policy and the acting tool's approval flow; a question answer does not grant permission or override a denial. Necessary clarification questions remain available when approval prompts are disabled. Do not use this for greetings, acknowledgements, casual chat, vague requests, or generic action, task, or tool menus. When the request clearly implies an available tool or execution path, inspect the context, choose it yourself, and proceed; do not ask the user to choose between tools or implementation options. Send one or more questions, each with a stable id that will be echoed in the answer.";
14
+ const GENERIC_IMPROVEMENT_LABELS = new Set([
15
+ "performance",
16
+ "features",
17
+ "reliability",
18
+ "safety",
19
+ "security",
20
+ "latency",
21
+ "quality",
22
+ "ux",
23
+ "user experience",
24
+ "explainability",
25
+ "scalability",
26
+ "hiệu suất",
27
+ "tính năng",
28
+ "độ tin cậy",
29
+ "an toàn",
30
+ "bảo mật",
31
+ "độ trễ",
32
+ "chất lượng",
33
+ "trải nghiệm người dùng",
34
+ "khả năng giải thích",
35
+ "khả năng mở rộng"
36
+ ]);
37
+ function latestDirectUserText(agent) {
38
+ const events = agent?.session.events;
39
+ if (events === void 0) return;
40
+ for (let index = events.length - 1; index >= 0; index -= 1) {
41
+ const event = events[index];
42
+ if (event?.type !== "user/message" || event.data.source.kind !== "user") continue;
43
+ return event.data.content.flatMap((block) => block.type === "text" ? [block.text] : []).join("\n");
44
+ }
45
+ }
46
+ function isGenericImprovementMenu(value) {
47
+ if (value === null || typeof value !== "object") return false;
48
+ const questions = value.questions;
49
+ if (!Array.isArray(questions)) return false;
50
+ return questions.some((question) => {
51
+ if (question === null || typeof question !== "object") return false;
52
+ const questionText = question.question;
53
+ const options = question.options;
54
+ const labels = Array.isArray(options) ? options.flatMap((option) => {
55
+ if (option === null || typeof option !== "object") return [];
56
+ const label = option.label;
57
+ return typeof label === "string" ? [label.trim().toLowerCase()] : [];
58
+ }) : [];
59
+ const menuText = [typeof questionText === "string" ? questionText : "", ...labels].join("\n").toLowerCase();
60
+ return [...GENERIC_IMPROVEMENT_LABELS].filter((label) => menuText.includes(label)).length >= 3;
61
+ });
62
+ }
63
+ function genericImprovementMenuReason(agent, args) {
64
+ const userText = latestDirectUserText(agent);
65
+ if (userText === void 0 || !/(?:\bimprov(?:e|ing|ement)\b|\benhanc(?:e|ing|ement)\b|\boptimi[sz](?:e|ing|ation)\b|cải thiện|nâng cấp|tối ưu)/iu.test(userText) || !isGenericImprovementMenu(args)) return;
66
+ return "ask_user_question cannot offer a generic improvement-category menu. Inspect the available context and take the smallest safe useful step; ask only for a material fact normal inspection cannot establish or a genuinely user-owned choice.";
67
+ }
68
+ function apply(ctx) {
69
+ ctx.tools.guard((exec) => exec.name === "ask_user_question" ? genericImprovementMenuReason(exec.agent, exec.arguments) : void 0);
70
+ ctx.tools.register(defineTool({
71
+ name: "ask_user_question",
72
+ description,
73
+ parameters: { questions: {
74
+ type: "array",
75
+ required: true,
76
+ description: "Questions to ask the user before continuing.",
77
+ items: {
78
+ type: "object",
79
+ additionalProperties: true,
80
+ properties: {
81
+ id: {
82
+ type: "string",
83
+ required: true,
84
+ description: "Stable id for this question; echoed in the answer."
85
+ },
86
+ question: {
87
+ type: "string",
88
+ required: true,
89
+ description: "The specific question to ask the user."
90
+ },
91
+ header: {
92
+ type: "string",
93
+ description: "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"."
94
+ },
95
+ options: {
96
+ type: "array",
97
+ description: "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.",
98
+ items: {
99
+ type: "object",
100
+ additionalProperties: true,
101
+ properties: {
102
+ label: {
103
+ type: "string",
104
+ required: true,
105
+ description: "Short user-facing option label."
106
+ },
107
+ description: {
108
+ type: "string",
109
+ description: "One sentence explaining the tradeoff or impact."
110
+ }
111
+ }
112
+ }
113
+ },
114
+ multi_select: {
115
+ type: "boolean",
116
+ description: "Whether the user may select more than one option. Defaults to false."
117
+ }
118
+ }
119
+ }
120
+ } },
121
+ output: {
122
+ schema: {
123
+ type: "object",
124
+ additionalProperties: false,
125
+ properties: { answers: {
126
+ type: "array",
127
+ required: true,
128
+ items: {
129
+ type: "object",
130
+ additionalProperties: false,
131
+ properties: {
132
+ id: {
133
+ type: "string",
134
+ required: true
135
+ },
136
+ selected: {
137
+ type: "array",
138
+ required: true,
139
+ items: { type: "string" }
140
+ },
141
+ custom: { type: "string" }
142
+ }
143
+ }
144
+ } }
145
+ },
146
+ render: (_args, value) => [{
147
+ type: "text",
148
+ text: JSON.stringify(value)
149
+ }]
150
+ },
151
+ async execute(args, exec) {
152
+ return { answers: (await ctx.userQuestions.ask({
153
+ questions: args.questions.map((question) => ({
154
+ id: question.id,
155
+ question: question.question,
156
+ ...question.header !== void 0 ? { header: question.header } : {},
157
+ ...question.options !== void 0 ? { options: question.options } : {},
158
+ ...question.multi_select !== void 0 ? { multiSelect: question.multi_select } : {}
159
+ })),
160
+ ...exec.agent !== void 0 ? { agent: exec.agent } : {},
161
+ signal: exec.signal
162
+ })).answers.map((answer) => ({
163
+ id: answer.id,
164
+ selected: [...answer.selected],
165
+ ...answer.custom !== void 0 ? { custom: answer.custom } : {}
166
+ })) };
167
+ }
168
+ }));
169
+ }
170
+ //#endregion
171
+ export { apply, inject, name };
@@ -0,0 +1,23 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@hydraharness/harness-tool-ask-user`.
4
+ * @module @hydraharness/harness-tool-ask-user/invariant
5
+ */
6
+ const PACKAGE_NAME = "@hydraharness/harness-tool-ask-user";
7
+ /** Cordis companion plugin name. */
8
+ const name = "tool-ask-user-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ /**
12
+ * No runtime invariant: this model-facing adapter has no independent lifecycle stream; execution
13
+ * relations are owned by the capability seam it calls.
14
+ */
15
+ const install = () => {};
16
+ /**
17
+ * Register this package's invariant companion.
18
+ * @param ctx - Cordis context carrying the invariant service.
19
+ * @returns the installed registration's disposer after setup succeeds.
20
+ */
21
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
22
+ //#endregion
23
+ export { apply, inject, name };
@@ -0,0 +1,13 @@
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 @hydraharness/harness-tool-ask-user
7
+ */
8
+ import type { Context } from '@hydraharness/cordis';
9
+ import '@hydraharness/harness-user-questions';
10
+ export declare const name = "tool-ask-user";
11
+ export declare const inject: string[];
12
+ export declare function apply(ctx: Context): void;
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Package-owned invariant companion for `@hydraharness/harness-tool-ask-user`.
3
+ * @module @hydraharness/harness-tool-ask-user/invariant
4
+ */
5
+ import type { Context } from '@hydraharness/cordis';
6
+ /** Cordis companion plugin name. */
7
+ export declare const name = "tool-ask-user-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,55 @@
1
+ {
2
+ "name": "@hydraharness/harness-tool-ask-user",
3
+ "description": "Model-facing ask_user_question tool over the ctx.userQuestions seam",
4
+ "hydra": {
5
+ "plugin": {
6
+ "application": "Let the agent ask structured questions and wait for the user's answers."
7
+ }
8
+ },
9
+ "version": "0.1.1-rc.6",
10
+ "publishConfig": {
11
+ "access": "public"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/MaiHongPhong1902/Hydra-Harness.git",
16
+ "directory": "packages/interaction/tool-ask-user"
17
+ },
18
+ "type": "module",
19
+ "main": "lib/index.js",
20
+ "types": "lib/types/index.d.ts",
21
+ "exports": {
22
+ ".": {
23
+ "types": "./lib/types/index.d.ts",
24
+ "default": "./lib/index.js"
25
+ },
26
+ "./invariant": {
27
+ "types": "./lib/types/invariant.d.ts",
28
+ "default": "./lib/invariant.js"
29
+ },
30
+ "./src/*": "./src/*",
31
+ "./package.json": "./package.json"
32
+ },
33
+ "files": [
34
+ "lib/index.js",
35
+ "lib/invariant.js",
36
+ "lib/types/**/*.d.ts"
37
+ ],
38
+ "license": "MIT",
39
+ "peerDependencies": {
40
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
41
+ "@hydraharness/harness-agent": "^0.1.1-rc.6",
42
+ "@hydraharness/harness-user-questions": "^0.1.1-rc.6",
43
+ "@hydraharness/cordis": "^4.0.2",
44
+ "@hydraharness/harness-tools": "^0.1.1-rc.6"
45
+ },
46
+ "devDependencies": {
47
+ "@hydraharness/harness-agent": "^0.1.1-rc.6",
48
+ "@hydraharness/harness-llm": "^0.1.1-rc.6",
49
+ "@hydraharness/harness-invariants": "^0.1.1-rc.6",
50
+ "@hydraharness/harness-system-prompt": "^0.1.1-rc.6",
51
+ "@hydraharness/harness-user-questions": "^0.1.1-rc.6",
52
+ "@hydraharness/harness-tools": "^0.1.1-rc.6",
53
+ "@hydraharness/cordis": "^4.0.2"
54
+ }
55
+ }