@sendevia/pi-ask-author 1.0.1 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ import{keyHint as Oe}from"@earendil-works/pi-coding-agent";import{Text as ke}from"@earendil-works/pi-tui";import{Editor as ft,Key as S,matchesKey as T,visibleWidth as ht}from"@earendil-works/pi-tui";import{truncateToWidth as J,visibleWidth as V,wrapTextWithAnsi as Le}from"@earendil-works/pi-tui";var y={dividerChar:" │ ",dividerThick:"═",dividerThin:"─",dividerWidth:3,leftColMax:46,leftColMin:30,leftColRatio:.42,splitViewMinWidth:72,previewScrollStep:3,singlePreviewHeight:7,splitPreviewHeight:14,summaryPageSize:16,summaryScrollStep:3,visibleOptionCount:7,statusDurationMs:3e3},g={cancel:"󰜺",check:"󰄬",checked:"󰐾",unchecked:"󰄰",error:"󰅚",note:"󰷬",cursor:"",scrollUp:"",scrollDown:"",checkedMulti:"[󰄬] ",uncheckedMulti:"[ ] ",checkedSingle:"(•) ",uncheckedSingle:"( ) "},M=30,D=20,B="\0",re=[{opener:"「",closer:"」",marker:"d",maxSpanLength:400,paint:(e,t)=>e.fg("accent",t)},{opener:"(",closer:")",marker:"m",maxSpanLength:400,paint:(e,t)=>e.fg("muted",e.italic(t))}],le=new Map(re.map(e=>[e.opener,e])),ue=new Map(re.map(e=>[e.marker,e])),$="• ",s={tool:{name:"ask_author",label:"向作者请示",description:"Consult the author on novel creative decisions, plot branches, scene drafting, or story settings. Supports single and batched questions with plot deductions and draft previews.",promptSnippet:"Consult author on novel creative decisions, plot branches, scene drafting, or story settings",promptGuidelines:["Invoke ask_author DIRECTLY without conversational preambles or greetings whenever author confirmation is needed for plot, characters, scenes, branches, or settings.","ask_author modes: Always provide a concise formTitle. Use flat mode (formTitle + question + options) for single decisions, or the questions array for batch consultations. Batch interrelated decisions into one ask_author call; never make consecutive single-question calls.","ask_author payload minimization: Strictly omit optional fields (id, description, minSelect, maxSelect, allowEmpty, allowCustom, customPlaceholder) when using default behavior. Never output auto-generated IDs or default boolean flags.","ask_author option quality: Provide clear, descriptive labels summarizing the core action (never generic placeholders like 'Option A'). Include cause-and-effect plot deductions in description, and draft excerpts or dialogue in preview when helpful.","ask_author multi-select & notes: Set multiSelect: true only when choices are not mutually exclusive. The author can always submit free-form notes alongside choices (both per-question and per-option).","ask_author draft directives: Confirmed draft previews are automatically condensed into single-line directives in the ask_author result to guide subsequent story drafting.","ask_author envelopes: [Author Decision Cancelled] means do not proceed with the discarded decision; [Author Consultation Error] means fix the arguments and retry once, then continue without the confirmation if it fails again."]},schema:{optionLabel:"Concise, descriptive label summarizing the action or choice",optionDesc:"Plot consequences, rationale, or deduction (optional)",optionPreview:"Draft prose excerpt or scene storyboard preview (optional, Markdown)",optionCustomPlaceholder:"Placeholder text for option note input (optional)",questionId:"Question identifier (optional, omit unless needed)",questionTitle:"Question title (e.g., 'Choose the next route')",questionDesc:"Context or background for this question (optional)",questionMultiSelect:"Allow multiple selections (default false)",questionMinSelect:"Minimum selections required (default 1)",questionMaxSelect:"Maximum selections allowed (default: all options)",questionAllowEmpty:"Allow submitting with zero selections (default false)",questionOptions:`Candidate options (1-${M}, 2-10 recommended)`,questionAllowCustom:"Allow author free-form custom notes (default true)",questionCustomPlaceholder:"Placeholder text for author note input (optional)",formTitle:"Concise title describing the overall consultation task",formDesc:"Overall context or background for the consultation (optional)",formQuestions:`Batched related questions (1-${D}); batch interrelated decisions into one call instead of consecutive single-question calls`,formSingleQuestion:"Question title for flat single-question mode",flatPrefix:"(Flat single-question mode only) ",flatQuestionOptions:`Candidate options for flat single-question mode (1-${M}, 2-10 recommended)`},markdown:{envelopePrefix:"[Author Decision Finalized]",envelopeSuffix:"Execute workflow and writing strictly according to the author decisions and draft directives above without conversational acknowledgments.",envelopeCancelled:"[Author Decision Cancelled]\nThe author cancelled the consultation. Do not proceed with the discarded decision; propose next steps or ask for clarification in chat.",envelopeError:(e,t)=>`[Author Consultation Error]\nFailed to consult author: ${e}\nGuidance: ${t}`,errorReasons:{nonTui:"Interactive TUI is unavailable in the current execution mode.",noQuestions:"No valid questions or options were provided in arguments.",runtimeError:e=>`Runtime error during consultation UI execution: ${e}`},errorGuidances:{nonTui:"Proceed using defaults or ask user via standard chat/message if crucial.",noQuestions:"Provide at least one question with concrete options.",runtimeError:"Check arguments format and retry with valid questions and options."},envelopeEmpty:"Author submitted the form without any answers. Proceed with your defaults, or ask a follow-up question in chat if the decision is crucial.",noSelection:"(No selection; author skipped this question)",notePrefix:"[Author note: ",noteSuffix:"]",draftDirectivePrefix:"[Draft directive: ",draftDirectiveSuffix:"]",answerLine:(e,t,i)=>`${e}. [${t}]: ${i}`,partSeparator:"; ",draftSegmentSeparator:" / ",optionWithDesc:(e,t,i,s)=>`【${e}】${t}${s?` ${s}`:""}${i?` ${i}`:""}`,optionWithoutDesc:(e,t,i)=>`【${e}】${i?` ${i}`:""}${t?` ${t}`:""}`,unselectedOptionNote:(e,t)=>`[Author note on unselected option 【${e}】] ${t}`,fallbackQuestionTitle:e=>`Question ${e}`,fallbackOptionLabel:e=>`Option ${e}`},command:{name:"ask-author",description:"查看 ask_author 创作请示工具说明",notify:"在提示词中指示 AI 调用 ask_author,即可向您请示创作决策"},common:{optionSeparator:"、",bulletPrefix:$,helpSeparator:" • "},tabs:{questionTab:e=>`第${e}题`,submitTab:`${g.check} 提交答卷`},view:{modeSingle:"[单选]",modeMulti:(e,t,i,s)=>{let o="[多选";return e>1?o+=` 至少${e}项`:(0===e||s)&&(o+=" 可不选"),t<i&&(o+=` 至多${t}项`),`${o}]`},attachedNoteBanner:e=>`${g.note} 题目补充说明:${e}`,optionAttachedNoteBanner:(e,t)=>`${g.note} [${e}] 选项补充说明:${t}`,listTopMore:e=>` ${g.scrollUp} 向上还有 ${e} 项`,listBottomMore:e=>` ${g.scrollDown} 向下还有 ${e} 项`,optionNotePanelTitle:"选项补充说明",storyDescriptionPanelTitle:"情节设定推演",storyDraftPanelTitle:"正文草稿/分镜头试撰写",customNotePanelTitle:"题目补充说明",confirmPanelTitle:"操作说明",customNotePanelCurrent:"当前填写内容:",previewScrollInfo:(e,t,i)=>`(行 ${e}-${t}/${i},PgUp/PgDn 滚动)`,previewNoDraft:"(无内容预览)",customNotePanelTips:[`${$}随时按「N」或选择此项按「Enter」填写/修改本题补充说明`,`${$}按「x」清空本题补充说明`,`${$}补充说明将随所选选项及选项专属补充说明一并提交给 AI 助手`],confirmPanelTips:[`${$}按 \`Enter\` 确认本题并前往下一题`,`${$}最后一题确认后自动进入总览提交页`],sectionTitle:e=>`### ${e}`,fieldLabel:e=>`**${e}**`,italicLine:e=>`_${e}_`},menu:{customNoteLabel:`${g.note} 题目补充说明`,customNoteLabelFilled:`${g.note} 题目补充说明 (已填写)`,confirmDetailMulti:(e,t)=>`已选 ${e}${e<t?`/${t}`:""} 项`,confirmLabel:(e,t)=>`${g.check} 确认本题 (${e}${t?" + 补充说明":""})`,confirmDesc:"确认并前往下一题或总览页"},editor:{title:e=>` ${g.note} 填写题目补充说明(${e})`,optionTitle:(e,t)=>` ${g.note} 填写选项补充说明(${t} - ${e})`,footerHelp:" [Enter] 保存并返回 • [Esc/Ctrl+C] 取消"},status:{questionNoteSaved:"已保存本题补充说明",noteCleared:"已清空本题补充说明",optionNoteSaved:e=>`已保存「${e}」补充说明`,optionNoteSavedUnselected:e=>`已保存「${e}」批注(该选项未勾选,仅作批注提交)`,optionNoteSavedAtLimit:e=>`已达上限(至多 ${e} 项):批注已保存,但该选项未勾选`,optionNoteCleared:e=>`已清空「${e}」补充说明`,selectedUpToLimit:e=>`已达选择上限(至多 ${e} 项)`,maxSelectExceeded:e=>`已达上限(至多 ${e} 项):请按「空格」取消已选项后重选`,minSelectRequired:e=>`未达下限(至少需 ${e} 项):请按「空格」继续勾选,或按「n/N」填写说明`,pleaseAnswerBeforeSubmit:"本题未完成:请按「空格/Enter」选定选项,或按「n/N」填写补充说明",editorDiscardConfirm:"再次按 Esc/Ctrl+C 放弃修改(未保存内容将丢失),继续输入可撤销",errorNonTui:"当前为非交互式终端环境,无法启动交互界面,请在交互式 TUI 会话中重试",errorNoQuestions:"未提供有效的问题或选项,请提供至少一个带选项的问题",errorExecutionFailed:e=>`组件执行异常:${e}(可修正参数后重试)`},help:{cancel:"Esc/Ctrl+C 取消",clearNote:"x 清空补充说明",confirmQuestion:"Enter 确认",confirmSubmit:"Enter 提交",editNote:"n 补充说明",editOptionNote:"n 选项补充说明",editQuestionNote:"N 题目补充说明",jumpQuestion:"1~9 跳转题目",moveCursor:"↑↓/jk 移动",scrollDraft:"PgUp/PgDn 滚动草稿",scrollSummary:"↑↓/PgUp/PgDn 滚动",selectOptionSingle:"空格/Enter 选定",switchQuestion:"Tab/←→/hl 切换题目",toggleAll:"a 全选/清空",toggleCheckMulti:"空格 勾选"},summary:{title:"【答卷总览与确认】",statusDone:"[已完成]",statusPending:"[未完成]",questionPrefix:(e,t)=>`第${e}题:${t}`,selectedLabel:"已选:",optionNoteBadge:`(${g.note} 批注)`,optionNotePrefix:e=>`${g.note} 选项补充说明(${e}):`,customNoteLabel:`${g.note} 题目补充说明:`,allDonePrompt:` ${g.check} 全部完成,按 Enter/Ctrl+S 提交答卷,Tab/hl/1-9 返回修改`,pendingWarning:` ${g.unchecked} 尚有题目未完成,按 Enter 前往作答,Tab/hl/1-9 切换修改`,topMore:e=>` ${g.scrollUp} 向上还有 ${e} 行`,bottomMore:e=>` ${g.scrollDown} 向下还有 ${e} 行`},card:{callQuestionAndOptionCount:(e,t)=>`${e} 题 • ${t} 选项`,callOptionCountOnly:e=>`${e} 选项`,resultCancelled:`${g.cancel} 已取消`,resultErrorShort:`${g.error} 执行失败`,resultCompleted:`${g.check} 已完成`,resultCount:e=>`${e} 项`,expandHint:"展开查看明细",optionBullet:(e,t)=>`${$}${e}:${t}`,optionWithNote:(e,t)=>`${e} [${g.note} ${t}]`,questionNoteTag:e=>` (${g.note} 题目补充说明: ${e})`,errorBullet:e=>`${$}错误原因:${e}`},fallbacks:{defaultFormTitle:"创作决策",defaultQuestionTitle:"请选择接下来的创作方案",defaultCustomPlaceholder:"输入本题整体补充要求、伏笔细节或修改意见...",defaultOptionCustomPlaceholder:"输入针对该选项的具体补充要求或修改细节...",defaultCustomChoiceName:"自定义方案",defaultOptionLabel:"默认方案",unnamedOptionLabel:"未命名方案",unselectedOption:"未选",autoQuestionId:e=>`q_${e}`,autoQuestionTitle:e=>`问题 ${e}`,autoOptionLabel:e=>`选项 ${e}`}};function I(e,t,i){return Math.max(t,Math.min(e,i))}function L(e,t,i){return I(e,0,Math.max(0,t-i))}function Z(e,t,i){return e.slice(t,t+i)}function N(e){return e?e.replace(/\t/g," ").replace(/\r/g,""):""}function Pe(e){return e.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g,"").replace(/\x1b\[[0-?]*[ -/]*[@-~]/g,"").replace(/\x1b[@-_]/g,"").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/g,"")}function w(e){let t=e?.trim();return t&&Pe(N(t))||void 0}function F(e){if("string"==typeof e){if(e.length>1e6)return;try{return JSON.parse(e)}catch{return}}return e}function Qe(e,t,i){let s=V(e);s>=i&&i>0&&(e=J(e,i-1),s=V(e));let o=Math.max(1,i-s),n=" ".repeat(s),r=Le(t,o);return 0===r.length?e?[e]:[]:r.map((t,i)=>0===i?e+t:n+t)}function A(e,t,i,s=""){e.push(...Qe(s,t,i))}function k(e,t){if(t<=0)return"";let i=N(e);return V(i)>t?J(i,t):i}function X(e,t){if(t<=0)return"";let i=N(e),s=V(i);return s>=t?J(i,t):i+" ".repeat(t-s)}function ee(e,t=y.dividerThin){return e>0?t.repeat(e):""}function ae(e){if(!e)return;let t=N(e);if(!t.trim())return;let i=t.split("\n").map(e=>{let t=e.trim();return t?(t=t.replace(/^(?:[>#]+\s*)+/,""),t=t.replace(/\s+/g," "),t):""}).filter(Boolean);return 0!==i.length?i.join(s.markdown.draftSegmentSeparator):void 0}function ce(e){return e.replace(/\s+/g," ").trim()}function R(e){return!!e.customText?.trim()}function Re(e){for(let t of e.optionNotes.values())if(t?.trim())return!0;return!1}function te(e){return R(e)||Re(e)}var qe={selectedIndices:new Set,optionNotes:new Map};function _e(e,t,i){let s=void 0!==e,o=I(e??1,0,i);return{minSelect:o,minSelectExplicit:s,maxSelect:I(t??i,o,i)}}function de(e,t){return{id:w(e.id)??s.fallbacks.autoQuestionId(t+1),title:w(e.title)??s.fallbacks.autoQuestionTitle(t+1),description:w(e.description),multiSelect:!!e.multiSelect,..._e(e.minSelect,e.maxSelect,e.options.length),options:e.options.map(e=>({label:w(e.label)??s.fallbacks.unnamedOptionLabel,description:w(e.description),preview:w(e.preview),customPlaceholder:w(e.customPlaceholder)})),allowEmpty:!!e.allowEmpty,allowCustom:!1!==e.allowCustom,customPlaceholder:w(e.customPlaceholder)??s.fallbacks.defaultCustomPlaceholder}}function De(e){let t=new Set;return e.map((e,i)=>{if(!t.has(e.id))return t.add(e.id),e;let o=s.fallbacks.autoQuestionId(i+1);for(let e=2;t.has(o);e++)o=`${o}_${e}`;return t.add(o),{...e,id:o}})}function P(e){return ce(e).replace(/[“”„‟«»]/g,"'").replace(/[\[\]]/g,e=>"["===e?"[":"]").replace(/[【】]/g,"").replace(/;/g,";")}function Be(e,t){return void 0===t?e:e===s.fallbacks.defaultQuestionTitle||e===s.fallbacks.autoQuestionTitle(t+1)?s.markdown.fallbackQuestionTitle(t+1):e}function me(e,t){return void 0===t?e:e===s.fallbacks.unnamedOptionLabel||e===s.fallbacks.defaultOptionLabel||e===s.fallbacks.autoOptionLabel(t)?s.markdown.fallbackOptionLabel(t):e}var Q=class{formTitle;formDescription;questions;revisionValue=0;states=new Map;constructor(e){let t=e.questions??[],i=t.length>0;this.formTitle=w(e.formTitle)??s.fallbacks.defaultFormTitle,this.formDescription=w(e.description),this.questions=i?De(t.map((e,t)=>de(e,t))):e.options?.length?[de({title:w(e.question)??s.fallbacks.defaultQuestionTitle,description:w(e.description),multiSelect:e.multiSelect,minSelect:e.minSelect,maxSelect:e.maxSelect,allowEmpty:e.allowEmpty,options:e.options,allowCustom:e.allowCustom,customPlaceholder:e.customPlaceholder},0)]:[]}get revision(){return this.revisionValue}get questionCount(){return this.questions.length}getQuestion(e){return this.questions[e]}getState(e){let t=this.states.get(e);return t||(t={selectedIndices:new Set,optionNotes:new Map},this.states.set(e,t)),t}getSelectedIndices(e){let t=this.states.get(e);return t?Array.from(t.selectedIndices).sort((e,t)=>e-t):[]}bumpRevision(){this.revisionValue++}getMenuItems(e){let t=this.getQuestion(e);if(!t)return[];let i=this.getState(e),o=te(i),n=t.options.map((e,t)=>({type:"option",optionIndex:t,label:e.label,description:e.description,preview:e.preview}));t.allowCustom&&n.push({type:"custom_note",label:R(i)?s.menu.customNoteLabelFilled:s.menu.customNoteLabel});let r=t.multiSelect?s.menu.confirmDetailMulti(i.selectedIndices.size,t.minSelect):void 0!==i.selectedIndex?t.options[i.selectedIndex]?.label??s.fallbacks.unselectedOption:o?s.fallbacks.defaultCustomChoiceName:s.fallbacks.unselectedOption;return n.push({type:"confirm_btn",label:s.menu.confirmLabel(r,o),description:s.menu.confirmDesc}),n}isQuestionAnswered(e){let t=this.questions[e];if(!t)return!1;let i=this.states.get(e)??qe;if(!t.multiSelect)return void 0!==i.selectedIndex||t.allowEmpty||t.allowCustom&&te(i);let s=i.selectedIndices.size;return!!(s>=t.minSelect||0===s&&t.allowEmpty||!t.minSelectExplicit&&0===s&&t.allowCustom&&te(i))}areAllQuestionsAnswered(){return this.questions.every((e,t)=>this.isQuestionAnswered(t))}toggleOption(e,t){let i=this.getQuestion(e);if(!i||!i.options[t])return"limit";let s=this.getState(e);return i.multiSelect?s.selectedIndices.has(t)?(s.selectedIndices.delete(t),this.bumpRevision(),"deselected"):s.selectedIndices.size>=i.maxSelect?"limit":(s.selectedIndices.add(t),this.bumpRevision(),"selected"):(s.selectedIndex=s.selectedIndex===t?void 0:t,this.bumpRevision(),void 0===s.selectedIndex?"deselected":"selected")}commitOption(e,t){let i=this.getQuestion(e);if(!i||!i.options[t])return;let s=this.getState(e);i.multiSelect?s.selectedIndices.has(t)||s.selectedIndices.size>=i.maxSelect||(s.selectedIndices.add(t),this.bumpRevision()):s.selectedIndex!==t&&(s.selectedIndex=t,this.bumpRevision())}selectAllOrClear(e){let t=this.getQuestion(e);if(!t||!t.multiSelect)return"cleared";let i=this.getState(e);if(i.selectedIndices.size>=t.maxSelect)return i.selectedIndices.clear(),this.bumpRevision(),"cleared";for(let e=0;e<t.options.length&&i.selectedIndices.size<t.maxSelect;e++)i.selectedIndices.add(e);return this.bumpRevision(),"filled"}setOptionNote(e,t,i){let s=this.getQuestion(e);if(!s||!s.options[t])return"cleared";let o=this.getState(e),n=w(i);return this.bumpRevision(),n?(o.optionNotes.set(t,n),s.multiSelect?o.selectedIndices.has(t)?"saved":o.selectedIndices.size>=s.maxSelect?"savedAtLimit":(o.selectedIndices.add(t),"saved"):void 0===o.selectedIndex?(o.selectedIndex=t,"saved"):o.selectedIndex===t?"saved":"savedUnselected"):(o.optionNotes.delete(t),"cleared")}setQuestionNote(e,t){let i=this.getState(e),s=w(t);return i.customText=s,this.bumpRevision(),s?"saved":"cleared"}clearOptionNote(e,t){let i=this.states.get(e);return!!i?.optionNotes.has(t)&&(i.optionNotes.delete(t),this.bumpRevision(),!0)}clearQuestionNote(e){let t=this.states.get(e);return!!t?.customText?.trim()&&(t.customText=void 0,this.bumpRevision(),!0)}collectOptionNotes(e,t){let i={};if(!t)return i;for(let[s,o]of t.optionNotes){let t=w(o);t&&e.options[s]&&(i[s+1]=t)}return i}compileAnswers(){let e=[];for(let[t,i]of this.questions.entries()){let s=this.states.get(t),o=s?.customText?.trim()?N(s.customText.trim()):void 0,n={questionId:i.id,questionTitle:i.title,customText:o};if(i.multiSelect){let o=this.getSelectedIndices(t),r=this.collectOptionNotes(i,s),l=[];for(let e of o){let t=i.options[e];t&&l.push({...t,customText:r[e+1]})}let a=Object.keys(r).length>0;e.push({...n,multiSelect:!0,selectedOptions:l,selectedIndices:o.map(e=>e+1),optionNotes:a?r:void 0})}else if(void 0!==s?.selectedIndex){let t=i.options[s.selectedIndex],o=this.collectOptionNotes(i,s);e.push({...n,multiSelect:!1,selectedOption:t?{...t,customText:o[s.selectedIndex+1]}:void 0,selectedOptionIndex:s.selectedIndex+1,optionNotes:Object.keys(o).length>0?o:void 0})}else if(this.isQuestionAnswered(t)){let t=this.collectOptionNotes(i,s),o=Object.keys(t).length>0;e.push({...n,multiSelect:!1,optionNotes:o?t:void 0})}}return e}formatAnswers(e){if(0===e.length)return`${s.markdown.envelopePrefix}\n${s.markdown.envelopeEmpty}\n${s.markdown.envelopeSuffix}`;let t=e=>e?`${s.markdown.notePrefix}${P(e)}${s.markdown.noteSuffix}`:"",i=e=>e?`${s.markdown.draftDirectivePrefix}${P(e)}${s.markdown.draftDirectiveSuffix}`:"",o=new Map(this.questions.map(e=>[e.id,e])),n=new Map(this.questions.map((e,t)=>[e.id,t])),r=e.map((e,r)=>{let l=n.get(e.questionId),a=(e.multiSelect?e.selectedOptions??[]:e.selectedOption?[e.selectedOption]:[]).map((o,n)=>{let r=ae(o.preview),l=t(w(o.customText)??""),a=P(me(o.label,e.multiSelect?e.selectedIndices?.[n]:e.selectedOptionIndex)),u=P(o.description??"");return u?s.markdown.optionWithDesc(a,u,i(r),l):s.markdown.optionWithoutDesc(a,i(r),l)}),u=o.get(e.questionId);if(u&&e.optionNotes){let i=new Set(e.multiSelect?e.selectedIndices??[]:void 0!==e.selectedOptionIndex?[e.selectedOptionIndex]:[]);for(let[o,n]of Object.entries(e.optionNotes)){let e=Number(o);if(i.has(e))continue;let r=u.options[e-1]?.label;r&&n&&a.push(s.markdown.unselectedOptionNote(P(me(r,e)),t(n)))}}e.customText&&a.push(t(e.customText));let c=a.join(s.markdown.partSeparator)||s.markdown.noSelection;return s.markdown.answerLine(r+1,P(Be(e.questionTitle,l)),c)});return`${s.markdown.envelopePrefix}\n${r.join("\n")}\n${s.markdown.envelopeSuffix}`}};import{getMarkdownTheme as Fe}from"@earendil-works/pi-coding-agent";import{Markdown as ze}from"@earendil-works/pi-tui";function je(e){let t=Fe();return{...t,heading:t=>e.fg("accent",e.bold(t)),quote:t=>e.fg("muted",t),quoteBorder:t=>e.fg("dim",t),listBullet:t=>e.fg("accent",t),code:i=>Ue(e,i)??t.code(i),codeBlockBorder:t=>e.fg("dim",t),bold:t=>e.bold(t),italic:t=>e.italic(t),link:t=>e.fg("accent",t),linkUrl:t=>e.fg("dim",t)}}function Ue(e,t){if(!t.startsWith(B))return;let i=[],s=0;for(;s<t.length;){if(t.charAt(s)!==B)return;let o=ue.get(t.charAt(s+1));if(!o)return;let n=t.indexOf(B,s+2),r=n<0?t.length:n;i.push(o.paint(e,t.slice(s+2,r))),s=r}return i.length>0?i.join(""):void 0}var He=/^ {0,3}(`{3,}|~{3,})/,Ve=/^ {4,}\S/,Xe=/^ {0,3}(?:<(?=[/!?a-zA-Z])|\|)/,pe=/^ {0,3}(?:\$\$|\\\[)/;function fe(e){for(let t=0;t<e.length;t++){let i=e.charAt(t);if("\\"===i){if("("===e.charAt(t+1)||"["===e.charAt(t+1))return!0;t++;continue}if("$"!==i)continue;let s=e.charAt(t+1);if(""!==s&&!/\s/.test(s))return!0}return!1}var We=/^(?: {0,3}>[ \t]?)+/,Ge=/^ {0,3}\[[^\]\n]*\]:/;function be(e){return e.replace(We,"")}function Ke(e){return!!e.includes("|")&&/^ {0,3}\|?[ \t]*:?-+:?[ \t]*(?:\|[ \t]*:?-+:?[ \t]*)*\|?[ \t]*$/.test(be(e))}var Ye=/^ {0,3}(?:#{1,6}[ \t]|[-+*][ \t]|\d{1,9}[.)][ \t]|>|\||`{3,}|~{3,})/,Je=/[`*_\[\]<>$\\|]|~~|&(?:[A-Za-z][A-Za-z0-9]*|#\d+|#[xX][0-9a-fA-F]+);/;function Ze(e){let t=[],i=0;for(let s of e)t.push(i),i+=s.length+1;return t}function et(e,t,i){let s=new Uint8Array(e.length),o=(e,t)=>{for(let i=e;i<t;i++)s[i]=1},n=e=>{let s=i[e]??0;o(s,s+(t[e]?.length??0))},r=null,l=!1,a=null,u="";for(let e=0;e<t.length;e++){let i=t[e]??"";if(""===i.trim()){l=!1,null!==r&&n(e),null!==a&&n(e);continue}if(null!==a){u+=i,n(e);let t=i.indexOf(a);if(t>=0){let e=i.slice(t+a.length);a=null,fe(e)&&(l=!0)}continue}if(null!==r){n(e),r.test(i)&&(r=null);continue}if(l){n(e);continue}if(Ke(i)){n(e),l=!0;let i=t[e-1];void 0!==i&&i.includes("|")&&n(e-1);continue}let s=He.exec(i),o=s?.[1],c=i.slice(s?.[0].length??0);if(void 0===o||"~"!==o[0]&&c.includes("`"))if(Ve.test(i))n(e);else if(Xe.test(be(i)))n(e),l=!0;else if(Ge.test(i))n(e);else{if(pe.test(i)){let t=i.replace(pe,"");u=t,n(e),t.includes(t.startsWith("$$")?"$$":"\\]")||(a=t.startsWith("$$")?"$$":"\\]");continue}fe(i)&&(n(e),l=!0)}else n(e),r=new RegExp(`^ {0,3}${o[0]??"`"}{${o.length},}[ \\t]*$`)}let c=0;for(;c<e.length;){if("`"!==e[c]||1===s[c]){c++;continue}let t=0;for(;"`"===e[c+t]&&0===s[c+t];)t++;let i=tt(e,s,c+t,t);if(i<0)return null;o(c,i+t),c=i+t}return s}function tt(e,t,i,s){for(let o=i;o<e.length;o++){if("`"!==e[o]||1===t[o])continue;let i=0;for(;"`"===e[o+i]&&0===t[o+i];)i++;if(i===s&&"`"!==e[o+s])return o;o+=i-1}return-1}function nt(e,t,i,s){let o=[],n=[];for(let r=0;r<e.length;r++){if(1===t[r])continue;let l=e[r]??"",a=le.get(l);if(void 0!==a){o.push({template:a,start:r});continue}let u=it(o,l);if(u<0)continue;let c=o[u];if(o.length=u,!c)continue;let d=r+1;if(d-c.start>c.template.maxSpanLength||Je.test(e.slice(c.start,d)))continue;let h=he(s,c.start),m=he(s,d-1);if(!ot(i,h,m))continue;let p=n[n.length-1];for(;void 0!==p&&p.startFlat>=c.start;)n.pop(),p=n[n.length-1];n.push({template:c.template,startFlat:c.start,startLine:h,startCol:c.start-(s[h]??0),endLine:m,endCol:d-(s[m]??0)})}return n}function it(e,t){for(let i=e.length-1;i>=0;i--)if(e[i]?.template.closer===t)return i;return-1}function he(e,t){let i=0,s=Math.max(0,e.length-1);for(;i<s;){let o=i+s+1>>1;(e[o]??0)<=t?i=o:s=o-1}return i}function ot(e,t,i){if(t===i)return!0;for(let s=t+1;s<=i;s++){let t=e[s]??"";if(""===t.trim()||Ye.test(t))return!1}return!0}function st(e,t){let i=[];for(let s of t)i.push(`${B}${s.template.marker}${e.slice(s.start,s.end)}`);return`\`${i.join("")}\``}function rt(e,t){let i=[],s=0,o=0;for(;o<t.length;){let n=t[o];if(!n)break;let r=[n],l=n;for(;o+1<t.length;){let e=t[o+1];if(!e||e.start!==l.end)break;o++,l=e,r.push(e)}let a=n.start,u=l.end,c=e.slice(s,a),d=e.slice(a,u),h="`"===e.charAt(a-1)||"`"===e.charAt(u);i.push(c,h?d:st(e,r)),s=u,o++}return i.push(e.slice(s)),i.join("")}function lt(e,t,i){for(let s=t;s<i;s++)if(1===e[s])return!0;return!1}function ut(e,t,i,s){let o=new Map;for(let n of s){let s=(t[n.startLine]??0)+n.startCol,r=(t[n.endLine]??0)+n.endCol;for(let l=n.startLine;l<=n.endLine;l++){let a=t[l]??0,u=a+(e[l]?.length??0),c=Math.max(s,a),d=Math.min(r,u);if(d<=c||lt(i,c,d))continue;let h={start:c-a,end:d-a,template:n.template},m=o.get(l);m?m.push(h):o.set(l,[h])}}return e.map((e,t)=>{let i=o.get(t);return i?(i.sort((e,t)=>e.start-t.start),rt(e,i)):e}).join("\n")}function at(e){if(0===e.length)return e;let t=e.split("\n"),i=Ze(t),s=et(e,t,i);if(null===s)return e;let o=nt(e,s,t,i);return 0===o.length?e:ut(t,i,s,o)}function ge(e,t){return`${s.view.sectionTitle(e)}\n\n${t.map(e=>`${e}\n`).join("")}`}function Se(e,t,i,o,n){if(!e)return[];let r="";"option"===e.type?(i&&(r+=`${s.view.sectionTitle(s.view.optionNotePanelTitle)}\n\n${s.view.fieldLabel(s.view.customNotePanelCurrent)}\n\n${N(i)}\n\n`),e.description&&(r+=`${s.view.sectionTitle(s.view.storyDescriptionPanelTitle)}\n\n${N(e.description)}\n\n`),e.preview?r+=`${s.view.sectionTitle(s.view.storyDraftPanelTitle)}\n\n${N(e.preview)}\n\n`:!e.description&&!i&&(r+=`${s.view.italicLine(s.view.previewNoDraft.trim())}\n\n`)):"custom_note"===e.type?(r+=ge(s.view.customNotePanelTitle,s.view.customNotePanelTips),t&&(r+=`\n${s.view.fieldLabel(s.view.customNotePanelCurrent)}\n\n${N(t)}\n\n`)):r+=ge(s.view.confirmPanelTitle,s.view.confirmPanelTips);let l=je(n);return new ze(r.trim(),1,0,l,void 0,{transform:at}).render(o)}import{visibleWidth as ne,wrapTextWithAnsi as ct}from"@earendil-works/pi-tui";function z(e,t){return e.options[t]?.label??s.fallbacks.autoOptionLabel(t+1)}function dt(e,t,i){return"option"===e.type&&void 0!==e.optionIndex?t.multiSelect?i.selectedIndices.has(e.optionIndex):i.selectedIndex===e.optionIndex:"custom_note"===e.type&&R(i)}function mt(e,t,i,s){return"option"!==s.type||void 0===s.optionIndex?"":t.multiSelect?i.selectedIndices.has(s.optionIndex)?e.fg("success",g.checkedMulti):e.fg("dim",g.uncheckedMulti):i.selectedIndex===s.optionIndex?e.fg("accent",g.checkedSingle):e.fg("dim",g.uncheckedSingle)}function Te(e,t,i,s,o,n){let r=dt(t,s,o),l=`${i?e.fg("accent",e.bold(`${g.cursor} `)):" "}${mt(e,s,o,t)}`,a=ne(l),u=" ".repeat(a),c=Math.max(8,n-a),d=t.label;return"option"===t.type&&void 0!==t.optionIndex&&o.optionNotes.get(t.optionIndex)&&(d=`${t.label} ${g.note}`),(ne(d)<=c?[d]:ct(d,c)).map((s,o)=>{if(i)return e.bg("selectedBg",e.fg("text",e.bold(X(0===o?`${l}${s}`:`${u}${s}`,n))));let a=e.fg("option"!==t.type&&"custom_note"!==t.type||r?"text":"dim",s);return k(0===o?`${l}${a}`:`${u}${a}`,n)})}function we(e,t,i,o){let n=i===t.questionCount,r=[],l=[];for(let o=0;o<t.questionCount;o++){let n=o===i,r=t.isQuestionAnswered(o),a=r?"success":"muted",u=` ${r?g.checked:g.unchecked} ${s.tabs.questionTab(o+1)} `;l.push(n?e.bg("selectedBg",e.fg("accent",e.bold(u))):e.fg(a,u))}let a=` ${s.tabs.submitTab} `;l.push(n?e.bg("selectedBg",e.fg("success",e.bold(a))):e.fg(t.areAllQuestionsAnswered()?"success":"dim",a));let u="",c=0;for(let e of l){let t=ne(e);c>0&&c+1+t>o?(r.push(k(u,o)),u="",c=0):c>0&&(u+=" ",c+=1),u+=e,c+=t}return r.push(k(u,o)),r.push(e.fg("dim",ee(o))),r}function pt(e){return e.join(s.common.helpSeparator)}function ie(e,t,i,s){A(e,i.fg("dim",pt(t)),s," ")}function j(e,t,i){e.push(t.fg("accent",ee(i,y.dividerThick)))}function ve(e){return[s.help.switchQuestion,...e.questionCount>1?[s.help.jumpQuestion]:[],s.help.scrollSummary,s.help.confirmSubmit,s.help.cancel]}function ye(e,t,i){let o=[];for(let n=0;n<e.questionCount;n++){let r=e.getQuestion(n);if(!r)continue;let l=e.isQuestionAnswered(n),a=e.getState(n);A(o,`${l?t.fg("success",s.summary.statusDone):t.fg("warning",s.summary.statusPending)} ${t.bold(s.summary.questionPrefix(n+1,r.title))}`,i," ");let u=[];if(r.multiSelect)for(let t of e.getSelectedIndices(n)){let e=r.options[t];if(!e)continue;let i=a.optionNotes.get(t);u.push(i?`${e.label} ${s.summary.optionNoteBadge}`:e.label)}else if(void 0!==a.selectedIndex){let e=r.options[a.selectedIndex];if(e){let t=a.optionNotes.get(a.selectedIndex);u.push(t?`${e.label} ${s.summary.optionNoteBadge}`:e.label)}}let c=u.length?t.fg("accent",u.join(s.common.optionSeparator)):t.fg("dim",s.fallbacks.unselectedOption);A(o,`${t.fg("muted",s.summary.selectedLabel)} ${c}`,i," ");for(let[e,n]of a.optionNotes.entries()){let l=r.options[e];l&&n?.trim()&&A(o,`${t.fg("muted",s.summary.optionNotePrefix(l.label))} ${t.fg("accent",n)}`,i," ")}a.customText&&A(o,`${t.fg("muted",s.summary.customNoteLabel)} ${t.fg("accent",a.customText)}`,i," "),o.push("")}return o}var gt=32,W=class{_focused=!1;get focused(){return this._focused}set focused(e){this._focused!==e&&(this._focused=e,this.editor.focused=this.inputMode&&e,this.touch(),this.tabCache=void 0,this.leftColCache=void 0)}tui;theme;model;done;currentTab=0;cursorByTab=new Map;currentOptionIndex=0;listScrollOffset=0;previewScrollOffset=0;summaryScrollOffset=0;inputMode=!1;editorEscArmed=!1;statusMessage=null;statusTimer=null;isFinished=!1;disposed=!1;abortBinding;cachedWidth;cachedLayoutWidth;cachedHeight;cachedLines;previewCache=new Map;summaryCachedLines;tabCache;leftColCache;menuCache;previewViewportObservation;editTarget;editor;constructor(e,t,i,o){this.tui=e,this.theme=t,this.model=i,this.done=o,this.editor=new ft(this.tui,{borderColor:e=>this.theme.fg("accent",e),selectList:{selectedPrefix:e=>this.theme.fg("accent",e),selectedText:e=>this.theme.fg("accent",e),description:e=>this.theme.fg("muted",e),scrollInfo:e=>this.theme.fg("dim",e),noMatch:e=>this.theme.fg("warning",e)}}),this.editor.focused=!1,this.editor.onSubmit=e=>{let t=this.editTarget;if(t){if("option"===t.type){let i=this.model.getQuestion(this.currentTab),o=t.optionIndex,n=i?z(i,o):s.fallbacks.autoOptionLabel(o+1),r=this.model.setOptionNote(this.currentTab,o,e);this.setStatus("cleared"===r?s.status.optionNoteCleared(n):"savedAtLimit"===r&&i?s.status.optionNoteSavedAtLimit(i.maxSelect):"savedUnselected"===r?s.status.optionNoteSavedUnselected(n):s.status.optionNoteSaved(n))}else{let t=this.model.setQuestionNote(this.currentTab,e);this.setStatus("saved"===t?s.status.questionNoteSaved:s.status.noteCleared)}this.touchDerived(),this.closeEditor()}else this.closeEditor()}}bindAbortSignal(e){if(!e||this.abortBinding||this.disposed)return;if(e.aborted)return void queueMicrotask(()=>this.finish(!0));let t=()=>this.finish(!0);e.addEventListener("abort",t,{once:!0}),this.abortBinding={signal:e,onAbort:t}}dispose(){this.disposed||(this.disposed=!0,this.clearStatusTimer(),this.abortBinding&&(this.abortBinding.signal.removeEventListener("abort",this.abortBinding.onAbort),this.abortBinding=void 0),this.clearDerivedCaches(),this.previewViewportObservation=void 0,this.clearFrameCaches())}clearFrameCaches(){this.cachedWidth=void 0,this.cachedLayoutWidth=void 0,this.cachedHeight=void 0,this.cachedLines=void 0}clearDerivedCaches(){this.previewCache.clear(),this.summaryCachedLines=void 0,this.tabCache=void 0,this.leftColCache=void 0,this.menuCache=void 0}touch(){this.clearFrameCaches(),this.tui.requestRender()}touchDerived(){this.clearDerivedCaches(),this.touch()}invalidate(){this.touchDerived(),this.editor.invalidate()}clearStatusTimer(){this.statusTimer&&(clearTimeout(this.statusTimer),this.statusTimer=null)}setStatus(e){this.statusMessage=e,this.clearStatusTimer(),this.statusTimer=setTimeout(()=>{this.statusMessage=null,this.statusTimer=null,this.touch()},y.statusDurationMs),this.touch()}finish(e){this.isFinished||(this.isFinished=!0,this.dispose(),this.done({formTitle:this.model.formTitle,formDescription:this.model.formDescription,questions:this.model.questions,answers:e?[]:this.model.compileAnswers(),cancelled:e}))}get totalTabs(){return this.model.questionCount+1}get isSubmitTab(){return this.currentTab===this.model.questionCount}get terminalRows(){let e=this.tui.terminal?.rows;return"number"==typeof e&&e>0?e:24}get summaryPageSize(){return I(this.terminalRows-10,4,y.summaryPageSize)}get splitPreviewHeight(){return I(this.terminalRows-14,6,y.splitPreviewHeight)}get singlePreviewHeight(){return I(this.terminalRows-18,3,y.singlePreviewHeight)}getMenuItems(){let e=this.menuCache;if(e&&e.tab===this.currentTab&&e.revision===this.model.revision)return e.items;let t=this.model.getMenuItems(this.currentTab);return this.menuCache={tab:this.currentTab,revision:this.model.revision,items:t},t}currentMenuItem(){return this.getMenuItems()[this.currentOptionIndex]}switchTab(e){if(e!==this.currentTab){if(this.currentTab=e,this.previewScrollOffset=0,this.summaryScrollOffset=0,this.listScrollOffset=0,this.menuCache=void 0,this.isSubmitTab)this.currentOptionIndex=0;else{let e=this.model.getState(this.currentTab);this.currentOptionIndex=this.cursorByTab.get(this.currentTab)??e.selectedIndex??0,this.adjustScrollForCurrentOption()}this.touch()}}adjustScrollForCurrentOption(){let e=this.getMenuItems().length;0!==e&&(this.currentOptionIndex=I(this.currentOptionIndex,0,e-1),this.currentOptionIndex<this.listScrollOffset?this.listScrollOffset=this.currentOptionIndex:this.currentOptionIndex>=this.listScrollOffset+y.visibleOptionCount&&(this.listScrollOffset=this.currentOptionIndex-y.visibleOptionCount+1))}moveCursor(e){let t=this.getMenuItems().length;if(0===t)return;let i=I("number"==typeof e?e:this.currentOptionIndex+e.delta,0,t-1);i!==this.currentOptionIndex&&(this.currentOptionIndex=i,this.cursorByTab.set(this.currentTab,i),this.previewScrollOffset=0,this.adjustScrollForCurrentOption(),this.touch())}getEditTargetForCurrentSelection(){let e=this.currentMenuItem();return"option"===e?.type&&void 0!==e.optionIndex?{type:"option",optionIndex:e.optionIndex}:{type:"question"}}openCustomEditor(e){let t=this.model.getQuestion(this.currentTab);if(!t||!t.allowCustom)return;let i=this.model.getState(this.currentTab),s=e??this.getEditTargetForCurrentSelection();this.editTarget=s,this.inputMode=!0,this.editorEscArmed=!1;let o="option"===s.type?i.optionNotes.get(s.optionIndex)??"":i.customText??"";this.editor.setText(o),this.editor.focused=this._focused,this.touch()}closeEditor(){this.inputMode=!1,this.editorEscArmed=!1,this.editTarget=void 0,this.editor.focused=!1,this.editor.setText(""),this.touch()}confirmAndAdvance(e,t){"option"===e.type&&void 0!==e.optionIndex&&this.model.commitOption(this.currentTab,e.optionIndex),this.model.isQuestionAnswered(this.currentTab)?this.switchTab(this.currentTab<this.model.questionCount-1?this.currentTab+1:this.model.questionCount):this.setStatus(t.multiSelect?s.status.minSelectRequired(t.minSelect):s.status.pleaseAnswerBeforeSubmit)}handleInput(e){if(this.isFinished||this.disposed)return;let t=T(e,S.escape)||T(e,S.ctrl("c"));if(this.inputMode)return t?""===this.editor.getText()||this.editorEscArmed?void this.closeEditor():(this.editorEscArmed=!0,void this.setStatus(s.status.editorDiscardConfirm)):(this.editorEscArmed&&(this.editorEscArmed=!1,this.statusMessage=null),this.editor.handleInput(e),void this.touch());if(t)this.finish(!0);else{if(/^[1-9]$/.test(e)){let t=parseInt(e,10)-1;if(t<this.model.questionCount)return void this.switchTab(t)}T(e,S.tab)||T(e,S.right)||"l"===e?this.switchTab((this.currentTab+1)%this.totalTabs):T(e,S.shift("tab"))||T(e,S.left)||"h"===e?this.switchTab((this.currentTab-1+this.totalTabs)%this.totalTabs):this.isSubmitTab?this.handleSummaryInput(e):this.handleQuestionInput(e)}}handleQuestionInput(e){let t=this.model.getQuestion(this.currentTab);if(!t||this.handleNoteShortcut(t,e)||this.handlePreviewScroll(e)||this.handleCursorNavigation(e)||this.handleSelectAllShortcut(t,e))return;let i=this.currentMenuItem();if(i){if(T(e,S.ctrl("s")))return void this.confirmAndAdvance(i,t);if(T(e,S.space))return void this.activateMenuItem(i,t);T(e,S.enter)&&("custom_note"===i.type?this.openCustomEditor({type:"question"}):this.confirmAndAdvance(i,t))}}handleNoteShortcut(e,t){if(!e.allowCustom)return!1;if("N"===t)return this.openCustomEditor({type:"question"}),!0;if("n"===t||"e"===t)return this.openCustomEditor(),!0;if("x"===t||T(t,S.delete)){let t=this.currentMenuItem();if("option"===t?.type&&void 0!==t.optionIndex){let i=t.optionIndex;if(this.model.clearOptionNote(this.currentTab,i))return this.setStatus(s.status.optionNoteCleared(z(e,i))),!0}if(this.model.clearQuestionNote(this.currentTab))return this.setStatus(s.status.noteCleared),!0}return!1}handlePreviewScroll(e){let t=0;if(T(e,S.pageUp)||"["===e||T(e,S.alt("up"))?t=-y.previewScrollStep:(T(e,S.pageDown)||"]"===e||T(e,S.alt("down")))&&(t=y.previewScrollStep),0===t)return!1;let i=this.previewViewportObservation,s=i?.viewHeight??this.singlePreviewHeight;return this.previewScrollOffset=L(this.previewScrollOffset+t,i?.total??0,s),this.touch(),!0}handleCursorNavigation(e){return T(e,S.up)||"k"===e?(this.moveCursor({delta:-1}),!0):T(e,S.down)||"j"===e?(this.moveCursor({delta:1}),!0):T(e,S.home)?(this.moveCursor(0),!0):!!T(e,S.end)&&(this.moveCursor(Number.MAX_SAFE_INTEGER),!0)}handleSelectAllShortcut(e,t){return!(!e.multiSelect||"a"!==t||("filled"===this.model.selectAllOrClear(this.currentTab)&&e.maxSelect<e.options.length?this.setStatus(s.status.selectedUpToLimit(e.maxSelect)):this.touch(),0))}activateMenuItem(e,t){"option"!==e.type||void 0===e.optionIndex?"custom_note"!==e.type?this.confirmAndAdvance(e,t):this.openCustomEditor({type:"question"}):"limit"===this.model.toggleOption(this.currentTab,e.optionIndex)?this.setStatus(s.status.maxSelectExceeded(t.maxSelect)):this.touch()}handleSummaryInput(e){if(T(e,S.enter)||T(e,S.ctrl("s"))){if(this.model.areAllQuestionsAnswered())this.finish(!1);else{let e=this.model.questions.findIndex((e,t)=>!this.model.isQuestionAnswered(t));if(-1!==e){this.switchTab(e);let t=this.model.getQuestion(e);t&&this.setStatus(t.multiSelect?s.status.minSelectRequired(t.minSelect):s.status.pleaseAnswerBeforeSubmit)}}return}if(T(e,S.home))return this.summaryScrollOffset=0,void this.touch();if(T(e,S.end))return this.summaryScrollOffset=Number.MAX_SAFE_INTEGER,void this.touch();let t=this.summaryScrollStep(e);if(0===t&&T(e,S.space)&&(t=y.summaryScrollStep),0===t)return;let i=this.summaryCachedLines?.lines.length??0,o=this.summaryPageSize,n=L(this.summaryScrollOffset,i,o);this.summaryScrollOffset=L(n+t,i,o),this.touch()}summaryScrollStep(e){return T(e,S.pageUp)||"["===e||T(e,S.alt("up"))?-y.summaryScrollStep:T(e,S.pageDown)||"]"===e||T(e,S.alt("down"))?y.summaryScrollStep:T(e,S.up)||"k"===e?-1:T(e,S.down)||"j"===e?1:0}render(e){let t=Math.max(20,e),i=this.terminalRows;if(this.cachedLines&&this.cachedWidth===e&&this.cachedLayoutWidth===t&&this.cachedHeight===i)return this.cachedLines;let s=[];j(s,this.theme,t);let o=`${this.currentTab}_${this.model.revision}_${t}`;return(!this.tabCache||this.tabCache.key!==o)&&(this.tabCache={key:o,lines:we(this.theme,this.model,this.currentTab,t)}),s.push(...this.tabCache.lines),this.isSubmitTab?this.renderSubmitSummary(s,t):this.inputMode?this.renderInputMode(s,t):this.renderQuestionView(s,t),this.cachedWidth=e,this.cachedLayoutWidth=t,this.cachedHeight=i,this.cachedLines=s.map(t=>k(t,e)),this.cachedLines}pushStatusMessage(e){this.statusMessage&&(e.push(""),e.push(` ${this.theme.fg("warning",this.statusMessage)}`))}renderSubmitSummary(e,t){e.push(this.theme.fg("accent",this.theme.bold(s.summary.title))),e.push("");let i=t-2;(!this.summaryCachedLines||this.summaryCachedLines.width!==i||this.summaryCachedLines.revision!==this.model.revision)&&(this.summaryCachedLines={width:i,revision:this.model.revision,lines:ye(this.model,this.theme,i)});let o=this.summaryCachedLines.lines,n=o.length,r=this.summaryPageSize,l=L(this.summaryScrollOffset,n,r);l>0&&e.push(this.theme.fg("dim",s.summary.topMore(l)));for(let t of Z(o,l,r))e.push(t);let a=n-(l+r);a>0&&e.push(this.theme.fg("dim",s.summary.bottomMore(a))),e.push(""),A(e,this.model.areAllQuestionsAnswered()?this.theme.fg("success",this.theme.bold(s.summary.allDonePrompt)):this.theme.fg("warning",s.summary.pendingWarning),i),this.pushStatusMessage(e),e.push(""),ie(e,ve(this.model),this.theme,t),j(e,this.theme,t)}renderInputMode(e,t){let i=this.model.getQuestion(this.currentTab);if(i){if("option"===this.editTarget?.type){let o=this.editTarget.optionIndex,n=i.options[o],r=z(i,o),l=n?.customPlaceholder??s.fallbacks.defaultOptionCustomPlaceholder;e.push(this.theme.fg("accent",this.theme.bold(s.editor.optionTitle(i.title,r)))),n?.description&&A(e,this.theme.fg("muted",n.description),t," "),e.push(""),e.push(this.theme.fg("accent",` ${l}`))}else e.push(this.theme.fg("accent",this.theme.bold(s.editor.title(i.title)))),i.description&&A(e,this.theme.fg("muted",i.description),t," "),e.push(""),e.push(this.theme.fg("accent",` ${i.customPlaceholder}`));e.push("");for(let i of this.editor.render(Math.max(10,t-4)))e.push(` ${i}`);e.push(""),e.push(this.theme.fg("dim",s.editor.footerHelp)),j(e,this.theme,t)}}renderQuestionView(e,t){let i=this.model.getQuestion(this.currentTab);if(!i)return;let o=this.model.getState(this.currentTab),n=this.getMenuItems();A(e,` ${i.multiSelect?this.theme.fg("warning",s.view.modeMulti(i.minSelect,i.maxSelect,i.options.length,i.allowEmpty)):this.theme.fg("accent",s.view.modeSingle)} ${this.theme.bold(i.title)}`,t),i.description&&A(e,this.theme.fg("muted",i.description),t," "),o.customText?.trim()&&A(e,this.theme.fg("success",s.view.attachedNoteBanner(o.customText)),t," ");let r=this.currentMenuItem();if("option"===r?.type&&void 0!==r.optionIndex){let n=o.optionNotes.get(r.optionIndex)?.trim();if(n){let o=z(i,r.optionIndex);A(e,this.theme.fg("success",s.view.optionAttachedNoteBanner(o,n)),t," ")}}e.push("");let l=i.options.some((e,t)=>!!e.preview||ht(e.description??"")>30||!!o.optionNotes.get(t));t>=y.splitViewMinWidth&&l?this.renderSplitView(e,i,o,n,t):this.pushListItems(e,n,i,o,t-2,l),this.pushStatusMessage(e),e.push("");let a=[s.help.switchQuestion];this.model.questionCount>1&&a.push(s.help.jumpQuestion),a.push(s.help.moveCursor),"option"===r?.type?(a.push(s.help.editOptionNote),a.push(s.help.editQuestionNote)):a.push(s.help.editNote),("option"===r?.type&&void 0!==r.optionIndex&&o.optionNotes.has(r.optionIndex)||R(o))&&a.push(s.help.clearNote),a.push(i.multiSelect?s.help.toggleCheckMulti:s.help.selectOptionSingle),i.multiSelect&&(a.push(s.help.toggleAll),a.push(s.help.confirmQuestion)),l&&a.push(s.help.scrollDraft),a.push(s.help.cancel),ie(e,a,this.theme,t),j(e,this.theme,t)}previewContentLines(e,t,i,s){if(!e)return[];let o=[this.currentTab,e.type,e.optionIndex??-1,s,t??"",i??""].join(""),n=this.previewCache.get(o);if(n)return n;let r=Se(e,t,i,s,this.theme);if(this.previewCache.size>=gt){let e=this.previewCache.keys().next();e.done||this.previewCache.delete(e.value)}return this.previewCache.set(o,r),r}buildPreviewBox(e,t,i,s,o){let n=this.previewContentLines(e,t,i,s),r=n.length;return this.previewViewportObservation={total:r,viewHeight:o},this.slicePreviewBoxLines(n,L(this.previewScrollOffset,r,o),o,s)}slicePreviewBoxLines(e,t,i,o){let n=e.length,r=L(t,n,i),l=Z(e,r,i).map(e=>k(e,o));if(n>i){let e=s.view.previewScrollInfo(r+1,Math.min(n,r+i),n);l.push(k(` ${this.theme.fg("dim",e)}`,o))}return l}renderSplitView(e,t,i,s,o){let n,r=I(Math.floor(o*y.leftColRatio),y.leftColMin,y.leftColMax),l=Math.max(20,o-r-y.dividerWidth),a=`${this.currentTab}_${this.currentOptionIndex}_${this.listScrollOffset}_${this.model.revision}_${r}`;this.leftColCache&&this.leftColCache.key===a?n=this.leftColCache.lines:(n=[],this.pushListItems(n,s,t,i,r,!1),this.leftColCache={key:a,lines:n});let u=s[this.currentOptionIndex],c="option"===u?.type&&void 0!==u.optionIndex?i.optionNotes.get(u.optionIndex):void 0,d=this.buildPreviewBox(u,i.customText,c,l,this.splitPreviewHeight),h=Math.max(n.length,d.length);for(let t=0;t<h;t++){let i=d[t]??"";e.push(`${X(n[t]??"",r)}${this.theme.fg("dim",y.dividerChar)}${k(i,l)}`)}}pushListItems(e,t,i,o,n,r){this.listScrollOffset>0&&e.push(this.theme.fg("dim",s.view.listTopMore(this.listScrollOffset)));let l=t.slice(this.listScrollOffset,this.listScrollOffset+y.visibleOptionCount);for(let[s,a]of l.entries()){if(!a)continue;let l=this.listScrollOffset+s,u=l===this.currentOptionIndex,c=t[l-1];if(c&&"option"===c.type&&"option"!==a.type&&e.push(""),e.push(...Te(this.theme,a,u,i,o,n)),u&&r&&("option"!==a.type||a.description||a.preview||o.optionNotes.has(a.optionIndex??-1))){e.push("");let t="option"===a.type&&void 0!==a.optionIndex?o.optionNotes.get(a.optionIndex):void 0,i=this.buildPreviewBox(a,o.customText,t,Math.max(10,n-2),this.singlePreviewHeight);for(let t of i)e.push(t);e.push("")}}let a=t.length-(this.listScrollOffset+y.visibleOptionCount);a>0&&e.push(this.theme.fg("dim",s.view.listBottomMore(a)))}};import{Type as f}from"typebox";var xe=f.Object({label:f.String({description:s.schema.optionLabel}),description:f.Optional(f.String({description:s.schema.optionDesc})),preview:f.Optional(f.String({description:s.schema.optionPreview})),customPlaceholder:f.Optional(f.String({description:s.schema.optionCustomPlaceholder}))}),bt=f.Object({id:f.Optional(f.String({description:s.schema.questionId})),title:f.String({description:s.schema.questionTitle}),description:f.Optional(f.String({description:s.schema.questionDesc})),multiSelect:f.Optional(f.Boolean({description:s.schema.questionMultiSelect})),minSelect:f.Optional(f.Integer({description:s.schema.questionMinSelect,minimum:0})),maxSelect:f.Optional(f.Integer({description:s.schema.questionMaxSelect,minimum:1})),allowEmpty:f.Optional(f.Boolean({description:s.schema.questionAllowEmpty})),options:f.Array(xe,{description:s.schema.questionOptions,minItems:1,maxItems:M}),allowCustom:f.Optional(f.Boolean({description:s.schema.questionAllowCustom})),customPlaceholder:f.Optional(f.String({description:s.schema.questionCustomPlaceholder}))}),oe=f.Object({formTitle:f.String({description:s.schema.formTitle}),description:f.Optional(f.String({description:s.schema.formDesc})),questions:f.Optional(f.Array(bt,{description:s.schema.formQuestions,minItems:1,maxItems:D})),question:f.Optional(f.String({description:s.schema.formSingleQuestion})),multiSelect:f.Optional(f.Boolean({description:`${s.schema.flatPrefix}${s.schema.questionMultiSelect}`})),minSelect:f.Optional(f.Integer({description:`${s.schema.flatPrefix}${s.schema.questionMinSelect}`,minimum:0})),maxSelect:f.Optional(f.Integer({description:`${s.schema.flatPrefix}${s.schema.questionMaxSelect}`,minimum:1})),allowEmpty:f.Optional(f.Boolean({description:`${s.schema.flatPrefix}${s.schema.questionAllowEmpty}`})),options:f.Optional(f.Array(xe,{description:s.schema.flatQuestionOptions,minItems:1,maxItems:M})),allowCustom:f.Optional(f.Boolean({description:`${s.schema.flatPrefix}${s.schema.questionAllowCustom}`})),customPlaceholder:f.Optional(f.String({description:`${s.schema.flatPrefix}${s.schema.questionCustomPlaceholder}`}))});function K(e){return"object"==typeof e&&null!==e&&!Array.isArray(e)}function St(e){let t=F(e.raw);return K(t)?t:(delete e.raw,e)}function C(e,t){for(let i of t){let t=e[i];if("string"==typeof t)return t}}function q(e,t){if("boolean"==typeof e)return e;if(1===e)return!0;if(0===e)return!1;if("string"==typeof e){let t=e.trim().toLowerCase();if("true"===t||"1"===t)return!0;if("false"===t||"0"===t)return!1}return t}function Ae(e){if("number"==typeof e&&Number.isFinite(e))return Math.floor(e);if("string"==typeof e){let t=parseInt(e.trim(),10);return Number.isFinite(t)?t:void 0}}function Ce(e){let t=Ae(e);return void 0===t?void 0:Math.max(0,t)}function Ie(e){let t=Ae(e);return void 0!==t&&t>=1?t:void 0}function Ne(e){let t=F(e);return Array.isArray(t)?t.slice(0,M).map((e,t)=>{if("string"==typeof e)return{label:e.trim()||s.fallbacks.autoOptionLabel(t+1)};if(!K(e))return{label:s.fallbacks.autoOptionLabel(t+1)};let i=e;return{label:w(C(i,["label","name","title","text","value","choice"]))??s.fallbacks.autoOptionLabel(t+1),description:w(C(i,["description","desc","reason","rationale","deduction"])),preview:w(C(i,["preview","draft","scene","content"])),customPlaceholder:w(C(i,["customPlaceholder","placeholder"]))}}):[]}function Tt(e){return"object"==typeof e&&null!==e&&!("questions"in e)&&("options"in e||"choices"in e||"items"in e||"question"in e||"prompt"in e)}function wt(e,t){if("string"==typeof e)return{title:e,options:G([])};if(!K(e))return{title:s.fallbacks.autoQuestionTitle(t+1),options:G([])};let i=e;return{id:C(i,["id"]),title:w(C(i,["title","question","prompt","query","heading"]))??s.fallbacks.autoQuestionTitle(t+1),description:w(C(i,["description","desc","rationale"])),multiSelect:q(i.multiSelect,!1),minSelect:Ce(i.minSelect),maxSelect:Ie(i.maxSelect),allowEmpty:q(i.allowEmpty,!1),options:G(Ne(i.options??i.choices??i.items)),allowCustom:q(i.allowCustom,!0),customPlaceholder:C(i,["customPlaceholder","placeholder"])}}function vt(e){if(void 0===e)return;let t=F(e),i=Array.isArray(t)?t:Tt(t)?[t]:[];return 0!==i.length?i.slice(0,D).map((e,t)=>wt(e,t)):void 0}function G(e){return e.length>0?e:[{label:s.fallbacks.defaultOptionLabel}]}function yt(e,t,i,s){return"string"==typeof e?e:"string"==typeof t?t:s?i:void 0}function Y(e){let t=e&&"object"==typeof e?e:F(e);if(!K(t))return{formTitle:s.fallbacks.defaultFormTitle};let i=St({...t}),o=w(C(i,["title","taskTitle"])),n=w(C(i,["formTitle"]))??o??s.fallbacks.defaultFormTitle,r=vt(i.questions??i.form??i.survey),l=void 0!==i.options||void 0!==i.choices||void 0!==i.items,a=l?G(Ne(i.options??i.choices??i.items)):void 0,u=yt(i.question,i.prompt,o,l);return{formTitle:n,description:C(i,["description"]),questions:r,question:u,multiSelect:void 0===i.multiSelect?void 0:q(i.multiSelect,!1),minSelect:void 0===i.minSelect?void 0:Ce(i.minSelect),maxSelect:void 0===i.maxSelect?void 0:Ie(i.maxSelect),allowEmpty:void 0===i.allowEmpty?void 0:q(i.allowEmpty,!1),options:a,allowCustom:void 0===i.allowCustom?void 0:q(i.allowCustom,!0),customPlaceholder:C(i,["customPlaceholder"])}}function xt(e){return e instanceof Error?e.message:String(e)}function Me(e={}){return{formTitle:e.formTitle??s.fallbacks.defaultFormTitle,formDescription:e.formDescription,questions:e.questions??[]}}function _(e,t){let i=t instanceof ke?t:new ke("",0,0);return i.setText(e),i}function $e(e={}){let{formTitle:t,formDescription:i,questions:o}=Me(e);return{content:[{type:"text",text:s.markdown.envelopeCancelled}],details:{formTitle:t,formDescription:i,questions:o,answers:[],cancelled:!0}}}function se(e,t,i,o={}){let{formTitle:n,formDescription:r,questions:l}=Me(o);return{content:[{type:"text",text:s.markdown.envelopeError(e,t)}],details:{formTitle:n,formDescription:r,questions:l,answers:[],cancelled:!1,error:i}}}function Ee(e,t={}){let i=xt(e);return se(s.markdown.errorReasons.runtimeError(i),s.markdown.errorGuidances.runtimeError,s.status.errorExecutionFailed(i),t)}function At(e,t){return{content:[{type:"text",text:e.formatAnswers(t)}],details:{formTitle:e.formTitle,formDescription:e.formDescription,questions:e.questions,answers:t,cancelled:!1}}}function Ct(e){if("object"!=typeof e||null===e||Array.isArray(e))throw new Error(`ask_author 结果详情应为普通对象,实际收到 ${JSON.stringify(e)}`);if(!("cancelled"in e&&"answers"in e&&"questions"in e&&"formTitle"in e))throw new Error("ask_author 结果详情缺少 cancelled/answers/questions/formTitle 字段");if("boolean"!=typeof e.cancelled)throw new Error("ask_author 结果详情的 cancelled 字段应为布尔值");if(!Array.isArray(e.answers)||!Array.isArray(e.questions))throw new Error("ask_author 结果详情的 answers/questions 字段应为数组");if("string"!=typeof e.formTitle)throw new Error("ask_author 结果详情的 formTitle 字段应为字符串");return e}function It(e){let t=e.customText?s.card.questionNoteTag(e.customText):"",i=(e,t)=>t?s.card.optionWithNote(e,t):e;return e.multiSelect?((e.selectedOptions??[]).map(e=>i(e.label,e.customText)).join(s.common.optionSeparator)||s.fallbacks.unselectedOption)+t:e.selectedOption?i(e.selectedOption.label,e.selectedOption.customText)+t:e.customText||s.fallbacks.unselectedOption}function Nt(e,t){if(t.cachedArgs===e&&t.cachedCard)return t.cachedCard;let i=new Q(Y(e)),o=i.questions.reduce((e,t)=>e+t.options.length,0),n={title:i.formTitle,countLabel:i.questions.length>1?s.card.callQuestionAndOptionCount(i.questions.length,o):s.card.callOptionCountOnly(o)};return t.cachedArgs=e,t.cachedCard=n,n}function Ot(e){e.registerTool({name:s.tool.name,label:s.tool.label,description:s.tool.description,promptSnippet:s.tool.promptSnippet,promptGuidelines:[...s.tool.promptGuidelines],parameters:oe,executionMode:"sequential",prepareArguments:Y,async execute(e,t,i,o,n){let r,l,a=w(t?.formTitle)??s.fallbacks.defaultFormTitle;if(i?.aborted)return $e({formTitle:a});if("tui"!==n.mode)return se(s.markdown.errorReasons.nonTui,s.markdown.errorGuidances.nonTui,s.status.errorNonTui,{formTitle:a});try{r=new Q(t)}catch(e){return Ee(e,{formTitle:a})}if(0===r.questions.length)return se(s.markdown.errorReasons.noQuestions,s.markdown.errorGuidances.noQuestions,s.status.errorNoQuestions,{questions:[],formTitle:r.formTitle,formDescription:r.formDescription});try{let e=await n.ui.custom((e,t,s,o)=>(l=new W(e,t,r,o),l.bindAbortSignal(i),l));return!e||e.cancelled?$e({questions:e?.questions??r.questions,formTitle:e?.formTitle??r.formTitle,formDescription:e?.formDescription??r.formDescription}):At(r,e.answers)}catch(e){return Ee(e,{questions:r.questions,formTitle:r.formTitle,formDescription:r.formDescription})}finally{l?.dispose()}},renderCall(e,t,i){let o=Nt(e,i.state);return _(`${t.fg("accent",s.tool.label)} ${t.bold(o.title)} ${t.fg("muted",`(${o.countLabel})`)}`,i.lastComponent)},renderResult(e,{expanded:t},i,o){let n=Ct(e.details);if(n.error){let e=`${i.fg("error",i.bold(s.card.resultErrorShort))} ${i.fg("muted",`${s.common.bulletPrefix}${n.error}`)}`;return _(t?`${e}\n${s.card.errorBullet(i.fg("error",n.error))}`:`${e} (${Oe("app.tools.expand",s.card.expandHint)})`,o.lastComponent)}if(n.cancelled)return _(i.fg("warning",s.card.resultCancelled),o.lastComponent);let r=`${i.fg("success",i.bold(s.card.resultCompleted))} ${i.fg("muted",`${s.common.bulletPrefix}${s.card.resultCount(n.answers.length)}`)}`;return _(t?[r,...n.answers.map(e=>s.card.optionBullet(i.bold(e.questionTitle),i.fg("accent",It(e))))].join("\n"):`${r} (${Oe("app.tools.expand",s.card.expandHint)})`,o.lastComponent)}}),e.registerCommand(s.command.name,{description:s.command.description,handler:async(e,t)=>{t.ui.notify(s.command.notify,"info")}})}export{Q as AskAuthorFormModel,oe as AskAuthorParams,g as ICONS,y as LAYOUT_CONFIG,s as TEXTS,Ot as askAuthorExtension,Ot as default,Y as sanitizeAskAuthorArgs};
@@ -0,0 +1,260 @@
1
+ /**
2
+ * @file model.ts
3
+ * @description 问卷数据模型与状态机:题目归一化、作答状态流转、答卷编译与封套输出
4
+ *
5
+ * 核心架构特性:
6
+ * - 索引约定:内部作答状态 0-based,对外答卷编号 1-based
7
+ * - 两层清洗:`sanitize.ts` 边界归一化 + 本层对文本字段幂等兜底(绕过 sanitize 直接构造模型时同样安全)
8
+ * - 封套防止击穿:插值文本经由 `sanitizeEnvelopeText` 归一化,定界符不可被作者文本击穿
9
+ * - 封套语言单一:自动生成的占位题目/标签在封套编译期替换为英文占位(`Question N` / `Option N`),作者界面保留中文占位
10
+ * - 答卷完整性:显式跳过与仅批注提交均产出条目,多选题目恒产出条目,问题不在结果中静默消失
11
+ *
12
+ * 依赖方向:model.ts → format.ts / schema.ts / texts.ts(纯函数工具、Schema 单一类型源与文案字典,无回边);不引用 `Theme` 与 TUI 组件,渲染细节全部上移组件层
13
+ */
14
+ import type { QuestionOption, RawParams } from "./schema.js";
15
+ /** 归一化后的问题对象(字段已兜底,区间已收敛) */
16
+ export interface NormalizedQuestion {
17
+ /** 题目唯一标识符 */
18
+ id: string;
19
+ /** 题目标题 */
20
+ title: string;
21
+ /** 题目背景描述(可选) */
22
+ description?: string;
23
+ /** 是否多选(默认 false,即单选) */
24
+ multiSelect: boolean;
25
+ /** 收敛后的最少勾选数(`0 ~ options.length`) */
26
+ minSelect: number;
27
+ /** 是否显式给出 `minSelect`(显式即强制下限,隐式为缺省下限) */
28
+ minSelectExplicit: boolean;
29
+ /** 收敛后的最多勾选数(`minSelect ~ options.length`) */
30
+ maxSelect: number;
31
+ /** 是否允许 0 勾选提交(默认 false;单选与多选均生效) */
32
+ allowEmpty: boolean;
33
+ /** 归一化后的候选选项列表(至少一项) */
34
+ options: QuestionOption[];
35
+ /** 是否允许自由补充说明(默认 true) */
36
+ allowCustom: boolean;
37
+ /** 补充说明输入框占位提示词 */
38
+ customPlaceholder: string;
39
+ }
40
+ /** 单题作答状态(内部数据结构,选项索引 0-based) */
41
+ export interface QuestionAnswerState {
42
+ /** 单选选中项索引(0-based;未选中时缺省) */
43
+ selectedIndex?: number;
44
+ /** 多选选中项索引集合(0-based) */
45
+ selectedIndices: Set<number>;
46
+ /** 题目级整体补充说明(可选) */
47
+ customText?: string;
48
+ /** 选项批注映射(0-based `optionIndex` → 批注内容) */
49
+ optionNotes: Map<number, string>;
50
+ }
51
+ /** 单题最终答卷条目(索引 1-based 便于人类阅读) */
52
+ export interface AuthorAnswerItem {
53
+ /** 题目唯一标识符 */
54
+ questionId: string;
55
+ /** 题目标题 */
56
+ questionTitle: string;
57
+ /** 是否为多选模式 */
58
+ multiSelect: boolean;
59
+ /** 单选选中项(含专属批注,可选) */
60
+ selectedOption?: QuestionOption & {
61
+ customText?: string;
62
+ };
63
+ /** 单选选中项序号(1-based;未选中时缺省) */
64
+ selectedOptionIndex?: number;
65
+ /** 多选选中项列表(每项含专属批注,可选) */
66
+ selectedOptions?: Array<QuestionOption & {
67
+ customText?: string;
68
+ }>;
69
+ /** 多选选中项序号列表(1-based,升序) */
70
+ selectedIndices?: number[];
71
+ /** 题目整体补充说明(可选) */
72
+ customText?: string;
73
+ /** 选项批注映射(1-based `optionIndex` → 批注内容,可选;含未选中选项上的孤立批注) */
74
+ optionNotes?: Record<number, string>;
75
+ }
76
+ /** 工具执行完成返回的持久化结果结构 */
77
+ export interface AskAuthorResult {
78
+ /** 问卷总标题 */
79
+ formTitle: string;
80
+ /** 问卷全局背景描述(可选) */
81
+ formDescription?: string;
82
+ /** 归一化的问题列表 */
83
+ questions: NormalizedQuestion[];
84
+ /** 收集到的答卷条目列表 */
85
+ answers: AuthorAnswerItem[];
86
+ /** 用户是否主动取消了本次请示 */
87
+ cancelled: boolean;
88
+ /** 执行过程中的错误信息(可选) */
89
+ error?: string;
90
+ }
91
+ /** 批注编辑目标:`question` 题目整体 / `option` 指定选项专属(`optionIndex` 为 0-based,越界不写入状态) */
92
+ export type EditTarget = {
93
+ type: "question";
94
+ } | {
95
+ type: "option";
96
+ optionIndex: number;
97
+ };
98
+ /** 题目视图左侧菜单项的统一抽象模型 */
99
+ export interface MenuItem {
100
+ /** 菜单项类型:预设选项 / 题目补充入口 / 确认按钮 */
101
+ type: "option" | "custom_note" | "confirm_btn";
102
+ /** 预设选项索引(0-based,仅 option 类型有效) */
103
+ optionIndex?: number;
104
+ /** 菜单项显示文本 */
105
+ label: string;
106
+ /** 菜单项背景描述推演 */
107
+ description?: string;
108
+ /** 菜单项正文草稿/分镜头预览 */
109
+ preview?: string;
110
+ }
111
+ /** 勾选 toggle 操作语义(三个操作语义类型均不导出,由组件层消费并映射为状态栏提示) */
112
+ type ToggleResult = "selected" | "deselected" | "limit";
113
+ /** 全选/清空操作语义 */
114
+ type SelectAllResult = "cleared" | "filled";
115
+ /** 批注保存操作语义 */
116
+ type NoteSaveResult = "saved" | "cleared" | "savedUnselected" | "savedAtLimit";
117
+ /**
118
+ * 判定是否存在非空题目级批注
119
+ * @param st - 题目作答状态
120
+ * @returns `customText` 去空白后非空时为 true
121
+ */
122
+ export declare function hasQuestionNote(st: QuestionAnswerState): boolean;
123
+ /**
124
+ * 问卷数据模型:题目归一化、作答状态流转与答卷编译;全部状态突变经本类 mutation 方法完成并自动推进 `revision`,组件层只读
125
+ * 只读访问面:`revision` / `questionCount` / `getQuestion` / `getState` / `getSelectedIndices`(0-based 升序,排序不变量唯一实现)/ `getMenuItems` / `isQuestionAnswered` / `areAllQuestionsAnswered` / `compileAnswers` / `formatAnswers`
126
+ */
127
+ export declare class AskAuthorFormModel {
128
+ /** 问卷总标题 */
129
+ readonly formTitle: string;
130
+ /** 问卷全局背景阐述(可选) */
131
+ readonly formDescription?: string;
132
+ /** 归一化后的题目列表 */
133
+ readonly questions: NormalizedQuestion[];
134
+ /** 作答状态版本号:单调递增,作为组件层派生缓存的失效键 */
135
+ private revisionValue;
136
+ /** 各题作答状态映射(题目索引 0-based) */
137
+ private readonly states;
138
+ /**
139
+ * 构造问卷数据模型实例(单题平铺与批量问卷两模式归一化)
140
+ * @param rawParams - 经 prepareArguments 规整的入参;`questions` 缺省或为空数组时回退单题平铺模式,`options` 亦为空则题目列表为空
141
+ */
142
+ constructor(rawParams: RawParams);
143
+ /** 当前作答状态版本号(组件层派生缓存失效键) */
144
+ get revision(): number;
145
+ /** 问卷题目总数 */
146
+ get questionCount(): number;
147
+ /**
148
+ * 获取指定索引的归一化题目
149
+ * @param index - 题目顺序索引(0-based)
150
+ * @returns 归一化题目对象;索引越界时为 undefined
151
+ */
152
+ getQuestion(index: number): NormalizedQuestion | undefined;
153
+ /**
154
+ * 获取指定题目的作答状态(懒加载单例初始化)
155
+ * 不校验题目边界:越界索引同样创建并缓存一个空状态对象(调用方须先经 {@link getQuestion} 校验)
156
+ * @param index - 题目顺序索引(0-based)
157
+ * @returns 该题目的状态引用,可写入,同一索引恒返回同一对象;首次创建仅初始化内部缓存,不改变任何可观察作答语义、不推进 `revision`
158
+ */
159
+ getState(index: number): QuestionAnswerState;
160
+ /**
161
+ * 获取已勾选选项索引(0-based 升序,未作答为空数组;排序不变量唯一实现在此)
162
+ * @param qIndex - 题目顺序索引(0-based)
163
+ * @returns 全新数组(调用方可安全持有而不影响内部状态);题目越界或状态未创建时为空数组
164
+ */
165
+ getSelectedIndices(qIndex: number): number[];
166
+ /** 推进作答状态版本号(全部 mutation 方法的统一出口) */
167
+ private bumpRevision;
168
+ /**
169
+ * 派生指定题目的左侧菜单项列表(预设选项 + 自由补充入口 + 确认推进按钮)
170
+ * 菜单项标签与确认按钮明细随当前作答状态派生:多选展示已选数/下限,单选展示已选项标签、自定义方案名或「未选」
171
+ * @param qIndex - 题目顺序索引(0-based)
172
+ * @returns 顺序固定的菜单项数组(选项按声明顺序在前、补充入口居中、确认按钮殿后);题目越界为空数组;`allowCustom` 为 false 时省略补充入口
173
+ */
174
+ getMenuItems(qIndex: number): MenuItem[];
175
+ /**
176
+ * 判定指定题目是否满足作答下限提交要求
177
+ * 单选:已选一项、`allowEmpty`,或 `allowCustom` 开启且存在任意非空批注;多选:勾选数达 `minSelect`、`allowEmpty` 下 0 选,
178
+ * 或缺省下限未勾选(`minSelect` 未显式给出时收敛为 `min(1, 选项数)`,故此时 `count < minSelect` 等价于 `count === 0`)且 `allowCustom` 开启并存在任意非空批注
179
+ * @param qIndex - 题目顺序索引(0-based)
180
+ * @returns 题目越界恒为 false;作答状态尚未懒加载时按只读空状态({@link EMPTY_STATE})判定,不写入任何状态
181
+ */
182
+ isQuestionAnswered(qIndex: number): boolean;
183
+ /**
184
+ * 判定问卷中的全部题目是否均已作答完毕
185
+ * @returns 每题均满足 {@link isQuestionAnswered} 时为 true;题目列表为空时恒为 true(`every` 的空集语义)
186
+ */
187
+ areAllQuestionsAnswered(): boolean;
188
+ /**
189
+ * 切换指定选项勾选状态(空格键主路径;单选再次点击取消,多选达上限拒绝新增)
190
+ * @param qIndex - 题目顺序索引(0-based)
191
+ * @param optionIndex - 目标选项索引(0-based)
192
+ * @returns `selected` / `deselected` / `limit`(题目越界、选项不存在、多选已达上限且未勾选时返回 `limit` 且不推进 `revision`)
193
+ */
194
+ toggleOption(qIndex: number, optionIndex: number): ToggleResult;
195
+ /**
196
+ * 确认路径下的选项写入(Enter/Ctrl+S 推进确认;单选直接更改选择,多选未达上限时补充勾选,已满未勾选静默保持原状,仅真实变更推进 `revision`)
197
+ * @param qIndex - 题目顺序索引(0-based)
198
+ * @param optionIndex - 目标选项索引(0-based)
199
+ */
200
+ commitOption(qIndex: number, optionIndex: number): void;
201
+ /**
202
+ * 多选全选或一键清空(`a` 键):已满或已全选时清空,否则按选项顺序填充至上限(保留既有勾选,恒保持 `size <= maxSelect`)
203
+ * @param qIndex - 题目顺序索引(0-based)
204
+ * @returns `cleared` / `filled`(题目越界或非多选同样返回 `cleared` 且不推进 `revision`)
205
+ */
206
+ selectAllOrClear(qIndex: number): SelectAllResult;
207
+ /**
208
+ * 保存或清空选项专属批注(空文本视为清除;单选无选中项时随批注勾选,已有其他选中项仅附加批注不更改选择;多选已满且未勾选仅保存批注)
209
+ * @param qIndex - 题目顺序索引(0-based)
210
+ * @param optionIndex - 目标选项索引(0-based)
211
+ * @param text - 批注内容(空视为清除)
212
+ * @returns `saved` / `cleared` / `savedUnselected` / `savedAtLimit`(守卫路径返回 `cleared` 且不推进 `revision`;其余路径即使未实际改变批注集合——如原本无批注时清除——同样推进 `revision`)
213
+ */
214
+ setOptionNote(qIndex: number, optionIndex: number, text: string): NoteSaveResult;
215
+ /**
216
+ * 保存或清空题目整体补充说明(空文本视为清空)
217
+ * 本方法不校验题目边界:越界索引同样经 {@link getState} 创建并写入状态对象
218
+ * @param qIndex - 题目顺序索引(0-based)
219
+ * @param text - 批注内容(经 `cleanOptional` 归一,空串或全空白视为清空)
220
+ * @returns `saved`(存入非空文本)/ `cleared`(清空);无论是否发生实际变更均推进 `revision`
221
+ */
222
+ setQuestionNote(qIndex: number, text: string): "saved" | "cleared";
223
+ /**
224
+ * 清空指定选项的专属批注(`x` 键路径;不存在时状态不变)
225
+ * @param qIndex - 题目顺序索引(0-based)
226
+ * @param optionIndex - 目标选项索引(0-based)
227
+ * @returns 实际删除到条目时为 true(并推进 `revision`);题目越界、状态未创建或该选项无批注时为 false(状态与 `revision` 均不变)
228
+ */
229
+ clearOptionNote(qIndex: number, optionIndex: number): boolean;
230
+ /**
231
+ * 清空题目整体补充说明(`x` 键路径;不存在时状态不变)
232
+ * @param qIndex - 题目顺序索引(0-based)
233
+ * @returns 题目级批注存在且去空白后非空并被清空时为 true(并推进 `revision`);题目越界、状态未创建或批注为空时为 false(状态与 `revision` 均不变)
234
+ */
235
+ clearQuestionNote(qIndex: number): boolean;
236
+ /**
237
+ * 收集全部非空选项批注(含未选中选项上的孤立批注),键转换为 1-based;与总览页展示保持一致
238
+ * @param q - 归一化题目对象
239
+ * @param st - 题目作答状态(可选)
240
+ * @returns 1-based 选项索引 → 批注内容的映射表
241
+ */
242
+ private collectOptionNotes;
243
+ /**
244
+ * 编译全部作答状态为结果答案条目列表
245
+ * 产出规则:多选题目恒产出条目(未作答时 `selectedOptions` / `selectedIndices` 为空);单选题目仅在已作答时才产出条目(已选一项、`allowEmpty` 主动跳过或仅批注提交)
246
+ * 两种模式均杜绝问题在结果封套中静默消失;0-based 内部索引统一转换为 1-based
247
+ * @returns 与题目顺序一致的答卷条目列表(未产出的单选题目不占位);题干无任何作答且非多选时为空数组
248
+ */
249
+ compileAnswers(): AuthorAnswerItem[];
250
+ /**
251
+ * 编译答案为确定性标准 Markdown 封套文本(固定前缀与收尾指令最大化 Prompt Cache 命中率并驱动下游工作流)
252
+ * 全部插值文本经 {@link sanitizeEnvelopeText} 消毒(半角 `[` / `]` / `;` 与换行一并归一,模板注入的令牌边界不可被插值文本提前闭合或伪造)
253
+ * 确定性来源:片段顺序固定为「已选项(`getSelectedIndices` 升序)→ 未选中选项的孤立批注(`Record` 整数键升序)→ 题目级批注」,不依赖哈希序与时间
254
+ * 语言归属:自动生成的占位题目/标签经由 {@link envelopeQuestionTitle} / {@link envelopeOptionLabel} 替换为英文占位,其余插值文本原样保留(作者文本不翻译)
255
+ * @param answers - 答卷列表(通常来自 {@link compileAnswers})
256
+ * @returns `envelopePrefix` + 逐题一行(1-based 序号 + 标题 + `; ` 连接的片段)+ `envelopeSuffix`;`answers` 为空时退化为空答卷封套
257
+ */
258
+ formatAnswers(answers: AuthorAnswerItem[]): string;
259
+ }
260
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @file novel-markdown.ts
3
+ * @description 小说语法高亮引擎:把语义定界符(`「」` / `()`)标注为携带哨兵的原生行内代码,折行与配色交还 Pi 原生 Markdown 引擎
4
+ *
5
+ * 核心架构特性:
6
+ * - 声明式语法模板表(`NOVEL_TEMPLATE_*` 派生索引)驱动「定界符 → 语义哨兵 → 行内代码载体 → 主题样式」映射
7
+ * - 对话台词「...」与旁白心理(...)由 `MarkdownOptions.transform` 在 marked 解析前完成标注
8
+ * - `theme.code` 回调兼作哨兵分类器,未命中哨兵即完整交还原生行内代码样式
9
+ * - 折行、嵌套与配色全部由原生 Markdown 引擎回填,插件不触碰任何 ANSI 转义流
10
+ * - 零文本改动不变量:输出剥离哨兵与新增反引号后与输入完全等价
11
+ *
12
+ * 索引约定:全文「扁平索引 / 行内列索引」一律指 UTF-16 码元下标,含义与 `String.prototype.length` / `charAt` / `slice` 一致
13
+ *
14
+ * 依赖方向:novel-markdown.ts → format.ts / texts.ts / theme.ts(另行引入 model.ts 的 `MenuItem` 类型,无回环依赖);外部依赖 `@earendil-works/pi-tui` 的 `Markdown` / `MarkdownTheme` 与 `@earendil-works/pi-coding-agent` 的 `getMarkdownTheme`
15
+ * 宿主主题耦合:围栏代码块由 `getMarkdownTheme()` 的 `highlightCode` 着色,提取颜色读取宿主全局活动主题(须宿主已 `initTheme`,本模块不自行持有主题);行内代码仅在未命中哨兵时走同一全局 `code` 提取颜色
16
+ */
17
+ import type { MenuItem } from "./model.js";
18
+ import type { Theme } from "./theme.js";
19
+ /**
20
+ * 构建选项详情/正文草稿预览框的完整未切片内容行列表(原生 Markdown 渲染 + 小说语法语义标注)
21
+ * @param item - 当前选中的菜单项
22
+ * @param customText - 当前填写的题目自由补充说明
23
+ * @param optionNote - 当前选项填写的专属补充说明
24
+ * @param targetWidth - 预览框渲染总列宽 (cols),含 `Markdown` 组件自身左右各 1 列 `paddingX`(正文可用宽度为其减 2)
25
+ * @param theme - TUI 主题实例
26
+ * @returns 未切片的完整内容行(每行已补足至 `targetWidth`),供组件层做视口切片;`item` 缺省时返回空数组
27
+ */
28
+ export declare function buildFullPreviewContentLines(item: MenuItem | undefined, customText: string | undefined, optionNote: string | undefined, targetWidth: number, theme: Theme): string[];
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @file sanitize.ts
3
+ * @description 入参防御性清洗:`prepareArguments` 钩子与不可信工具边界的容错体系
4
+ *
5
+ * 核心架构特性:
6
+ * - 容错范围:JSON 串包裹 / `raw` 包装键 / 字段别名 / 纯字符串选项 / 空选项兜底
7
+ * - 白名单收敛:产物仅含 `RawParams` 声明字段(丢弃未知键),保障宿主 Schema 校验通过
8
+ * - 防御纵深:本层做类型收敛 / 别名映射 / 结构规整 / 区间兜底,模型层再幂等兜底一次
9
+ *
10
+ * 依赖方向:sanitize.ts → format.ts / texts.ts / schema.ts(类型);仅由 index.ts 装配
11
+ */
12
+ import type { RawParams } from "./schema.js";
13
+ /**
14
+ * 入参防御性清洗(`prepareArguments` 钩子);容错面见文件头,各字段先收敛为本地强类型 `const` 再组装为显式白名单产物
15
+ * @param args - 工具调用原始入参(不可信边界)
16
+ * @returns 仅含 Schema 声明字段的 `RawParams` 白名单对象;输入完全不可辨识时仅返回兜底 `formTitle`
17
+ */
18
+ export declare function sanitizeAskAuthorArgs(args: unknown): RawParams;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @file schema.ts
3
+ * @description `ask_author` 工具参数 Schema 与类型的单一源头(Single Source of Truth)
4
+ *
5
+ * 核心架构特性:
6
+ * - 字段名称、可选性与区间约束(`minItems` / `maxItems` / `minimum`)仅在 TypeBox 定义一次,运行时类型经 `Static<typeof>` 派生
7
+ * - 默认值不写入 Schema:题目内字段由 `sanitize.ts` 规整时填入(`multiSelect`→false、`allowCustom`→true),模型层再按同一语义兜底
8
+ * - LLM 端字段描述保持单句浓缩英文(取自 `TEXTS.schema`)
9
+ *
10
+ * 依赖方向:schema.ts → typebox / texts.ts(上限常量与字段描述);被 sanitize / model / index 消费
11
+ */
12
+ import { Type, type Static } from "typebox";
13
+ /** 单个候选选项的参数 Schema */
14
+ declare const OptionSchema: Type.TObject<{
15
+ /** 选项简短标题/行动标签(必须填写;缺失时经 `sanitize.ts` → `model.ts` 两级兜底为自动占位标签) */
16
+ label: Type.TString;
17
+ /** 情节推演后果或设定阐述(可选) */
18
+ description: Type.TOptional<Type.TString>;
19
+ /** Markdown 正文草稿或分镜头试撰写(可选) */
20
+ preview: Type.TOptional<Type.TString>;
21
+ /** 该选项专属补充说明的占位引导文案(可选) */
22
+ customPlaceholder: Type.TOptional<Type.TString>;
23
+ }>;
24
+ /** 单个问题的参数 Schema(批量问卷与平铺模式共用) */
25
+ declare const QuestionSchema: Type.TObject<{
26
+ /** 题目唯一标识符(可选;缺省由模型层按顺序生成,跨题重复由模型层去重) */
27
+ id: Type.TOptional<Type.TString>;
28
+ /** 题目标题(必须填写;缺失时两级兜底为自动占位标题) */
29
+ title: Type.TString;
30
+ /** 题目背景与语境(可选) */
31
+ description: Type.TOptional<Type.TString>;
32
+ /** 是否允许多选(可选;默认 false,即单选) */
33
+ multiSelect: Type.TOptional<Type.TBoolean>;
34
+ /** 最小勾选数下限(可选;仅多选有效,默认 1) */
35
+ minSelect: Type.TOptional<Type.TInteger>;
36
+ /** 最大勾选数上限(可选;仅多选有效,默认全部选项数) */
37
+ maxSelect: Type.TOptional<Type.TInteger>;
38
+ /** 是否允许零勾选直接提交(可选;默认 false) */
39
+ allowEmpty: Type.TOptional<Type.TBoolean>;
40
+ /** 候选选项列表(必须填写;1 项起、上限 `MAX_OPTIONS`,建议 2~10 项) */
41
+ options: Type.TArray<Type.TObject<{
42
+ /** 选项简短标题/行动标签(必须填写;缺失时经 `sanitize.ts` → `model.ts` 两级兜底为自动占位标签) */
43
+ label: Type.TString;
44
+ /** 情节推演后果或设定阐述(可选) */
45
+ description: Type.TOptional<Type.TString>;
46
+ /** Markdown 正文草稿或分镜头试撰写(可选) */
47
+ preview: Type.TOptional<Type.TString>;
48
+ /** 该选项专属补充说明的占位引导文案(可选) */
49
+ customPlaceholder: Type.TOptional<Type.TString>;
50
+ }>>;
51
+ /** 是否允许自由补充说明(可选;默认 true) */
52
+ allowCustom: Type.TOptional<Type.TBoolean>;
53
+ /** 题目整体补充说明的占位引导词(可选) */
54
+ customPlaceholder: Type.TOptional<Type.TString>;
55
+ }>;
56
+ /** `ask_author` 工具完整参数 Schema(必须填写 `formTitle`;批量问卷传递 `questions`,单题平铺传递顶层 `question` + `options`) */
57
+ export declare const AskAuthorParams: Type.TObject<{
58
+ /** 问卷总任务标题(必须填写;简明概括请示主题) */
59
+ formTitle: Type.TString;
60
+ /** 问卷全局背景或前述情境(可选;平铺单题模式下同时充当该题描述) */
61
+ description: Type.TOptional<Type.TString>;
62
+ /** 关联多题批量问卷列表(可选;批量模式使用,上限 `MAX_QUESTIONS` 题) */
63
+ questions: Type.TOptional<Type.TArray<Type.TObject<{
64
+ /** 题目唯一标识符(可选;缺省由模型层按顺序生成,跨题重复由模型层去重) */
65
+ id: Type.TOptional<Type.TString>;
66
+ /** 题目标题(必须填写;缺失时两级兜底为自动占位标题) */
67
+ title: Type.TString;
68
+ /** 题目背景与语境(可选) */
69
+ description: Type.TOptional<Type.TString>;
70
+ /** 是否允许多选(可选;默认 false,即单选) */
71
+ multiSelect: Type.TOptional<Type.TBoolean>;
72
+ /** 最小勾选数下限(可选;仅多选有效,默认 1) */
73
+ minSelect: Type.TOptional<Type.TInteger>;
74
+ /** 最大勾选数上限(可选;仅多选有效,默认全部选项数) */
75
+ maxSelect: Type.TOptional<Type.TInteger>;
76
+ /** 是否允许零勾选直接提交(可选;默认 false) */
77
+ allowEmpty: Type.TOptional<Type.TBoolean>;
78
+ /** 候选选项列表(必须填写;1 项起、上限 `MAX_OPTIONS`,建议 2~10 项) */
79
+ options: Type.TArray<Type.TObject<{
80
+ /** 选项简短标题/行动标签(必须填写;缺失时经 `sanitize.ts` → `model.ts` 两级兜底为自动占位标签) */
81
+ label: Type.TString;
82
+ /** 情节推演后果或设定阐述(可选) */
83
+ description: Type.TOptional<Type.TString>;
84
+ /** Markdown 正文草稿或分镜头试撰写(可选) */
85
+ preview: Type.TOptional<Type.TString>;
86
+ /** 该选项专属补充说明的占位引导文案(可选) */
87
+ customPlaceholder: Type.TOptional<Type.TString>;
88
+ }>>;
89
+ /** 是否允许自由补充说明(可选;默认 true) */
90
+ allowCustom: Type.TOptional<Type.TBoolean>;
91
+ /** 题目整体补充说明的占位引导词(可选) */
92
+ customPlaceholder: Type.TOptional<Type.TString>;
93
+ }>>>;
94
+ /** 单题平铺模式题目标题(可选;仅在携带非空 `options` 时参与选题,缺省回退 `TEXTS.fallbacks.defaultQuestionTitle`) */
95
+ question: Type.TOptional<Type.TString>;
96
+ /** 单题平铺模式是否允许多选(可选;默认 false) */
97
+ multiSelect: Type.TOptional<Type.TBoolean>;
98
+ /** 单题平铺模式最少勾选数下限(可选;默认 1) */
99
+ minSelect: Type.TOptional<Type.TInteger>;
100
+ /** 单题平铺模式最多勾选数上限(可选;默认全部选项数) */
101
+ maxSelect: Type.TOptional<Type.TInteger>;
102
+ /** 单题平铺模式是否允许零勾选提交(可选;默认 false) */
103
+ allowEmpty: Type.TOptional<Type.TBoolean>;
104
+ /** 单题平铺模式候选选项列表(可选;1~`MAX_OPTIONS` 项) */
105
+ options: Type.TOptional<Type.TArray<Type.TObject<{
106
+ /** 选项简短标题/行动标签(必须填写;缺失时经 `sanitize.ts` → `model.ts` 两级兜底为自动占位标签) */
107
+ label: Type.TString;
108
+ /** 情节推演后果或设定阐述(可选) */
109
+ description: Type.TOptional<Type.TString>;
110
+ /** Markdown 正文草稿或分镜头试撰写(可选) */
111
+ preview: Type.TOptional<Type.TString>;
112
+ /** 该选项专属补充说明的占位引导文案(可选) */
113
+ customPlaceholder: Type.TOptional<Type.TString>;
114
+ }>>>;
115
+ /** 单题平铺模式是否允许自由补充说明(可选;默认 true) */
116
+ allowCustom: Type.TOptional<Type.TBoolean>;
117
+ /** 单题平铺模式补充说明的占位引导词(可选) */
118
+ customPlaceholder: Type.TOptional<Type.TString>;
119
+ }>;
120
+ /**
121
+ * `ask_author` 工具的运行时参数类型(`prepareArguments` 清洗后的白名单形状)
122
+ * 由 {@link AskAuthorParams} 经 `Static<typeof>` 单一源头派生,可选性与区间约束以 Schema 为准
123
+ */
124
+ export type RawParams = Static<typeof AskAuthorParams>;
125
+ /** 单个候选选项的运行时类型(由 `OptionSchema` 经 `Static<typeof>` 派生) */
126
+ export type QuestionOption = Static<typeof OptionSchema>;
127
+ /** 单个问题的运行时类型(由 `QuestionSchema` 经 `Static<typeof>` 派生,批量与平铺共用) */
128
+ export type QuestionInput = Static<typeof QuestionSchema>;
129
+ export {};