@hank-warren/pi-ask-user-question 0.6.0 → 0.6.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # @hank-warren/pi-ask-user-question
2
2
 
3
+ ## 0.6.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 1035138: Report a waiting question to Herdr as `blocked`.
8
+
9
+ `ask_user_question` and Plan Mode's `plan_mode_question` now emit the same
10
+ `herdr:blocked` event pi-auto-permissions emits for an approval prompt, labelled
11
+ `question` / `plan question` and cleared in a `finally`, so Herdr's pi
12
+ integration shows a session waiting on a question as `blocked` rather than
13
+ `working`. A supervising agent in another pane can wait on that state and answer
14
+ the dialog. No-op outside Herdr.
15
+
3
16
  ## 0.6.0
4
17
 
5
18
  ### Minor Changes
package/README.md CHANGED
@@ -123,6 +123,10 @@ pi.events.on("hank:ask-user:prompt", ({ questions }) => {
123
123
  Channel names are immutable and payloads are append-only — see
124
124
  [`events.ts`](./events.ts) for the full stability policy.
125
125
 
126
+ ## Changelog
127
+
128
+ See [CHANGELOG.md](CHANGELOG.md) for release history.
129
+
126
130
  ## License
127
131
 
128
132
  MIT
@@ -65,9 +65,23 @@ function emitPrompt(pi: ExtensionAPI, params: AskUserParams): void {
65
65
  pi.events.emit(ASK_USER_PROMPT_EVENT, payload);
66
66
  }
67
67
 
68
+ /**
69
+ * Label Herdr shows for a pane waiting on this dialog. A supervising agent
70
+ * reads it to tell a question from an Auto Permissions prompt (whose label
71
+ * is the gate name, e.g. `shell command`).
72
+ */
73
+ export const HERDR_BLOCKED_LABEL = "question";
74
+
68
75
  function emitBlocked(pi: ExtensionAPI, active: boolean): void {
69
76
  const payload: AskUserBlockedEventPayload = { active };
70
77
  pi.events.emit(ASK_USER_BLOCKED_EVENT, payload);
78
+ // Herdr's pi integration turns `herdr:blocked` into `agent_status: "blocked"`,
79
+ // which is how an orchestrator in another pane learns this session is
80
+ // waiting on a human rather than working. Same contract pi-auto-permissions
81
+ // uses (`setHerdrBlocked`); duplicated rather than imported, because a
82
+ // questionnaire must not pull in the permissions engine. No-op outside Herdr.
83
+ if (process.env.HERDR_ENV !== "1") return;
84
+ pi.events.emit("herdr:blocked", active ? { active: true, label: HERDR_BLOCKED_LABEL } : { active: false });
71
85
  }
72
86
 
73
87
  export function registerTool(pi: ExtensionAPI): void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hank-warren/pi-ask-user-question",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "Structured questionnaire tool for Pi with numbered options, digit hotkeys and Tab-to-comment, composed from the shared permission-selector component.",
5
5
  "type": "module",
6
6
  "keywords": [
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "homepage": "https://github.com/hank-warren/pi-extensions/tree/main/packages/pi-ask-user-question#readme",
26
26
  "engines": {
27
- "node": ">=18.0.0"
27
+ "node": ">=22.19.0"
28
28
  },
29
29
  "pi": {
30
30
  "extensions": [
package/questionnaire.ts CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  type QuestionnaireResult,
26
26
  } from "./tool/schema.ts";
27
27
 
28
- export interface SelectableRow {
28
+ interface SelectableRow {
29
29
  value: string;
30
30
  label: string;
31
31
  description?: string;
package/tool/envelope.ts CHANGED
@@ -14,7 +14,7 @@ export const DECLINE_MESSAGE = "User declined to answer questions";
14
14
  export const ENVELOPE_PREFIX = "User has answered your questions:";
15
15
  export const ENVELOPE_SUFFIX = "You can now continue with the user's answers in mind.";
16
16
 
17
- export interface ToolResult {
17
+ interface ToolResult {
18
18
  content: Array<{ type: "text"; text: string }>;
19
19
  details: QuestionnaireResult;
20
20
  }
@@ -28,7 +28,7 @@ export function buildToolResult(text: string, details: QuestionnaireResult): Too
28
28
  * preview and the user's note appended when present. Order and wording match
29
29
  * rpiv's envelope and are pinned by tests.
30
30
  */
31
- export function buildAnswerSegment(a: QuestionAnswer): string {
31
+ function buildAnswerSegment(a: QuestionAnswer): string {
32
32
  const parts: string[] = [`"${a.question}"="${a.answer}"`];
33
33
  if (a.preview && a.preview.length > 0) parts.push(`selected preview: ${a.preview}`);
34
34
  if (a.notes && a.notes.length > 0) parts.push(`user notes: ${a.notes}`);
package/tool/schema.ts CHANGED
@@ -12,13 +12,13 @@ import { Type } from "typebox";
12
12
  export const TOOL_NAME = "ask_user_question";
13
13
 
14
14
  export const MIN_OPTIONS = 2;
15
- export const MAX_OPTIONS = 4;
15
+ const MAX_OPTIONS = 4;
16
16
  /**
17
17
  * Multi-select questions get a larger cap. Checkboxes are a shortlist UI, not a
18
18
  * pick-one UI, so six is where a list stops fitting comfortably above the input
19
19
  * dock — not where it stops being a decision.
20
20
  */
21
- export const MAX_MULTI_OPTIONS = 6;
21
+ const MAX_MULTI_OPTIONS = 6;
22
22
  export const MIN_QUESTIONS = 1;
23
23
  export const MAX_QUESTIONS = 4;
24
24
 
@@ -42,7 +42,7 @@ export const CUSTOM_ANSWER_VALUE = "\u0000custom-answer";
42
42
  /** Separator joining a multi-select answer's labels into `answer` (spec §5.4). */
43
43
  export const MULTI_SELECT_JOIN = ", ";
44
44
 
45
- export const OptionSchema = Type.Object({
45
+ const OptionSchema = Type.Object({
46
46
  label: Type.String({
47
47
  description:
48
48
  "The display text for this option that the user will see and select. Aim for 1-5 words, but there is no hard limit: a longer label is fine when it genuinely helps, and long labels wrap in the dialog rather than being rejected.",
@@ -59,7 +59,7 @@ export const OptionSchema = Type.Object({
59
59
  ),
60
60
  });
61
61
 
62
- export const QuestionSchema = Type.Object({
62
+ const QuestionSchema = Type.Object({
63
63
  question: Type.String({
64
64
  description:
65
65
  "The complete question to ask the user. Should be clear, specific, and end with a question mark.",
@@ -91,13 +91,13 @@ export const QuestionParamsSchema = Type.Object({
91
91
  }),
92
92
  });
93
93
 
94
- export interface OptionParams {
94
+ interface OptionParams {
95
95
  label: string;
96
96
  description: string;
97
97
  preview?: string;
98
98
  }
99
99
 
100
- export interface QuestionParams {
100
+ interface QuestionParams {
101
101
  question: string;
102
102
  header: string;
103
103
  /** Checkbox mode: the user may check several options. Default false. */
package/tool/validate.ts CHANGED
@@ -24,7 +24,7 @@ import {
24
24
  RESERVED_LABELS,
25
25
  } from "./schema.ts";
26
26
 
27
- export type ValidationCode =
27
+ type ValidationCode =
28
28
  | "bad_question_count"
29
29
  | "bad_option_count"
30
30
  | "header_too_long"
@@ -33,7 +33,7 @@ export type ValidationCode =
33
33
  | "duplicate_label"
34
34
  | "empty_question";
35
35
 
36
- export interface ValidationError {
36
+ interface ValidationError {
37
37
  code: ValidationCode;
38
38
  message: string;
39
39
  }
package/view/dialog.ts CHANGED
@@ -64,11 +64,11 @@ import type { QuestionnaireSession } from "../questionnaire.ts";
64
64
  import type { QuestionnaireResult } from "../tool/schema.ts";
65
65
 
66
66
  /** Structural subset of pi's Theme used here; keeps the view unit-testable. */
67
- export interface DialogTheme {
67
+ interface DialogTheme {
68
68
  fg(role: string, text: string): string;
69
69
  }
70
70
 
71
- export interface DialogOptions {
71
+ interface DialogOptions {
72
72
  session: QuestionnaireSession;
73
73
  theme?: DialogTheme;
74
74
  /** Called exactly once with the final outcome. */