@agimon-ai/po-user-feedback 0.0.0-alpha.16 → 0.0.0-alpha.18

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.
@@ -122,8 +122,7 @@ const root = (0, _agimon_ai_po_core_extensionFile.defineRoot)((_context) => {
122
122
  return {
123
123
  value: { coordinator },
124
124
  onDispose: async () => {
125
- coordinator.shutdown();
126
- await coordinator.waitForIdle();
125
+ await (0, _agimon_ai_po_core_extensionFile.runCleanups)([() => coordinator.shutdown(), () => coordinator.waitForIdle()], "User-feedback cleanup failed.");
127
126
  }
128
127
  };
129
128
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server.cjs","names":["defineRoot","defineRoot","contribution","defineResource","PO_HELP_WHEN","readPackageResource","defineToolRestriction","PO_VOICE_AUTO_MODE_ID","Type","defineServerTool","defineServerPlugin","composeServerProviderOverrides","rootSession","composeRootInjects","composeRootHooks","resourcePoUseUserFeedback","toolRestrictionAskUserQuestion","toolAskUserQuestion"],"sources":["../../src/services/questionnaireCoordinator/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server.ts","../../src/constants/questionnaire.ts","../../src/constants/tool.ts","../../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server.ts","../../src/schemas/questionnaire.ts","../../src/types/askUserWire.ts","../../src/services/answerEnvelope/index.ts","../../src/services/questionnaireService/index.ts","../../src/services/responseService/index.ts","../../src/services/validationService/index.ts","../../src/extensions/workspaces/sessions/(backend)/_tools/askUserHeadless.ts","../../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server.ts","../../generated/server.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../../types/questionnaire';\n\nexport interface QuestionnaireRunContext {\n signal: AbortSignal;\n reportProgress: (result: QuestionnaireResult) => void;\n}\n\nexport type QuestionnaireRunner = (context: QuestionnaireRunContext) => Promise<QuestionnaireResult>;\n\ninterface QueueEntry {\n id: number;\n runner: QuestionnaireRunner;\n controller: AbortController;\n externalSignal?: AbortSignal;\n removeAbortListener?: () => void;\n latestAnswers: QuestionnaireResult['answers'];\n state: 'queued' | 'active' | 'settled';\n resolve(result: QuestionnaireResult): void;\n reject(error: unknown): void;\n}\n\nfunction cancelledResult(entry: QueueEntry): QuestionnaireResult {\n return {\n answers: entry.latestAnswers.map((answer) => ({ ...answer })),\n cancelled: true,\n };\n}\n\nexport class QuestionnaireCoordinator {\n private readonly pending: QueueEntry[] = [];\n private readonly running = new Set<Promise<QuestionnaireResult>>();\n private active: QueueEntry | undefined;\n private nextRequestId = 1;\n private accepting = true;\n\n enqueue(runner: QuestionnaireRunner, externalSignal?: AbortSignal): Promise<QuestionnaireResult> {\n if (!this.accepting || externalSignal?.aborted) {\n return Promise.resolve({ answers: [], cancelled: true });\n }\n\n return new Promise<QuestionnaireResult>((resolve, reject) => {\n const entry: QueueEntry = {\n id: this.nextRequestId,\n runner,\n controller: new AbortController(),\n ...(externalSignal ? { externalSignal } : {}),\n latestAnswers: [],\n state: 'queued',\n resolve,\n reject,\n };\n this.nextRequestId += 1;\n const abort = (): void => this.cancel(entry);\n externalSignal?.addEventListener('abort', abort, { once: true });\n entry.removeAbortListener = () => externalSignal?.removeEventListener('abort', abort);\n this.pending.push(entry);\n this.startNext();\n });\n }\n\n shutdown(): void {\n if (!this.accepting && !this.active && this.pending.length === 0) return;\n this.accepting = false;\n if (this.active) this.cancel(this.active);\n while (this.pending.length > 0) {\n const entry = this.pending[0];\n if (!entry) break;\n this.cancel(entry);\n }\n }\n\n /** Waits for every started presenter to observe cancellation and finish. */\n async waitForIdle(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n\n private startNext(): void {\n if (!this.accepting || this.active) return;\n const entry = this.pending.shift();\n if (!entry) return;\n entry.state = 'active';\n this.active = entry;\n const reportProgress = (result: QuestionnaireResult): void => {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n entry.latestAnswers = result.answers.map((answer) => ({ ...answer }));\n };\n let operation: Promise<QuestionnaireResult>;\n try {\n operation = entry.runner({ signal: entry.controller.signal, reportProgress });\n } catch (error) {\n operation = Promise.reject(error);\n }\n this.running.add(operation);\n void operation\n .then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n )\n .finally(() => this.running.delete(operation));\n }\n\n private complete(entry: QueueEntry, result: QuestionnaireResult): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.resolve(result);\n this.startNext();\n }\n\n private fail(entry: QueueEntry, error: unknown): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.reject(error);\n this.startNext();\n }\n\n private cancel(entry: QueueEntry): void {\n if (entry.state === 'settled') return;\n if (entry.state === 'queued') {\n const index = this.pending.indexOf(entry);\n this.pending.splice(index, 1);\n entry.state = 'settled';\n entry.removeAbortListener?.();\n entry.controller.abort();\n entry.resolve(cancelledResult(entry));\n return;\n }\n\n const result = cancelledResult(entry);\n this.releaseActive(entry);\n entry.controller.abort();\n entry.resolve(result);\n this.startNext();\n }\n\n private releaseActive(entry: QueueEntry): void {\n entry.state = 'settled';\n entry.removeAbortListener?.();\n this.active = undefined;\n }\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\n\nconst root = defineRoot((_context: PoServerPluginContext) => {\n const coordinator = new QuestionnaireCoordinator();\n return {\n value: { coordinator },\n onDispose: async () => {\n coordinator.shutdown();\n await coordinator.waitForIdle();\n },\n };\n});\n\nexport type UserFeedbackServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\n\nimport contribution from './_lib/root.server';\n\nexport default defineRoot(contribution);\n","import { defineResource } from '@agimon-ai/po-core/extensionFile';\nimport { PO_HELP_WHEN } from '@agimon-ai/po-core/help';\nimport { readPackageResource } from '@agimon-ai/po-core/serverPlugin';\n\nexport default defineResource({\n name: 'po-use-user-feedback',\n when: PO_HELP_WHEN,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-user-feedback/SKILL.md'),\n});\n","export const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\nexport const RESERVED_LABELS = ['Other', 'Type something.', 'Next'] as const;\n","import { MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS } from './questionnaire';\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\nexport const ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nexport const 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.';\nexport const 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.';\nexport const ERROR_SESSION_INACTIVE = 'Error: the questionnaire session is no longer active.';\n","import { defineToolRestriction } from '@agimon-ai/po-core/extensionFile';\nimport type { PoHeadlessToolRestriction } from '@agimon-ai/po-core/headless';\nimport { PO_VOICE_AUTO_MODE_ID } from '@agimon-ai/po-core/voiceTools';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\n\nexport default defineToolRestriction({\n when: { state: { 'minor-mode': PO_VOICE_AUTO_MODE_ID } },\n excludedTools: [ASK_USER_QUESTION_TOOL_NAME],\n} satisfies PoHeadlessToolRestriction);\n","import { type Static, Type } from 'typebox';\n\nimport {\n MAX_QUESTIONS,\n MIN_OPTIONS,\n MAX_OPTIONS,\n MAX_HEADER_LENGTH,\n MAX_LABEL_LENGTH,\n} from '../constants/questionnaire';\n\nexport const OptionSchema = Type.Object(\n {\n label: Type.String({\n maxLength: MAX_LABEL_LENGTH,\n description: 'MAX 60 CHARACTERS, hard limit. Concise option label shown to the user, ideally 1-5 words.',\n }),\n description: Type.String({\n description: 'Explanation of what the option means and its important trade-offs.',\n }),\n preview: Type.Optional(\n Type.String({ description: 'Optional markdown preview rendered while this option is focused.' }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionSchema = Type.Object(\n {\n question: Type.String({ description: 'The complete, clear question to ask the user.' }),\n header: Type.String({\n maxLength: MAX_HEADER_LENGTH,\n description: 'MAX 16 CHARACTERS, short label used when navigating several questions.',\n }),\n options: Type.Array(OptionSchema, {\n minItems: MIN_OPTIONS,\n maxItems: MAX_OPTIONS,\n description: 'The 2-4 choices offered for this question.',\n }),\n multiSelect: Type.Optional(\n Type.Boolean({ description: 'Allow more than one option to be selected.', default: false }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionParamsSchema = Type.Object(\n {\n questions: Type.Array(QuestionSchema, {\n minItems: 1,\n maxItems: MAX_QUESTIONS,\n description: 'Questions to ask the user (1-4 questions).',\n }),\n },\n { additionalProperties: false },\n);\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport type { AnswerKind, QuestionAnswer } from '../../types/questionnaire';\n\n/**\n * Read a whole-questionnaire answer envelope back against the questions it claims to answer.\n *\n * The envelope arrives on a select response, which is reader input rather than\n * anything the tool authored, so nothing in it is taken on trust: an index has\n * to name a question that was asked, a chosen label has to be one that\n * question offered, and free text is capped. An envelope that fails is refused\n * rather than repaired, because a half-read answer set reaches the model as\n * the reader's decision and there is no way to tell from there that part of it\n * was invented.\n *\n * `preview` is the clearest case: it is read from the option the tool\n * declared, never from the wire, so the model cannot be shown a preview that\n * was never offered.\n *\n * Host-neutral by construction: the questions and the payload both arrive as\n * arguments, so the same arguments always produce the same result.\n */\nexport const MAX_ANSWER_TEXT_LENGTH = 4000;\n\nexport type AnswerEnvelopeResult =\n | { readonly ok: true; readonly answers: QuestionAnswer[] }\n | { readonly ok: false; readonly reason: string };\n\nconst ANSWER_KINDS: readonly string[] = ['option', 'custom', 'multi'] satisfies readonly AnswerKind[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalNote(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed.slice(0, MAX_ANSWER_TEXT_LENGTH);\n}\n\n/** One answer, rebuilt from the question it names rather than copied off the wire. */\nfunction readAnswer(entry: unknown, params: QuestionParams, seen: Set<number>): QuestionAnswer | string {\n if (!isRecord(entry)) return 'an answer was not an object';\n const questionIndex = entry.questionIndex;\n if (typeof questionIndex !== 'number' || !Number.isInteger(questionIndex)) {\n return 'an answer carried no question index';\n }\n const question: QuestionData | undefined = params.questions[questionIndex];\n if (question === undefined) return `no question ${String(questionIndex)} was asked`;\n if (seen.has(questionIndex)) return `question ${String(questionIndex)} was answered twice`;\n seen.add(questionIndex);\n\n const kind = entry.kind;\n if (typeof kind !== 'string' || !ANSWER_KINDS.includes(kind)) {\n return `question ${String(questionIndex)} carried an unknown answer kind`;\n }\n const notes = optionalNote(entry.notes);\n const base = { questionIndex, question: question.question, ...(notes === undefined ? {} : { notes }) };\n\n if (kind === 'multi') {\n const selected = entry.selected;\n if (!Array.isArray(selected)) return `question ${String(questionIndex)} selected nothing readable`;\n const labels = selected.filter((label): label is string => typeof label === 'string');\n if (labels.length !== selected.length) return `question ${String(questionIndex)} selected a non-label`;\n if (labels.some((label) => !question.options.some((option) => option.label === label))) {\n return `question ${String(questionIndex)} selected an option it was not offered`;\n }\n return { ...base, kind: 'multi', answer: null, selected: [...new Set(labels)] };\n }\n\n if (kind === 'option') {\n const label = entry.answer;\n const chosen = typeof label === 'string' ? question.options.find((option) => option.label === label) : undefined;\n if (chosen === undefined) return `question ${String(questionIndex)} chose an option it was not offered`;\n // From the option the tool declared, so a preview the reader never saw\n // cannot be put in the model's mouth.\n return { ...base, kind: 'option', answer: chosen.label, ...(chosen.preview ? { preview: chosen.preview } : {}) };\n }\n\n const typed = entry.answer;\n if (typeof typed !== 'string') return `question ${String(questionIndex)} typed nothing readable`;\n return { ...base, kind: 'custom', answer: typed.slice(0, MAX_ANSWER_TEXT_LENGTH) };\n}\n\nexport function readAnswerEnvelope(payload: unknown, params: QuestionParams): AnswerEnvelopeResult {\n if (!isRecord(payload) || !Array.isArray(payload.answers)) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length === 0) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length > params.questions.length) return { ok: false, reason: 'more answers than questions' };\n\n const seen = new Set<number>();\n const answers: QuestionAnswer[] = [];\n for (const entry of payload.answers) {\n const answer = readAnswer(entry, params, seen);\n if (typeof answer === 'string') return { ok: false, reason: answer };\n answers.push(answer);\n }\n // The model reads them in the order it asked, whatever order they arrived in.\n answers.sort((left, right) => left.questionIndex - right.questionIndex);\n return { ok: true, answers };\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport { decodeAnswerEnvelope } from '../../types/askUserWire';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire';\nimport { readAnswerEnvelope } from '../answerEnvelope';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nexport interface QuestionnaireInteraction {\n select(title: string, options: readonly string[], signal?: AbortSignal): Promise<string | undefined>;\n input(title: string, placeholder: string, signal?: AbortSignal): Promise<string | undefined>;\n}\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\n/**\n * What a rich client replied, when it replied with the whole questionnaire.\n *\n * A client that can see the whole call answers every question at once rather\n * than walking this loop, so any select response may be an envelope instead of\n * a label. A response that is not one leaves the loop exactly as it was, which\n * is what keeps a plain RPC client working unchanged.\n */\ninterface EnvelopeSink {\n params: QuestionParams;\n /** Set once a select response carried the whole questionnaire; the loop stops and returns it. */\n result?: QuestionnaireResult;\n}\n\n/** True when this response was the whole set, in which case the sink now holds the answer. */\nfunction takeEnvelope(sink: EnvelopeSink, selected: string | undefined): boolean {\n const read = decodeAnswerEnvelope(selected);\n if (read.kind === 'absent') return false;\n if (read.kind === 'malformed') {\n sink.result = { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n }\n const envelope = readAnswerEnvelope(read.payload, sink.params);\n sink.result = envelope.ok\n ? { answers: envelope.answers, cancelled: false }\n : { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n}\n\nasync function askForCustomAnswer(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await interaction.input(question.question, 'Type your answer', signal);\n if (answer === undefined) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'custom',\n answer,\n };\n}\n\nasync function askSingleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await interaction.select(question.question, labels, signal);\n if (selected === undefined) return undefined;\n if (takeEnvelope(sink, selected)) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n const option = question.options.find((candidate) => candidate.label === selected);\n if (!option) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n };\n}\n\nfunction multiChoiceLabels(question: QuestionData, selected: ReadonlySet<string>): string[] {\n const options = question.options.map((option) => {\n const marker = selected.has(option.label) ? '[x]' : '[ ]';\n return `${marker} ${option.label}`;\n });\n return [...options, CUSTOM_LABEL, DONE_LABEL];\n}\n\nfunction stripSelectionMarker(value: string): string {\n return value.replace(/^\\[[ x]\\]\\s/u, '');\n}\n\nasync function askMultipleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await interaction.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n signal,\n );\n if (choice === undefined) return undefined;\n if (takeEnvelope(sink, choice)) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n if (choice === DONE_LABEL) {\n return {\n questionIndex,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: [...selected],\n };\n }\n const label = stripSelectionMarker(choice);\n if (!question.options.some((option) => option.label === label)) continue;\n if (selected.has(label)) selected.delete(label);\n else selected.add(label);\n }\n return undefined;\n}\n\nexport async function runQuestionnaire(\n interaction: QuestionnaireInteraction,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\n const sink: EnvelopeSink = { params };\n for (let questionIndex = 0; questionIndex < params.questions.length; questionIndex += 1) {\n if (signal?.aborted) return cancelled(answers);\n const question = params.questions[questionIndex];\n if (!question) continue;\n const answer = question.multiSelect\n ? await askMultipleChoice(interaction, question, questionIndex, sink, signal)\n : await askSingleChoice(interaction, question, questionIndex, sink, signal);\n // A client that answered everything at once ends the walk here; the loop\n // is only how a client that can see one question at a time gets through.\n if (sink.result !== undefined) {\n const whole = sink.result;\n if (!whole.cancelled) {\n reportProgress?.({ ...whole, answers: whole.answers.map((item) => ({ ...item })) });\n }\n return whole;\n }\n if (!answer) return cancelled(answers);\n answers.push(answer);\n reportProgress?.({ answers: answers.map((item) => ({ ...item })), cancelled: false });\n }\n return { answers, cancelled: false };\n}\n","import type { QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../../types/questionnaire';\n\nexport const DECLINE_MESSAGE = 'User declined to answer questions';\nexport const ENVELOPE_PREFIX = 'User has answered your questions:';\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nexport const NO_INPUT_PLACEHOLDER = '(no input)';\n\nexport function buildToolResult(text: string, details: QuestionnaireResult): ToolTextResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function formatAnswer(answer: QuestionAnswer): string {\n if (answer.kind === 'multi') {\n return answer.selected && answer.selected.length > 0 ? answer.selected.join(', ') : NO_INPUT_PLACEHOLDER;\n }\n return answer.answer && answer.answer.length > 0 ? answer.answer : NO_INPUT_PLACEHOLDER;\n}\n\nexport function buildAnswerSegment(answer: QuestionAnswer): string {\n const sections = [`\"${answer.question}\"=\"${formatAnswer(answer)}\"`];\n if (answer.preview) sections.push(`selected preview: ${answer.preview}`);\n if (answer.notes) sections.push(`user notes: ${answer.notes}`);\n return `${sections.join('. ')}.`;\n}\n\nexport function buildQuestionnaireResponse(\n result: QuestionnaireResult | null | undefined,\n params: QuestionParams,\n): ToolTextResult {\n if (!result || result.cancelled) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result?.answers ?? [], cancelled: true });\n }\n\n const segments = params.questions.flatMap((_question, index) => {\n const answer = result.answers.find((candidate) => candidate.questionIndex === index);\n return answer ? [buildAnswerSegment(answer)] : [];\n });\n if (segments.length === 0) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n }\n\n return buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(' ')} ${ENVELOPE_SUFFIX}`, result);\n}\n","import { MAX_QUESTIONS, MIN_OPTIONS, RESERVED_LABELS } from '../../constants/questionnaire';\nimport { type QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionnaireError } from '../../types/questionnaire';\n\nexport const ERROR_NO_QUESTIONS = 'Error: At least one question is required';\nexport const ERROR_TOO_MANY_QUESTIONS = `Error: At most ${MAX_QUESTIONS} questions are allowed per invocation`;\nexport const ERROR_DUPLICATE_QUESTION = 'Error: Question text must be unique within an invocation';\nexport const ERROR_TOO_FEW_OPTIONS = `Error: Each question requires at least ${MIN_OPTIONS} options`;\nexport const ERROR_RESERVED_LABEL = `Error: Option label is reserved (${RESERVED_LABELS.join(', ')})`;\nexport const ERROR_DUPLICATE_OPTION_LABEL = 'Error: Option labels must be unique within a question';\n\nexport type ValidationResult = { ok: true } | { ok: false; error: QuestionnaireError; message: string };\n\nconst reservedLabels = new Set<string>(RESERVED_LABELS);\n\nexport function validateQuestionnaire(params: QuestionParams): ValidationResult {\n if (params.questions.length === 0) {\n return { ok: false, error: 'no_questions', message: ERROR_NO_QUESTIONS };\n }\n if (params.questions.length > MAX_QUESTIONS) {\n return { ok: false, error: 'too_many_questions', message: ERROR_TOO_MANY_QUESTIONS };\n }\n\n const seenQuestions = new Set<string>();\n for (const question of params.questions) {\n if (seenQuestions.has(question.question)) {\n return { ok: false, error: 'duplicate_question', message: ERROR_DUPLICATE_QUESTION };\n }\n seenQuestions.add(question.question);\n }\n\n for (const question of params.questions) {\n if (question.options.length < MIN_OPTIONS) {\n return { ok: false, error: 'empty_options', message: ERROR_TOO_FEW_OPTIONS };\n }\n const seenLabels = new Set<string>();\n for (const option of question.options) {\n if (reservedLabels.has(option.label)) {\n return { ok: false, error: 'reserved_label', message: ERROR_RESERVED_LABEL };\n }\n if (seenLabels.has(option.label)) {\n return {\n ok: false,\n error: 'duplicate_option_label',\n message: ERROR_DUPLICATE_OPTION_LABEL,\n };\n }\n seenLabels.add(option.label);\n }\n }\n\n return { ok: true };\n}\n","import { type PoHeadlessTool, type PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\nimport type { QuestionParams } from '../../../../../schemas/questionnaire';\nimport { QuestionParamsSchema } from '../../../../../schemas/questionnaire';\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\nimport { runQuestionnaire, type QuestionnaireInteraction } from '../../../../../services/questionnaireService';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../../../../services/responseService';\nimport { validateQuestionnaire } from '../../../../../services/validationService';\nimport type { QuestionnaireResult } from '../../../../../types/questionnaire';\n\nfunction output(result: {\n content: Array<{ type: 'text'; text: string }>;\n details: QuestionnaireResult;\n}): PoHeadlessToolResult {\n return { content: result.content, details: result.details };\n}\n\nexport function createAskUserHeadlessTool(\n coordinator?: QuestionnaireCoordinator,\n): PoHeadlessTool<typeof QuestionParamsSchema> {\n const tool: PoHeadlessTool<typeof QuestionParamsSchema> = {\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: 'Ask the user one or more structured questions during execution.',\n parameters: QuestionParamsSchema,\n promptSnippet: 'Ask the user structured questions when requirements are ambiguous',\n executionMode: 'serial',\n async execute(_toolCallId, parameters, signal, _onUpdate, executionContext) {\n const params = parameters as QuestionParams;\n const validation = validateQuestionnaire(params);\n if (!validation.ok) {\n return output(buildToolResult(validation.message, { answers: [], cancelled: true, error: validation.error }));\n }\n const activeCoordinator = coordinator ?? new QuestionnaireCoordinator();\n try {\n const result = await activeCoordinator.enqueue(async ({ signal: requestSignal, reportProgress }) => {\n requestSignal.throwIfAborted();\n const interaction: QuestionnaireInteraction = {\n select: async (title, options, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n {\n kind: 'select',\n title,\n options: options.map((label) => ({ label, value: label })),\n },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n input: async (title, placeholder, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n { kind: 'input', title, message: placeholder },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n };\n const questionnaire = await runQuestionnaire(interaction, params, requestSignal, reportProgress);\n requestSignal.throwIfAborted();\n return questionnaire;\n }, signal);\n return output(buildQuestionnaireResponse(result, params));\n } finally {\n if (coordinator === undefined) {\n activeCoordinator.shutdown();\n await activeCoordinator.waitForIdle();\n }\n }\n },\n };\n return tool;\n}\n","import { defineServerTool, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport type { UserFeedbackServerScope } from '../_lib/root.server';\nimport { createAskUserHeadlessTool } from '../_tools/askUserHeadless';\n\nexport default defineServerTool((context: WithRoot<PoServerPluginContext, UserFeedbackServerScope>) =>\n createAskUserHeadlessTool(context.root.coordinator),\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverPlugin';\nimport { composeRootHooks, composeRootInjects } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport resourcePoUseUserFeedback from '../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server';\nimport toolRestrictionAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server';\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-user-feedback',\n providerOverrides: composeServerProviderOverrides(rootSession),\n injects: composeRootInjects({ session: [rootSession] }),\n session: async (context) => {\n const scopeSession = await context.scopeRoot('session', rootSession, context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get resources(): PoServerSessionPlugin['resources'] { return [at(resourcePoUseUserFeedback, contextSession)]; },\n get toolRestrictions(): PoServerSessionPlugin['toolRestrictions'] { return [at(toolRestrictionAskUserQuestion, contextSession)]; },\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'ask_user_question' }, at(toolAskUserQuestion, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): PoServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"server.cjs","names":["defineRoot","runCleanups","defineRoot","contribution","defineResource","PO_HELP_WHEN","readPackageResource","defineToolRestriction","PO_VOICE_AUTO_MODE_ID","Type","defineServerTool","defineServerPlugin","composeServerProviderOverrides","rootSession","composeRootInjects","composeRootHooks","resourcePoUseUserFeedback","toolRestrictionAskUserQuestion","toolAskUserQuestion"],"sources":["../../src/services/questionnaireCoordinator/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server.ts","../../src/constants/questionnaire.ts","../../src/constants/tool.ts","../../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server.ts","../../src/schemas/questionnaire.ts","../../src/types/askUserWire.ts","../../src/services/answerEnvelope/index.ts","../../src/services/questionnaireService/index.ts","../../src/services/responseService/index.ts","../../src/services/validationService/index.ts","../../src/extensions/workspaces/sessions/(backend)/_tools/askUserHeadless.ts","../../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server.ts","../../generated/server.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../../types/questionnaire';\n\nexport interface QuestionnaireRunContext {\n signal: AbortSignal;\n reportProgress: (result: QuestionnaireResult) => void;\n}\n\nexport type QuestionnaireRunner = (context: QuestionnaireRunContext) => Promise<QuestionnaireResult>;\n\ninterface QueueEntry {\n id: number;\n runner: QuestionnaireRunner;\n controller: AbortController;\n externalSignal?: AbortSignal;\n removeAbortListener?: () => void;\n latestAnswers: QuestionnaireResult['answers'];\n state: 'queued' | 'active' | 'settled';\n resolve(result: QuestionnaireResult): void;\n reject(error: unknown): void;\n}\n\nfunction cancelledResult(entry: QueueEntry): QuestionnaireResult {\n return {\n answers: entry.latestAnswers.map((answer) => ({ ...answer })),\n cancelled: true,\n };\n}\n\nexport class QuestionnaireCoordinator {\n private readonly pending: QueueEntry[] = [];\n private readonly running = new Set<Promise<QuestionnaireResult>>();\n private active: QueueEntry | undefined;\n private nextRequestId = 1;\n private accepting = true;\n\n enqueue(runner: QuestionnaireRunner, externalSignal?: AbortSignal): Promise<QuestionnaireResult> {\n if (!this.accepting || externalSignal?.aborted) {\n return Promise.resolve({ answers: [], cancelled: true });\n }\n\n return new Promise<QuestionnaireResult>((resolve, reject) => {\n const entry: QueueEntry = {\n id: this.nextRequestId,\n runner,\n controller: new AbortController(),\n ...(externalSignal ? { externalSignal } : {}),\n latestAnswers: [],\n state: 'queued',\n resolve,\n reject,\n };\n this.nextRequestId += 1;\n const abort = (): void => this.cancel(entry);\n externalSignal?.addEventListener('abort', abort, { once: true });\n entry.removeAbortListener = () => externalSignal?.removeEventListener('abort', abort);\n this.pending.push(entry);\n this.startNext();\n });\n }\n\n shutdown(): void {\n if (!this.accepting && !this.active && this.pending.length === 0) return;\n this.accepting = false;\n if (this.active) this.cancel(this.active);\n while (this.pending.length > 0) {\n const entry = this.pending[0];\n if (!entry) break;\n this.cancel(entry);\n }\n }\n\n /** Waits for every started presenter to observe cancellation and finish. */\n async waitForIdle(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n\n private startNext(): void {\n if (!this.accepting || this.active) return;\n const entry = this.pending.shift();\n if (!entry) return;\n entry.state = 'active';\n this.active = entry;\n const reportProgress = (result: QuestionnaireResult): void => {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n entry.latestAnswers = result.answers.map((answer) => ({ ...answer }));\n };\n let operation: Promise<QuestionnaireResult>;\n try {\n operation = entry.runner({ signal: entry.controller.signal, reportProgress });\n } catch (error) {\n operation = Promise.reject(error);\n }\n this.running.add(operation);\n void operation\n .then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n )\n .finally(() => this.running.delete(operation));\n }\n\n private complete(entry: QueueEntry, result: QuestionnaireResult): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.resolve(result);\n this.startNext();\n }\n\n private fail(entry: QueueEntry, error: unknown): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.reject(error);\n this.startNext();\n }\n\n private cancel(entry: QueueEntry): void {\n if (entry.state === 'settled') return;\n if (entry.state === 'queued') {\n const index = this.pending.indexOf(entry);\n this.pending.splice(index, 1);\n entry.state = 'settled';\n entry.removeAbortListener?.();\n entry.controller.abort();\n entry.resolve(cancelledResult(entry));\n return;\n }\n\n const result = cancelledResult(entry);\n this.releaseActive(entry);\n entry.controller.abort();\n entry.resolve(result);\n this.startNext();\n }\n\n private releaseActive(entry: QueueEntry): void {\n entry.state = 'settled';\n entry.removeAbortListener?.();\n this.active = undefined;\n }\n}\n","import { defineRoot, runCleanups } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\n\nconst root = defineRoot((_context: PoServerPluginContext) => {\n const coordinator = new QuestionnaireCoordinator();\n return {\n value: { coordinator },\n onDispose: async () => {\n await runCleanups(\n [() => coordinator.shutdown(), () => coordinator.waitForIdle()],\n 'User-feedback cleanup failed.',\n );\n },\n };\n});\n\nexport type UserFeedbackServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\n\nimport contribution from './_lib/root.server';\n\nexport default defineRoot(contribution);\n","import { defineResource } from '@agimon-ai/po-core/extensionFile';\nimport { PO_HELP_WHEN } from '@agimon-ai/po-core/help';\nimport { readPackageResource } from '@agimon-ai/po-core/serverPlugin';\n\nexport default defineResource({\n name: 'po-use-user-feedback',\n when: PO_HELP_WHEN,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-user-feedback/SKILL.md'),\n});\n","export const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\nexport const RESERVED_LABELS = ['Other', 'Type something.', 'Next'] as const;\n","import { MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS } from './questionnaire';\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\nexport const ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nexport const 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.';\nexport const 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.';\nexport const ERROR_SESSION_INACTIVE = 'Error: the questionnaire session is no longer active.';\n","import { defineToolRestriction } from '@agimon-ai/po-core/extensionFile';\nimport type { PoHeadlessToolRestriction } from '@agimon-ai/po-core/headless';\nimport { PO_VOICE_AUTO_MODE_ID } from '@agimon-ai/po-core/voiceTools';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\n\nexport default defineToolRestriction({\n when: { state: { 'minor-mode': PO_VOICE_AUTO_MODE_ID } },\n excludedTools: [ASK_USER_QUESTION_TOOL_NAME],\n} satisfies PoHeadlessToolRestriction);\n","import { type Static, Type } from 'typebox';\n\nimport {\n MAX_QUESTIONS,\n MIN_OPTIONS,\n MAX_OPTIONS,\n MAX_HEADER_LENGTH,\n MAX_LABEL_LENGTH,\n} from '../constants/questionnaire';\n\nexport const OptionSchema = Type.Object(\n {\n label: Type.String({\n maxLength: MAX_LABEL_LENGTH,\n description: 'MAX 60 CHARACTERS, hard limit. Concise option label shown to the user, ideally 1-5 words.',\n }),\n description: Type.String({\n description: 'Explanation of what the option means and its important trade-offs.',\n }),\n preview: Type.Optional(\n Type.String({ description: 'Optional markdown preview rendered while this option is focused.' }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionSchema = Type.Object(\n {\n question: Type.String({ description: 'The complete, clear question to ask the user.' }),\n header: Type.String({\n maxLength: MAX_HEADER_LENGTH,\n description: 'MAX 16 CHARACTERS, short label used when navigating several questions.',\n }),\n options: Type.Array(OptionSchema, {\n minItems: MIN_OPTIONS,\n maxItems: MAX_OPTIONS,\n description: 'The 2-4 choices offered for this question.',\n }),\n multiSelect: Type.Optional(\n Type.Boolean({ description: 'Allow more than one option to be selected.', default: false }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionParamsSchema = Type.Object(\n {\n questions: Type.Array(QuestionSchema, {\n minItems: 1,\n maxItems: MAX_QUESTIONS,\n description: 'Questions to ask the user (1-4 questions).',\n }),\n },\n { additionalProperties: false },\n);\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport type { AnswerKind, QuestionAnswer } from '../../types/questionnaire';\n\n/**\n * Read a whole-questionnaire answer envelope back against the questions it claims to answer.\n *\n * The envelope arrives on a select response, which is reader input rather than\n * anything the tool authored, so nothing in it is taken on trust: an index has\n * to name a question that was asked, a chosen label has to be one that\n * question offered, and free text is capped. An envelope that fails is refused\n * rather than repaired, because a half-read answer set reaches the model as\n * the reader's decision and there is no way to tell from there that part of it\n * was invented.\n *\n * `preview` is the clearest case: it is read from the option the tool\n * declared, never from the wire, so the model cannot be shown a preview that\n * was never offered.\n *\n * Host-neutral by construction: the questions and the payload both arrive as\n * arguments, so the same arguments always produce the same result.\n */\nexport const MAX_ANSWER_TEXT_LENGTH = 4000;\n\nexport type AnswerEnvelopeResult =\n | { readonly ok: true; readonly answers: QuestionAnswer[] }\n | { readonly ok: false; readonly reason: string };\n\nconst ANSWER_KINDS: readonly string[] = ['option', 'custom', 'multi'] satisfies readonly AnswerKind[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalNote(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed.slice(0, MAX_ANSWER_TEXT_LENGTH);\n}\n\n/** One answer, rebuilt from the question it names rather than copied off the wire. */\nfunction readAnswer(entry: unknown, params: QuestionParams, seen: Set<number>): QuestionAnswer | string {\n if (!isRecord(entry)) return 'an answer was not an object';\n const questionIndex = entry.questionIndex;\n if (typeof questionIndex !== 'number' || !Number.isInteger(questionIndex)) {\n return 'an answer carried no question index';\n }\n const question: QuestionData | undefined = params.questions[questionIndex];\n if (question === undefined) return `no question ${String(questionIndex)} was asked`;\n if (seen.has(questionIndex)) return `question ${String(questionIndex)} was answered twice`;\n seen.add(questionIndex);\n\n const kind = entry.kind;\n if (typeof kind !== 'string' || !ANSWER_KINDS.includes(kind)) {\n return `question ${String(questionIndex)} carried an unknown answer kind`;\n }\n const notes = optionalNote(entry.notes);\n const base = { questionIndex, question: question.question, ...(notes === undefined ? {} : { notes }) };\n\n if (kind === 'multi') {\n const selected = entry.selected;\n if (!Array.isArray(selected)) return `question ${String(questionIndex)} selected nothing readable`;\n const labels = selected.filter((label): label is string => typeof label === 'string');\n if (labels.length !== selected.length) return `question ${String(questionIndex)} selected a non-label`;\n if (labels.some((label) => !question.options.some((option) => option.label === label))) {\n return `question ${String(questionIndex)} selected an option it was not offered`;\n }\n return { ...base, kind: 'multi', answer: null, selected: [...new Set(labels)] };\n }\n\n if (kind === 'option') {\n const label = entry.answer;\n const chosen = typeof label === 'string' ? question.options.find((option) => option.label === label) : undefined;\n if (chosen === undefined) return `question ${String(questionIndex)} chose an option it was not offered`;\n // From the option the tool declared, so a preview the reader never saw\n // cannot be put in the model's mouth.\n return { ...base, kind: 'option', answer: chosen.label, ...(chosen.preview ? { preview: chosen.preview } : {}) };\n }\n\n const typed = entry.answer;\n if (typeof typed !== 'string') return `question ${String(questionIndex)} typed nothing readable`;\n return { ...base, kind: 'custom', answer: typed.slice(0, MAX_ANSWER_TEXT_LENGTH) };\n}\n\nexport function readAnswerEnvelope(payload: unknown, params: QuestionParams): AnswerEnvelopeResult {\n if (!isRecord(payload) || !Array.isArray(payload.answers)) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length === 0) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length > params.questions.length) return { ok: false, reason: 'more answers than questions' };\n\n const seen = new Set<number>();\n const answers: QuestionAnswer[] = [];\n for (const entry of payload.answers) {\n const answer = readAnswer(entry, params, seen);\n if (typeof answer === 'string') return { ok: false, reason: answer };\n answers.push(answer);\n }\n // The model reads them in the order it asked, whatever order they arrived in.\n answers.sort((left, right) => left.questionIndex - right.questionIndex);\n return { ok: true, answers };\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport { decodeAnswerEnvelope } from '../../types/askUserWire';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire';\nimport { readAnswerEnvelope } from '../answerEnvelope';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nexport interface QuestionnaireInteraction {\n select(title: string, options: readonly string[], signal?: AbortSignal): Promise<string | undefined>;\n input(title: string, placeholder: string, signal?: AbortSignal): Promise<string | undefined>;\n}\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\n/**\n * What a rich client replied, when it replied with the whole questionnaire.\n *\n * A client that can see the whole call answers every question at once rather\n * than walking this loop, so any select response may be an envelope instead of\n * a label. A response that is not one leaves the loop exactly as it was, which\n * is what keeps a plain RPC client working unchanged.\n */\ninterface EnvelopeSink {\n params: QuestionParams;\n /** Set once a select response carried the whole questionnaire; the loop stops and returns it. */\n result?: QuestionnaireResult;\n}\n\n/** True when this response was the whole set, in which case the sink now holds the answer. */\nfunction takeEnvelope(sink: EnvelopeSink, selected: string | undefined): boolean {\n const read = decodeAnswerEnvelope(selected);\n if (read.kind === 'absent') return false;\n if (read.kind === 'malformed') {\n sink.result = { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n }\n const envelope = readAnswerEnvelope(read.payload, sink.params);\n sink.result = envelope.ok\n ? { answers: envelope.answers, cancelled: false }\n : { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n}\n\nasync function askForCustomAnswer(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await interaction.input(question.question, 'Type your answer', signal);\n if (answer === undefined) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'custom',\n answer,\n };\n}\n\nasync function askSingleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await interaction.select(question.question, labels, signal);\n if (selected === undefined) return undefined;\n if (takeEnvelope(sink, selected)) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n const option = question.options.find((candidate) => candidate.label === selected);\n if (!option) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n };\n}\n\nfunction multiChoiceLabels(question: QuestionData, selected: ReadonlySet<string>): string[] {\n const options = question.options.map((option) => {\n const marker = selected.has(option.label) ? '[x]' : '[ ]';\n return `${marker} ${option.label}`;\n });\n return [...options, CUSTOM_LABEL, DONE_LABEL];\n}\n\nfunction stripSelectionMarker(value: string): string {\n return value.replace(/^\\[[ x]\\]\\s/u, '');\n}\n\nasync function askMultipleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await interaction.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n signal,\n );\n if (choice === undefined) return undefined;\n if (takeEnvelope(sink, choice)) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n if (choice === DONE_LABEL) {\n return {\n questionIndex,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: [...selected],\n };\n }\n const label = stripSelectionMarker(choice);\n if (!question.options.some((option) => option.label === label)) continue;\n if (selected.has(label)) selected.delete(label);\n else selected.add(label);\n }\n return undefined;\n}\n\nexport async function runQuestionnaire(\n interaction: QuestionnaireInteraction,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\n const sink: EnvelopeSink = { params };\n for (let questionIndex = 0; questionIndex < params.questions.length; questionIndex += 1) {\n if (signal?.aborted) return cancelled(answers);\n const question = params.questions[questionIndex];\n if (!question) continue;\n const answer = question.multiSelect\n ? await askMultipleChoice(interaction, question, questionIndex, sink, signal)\n : await askSingleChoice(interaction, question, questionIndex, sink, signal);\n // A client that answered everything at once ends the walk here; the loop\n // is only how a client that can see one question at a time gets through.\n if (sink.result !== undefined) {\n const whole = sink.result;\n if (!whole.cancelled) {\n reportProgress?.({ ...whole, answers: whole.answers.map((item) => ({ ...item })) });\n }\n return whole;\n }\n if (!answer) return cancelled(answers);\n answers.push(answer);\n reportProgress?.({ answers: answers.map((item) => ({ ...item })), cancelled: false });\n }\n return { answers, cancelled: false };\n}\n","import type { QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../../types/questionnaire';\n\nexport const DECLINE_MESSAGE = 'User declined to answer questions';\nexport const ENVELOPE_PREFIX = 'User has answered your questions:';\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nexport const NO_INPUT_PLACEHOLDER = '(no input)';\n\nexport function buildToolResult(text: string, details: QuestionnaireResult): ToolTextResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function formatAnswer(answer: QuestionAnswer): string {\n if (answer.kind === 'multi') {\n return answer.selected && answer.selected.length > 0 ? answer.selected.join(', ') : NO_INPUT_PLACEHOLDER;\n }\n return answer.answer && answer.answer.length > 0 ? answer.answer : NO_INPUT_PLACEHOLDER;\n}\n\nexport function buildAnswerSegment(answer: QuestionAnswer): string {\n const sections = [`\"${answer.question}\"=\"${formatAnswer(answer)}\"`];\n if (answer.preview) sections.push(`selected preview: ${answer.preview}`);\n if (answer.notes) sections.push(`user notes: ${answer.notes}`);\n return `${sections.join('. ')}.`;\n}\n\nexport function buildQuestionnaireResponse(\n result: QuestionnaireResult | null | undefined,\n params: QuestionParams,\n): ToolTextResult {\n if (!result || result.cancelled) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result?.answers ?? [], cancelled: true });\n }\n\n const segments = params.questions.flatMap((_question, index) => {\n const answer = result.answers.find((candidate) => candidate.questionIndex === index);\n return answer ? [buildAnswerSegment(answer)] : [];\n });\n if (segments.length === 0) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n }\n\n return buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(' ')} ${ENVELOPE_SUFFIX}`, result);\n}\n","import { MAX_QUESTIONS, MIN_OPTIONS, RESERVED_LABELS } from '../../constants/questionnaire';\nimport { type QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionnaireError } from '../../types/questionnaire';\n\nexport const ERROR_NO_QUESTIONS = 'Error: At least one question is required';\nexport const ERROR_TOO_MANY_QUESTIONS = `Error: At most ${MAX_QUESTIONS} questions are allowed per invocation`;\nexport const ERROR_DUPLICATE_QUESTION = 'Error: Question text must be unique within an invocation';\nexport const ERROR_TOO_FEW_OPTIONS = `Error: Each question requires at least ${MIN_OPTIONS} options`;\nexport const ERROR_RESERVED_LABEL = `Error: Option label is reserved (${RESERVED_LABELS.join(', ')})`;\nexport const ERROR_DUPLICATE_OPTION_LABEL = 'Error: Option labels must be unique within a question';\n\nexport type ValidationResult = { ok: true } | { ok: false; error: QuestionnaireError; message: string };\n\nconst reservedLabels = new Set<string>(RESERVED_LABELS);\n\nexport function validateQuestionnaire(params: QuestionParams): ValidationResult {\n if (params.questions.length === 0) {\n return { ok: false, error: 'no_questions', message: ERROR_NO_QUESTIONS };\n }\n if (params.questions.length > MAX_QUESTIONS) {\n return { ok: false, error: 'too_many_questions', message: ERROR_TOO_MANY_QUESTIONS };\n }\n\n const seenQuestions = new Set<string>();\n for (const question of params.questions) {\n if (seenQuestions.has(question.question)) {\n return { ok: false, error: 'duplicate_question', message: ERROR_DUPLICATE_QUESTION };\n }\n seenQuestions.add(question.question);\n }\n\n for (const question of params.questions) {\n if (question.options.length < MIN_OPTIONS) {\n return { ok: false, error: 'empty_options', message: ERROR_TOO_FEW_OPTIONS };\n }\n const seenLabels = new Set<string>();\n for (const option of question.options) {\n if (reservedLabels.has(option.label)) {\n return { ok: false, error: 'reserved_label', message: ERROR_RESERVED_LABEL };\n }\n if (seenLabels.has(option.label)) {\n return {\n ok: false,\n error: 'duplicate_option_label',\n message: ERROR_DUPLICATE_OPTION_LABEL,\n };\n }\n seenLabels.add(option.label);\n }\n }\n\n return { ok: true };\n}\n","import { type PoHeadlessTool, type PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\nimport type { QuestionParams } from '../../../../../schemas/questionnaire';\nimport { QuestionParamsSchema } from '../../../../../schemas/questionnaire';\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\nimport { runQuestionnaire, type QuestionnaireInteraction } from '../../../../../services/questionnaireService';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../../../../services/responseService';\nimport { validateQuestionnaire } from '../../../../../services/validationService';\nimport type { QuestionnaireResult } from '../../../../../types/questionnaire';\n\nfunction output(result: {\n content: Array<{ type: 'text'; text: string }>;\n details: QuestionnaireResult;\n}): PoHeadlessToolResult {\n return { content: result.content, details: result.details };\n}\n\nexport function createAskUserHeadlessTool(\n coordinator?: QuestionnaireCoordinator,\n): PoHeadlessTool<typeof QuestionParamsSchema> {\n const tool: PoHeadlessTool<typeof QuestionParamsSchema> = {\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: 'Ask the user one or more structured questions during execution.',\n parameters: QuestionParamsSchema,\n promptSnippet: 'Ask the user structured questions when requirements are ambiguous',\n executionMode: 'serial',\n async execute(_toolCallId, parameters, signal, _onUpdate, executionContext) {\n const params = parameters as QuestionParams;\n const validation = validateQuestionnaire(params);\n if (!validation.ok) {\n return output(buildToolResult(validation.message, { answers: [], cancelled: true, error: validation.error }));\n }\n const activeCoordinator = coordinator ?? new QuestionnaireCoordinator();\n try {\n const result = await activeCoordinator.enqueue(async ({ signal: requestSignal, reportProgress }) => {\n requestSignal.throwIfAborted();\n const interaction: QuestionnaireInteraction = {\n select: async (title, options, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n {\n kind: 'select',\n title,\n options: options.map((label) => ({ label, value: label })),\n },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n input: async (title, placeholder, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n { kind: 'input', title, message: placeholder },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n };\n const questionnaire = await runQuestionnaire(interaction, params, requestSignal, reportProgress);\n requestSignal.throwIfAborted();\n return questionnaire;\n }, signal);\n return output(buildQuestionnaireResponse(result, params));\n } finally {\n if (coordinator === undefined) {\n activeCoordinator.shutdown();\n await activeCoordinator.waitForIdle();\n }\n }\n },\n };\n return tool;\n}\n","import { defineServerTool, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport type { UserFeedbackServerScope } from '../_lib/root.server';\nimport { createAskUserHeadlessTool } from '../_tools/askUserHeadless';\n\nexport default defineServerTool((context: WithRoot<PoServerPluginContext, UserFeedbackServerScope>) =>\n createAskUserHeadlessTool(context.root.coordinator),\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverPlugin';\nimport { composeRootHooks, composeRootInjects } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport resourcePoUseUserFeedback from '../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server';\nimport toolRestrictionAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server';\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-user-feedback',\n providerOverrides: composeServerProviderOverrides(rootSession),\n injects: composeRootInjects({ session: [rootSession] }),\n session: async (context) => {\n const scopeSession = await context.scopeRoot('session', rootSession, context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get resources(): PoServerSessionPlugin['resources'] { return [at(resourcePoUseUserFeedback, contextSession)]; },\n get toolRestrictions(): PoServerSessionPlugin['toolRestrictions'] { return [at(toolRestrictionAskUserQuestion, contextSession)]; },\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'ask_user_question' }, at(toolAskUserQuestion, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): PoServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { composeServerProviderOverrides, defineServerPlugin, readPackageResource } from "@agimon-ai/po-core/serverPlugin";
2
- import { composeRootHooks, composeRootInjects, defineResource, defineRoot, defineServerTool, defineToolRestriction } from "@agimon-ai/po-core/extensionFile";
2
+ import { composeRootHooks, composeRootInjects, defineResource, defineRoot, defineServerTool, defineToolRestriction, runCleanups } from "@agimon-ai/po-core/extensionFile";
3
3
  import { PO_HELP_WHEN } from "@agimon-ai/po-core/help";
4
4
  import { PO_VOICE_AUTO_MODE_ID } from "@agimon-ai/po-core/voiceTools";
5
5
  import { Type } from "typebox";
@@ -118,8 +118,7 @@ const root = defineRoot((_context) => {
118
118
  return {
119
119
  value: { coordinator },
120
120
  onDispose: async () => {
121
- coordinator.shutdown();
122
- await coordinator.waitForIdle();
121
+ await runCleanups([() => coordinator.shutdown(), () => coordinator.waitForIdle()], "User-feedback cleanup failed.");
123
122
  }
124
123
  };
125
124
  });
@@ -1 +1 @@
1
- {"version":3,"file":"server.mjs","names":["contribution","rootSession","resourcePoUseUserFeedback","toolRestrictionAskUserQuestion","toolAskUserQuestion"],"sources":["../../src/services/questionnaireCoordinator/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server.ts","../../src/constants/questionnaire.ts","../../src/constants/tool.ts","../../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server.ts","../../src/schemas/questionnaire.ts","../../src/types/askUserWire.ts","../../src/services/answerEnvelope/index.ts","../../src/services/questionnaireService/index.ts","../../src/services/responseService/index.ts","../../src/services/validationService/index.ts","../../src/extensions/workspaces/sessions/(backend)/_tools/askUserHeadless.ts","../../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server.ts","../../generated/server.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../../types/questionnaire';\n\nexport interface QuestionnaireRunContext {\n signal: AbortSignal;\n reportProgress: (result: QuestionnaireResult) => void;\n}\n\nexport type QuestionnaireRunner = (context: QuestionnaireRunContext) => Promise<QuestionnaireResult>;\n\ninterface QueueEntry {\n id: number;\n runner: QuestionnaireRunner;\n controller: AbortController;\n externalSignal?: AbortSignal;\n removeAbortListener?: () => void;\n latestAnswers: QuestionnaireResult['answers'];\n state: 'queued' | 'active' | 'settled';\n resolve(result: QuestionnaireResult): void;\n reject(error: unknown): void;\n}\n\nfunction cancelledResult(entry: QueueEntry): QuestionnaireResult {\n return {\n answers: entry.latestAnswers.map((answer) => ({ ...answer })),\n cancelled: true,\n };\n}\n\nexport class QuestionnaireCoordinator {\n private readonly pending: QueueEntry[] = [];\n private readonly running = new Set<Promise<QuestionnaireResult>>();\n private active: QueueEntry | undefined;\n private nextRequestId = 1;\n private accepting = true;\n\n enqueue(runner: QuestionnaireRunner, externalSignal?: AbortSignal): Promise<QuestionnaireResult> {\n if (!this.accepting || externalSignal?.aborted) {\n return Promise.resolve({ answers: [], cancelled: true });\n }\n\n return new Promise<QuestionnaireResult>((resolve, reject) => {\n const entry: QueueEntry = {\n id: this.nextRequestId,\n runner,\n controller: new AbortController(),\n ...(externalSignal ? { externalSignal } : {}),\n latestAnswers: [],\n state: 'queued',\n resolve,\n reject,\n };\n this.nextRequestId += 1;\n const abort = (): void => this.cancel(entry);\n externalSignal?.addEventListener('abort', abort, { once: true });\n entry.removeAbortListener = () => externalSignal?.removeEventListener('abort', abort);\n this.pending.push(entry);\n this.startNext();\n });\n }\n\n shutdown(): void {\n if (!this.accepting && !this.active && this.pending.length === 0) return;\n this.accepting = false;\n if (this.active) this.cancel(this.active);\n while (this.pending.length > 0) {\n const entry = this.pending[0];\n if (!entry) break;\n this.cancel(entry);\n }\n }\n\n /** Waits for every started presenter to observe cancellation and finish. */\n async waitForIdle(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n\n private startNext(): void {\n if (!this.accepting || this.active) return;\n const entry = this.pending.shift();\n if (!entry) return;\n entry.state = 'active';\n this.active = entry;\n const reportProgress = (result: QuestionnaireResult): void => {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n entry.latestAnswers = result.answers.map((answer) => ({ ...answer }));\n };\n let operation: Promise<QuestionnaireResult>;\n try {\n operation = entry.runner({ signal: entry.controller.signal, reportProgress });\n } catch (error) {\n operation = Promise.reject(error);\n }\n this.running.add(operation);\n void operation\n .then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n )\n .finally(() => this.running.delete(operation));\n }\n\n private complete(entry: QueueEntry, result: QuestionnaireResult): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.resolve(result);\n this.startNext();\n }\n\n private fail(entry: QueueEntry, error: unknown): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.reject(error);\n this.startNext();\n }\n\n private cancel(entry: QueueEntry): void {\n if (entry.state === 'settled') return;\n if (entry.state === 'queued') {\n const index = this.pending.indexOf(entry);\n this.pending.splice(index, 1);\n entry.state = 'settled';\n entry.removeAbortListener?.();\n entry.controller.abort();\n entry.resolve(cancelledResult(entry));\n return;\n }\n\n const result = cancelledResult(entry);\n this.releaseActive(entry);\n entry.controller.abort();\n entry.resolve(result);\n this.startNext();\n }\n\n private releaseActive(entry: QueueEntry): void {\n entry.state = 'settled';\n entry.removeAbortListener?.();\n this.active = undefined;\n }\n}\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\n\nconst root = defineRoot((_context: PoServerPluginContext) => {\n const coordinator = new QuestionnaireCoordinator();\n return {\n value: { coordinator },\n onDispose: async () => {\n coordinator.shutdown();\n await coordinator.waitForIdle();\n },\n };\n});\n\nexport type UserFeedbackServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\n\nimport contribution from './_lib/root.server';\n\nexport default defineRoot(contribution);\n","import { defineResource } from '@agimon-ai/po-core/extensionFile';\nimport { PO_HELP_WHEN } from '@agimon-ai/po-core/help';\nimport { readPackageResource } from '@agimon-ai/po-core/serverPlugin';\n\nexport default defineResource({\n name: 'po-use-user-feedback',\n when: PO_HELP_WHEN,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-user-feedback/SKILL.md'),\n});\n","export const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\nexport const RESERVED_LABELS = ['Other', 'Type something.', 'Next'] as const;\n","import { MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS } from './questionnaire';\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\nexport const ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nexport const 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.';\nexport const 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.';\nexport const ERROR_SESSION_INACTIVE = 'Error: the questionnaire session is no longer active.';\n","import { defineToolRestriction } from '@agimon-ai/po-core/extensionFile';\nimport type { PoHeadlessToolRestriction } from '@agimon-ai/po-core/headless';\nimport { PO_VOICE_AUTO_MODE_ID } from '@agimon-ai/po-core/voiceTools';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\n\nexport default defineToolRestriction({\n when: { state: { 'minor-mode': PO_VOICE_AUTO_MODE_ID } },\n excludedTools: [ASK_USER_QUESTION_TOOL_NAME],\n} satisfies PoHeadlessToolRestriction);\n","import { type Static, Type } from 'typebox';\n\nimport {\n MAX_QUESTIONS,\n MIN_OPTIONS,\n MAX_OPTIONS,\n MAX_HEADER_LENGTH,\n MAX_LABEL_LENGTH,\n} from '../constants/questionnaire';\n\nexport const OptionSchema = Type.Object(\n {\n label: Type.String({\n maxLength: MAX_LABEL_LENGTH,\n description: 'MAX 60 CHARACTERS, hard limit. Concise option label shown to the user, ideally 1-5 words.',\n }),\n description: Type.String({\n description: 'Explanation of what the option means and its important trade-offs.',\n }),\n preview: Type.Optional(\n Type.String({ description: 'Optional markdown preview rendered while this option is focused.' }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionSchema = Type.Object(\n {\n question: Type.String({ description: 'The complete, clear question to ask the user.' }),\n header: Type.String({\n maxLength: MAX_HEADER_LENGTH,\n description: 'MAX 16 CHARACTERS, short label used when navigating several questions.',\n }),\n options: Type.Array(OptionSchema, {\n minItems: MIN_OPTIONS,\n maxItems: MAX_OPTIONS,\n description: 'The 2-4 choices offered for this question.',\n }),\n multiSelect: Type.Optional(\n Type.Boolean({ description: 'Allow more than one option to be selected.', default: false }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionParamsSchema = Type.Object(\n {\n questions: Type.Array(QuestionSchema, {\n minItems: 1,\n maxItems: MAX_QUESTIONS,\n description: 'Questions to ask the user (1-4 questions).',\n }),\n },\n { additionalProperties: false },\n);\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport type { AnswerKind, QuestionAnswer } from '../../types/questionnaire';\n\n/**\n * Read a whole-questionnaire answer envelope back against the questions it claims to answer.\n *\n * The envelope arrives on a select response, which is reader input rather than\n * anything the tool authored, so nothing in it is taken on trust: an index has\n * to name a question that was asked, a chosen label has to be one that\n * question offered, and free text is capped. An envelope that fails is refused\n * rather than repaired, because a half-read answer set reaches the model as\n * the reader's decision and there is no way to tell from there that part of it\n * was invented.\n *\n * `preview` is the clearest case: it is read from the option the tool\n * declared, never from the wire, so the model cannot be shown a preview that\n * was never offered.\n *\n * Host-neutral by construction: the questions and the payload both arrive as\n * arguments, so the same arguments always produce the same result.\n */\nexport const MAX_ANSWER_TEXT_LENGTH = 4000;\n\nexport type AnswerEnvelopeResult =\n | { readonly ok: true; readonly answers: QuestionAnswer[] }\n | { readonly ok: false; readonly reason: string };\n\nconst ANSWER_KINDS: readonly string[] = ['option', 'custom', 'multi'] satisfies readonly AnswerKind[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalNote(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed.slice(0, MAX_ANSWER_TEXT_LENGTH);\n}\n\n/** One answer, rebuilt from the question it names rather than copied off the wire. */\nfunction readAnswer(entry: unknown, params: QuestionParams, seen: Set<number>): QuestionAnswer | string {\n if (!isRecord(entry)) return 'an answer was not an object';\n const questionIndex = entry.questionIndex;\n if (typeof questionIndex !== 'number' || !Number.isInteger(questionIndex)) {\n return 'an answer carried no question index';\n }\n const question: QuestionData | undefined = params.questions[questionIndex];\n if (question === undefined) return `no question ${String(questionIndex)} was asked`;\n if (seen.has(questionIndex)) return `question ${String(questionIndex)} was answered twice`;\n seen.add(questionIndex);\n\n const kind = entry.kind;\n if (typeof kind !== 'string' || !ANSWER_KINDS.includes(kind)) {\n return `question ${String(questionIndex)} carried an unknown answer kind`;\n }\n const notes = optionalNote(entry.notes);\n const base = { questionIndex, question: question.question, ...(notes === undefined ? {} : { notes }) };\n\n if (kind === 'multi') {\n const selected = entry.selected;\n if (!Array.isArray(selected)) return `question ${String(questionIndex)} selected nothing readable`;\n const labels = selected.filter((label): label is string => typeof label === 'string');\n if (labels.length !== selected.length) return `question ${String(questionIndex)} selected a non-label`;\n if (labels.some((label) => !question.options.some((option) => option.label === label))) {\n return `question ${String(questionIndex)} selected an option it was not offered`;\n }\n return { ...base, kind: 'multi', answer: null, selected: [...new Set(labels)] };\n }\n\n if (kind === 'option') {\n const label = entry.answer;\n const chosen = typeof label === 'string' ? question.options.find((option) => option.label === label) : undefined;\n if (chosen === undefined) return `question ${String(questionIndex)} chose an option it was not offered`;\n // From the option the tool declared, so a preview the reader never saw\n // cannot be put in the model's mouth.\n return { ...base, kind: 'option', answer: chosen.label, ...(chosen.preview ? { preview: chosen.preview } : {}) };\n }\n\n const typed = entry.answer;\n if (typeof typed !== 'string') return `question ${String(questionIndex)} typed nothing readable`;\n return { ...base, kind: 'custom', answer: typed.slice(0, MAX_ANSWER_TEXT_LENGTH) };\n}\n\nexport function readAnswerEnvelope(payload: unknown, params: QuestionParams): AnswerEnvelopeResult {\n if (!isRecord(payload) || !Array.isArray(payload.answers)) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length === 0) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length > params.questions.length) return { ok: false, reason: 'more answers than questions' };\n\n const seen = new Set<number>();\n const answers: QuestionAnswer[] = [];\n for (const entry of payload.answers) {\n const answer = readAnswer(entry, params, seen);\n if (typeof answer === 'string') return { ok: false, reason: answer };\n answers.push(answer);\n }\n // The model reads them in the order it asked, whatever order they arrived in.\n answers.sort((left, right) => left.questionIndex - right.questionIndex);\n return { ok: true, answers };\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport { decodeAnswerEnvelope } from '../../types/askUserWire';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire';\nimport { readAnswerEnvelope } from '../answerEnvelope';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nexport interface QuestionnaireInteraction {\n select(title: string, options: readonly string[], signal?: AbortSignal): Promise<string | undefined>;\n input(title: string, placeholder: string, signal?: AbortSignal): Promise<string | undefined>;\n}\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\n/**\n * What a rich client replied, when it replied with the whole questionnaire.\n *\n * A client that can see the whole call answers every question at once rather\n * than walking this loop, so any select response may be an envelope instead of\n * a label. A response that is not one leaves the loop exactly as it was, which\n * is what keeps a plain RPC client working unchanged.\n */\ninterface EnvelopeSink {\n params: QuestionParams;\n /** Set once a select response carried the whole questionnaire; the loop stops and returns it. */\n result?: QuestionnaireResult;\n}\n\n/** True when this response was the whole set, in which case the sink now holds the answer. */\nfunction takeEnvelope(sink: EnvelopeSink, selected: string | undefined): boolean {\n const read = decodeAnswerEnvelope(selected);\n if (read.kind === 'absent') return false;\n if (read.kind === 'malformed') {\n sink.result = { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n }\n const envelope = readAnswerEnvelope(read.payload, sink.params);\n sink.result = envelope.ok\n ? { answers: envelope.answers, cancelled: false }\n : { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n}\n\nasync function askForCustomAnswer(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await interaction.input(question.question, 'Type your answer', signal);\n if (answer === undefined) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'custom',\n answer,\n };\n}\n\nasync function askSingleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await interaction.select(question.question, labels, signal);\n if (selected === undefined) return undefined;\n if (takeEnvelope(sink, selected)) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n const option = question.options.find((candidate) => candidate.label === selected);\n if (!option) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n };\n}\n\nfunction multiChoiceLabels(question: QuestionData, selected: ReadonlySet<string>): string[] {\n const options = question.options.map((option) => {\n const marker = selected.has(option.label) ? '[x]' : '[ ]';\n return `${marker} ${option.label}`;\n });\n return [...options, CUSTOM_LABEL, DONE_LABEL];\n}\n\nfunction stripSelectionMarker(value: string): string {\n return value.replace(/^\\[[ x]\\]\\s/u, '');\n}\n\nasync function askMultipleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await interaction.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n signal,\n );\n if (choice === undefined) return undefined;\n if (takeEnvelope(sink, choice)) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n if (choice === DONE_LABEL) {\n return {\n questionIndex,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: [...selected],\n };\n }\n const label = stripSelectionMarker(choice);\n if (!question.options.some((option) => option.label === label)) continue;\n if (selected.has(label)) selected.delete(label);\n else selected.add(label);\n }\n return undefined;\n}\n\nexport async function runQuestionnaire(\n interaction: QuestionnaireInteraction,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\n const sink: EnvelopeSink = { params };\n for (let questionIndex = 0; questionIndex < params.questions.length; questionIndex += 1) {\n if (signal?.aborted) return cancelled(answers);\n const question = params.questions[questionIndex];\n if (!question) continue;\n const answer = question.multiSelect\n ? await askMultipleChoice(interaction, question, questionIndex, sink, signal)\n : await askSingleChoice(interaction, question, questionIndex, sink, signal);\n // A client that answered everything at once ends the walk here; the loop\n // is only how a client that can see one question at a time gets through.\n if (sink.result !== undefined) {\n const whole = sink.result;\n if (!whole.cancelled) {\n reportProgress?.({ ...whole, answers: whole.answers.map((item) => ({ ...item })) });\n }\n return whole;\n }\n if (!answer) return cancelled(answers);\n answers.push(answer);\n reportProgress?.({ answers: answers.map((item) => ({ ...item })), cancelled: false });\n }\n return { answers, cancelled: false };\n}\n","import type { QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../../types/questionnaire';\n\nexport const DECLINE_MESSAGE = 'User declined to answer questions';\nexport const ENVELOPE_PREFIX = 'User has answered your questions:';\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nexport const NO_INPUT_PLACEHOLDER = '(no input)';\n\nexport function buildToolResult(text: string, details: QuestionnaireResult): ToolTextResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function formatAnswer(answer: QuestionAnswer): string {\n if (answer.kind === 'multi') {\n return answer.selected && answer.selected.length > 0 ? answer.selected.join(', ') : NO_INPUT_PLACEHOLDER;\n }\n return answer.answer && answer.answer.length > 0 ? answer.answer : NO_INPUT_PLACEHOLDER;\n}\n\nexport function buildAnswerSegment(answer: QuestionAnswer): string {\n const sections = [`\"${answer.question}\"=\"${formatAnswer(answer)}\"`];\n if (answer.preview) sections.push(`selected preview: ${answer.preview}`);\n if (answer.notes) sections.push(`user notes: ${answer.notes}`);\n return `${sections.join('. ')}.`;\n}\n\nexport function buildQuestionnaireResponse(\n result: QuestionnaireResult | null | undefined,\n params: QuestionParams,\n): ToolTextResult {\n if (!result || result.cancelled) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result?.answers ?? [], cancelled: true });\n }\n\n const segments = params.questions.flatMap((_question, index) => {\n const answer = result.answers.find((candidate) => candidate.questionIndex === index);\n return answer ? [buildAnswerSegment(answer)] : [];\n });\n if (segments.length === 0) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n }\n\n return buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(' ')} ${ENVELOPE_SUFFIX}`, result);\n}\n","import { MAX_QUESTIONS, MIN_OPTIONS, RESERVED_LABELS } from '../../constants/questionnaire';\nimport { type QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionnaireError } from '../../types/questionnaire';\n\nexport const ERROR_NO_QUESTIONS = 'Error: At least one question is required';\nexport const ERROR_TOO_MANY_QUESTIONS = `Error: At most ${MAX_QUESTIONS} questions are allowed per invocation`;\nexport const ERROR_DUPLICATE_QUESTION = 'Error: Question text must be unique within an invocation';\nexport const ERROR_TOO_FEW_OPTIONS = `Error: Each question requires at least ${MIN_OPTIONS} options`;\nexport const ERROR_RESERVED_LABEL = `Error: Option label is reserved (${RESERVED_LABELS.join(', ')})`;\nexport const ERROR_DUPLICATE_OPTION_LABEL = 'Error: Option labels must be unique within a question';\n\nexport type ValidationResult = { ok: true } | { ok: false; error: QuestionnaireError; message: string };\n\nconst reservedLabels = new Set<string>(RESERVED_LABELS);\n\nexport function validateQuestionnaire(params: QuestionParams): ValidationResult {\n if (params.questions.length === 0) {\n return { ok: false, error: 'no_questions', message: ERROR_NO_QUESTIONS };\n }\n if (params.questions.length > MAX_QUESTIONS) {\n return { ok: false, error: 'too_many_questions', message: ERROR_TOO_MANY_QUESTIONS };\n }\n\n const seenQuestions = new Set<string>();\n for (const question of params.questions) {\n if (seenQuestions.has(question.question)) {\n return { ok: false, error: 'duplicate_question', message: ERROR_DUPLICATE_QUESTION };\n }\n seenQuestions.add(question.question);\n }\n\n for (const question of params.questions) {\n if (question.options.length < MIN_OPTIONS) {\n return { ok: false, error: 'empty_options', message: ERROR_TOO_FEW_OPTIONS };\n }\n const seenLabels = new Set<string>();\n for (const option of question.options) {\n if (reservedLabels.has(option.label)) {\n return { ok: false, error: 'reserved_label', message: ERROR_RESERVED_LABEL };\n }\n if (seenLabels.has(option.label)) {\n return {\n ok: false,\n error: 'duplicate_option_label',\n message: ERROR_DUPLICATE_OPTION_LABEL,\n };\n }\n seenLabels.add(option.label);\n }\n }\n\n return { ok: true };\n}\n","import { type PoHeadlessTool, type PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\nimport type { QuestionParams } from '../../../../../schemas/questionnaire';\nimport { QuestionParamsSchema } from '../../../../../schemas/questionnaire';\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\nimport { runQuestionnaire, type QuestionnaireInteraction } from '../../../../../services/questionnaireService';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../../../../services/responseService';\nimport { validateQuestionnaire } from '../../../../../services/validationService';\nimport type { QuestionnaireResult } from '../../../../../types/questionnaire';\n\nfunction output(result: {\n content: Array<{ type: 'text'; text: string }>;\n details: QuestionnaireResult;\n}): PoHeadlessToolResult {\n return { content: result.content, details: result.details };\n}\n\nexport function createAskUserHeadlessTool(\n coordinator?: QuestionnaireCoordinator,\n): PoHeadlessTool<typeof QuestionParamsSchema> {\n const tool: PoHeadlessTool<typeof QuestionParamsSchema> = {\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: 'Ask the user one or more structured questions during execution.',\n parameters: QuestionParamsSchema,\n promptSnippet: 'Ask the user structured questions when requirements are ambiguous',\n executionMode: 'serial',\n async execute(_toolCallId, parameters, signal, _onUpdate, executionContext) {\n const params = parameters as QuestionParams;\n const validation = validateQuestionnaire(params);\n if (!validation.ok) {\n return output(buildToolResult(validation.message, { answers: [], cancelled: true, error: validation.error }));\n }\n const activeCoordinator = coordinator ?? new QuestionnaireCoordinator();\n try {\n const result = await activeCoordinator.enqueue(async ({ signal: requestSignal, reportProgress }) => {\n requestSignal.throwIfAborted();\n const interaction: QuestionnaireInteraction = {\n select: async (title, options, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n {\n kind: 'select',\n title,\n options: options.map((label) => ({ label, value: label })),\n },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n input: async (title, placeholder, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n { kind: 'input', title, message: placeholder },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n };\n const questionnaire = await runQuestionnaire(interaction, params, requestSignal, reportProgress);\n requestSignal.throwIfAborted();\n return questionnaire;\n }, signal);\n return output(buildQuestionnaireResponse(result, params));\n } finally {\n if (coordinator === undefined) {\n activeCoordinator.shutdown();\n await activeCoordinator.waitForIdle();\n }\n }\n },\n };\n return tool;\n}\n","import { defineServerTool, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport type { UserFeedbackServerScope } from '../_lib/root.server';\nimport { createAskUserHeadlessTool } from '../_tools/askUserHeadless';\n\nexport default defineServerTool((context: WithRoot<PoServerPluginContext, UserFeedbackServerScope>) =>\n createAskUserHeadlessTool(context.root.coordinator),\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverPlugin';\nimport { composeRootHooks, composeRootInjects } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport resourcePoUseUserFeedback from '../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server';\nimport toolRestrictionAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server';\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-user-feedback',\n providerOverrides: composeServerProviderOverrides(rootSession),\n injects: composeRootInjects({ session: [rootSession] }),\n session: async (context) => {\n const scopeSession = await context.scopeRoot('session', rootSession, context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get resources(): PoServerSessionPlugin['resources'] { return [at(resourcePoUseUserFeedback, contextSession)]; },\n get toolRestrictions(): PoServerSessionPlugin['toolRestrictions'] { return [at(toolRestrictionAskUserQuestion, contextSession)]; },\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'ask_user_question' }, at(toolAskUserQuestion, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): PoServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default serverPlugin;\n"],"mappings":";;;;;;AAqBA,SAAS,gBAAgB,OAAwC;CAC/D,OAAO;EACL,SAAS,MAAM,cAAc,KAAK,YAAY,EAAE,GAAG,OAAO,EAAE;EAC5D,WAAW;CACb;AACF;AAEA,IAAa,2BAAb,MAAsC;CACpC,UAAyC,CAAC;CAC1C,0BAA2B,IAAI,IAAkC;CACjE;CACA,gBAAwB;CACxB,YAAoB;CAEpB,QAAQ,QAA6B,gBAA4D;EAC/F,IAAI,CAAC,KAAK,aAAa,gBAAgB,SACrC,OAAO,QAAQ,QAAQ;GAAE,SAAS,CAAC;GAAG,WAAW;EAAK,CAAC;EAGzD,OAAO,IAAI,SAA8B,SAAS,WAAW;GAC3D,MAAM,QAAoB;IACxB,IAAI,KAAK;IACT;IACA,YAAY,IAAI,gBAAgB;IAChC,GAAI,iBAAiB,EAAE,eAAe,IAAI,CAAC;IAC3C,eAAe,CAAC;IAChB,OAAO;IACP;IACA;GACF;GACA,KAAK,iBAAiB;GACtB,MAAM,cAAoB,KAAK,OAAO,KAAK;GAC3C,gBAAgB,iBAAiB,SAAS,OAAO,EAAE,MAAM,KAAK,CAAC;GAC/D,MAAM,4BAA4B,gBAAgB,oBAAoB,SAAS,KAAK;GACpF,KAAK,QAAQ,KAAK,KAAK;GACvB,KAAK,UAAU;EACjB,CAAC;CACH;CAEA,WAAiB;EACf,IAAI,CAAC,KAAK,aAAa,CAAC,KAAK,UAAU,KAAK,QAAQ,WAAW,GAAG;EAClE,KAAK,YAAY;EACjB,IAAI,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM;EACxC,OAAO,KAAK,QAAQ,SAAS,GAAG;GAC9B,MAAM,QAAQ,KAAK,QAAQ;GAC3B,IAAI,CAAC,OAAO;GACZ,KAAK,OAAO,KAAK;EACnB;CACF;;CAGA,MAAM,cAA6B;EACjC,OAAO,KAAK,QAAQ,OAAO,GAAG,MAAM,QAAQ,WAAW,KAAK,OAAO;CACrE;CAEA,YAA0B;EACxB,IAAI,CAAC,KAAK,aAAa,KAAK,QAAQ;EACpC,MAAM,QAAQ,KAAK,QAAQ,MAAM;EACjC,IAAI,CAAC,OAAO;EACZ,MAAM,QAAQ;EACd,KAAK,SAAS;EACd,MAAM,kBAAkB,WAAsC;GAC5D,IAAI,KAAK,QAAQ,OAAO,MAAM,MAAM,MAAM,UAAU,UAAU;GAC9D,MAAM,gBAAgB,OAAO,QAAQ,KAAK,YAAY,EAAE,GAAG,OAAO,EAAE;EACtE;EACA,IAAI;EACJ,IAAI;GACF,YAAY,MAAM,OAAO;IAAE,QAAQ,MAAM,WAAW;IAAQ;GAAe,CAAC;EAC9E,SAAS,OAAO;GACd,YAAY,QAAQ,OAAO,KAAK;EAClC;EACA,KAAK,QAAQ,IAAI,SAAS;EAC1B,UACG,MACE,WAAW,KAAK,SAAS,OAAO,MAAM,IACtC,UAAmB,KAAK,KAAK,OAAO,KAAK,CAC5C,CAAC,CACA,cAAc,KAAK,QAAQ,OAAO,SAAS,CAAC;CACjD;CAEA,SAAiB,OAAmB,QAAmC;EACrE,IAAI,KAAK,QAAQ,OAAO,MAAM,MAAM,MAAM,UAAU,UAAU;EAC9D,KAAK,cAAc,KAAK;EACxB,MAAM,QAAQ,MAAM;EACpB,KAAK,UAAU;CACjB;CAEA,KAAa,OAAmB,OAAsB;EACpD,IAAI,KAAK,QAAQ,OAAO,MAAM,MAAM,MAAM,UAAU,UAAU;EAC9D,KAAK,cAAc,KAAK;EACxB,MAAM,OAAO,KAAK;EAClB,KAAK,UAAU;CACjB;CAEA,OAAe,OAAyB;EACtC,IAAI,MAAM,UAAU,WAAW;EAC/B,IAAI,MAAM,UAAU,UAAU;GAC5B,MAAM,QAAQ,KAAK,QAAQ,QAAQ,KAAK;GACxC,KAAK,QAAQ,OAAO,OAAO,CAAC;GAC5B,MAAM,QAAQ;GACd,MAAM,sBAAsB;GAC5B,MAAM,WAAW,MAAM;GACvB,MAAM,QAAQ,gBAAgB,KAAK,CAAC;GACpC;EACF;EAEA,MAAM,SAAS,gBAAgB,KAAK;EACpC,KAAK,cAAc,KAAK;EACxB,MAAM,WAAW,MAAM;EACvB,MAAM,QAAQ,MAAM;EACpB,KAAK,UAAU;CACjB;CAEA,cAAsB,OAAyB;EAC7C,MAAM,QAAQ;EACd,MAAM,sBAAsB;EAC5B,KAAK,SAAS,KAAA;CAChB;AACF;;;ACtIA,MAAM,OAAO,YAAY,aAAoC;CAC3D,MAAM,cAAc,IAAI,yBAAyB;CACjD,OAAO;EACL,OAAO,EAAE,YAAY;EACrB,WAAW,YAAY;GACrB,YAAY,SAAS;GACrB,MAAM,YAAY,YAAY;EAChC;CACF;AACF,CAAC;;;ACVD,IAAA,sBAAe,WAAWA,IAAY;;;ACAtC,IAAA,mCAAe,eAAe;CAC5B,MAAM;CACN,MAAM;CACN,MAAM;CACN,YAAY,oBAAoB,YAAY,KAAK,2CAA2C;AAC9F,CAAC;;;ACHD,MAAa,kBAAkB;CAAC;CAAS;CAAmB;AAAM;;;ACLlE,MAAa,8BAA8B;;;ACK3C,IAAA,mCAAe,sBAAsB;CACnC,MAAM,EAAE,OAAO,EAAE,cAAc,sBAAsB,EAAE;CACvD,eAAe,CAAC,2BAA2B;AAC7C,CAAqC;;;ACCrC,MAAa,eAAe,KAAK,OAC/B;CACE,OAAO,KAAK,OAAO;EACjB,WAAA;EACA,aAAa;CACf,CAAC;CACD,aAAa,KAAK,OAAO,EACvB,aAAa,qEACf,CAAC;CACD,SAAS,KAAK,SACZ,KAAK,OAAO,EAAE,aAAa,mEAAmE,CAAC,CACjG;AACF,GACA,EAAE,sBAAsB,MAAM,CAChC;AAEA,MAAa,iBAAiB,KAAK,OACjC;CACE,UAAU,KAAK,OAAO,EAAE,aAAa,gDAAgD,CAAC;CACtF,QAAQ,KAAK,OAAO;EAClB,WAAA;EACA,aAAa;CACf,CAAC;CACD,SAAS,KAAK,MAAM,cAAc;EAChC,UAAA;EACA,UAAA;EACA,aAAa;CACf,CAAC;CACD,aAAa,KAAK,SAChB,KAAK,QAAQ;EAAE,aAAa;EAA8C,SAAS;CAAM,CAAC,CAC5F;AACF,GACA,EAAE,sBAAsB,MAAM,CAChC;AAEA,MAAa,uBAAuB,KAAK,OACvC,EACE,WAAW,KAAK,MAAM,gBAAgB;CACpC,UAAU;CACV,UAAA;CACA,aAAa;AACf,CAAC,EACH,GACA,EAAE,sBAAsB,MAAM,CAChC;ACpBA,SAAgB,qBAAqB,OAA+C;CAClF,IAAI,UAAU,KAAA,KAAa,CAAC,MAAM,WAAA,iBAAiC,GAAG,OAAO,EAAE,MAAM,SAAS;CAC9F,IAAI,MAAM,SAAA,OAA6B,OAAO,EAAE,MAAM,YAAY;CAClE,IAAI;EACF,OAAO;GAAE,MAAM;GAAY,SAAS,KAAK,MAAM,MAAM,MAAM,EAA6B,CAAC;EAAE;CAC7F,QAAQ;EAGN,OAAO,EAAE,MAAM,YAAY;CAC7B;AACF;;;;;;;;;;;;;;;;;;;;;ACvBA,MAAa,yBAAyB;AAMtC,MAAM,eAAkC;CAAC;CAAU;CAAU;AAAO;AAEpE,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,aAAa,OAAoC;CACxD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,YAAY,KAAK,KAAA,IAAY,QAAQ,MAAM,GAAG,sBAAsB;AAC7E;;AAGA,SAAS,WAAW,OAAgB,QAAwB,MAA4C;CACtG,IAAI,CAAC,SAAS,KAAK,GAAG,OAAO;CAC7B,MAAM,gBAAgB,MAAM;CAC5B,IAAI,OAAO,kBAAkB,YAAY,CAAC,OAAO,UAAU,aAAa,GACtE,OAAO;CAET,MAAM,WAAqC,OAAO,UAAU;CAC5D,IAAI,aAAa,KAAA,GAAW,OAAO,eAAe,OAAO,aAAa,EAAE;CACxE,IAAI,KAAK,IAAI,aAAa,GAAG,OAAO,YAAY,OAAO,aAAa,EAAE;CACtE,KAAK,IAAI,aAAa;CAEtB,MAAM,OAAO,MAAM;CACnB,IAAI,OAAO,SAAS,YAAY,CAAC,aAAa,SAAS,IAAI,GACzD,OAAO,YAAY,OAAO,aAAa,EAAE;CAE3C,MAAM,QAAQ,aAAa,MAAM,KAAK;CACtC,MAAM,OAAO;EAAE;EAAe,UAAU,SAAS;EAAU,GAAI,UAAU,KAAA,IAAY,CAAC,IAAI,EAAE,MAAM;CAAG;CAErG,IAAI,SAAS,SAAS;EACpB,MAAM,WAAW,MAAM;EACvB,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO,YAAY,OAAO,aAAa,EAAE;EACvE,MAAM,SAAS,SAAS,QAAQ,UAA2B,OAAO,UAAU,QAAQ;EACpF,IAAI,OAAO,WAAW,SAAS,QAAQ,OAAO,YAAY,OAAO,aAAa,EAAE;EAChF,IAAI,OAAO,MAAM,UAAU,CAAC,SAAS,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,CAAC,GACnF,OAAO,YAAY,OAAO,aAAa,EAAE;EAE3C,OAAO;GAAE,GAAG;GAAM,MAAM;GAAS,QAAQ;GAAM,UAAU,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC;EAAE;CAChF;CAEA,IAAI,SAAS,UAAU;EACrB,MAAM,QAAQ,MAAM;EACpB,MAAM,SAAS,OAAO,UAAU,WAAW,SAAS,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,IAAI,KAAA;EACvG,IAAI,WAAW,KAAA,GAAW,OAAO,YAAY,OAAO,aAAa,EAAE;EAGnE,OAAO;GAAE,GAAG;GAAM,MAAM;GAAU,QAAQ,OAAO;GAAO,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;EAAG;CACjH;CAEA,MAAM,QAAQ,MAAM;CACpB,IAAI,OAAO,UAAU,UAAU,OAAO,YAAY,OAAO,aAAa,EAAE;CACxE,OAAO;EAAE,GAAG;EAAM,MAAM;EAAU,QAAQ,MAAM,MAAM,GAAG,sBAAsB;CAAE;AACnF;AAEA,SAAgB,mBAAmB,SAAkB,QAA8C;CACjG,IAAI,CAAC,SAAS,OAAO,KAAK,CAAC,MAAM,QAAQ,QAAQ,OAAO,GAAG,OAAO;EAAE,IAAI;EAAO,QAAQ;CAA0B;CACjH,IAAI,QAAQ,QAAQ,WAAW,GAAG,OAAO;EAAE,IAAI;EAAO,QAAQ;CAA0B;CACxF,IAAI,QAAQ,QAAQ,SAAS,OAAO,UAAU,QAAQ,OAAO;EAAE,IAAI;EAAO,QAAQ;CAA8B;CAEhH,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAA4B,CAAC;CACnC,KAAK,MAAM,SAAS,QAAQ,SAAS;EACnC,MAAM,SAAS,WAAW,OAAO,QAAQ,IAAI;EAC7C,IAAI,OAAO,WAAW,UAAU,OAAO;GAAE,IAAI;GAAO,QAAQ;EAAO;EACnE,QAAQ,KAAK,MAAM;CACrB;CAEA,QAAQ,MAAM,MAAM,UAAU,KAAK,gBAAgB,MAAM,aAAa;CACtE,OAAO;EAAE,IAAI;EAAM;CAAQ;AAC7B;;;AC7FA,MAAM,eAAe;AACrB,MAAM,aAAa;AAOnB,SAAS,UAAU,SAAgD;CACjE,OAAO;EAAE;EAAS,WAAW;CAAK;AACpC;;AAiBA,SAAS,aAAa,MAAoB,UAAuC;CAC/E,MAAM,OAAO,qBAAqB,QAAQ;CAC1C,IAAI,KAAK,SAAS,UAAU,OAAO;CACnC,IAAI,KAAK,SAAS,aAAa;EAC7B,KAAK,SAAS;GAAE,SAAS,CAAC;GAAG,WAAW;GAAM,OAAO;EAAoB;EACzE,OAAO;CACT;CACA,MAAM,WAAW,mBAAmB,KAAK,SAAS,KAAK,MAAM;CAC7D,KAAK,SAAS,SAAS,KACnB;EAAE,SAAS,SAAS;EAAS,WAAW;CAAM,IAC9C;EAAE,SAAS,CAAC;EAAG,WAAW;EAAM,OAAO;CAAoB;CAC/D,OAAO;AACT;AAEA,eAAe,mBACb,aACA,UACA,eACA,QACqC;CACrC,MAAM,SAAS,MAAM,YAAY,MAAM,SAAS,UAAU,oBAAoB,MAAM;CACpF,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;CACjC,OAAO;EACL;EACA,UAAU,SAAS;EACnB,MAAM;EACN;CACF;AACF;AAEA,eAAe,gBACb,aACA,UACA,eACA,MACA,QACqC;CACrC,MAAM,SAAS,CAAC,GAAG,SAAS,QAAQ,KAAK,WAAW,OAAO,KAAK,GAAG,YAAY;CAC/E,MAAM,WAAW,MAAM,YAAY,OAAO,SAAS,UAAU,QAAQ,MAAM;CAC3E,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;CACnC,IAAI,aAAa,MAAM,QAAQ,GAAG,OAAO,KAAA;CACzC,IAAI,aAAa,cACf,OAAO,mBAAmB,aAAa,UAAU,eAAe,MAAM;CAExE,MAAM,SAAS,SAAS,QAAQ,MAAM,cAAc,UAAU,UAAU,QAAQ;CAChF,IAAI,CAAC,QAAQ,OAAO,KAAA;CACpB,OAAO;EACL;EACA,UAAU,SAAS;EACnB,MAAM;EACN,QAAQ,OAAO;EACf,GAAI,OAAO,UAAU,EAAE,SAAS,OAAO,QAAQ,IAAI,CAAC;CACtD;AACF;AAEA,SAAS,kBAAkB,UAAwB,UAAyC;CAK1F,OAAO;EAAC,GAJQ,SAAS,QAAQ,KAAK,WAAW;GAE/C,OAAO,GADQ,SAAS,IAAI,OAAO,KAAK,IAAI,QAAQ,MACnC,GAAG,OAAO;EAC7B,CACiB;EAAG;EAAc;CAAU;AAC9C;AAEA,SAAS,qBAAqB,OAAuB;CACnD,OAAO,MAAM,QAAQ,gBAAgB,EAAE;AACzC;AAEA,eAAe,kBACb,aACA,UACA,eACA,MACA,QACqC;CACrC,MAAM,2BAAW,IAAI,IAAY;CACjC,OAAO,CAAC,QAAQ,SAAS;EACvB,MAAM,SAAS,MAAM,YAAY,OAC/B,GAAG,SAAS,SAAS,+BACrB,kBAAkB,UAAU,QAAQ,GACpC,MACF;EACA,IAAI,WAAW,KAAA,GAAW,OAAO,KAAA;EACjC,IAAI,aAAa,MAAM,MAAM,GAAG,OAAO,KAAA;EACvC,IAAI,WAAW,cACb,OAAO,mBAAmB,aAAa,UAAU,eAAe,MAAM;EAExE,IAAI,WAAW,YACb,OAAO;GACL;GACA,UAAU,SAAS;GACnB,MAAM;GACN,QAAQ;GACR,UAAU,CAAC,GAAG,QAAQ;EACxB;EAEF,MAAM,QAAQ,qBAAqB,MAAM;EACzC,IAAI,CAAC,SAAS,QAAQ,MAAM,WAAW,OAAO,UAAU,KAAK,GAAG;EAChE,IAAI,SAAS,IAAI,KAAK,GAAG,SAAS,OAAO,KAAK;OACzC,SAAS,IAAI,KAAK;CACzB;AAEF;AAEA,eAAsB,iBACpB,aACA,QACA,QACA,gBAC8B;CAC9B,MAAM,UAA4B,CAAC;CACnC,MAAM,OAAqB,EAAE,OAAO;CACpC,KAAK,IAAI,gBAAgB,GAAG,gBAAgB,OAAO,UAAU,QAAQ,iBAAiB,GAAG;EACvF,IAAI,QAAQ,SAAS,OAAO,UAAU,OAAO;EAC7C,MAAM,WAAW,OAAO,UAAU;EAClC,IAAI,CAAC,UAAU;EACf,MAAM,SAAS,SAAS,cACpB,MAAM,kBAAkB,aAAa,UAAU,eAAe,MAAM,MAAM,IAC1E,MAAM,gBAAgB,aAAa,UAAU,eAAe,MAAM,MAAM;EAG5E,IAAI,KAAK,WAAW,KAAA,GAAW;GAC7B,MAAM,QAAQ,KAAK;GACnB,IAAI,CAAC,MAAM,WACT,iBAAiB;IAAE,GAAG;IAAO,SAAS,MAAM,QAAQ,KAAK,UAAU,EAAE,GAAG,KAAK,EAAE;GAAE,CAAC;GAEpF,OAAO;EACT;EACA,IAAI,CAAC,QAAQ,OAAO,UAAU,OAAO;EACrC,QAAQ,KAAK,MAAM;EACnB,iBAAiB;GAAE,SAAS,QAAQ,KAAK,UAAU,EAAE,GAAG,KAAK,EAAE;GAAG,WAAW;EAAM,CAAC;CACtF;CACA,OAAO;EAAE;EAAS,WAAW;CAAM;AACrC;;;ACjKA,MAAa,kBAAkB;AAC/B,MAAa,kBAAkB;AAC/B,MAAa,kBAAkB;AAC/B,MAAa,uBAAuB;AAEpC,SAAgB,gBAAgB,MAAc,SAA8C;CAC1F,OAAO;EAAE,SAAS,CAAC;GAAE,MAAM;GAAQ;EAAK,CAAC;EAAG;CAAQ;AACtD;AAEA,SAAgB,aAAa,QAAgC;CAC3D,IAAI,OAAO,SAAS,SAClB,OAAO,OAAO,YAAY,OAAO,SAAS,SAAS,IAAI,OAAO,SAAS,KAAK,IAAI,IAAI;CAEtF,OAAO,OAAO,UAAU,OAAO,OAAO,SAAS,IAAI,OAAO,SAAS;AACrE;AAEA,SAAgB,mBAAmB,QAAgC;CACjE,MAAM,WAAW,CAAC,IAAI,OAAO,SAAS,KAAK,aAAa,MAAM,EAAE,EAAE;CAClE,IAAI,OAAO,SAAS,SAAS,KAAK,qBAAqB,OAAO,SAAS;CACvE,IAAI,OAAO,OAAO,SAAS,KAAK,eAAe,OAAO,OAAO;CAC7D,OAAO,GAAG,SAAS,KAAK,IAAI,EAAE;AAChC;AAEA,SAAgB,2BACd,QACA,QACgB;CAChB,IAAI,CAAC,UAAU,OAAO,WACpB,OAAO,gBAAgB,iBAAiB;EAAE,SAAS,QAAQ,WAAW,CAAC;EAAG,WAAW;CAAK,CAAC;CAG7F,MAAM,WAAW,OAAO,UAAU,SAAS,WAAW,UAAU;EAC9D,MAAM,SAAS,OAAO,QAAQ,MAAM,cAAc,UAAU,kBAAkB,KAAK;EACnF,OAAO,SAAS,CAAC,mBAAmB,MAAM,CAAC,IAAI,CAAC;CAClD,CAAC;CACD,IAAI,SAAS,WAAW,GACtB,OAAO,gBAAgB,iBAAiB;EAAE,SAAS,OAAO;EAAS,WAAW;CAAK,CAAC;CAGtF,OAAO,gBAAgB,GAAG,gBAAgB,GAAG,SAAS,KAAK,GAAG,EAAE,GAAG,mBAAmB,MAAM;AAC9F;;;ACvCA,MAAa,qBAAqB;AAClC,MAAa,2BAA2B;AACxC,MAAa,2BAA2B;AACxC,MAAa,wBAAwB;AACrC,MAAa,uBAAuB,oCAAoC,gBAAgB,KAAK,IAAI,EAAE;AACnG,MAAa,+BAA+B;AAI5C,MAAM,iBAAiB,IAAI,IAAY,eAAe;AAEtD,SAAgB,sBAAsB,QAA0C;CAC9E,IAAI,OAAO,UAAU,WAAW,GAC9B,OAAO;EAAE,IAAI;EAAO,OAAO;EAAgB,SAAS;CAAmB;CAEzE,IAAI,OAAO,UAAU,SAAA,GACnB,OAAO;EAAE,IAAI;EAAO,OAAO;EAAsB,SAAS;CAAyB;CAGrF,MAAM,gCAAgB,IAAI,IAAY;CACtC,KAAK,MAAM,YAAY,OAAO,WAAW;EACvC,IAAI,cAAc,IAAI,SAAS,QAAQ,GACrC,OAAO;GAAE,IAAI;GAAO,OAAO;GAAsB,SAAS;EAAyB;EAErF,cAAc,IAAI,SAAS,QAAQ;CACrC;CAEA,KAAK,MAAM,YAAY,OAAO,WAAW;EACvC,IAAI,SAAS,QAAQ,SAAA,GACnB,OAAO;GAAE,IAAI;GAAO,OAAO;GAAiB,SAAS;EAAsB;EAE7E,MAAM,6BAAa,IAAI,IAAY;EACnC,KAAK,MAAM,UAAU,SAAS,SAAS;GACrC,IAAI,eAAe,IAAI,OAAO,KAAK,GACjC,OAAO;IAAE,IAAI;IAAO,OAAO;IAAkB,SAAS;GAAqB;GAE7E,IAAI,WAAW,IAAI,OAAO,KAAK,GAC7B,OAAO;IACL,IAAI;IACJ,OAAO;IACP,SAAS;GACX;GAEF,WAAW,IAAI,OAAO,KAAK;EAC7B;CACF;CAEA,OAAO,EAAE,IAAI,KAAK;AACpB;;;ACzCA,SAAS,OAAO,QAGS;CACvB,OAAO;EAAE,SAAS,OAAO;EAAS,SAAS,OAAO;CAAQ;AAC5D;AAEA,SAAgB,0BACd,aAC6C;CAqD7C,OAAO;EAnDL,MAAM;EACN,OAAO;EACP,aAAa;EACb,YAAY;EACZ,eAAe;EACf,eAAe;EACf,MAAM,QAAQ,aAAa,YAAY,QAAQ,WAAW,kBAAkB;GAC1E,MAAM,SAAS;GACf,MAAM,aAAa,sBAAsB,MAAM;GAC/C,IAAI,CAAC,WAAW,IACd,OAAO,OAAO,gBAAgB,WAAW,SAAS;IAAE,SAAS,CAAC;IAAG,WAAW;IAAM,OAAO,WAAW;GAAM,CAAC,CAAC;GAE9G,MAAM,oBAAoB,eAAe,IAAI,yBAAyB;GACtE,IAAI;IA6BF,OAAO,OAAO,2BAA2B,MA5BpB,kBAAkB,QAAQ,OAAO,EAAE,QAAQ,eAAe,qBAAqB;KAClG,cAAc,eAAe;KAuB7B,MAAM,gBAAgB,MAAM,iBAAiB;MArB3C,QAAQ,OAAO,OAAO,SAAS,sBAAsB;OACnD,cAAc,eAAe;OAC7B,MAAM,WAAW,MAAM,iBAAiB,OAAO,QAC7C;QACE,MAAM;QACN;QACA,SAAS,QAAQ,KAAK,WAAW;SAAE;SAAO,OAAO;QAAM,EAAE;OAC3D,GACA,iBACF;OACA,OAAO,OAAO,aAAa,WAAW,WAAW,KAAA;MACnD;MACA,OAAO,OAAO,OAAO,aAAa,sBAAsB;OACtD,cAAc,eAAe;OAC7B,MAAM,WAAW,MAAM,iBAAiB,OAAO,QAC7C;QAAE,MAAM;QAAS;QAAO,SAAS;OAAY,GAC7C,iBACF;OACA,OAAO,OAAO,aAAa,WAAW,WAAW,KAAA;MACnD;KAEqD,GAAG,QAAQ,eAAe,cAAc;KAC/F,cAAc,eAAe;KAC7B,OAAO;IACT,GAAG,MAAM,GACwC,MAAM,CAAC;GAC1D,UAAU;IACR,IAAI,gBAAgB,KAAA,GAAW;KAC7B,kBAAkB,SAAS;KAC3B,MAAM,kBAAkB,YAAY;IACtC;GACF;EACF;CAEQ;AACZ;;;ACpEA,IAAA,iCAAe,kBAAkB,YAC/B,0BAA0B,QAAQ,KAAK,WAAW,CACpD;;;ACGA,MAAM,MAAY,OAA0B,YAC1C,OAAO,UAAU,aAAc,MAAwB,OAAO,IAAI;AACpE,MAAM,OAAU,UAAmC,UAChD,UAAU,KAAA,IAAY,KAAA,IAAY;CAAE,GAAG;CAAU,GAAI;AAAiB;AAEzE,MAAa,eAAe,mBAAmB;CAC7C,MAAM;CACN,mBAAmB,+BAA+BC,mBAAW;CAC7D,SAAS,mBAAmB,EAAE,SAAS,CAACA,mBAAW,EAAE,CAAC;CACtD,SAAS,OAAO,YAAY;EAC1B,MAAM,eAAe,MAAM,QAAQ,UAAU,WAAWA,qBAAa,OAAO;EAC5E,MAAM,iBAAiB;GAAE,GAAG;GAAS,MAAM,aAAa;EAAM;EAC9D,OAAO;GACL,GAAG,iBAAiB,YAAY;GAChC,IAAI,YAAgD;IAAE,OAAO,CAAC,GAAGC,kCAA2B,cAAc,CAAC;GAAG;GAC9G,IAAI,mBAA8D;IAAE,OAAO,CAAC,GAAGC,kCAAgC,cAAc,CAAC;GAAG;GACjI,IAAI,QAAwC;IAAE,OAAO,CAAC,IAAI,EAAE,MAAM,oBAAoB,GAAG,GAAGC,gCAAqB,cAAc,CAAC,CAAC;GAAG;GACpI,UAAU,CAAC,GAAI,aAAa,YAAY,CAAC,CAAE;GAC3C,IAAI,aAAkD;IAAE,OAAO,CAAC,GAAI,aAAa,cAAc,CAAC,CAAE;GAAG;EACvG;CACF;AACF,CAAC"}
1
+ {"version":3,"file":"server.mjs","names":["contribution","rootSession","resourcePoUseUserFeedback","toolRestrictionAskUserQuestion","toolAskUserQuestion"],"sources":["../../src/services/questionnaireCoordinator/index.ts","../../src/extensions/workspaces/sessions/(backend)/_lib/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/root.server.ts","../../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server.ts","../../src/constants/questionnaire.ts","../../src/constants/tool.ts","../../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server.ts","../../src/schemas/questionnaire.ts","../../src/types/askUserWire.ts","../../src/services/answerEnvelope/index.ts","../../src/services/questionnaireService/index.ts","../../src/services/responseService/index.ts","../../src/services/validationService/index.ts","../../src/extensions/workspaces/sessions/(backend)/_tools/askUserHeadless.ts","../../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server.ts","../../generated/server.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../../types/questionnaire';\n\nexport interface QuestionnaireRunContext {\n signal: AbortSignal;\n reportProgress: (result: QuestionnaireResult) => void;\n}\n\nexport type QuestionnaireRunner = (context: QuestionnaireRunContext) => Promise<QuestionnaireResult>;\n\ninterface QueueEntry {\n id: number;\n runner: QuestionnaireRunner;\n controller: AbortController;\n externalSignal?: AbortSignal;\n removeAbortListener?: () => void;\n latestAnswers: QuestionnaireResult['answers'];\n state: 'queued' | 'active' | 'settled';\n resolve(result: QuestionnaireResult): void;\n reject(error: unknown): void;\n}\n\nfunction cancelledResult(entry: QueueEntry): QuestionnaireResult {\n return {\n answers: entry.latestAnswers.map((answer) => ({ ...answer })),\n cancelled: true,\n };\n}\n\nexport class QuestionnaireCoordinator {\n private readonly pending: QueueEntry[] = [];\n private readonly running = new Set<Promise<QuestionnaireResult>>();\n private active: QueueEntry | undefined;\n private nextRequestId = 1;\n private accepting = true;\n\n enqueue(runner: QuestionnaireRunner, externalSignal?: AbortSignal): Promise<QuestionnaireResult> {\n if (!this.accepting || externalSignal?.aborted) {\n return Promise.resolve({ answers: [], cancelled: true });\n }\n\n return new Promise<QuestionnaireResult>((resolve, reject) => {\n const entry: QueueEntry = {\n id: this.nextRequestId,\n runner,\n controller: new AbortController(),\n ...(externalSignal ? { externalSignal } : {}),\n latestAnswers: [],\n state: 'queued',\n resolve,\n reject,\n };\n this.nextRequestId += 1;\n const abort = (): void => this.cancel(entry);\n externalSignal?.addEventListener('abort', abort, { once: true });\n entry.removeAbortListener = () => externalSignal?.removeEventListener('abort', abort);\n this.pending.push(entry);\n this.startNext();\n });\n }\n\n shutdown(): void {\n if (!this.accepting && !this.active && this.pending.length === 0) return;\n this.accepting = false;\n if (this.active) this.cancel(this.active);\n while (this.pending.length > 0) {\n const entry = this.pending[0];\n if (!entry) break;\n this.cancel(entry);\n }\n }\n\n /** Waits for every started presenter to observe cancellation and finish. */\n async waitForIdle(): Promise<void> {\n while (this.running.size > 0) await Promise.allSettled(this.running);\n }\n\n private startNext(): void {\n if (!this.accepting || this.active) return;\n const entry = this.pending.shift();\n if (!entry) return;\n entry.state = 'active';\n this.active = entry;\n const reportProgress = (result: QuestionnaireResult): void => {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n entry.latestAnswers = result.answers.map((answer) => ({ ...answer }));\n };\n let operation: Promise<QuestionnaireResult>;\n try {\n operation = entry.runner({ signal: entry.controller.signal, reportProgress });\n } catch (error) {\n operation = Promise.reject(error);\n }\n this.running.add(operation);\n void operation\n .then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n )\n .finally(() => this.running.delete(operation));\n }\n\n private complete(entry: QueueEntry, result: QuestionnaireResult): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.resolve(result);\n this.startNext();\n }\n\n private fail(entry: QueueEntry, error: unknown): void {\n if (this.active?.id !== entry.id || entry.state !== 'active') return;\n this.releaseActive(entry);\n entry.reject(error);\n this.startNext();\n }\n\n private cancel(entry: QueueEntry): void {\n if (entry.state === 'settled') return;\n if (entry.state === 'queued') {\n const index = this.pending.indexOf(entry);\n this.pending.splice(index, 1);\n entry.state = 'settled';\n entry.removeAbortListener?.();\n entry.controller.abort();\n entry.resolve(cancelledResult(entry));\n return;\n }\n\n const result = cancelledResult(entry);\n this.releaseActive(entry);\n entry.controller.abort();\n entry.resolve(result);\n this.startNext();\n }\n\n private releaseActive(entry: QueueEntry): void {\n entry.state = 'settled';\n entry.removeAbortListener?.();\n this.active = undefined;\n }\n}\n","import { defineRoot, runCleanups } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\n\nconst root = defineRoot((_context: PoServerPluginContext) => {\n const coordinator = new QuestionnaireCoordinator();\n return {\n value: { coordinator },\n onDispose: async () => {\n await runCleanups(\n [() => coordinator.shutdown(), () => coordinator.waitForIdle()],\n 'User-feedback cleanup failed.',\n );\n },\n };\n});\n\nexport type UserFeedbackServerScope = Awaited<ReturnType<typeof root>>['value'];\nexport default root;\n","import { defineRoot } from '@agimon-ai/po-core/extensionFile';\n\nimport contribution from './_lib/root.server';\n\nexport default defineRoot(contribution);\n","import { defineResource } from '@agimon-ai/po-core/extensionFile';\nimport { PO_HELP_WHEN } from '@agimon-ai/po-core/help';\nimport { readPackageResource } from '@agimon-ai/po-core/serverPlugin';\n\nexport default defineResource({\n name: 'po-use-user-feedback',\n when: PO_HELP_WHEN,\n kind: 'skill' as const,\n read: () => readPackageResource(import.meta.url, 'src/prompts/po-use-user-feedback/SKILL.md'),\n});\n","export const MAX_QUESTIONS = 4;\nexport const MIN_OPTIONS = 2;\nexport const MAX_OPTIONS = 4;\nexport const MAX_HEADER_LENGTH = 16;\nexport const MAX_LABEL_LENGTH = 60;\n\nexport const RESERVED_LABELS = ['Other', 'Type something.', 'Next'] as const;\n","import { MAX_OPTIONS, MAX_QUESTIONS, MIN_OPTIONS } from './questionnaire';\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\nexport const ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nexport const 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.';\nexport const 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.';\nexport const ERROR_SESSION_INACTIVE = 'Error: the questionnaire session is no longer active.';\n","import { defineToolRestriction } from '@agimon-ai/po-core/extensionFile';\nimport type { PoHeadlessToolRestriction } from '@agimon-ai/po-core/headless';\nimport { PO_VOICE_AUTO_MODE_ID } from '@agimon-ai/po-core/voiceTools';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\n\nexport default defineToolRestriction({\n when: { state: { 'minor-mode': PO_VOICE_AUTO_MODE_ID } },\n excludedTools: [ASK_USER_QUESTION_TOOL_NAME],\n} satisfies PoHeadlessToolRestriction);\n","import { type Static, Type } from 'typebox';\n\nimport {\n MAX_QUESTIONS,\n MIN_OPTIONS,\n MAX_OPTIONS,\n MAX_HEADER_LENGTH,\n MAX_LABEL_LENGTH,\n} from '../constants/questionnaire';\n\nexport const OptionSchema = Type.Object(\n {\n label: Type.String({\n maxLength: MAX_LABEL_LENGTH,\n description: 'MAX 60 CHARACTERS, hard limit. Concise option label shown to the user, ideally 1-5 words.',\n }),\n description: Type.String({\n description: 'Explanation of what the option means and its important trade-offs.',\n }),\n preview: Type.Optional(\n Type.String({ description: 'Optional markdown preview rendered while this option is focused.' }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionSchema = Type.Object(\n {\n question: Type.String({ description: 'The complete, clear question to ask the user.' }),\n header: Type.String({\n maxLength: MAX_HEADER_LENGTH,\n description: 'MAX 16 CHARACTERS, short label used when navigating several questions.',\n }),\n options: Type.Array(OptionSchema, {\n minItems: MIN_OPTIONS,\n maxItems: MAX_OPTIONS,\n description: 'The 2-4 choices offered for this question.',\n }),\n multiSelect: Type.Optional(\n Type.Boolean({ description: 'Allow more than one option to be selected.', default: false }),\n ),\n },\n { additionalProperties: false },\n);\n\nexport const QuestionParamsSchema = Type.Object(\n {\n questions: Type.Array(QuestionSchema, {\n minItems: 1,\n maxItems: MAX_QUESTIONS,\n description: 'Questions to ask the user (1-4 questions).',\n }),\n },\n { additionalProperties: false },\n);\n\nexport type OptionData = Static<typeof OptionSchema>;\nexport type QuestionData = Static<typeof QuestionSchema>;\nexport type QuestionParams = Static<typeof QuestionParamsSchema>;\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport type { AnswerKind, QuestionAnswer } from '../../types/questionnaire';\n\n/**\n * Read a whole-questionnaire answer envelope back against the questions it claims to answer.\n *\n * The envelope arrives on a select response, which is reader input rather than\n * anything the tool authored, so nothing in it is taken on trust: an index has\n * to name a question that was asked, a chosen label has to be one that\n * question offered, and free text is capped. An envelope that fails is refused\n * rather than repaired, because a half-read answer set reaches the model as\n * the reader's decision and there is no way to tell from there that part of it\n * was invented.\n *\n * `preview` is the clearest case: it is read from the option the tool\n * declared, never from the wire, so the model cannot be shown a preview that\n * was never offered.\n *\n * Host-neutral by construction: the questions and the payload both arrive as\n * arguments, so the same arguments always produce the same result.\n */\nexport const MAX_ANSWER_TEXT_LENGTH = 4000;\n\nexport type AnswerEnvelopeResult =\n | { readonly ok: true; readonly answers: QuestionAnswer[] }\n | { readonly ok: false; readonly reason: string };\n\nconst ANSWER_KINDS: readonly string[] = ['option', 'custom', 'multi'] satisfies readonly AnswerKind[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction optionalNote(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined;\n const trimmed = value.trim();\n return trimmed === '' ? undefined : trimmed.slice(0, MAX_ANSWER_TEXT_LENGTH);\n}\n\n/** One answer, rebuilt from the question it names rather than copied off the wire. */\nfunction readAnswer(entry: unknown, params: QuestionParams, seen: Set<number>): QuestionAnswer | string {\n if (!isRecord(entry)) return 'an answer was not an object';\n const questionIndex = entry.questionIndex;\n if (typeof questionIndex !== 'number' || !Number.isInteger(questionIndex)) {\n return 'an answer carried no question index';\n }\n const question: QuestionData | undefined = params.questions[questionIndex];\n if (question === undefined) return `no question ${String(questionIndex)} was asked`;\n if (seen.has(questionIndex)) return `question ${String(questionIndex)} was answered twice`;\n seen.add(questionIndex);\n\n const kind = entry.kind;\n if (typeof kind !== 'string' || !ANSWER_KINDS.includes(kind)) {\n return `question ${String(questionIndex)} carried an unknown answer kind`;\n }\n const notes = optionalNote(entry.notes);\n const base = { questionIndex, question: question.question, ...(notes === undefined ? {} : { notes }) };\n\n if (kind === 'multi') {\n const selected = entry.selected;\n if (!Array.isArray(selected)) return `question ${String(questionIndex)} selected nothing readable`;\n const labels = selected.filter((label): label is string => typeof label === 'string');\n if (labels.length !== selected.length) return `question ${String(questionIndex)} selected a non-label`;\n if (labels.some((label) => !question.options.some((option) => option.label === label))) {\n return `question ${String(questionIndex)} selected an option it was not offered`;\n }\n return { ...base, kind: 'multi', answer: null, selected: [...new Set(labels)] };\n }\n\n if (kind === 'option') {\n const label = entry.answer;\n const chosen = typeof label === 'string' ? question.options.find((option) => option.label === label) : undefined;\n if (chosen === undefined) return `question ${String(questionIndex)} chose an option it was not offered`;\n // From the option the tool declared, so a preview the reader never saw\n // cannot be put in the model's mouth.\n return { ...base, kind: 'option', answer: chosen.label, ...(chosen.preview ? { preview: chosen.preview } : {}) };\n }\n\n const typed = entry.answer;\n if (typeof typed !== 'string') return `question ${String(questionIndex)} typed nothing readable`;\n return { ...base, kind: 'custom', answer: typed.slice(0, MAX_ANSWER_TEXT_LENGTH) };\n}\n\nexport function readAnswerEnvelope(payload: unknown, params: QuestionParams): AnswerEnvelopeResult {\n if (!isRecord(payload) || !Array.isArray(payload.answers)) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length === 0) return { ok: false, reason: 'no answers were carried' };\n if (payload.answers.length > params.questions.length) return { ok: false, reason: 'more answers than questions' };\n\n const seen = new Set<number>();\n const answers: QuestionAnswer[] = [];\n for (const entry of payload.answers) {\n const answer = readAnswer(entry, params, seen);\n if (typeof answer === 'string') return { ok: false, reason: answer };\n answers.push(answer);\n }\n // The model reads them in the order it asked, whatever order they arrived in.\n answers.sort((left, right) => left.questionIndex - right.questionIndex);\n return { ok: true, answers };\n}\n","import type { QuestionData, QuestionParams } from '../../schemas/questionnaire';\nimport { decodeAnswerEnvelope } from '../../types/askUserWire';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire';\nimport { readAnswerEnvelope } from '../answerEnvelope';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nexport interface QuestionnaireInteraction {\n select(title: string, options: readonly string[], signal?: AbortSignal): Promise<string | undefined>;\n input(title: string, placeholder: string, signal?: AbortSignal): Promise<string | undefined>;\n}\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\n/**\n * What a rich client replied, when it replied with the whole questionnaire.\n *\n * A client that can see the whole call answers every question at once rather\n * than walking this loop, so any select response may be an envelope instead of\n * a label. A response that is not one leaves the loop exactly as it was, which\n * is what keeps a plain RPC client working unchanged.\n */\ninterface EnvelopeSink {\n params: QuestionParams;\n /** Set once a select response carried the whole questionnaire; the loop stops and returns it. */\n result?: QuestionnaireResult;\n}\n\n/** True when this response was the whole set, in which case the sink now holds the answer. */\nfunction takeEnvelope(sink: EnvelopeSink, selected: string | undefined): boolean {\n const read = decodeAnswerEnvelope(selected);\n if (read.kind === 'absent') return false;\n if (read.kind === 'malformed') {\n sink.result = { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n }\n const envelope = readAnswerEnvelope(read.payload, sink.params);\n sink.result = envelope.ok\n ? { answers: envelope.answers, cancelled: false }\n : { answers: [], cancelled: true, error: 'malformed_answers' };\n return true;\n}\n\nasync function askForCustomAnswer(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await interaction.input(question.question, 'Type your answer', signal);\n if (answer === undefined) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'custom',\n answer,\n };\n}\n\nasync function askSingleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await interaction.select(question.question, labels, signal);\n if (selected === undefined) return undefined;\n if (takeEnvelope(sink, selected)) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n const option = question.options.find((candidate) => candidate.label === selected);\n if (!option) return undefined;\n return {\n questionIndex,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n };\n}\n\nfunction multiChoiceLabels(question: QuestionData, selected: ReadonlySet<string>): string[] {\n const options = question.options.map((option) => {\n const marker = selected.has(option.label) ? '[x]' : '[ ]';\n return `${marker} ${option.label}`;\n });\n return [...options, CUSTOM_LABEL, DONE_LABEL];\n}\n\nfunction stripSelectionMarker(value: string): string {\n return value.replace(/^\\[[ x]\\]\\s/u, '');\n}\n\nasync function askMultipleChoice(\n interaction: QuestionnaireInteraction,\n question: QuestionData,\n questionIndex: number,\n sink: EnvelopeSink,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await interaction.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n signal,\n );\n if (choice === undefined) return undefined;\n if (takeEnvelope(sink, choice)) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(interaction, question, questionIndex, signal);\n }\n if (choice === DONE_LABEL) {\n return {\n questionIndex,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: [...selected],\n };\n }\n const label = stripSelectionMarker(choice);\n if (!question.options.some((option) => option.label === label)) continue;\n if (selected.has(label)) selected.delete(label);\n else selected.add(label);\n }\n return undefined;\n}\n\nexport async function runQuestionnaire(\n interaction: QuestionnaireInteraction,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\n const sink: EnvelopeSink = { params };\n for (let questionIndex = 0; questionIndex < params.questions.length; questionIndex += 1) {\n if (signal?.aborted) return cancelled(answers);\n const question = params.questions[questionIndex];\n if (!question) continue;\n const answer = question.multiSelect\n ? await askMultipleChoice(interaction, question, questionIndex, sink, signal)\n : await askSingleChoice(interaction, question, questionIndex, sink, signal);\n // A client that answered everything at once ends the walk here; the loop\n // is only how a client that can see one question at a time gets through.\n if (sink.result !== undefined) {\n const whole = sink.result;\n if (!whole.cancelled) {\n reportProgress?.({ ...whole, answers: whole.answers.map((item) => ({ ...item })) });\n }\n return whole;\n }\n if (!answer) return cancelled(answers);\n answers.push(answer);\n reportProgress?.({ answers: answers.map((item) => ({ ...item })), cancelled: false });\n }\n return { answers, cancelled: false };\n}\n","import type { QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../../types/questionnaire';\n\nexport const DECLINE_MESSAGE = 'User declined to answer questions';\nexport const ENVELOPE_PREFIX = 'User has answered your questions:';\nexport const ENVELOPE_SUFFIX = \"You can now continue with the user's answers in mind.\";\nexport const NO_INPUT_PLACEHOLDER = '(no input)';\n\nexport function buildToolResult(text: string, details: QuestionnaireResult): ToolTextResult {\n return { content: [{ type: 'text', text }], details };\n}\n\nexport function formatAnswer(answer: QuestionAnswer): string {\n if (answer.kind === 'multi') {\n return answer.selected && answer.selected.length > 0 ? answer.selected.join(', ') : NO_INPUT_PLACEHOLDER;\n }\n return answer.answer && answer.answer.length > 0 ? answer.answer : NO_INPUT_PLACEHOLDER;\n}\n\nexport function buildAnswerSegment(answer: QuestionAnswer): string {\n const sections = [`\"${answer.question}\"=\"${formatAnswer(answer)}\"`];\n if (answer.preview) sections.push(`selected preview: ${answer.preview}`);\n if (answer.notes) sections.push(`user notes: ${answer.notes}`);\n return `${sections.join('. ')}.`;\n}\n\nexport function buildQuestionnaireResponse(\n result: QuestionnaireResult | null | undefined,\n params: QuestionParams,\n): ToolTextResult {\n if (!result || result.cancelled) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result?.answers ?? [], cancelled: true });\n }\n\n const segments = params.questions.flatMap((_question, index) => {\n const answer = result.answers.find((candidate) => candidate.questionIndex === index);\n return answer ? [buildAnswerSegment(answer)] : [];\n });\n if (segments.length === 0) {\n return buildToolResult(DECLINE_MESSAGE, { answers: result.answers, cancelled: true });\n }\n\n return buildToolResult(`${ENVELOPE_PREFIX} ${segments.join(' ')} ${ENVELOPE_SUFFIX}`, result);\n}\n","import { MAX_QUESTIONS, MIN_OPTIONS, RESERVED_LABELS } from '../../constants/questionnaire';\nimport { type QuestionParams } from '../../schemas/questionnaire';\nimport type { QuestionnaireError } from '../../types/questionnaire';\n\nexport const ERROR_NO_QUESTIONS = 'Error: At least one question is required';\nexport const ERROR_TOO_MANY_QUESTIONS = `Error: At most ${MAX_QUESTIONS} questions are allowed per invocation`;\nexport const ERROR_DUPLICATE_QUESTION = 'Error: Question text must be unique within an invocation';\nexport const ERROR_TOO_FEW_OPTIONS = `Error: Each question requires at least ${MIN_OPTIONS} options`;\nexport const ERROR_RESERVED_LABEL = `Error: Option label is reserved (${RESERVED_LABELS.join(', ')})`;\nexport const ERROR_DUPLICATE_OPTION_LABEL = 'Error: Option labels must be unique within a question';\n\nexport type ValidationResult = { ok: true } | { ok: false; error: QuestionnaireError; message: string };\n\nconst reservedLabels = new Set<string>(RESERVED_LABELS);\n\nexport function validateQuestionnaire(params: QuestionParams): ValidationResult {\n if (params.questions.length === 0) {\n return { ok: false, error: 'no_questions', message: ERROR_NO_QUESTIONS };\n }\n if (params.questions.length > MAX_QUESTIONS) {\n return { ok: false, error: 'too_many_questions', message: ERROR_TOO_MANY_QUESTIONS };\n }\n\n const seenQuestions = new Set<string>();\n for (const question of params.questions) {\n if (seenQuestions.has(question.question)) {\n return { ok: false, error: 'duplicate_question', message: ERROR_DUPLICATE_QUESTION };\n }\n seenQuestions.add(question.question);\n }\n\n for (const question of params.questions) {\n if (question.options.length < MIN_OPTIONS) {\n return { ok: false, error: 'empty_options', message: ERROR_TOO_FEW_OPTIONS };\n }\n const seenLabels = new Set<string>();\n for (const option of question.options) {\n if (reservedLabels.has(option.label)) {\n return { ok: false, error: 'reserved_label', message: ERROR_RESERVED_LABEL };\n }\n if (seenLabels.has(option.label)) {\n return {\n ok: false,\n error: 'duplicate_option_label',\n message: ERROR_DUPLICATE_OPTION_LABEL,\n };\n }\n seenLabels.add(option.label);\n }\n }\n\n return { ok: true };\n}\n","import { type PoHeadlessTool, type PoHeadlessToolResult } from '@agimon-ai/po-core/headless';\n\nimport { ASK_USER_QUESTION_TOOL_NAME } from '../../../../../constants/tool';\nimport type { QuestionParams } from '../../../../../schemas/questionnaire';\nimport { QuestionParamsSchema } from '../../../../../schemas/questionnaire';\nimport { QuestionnaireCoordinator } from '../../../../../services/questionnaireCoordinator';\nimport { runQuestionnaire, type QuestionnaireInteraction } from '../../../../../services/questionnaireService';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../../../../services/responseService';\nimport { validateQuestionnaire } from '../../../../../services/validationService';\nimport type { QuestionnaireResult } from '../../../../../types/questionnaire';\n\nfunction output(result: {\n content: Array<{ type: 'text'; text: string }>;\n details: QuestionnaireResult;\n}): PoHeadlessToolResult {\n return { content: result.content, details: result.details };\n}\n\nexport function createAskUserHeadlessTool(\n coordinator?: QuestionnaireCoordinator,\n): PoHeadlessTool<typeof QuestionParamsSchema> {\n const tool: PoHeadlessTool<typeof QuestionParamsSchema> = {\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: 'Ask the user one or more structured questions during execution.',\n parameters: QuestionParamsSchema,\n promptSnippet: 'Ask the user structured questions when requirements are ambiguous',\n executionMode: 'serial',\n async execute(_toolCallId, parameters, signal, _onUpdate, executionContext) {\n const params = parameters as QuestionParams;\n const validation = validateQuestionnaire(params);\n if (!validation.ok) {\n return output(buildToolResult(validation.message, { answers: [], cancelled: true, error: validation.error }));\n }\n const activeCoordinator = coordinator ?? new QuestionnaireCoordinator();\n try {\n const result = await activeCoordinator.enqueue(async ({ signal: requestSignal, reportProgress }) => {\n requestSignal.throwIfAborted();\n const interaction: QuestionnaireInteraction = {\n select: async (title, options, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n {\n kind: 'select',\n title,\n options: options.map((label) => ({ label, value: label })),\n },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n input: async (title, placeholder, interactionSignal) => {\n requestSignal.throwIfAborted();\n const response = await executionContext.client.request(\n { kind: 'input', title, message: placeholder },\n interactionSignal,\n );\n return typeof response === 'string' ? response : undefined;\n },\n };\n const questionnaire = await runQuestionnaire(interaction, params, requestSignal, reportProgress);\n requestSignal.throwIfAborted();\n return questionnaire;\n }, signal);\n return output(buildQuestionnaireResponse(result, params));\n } finally {\n if (coordinator === undefined) {\n activeCoordinator.shutdown();\n await activeCoordinator.waitForIdle();\n }\n }\n },\n };\n return tool;\n}\n","import { defineServerTool, type WithRoot } from '@agimon-ai/po-core/extensionFile';\nimport type { PoServerPluginContext } from '@agimon-ai/po-core/serverPlugin';\n\nimport type { UserFeedbackServerScope } from '../_lib/root.server';\nimport { createAskUserHeadlessTool } from '../_tools/askUserHeadless';\n\nexport default defineServerTool((context: WithRoot<PoServerPluginContext, UserFeedbackServerScope>) =>\n createAskUserHeadlessTool(context.root.coordinator),\n);\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineServerPlugin, type PoServerSessionPlugin } from '@agimon-ai/po-core/serverPlugin';\nimport { composeServerProviderOverrides } from '@agimon-ai/po-core/serverPlugin';\nimport { composeRootHooks, composeRootInjects } from '@agimon-ai/po-core/extensionFile';\n\nimport rootSession from '../src/extensions/workspaces/sessions/(backend)/root.server';\nimport resourcePoUseUserFeedback from '../src/extensions/workspaces/sessions/(backend)/resource/poUseUserFeedback.server';\nimport toolRestrictionAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool-restriction/askUserQuestion.server';\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(backend)/tool/askUserQuestion.server';\n\ntype Factory<T, C> = (context: C) => T;\nconst at = <T, C>(value: T | Factory<T, C>, context: C): T =>\n typeof value === 'function' ? (value as Factory<T, C>)(context) : value;\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const serverPlugin = defineServerPlugin({\n name: '@agimon-ai/po-user-feedback',\n providerOverrides: composeServerProviderOverrides(rootSession),\n injects: composeRootInjects({ session: [rootSession] }),\n session: async (context) => {\n const scopeSession = await context.scopeRoot('session', rootSession, context);\n const contextSession = { ...context, root: scopeSession.value };\n return {\n ...composeRootHooks(scopeSession),\n get resources(): PoServerSessionPlugin['resources'] { return [at(resourcePoUseUserFeedback, contextSession)]; },\n get toolRestrictions(): PoServerSessionPlugin['toolRestrictions'] { return [at(toolRestrictionAskUserQuestion, contextSession)]; },\n get tools(): PoServerSessionPlugin['tools'] { return [via({ name: 'ask_user_question' }, at(toolAskUserQuestion, contextSession))]; },\n services: [...(scopeSession.services ?? [])],\n get activities(): PoServerSessionPlugin['activities'] { return [...(scopeSession.activities ?? [])]; },\n };\n },\n});\n\nexport default serverPlugin;\n"],"mappings":"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"}
@@ -332,7 +332,9 @@ function fieldKeys(commit) {
332
332
  function QuestionBody({ question, index, draft, cursor, onCursor, onDraft, onCommit }) {
333
333
  const entry = draftEntry(draft, index);
334
334
  const typing = entry.custom !== null;
335
- const preview = question.options[cursor]?.preview;
335
+ const option = question.options[cursor];
336
+ const preview = typing ? void 0 : option?.preview;
337
+ const hasPreviews = question.options.some((item) => item.preview);
336
338
  return /* @__PURE__ */ jsxs("div", {
337
339
  className: "flex min-w-0 flex-col gap-2",
338
340
  children: [
@@ -341,77 +343,98 @@ function QuestionBody({ question, index, draft, cursor, onCursor, onDraft, onCom
341
343
  className: "break-words text-base leading-relaxed text-po-hi",
342
344
  children: question.question
343
345
  }),
344
- /* @__PURE__ */ jsxs("div", {
345
- role: "listbox",
346
- "aria-label": "options",
347
- className: "flex min-w-0 flex-col",
348
- children: [question.options.map((option, optionIndex) => {
349
- const chosen = entry.selected.includes(option.label);
350
- return /* @__PURE__ */ jsxs(OptionRow, {
351
- active: optionIndex === cursor && !typing,
352
- "data-testid": `questionnaire-option-${String(optionIndex)}`,
353
- "data-chosen": chosen,
354
- onMouseEnter: () => onCursor(optionIndex),
355
- onClick: () => onDraft(chooseOption(draft, index, option.label, question.multiSelect), !question.multiSelect),
356
- className: "min-w-0 items-start gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal",
357
- children: [/* @__PURE__ */ jsx("span", {
358
- className: `mt-[2px] shrink-0 text-sm ${chosen ? "text-po-green" : "text-po-faint"}`,
359
- children: question.multiSelect ? chosen ? "[x]" : "[ ]" : chosen ? "●" : "○"
360
- }), /* @__PURE__ */ jsxs(OptionLabel, {
361
- className: "min-w-0 flex flex-col gap-0.5 text-left",
362
- children: [/* @__PURE__ */ jsx("span", {
363
- className: `break-words text-base font-medium ${chosen ? "font-semibold text-po-hi" : "text-po-text"}`,
364
- children: option.label
365
- }), option.description ? /* @__PURE__ */ jsx("span", {
366
- className: "break-words text-sm leading-relaxed whitespace-normal text-po-dim",
367
- children: option.description
368
- }) : null]
369
- })]
370
- }, option.label);
371
- }), /* @__PURE__ */ jsxs(OptionRow, {
372
- active: typing,
373
- "data-testid": "questionnaire-option-custom",
374
- onClick: () => onDraft(setCustom(draft, index, entry.custom ?? ""), false),
375
- className: "min-w-0 items-center gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal",
376
- children: [/* @__PURE__ */ jsx("span", {
377
- className: "shrink-0 text-sm text-po-faint",
378
- children: "✎"
379
- }), /* @__PURE__ */ jsx(OptionLabel, {
380
- className: "min-w-0 whitespace-normal text-po-text",
381
- children: CUSTOM_LABEL
382
- })]
383
- })]
384
- }),
385
- typing ? /* @__PURE__ */ jsxs("div", {
386
- className: "flex flex-col gap-1",
387
- children: [/* @__PURE__ */ jsx(SectionLabel, { children: "your answer" }), /* @__PURE__ */ jsx(Textarea, {
388
- "data-testid": "questionnaire-custom",
389
- autoFocus: true,
390
- rows: 2,
391
- value: entry.custom ?? "",
392
- placeholder: "type your answer…",
393
- onChange: (event) => onDraft(setCustom(draft, index, event.target.value), false),
394
- onKeyDown: fieldKeys(() => onCommit(true))
395
- })]
396
- }) : null,
397
- preview ? /* @__PURE__ */ jsxs("div", {
398
- "data-testid": "questionnaire-preview",
399
- className: "flex flex-col gap-1",
400
- children: [/* @__PURE__ */ jsx(SectionLabel, { children: "preview" }), /* @__PURE__ */ jsx("div", {
401
- className: "max-h-40 overflow-y-auto border-l-2 border-po-edge-magenta pl-3 text-base text-po-dim",
402
- children: /* @__PURE__ */ jsx(Markdown, { text: preview })
403
- })]
346
+ hasPreviews ? /* @__PURE__ */ jsx("p", {
347
+ className: "text-sm text-po-dim",
348
+ children: "Choose a concept to see its preview. Nothing is sent until you submit."
404
349
  }) : null,
405
350
  /* @__PURE__ */ jsxs("div", {
406
- className: "flex flex-col gap-1",
407
- children: [/* @__PURE__ */ jsx(SectionLabel, { children: "note (optional)" }), /* @__PURE__ */ jsx(Textarea, {
408
- "data-testid": "questionnaire-notes",
409
- rows: 1,
410
- value: entry.notes,
411
- placeholder: "anything the options do not cover",
412
- onChange: (event) => onDraft(setNotes(draft, index, event.target.value), false),
413
- onKeyDown: fieldKeys(() => onCommit(false))
414
- })]
351
+ className: hasPreviews ? "grid min-w-0 gap-4 md:grid-cols-3" : "min-w-0",
352
+ children: [/* @__PURE__ */ jsxs("div", {
353
+ className: "flex min-w-0 flex-col gap-3",
354
+ children: [
355
+ /* @__PURE__ */ jsxs("div", {
356
+ role: "listbox",
357
+ "aria-label": "options",
358
+ className: "flex min-w-0 flex-col",
359
+ children: [question.options.map((option, optionIndex) => {
360
+ const chosen = entry.selected.includes(option.label);
361
+ return /* @__PURE__ */ jsxs(OptionRow, {
362
+ active: optionIndex === cursor && !typing,
363
+ "data-testid": `questionnaire-option-${String(optionIndex)}`,
364
+ "data-chosen": chosen,
365
+ onFocus: () => onCursor(optionIndex),
366
+ onClick: () => {
367
+ onCursor(optionIndex);
368
+ onDraft(chooseOption(draft, index, option.label, question.multiSelect), !question.multiSelect);
369
+ },
370
+ className: "min-w-0 items-start gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal",
371
+ children: [/* @__PURE__ */ jsx("span", {
372
+ className: `mt-[2px] shrink-0 text-sm ${chosen ? "text-po-green" : "text-po-faint"}`,
373
+ children: question.multiSelect ? chosen ? "[x]" : "[ ]" : chosen ? "●" : "○"
374
+ }), /* @__PURE__ */ jsxs(OptionLabel, {
375
+ className: "min-w-0 flex flex-col gap-0.5 text-left",
376
+ children: [/* @__PURE__ */ jsx("span", {
377
+ className: `break-words text-base font-medium ${chosen ? "font-semibold text-po-hi" : "text-po-text"}`,
378
+ children: option.label
379
+ }), option.description ? /* @__PURE__ */ jsx("span", {
380
+ className: "break-words text-sm leading-relaxed whitespace-normal text-po-dim",
381
+ children: option.description
382
+ }) : null]
383
+ })]
384
+ }, option.label);
385
+ }), /* @__PURE__ */ jsxs(OptionRow, {
386
+ active: typing,
387
+ "data-testid": "questionnaire-option-custom",
388
+ onClick: () => onDraft(setCustom(draft, index, entry.custom ?? ""), false),
389
+ className: "min-w-0 items-center gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal",
390
+ children: [/* @__PURE__ */ jsx("span", {
391
+ className: "shrink-0 text-sm text-po-faint",
392
+ children: "✎"
393
+ }), /* @__PURE__ */ jsx(OptionLabel, {
394
+ className: "min-w-0 whitespace-normal text-po-text",
395
+ children: CUSTOM_LABEL
396
+ })]
397
+ })]
398
+ }),
399
+ typing ? /* @__PURE__ */ jsxs("div", {
400
+ className: "flex flex-col gap-1",
401
+ children: [/* @__PURE__ */ jsx(SectionLabel, { children: "your answer" }), /* @__PURE__ */ jsx(Textarea, {
402
+ "data-testid": "questionnaire-custom",
403
+ autoFocus: true,
404
+ rows: 2,
405
+ value: entry.custom ?? "",
406
+ placeholder: "type your answer…",
407
+ onChange: (event) => onDraft(setCustom(draft, index, event.target.value), false),
408
+ onKeyDown: fieldKeys(() => onCommit(true))
409
+ })]
410
+ }) : null,
411
+ /* @__PURE__ */ jsxs("div", {
412
+ className: "flex flex-col gap-1",
413
+ children: [/* @__PURE__ */ jsx(SectionLabel, { children: "note (optional)" }), /* @__PURE__ */ jsx(Textarea, {
414
+ "data-testid": "questionnaire-notes",
415
+ rows: 1,
416
+ value: entry.notes,
417
+ placeholder: "anything the options do not cover",
418
+ onChange: (event) => onDraft(setNotes(draft, index, event.target.value), false),
419
+ onKeyDown: fieldKeys(() => onCommit(false))
420
+ })]
421
+ })
422
+ ]
423
+ }), hasPreviews ? /* @__PURE__ */ jsxs("section", {
424
+ "data-testid": "questionnaire-preview",
425
+ "aria-label": "Concept preview",
426
+ className: "flex min-w-0 flex-col gap-3 rounded-md border border-po-border bg-po-bg p-3 md:col-span-2",
427
+ children: [/* @__PURE__ */ jsx("h3", {
428
+ className: "text-base font-semibold text-po-hi",
429
+ children: typing ? "Your answer" : option?.label
430
+ }), /* @__PURE__ */ jsx("div", {
431
+ className: "min-w-0 overflow-auto text-base text-po-text",
432
+ children: preview ? /* @__PURE__ */ jsx(Markdown, { text: preview }) : /* @__PURE__ */ jsx("p", {
433
+ className: "text-sm text-po-dim",
434
+ children: typing ? "Describe your own direction in the answer field." : "No preview for this option."
435
+ })
436
+ })]
437
+ }) : null]
415
438
  })
416
439
  ]
417
440
  });
@@ -441,6 +464,7 @@ function QuestionnairePrompt({ args, dialog, answer, cancel }) {
441
464
  const complete = isComplete(draft);
442
465
  const move = (next, answeredIndex, settles) => {
443
466
  setDraft(next);
467
+ if (questions[answeredIndex]?.options.some((option) => option.preview)) return;
444
468
  if (!settles) return;
445
469
  const pending = nextUnanswered(next, answeredIndex);
446
470
  if (pending >= 0 && pending !== answeredIndex) {
@@ -475,7 +499,7 @@ function QuestionnairePrompt({ args, dialog, answer, cancel }) {
475
499
  ref: frame,
476
500
  tabIndex: -1,
477
501
  "data-testid": "questionnaire",
478
- className: "flex min-w-0 flex-col overflow-hidden outline-none",
502
+ className: "flex min-w-0 flex-col overflow-hidden bg-po-bg outline-none",
479
503
  onKeyDown: (event) => {
480
504
  if (event.key === "Escape") {
481
505
  event.preventDefault();
@@ -535,7 +559,7 @@ function QuestionnairePrompt({ args, dialog, answer, cancel }) {
535
559
  }),
536
560
  /* @__PURE__ */ jsx(Separator, {}),
537
561
  /* @__PURE__ */ jsx("div", {
538
- className: "max-h-[46vh] min-w-0 overflow-x-hidden overflow-y-auto px-3 py-3 sm:px-3.5",
562
+ className: "max-h-[65vh] min-w-0 overflow-x-hidden overflow-y-auto px-3 py-3 sm:px-3.5",
539
563
  children: /* @__PURE__ */ jsx(QuestionBody, {
540
564
  question,
541
565
  index: current,
@@ -1 +1 @@
1
- {"version":3,"file":"web.mjs","names":["isRecord","asString","toolAskUserQuestion"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/askUserText.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/AskUserQuestionToolMessage.tsx","../../src/types/askUserWire.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/questionnaireDraft.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/QuestionnairePrompt.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.web.tsx","../../generated/web.ts"],"sourcesContent":["/**\n * Pure text shaping for the ask_user_question card, apart from React so the\n * unit suite covers it without a DOM. Args and details are wire JSON; the\n * shapes narrowed here mirror src/types/questionnaire.ts, which this browser\n * module may not import at runtime.\n */\n\ntype Args = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nexport interface AskCallSummary {\n /** \"N question(s)\", the TUI heading's purpose. */\n count: string;\n /** The question headers, comma separated; empty when none carry one. */\n headers: string;\n}\n\n/** The call header: how many questions, and their headers in parentheses. */\nexport function askCallSummary(args: Args): AskCallSummary {\n const questions = Array.isArray(args.questions) ? args.questions.filter(isRecord) : [];\n const headers = questions.map((question) => asString(question.header)).filter(Boolean);\n return {\n count: `${questions.length} question${questions.length === 1 ? '' : 's'}`,\n headers: headers.join(', '),\n };\n}\n\nexport interface AnsweredQuestion {\n question: string;\n answer: string;\n}\n\nexport type AskResultView =\n | { kind: 'cancelled' }\n | { kind: 'voice'; prompt: string }\n | { kind: 'answers'; answers: AnsweredQuestion[] }\n | { kind: 'text'; text: string };\n\n/**\n * The result body the TUI's renderResult chooses: a cancelled glyph, the\n * voice prompt when the questionnaire was handed to voice, the answered\n * list, or the raw text when the tool attached no details (an error path).\n */\nexport function askResultView(details: unknown, output: string): AskResultView {\n if (!isRecord(details) || !Array.isArray(details.answers)) return { kind: 'text', text: output };\n if (details.cancelled === true) return { kind: 'cancelled' };\n if (details.delivery === 'voice' && typeof details.voicePrompt === 'string') {\n return { kind: 'voice', prompt: details.voicePrompt };\n }\n const answers = details.answers.filter(isRecord).map((answer) => ({\n question: asString(answer.question),\n answer:\n answer.kind === 'multi'\n ? (Array.isArray(answer.selected) ? answer.selected : []).filter((v) => typeof v === 'string').join(', ')\n : asString(answer.answer),\n }));\n return { kind: 'answers', answers };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/po-core/web';\nimport {\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n toolTone,\n} from '@agimon-ai/po-web-components';\n\nimport { askCallSummary, askResultView } from '../_lib/askUserText';\n\n/** The answered list, or the outcome that stood in for it. */\nfunction AskUserQuestionBody({ result, output, running, isError }: ToolMessageRenderProps) {\n if (running) {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <MessageItemStatus tone=\"running\">running</MessageItemStatus>\n </MessageItemBody>\n );\n }\n const view = askResultView(result?.details, output);\n if (view.kind === 'cancelled') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <MessageItemStatus tone=\"running\">cancelled</MessageItemStatus>\n </MessageItemBody>\n );\n }\n if (view.kind === 'voice') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <pre className=\"whitespace-pre-wrap break-words text-po-dim\">{view.prompt}</pre>\n </MessageItemBody>\n );\n }\n if (view.kind === 'text') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n {view.text ? (\n <pre className={`whitespace-pre-wrap break-words ${isError ? 'text-po-red' : 'text-po-dim'}`}>\n {view.text}\n </pre>\n ) : (\n <MessageItemStatus tone={isError ? 'error' : 'ok'}>{isError ? 'failed' : 'submitted'}</MessageItemStatus>\n )}\n </MessageItemBody>\n );\n }\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n {view.answers.length === 0 ? (\n <MessageItemStatus tone=\"ok\">submitted</MessageItemStatus>\n ) : (\n <ul className=\"flex flex-col gap-1\">\n {view.answers.map((answer, index) => (\n <li key={`${String(index)}-${answer.question}`} className=\"break-words\">\n <span className=\"text-po-green\">✓</span> <span className=\"text-po-cyan\">{answer.question}</span>:{' '}\n <span className=\"text-po-dim\">{answer.answer}</span>\n </li>\n ))}\n </ul>\n )}\n </MessageItemBody>\n );\n}\n\n/**\n * The ask_user_question tool's timeline item: \"N question(s) (headers)\" in\n * the header, the web half of the TUI renderCall, and the answered list or\n * the outcome that stood in for it in the body. The live questionnaire\n * arrives as an extension-UI dialog; this is the transcript record.\n */\nexport function AskUserQuestionToolMessage(props: ToolMessageRenderProps) {\n const summary = askCallSummary(props.args);\n return (\n <MessageItem tone={toolTone({ running: props.running, isError: props.isError })}>\n <MessageItemHeader title=\"ask_user_question\">\n <span data-testid=\"tool-call-ask_user_question\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"text-po-hi\">{summary.count}</span>\n {summary.headers ? <span className=\"min-w-0 truncate text-po-dim\">({summary.headers})</span> : null}\n </span>\n </MessageItemHeader>\n <AskUserQuestionBody {...props} />\n </MessageItem>\n );\n}\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","/**\n * The questionnaire the cockpit is filling in, as a value.\n *\n * Apart from React so the unit suite covers it without a DOM, the same way\n * askUserText.ts is. The questions come from the running tool call's\n * arguments, which are wire JSON: the shapes narrowed here mirror\n * src/schemas/questionnaire.ts, which a browser module may not import.\n *\n * Everything is held here until the reader submits, which is the whole point.\n * A step the reader goes back to still has what they chose, and the agent is\n * asked once rather than once per question.\n */\nimport type { QuestionAnswer } from '../../../../../../types/questionnaire';\n\n/** The row every question gets, so a reader always has a way past a bad set of options. */\nexport const CUSTOM_LABEL = 'Type something.';\n\nexport interface PromptOption {\n label: string;\n description: string;\n preview?: string;\n}\n\nexport interface PromptQuestion {\n question: string;\n header: string;\n multiSelect: boolean;\n options: PromptOption[];\n}\n\nexport interface QuestionDraft {\n /** Chosen option labels: at most one unless the question is multiSelect. */\n selected: string[];\n /** Text typed in place of an option; null while the reader has not typed any. */\n custom: string | null;\n notes: string;\n}\n\nexport type QuestionnaireDraft = readonly QuestionDraft[];\n\nconst EMPTY_ENTRY: QuestionDraft = { selected: [], custom: null, notes: '' };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readOption(value: unknown): PromptOption | null {\n if (!isRecord(value)) return null;\n const label = asString(value.label);\n if (label === '') return null;\n const preview = asString(value.preview);\n return { label, description: asString(value.description), ...(preview === '' ? {} : { preview }) };\n}\n\n/** The questions as the tool was called with them; an unreadable call yields none. */\nexport function readPromptQuestions(args: Readonly<Record<string, unknown>>): PromptQuestion[] {\n if (!Array.isArray(args.questions)) return [];\n return args.questions.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const question = asString(entry.question);\n if (question === '') return [];\n const options = (Array.isArray(entry.options) ? entry.options : []).flatMap((option) => {\n const read = readOption(option);\n return read === null ? [] : [read];\n });\n return [{ question, header: asString(entry.header), multiSelect: entry.multiSelect === true, options }];\n });\n}\n\nexport function emptyDraft(count: number): QuestionnaireDraft {\n return Array.from({ length: count }, () => EMPTY_ENTRY);\n}\n\nexport function draftEntry(draft: QuestionnaireDraft, index: number): QuestionDraft {\n return draft[index] ?? EMPTY_ENTRY;\n}\n\nfunction withEntry(draft: QuestionnaireDraft, index: number, patch: Partial<QuestionDraft>): QuestionnaireDraft {\n if (index < 0 || index >= draft.length) return draft;\n return draft.map((entry, position) => (position === index ? { ...entry, ...patch } : entry));\n}\n\n/**\n * Picks an option. A single-select question replaces its choice; a multiSelect\n * one toggles. Either way the typed answer is dropped, because an option and\n * free text are two answers to the same question and only one can be sent.\n */\nexport function chooseOption(\n draft: QuestionnaireDraft,\n index: number,\n label: string,\n multiSelect: boolean,\n): QuestionnaireDraft {\n const current = draftEntry(draft, index).selected;\n const selected = multiSelect\n ? current.includes(label)\n ? current.filter((entry) => entry !== label)\n : [...current, label]\n : [label];\n return withEntry(draft, index, { selected, custom: null });\n}\n\n/** Types an answer instead of choosing one; null returns to the options. */\nexport function setCustom(draft: QuestionnaireDraft, index: number, custom: string | null): QuestionnaireDraft {\n return withEntry(draft, index, { custom, selected: [] });\n}\n\nexport function setNotes(draft: QuestionnaireDraft, index: number, notes: string): QuestionnaireDraft {\n return withEntry(draft, index, { notes });\n}\n\nexport function isAnswered(entry: QuestionDraft): boolean {\n return entry.selected.length > 0 || (entry.custom !== null && entry.custom.trim() !== '');\n}\n\n/** The next question still needing an answer, searching after `from` first; -1 when none is left. */\nexport function nextUnanswered(draft: QuestionnaireDraft, from: number): number {\n const after = draft.findIndex((entry, index) => index > from && !isAnswered(entry));\n return after >= 0 ? after : draft.findIndex((entry) => !isAnswered(entry));\n}\n\nexport function isComplete(draft: QuestionnaireDraft): boolean {\n return draft.length > 0 && draft.every(isAnswered);\n}\n\n/**\n * The answers as the tool reads them back. `preview` is left off: the session\n * half looks it up from the option it declared, so sending one here would only\n * be a claim it has to ignore.\n */\nexport function draftAnswers(questions: readonly PromptQuestion[], draft: QuestionnaireDraft): QuestionAnswer[] {\n return questions.flatMap((question, index): QuestionAnswer[] => {\n const entry = draftEntry(draft, index);\n if (!isAnswered(entry)) return [];\n const notes = entry.notes.trim();\n const base = { questionIndex: index, question: question.question, ...(notes === '' ? {} : { notes }) };\n if (entry.custom !== null && entry.custom.trim() !== '') {\n return [{ ...base, kind: 'custom' as const, answer: entry.custom }];\n }\n if (question.multiSelect) {\n return [{ ...base, kind: 'multi' as const, answer: null, selected: [...entry.selected] }];\n }\n return [{ ...base, kind: 'option' as const, answer: entry.selected[0] ?? null }];\n });\n}\n","import type { ToolPromptRenderProps } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Kbd,\n Markdown,\n OptionLabel,\n OptionRow,\n SectionLabel,\n Separator,\n Textarea,\n} from '@agimon-ai/po-web-components';\nimport { type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { encodeAnswerEnvelope } from '../../../../../../types/askUserWire';\nimport {\n chooseOption,\n CUSTOM_LABEL,\n draftAnswers,\n draftEntry,\n emptyDraft,\n isAnswered,\n isComplete,\n nextUnanswered,\n type PromptQuestion,\n type QuestionnaireDraft,\n readPromptQuestions,\n setCustom,\n setNotes,\n} from '../_lib/questionnaireDraft';\n\n/** The step bar: which questions are answered, which one is open, and a click back to any of them. */\nfunction StepBar({\n questions,\n draft,\n current,\n onPick,\n}: {\n questions: readonly PromptQuestion[];\n draft: QuestionnaireDraft;\n current: number;\n onPick: (index: number) => void;\n}) {\n return (\n <div data-testid=\"questionnaire-steps\" className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {questions.map((question, index) => {\n const answered = isAnswered(draftEntry(draft, index));\n const active = index === current;\n return (\n <Button\n key={question.question}\n variant=\"ghost\"\n size=\"xs\"\n data-testid={`questionnaire-step-${String(index)}`}\n data-step-state={active ? 'current' : answered ? 'answered' : 'pending'}\n onClick={() => onPick(index)}\n className={`gap-1.5 rounded-full px-2.5 py-0.5 text-base font-medium ${\n active\n ? 'bg-po-selected font-semibold text-po-on-selected'\n : answered\n ? 'text-po-green hover:bg-po-panel'\n : 'text-po-faint hover:bg-po-panel'\n }`}\n >\n <span aria-hidden>{answered ? '✓' : '○'}</span>\n {question.header || `question ${String(index + 1)}`}\n </Button>\n );\n })}\n </div>\n );\n}\n\n/**\n * The frame's shortcuts stop at a text field: while the caret is in one the\n * arrows and Enter belong to the field, so they are kept from bubbling. Enter\n * commits the field the way the terminal editor does, Shift+Enter writes a\n * newline, and Escape and Cmd/Ctrl+Enter still reach the frame.\n */\nfunction fieldKeys(commit: () => void) {\n return (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n if (event.key === 'Escape') return;\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) return;\n event.stopPropagation();\n if (event.key !== 'Enter' || event.shiftKey) return;\n event.preventDefault();\n commit();\n };\n}\n\n/** One question's options, its typed-answer row, and the preview of whatever is focused. */\nfunction QuestionBody({\n question,\n index,\n draft,\n cursor,\n onCursor,\n onDraft,\n onCommit,\n}: {\n question: PromptQuestion;\n index: number;\n draft: QuestionnaireDraft;\n cursor: number;\n onCursor: (cursor: number) => void;\n /** `settles` is true only for an edit that finishes the question, which is what may move on. */\n onDraft: (draft: QuestionnaireDraft, settles: boolean) => void;\n /** Enter in a text field: hands the keyboard back to the frame, settling the question or not. */\n onCommit: (settles: boolean) => void;\n}) {\n const entry = draftEntry(draft, index);\n const typing = entry.custom !== null;\n const preview = question.options[cursor]?.preview;\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <p data-testid=\"questionnaire-question\" className=\"break-words text-base leading-relaxed text-po-hi\">\n {question.question}\n </p>\n\n <div role=\"listbox\" aria-label=\"options\" className=\"flex min-w-0 flex-col\">\n {question.options.map((option, optionIndex) => {\n const chosen = entry.selected.includes(option.label);\n return (\n <OptionRow\n key={option.label}\n active={optionIndex === cursor && !typing}\n data-testid={`questionnaire-option-${String(optionIndex)}`}\n data-chosen={chosen}\n onMouseEnter={() => onCursor(optionIndex)}\n // A multiSelect question is not finished by one click: moving on\n // there would take the list away before a second option could be\n // picked, which is the whole point of the question.\n onClick={() =>\n onDraft(chooseOption(draft, index, option.label, question.multiSelect), !question.multiSelect)\n }\n className=\"min-w-0 items-start gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal\"\n >\n <span className={`mt-[2px] shrink-0 text-sm ${chosen ? 'text-po-green' : 'text-po-faint'}`}>\n {question.multiSelect ? (chosen ? '[x]' : '[ ]') : chosen ? '●' : '○'}\n </span>\n <OptionLabel className=\"min-w-0 flex flex-col gap-0.5 text-left\">\n <span\n className={`break-words text-base font-medium ${chosen ? 'font-semibold text-po-hi' : 'text-po-text'}`}\n >\n {option.label}\n </span>\n {option.description ? (\n <span className=\"break-words text-sm leading-relaxed whitespace-normal text-po-dim\">\n {option.description}\n </span>\n ) : null}\n </OptionLabel>\n </OptionRow>\n );\n })}\n\n <OptionRow\n active={typing}\n data-testid=\"questionnaire-option-custom\"\n onClick={() => onDraft(setCustom(draft, index, entry.custom ?? ''), false)}\n className=\"min-w-0 items-center gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal\"\n >\n <span className=\"shrink-0 text-sm text-po-faint\">✎</span>\n <OptionLabel className=\"min-w-0 whitespace-normal text-po-text\">{CUSTOM_LABEL}</OptionLabel>\n </OptionRow>\n </div>\n\n {typing ? (\n <div className=\"flex flex-col gap-1\">\n <SectionLabel>your answer</SectionLabel>\n <Textarea\n data-testid=\"questionnaire-custom\"\n autoFocus\n rows={2}\n value={entry.custom ?? ''}\n placeholder=\"type your answer…\"\n onChange={(event) => onDraft(setCustom(draft, index, event.target.value), false)}\n onKeyDown={fieldKeys(() => onCommit(true))}\n />\n </div>\n ) : null}\n\n {preview ? (\n <div data-testid=\"questionnaire-preview\" className=\"flex flex-col gap-1\">\n <SectionLabel>preview</SectionLabel>\n <div className=\"max-h-40 overflow-y-auto border-l-2 border-po-edge-magenta pl-3 text-base text-po-dim\">\n <Markdown text={preview} />\n </div>\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-1\">\n <SectionLabel>note (optional)</SectionLabel>\n <Textarea\n data-testid=\"questionnaire-notes\"\n rows={1}\n value={entry.notes}\n placeholder=\"anything the options do not cover\"\n onChange={(event) => onDraft(setNotes(draft, index, event.target.value), false)}\n onKeyDown={fieldKeys(() => onCommit(false))}\n />\n </div>\n </div>\n );\n}\n\n/**\n * The questionnaire, standing in for the composer input while the agent waits.\n *\n * Every question is here at once, because the cockpit can see the whole call\n * even though the request it answers can only carry one question's labels.\n * That is what makes the step bar honest: nothing is sent until the reader\n * submits, so going back to a step is just moving a cursor, and the agent is\n * asked exactly once.\n */\nexport function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPromptRenderProps) {\n const questions = useMemo(() => readPromptQuestions(args), [args]);\n const [draft, setDraft] = useState<QuestionnaireDraft>(() => emptyDraft(questions.length));\n const [current, setCurrent] = useState(0);\n const [cursor, setCursor] = useState(0);\n const frame = useRef<HTMLDivElement>(null);\n\n // A new request is a new questionnaire; nothing from the last one carries over.\n useEffect(() => {\n // Remounting on dialog.id would be the alternative, but the prompt host owns the\n // element, so the reset lives here.\n // oxlint-disable-next-line react/set-state-in-effect\n setDraft(emptyDraft(questions.length));\n setCurrent(0);\n setCursor(0);\n }, [dialog.id, questions.length]);\n\n // The agent is blocked on this, so it takes the keyboard the input had.\n useEffect(() => frame.current?.focus(), [dialog.id]);\n\n const question = questions[current];\n const complete = isComplete(draft);\n\n const move = (next: QuestionnaireDraft, answeredIndex: number, settles: boolean): void => {\n setDraft(next);\n // Follow the reader forwards the way the terminal does, but only on an\n // edit that finished the question, and only while something is still\n // unanswered: jumping after the last answer would take the step they just\n // finished off the screen before they could look at it.\n if (!settles) return;\n const pending = nextUnanswered(next, answeredIndex);\n if (pending >= 0 && pending !== answeredIndex) {\n setCurrent(pending);\n setCursor(0);\n }\n };\n\n const show = (index: number): void => {\n setCurrent(index);\n setCursor(0);\n };\n\n const submit = (): void => {\n if (!complete) return;\n answer(encodeAnswerEnvelope(draftAnswers(questions, draft)));\n };\n\n if (question === undefined) {\n return (\n <div className=\"flex items-center justify-between gap-3 px-3.5 py-3\">\n <span data-testid=\"questionnaire-empty\" className=\"text-sm text-po-dim\">\n the agent asked something this cockpit could not read\n </span>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"questionnaire-cancel\" onClick={cancel}>\n cancel\n </Button>\n </div>\n );\n }\n\n const typing = draftEntry(draft, current).custom !== null;\n return (\n // The keys belong to the frame rather than to any row, so the reader can\n // move between questions and options without tabbing through both.\n <div\n ref={frame}\n tabIndex={-1}\n data-testid=\"questionnaire\"\n className=\"flex min-w-0 flex-col overflow-hidden outline-none\"\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n cancel();\n return;\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n submit();\n return;\n }\n // While typing an answer the arrows belong to the textarea.\n if (typing) return;\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n const count = question.options.length;\n setCursor((event.key === 'ArrowDown' ? cursor + 1 : cursor - 1 + count) % count);\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const count = questions.length;\n show((event.key === 'ArrowRight' ? current + 1 : current - 1 + count) % count);\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n const option = question.options[cursor];\n if (option === undefined) {\n submit();\n return;\n }\n move(chooseOption(draft, current, option.label, question.multiSelect), current, !question.multiSelect);\n }\n }}\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2 px-3 pt-2.5 pb-2 sm:gap-3 sm:px-3.5\">\n <StepBar questions={questions} draft={draft} current={current} onPick={show} />\n <span className=\"min-w-0 flex-1\" />\n <Badge size=\"xs\" className=\"shrink-0 border-transparent bg-po-panel py-0.5 text-po-dim\">\n question {current + 1} / {questions.length}\n </Badge>\n </div>\n\n <Separator />\n\n <div className=\"max-h-[46vh] min-w-0 overflow-x-hidden overflow-y-auto px-3 py-3 sm:px-3.5\">\n <QuestionBody\n question={question}\n index={current}\n draft={draft}\n cursor={cursor}\n onCursor={setCursor}\n onDraft={(next, settles) => move(next, current, settles)}\n onCommit={(settles) => {\n frame.current?.focus();\n move(draft, current, settles);\n }}\n />\n </div>\n\n <Separator />\n\n <div className=\"flex min-w-0 items-center gap-2 px-3 py-2 sm:px-3.5\">\n <span data-testid=\"questionnaire-hint\" className=\"min-w-0 truncate text-sm text-po-dim max-sm:hidden\">\n <Kbd>←→</Kbd> questions · <Kbd>↑↓</Kbd> options · <Kbd>enter</Kbd>{' '}\n {question.multiSelect ? 'toggles' : 'selects'} · <Kbd>esc</Kbd> cancels\n </span>\n <span className=\"flex-1\" />\n <Button variant=\"outline\" size=\"md\" data-testid=\"questionnaire-cancel\" onClick={cancel}>\n cancel\n </Button>\n <Button\n variant=\"primary\"\n size=\"md\"\n data-testid=\"questionnaire-submit\"\n onClick={submit}\n disabled={!complete}\n title={complete ? 'send every answer to the agent' : 'answer every question first'}\n className=\"shrink-0 px-3 max-sm:max-w-32 max-sm:truncate sm:px-3.5\"\n >\n submit answers\n </Button>\n </div>\n </div>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/po-core/web';\n\nimport { AskUserQuestionToolMessage } from './_components/AskUserQuestionToolMessage';\nimport { QuestionnairePrompt } from './_components/QuestionnairePrompt';\nimport { readPromptQuestions } from './_lib/questionnaireDraft';\n\nexport default defineToolRenderer({\n message: AskUserQuestionToolMessage,\n prompt: {\n claims: (dialog, args) =>\n dialog.method === 'select' &&\n readPromptQuestions(args).some((question) => dialog.title.startsWith(question.question)),\n component: QuestionnairePrompt,\n },\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-core/web';\n\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'ask-user',\n session: {\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['ask_user_question'] }, toolAskUserQuestion)]; },\n },\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"web.mjs","names":["isRecord","asString","toolAskUserQuestion"],"sources":["../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/askUserText.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/AskUserQuestionToolMessage.tsx","../../src/types/askUserWire.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_lib/questionnaireDraft.ts","../../src/extensions/workspaces/sessions/(frontend)/tool/_components/QuestionnairePrompt.tsx","../../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.web.tsx","../../generated/web.ts"],"sourcesContent":["/**\n * Pure text shaping for the ask_user_question card, apart from React so the\n * unit suite covers it without a DOM. Args and details are wire JSON; the\n * shapes narrowed here mirror src/types/questionnaire.ts, which this browser\n * module may not import at runtime.\n */\n\ntype Args = Readonly<Record<string, unknown>>;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nexport interface AskCallSummary {\n /** \"N question(s)\", the TUI heading's purpose. */\n count: string;\n /** The question headers, comma separated; empty when none carry one. */\n headers: string;\n}\n\n/** The call header: how many questions, and their headers in parentheses. */\nexport function askCallSummary(args: Args): AskCallSummary {\n const questions = Array.isArray(args.questions) ? args.questions.filter(isRecord) : [];\n const headers = questions.map((question) => asString(question.header)).filter(Boolean);\n return {\n count: `${questions.length} question${questions.length === 1 ? '' : 's'}`,\n headers: headers.join(', '),\n };\n}\n\nexport interface AnsweredQuestion {\n question: string;\n answer: string;\n}\n\nexport type AskResultView =\n | { kind: 'cancelled' }\n | { kind: 'voice'; prompt: string }\n | { kind: 'answers'; answers: AnsweredQuestion[] }\n | { kind: 'text'; text: string };\n\n/**\n * The result body the TUI's renderResult chooses: a cancelled glyph, the\n * voice prompt when the questionnaire was handed to voice, the answered\n * list, or the raw text when the tool attached no details (an error path).\n */\nexport function askResultView(details: unknown, output: string): AskResultView {\n if (!isRecord(details) || !Array.isArray(details.answers)) return { kind: 'text', text: output };\n if (details.cancelled === true) return { kind: 'cancelled' };\n if (details.delivery === 'voice' && typeof details.voicePrompt === 'string') {\n return { kind: 'voice', prompt: details.voicePrompt };\n }\n const answers = details.answers.filter(isRecord).map((answer) => ({\n question: asString(answer.question),\n answer:\n answer.kind === 'multi'\n ? (Array.isArray(answer.selected) ? answer.selected : []).filter((v) => typeof v === 'string').join(', ')\n : asString(answer.answer),\n }));\n return { kind: 'answers', answers };\n}\n","import type { ToolMessageRenderProps } from '@agimon-ai/po-core/web';\nimport {\n MessageItem,\n MessageItemBody,\n MessageItemHeader,\n MessageItemStatus,\n toolTone,\n} from '@agimon-ai/po-web-components';\n\nimport { askCallSummary, askResultView } from '../_lib/askUserText';\n\n/** The answered list, or the outcome that stood in for it. */\nfunction AskUserQuestionBody({ result, output, running, isError }: ToolMessageRenderProps) {\n if (running) {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <MessageItemStatus tone=\"running\">running</MessageItemStatus>\n </MessageItemBody>\n );\n }\n const view = askResultView(result?.details, output);\n if (view.kind === 'cancelled') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <MessageItemStatus tone=\"running\">cancelled</MessageItemStatus>\n </MessageItemBody>\n );\n }\n if (view.kind === 'voice') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n <pre className=\"whitespace-pre-wrap break-words text-po-dim\">{view.prompt}</pre>\n </MessageItemBody>\n );\n }\n if (view.kind === 'text') {\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n {view.text ? (\n <pre className={`whitespace-pre-wrap break-words ${isError ? 'text-po-red' : 'text-po-dim'}`}>\n {view.text}\n </pre>\n ) : (\n <MessageItemStatus tone={isError ? 'error' : 'ok'}>{isError ? 'failed' : 'submitted'}</MessageItemStatus>\n )}\n </MessageItemBody>\n );\n }\n return (\n <MessageItemBody data-testid=\"tool-result-ask_user_question\">\n {view.answers.length === 0 ? (\n <MessageItemStatus tone=\"ok\">submitted</MessageItemStatus>\n ) : (\n <ul className=\"flex flex-col gap-1\">\n {view.answers.map((answer, index) => (\n <li key={`${String(index)}-${answer.question}`} className=\"break-words\">\n <span className=\"text-po-green\">✓</span> <span className=\"text-po-cyan\">{answer.question}</span>:{' '}\n <span className=\"text-po-dim\">{answer.answer}</span>\n </li>\n ))}\n </ul>\n )}\n </MessageItemBody>\n );\n}\n\n/**\n * The ask_user_question tool's timeline item: \"N question(s) (headers)\" in\n * the header, the web half of the TUI renderCall, and the answered list or\n * the outcome that stood in for it in the body. The live questionnaire\n * arrives as an extension-UI dialog; this is the transcript record.\n */\nexport function AskUserQuestionToolMessage(props: ToolMessageRenderProps) {\n const summary = askCallSummary(props.args);\n return (\n <MessageItem tone={toolTone({ running: props.running, isError: props.isError })}>\n <MessageItemHeader title=\"ask_user_question\">\n <span data-testid=\"tool-call-ask_user_question\" className=\"flex min-w-0 flex-1 items-center gap-2\">\n <span className=\"text-po-hi\">{summary.count}</span>\n {summary.headers ? <span className=\"min-w-0 truncate text-po-dim\">({summary.headers})</span> : null}\n </span>\n </MessageItemHeader>\n <AskUserQuestionBody {...props} />\n </MessageItem>\n );\n}\n","import type { QuestionAnswer } from './questionnaire';\n\n/**\n * How a rich client answers the whole questionnaire in one reply.\n *\n * Pi's select request carries a title and a list of labels and nothing else,\n * so over RPC the questionnaire degrades into one request per question, asked\n * forwards only. A client that can already see the whole call (the cockpit\n * reads it from the tool's arguments) has no use for that: it renders every\n * question at once, lets the reader move between them, and replies once.\n *\n * The reply rides on the select response, because that is the only channel a\n * blocked extension has. An ordinary label is still an ordinary label, so a\n * client that knows nothing about this keeps the per-question flow exactly as\n * it was; only a response carrying the prefix is read as the whole set.\n *\n * It lives under src/types because that is the one server root a browser\n * bundle may read, so neither half can drift from the other.\n */\nexport const ANSWER_ENVELOPE_PREFIX = 'po-ask-user/v1:';\n\n/** A response that claims the prefix but cannot be read is refused, never guessed at. */\nexport type AnswerEnvelopeRead =\n | { readonly kind: 'absent' }\n | { readonly kind: 'malformed' }\n | { readonly kind: 'envelope'; readonly payload: unknown };\n\n/** Beyond this a response is not a questionnaire answer, whatever it claims. */\nexport const MAX_ENVELOPE_BYTES = 64 * 1024;\n\nexport function encodeAnswerEnvelope(answers: readonly QuestionAnswer[]): string {\n return `${ANSWER_ENVELOPE_PREFIX}${JSON.stringify({ answers })}`;\n}\n\nexport function decodeAnswerEnvelope(value: string | undefined): AnswerEnvelopeRead {\n if (value === undefined || !value.startsWith(ANSWER_ENVELOPE_PREFIX)) return { kind: 'absent' };\n if (value.length > MAX_ENVELOPE_BYTES) return { kind: 'malformed' };\n try {\n return { kind: 'envelope', payload: JSON.parse(value.slice(ANSWER_ENVELOPE_PREFIX.length)) };\n } catch {\n // Unreadable JSON behind the prefix: the sender meant an envelope and got\n // it wrong, which is a refusal rather than a label the model should see.\n return { kind: 'malformed' };\n }\n}\n","/**\n * The questionnaire the cockpit is filling in, as a value.\n *\n * Apart from React so the unit suite covers it without a DOM, the same way\n * askUserText.ts is. The questions come from the running tool call's\n * arguments, which are wire JSON: the shapes narrowed here mirror\n * src/schemas/questionnaire.ts, which a browser module may not import.\n *\n * Everything is held here until the reader submits, which is the whole point.\n * A step the reader goes back to still has what they chose, and the agent is\n * asked once rather than once per question.\n */\nimport type { QuestionAnswer } from '../../../../../../types/questionnaire';\n\n/** The row every question gets, so a reader always has a way past a bad set of options. */\nexport const CUSTOM_LABEL = 'Type something.';\n\nexport interface PromptOption {\n label: string;\n description: string;\n preview?: string;\n}\n\nexport interface PromptQuestion {\n question: string;\n header: string;\n multiSelect: boolean;\n options: PromptOption[];\n}\n\nexport interface QuestionDraft {\n /** Chosen option labels: at most one unless the question is multiSelect. */\n selected: string[];\n /** Text typed in place of an option; null while the reader has not typed any. */\n custom: string | null;\n notes: string;\n}\n\nexport type QuestionnaireDraft = readonly QuestionDraft[];\n\nconst EMPTY_ENTRY: QuestionDraft = { selected: [], custom: null, notes: '' };\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction asString(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nfunction readOption(value: unknown): PromptOption | null {\n if (!isRecord(value)) return null;\n const label = asString(value.label);\n if (label === '') return null;\n const preview = asString(value.preview);\n return { label, description: asString(value.description), ...(preview === '' ? {} : { preview }) };\n}\n\n/** The questions as the tool was called with them; an unreadable call yields none. */\nexport function readPromptQuestions(args: Readonly<Record<string, unknown>>): PromptQuestion[] {\n if (!Array.isArray(args.questions)) return [];\n return args.questions.flatMap((entry) => {\n if (!isRecord(entry)) return [];\n const question = asString(entry.question);\n if (question === '') return [];\n const options = (Array.isArray(entry.options) ? entry.options : []).flatMap((option) => {\n const read = readOption(option);\n return read === null ? [] : [read];\n });\n return [{ question, header: asString(entry.header), multiSelect: entry.multiSelect === true, options }];\n });\n}\n\nexport function emptyDraft(count: number): QuestionnaireDraft {\n return Array.from({ length: count }, () => EMPTY_ENTRY);\n}\n\nexport function draftEntry(draft: QuestionnaireDraft, index: number): QuestionDraft {\n return draft[index] ?? EMPTY_ENTRY;\n}\n\nfunction withEntry(draft: QuestionnaireDraft, index: number, patch: Partial<QuestionDraft>): QuestionnaireDraft {\n if (index < 0 || index >= draft.length) return draft;\n return draft.map((entry, position) => (position === index ? { ...entry, ...patch } : entry));\n}\n\n/**\n * Picks an option. A single-select question replaces its choice; a multiSelect\n * one toggles. Either way the typed answer is dropped, because an option and\n * free text are two answers to the same question and only one can be sent.\n */\nexport function chooseOption(\n draft: QuestionnaireDraft,\n index: number,\n label: string,\n multiSelect: boolean,\n): QuestionnaireDraft {\n const current = draftEntry(draft, index).selected;\n const selected = multiSelect\n ? current.includes(label)\n ? current.filter((entry) => entry !== label)\n : [...current, label]\n : [label];\n return withEntry(draft, index, { selected, custom: null });\n}\n\n/** Types an answer instead of choosing one; null returns to the options. */\nexport function setCustom(draft: QuestionnaireDraft, index: number, custom: string | null): QuestionnaireDraft {\n return withEntry(draft, index, { custom, selected: [] });\n}\n\nexport function setNotes(draft: QuestionnaireDraft, index: number, notes: string): QuestionnaireDraft {\n return withEntry(draft, index, { notes });\n}\n\nexport function isAnswered(entry: QuestionDraft): boolean {\n return entry.selected.length > 0 || (entry.custom !== null && entry.custom.trim() !== '');\n}\n\n/** The next question still needing an answer, searching after `from` first; -1 when none is left. */\nexport function nextUnanswered(draft: QuestionnaireDraft, from: number): number {\n const after = draft.findIndex((entry, index) => index > from && !isAnswered(entry));\n return after >= 0 ? after : draft.findIndex((entry) => !isAnswered(entry));\n}\n\nexport function isComplete(draft: QuestionnaireDraft): boolean {\n return draft.length > 0 && draft.every(isAnswered);\n}\n\n/**\n * The answers as the tool reads them back. `preview` is left off: the session\n * half looks it up from the option it declared, so sending one here would only\n * be a claim it has to ignore.\n */\nexport function draftAnswers(questions: readonly PromptQuestion[], draft: QuestionnaireDraft): QuestionAnswer[] {\n return questions.flatMap((question, index): QuestionAnswer[] => {\n const entry = draftEntry(draft, index);\n if (!isAnswered(entry)) return [];\n const notes = entry.notes.trim();\n const base = { questionIndex: index, question: question.question, ...(notes === '' ? {} : { notes }) };\n if (entry.custom !== null && entry.custom.trim() !== '') {\n return [{ ...base, kind: 'custom' as const, answer: entry.custom }];\n }\n if (question.multiSelect) {\n return [{ ...base, kind: 'multi' as const, answer: null, selected: [...entry.selected] }];\n }\n return [{ ...base, kind: 'option' as const, answer: entry.selected[0] ?? null }];\n });\n}\n","import type { ToolPromptRenderProps } from '@agimon-ai/po-core/web';\nimport {\n Badge,\n Button,\n Kbd,\n Markdown,\n OptionLabel,\n OptionRow,\n SectionLabel,\n Separator,\n Textarea,\n} from '@agimon-ai/po-web-components';\nimport { type KeyboardEvent, useEffect, useMemo, useRef, useState } from 'react';\n\nimport { encodeAnswerEnvelope } from '../../../../../../types/askUserWire';\nimport {\n chooseOption,\n CUSTOM_LABEL,\n draftAnswers,\n draftEntry,\n emptyDraft,\n isAnswered,\n isComplete,\n nextUnanswered,\n type PromptQuestion,\n type QuestionnaireDraft,\n readPromptQuestions,\n setCustom,\n setNotes,\n} from '../_lib/questionnaireDraft';\n\n/** The step bar: which questions are answered, which one is open, and a click back to any of them. */\nfunction StepBar({\n questions,\n draft,\n current,\n onPick,\n}: {\n questions: readonly PromptQuestion[];\n draft: QuestionnaireDraft;\n current: number;\n onPick: (index: number) => void;\n}) {\n return (\n <div data-testid=\"questionnaire-steps\" className=\"flex min-w-0 flex-wrap items-center gap-1.5\">\n {questions.map((question, index) => {\n const answered = isAnswered(draftEntry(draft, index));\n const active = index === current;\n return (\n <Button\n key={question.question}\n variant=\"ghost\"\n size=\"xs\"\n data-testid={`questionnaire-step-${String(index)}`}\n data-step-state={active ? 'current' : answered ? 'answered' : 'pending'}\n onClick={() => onPick(index)}\n className={`gap-1.5 rounded-full px-2.5 py-0.5 text-base font-medium ${\n active\n ? 'bg-po-selected font-semibold text-po-on-selected'\n : answered\n ? 'text-po-green hover:bg-po-panel'\n : 'text-po-faint hover:bg-po-panel'\n }`}\n >\n <span aria-hidden>{answered ? '✓' : '○'}</span>\n {question.header || `question ${String(index + 1)}`}\n </Button>\n );\n })}\n </div>\n );\n}\n\n/**\n * The frame's shortcuts stop at a text field: while the caret is in one the\n * arrows and Enter belong to the field, so they are kept from bubbling. Enter\n * commits the field the way the terminal editor does, Shift+Enter writes a\n * newline, and Escape and Cmd/Ctrl+Enter still reach the frame.\n */\nfunction fieldKeys(commit: () => void) {\n return (event: KeyboardEvent<HTMLTextAreaElement>): void => {\n if (event.key === 'Escape') return;\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) return;\n event.stopPropagation();\n if (event.key !== 'Enter' || event.shiftKey) return;\n event.preventDefault();\n commit();\n };\n}\n\n/** One question's options, its typed-answer row, and the preview of whatever is focused. */\nfunction QuestionBody({\n question,\n index,\n draft,\n cursor,\n onCursor,\n onDraft,\n onCommit,\n}: {\n question: PromptQuestion;\n index: number;\n draft: QuestionnaireDraft;\n cursor: number;\n onCursor: (cursor: number) => void;\n /** `settles` is true only for an edit that finishes the question, which is what may move on. */\n onDraft: (draft: QuestionnaireDraft, settles: boolean) => void;\n /** Enter in a text field: hands the keyboard back to the frame, settling the question or not. */\n onCommit: (settles: boolean) => void;\n}) {\n const entry = draftEntry(draft, index);\n const typing = entry.custom !== null;\n const option = question.options[cursor];\n const preview = typing ? undefined : option?.preview;\n const hasPreviews = question.options.some((item) => item.preview);\n return (\n <div className=\"flex min-w-0 flex-col gap-2\">\n <p data-testid=\"questionnaire-question\" className=\"break-words text-base leading-relaxed text-po-hi\">\n {question.question}\n </p>\n {hasPreviews ? (\n <p className=\"text-sm text-po-dim\">Choose a concept to see its preview. Nothing is sent until you submit.</p>\n ) : null}\n <div className={hasPreviews ? 'grid min-w-0 gap-4 md:grid-cols-3' : 'min-w-0'}>\n <div className=\"flex min-w-0 flex-col gap-3\">\n <div role=\"listbox\" aria-label=\"options\" className=\"flex min-w-0 flex-col\">\n {question.options.map((option, optionIndex) => {\n const chosen = entry.selected.includes(option.label);\n return (\n <OptionRow\n key={option.label}\n active={optionIndex === cursor && !typing}\n data-testid={`questionnaire-option-${String(optionIndex)}`}\n data-chosen={chosen}\n onFocus={() => onCursor(optionIndex)}\n onClick={() => {\n onCursor(optionIndex);\n onDraft(chooseOption(draft, index, option.label, question.multiSelect), !question.multiSelect);\n }}\n className=\"min-w-0 items-start gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal\"\n >\n <span className={`mt-[2px] shrink-0 text-sm ${chosen ? 'text-po-green' : 'text-po-faint'}`}>\n {question.multiSelect ? (chosen ? '[x]' : '[ ]') : chosen ? '●' : '○'}\n </span>\n <OptionLabel className=\"min-w-0 flex flex-col gap-0.5 text-left\">\n <span\n className={`break-words text-base font-medium ${chosen ? 'font-semibold text-po-hi' : 'text-po-text'}`}\n >\n {option.label}\n </span>\n {option.description ? (\n <span className=\"break-words text-sm leading-relaxed whitespace-normal text-po-dim\">\n {option.description}\n </span>\n ) : null}\n </OptionLabel>\n </OptionRow>\n );\n })}\n\n <OptionRow\n active={typing}\n data-testid=\"questionnaire-option-custom\"\n onClick={() => onDraft(setCustom(draft, index, entry.custom ?? ''), false)}\n className=\"min-w-0 items-center gap-2.5 rounded-md px-2.5 py-1.5 whitespace-normal\"\n >\n <span className=\"shrink-0 text-sm text-po-faint\">✎</span>\n <OptionLabel className=\"min-w-0 whitespace-normal text-po-text\">{CUSTOM_LABEL}</OptionLabel>\n </OptionRow>\n </div>\n\n {typing ? (\n <div className=\"flex flex-col gap-1\">\n <SectionLabel>your answer</SectionLabel>\n <Textarea\n data-testid=\"questionnaire-custom\"\n autoFocus\n rows={2}\n value={entry.custom ?? ''}\n placeholder=\"type your answer…\"\n onChange={(event) => onDraft(setCustom(draft, index, event.target.value), false)}\n onKeyDown={fieldKeys(() => onCommit(true))}\n />\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-1\">\n <SectionLabel>note (optional)</SectionLabel>\n <Textarea\n data-testid=\"questionnaire-notes\"\n rows={1}\n value={entry.notes}\n placeholder=\"anything the options do not cover\"\n onChange={(event) => onDraft(setNotes(draft, index, event.target.value), false)}\n onKeyDown={fieldKeys(() => onCommit(false))}\n />\n </div>\n </div>\n {hasPreviews ? (\n <section\n data-testid=\"questionnaire-preview\"\n aria-label=\"Concept preview\"\n className=\"flex min-w-0 flex-col gap-3 rounded-md border border-po-border bg-po-bg p-3 md:col-span-2\"\n >\n <h3 className=\"text-base font-semibold text-po-hi\">{typing ? 'Your answer' : option?.label}</h3>\n <div className=\"min-w-0 overflow-auto text-base text-po-text\">\n {preview ? (\n <Markdown text={preview} />\n ) : (\n <p className=\"text-sm text-po-dim\">\n {typing ? 'Describe your own direction in the answer field.' : 'No preview for this option.'}\n </p>\n )}\n </div>\n </section>\n ) : null}\n </div>\n </div>\n );\n}\n\n/**\n * The questionnaire, standing in for the composer input while the agent waits.\n *\n * Every question is here at once, because the cockpit can see the whole call\n * even though the request it answers can only carry one question's labels.\n * That is what makes the step bar honest: nothing is sent until the reader\n * submits, so going back to a step is just moving a cursor, and the agent is\n * asked exactly once.\n */\nexport function QuestionnairePrompt({ args, dialog, answer, cancel }: ToolPromptRenderProps) {\n const questions = useMemo(() => readPromptQuestions(args), [args]);\n const [draft, setDraft] = useState<QuestionnaireDraft>(() => emptyDraft(questions.length));\n const [current, setCurrent] = useState(0);\n const [cursor, setCursor] = useState(0);\n const frame = useRef<HTMLDivElement>(null);\n\n // A new request is a new questionnaire; nothing from the last one carries over.\n useEffect(() => {\n // Remounting on dialog.id would be the alternative, but the prompt host owns the\n // element, so the reset lives here.\n // oxlint-disable-next-line react/set-state-in-effect\n setDraft(emptyDraft(questions.length));\n setCurrent(0);\n setCursor(0);\n }, [dialog.id, questions.length]);\n\n // The agent is blocked on this, so it takes the keyboard the input had.\n useEffect(() => frame.current?.focus(), [dialog.id]);\n\n const question = questions[current];\n const complete = isComplete(draft);\n\n const move = (next: QuestionnaireDraft, answeredIndex: number, settles: boolean): void => {\n setDraft(next);\n // Follow the reader forwards the way the terminal does, but only on an\n // edit that finished the question, and only while something is still\n // unanswered: jumping after the last answer would take the step they just\n // finished off the screen before they could look at it.\n // Preview choices stay open so the reader can compare before moving on.\n if (questions[answeredIndex]?.options.some((option) => option.preview)) return;\n if (!settles) return;\n const pending = nextUnanswered(next, answeredIndex);\n if (pending >= 0 && pending !== answeredIndex) {\n setCurrent(pending);\n setCursor(0);\n }\n };\n\n const show = (index: number): void => {\n setCurrent(index);\n setCursor(0);\n };\n\n const submit = (): void => {\n if (!complete) return;\n answer(encodeAnswerEnvelope(draftAnswers(questions, draft)));\n };\n\n if (question === undefined) {\n return (\n <div className=\"flex items-center justify-between gap-3 px-3.5 py-3\">\n <span data-testid=\"questionnaire-empty\" className=\"text-sm text-po-dim\">\n the agent asked something this cockpit could not read\n </span>\n <Button variant=\"outline\" size=\"sm\" data-testid=\"questionnaire-cancel\" onClick={cancel}>\n cancel\n </Button>\n </div>\n );\n }\n\n const typing = draftEntry(draft, current).custom !== null;\n return (\n // The keys belong to the frame rather than to any row, so the reader can\n // move between questions and options without tabbing through both.\n <div\n ref={frame}\n tabIndex={-1}\n data-testid=\"questionnaire\"\n className=\"flex min-w-0 flex-col overflow-hidden bg-po-bg outline-none\"\n onKeyDown={(event) => {\n if (event.key === 'Escape') {\n event.preventDefault();\n cancel();\n return;\n }\n if (event.key === 'Enter' && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n submit();\n return;\n }\n // While typing an answer the arrows belong to the textarea.\n if (typing) return;\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n event.preventDefault();\n const count = question.options.length;\n setCursor((event.key === 'ArrowDown' ? cursor + 1 : cursor - 1 + count) % count);\n return;\n }\n if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {\n event.preventDefault();\n const count = questions.length;\n show((event.key === 'ArrowRight' ? current + 1 : current - 1 + count) % count);\n return;\n }\n if (event.key === 'Enter') {\n event.preventDefault();\n const option = question.options[cursor];\n if (option === undefined) {\n submit();\n return;\n }\n move(chooseOption(draft, current, option.label, question.multiSelect), current, !question.multiSelect);\n }\n }}\n >\n <div className=\"flex min-w-0 flex-wrap items-center gap-2 px-3 pt-2.5 pb-2 sm:gap-3 sm:px-3.5\">\n <StepBar questions={questions} draft={draft} current={current} onPick={show} />\n <span className=\"min-w-0 flex-1\" />\n <Badge size=\"xs\" className=\"shrink-0 border-transparent bg-po-panel py-0.5 text-po-dim\">\n question {current + 1} / {questions.length}\n </Badge>\n </div>\n\n <Separator />\n\n <div className=\"max-h-[65vh] min-w-0 overflow-x-hidden overflow-y-auto px-3 py-3 sm:px-3.5\">\n <QuestionBody\n question={question}\n index={current}\n draft={draft}\n cursor={cursor}\n onCursor={setCursor}\n onDraft={(next, settles) => move(next, current, settles)}\n onCommit={(settles) => {\n frame.current?.focus();\n move(draft, current, settles);\n }}\n />\n </div>\n\n <Separator />\n\n <div className=\"flex min-w-0 items-center gap-2 px-3 py-2 sm:px-3.5\">\n <span data-testid=\"questionnaire-hint\" className=\"min-w-0 truncate text-sm text-po-dim max-sm:hidden\">\n <Kbd>←→</Kbd> questions · <Kbd>↑↓</Kbd> options · <Kbd>enter</Kbd>{' '}\n {question.multiSelect ? 'toggles' : 'selects'} · <Kbd>esc</Kbd> cancels\n </span>\n <span className=\"flex-1\" />\n <Button variant=\"outline\" size=\"md\" data-testid=\"questionnaire-cancel\" onClick={cancel}>\n cancel\n </Button>\n <Button\n variant=\"primary\"\n size=\"md\"\n data-testid=\"questionnaire-submit\"\n onClick={submit}\n disabled={!complete}\n title={complete ? 'send every answer to the agent' : 'answer every question first'}\n className=\"shrink-0 px-3 max-sm:max-w-32 max-sm:truncate sm:px-3.5\"\n >\n submit answers\n </Button>\n </div>\n </div>\n );\n}\n","import { defineToolRenderer } from '@agimon-ai/po-core/web';\n\nimport { AskUserQuestionToolMessage } from './_components/AskUserQuestionToolMessage';\nimport { QuestionnairePrompt } from './_components/QuestionnairePrompt';\nimport { readPromptQuestions } from './_lib/questionnaireDraft';\n\nexport default defineToolRenderer({\n message: AskUserQuestionToolMessage,\n prompt: {\n claims: (dialog, args) =>\n dialog.method === 'select' &&\n readPromptQuestions(args).some((question) => dialog.title.startsWith(question.question)),\n component: QuestionnairePrompt,\n },\n});\n","// Generated by @agimon-ai/po-build. Do not edit by hand.\nimport { defineWebPlugin, type WebPluginContributions } from '@agimon-ai/po-core/web';\n\nimport toolAskUserQuestion from '../src/extensions/workspaces/sessions/(frontend)/tool/askUserQuestion.web';\n\nconst via = <T>(identity: Record<string, unknown>, value: unknown): T =>\n (value === undefined ? undefined : { ...identity, ...(value as object) }) as T;\n\nexport const webPlugin = defineWebPlugin({\n id: 'ask-user',\n session: {\n get toolRenderers(): WebPluginContributions['toolRenderers'] { return [via({ tools: ['ask_user_question'] }, toolAskUserQuestion)]; },\n },\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-user-feedback",
3
- "version": "0.0.0-alpha.16",
3
+ "version": "0.0.0-alpha.18",
4
4
  "description": "Structured user questions with interactive and autonomous Voice handoff for Pi agents.",
5
5
  "keywords": [
6
6
  "ai",
@@ -66,16 +66,16 @@
66
66
  "access": "public"
67
67
  },
68
68
  "dependencies": {
69
- "@agimon-ai/po-core": "0.0.0-alpha.16",
70
- "@agimon-ai/po-minor-mode": "0.0.0-alpha.16",
71
- "@agimon-ai/po-web-components": "0.0.0-alpha.16",
69
+ "@agimon-ai/po-core": "0.0.0-alpha.18",
70
+ "@agimon-ai/po-minor-mode": "0.0.0-alpha.18",
71
+ "@agimon-ai/po-web-components": "0.0.0-alpha.18",
72
72
  "@deepseek-ai/cordis": "4.0.5",
73
73
  "typebox": "1.3.37"
74
74
  },
75
75
  "devDependencies": {
76
- "@agimon-ai/po-build": "0.0.0-alpha.16",
77
- "@agimon-ai/po-native-components": "0.0.0-alpha.16",
78
- "@agimon-ai/po-voice": "0.0.0-alpha.16",
76
+ "@agimon-ai/po-build": "0.0.0-alpha.18",
77
+ "@agimon-ai/po-native-components": "0.0.0-alpha.18",
78
+ "@agimon-ai/po-voice": "0.0.0-alpha.18",
79
79
  "@earendil-works/pi-coding-agent": "1.3.0",
80
80
  "@tanstack/react-store": "0.11.2",
81
81
  "@tanstack/store": "0.11.2",
@@ -91,7 +91,7 @@
91
91
  "vitest": "5.0.4"
92
92
  },
93
93
  "peerDependencies": {
94
- "@agimon-ai/po-native-components": "0.0.0-alpha.16",
94
+ "@agimon-ai/po-native-components": "0.0.0-alpha.18",
95
95
  "@earendil-works/pi-coding-agent": "1.3.0",
96
96
  "react-native": "0.86.3",
97
97
  "react-native-unistyles": "^3.5.3"