@mrclrchtr/supi-ask-user 1.3.1 → 1.5.0
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.md +163 -67
- package/node_modules/@mrclrchtr/supi-core/README.md +52 -41
- package/node_modules/@mrclrchtr/supi-core/package.json +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/api.ts +15 -13
- package/node_modules/@mrclrchtr/supi-core/src/{config-settings.ts → config/config-settings.ts} +2 -2
- package/node_modules/@mrclrchtr/supi-core/src/{context-provider-registry.ts → context/context-provider-registry.ts} +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/extension.ts +1 -1
- package/node_modules/@mrclrchtr/supi-core/src/index.ts +15 -13
- package/node_modules/@mrclrchtr/supi-core/src/path-utils.ts +40 -0
- package/node_modules/@mrclrchtr/supi-core/src/registry-utils.ts +42 -10
- package/node_modules/@mrclrchtr/supi-core/src/{settings-registry.ts → settings/settings-registry.ts} +1 -1
- package/package.json +2 -2
- package/src/api.ts +19 -0
- package/src/ask-user.ts +71 -131
- package/src/index.ts +23 -1
- package/src/normalize.ts +153 -142
- package/src/render/result.ts +102 -0
- package/src/render/transcript.ts +65 -0
- package/src/render/tree-summary.ts +10 -0
- package/src/schema.ts +41 -38
- package/src/session/controller.ts +281 -0
- package/src/session/lock.ts +19 -0
- package/src/tool/guidance.ts +15 -0
- package/src/types.ts +56 -55
- package/src/ui/choose-renderer.ts +11 -0
- package/src/ui/overlay-actions.ts +42 -0
- package/src/ui/overlay-component.ts +400 -0
- package/src/ui/overlay-render.ts +219 -0
- package/src/ui/overlay-view.ts +313 -0
- package/src/ui/overlay.ts +28 -0
- package/src/ui/types.ts +38 -0
- package/src/flow.ts +0 -224
- package/src/format.ts +0 -66
- package/src/render/ui-rich-render-editor.ts +0 -51
- package/src/render/ui-rich-render-env.ts +0 -15
- package/src/render/ui-rich-render-footer.ts +0 -55
- package/src/render/ui-rich-render-markdown.ts +0 -33
- package/src/render/ui-rich-render-notes.ts +0 -80
- package/src/render/ui-rich-render-types.ts +0 -17
- package/src/render/ui-rich-render.ts +0 -323
- package/src/render.ts +0 -95
- package/src/result.ts +0 -90
- package/src/ui/ui-rich-handlers.ts +0 -369
- package/src/ui/ui-rich-inline.ts +0 -77
- package/src/ui/ui-rich-state.ts +0 -179
- package/src/ui/ui-rich.ts +0 -144
- /package/node_modules/@mrclrchtr/supi-core/src/{config.ts → config/config.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-messages.ts → context/context-messages.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{context-tag.ts → context/context-tag.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-command.ts → settings/settings-command.ts} +0 -0
- /package/node_modules/@mrclrchtr/supi-core/src/{settings-ui.ts → settings/settings-ui.ts} +0 -0
|
@@ -5,8 +5,20 @@
|
|
|
5
5
|
// Without this, each symlink path gets its own module copy and its own Map,
|
|
6
6
|
// so registrations from one instance are invisible to consumers in another.
|
|
7
7
|
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
|
|
8
10
|
const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
|
|
9
11
|
|
|
12
|
+
function getGlobalRegistryMap<T>(name: string): Map<string, T> {
|
|
13
|
+
const key = Symbol.for(SYMBOL_PREFIX + name);
|
|
14
|
+
let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
|
|
15
|
+
if (!map) {
|
|
16
|
+
map = new Map<string, T>();
|
|
17
|
+
(globalThis as Record<symbol, unknown>)[key] = map;
|
|
18
|
+
}
|
|
19
|
+
return map;
|
|
20
|
+
}
|
|
21
|
+
|
|
10
22
|
/**
|
|
11
23
|
* Create a named registry backed by `globalThis` + `Symbol.for`.
|
|
12
24
|
*
|
|
@@ -18,16 +30,7 @@ const SYMBOL_PREFIX = "@mrclrchtr/supi-core/";
|
|
|
18
30
|
* @returns An object with `register`, `getAll`, and `clear` functions.
|
|
19
31
|
*/
|
|
20
32
|
export function createRegistry<T>(name: string) {
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const getMap = (): Map<string, T> => {
|
|
24
|
-
let map = (globalThis as Record<symbol, unknown>)[key] as Map<string, T> | undefined;
|
|
25
|
-
if (!map) {
|
|
26
|
-
map = new Map<string, T>();
|
|
27
|
-
(globalThis as Record<symbol, unknown>)[key] = map;
|
|
28
|
-
}
|
|
29
|
-
return map;
|
|
30
|
-
};
|
|
33
|
+
const getMap = (): Map<string, T> => getGlobalRegistryMap<T>(name);
|
|
31
34
|
|
|
32
35
|
return {
|
|
33
36
|
/**
|
|
@@ -52,3 +55,32 @@ export function createRegistry<T>(name: string) {
|
|
|
52
55
|
},
|
|
53
56
|
};
|
|
54
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Create a named session-state registry keyed by normalized cwd.
|
|
61
|
+
*
|
|
62
|
+
* This helper is intended for session-scoped runtime services that should be
|
|
63
|
+
* shared across duplicate jiti module instances while keeping package-specific
|
|
64
|
+
* state unions and convenience wrappers local to the calling package.
|
|
65
|
+
*/
|
|
66
|
+
export function createSessionStateRegistry<TState>(name: string) {
|
|
67
|
+
const getMap = (): Map<string, TState> => getGlobalRegistryMap<TState>(name);
|
|
68
|
+
const normalizeCwd = (cwd: string): string => path.resolve(cwd);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
/** Get the current state for one session cwd. */
|
|
72
|
+
get: (cwd: string): TState | undefined => {
|
|
73
|
+
return getMap().get(normalizeCwd(cwd));
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/** Store the current state for one session cwd. */
|
|
77
|
+
set: (cwd: string, state: TState): void => {
|
|
78
|
+
getMap().set(normalizeCwd(cwd), state);
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
/** Clear the current state for one session cwd. */
|
|
82
|
+
clear: (cwd: string): void => {
|
|
83
|
+
getMap().delete(normalizeCwd(cwd));
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
package/node_modules/@mrclrchtr/supi-core/src/{settings-registry.ts → settings/settings-registry.ts}
RENAMED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// factory function. The generic settings UI reads them via `getRegisteredSettings()`.
|
|
5
5
|
|
|
6
6
|
import type { SettingItem } from "@earendil-works/pi-tui";
|
|
7
|
-
import { createRegistry } from "
|
|
7
|
+
import { createRegistry } from "../registry-utils.ts";
|
|
8
8
|
|
|
9
9
|
export type SettingsScope = "project" | "global";
|
|
10
10
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mrclrchtr/supi-ask-user",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "SuPi ask-user extension — rich questionnaire UI for structured agent-user decisions",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
],
|
|
22
22
|
"main": "src/api.ts",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@mrclrchtr/supi-core": "1.
|
|
24
|
+
"@mrclrchtr/supi-core": "1.5.0"
|
|
25
25
|
},
|
|
26
26
|
"bundledDependencies": [
|
|
27
27
|
"@mrclrchtr/supi-core"
|
package/src/api.ts
CHANGED
|
@@ -1 +1,20 @@
|
|
|
1
1
|
export { default } from "./ask-user.ts";
|
|
2
|
+
export { AskUserValidationError, normalizeQuestionnaire } from "./normalize.ts";
|
|
3
|
+
export { AskUserParamsSchema } from "./schema.ts";
|
|
4
|
+
export { AskUserController } from "./session/controller.ts";
|
|
5
|
+
export { ActiveQuestionnaireLock } from "./session/lock.ts";
|
|
6
|
+
export type {
|
|
7
|
+
Answer,
|
|
8
|
+
AskUserDetails,
|
|
9
|
+
AskUserErrorDetails,
|
|
10
|
+
AskUserOutcome,
|
|
11
|
+
AskUserStatus,
|
|
12
|
+
AskUserToolDetails,
|
|
13
|
+
ChoiceAnswer,
|
|
14
|
+
CustomAnswer,
|
|
15
|
+
NormalizedChoiceQuestion,
|
|
16
|
+
NormalizedQuestion,
|
|
17
|
+
NormalizedQuestionnaire,
|
|
18
|
+
NormalizedTextQuestion,
|
|
19
|
+
TextAnswer,
|
|
20
|
+
} from "./types.ts";
|
package/src/ask-user.ts
CHANGED
|
@@ -1,140 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
// for focused interactive decisions during an agent run. Holds the per-session
|
|
3
|
-
// single-active-questionnaire lock and drives the rich overlay UI.
|
|
4
|
-
//
|
|
5
|
-
// Implementation modules:
|
|
6
|
-
// schema.ts — external (LLM-facing) parameter schema
|
|
7
|
-
// normalize.ts — validation + normalization into the shared internal model
|
|
8
|
-
// flow.ts — shared questionnaire flow + concurrency lock
|
|
9
|
-
// ui-rich.ts — overlay UI via ctx.ui.custom()
|
|
10
|
-
// ui-rich-render.ts — overlay rendering helpers
|
|
11
|
-
// result.ts — hybrid (content + details) result formatting
|
|
12
|
-
// render.ts — custom renderCall / renderResult for the transcript
|
|
13
|
-
|
|
14
|
-
import type { ExtensionAPI, Theme } from "@earendil-works/pi-coding-agent";
|
|
15
|
-
import type { Component } from "@earendil-works/pi-tui";
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
16
2
|
import { formatTitle, signalWaiting } from "@mrclrchtr/supi-core/api";
|
|
17
|
-
import { ActiveQuestionnaireLock } from "./flow.ts";
|
|
18
3
|
import { AskUserValidationError, normalizeQuestionnaire } from "./normalize.ts";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
4
|
+
import { type AskUserToolResult, buildErrorResult, buildResult } from "./render/result.ts";
|
|
5
|
+
import { renderAskUserCall, renderAskUserResult } from "./render/transcript.ts";
|
|
6
|
+
import { buildTreeSummaryLabel } from "./render/tree-summary.ts";
|
|
21
7
|
import { type AskUserParams, AskUserParamsSchema } from "./schema.ts";
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
8
|
+
import { ActiveQuestionnaireLock } from "./session/lock.ts";
|
|
9
|
+
import { promptGuidelines, promptSnippet, toolDescription } from "./tool/guidance.ts";
|
|
10
|
+
import type { AskUserToolDetails, NormalizedQuestionnaire } from "./types.ts";
|
|
11
|
+
import { runQuestionnaire } from "./ui/choose-renderer.ts";
|
|
24
12
|
|
|
25
13
|
const TOOL_NAME = "ask_user";
|
|
26
14
|
const TOOL_LABEL = "Ask User";
|
|
27
15
|
|
|
28
|
-
|
|
29
|
-
"Ask the user a focused decision question (or up to 4 grouped questions) when explicit user input is required to proceed safely. Use for clarifying intent, picking between options, prioritizing a short set of features, or confirming a destructive action — not for surveys or open-ended discovery. Questions are `choice` (with options; set `multi: true` for multi-select) or `text` (freeform input). Structured questions can add `recommendation`, `default`, `allowOther`, `allowDiscuss`, and option `preview` content.";
|
|
30
|
-
|
|
31
|
-
const PROMPT_SNIPPET =
|
|
32
|
-
"ask_user — pause and request a focused decision (1-4 typed questions) when explicit user input is required to proceed, including rich choice and discuss flows";
|
|
33
|
-
|
|
34
|
-
const PROMPT_GUIDELINES = [
|
|
35
|
-
"Use ask_user only for decisions that require explicit user input — never as a substitute for reading code or thinking through a problem.",
|
|
36
|
-
"Keep questionnaires bounded: 1-4 focused questions with short headers; prefer one decision per call when possible.",
|
|
37
|
-
'There are two question types: `choice` for picking from options (single-select by default; set `multi: true` for multi-select — use this instead of the now-removed `multichoice`) and `text` for freeform input. For yes/no questions, use `choice` with options `{value: "yes", label: "Yes"}` and `{value: "no", label: "No"}`.',
|
|
38
|
-
"Set `recommendation` when one option or a small set of options is clearly preferable, so the UI can surface that guidance.",
|
|
39
|
-
"Set `default` to pre-select a starting value or option; the user can accept it with a single keystroke. Use it for safe/common defaults, distinct from `recommendation` which highlights what you think is best.",
|
|
40
|
-
"Enable `allowOther` only when a custom answer is genuinely useful, and `allowDiscuss` only when the user may need to talk through the choice instead of deciding immediately.",
|
|
41
|
-
"Use `description` to explain what each option means — it wraps naturally and a few sentences is fine. Reserve `preview` for code, config, or diagrams that need dedicated rendering space in a side pane.",
|
|
42
|
-
"Do not call ask_user while another ask_user interaction is in flight — wait for the previous result before issuing another.",
|
|
43
|
-
];
|
|
44
|
-
|
|
45
|
-
/** Minimal ui subset needed by executeAskUser — extended with setTitle/notify from ExtensionUIContext. */
|
|
46
|
-
interface ExtensionUi {
|
|
16
|
+
export type AskUserExecutionContext = Pick<ExtensionContext, "cwd" | "hasUI" | "abort"> & {
|
|
47
17
|
ui: {
|
|
48
|
-
custom?:
|
|
18
|
+
custom?: unknown;
|
|
19
|
+
notify?(message: string, type?: "info" | "warning" | "error"): void;
|
|
49
20
|
setWorkingVisible?(visible: boolean): void;
|
|
50
|
-
/** Set the terminal window/tab title. */
|
|
51
21
|
setTitle?(title: string): void;
|
|
52
|
-
|
|
53
|
-
|
|
22
|
+
getToolsExpanded?(): boolean;
|
|
23
|
+
setToolsExpanded?(expanded: boolean): void;
|
|
54
24
|
};
|
|
55
|
-
|
|
56
|
-
* Absolute path to the current working directory, available on the full
|
|
57
|
-
* ExtensionContext. Marked optional here to tolerate partial mocks.
|
|
58
|
-
*/
|
|
59
|
-
cwd?: string;
|
|
60
|
-
hasUI: boolean;
|
|
61
|
-
abort(): void;
|
|
62
|
-
}
|
|
25
|
+
};
|
|
63
26
|
|
|
64
27
|
export default function askUserExtension(pi: ExtensionAPI): void {
|
|
65
28
|
const lock = new ActiveQuestionnaireLock();
|
|
66
29
|
|
|
67
|
-
pi.registerTool({
|
|
30
|
+
pi.registerTool<typeof AskUserParamsSchema, AskUserToolDetails>({
|
|
68
31
|
name: TOOL_NAME,
|
|
69
32
|
label: TOOL_LABEL,
|
|
70
|
-
description:
|
|
71
|
-
promptSnippet
|
|
72
|
-
promptGuidelines
|
|
33
|
+
description: toolDescription,
|
|
34
|
+
promptSnippet,
|
|
35
|
+
promptGuidelines,
|
|
73
36
|
parameters: AskUserParamsSchema,
|
|
74
37
|
// biome-ignore lint/complexity/useMaxParams: pi ToolDefinition.execute signature
|
|
75
38
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
76
|
-
return executeAskUser(
|
|
77
|
-
params as AskUserParams,
|
|
78
|
-
signal,
|
|
79
|
-
ctx as unknown as ExtensionUi,
|
|
80
|
-
lock,
|
|
81
|
-
pi,
|
|
82
|
-
);
|
|
39
|
+
return executeAskUser(params, signal, ctx, lock, pi);
|
|
83
40
|
},
|
|
84
|
-
renderCall: (args, theme) => renderAskUserCall(args, theme
|
|
85
|
-
renderResult: (result, _options, theme) =>
|
|
86
|
-
renderAskUserResult(
|
|
87
|
-
result as { details?: unknown; content: { type: string; text?: string }[] },
|
|
88
|
-
theme as Theme,
|
|
89
|
-
) as unknown as Component,
|
|
41
|
+
renderCall: (args, theme) => renderAskUserCall(args, theme),
|
|
42
|
+
renderResult: (result, _options, theme) => renderAskUserResult(result, theme),
|
|
90
43
|
});
|
|
91
44
|
}
|
|
92
45
|
|
|
93
|
-
// biome-ignore lint/complexity/useMaxParams:
|
|
94
|
-
async function executeAskUser(
|
|
46
|
+
// biome-ignore lint/complexity/useMaxParams: keep the execution boundary explicit for tests
|
|
47
|
+
export async function executeAskUser(
|
|
95
48
|
params: AskUserParams,
|
|
96
49
|
signal: AbortSignal | undefined,
|
|
97
|
-
ctx:
|
|
50
|
+
ctx: AskUserExecutionContext,
|
|
98
51
|
lock: ActiveQuestionnaireLock,
|
|
99
52
|
pi: ExtensionAPI,
|
|
100
|
-
): Promise<
|
|
101
|
-
let
|
|
53
|
+
): Promise<AskUserToolResult> {
|
|
54
|
+
let questionnaire: NormalizedQuestionnaire;
|
|
102
55
|
try {
|
|
103
|
-
|
|
104
|
-
} catch (
|
|
105
|
-
if (
|
|
106
|
-
|
|
56
|
+
questionnaire = normalizeQuestionnaire(params);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error instanceof AskUserValidationError) {
|
|
59
|
+
return buildErrorResult(`Error: ${error.message}`);
|
|
60
|
+
}
|
|
61
|
+
throw error;
|
|
107
62
|
}
|
|
63
|
+
|
|
108
64
|
if (!ctx.hasUI) {
|
|
109
65
|
return buildErrorResult(
|
|
110
|
-
"Error: ask_user requires interactive UI
|
|
66
|
+
"Error: ask_user requires an interactive UI session. No user-facing UI is available in the current mode.",
|
|
111
67
|
);
|
|
112
68
|
}
|
|
113
69
|
if (!lock.acquire()) {
|
|
114
70
|
return buildErrorResult(
|
|
115
|
-
"Error: another ask_user
|
|
71
|
+
"Error: another ask_user form is already in flight. Wait for it to complete before calling ask_user again.",
|
|
116
72
|
);
|
|
117
73
|
}
|
|
74
|
+
|
|
118
75
|
signalAttention(ctx);
|
|
119
76
|
pi.events.emit("supi:ask-user:start", { source: "supi-ask-user" });
|
|
77
|
+
|
|
120
78
|
try {
|
|
121
|
-
// Hide the built-in working loader so it doesn't compete with the overlay.
|
|
122
79
|
ctx.ui.setWorkingVisible?.(false);
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
80
|
+
const outcome = await runQuestionnaire(questionnaire, {
|
|
81
|
+
ui: {
|
|
82
|
+
custom: asFunction(ctx.ui.custom),
|
|
83
|
+
notify: ctx.ui.notify,
|
|
84
|
+
},
|
|
85
|
+
signal,
|
|
86
|
+
onToggleToolsExpanded:
|
|
87
|
+
ctx.ui.getToolsExpanded && ctx.ui.setToolsExpanded
|
|
88
|
+
? () => ctx.ui.setToolsExpanded?.(!ctx.ui.getToolsExpanded?.())
|
|
89
|
+
: undefined,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (outcome === "unsupported") {
|
|
93
|
+
return buildErrorResult(
|
|
94
|
+
"Error: ask_user requires a TUI with custom overlay support. Do not use ask_user in non-interactive or degraded UI sessions.",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (outcome.status === "cancelled" || outcome.status === "aborted") {
|
|
128
99
|
ctx.abort();
|
|
129
100
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// "all" mode (Ctrl+O).
|
|
134
|
-
pi.appendEntry(treeSummaryLabel(normalized));
|
|
135
|
-
return result;
|
|
101
|
+
|
|
102
|
+
pi.appendEntry(buildTreeSummaryLabel(questionnaire));
|
|
103
|
+
return buildResult(questionnaire, outcome);
|
|
136
104
|
} finally {
|
|
137
|
-
// Restore the working loader regardless of how the overlay closed.
|
|
138
105
|
ctx.ui.setWorkingVisible?.(true);
|
|
139
106
|
pi.events.emit("supi:ask-user:end", { source: "supi-ask-user" });
|
|
140
107
|
restoreTerminalTitle(ctx, pi);
|
|
@@ -142,50 +109,23 @@ async function executeAskUser(
|
|
|
142
109
|
}
|
|
143
110
|
}
|
|
144
111
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
signalWaiting(ctx, `pi — waiting for your input`);
|
|
112
|
+
function signalAttention(ctx: AskUserExecutionContext): void {
|
|
113
|
+
signalWaiting(ctx, "pi — waiting for your input");
|
|
148
114
|
}
|
|
149
115
|
|
|
150
|
-
|
|
151
|
-
function restoreTerminalTitle(ctx: ExtensionUi, pi: ExtensionAPI): void {
|
|
116
|
+
function restoreTerminalTitle(ctx: AskUserExecutionContext, pi: ExtensionAPI): void {
|
|
152
117
|
ctx.ui.setTitle?.(formatTitle(pi.getSessionName(), ctx.cwd));
|
|
153
118
|
}
|
|
154
119
|
|
|
155
|
-
|
|
156
|
-
function
|
|
157
|
-
const count = q.questions.length;
|
|
158
|
-
const s = count === 1 ? "" : "s";
|
|
159
|
-
const headers = q.questions.map((q) => q.header).join(", ");
|
|
160
|
-
if (headers.length > 70) {
|
|
161
|
-
return `ask_user · ${count} question${s} · ${headers.slice(0, 67)}...`;
|
|
162
|
-
}
|
|
163
|
-
return `ask_user · ${count} question${s} · ${headers}`;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
async function driveQuestionnaire(
|
|
167
|
-
questionnaire: NormalizedQuestionnaire,
|
|
168
|
-
signal: AbortSignal | undefined,
|
|
169
|
-
ctx: ExtensionUi,
|
|
170
|
-
): Promise<HybridResult> {
|
|
171
|
-
const questions = questionnaire.questions;
|
|
172
|
-
if (typeof ctx.ui.custom !== "function") {
|
|
173
|
-
return buildErrorResult(
|
|
174
|
-
"Error: ask_user requires a TUI with custom overlay support. Do not use ask_user in non-interactive or degraded UI sessions.",
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
const richHost: RichUiHost = { custom: ctx.ui.custom.bind(ctx.ui) };
|
|
178
|
-
const outcome = await runRichQuestionnaire(questionnaire, { ui: richHost, signal });
|
|
179
|
-
if (outcome === "unsupported") {
|
|
180
|
-
return buildErrorResult(
|
|
181
|
-
"Error: ask_user requires a TUI with custom overlay support. Do not use ask_user in non-interactive or degraded UI sessions.",
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
return buildResult(questions, outcome);
|
|
120
|
+
function asFunction<T extends (...args: never[]) => unknown>(value: unknown): T | undefined {
|
|
121
|
+
return typeof value === "function" ? (value as T) : undefined;
|
|
185
122
|
}
|
|
186
123
|
|
|
187
|
-
export { ActiveQuestionnaireLock, QuestionnaireFlow } from "./flow.ts";
|
|
188
|
-
// Re-exports used by tests.
|
|
189
124
|
export { AskUserValidationError, normalizeQuestionnaire } from "./normalize.ts";
|
|
190
|
-
export { buildResult } from "./result.ts";
|
|
191
|
-
export {
|
|
125
|
+
export { buildErrorResult, buildResult } from "./render/result.ts";
|
|
126
|
+
export { AskUserController } from "./session/controller.ts";
|
|
127
|
+
export { ActiveQuestionnaireLock } from "./session/lock.ts";
|
|
128
|
+
export {
|
|
129
|
+
promptGuidelines as askUserPromptGuidelines,
|
|
130
|
+
promptSnippet as askUserPromptSnippet,
|
|
131
|
+
} from "./tool/guidance.ts";
|
package/src/index.ts
CHANGED
|
@@ -1 +1,23 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export type {
|
|
2
|
+
Answer,
|
|
3
|
+
AskUserDetails,
|
|
4
|
+
AskUserErrorDetails,
|
|
5
|
+
AskUserOutcome,
|
|
6
|
+
AskUserStatus,
|
|
7
|
+
AskUserToolDetails,
|
|
8
|
+
ChoiceAnswer,
|
|
9
|
+
CustomAnswer,
|
|
10
|
+
NormalizedChoiceQuestion,
|
|
11
|
+
NormalizedQuestion,
|
|
12
|
+
NormalizedQuestionnaire,
|
|
13
|
+
NormalizedTextQuestion,
|
|
14
|
+
TextAnswer,
|
|
15
|
+
} from "./api.ts";
|
|
16
|
+
export {
|
|
17
|
+
ActiveQuestionnaireLock,
|
|
18
|
+
AskUserController,
|
|
19
|
+
AskUserParamsSchema,
|
|
20
|
+
AskUserValidationError,
|
|
21
|
+
default,
|
|
22
|
+
normalizeQuestionnaire,
|
|
23
|
+
} from "./api.ts";
|