@agimon-ai/doompi-user-feedback 0.0.1-alpha.0 → 0.0.1-alpha.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.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const e=require("../doom/voiceQuestionHandoff.cjs"),t=require("
|
|
1
|
+
const e=require("../doom/voiceQuestionHandoff.cjs"),t=require("../../schemas/questionnaire.cjs"),n=require("../../services/responseService.cjs"),r=require("../../services/validationService.cjs"),i=require("../config/configAdapter.cjs"),a=require("./rpcQuestionnaireAdapter.cjs");let o=require("@agimon-ai/doompi-extension-contracts"),s=require("@agimon-ai/doompi-ui/toolChrome");const c=`ask_user_question`,l=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,u=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function d(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};(0,o.publishAskUserPrompt)(e.events,n)}function f(e,t){let n={active:t};(0,o.publishAskUserBlocked)(e.events,n)}function p(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function m(o,m){let h=i.loadUserFeedbackConfig().guidance;o.registerTool({name:c,label:`Ask User Question`,renderShell:`self`,description:`Ask the user one or more structured questions during execution. Use when you need to:
|
|
2
2
|
1. Gather user preferences or requirements
|
|
3
3
|
2. Clarify ambiguous instructions
|
|
4
4
|
3. Get decisions on implementation choices as you work
|
|
@@ -10,6 +10,6 @@ Usage notes:
|
|
|
10
10
|
- If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
|
|
11
11
|
|
|
12
12
|
Preview feature:
|
|
13
|
-
Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:h?.promptSnippet??l,promptGuidelines:h?.promptGuidelines??u,parameters:
|
|
14
|
-
`),0,0)}})}exports.ASK_USER_QUESTION_TOOL_NAME=c,exports.DEFAULT_PROMPT_GUIDELINES=u,exports.DEFAULT_PROMPT_SNIPPET=l,exports.registerAskUserQuestionTool=m;
|
|
13
|
+
Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:h?.promptSnippet??l,promptGuidelines:h?.promptGuidelines??u,parameters:t.QuestionParamsSchema,executionMode:`sequential`,async execute(t,s,c,l,u){let h=s;if(!u.hasUI)return n.buildToolResult(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let g=r.validateQuestionnaire(h);if(!g.ok)return n.buildToolResult(g.message,{answers:[],cancelled:!0,error:g.error});try{let t=await m.enqueue(async({signal:e,reportProgress:t})=>{d(o,h);let n=m.tryVoice?.(h);if(n)return n;let r=!0,s=()=>{r&&(r=!1,f(o,!1))};e.addEventListener(`abort`,s,{once:!0}),f(o,!0);try{if(u.mode===`rpc`&&p(u))return await a.runRpcQuestionnaire(u,h,e,t);let n=await m.runTui(u,h,i.resolveCollapseKey(i.loadUserFeedbackConfig()),e,t);return n===void 0?p(u)?await a.runRpcQuestionnaire(u,h,e,t):{answers:[],cancelled:!0,error:`no_custom_ui`}:n}finally{e.removeEventListener(`abort`,s),s()}},c);return t.delivery===`voice`?e.buildVoiceToolResult(t):t.error===`no_custom_ui`?n.buildToolResult(`Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.`,t):n.buildQuestionnaireResponse(t,h)}catch(e){let t=e instanceof Error?e.message:String(e);return n.buildToolResult(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=`${n.questions.length} question${n.questions.length===1?``:`s`}`,a=(0,s.renderToolHeading)(`ask`,i,t);return new s.DoomToolCall(r?`${a} ${t.fg(`dim`,`(${r})`)}`:a)},renderResult(e,t,n,r){let i=e.details;if(!i){let t=e.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
14
|
+
`),i=r.isError?`error`:`toolOutput`,a=r.isError?`✗ failed`:`✓ submitted`;return new s.DoomToolResult([n.fg(i,t||a)],n,{wrap:!0})}if(i.cancelled)return new s.DoomToolResult([n.fg(`warning`,`◐`)+n.fg(`dim`,` cancelled`)],n);if(i.delivery===`voice`&&i.voicePrompt)return new s.DoomToolResult([n.fg(`toolOutput`,i.voicePrompt)],n,{wrap:!0});let a=i.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${n.fg(`toolOutput`,t??``)}`});return a.length===0&&a.push(n.fg(`success`,`✓`)+n.fg(`dim`,` submitted`)),new s.DoomToolResult(a,n,{wrap:!0})}})}exports.ASK_USER_QUESTION_TOOL_NAME=c,exports.DEFAULT_PROMPT_GUIDELINES=u,exports.DEFAULT_PROMPT_SNIPPET=l,exports.registerAskUserQuestionTool=m;
|
|
15
15
|
//# sourceMappingURL=askUserQuestionAdapter.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"askUserQuestionAdapter.cjs","names":["loadUserFeedbackConfig","QuestionParamsSchema","buildToolResult","validateQuestionnaire","runRpcQuestionnaire","resolveCollapseKey","buildVoiceToolResult","buildQuestionnaireResponse","Text"],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"askUserQuestionAdapter.cjs","names":["loadUserFeedbackConfig","QuestionParamsSchema","buildToolResult","validateQuestionnaire","runRpcQuestionnaire","resolveCollapseKey","buildVoiceToolResult","buildQuestionnaireResponse","renderToolHeading","DoomToolCall","DoomToolResult"],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport { DoomToolCall, DoomToolResult, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n renderShell: 'self',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n const heading = renderToolHeading('ask', count, theme);\n return new DoomToolCall(headers ? `${heading} ${theme.fg('dim', `(${headers})`)}` : heading);\n },\n\n renderResult(result, _options, theme, context) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const raw = result.content\n .filter((block) => block.type === 'text')\n .map((block) => block.text)\n .join('\\n');\n const color = context.isError ? 'error' : 'toolOutput';\n const fallback = context.isError ? '✗ failed' : '✓ submitted';\n return new DoomToolResult([theme.fg(color, raw || fallback)], theme, { wrap: true });\n }\n if (details.cancelled) {\n return new DoomToolResult([theme.fg('warning', '◐') + theme.fg('dim', ' cancelled')], theme);\n }\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new DoomToolResult([theme.fg('toolOutput', details.voicePrompt)], theme, { wrap: true });\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${theme.fg(\n 'toolOutput',\n scalar ?? '',\n )}`;\n });\n if (lines.length === 0) lines.push(theme.fg('success', '✓') + theme.fg('dim', ' submitted'));\n return new DoomToolResult(lines, theme, { wrap: true });\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{buildVoiceToolResult as e}from"../doom/voiceQuestionHandoff.mjs";import{
|
|
1
|
+
import{buildVoiceToolResult as e}from"../doom/voiceQuestionHandoff.mjs";import{QuestionParamsSchema as t}from"../../schemas/questionnaire.mjs";import{buildQuestionnaireResponse as n,buildToolResult as r}from"../../services/responseService.mjs";import{validateQuestionnaire as i}from"../../services/validationService.mjs";import{loadUserFeedbackConfig as a,resolveCollapseKey as o}from"../config/configAdapter.mjs";import{runRpcQuestionnaire as s}from"./rpcQuestionnaireAdapter.mjs";import{publishAskUserBlocked as c,publishAskUserPrompt as l}from"@agimon-ai/doompi-extension-contracts";import{DoomToolCall as u,DoomToolResult as d,renderToolHeading as f}from"@agimon-ai/doompi-ui/toolChrome";const p=`ask_user_question`,m=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,h=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function g(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};l(e.events,n)}function _(e,t){let n={active:t};c(e.events,n)}function v(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function y(c,l){let y=a().guidance;c.registerTool({name:p,label:`Ask User Question`,renderShell:`self`,description:`Ask the user one or more structured questions during execution. Use when you need to:
|
|
2
2
|
1. Gather user preferences or requirements
|
|
3
3
|
2. Clarify ambiguous instructions
|
|
4
4
|
3. Get decisions on implementation choices as you work
|
|
@@ -10,6 +10,6 @@ Usage notes:
|
|
|
10
10
|
- If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
|
|
11
11
|
|
|
12
12
|
Preview feature:
|
|
13
|
-
Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:
|
|
14
|
-
`),0,0)}})}export{
|
|
13
|
+
Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:y?.promptSnippet??m,promptGuidelines:y?.promptGuidelines??h,parameters:t,executionMode:`sequential`,async execute(t,u,d,f,p){let m=u;if(!p.hasUI)return r(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let h=i(m);if(!h.ok)return r(h.message,{answers:[],cancelled:!0,error:h.error});try{let t=await l.enqueue(async({signal:e,reportProgress:t})=>{g(c,m);let n=l.tryVoice?.(m);if(n)return n;let r=!0,i=()=>{r&&(r=!1,_(c,!1))};e.addEventListener(`abort`,i,{once:!0}),_(c,!0);try{if(p.mode===`rpc`&&v(p))return await s(p,m,e,t);let n=await l.runTui(p,m,o(a()),e,t);return n===void 0?v(p)?await s(p,m,e,t):{answers:[],cancelled:!0,error:`no_custom_ui`}:n}finally{e.removeEventListener(`abort`,i),i()}},d);return t.delivery===`voice`?e(t):t.error===`no_custom_ui`?r(`Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.`,t):n(t,m)}catch(e){let t=e instanceof Error?e.message:String(e);return r(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=`${n.questions.length} question${n.questions.length===1?``:`s`}`,a=f(`ask`,i,t);return new u(r?`${a} ${t.fg(`dim`,`(${r})`)}`:a)},renderResult(e,t,n,r){let i=e.details;if(!i){let t=e.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
|
|
14
|
+
`),i=r.isError?`error`:`toolOutput`,a=r.isError?`✗ failed`:`✓ submitted`;return new d([n.fg(i,t||a)],n,{wrap:!0})}if(i.cancelled)return new d([n.fg(`warning`,`◐`)+n.fg(`dim`,` cancelled`)],n);if(i.delivery===`voice`&&i.voicePrompt)return new d([n.fg(`toolOutput`,i.voicePrompt)],n,{wrap:!0});let a=i.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${n.fg(`toolOutput`,t??``)}`});return a.length===0&&a.push(n.fg(`success`,`✓`)+n.fg(`dim`,` submitted`)),new d(a,n,{wrap:!0})}})}export{p as ASK_USER_QUESTION_TOOL_NAME,h as DEFAULT_PROMPT_GUIDELINES,m as DEFAULT_PROMPT_SNIPPET,y as registerAskUserQuestionTool};
|
|
15
15
|
//# sourceMappingURL=askUserQuestionAdapter.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"askUserQuestionAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"askUserQuestionAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport { DoomToolCall, DoomToolResult, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n renderShell: 'self',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n const heading = renderToolHeading('ask', count, theme);\n return new DoomToolCall(headers ? `${heading} ${theme.fg('dim', `(${headers})`)}` : heading);\n },\n\n renderResult(result, _options, theme, context) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const raw = result.content\n .filter((block) => block.type === 'text')\n .map((block) => block.text)\n .join('\\n');\n const color = context.isError ? 'error' : 'toolOutput';\n const fallback = context.isError ? '✗ failed' : '✓ submitted';\n return new DoomToolResult([theme.fg(color, raw || fallback)], theme, { wrap: true });\n }\n if (details.cancelled) {\n return new DoomToolResult([theme.fg('warning', '◐') + theme.fg('dim', ' cancelled')], theme);\n }\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new DoomToolResult([theme.fg('toolOutput', details.voicePrompt)], theme, { wrap: true });\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${theme.fg(\n 'toolOutput',\n scalar ?? '',\n )}`;\n });\n if (lines.length === 0) lines.push(theme.fg('success', '✓') + theme.fg('dim', ' submitted'));\n return new DoomToolResult(lines, theme, { wrap: true });\n },\n });\n}\n"],"mappings":"orBAmBA,MAAa,EAA8B,oBAE9B,EAAyB,+FACzB,EAA4B,CACvC,wKACA,uaACA,yUACA,kHACF,EAoBA,SAAS,EAAW,EAAkB,EAA8B,CAClE,IAAM,EAAqC,CACzC,UAAW,EAAO,UAAU,IAAK,IAAc,CAC7C,SAAU,EAAS,SACnB,OAAQ,EAAS,OACjB,YAAa,EAAS,aAAe,GACrC,QAAS,EAAS,QAAQ,IAAK,IAAY,CACzC,MAAO,EAAO,MACd,YAAa,EAAO,YACpB,WAAY,EAAQ,EAAO,OAC7B,EAAE,CACJ,EAAE,CACJ,EACA,EAAqB,EAAG,OAAQ,CAAO,CACzC,CAEA,SAAS,EAAY,EAAkB,EAAuB,CAC5D,IAAM,EAAsC,CAAE,QAAO,EACrD,EAAsB,EAAG,OAAQ,CAAO,CAC1C,CAEA,SAAS,EAAY,EAAoC,CACvD,IAAM,EAAK,EAAQ,GACnB,OAAO,OAAO,EAAG,QAAW,YAAc,OAAO,EAAG,OAAU,UAChE,CAEA,SAAgB,EAA4B,EAAkB,EAAqD,CACjH,IAAM,EAAqB,EAAuB,CAAC,CAAC,SACpD,EAAG,aAAa,CACd,KAAM,EACN,MAAO,oBACP,YAAa,OACb,YAAa;;;;;;;;;;;;+LAab,cAAe,GAAoB,eAAiB,EACpD,iBAAkB,GAAoB,kBAAoB,EAC1D,WAAY,EACZ,cAAe,aAEf,MAAM,QAAQ,EAAa,EAAQ,EAAQ,EAAW,EAAS,CAC7D,IAAM,EAAQ,EACd,GAAI,CAAC,EAAQ,MACX,OAAO,EAAgB,4DAAa,CAAE,QAAS,CAAC,EAAG,UAAW,GAAM,MAAO,OAAQ,CAAC,EAGtF,IAAM,EAAa,EAAsB,CAAK,EAC9C,GAAI,CAAC,EAAW,GACd,OAAO,EAAgB,EAAW,QAAS,CACzC,QAAS,CAAC,EACV,UAAW,GACX,MAAO,EAAW,KACpB,CAAC,EAGH,GAAI,CACF,IAAM,EAAS,MAAM,EAAa,QAAQ,MAAO,CAAE,OAAQ,EAAc,oBAAqB,CAC5F,EAAW,EAAI,CAAK,EACpB,IAAM,EAAc,EAAa,WAAW,CAAK,EACjD,GAAI,EAAa,OAAO,EAExB,IAAI,EAAU,GACR,MAA2B,CAC1B,IACL,EAAU,GACV,EAAY,EAAI,EAAK,EACvB,EACA,EAAa,iBAAiB,QAAS,EAAc,CAAE,KAAM,EAAK,CAAC,EACnE,EAAY,EAAI,EAAI,EACpB,GAAI,CACF,GAAI,EAAQ,OAAS,OAAS,EAAY,CAAO,EAC/C,OAAO,MAAM,EAAoB,EAAS,EAAO,EAAc,CAAc,EAG/E,IAAM,EAAY,MAAM,EAAa,OACnC,EACA,EACA,EAAmB,EAAuB,CAAC,EAC3C,EACA,CACF,EAKA,OAJI,IAAc,IAAA,GACd,EAAY,CAAO,EACd,MAAM,EAAoB,EAAS,EAAO,EAAc,CAAc,EAExE,CAAE,QAAS,CAAC,EAAG,UAAW,GAAM,MAAO,cAAe,EAJzB,CAKtC,QAAU,CACR,EAAa,oBAAoB,QAAS,CAAY,EACtD,EAAa,CACf,CACF,EAAG,CAAM,EAIT,OAFI,EAAO,WAAa,QAAgB,EAAqB,CAAM,EAC/D,EAAO,QAAU,eAAuB,EAAgB,2NAAoB,CAAM,EAC/E,EAA2B,EAAQ,CAAK,CACjD,OAAS,EAAO,CACd,IAAM,EAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACnE,OAAO,EAAgB,wKAAwC,EAAM,GAAI,CACvE,QAAS,CAAC,EACV,UAAW,GACX,MAAO,qBACT,CAAC,CACH,CACF,EAEA,WAAW,EAAM,EAAO,CACtB,IAAM,EAAS,EACT,EAAU,EAAO,UAAU,IAAK,GAAa,EAAS,MAAM,CAAC,CAAC,KAAK,IAAI,EACvE,EAAQ,GAAG,EAAO,UAAU,OAAO,WAAW,EAAO,UAAU,SAAW,EAAI,GAAK,MACnF,EAAU,EAAkB,MAAO,EAAO,CAAK,EACrD,OAAO,IAAI,EAAa,EAAU,GAAG,EAAQ,GAAG,EAAM,GAAG,MAAO,IAAI,EAAQ,EAAE,IAAM,CAAO,CAC7F,EAEA,aAAa,EAAQ,EAAU,EAAO,EAAS,CAC7C,IAAM,EAAU,EAAO,QACvB,GAAI,CAAC,EAAS,CACZ,IAAM,EAAM,EAAO,QAChB,OAAQ,GAAU,EAAM,OAAS,MAAM,CAAC,CACxC,IAAK,GAAU,EAAM,IAAI,CAAC,CAC1B,KAAK;CAAI,EACN,EAAQ,EAAQ,QAAU,QAAU,aACpC,EAAW,EAAQ,QAAU,WAAa,cAChD,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,EAAO,GAAO,CAAQ,CAAC,EAAG,EAAO,CAAE,KAAM,EAAK,CAAC,CACrF,CACA,GAAI,EAAQ,UACV,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,YAAY,CAAC,EAAG,CAAK,EAE7F,GAAI,EAAQ,WAAa,SAAW,EAAQ,YAC1C,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,aAAc,EAAQ,WAAW,CAAC,EAAG,EAAO,CAAE,KAAM,EAAK,CAAC,EAEhG,IAAM,EAAQ,EAAQ,QAAQ,IAAK,GAAW,CAC5C,IAAM,EAAS,EAAO,OAAS,SAAW,EAAO,UAAY,CAAC,EAAA,CAAG,KAAK,IAAI,EAAI,EAAO,OACrF,MAAO,GAAG,EAAM,GAAG,UAAW,GAAG,EAAE,GAAG,EAAM,GAAG,SAAU,EAAO,QAAQ,EAAE,IAAI,EAAM,GAClF,aACA,GAAU,EACZ,GACF,CAAC,EAED,OADI,EAAM,SAAW,GAAG,EAAM,KAAK,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,YAAY,CAAC,EACpF,IAAI,EAAe,EAAO,EAAO,CAAE,KAAM,EAAK,CAAC,CACxD,CACF,CAAC,CACH"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-user-feedback",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"description": "Structured user feedback for DoomPi agents",
|
|
5
5
|
"homepage": "https://agimon.ai",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"typebox": "1.1.38",
|
|
41
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
41
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.13",
|
|
42
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.13"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@earendil-works/pi-coding-agent": "0.84.1",
|