@agimon-ai/doompi-user-feedback 0.0.1-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  4. package/dist/adapters/config/configAdapter.cjs +2 -0
  5. package/dist/adapters/config/configAdapter.cjs.map +1 -0
  6. package/dist/adapters/config/configAdapter.mjs +2 -0
  7. package/dist/adapters/config/configAdapter.mjs.map +1 -0
  8. package/dist/adapters/doom/voiceQuestionHandoff.cjs +5 -0
  9. package/dist/adapters/doom/voiceQuestionHandoff.cjs.map +1 -0
  10. package/dist/adapters/doom/voiceQuestionHandoff.mjs +5 -0
  11. package/dist/adapters/doom/voiceQuestionHandoff.mjs.map +1 -0
  12. package/dist/adapters/pi/askUserQuestionAdapter.cjs +15 -0
  13. package/dist/adapters/pi/askUserQuestionAdapter.cjs.map +1 -0
  14. package/dist/adapters/pi/askUserQuestionAdapter.mjs +15 -0
  15. package/dist/adapters/pi/askUserQuestionAdapter.mjs.map +1 -0
  16. package/dist/adapters/pi/reconcileAdapter.cjs +2 -0
  17. package/dist/adapters/pi/reconcileAdapter.cjs.map +1 -0
  18. package/dist/adapters/pi/reconcileAdapter.mjs +2 -0
  19. package/dist/adapters/pi/reconcileAdapter.mjs.map +1 -0
  20. package/dist/adapters/pi/rpcQuestionnaireAdapter.cjs +2 -0
  21. package/dist/adapters/pi/rpcQuestionnaireAdapter.cjs.map +1 -0
  22. package/dist/adapters/pi/rpcQuestionnaireAdapter.mjs +2 -0
  23. package/dist/adapters/pi/rpcQuestionnaireAdapter.mjs.map +1 -0
  24. package/dist/events.cjs +1 -0
  25. package/dist/events.d.cts +2 -0
  26. package/dist/events.d.mts +2 -0
  27. package/dist/events.mjs +1 -0
  28. package/dist/extensions/pi.cjs +2 -0
  29. package/dist/extensions/pi.cjs.map +1 -0
  30. package/dist/extensions/pi.d.cts +6 -0
  31. package/dist/extensions/pi.d.cts.map +1 -0
  32. package/dist/extensions/pi.d.mts +6 -0
  33. package/dist/extensions/pi.d.mts.map +1 -0
  34. package/dist/extensions/pi.mjs +2 -0
  35. package/dist/extensions/pi.mjs.map +1 -0
  36. package/dist/index.cjs +1 -0
  37. package/dist/index.d.cts +4 -0
  38. package/dist/index.d.mts +4 -0
  39. package/dist/index.mjs +1 -0
  40. package/dist/schemas/events.cjs +1 -0
  41. package/dist/schemas/events.d.cts +22 -0
  42. package/dist/schemas/events.d.cts.map +1 -0
  43. package/dist/schemas/events.d.mts +22 -0
  44. package/dist/schemas/events.d.mts.map +1 -0
  45. package/dist/schemas/events.mjs +1 -0
  46. package/dist/schemas/questionnaire.cjs +2 -0
  47. package/dist/schemas/questionnaire.cjs.map +1 -0
  48. package/dist/schemas/questionnaire.mjs +2 -0
  49. package/dist/schemas/questionnaire.mjs.map +1 -0
  50. package/dist/services/questionnaireCoordinator.cjs +2 -0
  51. package/dist/services/questionnaireCoordinator.cjs.map +1 -0
  52. package/dist/services/questionnaireCoordinator.mjs +2 -0
  53. package/dist/services/questionnaireCoordinator.mjs.map +1 -0
  54. package/dist/services/responseService.cjs +2 -0
  55. package/dist/services/responseService.cjs.map +1 -0
  56. package/dist/services/responseService.mjs +2 -0
  57. package/dist/services/responseService.mjs.map +1 -0
  58. package/dist/services/validationService.cjs +2 -0
  59. package/dist/services/validationService.cjs.map +1 -0
  60. package/dist/services/validationService.mjs +2 -0
  61. package/dist/services/validationService.mjs.map +1 -0
  62. package/dist/services/voiceQuestionNarration.cjs +2 -0
  63. package/dist/services/voiceQuestionNarration.cjs.map +1 -0
  64. package/dist/services/voiceQuestionNarration.mjs +2 -0
  65. package/dist/services/voiceQuestionNarration.mjs.map +1 -0
  66. package/dist/tui/externalEditor.cjs +2 -0
  67. package/dist/tui/externalEditor.cjs.map +1 -0
  68. package/dist/tui/externalEditor.mjs +2 -0
  69. package/dist/tui/externalEditor.mjs.map +1 -0
  70. package/dist/tui/questionnaireComponent.cjs +2 -0
  71. package/dist/tui/questionnaireComponent.cjs.map +1 -0
  72. package/dist/tui/questionnaireComponent.mjs +2 -0
  73. package/dist/tui/questionnaireComponent.mjs.map +1 -0
  74. package/dist/tui/runQuestionnaire.cjs +2 -0
  75. package/dist/tui/runQuestionnaire.cjs.map +1 -0
  76. package/dist/tui/runQuestionnaire.mjs +2 -0
  77. package/dist/tui/runQuestionnaire.mjs.map +1 -0
  78. package/package.json +79 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vuong Ngo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,18 @@
1
+ # @agimon-ai/doompi-user-feedback
2
+
3
+ An independent DoomPi extension for structured `ask_user_question` interactions.
4
+
5
+ The implementation is written from scratch for Pi 0.84.1. It retains the established `rpiv:ask-user:prompt` and `rpiv:ask-user:blocked` channel names so existing DoomPi consumers can migrate without changing their listeners. It has no dependency on `@juicesharp/rpiv-ask-user-question`, `@juicesharp/rpiv-i18n`, or other Juicesharp packages.
6
+
7
+ When DoomPi autonomous voice mode is active, the extension narrates the questions without opening a modal questionnaire. The spoken response returns through Doom Voice as the next user message.
8
+
9
+ ## Development
10
+
11
+ ```bash
12
+ pnpm build
13
+ pnpm typecheck
14
+ pnpm test
15
+ pnpm lint
16
+ ```
17
+
18
+ The Pi extension entrypoint is `@agimon-ai/doompi-user-feedback/extensions/pi`.
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:os");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);const i=`ctrl+]`;function a(e){let t=e.XDG_CONFIG_HOME?.trim()||r.default.join(n.default.homedir(),`.config`);return r.default.join(t,`rpiv-ask-user-question`,`config.json`)}function o(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function s(e=process.env){let n=a(e);if(!t.default.existsSync(n))return{};try{let e=JSON.parse(t.default.readFileSync(n,`utf8`));if(!o(e))return{};let r=o(e.guidance)?e.guidance:void 0,i=r?{...typeof r.promptSnippet==`string`?{promptSnippet:r.promptSnippet}:{},...Array.isArray(r.promptGuidelines)&&r.promptGuidelines.every(e=>typeof e==`string`)?{promptGuidelines:r.promptGuidelines}:{}}:void 0;return{...typeof e.collapseKey==`string`?{collapseKey:e.collapseKey}:{},...i?{guidance:i}:{}}}catch{return{}}}function c(e){let t=e.collapseKey?.trim().toLowerCase();return t?t===`off`?`off`:/^[a-z0-9_+\-\]{}]+$/u.test(t)?t:i:i}exports.DEFAULT_COLLAPSE_KEY=i,exports.loadUserFeedbackConfig=s,exports.resolveCollapseKey=c;
2
+ //# sourceMappingURL=configAdapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configAdapter.cjs","names":["path","os","fs"],"sources":["../../../src/adapters/config/configAdapter.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_COLLAPSE_KEY = 'ctrl+]';\nexport const COLLAPSE_KEY_OFF = 'off';\n\nexport interface GuidanceConfig {\n promptSnippet?: string;\n promptGuidelines?: string[];\n}\n\nexport interface UserFeedbackConfig {\n collapseKey?: string;\n guidance?: GuidanceConfig;\n}\n\nfunction legacyConfigPath(environment: NodeJS.ProcessEnv): string {\n const configRoot = environment.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config');\n return path.join(configRoot, 'rpiv-ask-user-question', 'config.json');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function loadUserFeedbackConfig(environment: NodeJS.ProcessEnv = process.env): UserFeedbackConfig {\n const filePath = legacyConfigPath(environment);\n if (!fs.existsSync(filePath)) return {};\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n if (!isRecord(parsed)) return {};\n const rawGuidance = isRecord(parsed.guidance) ? parsed.guidance : undefined;\n const guidance = rawGuidance\n ? {\n ...(typeof rawGuidance.promptSnippet === 'string' ? { promptSnippet: rawGuidance.promptSnippet } : {}),\n ...(Array.isArray(rawGuidance.promptGuidelines) &&\n rawGuidance.promptGuidelines.every((item) => typeof item === 'string')\n ? { promptGuidelines: rawGuidance.promptGuidelines as string[] }\n : {}),\n }\n : undefined;\n return {\n ...(typeof parsed.collapseKey === 'string' ? { collapseKey: parsed.collapseKey } : {}),\n ...(guidance ? { guidance } : {}),\n };\n } catch {\n return {};\n }\n}\n\nexport function resolveCollapseKey(config: UserFeedbackConfig): string {\n const value = config.collapseKey?.trim().toLowerCase();\n if (!value) return DEFAULT_COLLAPSE_KEY;\n if (value === COLLAPSE_KEY_OFF) return COLLAPSE_KEY_OFF;\n return /^[a-z0-9_+\\-\\]{}]+$/u.test(value) ? value : DEFAULT_COLLAPSE_KEY;\n}\n"],"mappings":"wLAIA,MAAa,EAAuB,SAapC,SAAS,EAAiB,EAAwC,CAChE,IAAM,EAAa,EAAY,iBAAiB,KAAK,GAAKA,EAAAA,QAAK,KAAKC,EAAAA,QAAG,QAAQ,EAAG,SAAS,EAC3F,OAAOD,EAAAA,QAAK,KAAK,EAAY,yBAA0B,aAAa,CACtE,CAEA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAgB,EAAuB,EAAiC,QAAQ,IAAyB,CACvG,IAAM,EAAW,EAAiB,CAAW,EAC7C,GAAI,CAACE,EAAAA,QAAG,WAAW,CAAQ,EAAG,MAAO,CAAC,EACtC,GAAI,CACF,IAAM,EAAkB,KAAK,MAAMA,EAAAA,QAAG,aAAa,EAAU,MAAM,CAAC,EACpE,GAAI,CAAC,EAAS,CAAM,EAAG,MAAO,CAAC,EAC/B,IAAM,EAAc,EAAS,EAAO,QAAQ,EAAI,EAAO,SAAW,IAAA,GAC5D,EAAW,EACb,CACE,GAAI,OAAO,EAAY,eAAkB,SAAW,CAAE,cAAe,EAAY,aAAc,EAAI,CAAC,EACpG,GAAI,MAAM,QAAQ,EAAY,gBAAgB,GAC9C,EAAY,iBAAiB,MAAO,GAAS,OAAO,GAAS,QAAQ,EACjE,CAAE,iBAAkB,EAAY,gBAA6B,EAC7D,CAAC,CACP,EACA,IAAA,GACJ,MAAO,CACL,GAAI,OAAO,EAAO,aAAgB,SAAW,CAAE,YAAa,EAAO,WAAY,EAAI,CAAC,EACpF,GAAI,EAAW,CAAE,UAAS,EAAI,CAAC,CACjC,CACF,MAAQ,CACN,MAAO,CAAC,CACV,CACF,CAEA,SAAgB,EAAmB,EAAoC,CACrE,IAAM,EAAQ,EAAO,aAAa,KAAK,CAAC,CAAC,YAAY,EAGrD,OAFK,EACD,IAAA,MAA4B,MACzB,uBAAuB,KAAK,CAAK,EAAI,EAAQ,EAFjC,CAGrB"}
@@ -0,0 +1,2 @@
1
+ import e from"node:fs";import t from"node:os";import n from"node:path";const r=`ctrl+]`;function i(e){let r=e.XDG_CONFIG_HOME?.trim()||n.join(t.homedir(),`.config`);return n.join(r,`rpiv-ask-user-question`,`config.json`)}function a(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function o(t=process.env){let n=i(t);if(!e.existsSync(n))return{};try{let t=JSON.parse(e.readFileSync(n,`utf8`));if(!a(t))return{};let r=a(t.guidance)?t.guidance:void 0,i=r?{...typeof r.promptSnippet==`string`?{promptSnippet:r.promptSnippet}:{},...Array.isArray(r.promptGuidelines)&&r.promptGuidelines.every(e=>typeof e==`string`)?{promptGuidelines:r.promptGuidelines}:{}}:void 0;return{...typeof t.collapseKey==`string`?{collapseKey:t.collapseKey}:{},...i?{guidance:i}:{}}}catch{return{}}}function s(e){let t=e.collapseKey?.trim().toLowerCase();return t?t===`off`?`off`:/^[a-z0-9_+\-\]{}]+$/u.test(t)?t:r:r}export{r as DEFAULT_COLLAPSE_KEY,o as loadUserFeedbackConfig,s as resolveCollapseKey};
2
+ //# sourceMappingURL=configAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configAdapter.mjs","names":[],"sources":["../../../src/adapters/config/configAdapter.ts"],"sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const DEFAULT_COLLAPSE_KEY = 'ctrl+]';\nexport const COLLAPSE_KEY_OFF = 'off';\n\nexport interface GuidanceConfig {\n promptSnippet?: string;\n promptGuidelines?: string[];\n}\n\nexport interface UserFeedbackConfig {\n collapseKey?: string;\n guidance?: GuidanceConfig;\n}\n\nfunction legacyConfigPath(environment: NodeJS.ProcessEnv): string {\n const configRoot = environment.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), '.config');\n return path.join(configRoot, 'rpiv-ask-user-question', 'config.json');\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nexport function loadUserFeedbackConfig(environment: NodeJS.ProcessEnv = process.env): UserFeedbackConfig {\n const filePath = legacyConfigPath(environment);\n if (!fs.existsSync(filePath)) return {};\n try {\n const parsed: unknown = JSON.parse(fs.readFileSync(filePath, 'utf8'));\n if (!isRecord(parsed)) return {};\n const rawGuidance = isRecord(parsed.guidance) ? parsed.guidance : undefined;\n const guidance = rawGuidance\n ? {\n ...(typeof rawGuidance.promptSnippet === 'string' ? { promptSnippet: rawGuidance.promptSnippet } : {}),\n ...(Array.isArray(rawGuidance.promptGuidelines) &&\n rawGuidance.promptGuidelines.every((item) => typeof item === 'string')\n ? { promptGuidelines: rawGuidance.promptGuidelines as string[] }\n : {}),\n }\n : undefined;\n return {\n ...(typeof parsed.collapseKey === 'string' ? { collapseKey: parsed.collapseKey } : {}),\n ...(guidance ? { guidance } : {}),\n };\n } catch {\n return {};\n }\n}\n\nexport function resolveCollapseKey(config: UserFeedbackConfig): string {\n const value = config.collapseKey?.trim().toLowerCase();\n if (!value) return DEFAULT_COLLAPSE_KEY;\n if (value === COLLAPSE_KEY_OFF) return COLLAPSE_KEY_OFF;\n return /^[a-z0-9_+\\-\\]{}]+$/u.test(value) ? value : DEFAULT_COLLAPSE_KEY;\n}\n"],"mappings":"uEAIA,MAAa,EAAuB,SAapC,SAAS,EAAiB,EAAwC,CAChE,IAAM,EAAa,EAAY,iBAAiB,KAAK,GAAK,EAAK,KAAK,EAAG,QAAQ,EAAG,SAAS,EAC3F,OAAO,EAAK,KAAK,EAAY,yBAA0B,aAAa,CACtE,CAEA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAgB,EAAuB,EAAiC,QAAQ,IAAyB,CACvG,IAAM,EAAW,EAAiB,CAAW,EAC7C,GAAI,CAAC,EAAG,WAAW,CAAQ,EAAG,MAAO,CAAC,EACtC,GAAI,CACF,IAAM,EAAkB,KAAK,MAAM,EAAG,aAAa,EAAU,MAAM,CAAC,EACpE,GAAI,CAAC,EAAS,CAAM,EAAG,MAAO,CAAC,EAC/B,IAAM,EAAc,EAAS,EAAO,QAAQ,EAAI,EAAO,SAAW,IAAA,GAC5D,EAAW,EACb,CACE,GAAI,OAAO,EAAY,eAAkB,SAAW,CAAE,cAAe,EAAY,aAAc,EAAI,CAAC,EACpG,GAAI,MAAM,QAAQ,EAAY,gBAAgB,GAC9C,EAAY,iBAAiB,MAAO,GAAS,OAAO,GAAS,QAAQ,EACjE,CAAE,iBAAkB,EAAY,gBAA6B,EAC7D,CAAC,CACP,EACA,IAAA,GACJ,MAAO,CACL,GAAI,OAAO,EAAO,aAAgB,SAAW,CAAE,YAAa,EAAO,WAAY,EAAI,CAAC,EACpF,GAAI,EAAW,CAAE,UAAS,EAAI,CAAC,CACjC,CACF,MAAQ,CACN,MAAO,CAAC,CACV,CACF,CAEA,SAAgB,EAAmB,EAAoC,CACrE,IAAM,EAAQ,EAAO,aAAa,KAAK,CAAC,CAAC,YAAY,EAGrD,OAFK,EACD,IAAA,MAA4B,MACzB,uBAAuB,KAAK,CAAK,EAAI,EAAQ,EAFjC,CAGrB"}
@@ -0,0 +1,5 @@
1
+ const e=`The questions were spoken through autonomous voice. Stop now and wait for the user's next message; it will arrive as an ordinary user message.`;function t(e){return e.source===`@agimon-ai/doompi-voice`&&e.id===`voice-auto`}function n(e){let t=e.questions.length>1;return e.questions.map((e,n)=>[`${t?`${n+1}. `:``}${e.question}${e.multiSelect?` (Select all that apply.)`:``}`,...e.options.map((e,t)=>` ${t+1}. ${e.label}`),` Type something.`].join(`
2
+ `)).join(`
3
+
4
+ `)}var r=class{modes;narration;buildNarration;constructor(e,t,n){this.modes=e,this.narration=t,this.buildNarration=n}handoff(e){if(this.modes.getStatuses().some(t))return this.narration.narrate(this.buildNarration(e)),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:n(e)}}};function i(t){return{content:[{type:`text`,text:e}],details:t,terminate:!0}}exports.DOOM_VOICE_AUTO_MODE_ID=`voice-auto`,exports.DOOM_VOICE_SOURCE=`@agimon-ai/doompi-voice`,exports.VOICE_WAIT_MESSAGE=e,exports.VoiceQuestionHandoff=r,exports.buildVoiceToolResult=i,exports.formatVoiceQuestionPrompt=n;
5
+ //# sourceMappingURL=voiceQuestionHandoff.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionHandoff.cjs","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import type { DoomModeStatusRegistry, DoomModeStatus } from '@agimon-ai/doompi-extension-contracts/mode';\nimport type { NarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\nexport const DOOM_VOICE_SOURCE = '@agimon-ai/doompi-voice';\nexport const DOOM_VOICE_AUTO_MODE_ID = 'voice-auto';\nexport const VOICE_WAIT_MESSAGE =\n \"The questions were spoken through autonomous voice. Stop now and wait for the user's next message; it will arrive as an ordinary user message.\";\n\nfunction isAutonomousVoice(status: DoomModeStatus): boolean {\n return status.source === DOOM_VOICE_SOURCE && status.id === DOOM_VOICE_AUTO_MODE_ID;\n}\n\nexport function formatVoiceQuestionPrompt(params: QuestionParams): string {\n const numberQuestions = params.questions.length > 1;\n return params.questions\n .map((question, questionIndex) => {\n const heading = `${numberQuestions ? `${questionIndex + 1}. ` : ''}${question.question}${question.multiSelect ? ' (Select all that apply.)' : ''}`;\n const options = question.options.map((option, optionIndex) => ` ${optionIndex + 1}. ${option.label}`);\n return [heading, ...options, ' Type something.'].join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport class VoiceQuestionHandoff {\n constructor(\n private readonly modes: Pick<DoomModeStatusRegistry, 'getStatuses'>,\n private readonly narration: NarrationRequester,\n private readonly buildNarration: (params: QuestionParams) => string,\n ) {}\n\n handoff(params: QuestionParams): QuestionnaireResult | undefined {\n if (!this.modes.getStatuses().some(isAutonomousVoice)) return undefined;\n this.narration.narrate(this.buildNarration(params));\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\nexport function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult {\n return {\n content: [{ type: 'text', text: VOICE_WAIT_MESSAGE }],\n details,\n terminate: true,\n };\n}\n"],"mappings":"AAKA,MAEa,EACX,iJAEF,SAAS,EAAkB,EAAiC,CAC1D,OAAO,EAAO,SAAA,2BAAgC,EAAO,KAAA,YACvD,CAEA,SAAgB,EAA0B,EAAgC,CACxE,IAAM,EAAkB,EAAO,UAAU,OAAS,EAClD,OAAO,EAAO,UACX,KAAK,EAAU,IAGP,CAAC,GAFW,EAAkB,GAAG,EAAgB,EAAE,IAAM,KAAK,EAAS,WAAW,EAAS,YAAc,4BAA8B,KAE7H,GADD,EAAS,QAAQ,KAAK,EAAQ,IAAgB,KAAK,EAAc,EAAE,IAAI,EAAO,OACpE,EAAG,mBAAmB,CAAC,CAAC,KAAK;CAAI,CAC5D,CAAC,CACD,KAAK;;CAAM,CAChB,CAEA,IAAa,EAAb,KAAkC,CAEb,MACA,UACA,eAHnB,YACE,EACA,EACA,EACA,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,QAAQ,EAAyD,CAC1D,QAAK,MAAM,YAAY,CAAC,CAAC,KAAK,CAAiB,EAEpD,OADA,KAAK,UAAU,QAAQ,KAAK,eAAe,CAAM,CAAC,EAC3C,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAEA,SAAgB,EAAqB,EAA8C,CACjF,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,CAAmB,CAAC,EACpD,UACA,UAAW,EACb,CACF"}
@@ -0,0 +1,5 @@
1
+ const e=`@agimon-ai/doompi-voice`,t=`voice-auto`,n=`The questions were spoken through autonomous voice. Stop now and wait for the user's next message; it will arrive as an ordinary user message.`;function r(e){return e.source===`@agimon-ai/doompi-voice`&&e.id===`voice-auto`}function i(e){let t=e.questions.length>1;return e.questions.map((e,n)=>[`${t?`${n+1}. `:``}${e.question}${e.multiSelect?` (Select all that apply.)`:``}`,...e.options.map((e,t)=>` ${t+1}. ${e.label}`),` Type something.`].join(`
2
+ `)).join(`
3
+
4
+ `)}var a=class{modes;narration;buildNarration;constructor(e,t,n){this.modes=e,this.narration=t,this.buildNarration=n}handoff(e){if(this.modes.getStatuses().some(r))return this.narration.narrate(this.buildNarration(e)),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:i(e)}}};function o(e){return{content:[{type:`text`,text:n}],details:e,terminate:!0}}export{t as DOOM_VOICE_AUTO_MODE_ID,e as DOOM_VOICE_SOURCE,n as VOICE_WAIT_MESSAGE,a as VoiceQuestionHandoff,o as buildVoiceToolResult,i as formatVoiceQuestionPrompt};
5
+ //# sourceMappingURL=voiceQuestionHandoff.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionHandoff.mjs","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import type { DoomModeStatusRegistry, DoomModeStatus } from '@agimon-ai/doompi-extension-contracts/mode';\nimport type { NarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\nexport const DOOM_VOICE_SOURCE = '@agimon-ai/doompi-voice';\nexport const DOOM_VOICE_AUTO_MODE_ID = 'voice-auto';\nexport const VOICE_WAIT_MESSAGE =\n \"The questions were spoken through autonomous voice. Stop now and wait for the user's next message; it will arrive as an ordinary user message.\";\n\nfunction isAutonomousVoice(status: DoomModeStatus): boolean {\n return status.source === DOOM_VOICE_SOURCE && status.id === DOOM_VOICE_AUTO_MODE_ID;\n}\n\nexport function formatVoiceQuestionPrompt(params: QuestionParams): string {\n const numberQuestions = params.questions.length > 1;\n return params.questions\n .map((question, questionIndex) => {\n const heading = `${numberQuestions ? `${questionIndex + 1}. ` : ''}${question.question}${question.multiSelect ? ' (Select all that apply.)' : ''}`;\n const options = question.options.map((option, optionIndex) => ` ${optionIndex + 1}. ${option.label}`);\n return [heading, ...options, ' Type something.'].join('\\n');\n })\n .join('\\n\\n');\n}\n\nexport class VoiceQuestionHandoff {\n constructor(\n private readonly modes: Pick<DoomModeStatusRegistry, 'getStatuses'>,\n private readonly narration: NarrationRequester,\n private readonly buildNarration: (params: QuestionParams) => string,\n ) {}\n\n handoff(params: QuestionParams): QuestionnaireResult | undefined {\n if (!this.modes.getStatuses().some(isAutonomousVoice)) return undefined;\n this.narration.narrate(this.buildNarration(params));\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\nexport function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult {\n return {\n content: [{ type: 'text', text: VOICE_WAIT_MESSAGE }],\n details,\n terminate: true,\n };\n}\n"],"mappings":"AAKA,MAAa,EAAoB,0BACpB,EAA0B,aAC1B,EACX,iJAEF,SAAS,EAAkB,EAAiC,CAC1D,OAAO,EAAO,SAAA,2BAAgC,EAAO,KAAA,YACvD,CAEA,SAAgB,EAA0B,EAAgC,CACxE,IAAM,EAAkB,EAAO,UAAU,OAAS,EAClD,OAAO,EAAO,UACX,KAAK,EAAU,IAGP,CAAC,GAFW,EAAkB,GAAG,EAAgB,EAAE,IAAM,KAAK,EAAS,WAAW,EAAS,YAAc,4BAA8B,KAE7H,GADD,EAAS,QAAQ,KAAK,EAAQ,IAAgB,KAAK,EAAc,EAAE,IAAI,EAAO,OACpE,EAAG,mBAAmB,CAAC,CAAC,KAAK;CAAI,CAC5D,CAAC,CACD,KAAK;;CAAM,CAChB,CAEA,IAAa,EAAb,KAAkC,CAEb,MACA,UACA,eAHnB,YACE,EACA,EACA,EACA,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,QAAQ,EAAyD,CAC1D,QAAK,MAAM,YAAY,CAAC,CAAC,KAAK,CAAiB,EAEpD,OADA,KAAK,UAAU,QAAQ,KAAK,eAAe,CAAM,CAAC,EAC3C,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAEA,SAAgB,EAAqB,EAA8C,CACjF,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,CAAmB,CAAC,EACpD,UACA,UAAW,EACb,CACF"}
@@ -0,0 +1,15 @@
1
+ const e=require("../doom/voiceQuestionHandoff.cjs"),t=require("../config/configAdapter.cjs"),n=require("../../schemas/questionnaire.cjs"),r=require("../../services/responseService.cjs"),i=require("../../services/validationService.cjs"),a=require("./rpcQuestionnaireAdapter.cjs");let o=require("@agimon-ai/doompi-extension-contracts"),s=require("@earendil-works/pi-tui");const c=`ask_user_question`,l=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,u=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function d(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};(0,o.publishAskUserPrompt)(e.events,n)}function f(e,t){let n={active:t};(0,o.publishAskUserBlocked)(e.events,n)}function p(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function m(o,m){let h=t.loadUserFeedbackConfig().guidance;o.registerTool({name:c,label:`Ask User Question`,description:`Ask the user one or more structured questions during execution. Use when you need to:
2
+ 1. Gather user preferences or requirements
3
+ 2. Clarify ambiguous instructions
4
+ 3. Get decisions on implementation choices as you work
5
+ 4. Offer choices to the user about what direction to take
6
+
7
+ Usage notes:
8
+ - Users can type a custom answer via the automatically appended "Type something." row on every question or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.
9
+ - Use multiSelect: true when multiple answers are valid.
10
+ - If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
11
+
12
+ Preview feature:
13
+ Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:h?.promptSnippet??l,promptGuidelines:h?.promptGuidelines??u,parameters:n.QuestionParamsSchema,executionMode:`sequential`,async execute(n,s,c,l,u){let h=s;if(!u.hasUI)return r.buildToolResult(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let g=i.validateQuestionnaire(h);if(!g.ok)return r.buildToolResult(g.message,{answers:[],cancelled:!0,error:g.error});try{let n=await m.enqueue(async({signal:e,reportProgress:n})=>{d(o,h);let r=m.tryVoice?.(h);if(r)return r;let i=!0,s=()=>{i&&(i=!1,f(o,!1))};e.addEventListener(`abort`,s,{once:!0}),f(o,!0);try{if(u.mode===`rpc`&&p(u))return await a.runRpcQuestionnaire(u,h,e,n);let r=await m.runTui(u,h,t.resolveCollapseKey(t.loadUserFeedbackConfig()),e,n);return r===void 0?p(u)?await a.runRpcQuestionnaire(u,h,e,n):{answers:[],cancelled:!0,error:`no_custom_ui`}:r}finally{e.removeEventListener(`abort`,s),s()}},c);return n.delivery===`voice`?e.buildVoiceToolResult(n):n.error===`no_custom_ui`?r.buildToolResult(`Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.`,n):r.buildQuestionnaireResponse(n,h)}catch(e){let t=e instanceof Error?e.message:String(e);return r.buildToolResult(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=t.fg(`toolTitle`,t.bold(c)),a=`${n.questions.length} question${n.questions.length===1?``:`s`}`;return new s.Text(`${i} ${t.fg(`muted`,a)}${r?` ${t.fg(`dim`,`(${r})`)}`:``}`,0,0)},renderResult(e,t,n){let r=e.details;if(!r){let t=e.content[0];return new s.Text(t?.type===`text`?t.text:``,0,0)}if(r.cancelled)return new s.Text(n.fg(`warning`,`Questionnaire cancelled`),0,0);if(r.delivery===`voice`&&r.voicePrompt)return new s.Text(n.fg(`text`,r.voicePrompt),0,0);let i=r.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${t??``}`});return new s.Text(i.join(`
14
+ `),0,0)}})}exports.ASK_USER_QUESTION_TOOL_NAME=c,exports.DEFAULT_PROMPT_GUIDELINES=u,exports.DEFAULT_PROMPT_SNIPPET=l,exports.registerAskUserQuestionTool=m;
15
+ //# sourceMappingURL=askUserQuestionAdapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askUserQuestionAdapter.cjs","names":["loadUserFeedbackConfig","QuestionParamsSchema","buildToolResult","validateQuestionnaire","runRpcQuestionnaire","resolveCollapseKey","buildVoiceToolResult","buildQuestionnaireResponse","Text"],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import{buildVoiceToolResult as e}from"../doom/voiceQuestionHandoff.mjs";import{loadUserFeedbackConfig as t,resolveCollapseKey as n}from"../config/configAdapter.mjs";import{QuestionParamsSchema as r}from"../../schemas/questionnaire.mjs";import{buildQuestionnaireResponse as i,buildToolResult as a}from"../../services/responseService.mjs";import{validateQuestionnaire as o}from"../../services/validationService.mjs";import{runRpcQuestionnaire as s}from"./rpcQuestionnaireAdapter.mjs";import{publishAskUserBlocked as c,publishAskUserPrompt as l}from"@agimon-ai/doompi-extension-contracts";import{Text as u}from"@earendil-works/pi-tui";const d=`ask_user_question`,f=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,p=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function m(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};l(e.events,n)}function h(e,t){let n={active:t};c(e.events,n)}function g(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function _(c,l){let _=t().guidance;c.registerTool({name:d,label:`Ask User Question`,description:`Ask the user one or more structured questions during execution. Use when you need to:
2
+ 1. Gather user preferences or requirements
3
+ 2. Clarify ambiguous instructions
4
+ 3. Get decisions on implementation choices as you work
5
+ 4. Offer choices to the user about what direction to take
6
+
7
+ Usage notes:
8
+ - Users can type a custom answer via the automatically appended "Type something." row on every question or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.
9
+ - Use multiSelect: true when multiple answers are valid.
10
+ - If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
11
+
12
+ Preview feature:
13
+ Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:_?.promptSnippet??f,promptGuidelines:_?.promptGuidelines??p,parameters:r,executionMode:`sequential`,async execute(r,u,d,f,p){let _=u;if(!p.hasUI)return a(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let v=o(_);if(!v.ok)return a(v.message,{answers:[],cancelled:!0,error:v.error});try{let r=await l.enqueue(async({signal:e,reportProgress:r})=>{m(c,_);let i=l.tryVoice?.(_);if(i)return i;let a=!0,o=()=>{a&&(a=!1,h(c,!1))};e.addEventListener(`abort`,o,{once:!0}),h(c,!0);try{if(p.mode===`rpc`&&g(p))return await s(p,_,e,r);let i=await l.runTui(p,_,n(t()),e,r);return i===void 0?g(p)?await s(p,_,e,r):{answers:[],cancelled:!0,error:`no_custom_ui`}:i}finally{e.removeEventListener(`abort`,o),o()}},d);return r.delivery===`voice`?e(r):r.error===`no_custom_ui`?a(`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.`,r):i(r,_)}catch(e){let t=e instanceof Error?e.message:String(e);return a(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=t.fg(`toolTitle`,t.bold(d)),a=`${n.questions.length} question${n.questions.length===1?``:`s`}`;return new u(`${i} ${t.fg(`muted`,a)}${r?` ${t.fg(`dim`,`(${r})`)}`:``}`,0,0)},renderResult(e,t,n){let r=e.details;if(!r){let t=e.content[0];return new u(t?.type===`text`?t.text:``,0,0)}if(r.cancelled)return new u(n.fg(`warning`,`Questionnaire cancelled`),0,0);if(r.delivery===`voice`&&r.voicePrompt)return new u(n.fg(`text`,r.voicePrompt),0,0);let i=r.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${t??``}`});return new u(i.join(`
14
+ `),0,0)}})}export{d as ASK_USER_QUESTION_TOOL_NAME,p as DEFAULT_PROMPT_GUIDELINES,f as DEFAULT_PROMPT_SNIPPET,_ as registerAskUserQuestionTool};
15
+ //# sourceMappingURL=askUserQuestionAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askUserQuestionAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("./askUserQuestionAdapter.cjs");function t(t,n){let r=t.getActiveTools(),i=r.includes(e.ASK_USER_QUESTION_TOOL_NAME);if(!n.hasUI&&i){t.setActiveTools(r.filter(t=>t!==e.ASK_USER_QUESTION_TOOL_NAME));return}n.hasUI&&!i&&t.setActiveTools([...r,e.ASK_USER_QUESTION_TOOL_NAME])}function n(e){e.on(`before_agent_start`,(n,r)=>{t(e,r)})}exports.reconcileAskUserQuestionTool=t,exports.registerAskUserQuestionReconciler=n;
2
+ //# sourceMappingURL=reconcileAdapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcileAdapter.cjs","names":["ASK_USER_QUESTION_TOOL_NAME"],"sources":["../../../src/adapters/pi/reconcileAdapter.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { ASK_USER_QUESTION_TOOL_NAME } from './askUserQuestionAdapter.js';\n\nexport function reconcileAskUserQuestionTool(pi: ExtensionAPI, context: Pick<ExtensionContext, 'hasUI'>): void {\n const activeTools = pi.getActiveTools();\n const active = activeTools.includes(ASK_USER_QUESTION_TOOL_NAME);\n if (!context.hasUI && active) {\n pi.setActiveTools(activeTools.filter((name) => name !== ASK_USER_QUESTION_TOOL_NAME));\n return;\n }\n if (context.hasUI && !active) {\n pi.setActiveTools([...activeTools, ASK_USER_QUESTION_TOOL_NAME]);\n }\n}\n\nexport function registerAskUserQuestionReconciler(pi: ExtensionAPI): void {\n pi.on('before_agent_start', (_event, context) => {\n reconcileAskUserQuestionTool(pi, context);\n });\n}\n"],"mappings":"gDAGA,SAAgB,EAA6B,EAAkB,EAAgD,CAC7G,IAAM,EAAc,EAAG,eAAe,EAChC,EAAS,EAAY,SAASA,EAAAA,2BAA2B,EAC/D,GAAI,CAAC,EAAQ,OAAS,EAAQ,CAC5B,EAAG,eAAe,EAAY,OAAQ,GAAS,IAASA,EAAAA,2BAA2B,CAAC,EACpF,MACF,CACI,EAAQ,OAAS,CAAC,GACpB,EAAG,eAAe,CAAC,GAAG,EAAaA,EAAAA,2BAA2B,CAAC,CAEnE,CAEA,SAAgB,EAAkC,EAAwB,CACxE,EAAG,GAAG,sBAAuB,EAAQ,IAAY,CAC/C,EAA6B,EAAI,CAAO,CAC1C,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ import{ASK_USER_QUESTION_TOOL_NAME as e}from"./askUserQuestionAdapter.mjs";function t(t,n){let r=t.getActiveTools(),i=r.includes(e);if(!n.hasUI&&i){t.setActiveTools(r.filter(t=>t!==e));return}n.hasUI&&!i&&t.setActiveTools([...r,e])}function n(e){e.on(`before_agent_start`,(n,r)=>{t(e,r)})}export{t as reconcileAskUserQuestionTool,n as registerAskUserQuestionReconciler};
2
+ //# sourceMappingURL=reconcileAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcileAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/reconcileAdapter.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { ASK_USER_QUESTION_TOOL_NAME } from './askUserQuestionAdapter.js';\n\nexport function reconcileAskUserQuestionTool(pi: ExtensionAPI, context: Pick<ExtensionContext, 'hasUI'>): void {\n const activeTools = pi.getActiveTools();\n const active = activeTools.includes(ASK_USER_QUESTION_TOOL_NAME);\n if (!context.hasUI && active) {\n pi.setActiveTools(activeTools.filter((name) => name !== ASK_USER_QUESTION_TOOL_NAME));\n return;\n }\n if (context.hasUI && !active) {\n pi.setActiveTools([...activeTools, ASK_USER_QUESTION_TOOL_NAME]);\n }\n}\n\nexport function registerAskUserQuestionReconciler(pi: ExtensionAPI): void {\n pi.on('before_agent_start', (_event, context) => {\n reconcileAskUserQuestionTool(pi, context);\n });\n}\n"],"mappings":"2EAGA,SAAgB,EAA6B,EAAkB,EAAgD,CAC7G,IAAM,EAAc,EAAG,eAAe,EAChC,EAAS,EAAY,SAAS,CAA2B,EAC/D,GAAI,CAAC,EAAQ,OAAS,EAAQ,CAC5B,EAAG,eAAe,EAAY,OAAQ,GAAS,IAAS,CAA2B,CAAC,EACpF,MACF,CACI,EAAQ,OAAS,CAAC,GACpB,EAAG,eAAe,CAAC,GAAG,EAAa,CAA2B,CAAC,CAEnE,CAEA,SAAgB,EAAkC,EAAwB,CACxE,EAAG,GAAG,sBAAuB,EAAQ,IAAY,CAC/C,EAA6B,EAAI,CAAO,CAC1C,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ const e=`Type something.`,t=`Next`;function n(e){return{answers:e,cancelled:!0}}async function r(e,t,n,r){let i=await e.ui.input(t.question,`Type your answer`,{signal:r});if(i!==void 0)return{questionIndex:n,question:t.question,kind:`custom`,answer:i}}async function i(t,n,i,a){let o=[...n.options.map(e=>e.label),e],s=await t.ui.select(n.question,o,{signal:a});if(s===void 0)return;if(s===e)return r(t,n,i,a);let c=n.options.find(e=>e.label===s);if(c)return{questionIndex:i,question:n.question,kind:`option`,answer:c.label,...c.preview?{preview:c.preview}:{}}}function a(n,r){return[...n.options.map(e=>`${r.has(e.label)?`[x]`:`[ ]`} ${e.label}`),e,t]}function o(e){return e.replace(/^\[[ x]\]\s/u,``)}async function s(n,i,s,c){let l=new Set;for(;!c?.aborted;){let u=await n.ui.select(`${i.question} (choose options, then Next)`,a(i,l),{signal:c});if(u===void 0)return;if(u===e)return r(n,i,s,c);if(u===t)return{questionIndex:s,question:i.question,kind:`multi`,answer:null,selected:[...l]};let d=o(u);i.options.some(e=>e.label===d)&&(l.has(d)?l.delete(d):l.add(d))}}async function c(e,t,r,a){let o=[];for(let c=0;c<t.questions.length;c+=1){if(r?.aborted)return n(o);let l=t.questions[c];if(!l)continue;let u=l.multiSelect?await s(e,l,c,r):await i(e,l,c,r);if(!u)return n(o);o.push(u),a?.({answers:o.map(e=>({...e})),cancelled:!1})}return{answers:o,cancelled:!1}}exports.runRpcQuestionnaire=c;
2
+ //# sourceMappingURL=rpcQuestionnaireAdapter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpcQuestionnaireAdapter.cjs","names":[],"sources":["../../../src/adapters/pi/rpcQuestionnaireAdapter.ts"],"sourcesContent":["import type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { QuestionData, QuestionParams } from '../../schemas/questionnaire.js';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire.js';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\nasync function askForCustomAnswer(\n context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await context.ui.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 context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await context.ui.select(question.question, labels, { signal });\n if (selected === undefined) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(context, 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 context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await context.ui.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n { signal },\n );\n if (choice === undefined) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(context, 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 runRpcQuestionnaire(\n context: ExtensionContext,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\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(context, question, questionIndex, signal)\n : await askSingleChoice(context, question, questionIndex, signal);\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"],"mappings":"AAIA,MAAM,EAAe,kBACf,EAAa,OAEnB,SAAS,EAAU,EAAgD,CACjE,MAAO,CAAE,UAAS,UAAW,EAAK,CACpC,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAS,MAAM,EAAQ,GAAG,MAAM,EAAS,SAAU,mBAAoB,CAAE,QAAO,CAAC,EACnF,OAAW,IAAA,GACf,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,SACN,QACF,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAS,CAAC,GAAG,EAAS,QAAQ,IAAK,GAAW,EAAO,KAAK,EAAG,CAAY,EACzE,EAAW,MAAM,EAAQ,GAAG,OAAO,EAAS,SAAU,EAAQ,CAAE,QAAO,CAAC,EAC9E,GAAI,IAAa,IAAA,GAAW,OAC5B,GAAI,IAAa,EACf,OAAO,EAAmB,EAAS,EAAU,EAAe,CAAM,EAEpE,IAAM,EAAS,EAAS,QAAQ,KAAM,GAAc,EAAU,QAAU,CAAQ,EAC3E,KACL,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,SACN,OAAQ,EAAO,MACf,GAAI,EAAO,QAAU,CAAE,QAAS,EAAO,OAAQ,EAAI,CAAC,CACtD,CACF,CAEA,SAAS,EAAkB,EAAwB,EAAyC,CAK1F,MAAO,CAAC,GAJQ,EAAS,QAAQ,IAAK,GAE7B,GADQ,EAAS,IAAI,EAAO,KAAK,EAAI,MAAQ,MACnC,GAAG,EAAO,OAEZ,EAAG,EAAc,CAAU,CAC9C,CAEA,SAAS,EAAqB,EAAuB,CACnD,OAAO,EAAM,QAAQ,eAAgB,EAAE,CACzC,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAW,IAAI,IACrB,KAAO,CAAC,GAAQ,SAAS,CACvB,IAAM,EAAS,MAAM,EAAQ,GAAG,OAC9B,GAAG,EAAS,SAAS,8BACrB,EAAkB,EAAU,CAAQ,EACpC,CAAE,QAAO,CACX,EACA,GAAI,IAAW,IAAA,GAAW,OAC1B,GAAI,IAAW,EACb,OAAO,EAAmB,EAAS,EAAU,EAAe,CAAM,EAEpE,GAAI,IAAW,EACb,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,QACN,OAAQ,KACR,SAAU,CAAC,GAAG,CAAQ,CACxB,EAEF,IAAM,EAAQ,EAAqB,CAAM,EACpC,EAAS,QAAQ,KAAM,GAAW,EAAO,QAAU,CAAK,IACzD,EAAS,IAAI,CAAK,EAAG,EAAS,OAAO,CAAK,EACzC,EAAS,IAAI,CAAK,EACzB,CAEF,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EAC8B,CAC9B,IAAM,EAA4B,CAAC,EACnC,IAAK,IAAI,EAAgB,EAAG,EAAgB,EAAO,UAAU,OAAQ,GAAiB,EAAG,CACvF,GAAI,GAAQ,QAAS,OAAO,EAAU,CAAO,EAC7C,IAAM,EAAW,EAAO,UAAU,GAClC,GAAI,CAAC,EAAU,SACf,IAAM,EAAS,EAAS,YACpB,MAAM,EAAkB,EAAS,EAAU,EAAe,CAAM,EAChE,MAAM,EAAgB,EAAS,EAAU,EAAe,CAAM,EAClE,GAAI,CAAC,EAAQ,OAAO,EAAU,CAAO,EACrC,EAAQ,KAAK,CAAM,EACnB,IAAiB,CAAE,QAAS,EAAQ,IAAK,IAAU,CAAE,GAAG,CAAK,EAAE,EAAG,UAAW,EAAM,CAAC,CACtF,CACA,MAAO,CAAE,UAAS,UAAW,EAAM,CACrC"}
@@ -0,0 +1,2 @@
1
+ const e=`Type something.`,t=`Next`;function n(e){return{answers:e,cancelled:!0}}async function r(e,t,n,r){let i=await e.ui.input(t.question,`Type your answer`,{signal:r});if(i!==void 0)return{questionIndex:n,question:t.question,kind:`custom`,answer:i}}async function i(t,n,i,a){let o=[...n.options.map(e=>e.label),e],s=await t.ui.select(n.question,o,{signal:a});if(s===void 0)return;if(s===e)return r(t,n,i,a);let c=n.options.find(e=>e.label===s);if(c)return{questionIndex:i,question:n.question,kind:`option`,answer:c.label,...c.preview?{preview:c.preview}:{}}}function a(n,r){return[...n.options.map(e=>`${r.has(e.label)?`[x]`:`[ ]`} ${e.label}`),e,t]}function o(e){return e.replace(/^\[[ x]\]\s/u,``)}async function s(n,i,s,c){let l=new Set;for(;!c?.aborted;){let u=await n.ui.select(`${i.question} (choose options, then Next)`,a(i,l),{signal:c});if(u===void 0)return;if(u===e)return r(n,i,s,c);if(u===t)return{questionIndex:s,question:i.question,kind:`multi`,answer:null,selected:[...l]};let d=o(u);i.options.some(e=>e.label===d)&&(l.has(d)?l.delete(d):l.add(d))}}async function c(e,t,r,a){let o=[];for(let c=0;c<t.questions.length;c+=1){if(r?.aborted)return n(o);let l=t.questions[c];if(!l)continue;let u=l.multiSelect?await s(e,l,c,r):await i(e,l,c,r);if(!u)return n(o);o.push(u),a?.({answers:o.map(e=>({...e})),cancelled:!1})}return{answers:o,cancelled:!1}}export{c as runRpcQuestionnaire};
2
+ //# sourceMappingURL=rpcQuestionnaireAdapter.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpcQuestionnaireAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/rpcQuestionnaireAdapter.ts"],"sourcesContent":["import type { ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { QuestionData, QuestionParams } from '../../schemas/questionnaire.js';\nimport type { QuestionAnswer, QuestionnaireResult } from '../../types/questionnaire.js';\n\nconst CUSTOM_LABEL = 'Type something.';\nconst DONE_LABEL = 'Next';\n\nfunction cancelled(answers: QuestionAnswer[]): QuestionnaireResult {\n return { answers, cancelled: true };\n}\n\nasync function askForCustomAnswer(\n context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const answer = await context.ui.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 context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const labels = [...question.options.map((option) => option.label), CUSTOM_LABEL];\n const selected = await context.ui.select(question.question, labels, { signal });\n if (selected === undefined) return undefined;\n if (selected === CUSTOM_LABEL) {\n return askForCustomAnswer(context, 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 context: ExtensionContext,\n question: QuestionData,\n questionIndex: number,\n signal?: AbortSignal,\n): Promise<QuestionAnswer | undefined> {\n const selected = new Set<string>();\n while (!signal?.aborted) {\n const choice = await context.ui.select(\n `${question.question} (choose options, then Next)`,\n multiChoiceLabels(question, selected),\n { signal },\n );\n if (choice === undefined) return undefined;\n if (choice === CUSTOM_LABEL) {\n return askForCustomAnswer(context, 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 runRpcQuestionnaire(\n context: ExtensionContext,\n params: QuestionParams,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult> {\n const answers: QuestionAnswer[] = [];\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(context, question, questionIndex, signal)\n : await askSingleChoice(context, question, questionIndex, signal);\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"],"mappings":"AAIA,MAAM,EAAe,kBACf,EAAa,OAEnB,SAAS,EAAU,EAAgD,CACjE,MAAO,CAAE,UAAS,UAAW,EAAK,CACpC,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAS,MAAM,EAAQ,GAAG,MAAM,EAAS,SAAU,mBAAoB,CAAE,QAAO,CAAC,EACnF,OAAW,IAAA,GACf,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,SACN,QACF,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAS,CAAC,GAAG,EAAS,QAAQ,IAAK,GAAW,EAAO,KAAK,EAAG,CAAY,EACzE,EAAW,MAAM,EAAQ,GAAG,OAAO,EAAS,SAAU,EAAQ,CAAE,QAAO,CAAC,EAC9E,GAAI,IAAa,IAAA,GAAW,OAC5B,GAAI,IAAa,EACf,OAAO,EAAmB,EAAS,EAAU,EAAe,CAAM,EAEpE,IAAM,EAAS,EAAS,QAAQ,KAAM,GAAc,EAAU,QAAU,CAAQ,EAC3E,KACL,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,SACN,OAAQ,EAAO,MACf,GAAI,EAAO,QAAU,CAAE,QAAS,EAAO,OAAQ,EAAI,CAAC,CACtD,CACF,CAEA,SAAS,EAAkB,EAAwB,EAAyC,CAK1F,MAAO,CAAC,GAJQ,EAAS,QAAQ,IAAK,GAE7B,GADQ,EAAS,IAAI,EAAO,KAAK,EAAI,MAAQ,MACnC,GAAG,EAAO,OAEZ,EAAG,EAAc,CAAU,CAC9C,CAEA,SAAS,EAAqB,EAAuB,CACnD,OAAO,EAAM,QAAQ,eAAgB,EAAE,CACzC,CAEA,eAAe,EACb,EACA,EACA,EACA,EACqC,CACrC,IAAM,EAAW,IAAI,IACrB,KAAO,CAAC,GAAQ,SAAS,CACvB,IAAM,EAAS,MAAM,EAAQ,GAAG,OAC9B,GAAG,EAAS,SAAS,8BACrB,EAAkB,EAAU,CAAQ,EACpC,CAAE,QAAO,CACX,EACA,GAAI,IAAW,IAAA,GAAW,OAC1B,GAAI,IAAW,EACb,OAAO,EAAmB,EAAS,EAAU,EAAe,CAAM,EAEpE,GAAI,IAAW,EACb,MAAO,CACL,gBACA,SAAU,EAAS,SACnB,KAAM,QACN,OAAQ,KACR,SAAU,CAAC,GAAG,CAAQ,CACxB,EAEF,IAAM,EAAQ,EAAqB,CAAM,EACpC,EAAS,QAAQ,KAAM,GAAW,EAAO,QAAU,CAAK,IACzD,EAAS,IAAI,CAAK,EAAG,EAAS,OAAO,CAAK,EACzC,EAAS,IAAI,CAAK,EACzB,CAEF,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EAC8B,CAC9B,IAAM,EAA4B,CAAC,EACnC,IAAK,IAAI,EAAgB,EAAG,EAAgB,EAAO,UAAU,OAAQ,GAAiB,EAAG,CACvF,GAAI,GAAQ,QAAS,OAAO,EAAU,CAAO,EAC7C,IAAM,EAAW,EAAO,UAAU,GAClC,GAAI,CAAC,EAAU,SACf,IAAM,EAAS,EAAS,YACpB,MAAM,EAAkB,EAAS,EAAU,EAAe,CAAM,EAChE,MAAM,EAAgB,EAAS,EAAU,EAAe,CAAM,EAClE,GAAI,CAAC,EAAQ,OAAO,EAAU,CAAO,EACrC,EAAQ,KAAK,CAAM,EACnB,IAAiB,CAAE,QAAS,EAAQ,IAAK,IAAU,CAAE,GAAG,CAAK,EAAE,EAAG,UAAW,EAAM,CAAC,CACtF,CACA,MAAO,CAAE,UAAS,UAAW,EAAM,CACrC"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("./schemas/events.cjs");let e=require("@agimon-ai/doompi-extension-contracts");Object.defineProperty(exports,"ASK_USER_BLOCKED_EVENT",{enumerable:!0,get:function(){return e.ASK_USER_BLOCKED_EVENT}}),Object.defineProperty(exports,"ASK_USER_PROMPT_EVENT",{enumerable:!0,get:function(){return e.ASK_USER_PROMPT_EVENT}});
@@ -0,0 +1,2 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion } from "./schemas/events.cjs";
2
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, type AskUserBlockedEventPayload, type AskUserPromptEventPayload, type AskUserPromptOption, type AskUserPromptQuestion };
@@ -0,0 +1,2 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion } from "./schemas/events.mjs";
2
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, type AskUserBlockedEventPayload, type AskUserPromptEventPayload, type AskUserPromptOption, type AskUserPromptQuestion };
@@ -0,0 +1 @@
1
+ import{ASK_USER_BLOCKED_EVENT as e,ASK_USER_PROMPT_EVENT as t}from"./schemas/events.mjs";export{e as ASK_USER_BLOCKED_EVENT,t as ASK_USER_PROMPT_EVENT};
@@ -0,0 +1,2 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("../adapters/doom/voiceQuestionHandoff.cjs"),t=require("../adapters/pi/askUserQuestionAdapter.cjs"),n=require("../adapters/pi/reconcileAdapter.cjs"),r=require("../services/questionnaireCoordinator.cjs"),i=require("../services/voiceQuestionNarration.cjs");let a=require("@agimon-ai/doompi-extension-contracts/mode"),o=require("@agimon-ai/doompi-extension-contracts/narration"),s=require("@agimon-ai/doompi-extension-contracts/protocol");const c=`@agimon-ai/doompi-user-feedback`,l=Symbol.for(`${c}/installed-hosts`);function u(e){let t=globalThis,n=t[l]??new WeakSet;return t[l]=n,!n.has(e)&&(n.add(e),!0)}function d(l){if(!u(l))return;let d=new r.QuestionnaireCoordinator,f,p,m,h=()=>{p?.(),p=void 0,f?.reset(),f=void 0,m=void 0};t.registerAskUserQuestionTool(l,{enqueue:(e,t)=>d.enqueue(e,t),runTui:async(...e)=>{let{runTuiQuestionnaire:t}=await Promise.resolve().then(()=>require("../tui/runQuestionnaire.cjs"));return t(...e)},tryVoice:e=>m?.handoff(e)}),n.registerAskUserQuestionReconciler(l),l.on(`session_start`,(t,n)=>{d.shutdown(),d=new r.QuestionnaireCoordinator,h();let u=n.sessionManager.getSessionId(),g=new a.DoomModeStatusRegistry,_=(0,s.createProtocolRuntime)({bus:l.events,source:c,sessionId:u});f=g,p=g.bind(_),m=new e.VoiceQuestionHandoff(g,(0,o.createNarrationRequester)({bus:l.events,source:c,sessionId:u}),i.buildVoiceQuestionNarration)}),l.on(`session_shutdown`,()=>{d.shutdown(),h()})}exports.default=d,exports.registerUserFeedbackExtension=d;
2
+ //# sourceMappingURL=pi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.cjs","names":["QuestionnaireCoordinator","DoomModeStatusRegistry","createProtocolRuntime","VoiceQuestionHandoff","createNarrationRequester","buildVoiceQuestionNarration"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { DoomModeStatusRegistry } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { VoiceQuestionHandoff } from '../adapters/doom/voiceQuestionHandoff.js';\nimport { registerAskUserQuestionTool } from '../adapters/pi/askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from '../adapters/pi/reconcileAdapter.js';\nimport { QuestionnaireCoordinator } from '../services/questionnaireCoordinator.js';\nimport { buildVoiceQuestionNarration } from '../services/voiceQuestionNarration.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst INSTALLATIONS_KEY = Symbol.for(`${PACKAGE_SOURCE}/installed-hosts`);\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerUserFeedbackExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n\n let coordinator = new QuestionnaireCoordinator();\n let modes: DoomModeStatusRegistry | undefined;\n let disposeModes: (() => void) | undefined;\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n const resetVoiceHandoff = (): void => {\n disposeModes?.();\n disposeModes = undefined;\n modes?.reset();\n modes = undefined;\n voiceHandoff = undefined;\n };\n\n registerAskUserQuestionTool(pi, {\n enqueue: (runner, signal) => coordinator.enqueue(runner, signal),\n runTui: async (...args) => {\n const { runTuiQuestionnaire } = await import('../tui/runQuestionnaire.js');\n return runTuiQuestionnaire(...args);\n },\n tryVoice: (params) => voiceHandoff?.handoff(params),\n });\n registerAskUserQuestionReconciler(pi);\n\n pi.on('session_start', (_event, context) => {\n coordinator.shutdown();\n coordinator = new QuestionnaireCoordinator();\n resetVoiceHandoff();\n\n const sessionId = context.sessionManager.getSessionId();\n const registry = new DoomModeStatusRegistry();\n const runtime = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId,\n });\n modes = registry;\n disposeModes = registry.bind(runtime);\n voiceHandoff = new VoiceQuestionHandoff(\n registry,\n createNarrationRequester({ bus: pi.events, source: PACKAGE_SOURCE, sessionId }),\n buildVoiceQuestionNarration,\n );\n });\n pi.on('session_shutdown', () => {\n coordinator.shutdown();\n resetVoiceHandoff();\n });\n}\n\nexport { registerUserFeedbackExtension as default };\n"],"mappings":"miBAUA,MAAM,EAAiB,kCACjB,EAAoB,OAAO,IAAI,GAAG,EAAe,iBAAiB,EAMxE,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAA8B,EAAwB,CACpE,GAAI,CAAC,EAAsB,CAAE,EAAG,OAEhC,IAAI,EAAc,IAAIA,EAAAA,yBAClB,EACA,EACA,EACE,MAAgC,CACpC,IAAe,EACf,EAAe,IAAA,GACf,GAAO,MAAM,EACb,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,EAEA,EAAA,4BAA4B,EAAI,CAC9B,SAAU,EAAQ,IAAW,EAAY,QAAQ,EAAQ,CAAM,EAC/D,OAAQ,MAAO,GAAG,IAAS,CACzB,GAAM,CAAE,uBAAwB,MAAA,QAAA,QAAA,CAAA,CAAA,SAAA,QAAM,6BAAA,CAAA,EACtC,OAAO,EAAoB,GAAG,CAAI,CACpC,EACA,SAAW,GAAW,GAAc,QAAQ,CAAM,CACpD,CAAC,EACD,EAAA,kCAAkC,CAAE,EAEpC,EAAG,GAAG,iBAAkB,EAAQ,IAAY,CAC1C,EAAY,SAAS,EACrB,EAAc,IAAIA,EAAAA,yBAClB,EAAkB,EAElB,IAAM,EAAY,EAAQ,eAAe,aAAa,EAChD,EAAW,IAAIC,EAAAA,uBACf,GAAA,EAAUC,EAAAA,sBAAAA,CAAsB,CACpC,IAAK,EAAG,OACR,OAAQ,EACR,WACF,CAAC,EACD,EAAQ,EACR,EAAe,EAAS,KAAK,CAAO,EACpC,EAAe,IAAIC,EAAAA,qBACjB,GAAA,EACAC,EAAAA,yBAAAA,CAAyB,CAAE,IAAK,EAAG,OAAQ,OAAQ,EAAgB,WAAU,CAAC,EAC9EC,EAAAA,2BACF,CACF,CAAC,EACD,EAAG,GAAG,uBAA0B,CAC9B,EAAY,SAAS,EACrB,EAAkB,CACpB,CAAC,CACH"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/extensions/pi.d.ts
3
+ declare function registerUserFeedbackExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerUserFeedbackExtension as default, registerUserFeedbackExtension };
6
+ //# sourceMappingURL=pi.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.d.cts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBA0BgB,8BAA8B,IAAI"}
@@ -0,0 +1,6 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/extensions/pi.d.ts
3
+ declare function registerUserFeedbackExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ export { registerUserFeedbackExtension as default, registerUserFeedbackExtension };
6
+ //# sourceMappingURL=pi.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.d.mts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBA0BgB,8BAA8B,IAAI"}
@@ -0,0 +1,2 @@
1
+ import{VoiceQuestionHandoff as e}from"../adapters/doom/voiceQuestionHandoff.mjs";import{registerAskUserQuestionTool as t}from"../adapters/pi/askUserQuestionAdapter.mjs";import{registerAskUserQuestionReconciler as n}from"../adapters/pi/reconcileAdapter.mjs";import{QuestionnaireCoordinator as r}from"../services/questionnaireCoordinator.mjs";import{buildVoiceQuestionNarration as i}from"../services/voiceQuestionNarration.mjs";import{DoomModeStatusRegistry as a}from"@agimon-ai/doompi-extension-contracts/mode";import{createNarrationRequester as o}from"@agimon-ai/doompi-extension-contracts/narration";import{createProtocolRuntime as s}from"@agimon-ai/doompi-extension-contracts/protocol";const c=`@agimon-ai/doompi-user-feedback`,l=Symbol.for(`${c}/installed-hosts`);function u(e){let t=globalThis,n=t[l]??new WeakSet;return t[l]=n,!n.has(e)&&(n.add(e),!0)}function d(l){if(!u(l))return;let d=new r,f,p,m,h=()=>{p?.(),p=void 0,f?.reset(),f=void 0,m=void 0};t(l,{enqueue:(e,t)=>d.enqueue(e,t),runTui:async(...e)=>{let{runTuiQuestionnaire:t}=await import(`../tui/runQuestionnaire.mjs`);return t(...e)},tryVoice:e=>m?.handoff(e)}),n(l),l.on(`session_start`,(t,n)=>{d.shutdown(),d=new r,h();let u=n.sessionManager.getSessionId(),g=new a,_=s({bus:l.events,source:c,sessionId:u});f=g,p=g.bind(_),m=new e(g,o({bus:l.events,source:c,sessionId:u}),i)}),l.on(`session_shutdown`,()=>{d.shutdown(),h()})}export{d as default,d as registerUserFeedbackExtension};
2
+ //# sourceMappingURL=pi.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { DoomModeStatusRegistry } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport { createProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { VoiceQuestionHandoff } from '../adapters/doom/voiceQuestionHandoff.js';\nimport { registerAskUserQuestionTool } from '../adapters/pi/askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from '../adapters/pi/reconcileAdapter.js';\nimport { QuestionnaireCoordinator } from '../services/questionnaireCoordinator.js';\nimport { buildVoiceQuestionNarration } from '../services/voiceQuestionNarration.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst INSTALLATIONS_KEY = Symbol.for(`${PACKAGE_SOURCE}/installed-hosts`);\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerUserFeedbackExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n\n let coordinator = new QuestionnaireCoordinator();\n let modes: DoomModeStatusRegistry | undefined;\n let disposeModes: (() => void) | undefined;\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n const resetVoiceHandoff = (): void => {\n disposeModes?.();\n disposeModes = undefined;\n modes?.reset();\n modes = undefined;\n voiceHandoff = undefined;\n };\n\n registerAskUserQuestionTool(pi, {\n enqueue: (runner, signal) => coordinator.enqueue(runner, signal),\n runTui: async (...args) => {\n const { runTuiQuestionnaire } = await import('../tui/runQuestionnaire.js');\n return runTuiQuestionnaire(...args);\n },\n tryVoice: (params) => voiceHandoff?.handoff(params),\n });\n registerAskUserQuestionReconciler(pi);\n\n pi.on('session_start', (_event, context) => {\n coordinator.shutdown();\n coordinator = new QuestionnaireCoordinator();\n resetVoiceHandoff();\n\n const sessionId = context.sessionManager.getSessionId();\n const registry = new DoomModeStatusRegistry();\n const runtime = createProtocolRuntime({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId,\n });\n modes = registry;\n disposeModes = registry.bind(runtime);\n voiceHandoff = new VoiceQuestionHandoff(\n registry,\n createNarrationRequester({ bus: pi.events, source: PACKAGE_SOURCE, sessionId }),\n buildVoiceQuestionNarration,\n );\n });\n pi.on('session_shutdown', () => {\n coordinator.shutdown();\n resetVoiceHandoff();\n });\n}\n\nexport { registerUserFeedbackExtension as default };\n"],"mappings":"grBAUA,MAAM,EAAiB,kCACjB,EAAoB,OAAO,IAAI,GAAG,EAAe,iBAAiB,EAMxE,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAA8B,EAAwB,CACpE,GAAI,CAAC,EAAsB,CAAE,EAAG,OAEhC,IAAI,EAAc,IAAI,EAClB,EACA,EACA,EACE,MAAgC,CACpC,IAAe,EACf,EAAe,IAAA,GACf,GAAO,MAAM,EACb,EAAQ,IAAA,GACR,EAAe,IAAA,EACjB,EAEA,EAA4B,EAAI,CAC9B,SAAU,EAAQ,IAAW,EAAY,QAAQ,EAAQ,CAAM,EAC/D,OAAQ,MAAO,GAAG,IAAS,CACzB,GAAM,CAAE,uBAAwB,MAAM,OAAO,+BAC7C,OAAO,EAAoB,GAAG,CAAI,CACpC,EACA,SAAW,GAAW,GAAc,QAAQ,CAAM,CACpD,CAAC,EACD,EAAkC,CAAE,EAEpC,EAAG,GAAG,iBAAkB,EAAQ,IAAY,CAC1C,EAAY,SAAS,EACrB,EAAc,IAAI,EAClB,EAAkB,EAElB,IAAM,EAAY,EAAQ,eAAe,aAAa,EAChD,EAAW,IAAI,EACf,EAAU,EAAsB,CACpC,IAAK,EAAG,OACR,OAAQ,EACR,WACF,CAAC,EACD,EAAQ,EACR,EAAe,EAAS,KAAK,CAAO,EACpC,EAAe,IAAI,EACjB,EACA,EAAyB,CAAE,IAAK,EAAG,OAAQ,OAAQ,EAAgB,WAAU,CAAC,EAC9E,CACF,CACF,CAAC,EACD,EAAG,GAAG,uBAA0B,CAC9B,EAAY,SAAS,EACrB,EAAkB,CACpB,CAAC,CACH"}
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require("./extensions/pi.cjs");require("./events.cjs");let t=require("@agimon-ai/doompi-extension-contracts");Object.defineProperty(exports,"ASK_USER_BLOCKED_EVENT",{enumerable:!0,get:function(){return t.ASK_USER_BLOCKED_EVENT}}),Object.defineProperty(exports,"ASK_USER_PROMPT_EVENT",{enumerable:!0,get:function(){return t.ASK_USER_PROMPT_EVENT}}),exports.default=e.default,exports.registerUserFeedbackExtension=e.default;
@@ -0,0 +1,4 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion } from "./schemas/events.cjs";
2
+ import "./events.cjs";
3
+ import registerUserFeedbackExtension from "./extensions/pi.cjs";
4
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, type AskUserBlockedEventPayload, type AskUserPromptEventPayload, type AskUserPromptOption, type AskUserPromptQuestion, registerUserFeedbackExtension as default, registerUserFeedbackExtension };
@@ -0,0 +1,4 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion } from "./schemas/events.mjs";
2
+ import "./events.mjs";
3
+ import registerUserFeedbackExtension from "./extensions/pi.mjs";
4
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, type AskUserBlockedEventPayload, type AskUserPromptEventPayload, type AskUserPromptOption, type AskUserPromptQuestion, registerUserFeedbackExtension as default, registerUserFeedbackExtension };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ import e from"./extensions/pi.mjs";import{ASK_USER_BLOCKED_EVENT as t,ASK_USER_PROMPT_EVENT as n}from"./schemas/events.mjs";import"./events.mjs";export{t as ASK_USER_BLOCKED_EVENT,n as ASK_USER_PROMPT_EVENT,e as default,e as registerUserFeedbackExtension};
@@ -0,0 +1 @@
1
+ require("@agimon-ai/doompi-extension-contracts");
@@ -0,0 +1,22 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT } from "@agimon-ai/doompi-extension-contracts";
2
+ //#region src/schemas/events.d.ts
3
+ interface AskUserPromptOption {
4
+ label: string;
5
+ description: string;
6
+ hasPreview: boolean;
7
+ }
8
+ interface AskUserPromptQuestion {
9
+ question: string;
10
+ header: string;
11
+ multiSelect: boolean;
12
+ options: AskUserPromptOption[];
13
+ }
14
+ interface AskUserPromptEventPayload {
15
+ questions: AskUserPromptQuestion[];
16
+ }
17
+ interface AskUserBlockedEventPayload {
18
+ active: boolean;
19
+ }
20
+ //#endregion
21
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion };
22
+ //# sourceMappingURL=events.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.cts","names":[],"sources":["../../src/schemas/events.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA,SAAS;;UAGM;EACf,WAAW;;UAGI;EACf"}
@@ -0,0 +1,22 @@
1
+ import { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT } from "@agimon-ai/doompi-extension-contracts";
2
+ //#region src/schemas/events.d.ts
3
+ interface AskUserPromptOption {
4
+ label: string;
5
+ description: string;
6
+ hasPreview: boolean;
7
+ }
8
+ interface AskUserPromptQuestion {
9
+ question: string;
10
+ header: string;
11
+ multiSelect: boolean;
12
+ options: AskUserPromptOption[];
13
+ }
14
+ interface AskUserPromptEventPayload {
15
+ questions: AskUserPromptQuestion[];
16
+ }
17
+ interface AskUserBlockedEventPayload {
18
+ active: boolean;
19
+ }
20
+ //#endregion
21
+ export { ASK_USER_BLOCKED_EVENT, ASK_USER_PROMPT_EVENT, AskUserBlockedEventPayload, AskUserPromptEventPayload, AskUserPromptOption, AskUserPromptQuestion };
22
+ //# sourceMappingURL=events.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.mts","names":[],"sources":["../../src/schemas/events.ts"],"mappings":";;UAEiB;EACf;EACA;EACA;;UAGe;EACf;EACA;EACA;EACA,SAAS;;UAGM;EACf,WAAW;;UAGI;EACf"}
@@ -0,0 +1 @@
1
+ import{ASK_USER_BLOCKED_EVENT as e,ASK_USER_PROMPT_EVENT as t}from"@agimon-ai/doompi-extension-contracts";export{e as ASK_USER_BLOCKED_EVENT,t as ASK_USER_PROMPT_EVENT};
@@ -0,0 +1,2 @@
1
+ let e=require("typebox");const t=[`Other`,`Type something.`,`Next`],n=e.Type.Object({label:e.Type.String({maxLength:60,description:`MAX 60 CHARACTERS — hard limit. Concise option label shown to the user, ideally 1-5 words.`}),description:e.Type.String({description:`Explanation of what the option means and its important trade-offs.`}),preview:e.Type.Optional(e.Type.String({description:`Optional markdown preview rendered while this option is focused.`}))},{additionalProperties:!1}),r=e.Type.Object({question:e.Type.String({description:`The complete, clear question to ask the user.`}),header:e.Type.String({maxLength:16,description:`MAX 16 CHARACTERS — short label used when navigating several questions.`}),options:e.Type.Array(n,{minItems:2,maxItems:4,description:`The 2-4 choices offered for this question.`}),multiSelect:e.Type.Optional(e.Type.Boolean({description:`Allow more than one option to be selected.`,default:!1}))},{additionalProperties:!1}),i=e.Type.Object({questions:e.Type.Array(r,{minItems:1,maxItems:4,description:`Questions to ask the user (1-4 questions).`})},{additionalProperties:!1});exports.OptionSchema=n,exports.QuestionParamsSchema=i,exports.QuestionSchema=r,exports.RESERVED_LABELS=t;
2
+ //# sourceMappingURL=questionnaire.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.cjs","names":["Type"],"sources":["../../src/schemas/questionnaire.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport 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\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"],"mappings":"yBAQA,MAAa,EAAkB,CAAC,QAAS,kBAAmB,MAAM,EAErD,EAAeA,EAAAA,KAAK,OAC/B,CACE,MAAOA,EAAAA,KAAK,OAAO,CACjB,UAAA,GACA,YAAa,4FACf,CAAC,EACD,YAAaA,EAAAA,KAAK,OAAO,CACvB,YAAa,oEACf,CAAC,EACD,QAASA,EAAAA,KAAK,SACZA,EAAAA,KAAK,OAAO,CAAE,YAAa,kEAAmE,CAAC,CACjG,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EAEa,EAAiBA,EAAAA,KAAK,OACjC,CACE,SAAUA,EAAAA,KAAK,OAAO,CAAE,YAAa,+CAAgD,CAAC,EACtF,OAAQA,EAAAA,KAAK,OAAO,CAClB,UAAA,GACA,YAAa,yEACf,CAAC,EACD,QAASA,EAAAA,KAAK,MAAM,EAAc,CAChC,SAAA,EACA,SAAA,EACA,YAAa,4CACf,CAAC,EACD,YAAaA,EAAAA,KAAK,SAChBA,EAAAA,KAAK,QAAQ,CAAE,YAAa,6CAA8C,QAAS,EAAM,CAAC,CAC5F,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EAEa,EAAuBA,EAAAA,KAAK,OACvC,CACE,UAAWA,EAAAA,KAAK,MAAM,EAAgB,CACpC,SAAU,EACV,SAAA,EACA,YAAa,4CACf,CAAC,CACH,EACA,CAAE,qBAAsB,EAAM,CAChC"}
@@ -0,0 +1,2 @@
1
+ import{Type as e}from"typebox";const t=[`Other`,`Type something.`,`Next`],n=e.Object({label:e.String({maxLength:60,description:`MAX 60 CHARACTERS — hard limit. Concise option label shown to the user, ideally 1-5 words.`}),description:e.String({description:`Explanation of what the option means and its important trade-offs.`}),preview:e.Optional(e.String({description:`Optional markdown preview rendered while this option is focused.`}))},{additionalProperties:!1}),r=e.Object({question:e.String({description:`The complete, clear question to ask the user.`}),header:e.String({maxLength:16,description:`MAX 16 CHARACTERS — short label used when navigating several questions.`}),options:e.Array(n,{minItems:2,maxItems:4,description:`The 2-4 choices offered for this question.`}),multiSelect:e.Optional(e.Boolean({description:`Allow more than one option to be selected.`,default:!1}))},{additionalProperties:!1}),i=e.Object({questions:e.Array(r,{minItems:1,maxItems:4,description:`Questions to ask the user (1-4 questions).`})},{additionalProperties:!1});export{n as OptionSchema,i as QuestionParamsSchema,r as QuestionSchema,t as RESERVED_LABELS};
2
+ //# sourceMappingURL=questionnaire.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.mjs","names":[],"sources":["../../src/schemas/questionnaire.ts"],"sourcesContent":["import { type Static, Type } from 'typebox';\n\nexport 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\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"],"mappings":"+BAQA,MAAa,EAAkB,CAAC,QAAS,kBAAmB,MAAM,EAErD,EAAe,EAAK,OAC/B,CACE,MAAO,EAAK,OAAO,CACjB,UAAA,GACA,YAAa,4FACf,CAAC,EACD,YAAa,EAAK,OAAO,CACvB,YAAa,oEACf,CAAC,EACD,QAAS,EAAK,SACZ,EAAK,OAAO,CAAE,YAAa,kEAAmE,CAAC,CACjG,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EAEa,EAAiB,EAAK,OACjC,CACE,SAAU,EAAK,OAAO,CAAE,YAAa,+CAAgD,CAAC,EACtF,OAAQ,EAAK,OAAO,CAClB,UAAA,GACA,YAAa,yEACf,CAAC,EACD,QAAS,EAAK,MAAM,EAAc,CAChC,SAAA,EACA,SAAA,EACA,YAAa,4CACf,CAAC,EACD,YAAa,EAAK,SAChB,EAAK,QAAQ,CAAE,YAAa,6CAA8C,QAAS,EAAM,CAAC,CAC5F,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EAEa,EAAuB,EAAK,OACvC,CACE,UAAW,EAAK,MAAM,EAAgB,CACpC,SAAU,EACV,SAAA,EACA,YAAa,4CACf,CAAC,CACH,EACA,CAAE,qBAAsB,EAAM,CAChC"}
@@ -0,0 +1,2 @@
1
+ function e(e){return{answers:e.latestAnswers.map(e=>({...e})),cancelled:!0}}var t=class{pending=[];active;nextRequestId=1;accepting=!0;enqueue(e,t){return!this.accepting||t?.aborted?Promise.resolve({answers:[],cancelled:!0}):new Promise((n,r)=>{let i={id:this.nextRequestId,runner:e,controller:new AbortController,...t?{externalSignal:t}:{},latestAnswers:[],state:`queued`,resolve:n,reject:r};this.nextRequestId+=1;let a=()=>this.cancel(i);t?.addEventListener(`abort`,a,{once:!0}),i.removeAbortListener=()=>t?.removeEventListener(`abort`,a),this.pending.push(i),this.startNext()})}shutdown(){if(!(!this.accepting&&!this.active&&this.pending.length===0))for(this.accepting=!1,this.active&&this.cancel(this.active);this.pending.length>0;){let e=this.pending[0];if(!e)break;this.cancel(e)}}startNext(){if(!this.accepting||this.active)return;let e=this.pending.shift();e&&(e.state=`active`,this.active=e,e.runner({signal:e.controller.signal,reportProgress:t=>{this.active?.id===e.id&&e.state===`active`&&(e.latestAnswers=t.answers.map(e=>({...e})))}}).then(t=>this.complete(e,t),t=>this.fail(e,t)))}complete(e,t){this.active?.id===e.id&&e.state===`active`&&(this.releaseActive(e),e.resolve(t),this.startNext())}fail(e,t){this.active?.id===e.id&&e.state===`active`&&(this.releaseActive(e),e.reject(t),this.startNext())}cancel(t){if(t.state===`settled`)return;if(t.state===`queued`){let n=this.pending.indexOf(t);this.pending.splice(n,1),t.state=`settled`,t.removeAbortListener?.(),t.controller.abort(),t.resolve(e(t));return}let n=e(t);this.releaseActive(t),t.controller.abort(),t.resolve(n),this.startNext()}releaseActive(e){e.state=`settled`,e.removeAbortListener?.(),this.active=void 0}};exports.QuestionnaireCoordinator=t;
2
+ //# sourceMappingURL=questionnaireCoordinator.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaireCoordinator.cjs","names":[],"sources":["../../src/services/questionnaireCoordinator.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../types/questionnaire.js';\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 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 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 void entry.runner({ signal: entry.controller.signal, reportProgress }).then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n );\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"],"mappings":"AAqBA,SAAS,EAAgB,EAAwC,CAC/D,MAAO,CACL,QAAS,EAAM,cAAc,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EAC5D,UAAW,EACb,CACF,CAEA,IAAa,EAAb,KAAsC,CACpC,QAAyC,CAAC,EAC1C,OACA,cAAwB,EACxB,UAAoB,GAEpB,QAAQ,EAA6B,EAA4D,CAK/F,MAJI,CAAC,KAAK,WAAa,GAAgB,QAC9B,QAAQ,QAAQ,CAAE,QAAS,CAAC,EAAG,UAAW,EAAK,CAAC,EAGlD,IAAI,SAA8B,EAAS,IAAW,CAC3D,IAAM,EAAoB,CACxB,GAAI,KAAK,cACT,SACA,WAAY,IAAI,gBAChB,GAAI,EAAiB,CAAE,gBAAe,EAAI,CAAC,EAC3C,cAAe,CAAC,EAChB,MAAO,SACP,UACA,QACF,EACA,KAAK,eAAiB,EACtB,IAAM,MAAoB,KAAK,OAAO,CAAK,EAC3C,GAAgB,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EAC/D,EAAM,wBAA4B,GAAgB,oBAAoB,QAAS,CAAK,EACpF,KAAK,QAAQ,KAAK,CAAK,EACvB,KAAK,UAAU,CACjB,CAAC,CACH,CAEA,UAAiB,CACX,MAAC,KAAK,WAAa,CAAC,KAAK,QAAU,KAAK,QAAQ,SAAW,GAG/D,IAFA,KAAK,UAAY,GACb,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM,EACjC,KAAK,QAAQ,OAAS,GAAG,CAC9B,IAAM,EAAQ,KAAK,QAAQ,GAC3B,GAAI,CAAC,EAAO,MACZ,KAAK,OAAO,CAAK,CACnB,CACF,CAEA,WAA0B,CACxB,GAAI,CAAC,KAAK,WAAa,KAAK,OAAQ,OACpC,IAAM,EAAQ,KAAK,QAAQ,MAAM,EAC5B,IACL,EAAM,MAAQ,SACd,KAAK,OAAS,EAKd,EAAW,OAAO,CAAE,OAAQ,EAAM,WAAW,OAAQ,eAJ7B,GAAsC,CACxD,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,EAAM,cAAgB,EAAO,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EACtE,CACoE,CAAC,CAAC,CAAC,KACpE,GAAW,KAAK,SAAS,EAAO,CAAM,EACtC,GAAmB,KAAK,KAAK,EAAO,CAAK,CAC5C,EACF,CAEA,SAAiB,EAAmB,EAAmC,CACjE,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,KAAK,cAAc,CAAK,EACxB,EAAM,QAAQ,CAAM,EACpB,KAAK,UAAU,EACjB,CAEA,KAAa,EAAmB,EAAsB,CAChD,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,KAAK,cAAc,CAAK,EACxB,EAAM,OAAO,CAAK,EAClB,KAAK,UAAU,EACjB,CAEA,OAAe,EAAyB,CACtC,GAAI,EAAM,QAAU,UAAW,OAC/B,GAAI,EAAM,QAAU,SAAU,CAC5B,IAAM,EAAQ,KAAK,QAAQ,QAAQ,CAAK,EACxC,KAAK,QAAQ,OAAO,EAAO,CAAC,EAC5B,EAAM,MAAQ,UACd,EAAM,sBAAsB,EAC5B,EAAM,WAAW,MAAM,EACvB,EAAM,QAAQ,EAAgB,CAAK,CAAC,EACpC,MACF,CAEA,IAAM,EAAS,EAAgB,CAAK,EACpC,KAAK,cAAc,CAAK,EACxB,EAAM,WAAW,MAAM,EACvB,EAAM,QAAQ,CAAM,EACpB,KAAK,UAAU,CACjB,CAEA,cAAsB,EAAyB,CAC7C,EAAM,MAAQ,UACd,EAAM,sBAAsB,EAC5B,KAAK,OAAS,IAAA,EAChB,CACF"}
@@ -0,0 +1,2 @@
1
+ function e(e){return{answers:e.latestAnswers.map(e=>({...e})),cancelled:!0}}var t=class{pending=[];active;nextRequestId=1;accepting=!0;enqueue(e,t){return!this.accepting||t?.aborted?Promise.resolve({answers:[],cancelled:!0}):new Promise((n,r)=>{let i={id:this.nextRequestId,runner:e,controller:new AbortController,...t?{externalSignal:t}:{},latestAnswers:[],state:`queued`,resolve:n,reject:r};this.nextRequestId+=1;let a=()=>this.cancel(i);t?.addEventListener(`abort`,a,{once:!0}),i.removeAbortListener=()=>t?.removeEventListener(`abort`,a),this.pending.push(i),this.startNext()})}shutdown(){if(!(!this.accepting&&!this.active&&this.pending.length===0))for(this.accepting=!1,this.active&&this.cancel(this.active);this.pending.length>0;){let e=this.pending[0];if(!e)break;this.cancel(e)}}startNext(){if(!this.accepting||this.active)return;let e=this.pending.shift();e&&(e.state=`active`,this.active=e,e.runner({signal:e.controller.signal,reportProgress:t=>{this.active?.id===e.id&&e.state===`active`&&(e.latestAnswers=t.answers.map(e=>({...e})))}}).then(t=>this.complete(e,t),t=>this.fail(e,t)))}complete(e,t){this.active?.id===e.id&&e.state===`active`&&(this.releaseActive(e),e.resolve(t),this.startNext())}fail(e,t){this.active?.id===e.id&&e.state===`active`&&(this.releaseActive(e),e.reject(t),this.startNext())}cancel(t){if(t.state===`settled`)return;if(t.state===`queued`){let n=this.pending.indexOf(t);this.pending.splice(n,1),t.state=`settled`,t.removeAbortListener?.(),t.controller.abort(),t.resolve(e(t));return}let n=e(t);this.releaseActive(t),t.controller.abort(),t.resolve(n),this.startNext()}releaseActive(e){e.state=`settled`,e.removeAbortListener?.(),this.active=void 0}};export{t as QuestionnaireCoordinator};
2
+ //# sourceMappingURL=questionnaireCoordinator.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaireCoordinator.mjs","names":[],"sources":["../../src/services/questionnaireCoordinator.ts"],"sourcesContent":["import type { QuestionnaireResult } from '../types/questionnaire.js';\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 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 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 void entry.runner({ signal: entry.controller.signal, reportProgress }).then(\n (result) => this.complete(entry, result),\n (error: unknown) => this.fail(entry, error),\n );\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"],"mappings":"AAqBA,SAAS,EAAgB,EAAwC,CAC/D,MAAO,CACL,QAAS,EAAM,cAAc,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EAC5D,UAAW,EACb,CACF,CAEA,IAAa,EAAb,KAAsC,CACpC,QAAyC,CAAC,EAC1C,OACA,cAAwB,EACxB,UAAoB,GAEpB,QAAQ,EAA6B,EAA4D,CAK/F,MAJI,CAAC,KAAK,WAAa,GAAgB,QAC9B,QAAQ,QAAQ,CAAE,QAAS,CAAC,EAAG,UAAW,EAAK,CAAC,EAGlD,IAAI,SAA8B,EAAS,IAAW,CAC3D,IAAM,EAAoB,CACxB,GAAI,KAAK,cACT,SACA,WAAY,IAAI,gBAChB,GAAI,EAAiB,CAAE,gBAAe,EAAI,CAAC,EAC3C,cAAe,CAAC,EAChB,MAAO,SACP,UACA,QACF,EACA,KAAK,eAAiB,EACtB,IAAM,MAAoB,KAAK,OAAO,CAAK,EAC3C,GAAgB,iBAAiB,QAAS,EAAO,CAAE,KAAM,EAAK,CAAC,EAC/D,EAAM,wBAA4B,GAAgB,oBAAoB,QAAS,CAAK,EACpF,KAAK,QAAQ,KAAK,CAAK,EACvB,KAAK,UAAU,CACjB,CAAC,CACH,CAEA,UAAiB,CACX,MAAC,KAAK,WAAa,CAAC,KAAK,QAAU,KAAK,QAAQ,SAAW,GAG/D,IAFA,KAAK,UAAY,GACb,KAAK,QAAQ,KAAK,OAAO,KAAK,MAAM,EACjC,KAAK,QAAQ,OAAS,GAAG,CAC9B,IAAM,EAAQ,KAAK,QAAQ,GAC3B,GAAI,CAAC,EAAO,MACZ,KAAK,OAAO,CAAK,CACnB,CACF,CAEA,WAA0B,CACxB,GAAI,CAAC,KAAK,WAAa,KAAK,OAAQ,OACpC,IAAM,EAAQ,KAAK,QAAQ,MAAM,EAC5B,IACL,EAAM,MAAQ,SACd,KAAK,OAAS,EAKd,EAAW,OAAO,CAAE,OAAQ,EAAM,WAAW,OAAQ,eAJ7B,GAAsC,CACxD,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,EAAM,cAAgB,EAAO,QAAQ,IAAK,IAAY,CAAE,GAAG,CAAO,EAAE,EACtE,CACoE,CAAC,CAAC,CAAC,KACpE,GAAW,KAAK,SAAS,EAAO,CAAM,EACtC,GAAmB,KAAK,KAAK,EAAO,CAAK,CAC5C,EACF,CAEA,SAAiB,EAAmB,EAAmC,CACjE,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,KAAK,cAAc,CAAK,EACxB,EAAM,QAAQ,CAAM,EACpB,KAAK,UAAU,EACjB,CAEA,KAAa,EAAmB,EAAsB,CAChD,KAAK,QAAQ,KAAO,EAAM,IAAM,EAAM,QAAU,WACpD,KAAK,cAAc,CAAK,EACxB,EAAM,OAAO,CAAK,EAClB,KAAK,UAAU,EACjB,CAEA,OAAe,EAAyB,CACtC,GAAI,EAAM,QAAU,UAAW,OAC/B,GAAI,EAAM,QAAU,SAAU,CAC5B,IAAM,EAAQ,KAAK,QAAQ,QAAQ,CAAK,EACxC,KAAK,QAAQ,OAAO,EAAO,CAAC,EAC5B,EAAM,MAAQ,UACd,EAAM,sBAAsB,EAC5B,EAAM,WAAW,MAAM,EACvB,EAAM,QAAQ,EAAgB,CAAK,CAAC,EACpC,MACF,CAEA,IAAM,EAAS,EAAgB,CAAK,EACpC,KAAK,cAAc,CAAK,EACxB,EAAM,WAAW,MAAM,EACvB,EAAM,QAAQ,CAAM,EACpB,KAAK,UAAU,CACjB,CAEA,cAAsB,EAAyB,CAC7C,EAAM,MAAQ,UACd,EAAM,sBAAsB,EAC5B,KAAK,OAAS,IAAA,EAChB,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=`User declined to answer questions`,t=`User has answered your questions:`,n=`You can now continue with the user's answers in mind.`,r=`(no input)`;function i(e,t){return{content:[{type:`text`,text:e}],details:t}}function a(e){return e.kind===`multi`?e.selected&&e.selected.length>0?e.selected.join(`, `):r:e.answer&&e.answer.length>0?e.answer:r}function o(e){let t=[`"${e.question}"="${a(e)}"`];return e.preview&&t.push(`selected preview: ${e.preview}`),e.notes&&t.push(`user notes: ${e.notes}`),`${t.join(`. `)}.`}function s(r,a){if(!r||r.cancelled)return i(e,{answers:r?.answers??[],cancelled:!0});let s=a.questions.flatMap((e,t)=>{let n=r.answers.find(e=>e.questionIndex===t);return n?[o(n)]:[]});return s.length===0?i(e,{answers:r.answers,cancelled:!0}):i(`${t} ${s.join(` `)} ${n}`,r)}exports.DECLINE_MESSAGE=e,exports.ENVELOPE_PREFIX=t,exports.ENVELOPE_SUFFIX=n,exports.NO_INPUT_PLACEHOLDER=r,exports.buildAnswerSegment=o,exports.buildQuestionnaireResponse=s,exports.buildToolResult=i,exports.formatAnswer=a;
2
+ //# sourceMappingURL=responseService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseService.cjs","names":[],"sources":["../../src/services/responseService.ts"],"sourcesContent":["import type { QuestionParams } from '../schemas/questionnaire.js';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../types/questionnaire.js';\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"],"mappings":"AAGA,MAAa,EAAkB,oCAClB,EAAkB,oCAClB,EAAkB,wDAClB,EAAuB,aAEpC,SAAgB,EAAgB,EAAc,EAA8C,CAC1F,MAAO,CAAE,QAAS,CAAC,CAAE,KAAM,OAAQ,MAAK,CAAC,EAAG,SAAQ,CACtD,CAEA,SAAgB,EAAa,EAAgC,CAI3D,OAHI,EAAO,OAAS,QACX,EAAO,UAAY,EAAO,SAAS,OAAS,EAAI,EAAO,SAAS,KAAK,IAAI,EAAI,EAE/E,EAAO,QAAU,EAAO,OAAO,OAAS,EAAI,EAAO,OAAS,CACrE,CAEA,SAAgB,EAAmB,EAAgC,CACjE,IAAM,EAAW,CAAC,IAAI,EAAO,SAAS,KAAK,EAAa,CAAM,EAAE,EAAE,EAGlE,OAFI,EAAO,SAAS,EAAS,KAAK,qBAAqB,EAAO,SAAS,EACnE,EAAO,OAAO,EAAS,KAAK,eAAe,EAAO,OAAO,EACtD,GAAG,EAAS,KAAK,IAAI,EAAE,EAChC,CAEA,SAAgB,EACd,EACA,EACgB,CAChB,GAAI,CAAC,GAAU,EAAO,UACpB,OAAO,EAAgB,EAAiB,CAAE,QAAS,GAAQ,SAAW,CAAC,EAAG,UAAW,EAAK,CAAC,EAG7F,IAAM,EAAW,EAAO,UAAU,SAAS,EAAW,IAAU,CAC9D,IAAM,EAAS,EAAO,QAAQ,KAAM,GAAc,EAAU,gBAAkB,CAAK,EACnF,OAAO,EAAS,CAAC,EAAmB,CAAM,CAAC,EAAI,CAAC,CAClD,CAAC,EAKD,OAJI,EAAS,SAAW,EACf,EAAgB,EAAiB,CAAE,QAAS,EAAO,QAAS,UAAW,EAAK,CAAC,EAG/E,EAAgB,GAAG,EAAgB,GAAG,EAAS,KAAK,GAAG,EAAE,GAAG,IAAmB,CAAM,CAC9F"}
@@ -0,0 +1,2 @@
1
+ const e=`User declined to answer questions`,t=`User has answered your questions:`,n=`You can now continue with the user's answers in mind.`,r=`(no input)`;function i(e,t){return{content:[{type:`text`,text:e}],details:t}}function a(e){return e.kind===`multi`?e.selected&&e.selected.length>0?e.selected.join(`, `):r:e.answer&&e.answer.length>0?e.answer:r}function o(e){let t=[`"${e.question}"="${a(e)}"`];return e.preview&&t.push(`selected preview: ${e.preview}`),e.notes&&t.push(`user notes: ${e.notes}`),`${t.join(`. `)}.`}function s(r,a){if(!r||r.cancelled)return i(e,{answers:r?.answers??[],cancelled:!0});let s=a.questions.flatMap((e,t)=>{let n=r.answers.find(e=>e.questionIndex===t);return n?[o(n)]:[]});return s.length===0?i(e,{answers:r.answers,cancelled:!0}):i(`${t} ${s.join(` `)} ${n}`,r)}export{e as DECLINE_MESSAGE,t as ENVELOPE_PREFIX,n as ENVELOPE_SUFFIX,r as NO_INPUT_PLACEHOLDER,o as buildAnswerSegment,s as buildQuestionnaireResponse,i as buildToolResult,a as formatAnswer};
2
+ //# sourceMappingURL=responseService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"responseService.mjs","names":[],"sources":["../../src/services/responseService.ts"],"sourcesContent":["import type { QuestionParams } from '../schemas/questionnaire.js';\nimport type { QuestionAnswer, QuestionnaireResult, ToolTextResult } from '../types/questionnaire.js';\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"],"mappings":"AAGA,MAAa,EAAkB,oCAClB,EAAkB,oCAClB,EAAkB,wDAClB,EAAuB,aAEpC,SAAgB,EAAgB,EAAc,EAA8C,CAC1F,MAAO,CAAE,QAAS,CAAC,CAAE,KAAM,OAAQ,MAAK,CAAC,EAAG,SAAQ,CACtD,CAEA,SAAgB,EAAa,EAAgC,CAI3D,OAHI,EAAO,OAAS,QACX,EAAO,UAAY,EAAO,SAAS,OAAS,EAAI,EAAO,SAAS,KAAK,IAAI,EAAI,EAE/E,EAAO,QAAU,EAAO,OAAO,OAAS,EAAI,EAAO,OAAS,CACrE,CAEA,SAAgB,EAAmB,EAAgC,CACjE,IAAM,EAAW,CAAC,IAAI,EAAO,SAAS,KAAK,EAAa,CAAM,EAAE,EAAE,EAGlE,OAFI,EAAO,SAAS,EAAS,KAAK,qBAAqB,EAAO,SAAS,EACnE,EAAO,OAAO,EAAS,KAAK,eAAe,EAAO,OAAO,EACtD,GAAG,EAAS,KAAK,IAAI,EAAE,EAChC,CAEA,SAAgB,EACd,EACA,EACgB,CAChB,GAAI,CAAC,GAAU,EAAO,UACpB,OAAO,EAAgB,EAAiB,CAAE,QAAS,GAAQ,SAAW,CAAC,EAAG,UAAW,EAAK,CAAC,EAG7F,IAAM,EAAW,EAAO,UAAU,SAAS,EAAW,IAAU,CAC9D,IAAM,EAAS,EAAO,QAAQ,KAAM,GAAc,EAAU,gBAAkB,CAAK,EACnF,OAAO,EAAS,CAAC,EAAmB,CAAM,CAAC,EAAI,CAAC,CAClD,CAAC,EAKD,OAJI,EAAS,SAAW,EACf,EAAgB,EAAiB,CAAE,QAAS,EAAO,QAAS,UAAW,EAAK,CAAC,EAG/E,EAAgB,GAAG,EAAgB,GAAG,EAAS,KAAK,GAAG,EAAE,GAAG,IAAmB,CAAM,CAC9F"}
@@ -0,0 +1,2 @@
1
+ const e=require("../schemas/questionnaire.cjs"),t=`Error: At least one question is required`,n=`Error: At most 4 questions are allowed per invocation`,r=`Error: Question text must be unique within an invocation`,i=`Error: Each question requires at least 2 options`,a=`Error: Option label is reserved (${e.RESERVED_LABELS.join(`, `)})`,o=`Error: Option labels must be unique within a question`,s=new Set(e.RESERVED_LABELS);function c(e){if(e.questions.length===0)return{ok:!1,error:`no_questions`,message:t};if(e.questions.length>4)return{ok:!1,error:`too_many_questions`,message:n};let c=new Set;for(let t of e.questions){if(c.has(t.question))return{ok:!1,error:`duplicate_question`,message:r};c.add(t.question)}for(let t of e.questions){if(t.options.length<2)return{ok:!1,error:`empty_options`,message:i};let e=new Set;for(let n of t.options){if(s.has(n.label))return{ok:!1,error:`reserved_label`,message:a};if(e.has(n.label))return{ok:!1,error:`duplicate_option_label`,message:o};e.add(n.label)}}return{ok:!0}}exports.ERROR_DUPLICATE_OPTION_LABEL=o,exports.ERROR_DUPLICATE_QUESTION=r,exports.ERROR_NO_QUESTIONS=t,exports.ERROR_RESERVED_LABEL=a,exports.ERROR_TOO_FEW_OPTIONS=i,exports.ERROR_TOO_MANY_QUESTIONS=n,exports.validateQuestionnaire=c;
2
+ //# sourceMappingURL=validationService.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validationService.cjs","names":["RESERVED_LABELS"],"sources":["../../src/services/validationService.ts"],"sourcesContent":["import { MAX_QUESTIONS, MIN_OPTIONS, type QuestionParams, RESERVED_LABELS } from '../schemas/questionnaire.js';\nimport type { QuestionnaireError } from '../types/questionnaire.js';\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"],"mappings":"gDAGa,EAAqB,2CACrB,EAA2B,wDAC3B,EAA2B,2DAC3B,EAAwB,mDACxB,EAAuB,oCAAoCA,EAAAA,gBAAgB,KAAK,IAAI,EAAE,GACtF,EAA+B,wDAItC,EAAiB,IAAI,IAAYA,EAAAA,eAAe,EAEtD,SAAgB,EAAsB,EAA0C,CAC9E,GAAI,EAAO,UAAU,SAAW,EAC9B,MAAO,CAAE,GAAI,GAAO,MAAO,eAAgB,QAAS,CAAmB,EAEzE,GAAI,EAAO,UAAU,OAAA,EACnB,MAAO,CAAE,GAAI,GAAO,MAAO,qBAAsB,QAAS,CAAyB,EAGrF,IAAM,EAAgB,IAAI,IAC1B,IAAK,IAAM,KAAY,EAAO,UAAW,CACvC,GAAI,EAAc,IAAI,EAAS,QAAQ,EACrC,MAAO,CAAE,GAAI,GAAO,MAAO,qBAAsB,QAAS,CAAyB,EAErF,EAAc,IAAI,EAAS,QAAQ,CACrC,CAEA,IAAK,IAAM,KAAY,EAAO,UAAW,CACvC,GAAI,EAAS,QAAQ,OAAA,EACnB,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAiB,QAAS,CAAsB,EAE7E,IAAM,EAAa,IAAI,IACvB,IAAK,IAAM,KAAU,EAAS,QAAS,CACrC,GAAI,EAAe,IAAI,EAAO,KAAK,EACjC,MAAO,CAAE,GAAI,GAAO,MAAO,iBAAkB,QAAS,CAAqB,EAE7E,GAAI,EAAW,IAAI,EAAO,KAAK,EAC7B,MAAO,CACL,GAAI,GACJ,MAAO,yBACP,QAAS,CACX,EAEF,EAAW,IAAI,EAAO,KAAK,CAC7B,CACF,CAEA,MAAO,CAAE,GAAI,EAAK,CACpB"}
@@ -0,0 +1,2 @@
1
+ import{RESERVED_LABELS as e}from"../schemas/questionnaire.mjs";const t=`Error: At least one question is required`,n=`Error: At most 4 questions are allowed per invocation`,r=`Error: Question text must be unique within an invocation`,i=`Error: Each question requires at least 2 options`,a=`Error: Option label is reserved (${e.join(`, `)})`,o=`Error: Option labels must be unique within a question`,s=new Set(e);function c(e){if(e.questions.length===0)return{ok:!1,error:`no_questions`,message:t};if(e.questions.length>4)return{ok:!1,error:`too_many_questions`,message:n};let c=new Set;for(let t of e.questions){if(c.has(t.question))return{ok:!1,error:`duplicate_question`,message:r};c.add(t.question)}for(let t of e.questions){if(t.options.length<2)return{ok:!1,error:`empty_options`,message:i};let e=new Set;for(let n of t.options){if(s.has(n.label))return{ok:!1,error:`reserved_label`,message:a};if(e.has(n.label))return{ok:!1,error:`duplicate_option_label`,message:o};e.add(n.label)}}return{ok:!0}}export{o as ERROR_DUPLICATE_OPTION_LABEL,r as ERROR_DUPLICATE_QUESTION,t as ERROR_NO_QUESTIONS,a as ERROR_RESERVED_LABEL,i as ERROR_TOO_FEW_OPTIONS,n as ERROR_TOO_MANY_QUESTIONS,c as validateQuestionnaire};
2
+ //# sourceMappingURL=validationService.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validationService.mjs","names":[],"sources":["../../src/services/validationService.ts"],"sourcesContent":["import { MAX_QUESTIONS, MIN_OPTIONS, type QuestionParams, RESERVED_LABELS } from '../schemas/questionnaire.js';\nimport type { QuestionnaireError } from '../types/questionnaire.js';\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"],"mappings":"+DAGA,MAAa,EAAqB,2CACrB,EAA2B,wDAC3B,EAA2B,2DAC3B,EAAwB,mDACxB,EAAuB,oCAAoC,EAAgB,KAAK,IAAI,EAAE,GACtF,EAA+B,wDAItC,EAAiB,IAAI,IAAY,CAAe,EAEtD,SAAgB,EAAsB,EAA0C,CAC9E,GAAI,EAAO,UAAU,SAAW,EAC9B,MAAO,CAAE,GAAI,GAAO,MAAO,eAAgB,QAAS,CAAmB,EAEzE,GAAI,EAAO,UAAU,OAAA,EACnB,MAAO,CAAE,GAAI,GAAO,MAAO,qBAAsB,QAAS,CAAyB,EAGrF,IAAM,EAAgB,IAAI,IAC1B,IAAK,IAAM,KAAY,EAAO,UAAW,CACvC,GAAI,EAAc,IAAI,EAAS,QAAQ,EACrC,MAAO,CAAE,GAAI,GAAO,MAAO,qBAAsB,QAAS,CAAyB,EAErF,EAAc,IAAI,EAAS,QAAQ,CACrC,CAEA,IAAK,IAAM,KAAY,EAAO,UAAW,CACvC,GAAI,EAAS,QAAQ,OAAA,EACnB,MAAO,CAAE,GAAI,GAAO,MAAO,gBAAiB,QAAS,CAAsB,EAE7E,IAAM,EAAa,IAAI,IACvB,IAAK,IAAM,KAAU,EAAS,QAAS,CACrC,GAAI,EAAe,IAAI,EAAO,KAAK,EACjC,MAAO,CAAE,GAAI,GAAO,MAAO,iBAAkB,QAAS,CAAqB,EAE7E,GAAI,EAAW,IAAI,EAAO,KAAK,EAC7B,MAAO,CACL,GAAI,GACJ,MAAO,yBACP,QAAS,CACX,EAEF,EAAW,IAAI,EAAO,KAAK,CAC7B,CACF,CAEA,MAAO,CAAE,GAAI,EAAK,CACpB"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/narration");const t=/\/(?:Users|home|private|tmp|var)\/[^\s,;:]+/gu,n=/\b(?:sk|api|token|key)[-_][A-Za-z0-9_-]{12,}\b/giu,r=/`[^`]*`|<\/?[A-Za-z][^>]*>|\$\([^)]*\)|\{[^}]*\}/gu;function i(e,t){return Array.from(e).slice(0,t).join(``).trim()}function a(e,a){return i(Array.from(e,e=>{let t=e.codePointAt(0)??0;return t<32||t===127?` `:e}).join(``).replace(t,` `).replace(n,` `).replace(r,` `).replace(/\s+/gu,` `).trim(),a)}function o(e,t){let n=e.questions[t];if(!n)return``;let r=t+1,i=a(n.question,240)||`Question ${r} needs your choice`,o=n.options.map((e,t)=>{let n=a(e.label,80);return`${t+1}, ${n||`option ${t+1}`}`}),s=n.multiSelect?`You may choose more than one option.`:`Please choose one option.`;return`Question ${r}: ${i}. Options: ${o.join(`; `)}. ${s} You may also answer in your own words.`}function s(t){return a(t.questions.map((e,n)=>o(t,n)).join(` `),e.MAX_NARRATION_TEXT_CHARACTERS)||`I need your answer to the questions shown in the conversation.`}exports.buildVoiceQuestionNarration=s,exports.sanitizeVoiceQuestionText=a;
2
+ //# sourceMappingURL=voiceQuestionNarration.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionNarration.cjs","names":["MAX_NARRATION_TEXT_CHARACTERS"],"sources":["../../src/services/voiceQuestionNarration.ts"],"sourcesContent":["import { MAX_NARRATION_TEXT_CHARACTERS } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../schemas/questionnaire.js';\n\nconst MAX_QUESTION_CHARACTERS = 240;\nconst MAX_OPTION_CHARACTERS = 80;\nconst PATH_PATTERN = /\\/(?:Users|home|private|tmp|var)\\/[^\\s,;:]+/gu;\nconst TOKEN_PATTERN = /\\b(?:sk|api|token|key)[-_][A-Za-z0-9_-]{12,}\\b/giu;\nconst CODE_PATTERN = /`[^`]*`|<\\/?[A-Za-z][^>]*>|\\$\\([^)]*\\)|\\{[^}]*\\}/gu;\nconst SAFE_FALLBACK = 'I need your answer to the questions shown in the conversation.';\n\nfunction takeCharacters(text: string, maximum: number): string {\n return Array.from(text).slice(0, maximum).join('').trim();\n}\n\nexport function sanitizeVoiceQuestionText(text: string, maximum: number): string {\n const withoutControls = Array.from(text, (character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint < 32 || codePoint === 127 ? ' ' : character;\n }).join('');\n return takeCharacters(\n withoutControls\n .replace(PATH_PATTERN, ' ')\n .replace(TOKEN_PATTERN, ' ')\n .replace(CODE_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim(),\n maximum,\n );\n}\n\nfunction spokenQuestion(params: QuestionParams, questionIndex: number): string {\n const question = params.questions[questionIndex];\n if (!question) return '';\n const position = questionIndex + 1;\n const prompt =\n sanitizeVoiceQuestionText(question.question, MAX_QUESTION_CHARACTERS) || `Question ${position} needs your choice`;\n const labels = question.options.map((option, optionIndex) => {\n const label = sanitizeVoiceQuestionText(option.label, MAX_OPTION_CHARACTERS);\n return `${optionIndex + 1}, ${label || `option ${optionIndex + 1}`}`;\n });\n const selection = question.multiSelect ? 'You may choose more than one option.' : 'Please choose one option.';\n return `Question ${position}: ${prompt}. Options: ${labels.join('; ')}. ${selection} You may also answer in your own words.`;\n}\n\nexport function buildVoiceQuestionNarration(params: QuestionParams): string {\n const questions = params.questions.map((_question, index) => spokenQuestion(params, index));\n const combined = sanitizeVoiceQuestionText(questions.join(' '), MAX_NARRATION_TEXT_CHARACTERS);\n return combined || SAFE_FALLBACK;\n}\n"],"mappings":"iEAGA,MAEM,EAAe,gDACf,EAAgB,oDAChB,EAAe,qDAGrB,SAAS,EAAe,EAAc,EAAyB,CAC7D,OAAO,MAAM,KAAK,CAAI,CAAC,CAAC,MAAM,EAAG,CAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAC1D,CAEA,SAAgB,EAA0B,EAAc,EAAyB,CAK/E,OAAO,EAJiB,MAAM,KAAK,EAAO,GAAc,CACtD,IAAM,EAAY,EAAU,YAAY,CAAC,GAAK,EAC9C,OAAO,EAAY,IAAM,IAAc,IAAM,IAAM,CACrD,CAAC,CAAC,CAAC,KAAK,EAEQ,CAAC,CACZ,QAAQ,EAAc,GAAG,CAAC,CAC1B,QAAQ,EAAe,GAAG,CAAC,CAC3B,QAAQ,EAAc,GAAG,CAAC,CAC1B,QAAQ,QAAS,GAAG,CAAC,CACrB,KAAK,EACR,CACF,CACF,CAEA,SAAS,EAAe,EAAwB,EAA+B,CAC7E,IAAM,EAAW,EAAO,UAAU,GAClC,GAAI,CAAC,EAAU,MAAO,GACtB,IAAM,EAAW,EAAgB,EAC3B,EACJ,EAA0B,EAAS,SAAU,GAAuB,GAAK,YAAY,EAAS,oBAC1F,EAAS,EAAS,QAAQ,KAAK,EAAQ,IAAgB,CAC3D,IAAM,EAAQ,EAA0B,EAAO,MAAO,EAAqB,EAC3E,MAAO,GAAG,EAAc,EAAE,IAAI,GAAS,UAAU,EAAc,KACjE,CAAC,EACK,EAAY,EAAS,YAAc,uCAAyC,4BAClF,MAAO,YAAY,EAAS,IAAI,EAAO,aAAa,EAAO,KAAK,IAAI,EAAE,IAAI,EAAU,wCACtF,CAEA,SAAgB,EAA4B,EAAgC,CAG1E,OADiB,EADC,EAAO,UAAU,KAAK,EAAW,IAAU,EAAe,EAAQ,CAAK,CACtC,CAAC,CAAC,KAAK,GAAG,EAAGA,EAAAA,6BAClD,GAAK,gEACrB"}
@@ -0,0 +1,2 @@
1
+ import{MAX_NARRATION_TEXT_CHARACTERS as e}from"@agimon-ai/doompi-extension-contracts/narration";const t=/\/(?:Users|home|private|tmp|var)\/[^\s,;:]+/gu,n=/\b(?:sk|api|token|key)[-_][A-Za-z0-9_-]{12,}\b/giu,r=/`[^`]*`|<\/?[A-Za-z][^>]*>|\$\([^)]*\)|\{[^}]*\}/gu;function i(e,t){return Array.from(e).slice(0,t).join(``).trim()}function a(e,a){return i(Array.from(e,e=>{let t=e.codePointAt(0)??0;return t<32||t===127?` `:e}).join(``).replace(t,` `).replace(n,` `).replace(r,` `).replace(/\s+/gu,` `).trim(),a)}function o(e,t){let n=e.questions[t];if(!n)return``;let r=t+1,i=a(n.question,240)||`Question ${r} needs your choice`,o=n.options.map((e,t)=>{let n=a(e.label,80);return`${t+1}, ${n||`option ${t+1}`}`}),s=n.multiSelect?`You may choose more than one option.`:`Please choose one option.`;return`Question ${r}: ${i}. Options: ${o.join(`; `)}. ${s} You may also answer in your own words.`}function s(t){return a(t.questions.map((e,n)=>o(t,n)).join(` `),e)||`I need your answer to the questions shown in the conversation.`}export{s as buildVoiceQuestionNarration,a as sanitizeVoiceQuestionText};
2
+ //# sourceMappingURL=voiceQuestionNarration.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionNarration.mjs","names":[],"sources":["../../src/services/voiceQuestionNarration.ts"],"sourcesContent":["import { MAX_NARRATION_TEXT_CHARACTERS } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../schemas/questionnaire.js';\n\nconst MAX_QUESTION_CHARACTERS = 240;\nconst MAX_OPTION_CHARACTERS = 80;\nconst PATH_PATTERN = /\\/(?:Users|home|private|tmp|var)\\/[^\\s,;:]+/gu;\nconst TOKEN_PATTERN = /\\b(?:sk|api|token|key)[-_][A-Za-z0-9_-]{12,}\\b/giu;\nconst CODE_PATTERN = /`[^`]*`|<\\/?[A-Za-z][^>]*>|\\$\\([^)]*\\)|\\{[^}]*\\}/gu;\nconst SAFE_FALLBACK = 'I need your answer to the questions shown in the conversation.';\n\nfunction takeCharacters(text: string, maximum: number): string {\n return Array.from(text).slice(0, maximum).join('').trim();\n}\n\nexport function sanitizeVoiceQuestionText(text: string, maximum: number): string {\n const withoutControls = Array.from(text, (character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint < 32 || codePoint === 127 ? ' ' : character;\n }).join('');\n return takeCharacters(\n withoutControls\n .replace(PATH_PATTERN, ' ')\n .replace(TOKEN_PATTERN, ' ')\n .replace(CODE_PATTERN, ' ')\n .replace(/\\s+/gu, ' ')\n .trim(),\n maximum,\n );\n}\n\nfunction spokenQuestion(params: QuestionParams, questionIndex: number): string {\n const question = params.questions[questionIndex];\n if (!question) return '';\n const position = questionIndex + 1;\n const prompt =\n sanitizeVoiceQuestionText(question.question, MAX_QUESTION_CHARACTERS) || `Question ${position} needs your choice`;\n const labels = question.options.map((option, optionIndex) => {\n const label = sanitizeVoiceQuestionText(option.label, MAX_OPTION_CHARACTERS);\n return `${optionIndex + 1}, ${label || `option ${optionIndex + 1}`}`;\n });\n const selection = question.multiSelect ? 'You may choose more than one option.' : 'Please choose one option.';\n return `Question ${position}: ${prompt}. Options: ${labels.join('; ')}. ${selection} You may also answer in your own words.`;\n}\n\nexport function buildVoiceQuestionNarration(params: QuestionParams): string {\n const questions = params.questions.map((_question, index) => spokenQuestion(params, index));\n const combined = sanitizeVoiceQuestionText(questions.join(' '), MAX_NARRATION_TEXT_CHARACTERS);\n return combined || SAFE_FALLBACK;\n}\n"],"mappings":"gGAGA,MAEM,EAAe,gDACf,EAAgB,oDAChB,EAAe,qDAGrB,SAAS,EAAe,EAAc,EAAyB,CAC7D,OAAO,MAAM,KAAK,CAAI,CAAC,CAAC,MAAM,EAAG,CAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAC1D,CAEA,SAAgB,EAA0B,EAAc,EAAyB,CAK/E,OAAO,EAJiB,MAAM,KAAK,EAAO,GAAc,CACtD,IAAM,EAAY,EAAU,YAAY,CAAC,GAAK,EAC9C,OAAO,EAAY,IAAM,IAAc,IAAM,IAAM,CACrD,CAAC,CAAC,CAAC,KAAK,EAEQ,CAAC,CACZ,QAAQ,EAAc,GAAG,CAAC,CAC1B,QAAQ,EAAe,GAAG,CAAC,CAC3B,QAAQ,EAAc,GAAG,CAAC,CAC1B,QAAQ,QAAS,GAAG,CAAC,CACrB,KAAK,EACR,CACF,CACF,CAEA,SAAS,EAAe,EAAwB,EAA+B,CAC7E,IAAM,EAAW,EAAO,UAAU,GAClC,GAAI,CAAC,EAAU,MAAO,GACtB,IAAM,EAAW,EAAgB,EAC3B,EACJ,EAA0B,EAAS,SAAU,GAAuB,GAAK,YAAY,EAAS,oBAC1F,EAAS,EAAS,QAAQ,KAAK,EAAQ,IAAgB,CAC3D,IAAM,EAAQ,EAA0B,EAAO,MAAO,EAAqB,EAC3E,MAAO,GAAG,EAAc,EAAE,IAAI,GAAS,UAAU,EAAc,KACjE,CAAC,EACK,EAAY,EAAS,YAAc,uCAAyC,4BAClF,MAAO,YAAY,EAAS,IAAI,EAAO,aAAa,EAAO,KAAK,IAAI,EAAE,IAAI,EAAU,wCACtF,CAEA,SAAgB,EAA4B,EAAgC,CAG1E,OADiB,EADC,EAAO,UAAU,KAAK,EAAW,IAAU,EAAe,EAAQ,CAAK,CACtC,CAAC,CAAC,KAAK,GAAG,EAAG,CAClD,GAAK,gEACrB"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:os");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:child_process"),i=require("node:fs/promises");const a=`utf8`;function o(){return process.env.VISUAL||process.env.EDITOR||(process.platform===`win32`?`notepad`:`nano`)}function s(e){return process.platform===`win32`?`"${e.replaceAll(`"`,`\\"`)}"`:`'${e.replaceAll(`'`,`'\\''`)}'`}function c(e,t){return new Promise(n=>{let i=(0,r.spawn)(`${e} ${s(t)}`,{shell:!0,stdio:`inherit`}),a=!1,o=e=>{a||(a=!0,n(e))};i.once(`error`,e=>o({status:`failed`,message:`External editor failed: ${e.message}`})),i.once(`exit`,(e,t)=>{o(e===0?void 0:{status:`failed`,message:`External editor exited with ${e===null?`signal ${String(t)}`:`code ${String(e)}`}`})})})}async function l(e){let r=await(0,i.mkdtemp)(n.default.join(t.default.tmpdir(),`doompi-user-feedback-`)),s=n.default.join(r,`response.md`);try{return await(0,i.writeFile)(s,e,a),await c(o(),s)||{status:`complete`,content:await(0,i.readFile)(s,a)}}catch(e){return{status:`failed`,message:`External editor failed: ${e instanceof Error?e.message:String(e)}`}}finally{await(0,i.rm)(r,{recursive:!0,force:!0})}}exports.editQuestionnaireText=l;
2
+ //# sourceMappingURL=externalEditor.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"externalEditor.cjs","names":["spawn","mkdtemp","path","os","writeFile","readFile","rm"],"sources":["../../src/tui/externalEditor.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { ExternalEditResult } from '../types/questionnaire.js';\n\nconst UTF8_ENCODING = 'utf8';\n\nfunction editorCommand(): string {\n return process.env.VISUAL || process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');\n}\n\nfunction quoteFileArgument(file: string): string {\n if (process.platform === 'win32') return `\"${file.replaceAll('\"', '\\\\\"')}\"`;\n return `'${file.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\nfunction runEditor(command: string, file: string): Promise<ExternalEditResult | undefined> {\n return new Promise((resolve) => {\n const child = spawn(`${command} ${quoteFileArgument(file)}`, { shell: true, stdio: 'inherit' });\n let settled = false;\n const finish = (result: ExternalEditResult | undefined): void => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n child.once('error', (error) => finish({ status: 'failed', message: `External editor failed: ${error.message}` }));\n child.once('exit', (code, signal) => {\n if (code === 0) finish(undefined);\n else {\n finish({\n status: 'failed',\n message: `External editor exited with ${code === null ? `signal ${String(signal)}` : `code ${String(code)}`}`,\n });\n }\n });\n });\n}\n\nexport async function editQuestionnaireText(content: string): Promise<ExternalEditResult> {\n const directory = await mkdtemp(path.join(os.tmpdir(), 'doompi-user-feedback-'));\n const file = path.join(directory, 'response.md');\n try {\n await writeFile(file, content, UTF8_ENCODING);\n const failure = await runEditor(editorCommand(), file);\n if (failure) return failure;\n return { status: 'complete', content: await readFile(file, UTF8_ENCODING) };\n } catch (error) {\n return {\n status: 'failed',\n message: `External editor failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await rm(directory, { recursive: true, force: true });\n }\n}\n"],"mappings":"6MAMA,MAAM,EAAgB,OAEtB,SAAS,GAAwB,CAC/B,OAAO,QAAQ,IAAI,QAAU,QAAQ,IAAI,SAAW,QAAQ,WAAa,QAAU,UAAY,OACjG,CAEA,SAAS,EAAkB,EAAsB,CAE/C,OADI,QAAQ,WAAa,QAAgB,IAAI,EAAK,WAAW,IAAK,KAAK,EAAE,GAClE,IAAI,EAAK,WAAW,IAAK,OAAO,EAAE,EAC3C,CAEA,SAAS,EAAU,EAAiB,EAAuD,CACzF,OAAO,IAAI,QAAS,GAAY,CAC9B,IAAM,GAAA,EAAQA,EAAAA,MAAAA,CAAM,GAAG,EAAQ,GAAG,EAAkB,CAAI,IAAK,CAAE,MAAO,GAAM,MAAO,SAAU,CAAC,EAC1F,EAAU,GACR,EAAU,GAAiD,CAC3D,IACJ,EAAU,GACV,EAAQ,CAAM,EAChB,EACA,EAAM,KAAK,QAAU,GAAU,EAAO,CAAE,OAAQ,SAAU,QAAS,2BAA2B,EAAM,SAAU,CAAC,CAAC,EAChH,EAAM,KAAK,QAAS,EAAM,IAAW,CACnB,EAAZ,IAAS,EAAU,IAAA,GAEd,CACL,OAAQ,SACR,QAAS,+BAA+B,IAAS,KAAO,UAAU,OAAO,CAAM,IAAM,QAAQ,OAAO,CAAI,KAC1G,CAAC,CAEL,CAAC,CACH,CAAC,CACH,CAEA,eAAsB,EAAsB,EAA8C,CACxF,IAAM,EAAY,MAAA,EAAMC,EAAAA,QAAAA,CAAQC,EAAAA,QAAK,KAAKC,EAAAA,QAAG,OAAO,EAAG,uBAAuB,CAAC,EACzE,EAAOD,EAAAA,QAAK,KAAK,EAAW,aAAa,EAC/C,GAAI,CAIF,OAHA,MAAA,EAAME,EAAAA,UAAAA,CAAU,EAAM,EAAS,CAAa,EAExC,MADkB,EAAU,EAAc,EAAG,CAAI,GAE9C,CAAE,OAAQ,WAAY,QAAS,MAAA,EAAMC,EAAAA,SAAAA,CAAS,EAAM,CAAa,CAAE,CAC5E,OAAS,EAAO,CACd,MAAO,CACL,OAAQ,SACR,QAAS,2BAA2B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAC3F,CACF,QAAU,CACR,MAAA,EAAMC,EAAAA,GAAAA,CAAG,EAAW,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACtD,CACF"}
@@ -0,0 +1,2 @@
1
+ import e from"node:os";import t from"node:path";import{spawn as n}from"node:child_process";import{mkdtemp as r,readFile as i,rm as a,writeFile as o}from"node:fs/promises";const s=`utf8`;function c(){return process.env.VISUAL||process.env.EDITOR||(process.platform===`win32`?`notepad`:`nano`)}function l(e){return process.platform===`win32`?`"${e.replaceAll(`"`,`\\"`)}"`:`'${e.replaceAll(`'`,`'\\''`)}'`}function u(e,t){return new Promise(r=>{let i=n(`${e} ${l(t)}`,{shell:!0,stdio:`inherit`}),a=!1,o=e=>{a||(a=!0,r(e))};i.once(`error`,e=>o({status:`failed`,message:`External editor failed: ${e.message}`})),i.once(`exit`,(e,t)=>{o(e===0?void 0:{status:`failed`,message:`External editor exited with ${e===null?`signal ${String(t)}`:`code ${String(e)}`}`})})})}async function d(n){let l=await r(t.join(e.tmpdir(),`doompi-user-feedback-`)),d=t.join(l,`response.md`);try{return await o(d,n,s),await u(c(),d)||{status:`complete`,content:await i(d,s)}}catch(e){return{status:`failed`,message:`External editor failed: ${e instanceof Error?e.message:String(e)}`}}finally{await a(l,{recursive:!0,force:!0})}}export{d as editQuestionnaireText};
2
+ //# sourceMappingURL=externalEditor.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"externalEditor.mjs","names":[],"sources":["../../src/tui/externalEditor.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';\nimport os from 'node:os';\nimport path from 'node:path';\nimport type { ExternalEditResult } from '../types/questionnaire.js';\n\nconst UTF8_ENCODING = 'utf8';\n\nfunction editorCommand(): string {\n return process.env.VISUAL || process.env.EDITOR || (process.platform === 'win32' ? 'notepad' : 'nano');\n}\n\nfunction quoteFileArgument(file: string): string {\n if (process.platform === 'win32') return `\"${file.replaceAll('\"', '\\\\\"')}\"`;\n return `'${file.replaceAll(\"'\", `'\\\\''`)}'`;\n}\n\nfunction runEditor(command: string, file: string): Promise<ExternalEditResult | undefined> {\n return new Promise((resolve) => {\n const child = spawn(`${command} ${quoteFileArgument(file)}`, { shell: true, stdio: 'inherit' });\n let settled = false;\n const finish = (result: ExternalEditResult | undefined): void => {\n if (settled) return;\n settled = true;\n resolve(result);\n };\n child.once('error', (error) => finish({ status: 'failed', message: `External editor failed: ${error.message}` }));\n child.once('exit', (code, signal) => {\n if (code === 0) finish(undefined);\n else {\n finish({\n status: 'failed',\n message: `External editor exited with ${code === null ? `signal ${String(signal)}` : `code ${String(code)}`}`,\n });\n }\n });\n });\n}\n\nexport async function editQuestionnaireText(content: string): Promise<ExternalEditResult> {\n const directory = await mkdtemp(path.join(os.tmpdir(), 'doompi-user-feedback-'));\n const file = path.join(directory, 'response.md');\n try {\n await writeFile(file, content, UTF8_ENCODING);\n const failure = await runEditor(editorCommand(), file);\n if (failure) return failure;\n return { status: 'complete', content: await readFile(file, UTF8_ENCODING) };\n } catch (error) {\n return {\n status: 'failed',\n message: `External editor failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n } finally {\n await rm(directory, { recursive: true, force: true });\n }\n}\n"],"mappings":"2KAMA,MAAM,EAAgB,OAEtB,SAAS,GAAwB,CAC/B,OAAO,QAAQ,IAAI,QAAU,QAAQ,IAAI,SAAW,QAAQ,WAAa,QAAU,UAAY,OACjG,CAEA,SAAS,EAAkB,EAAsB,CAE/C,OADI,QAAQ,WAAa,QAAgB,IAAI,EAAK,WAAW,IAAK,KAAK,EAAE,GAClE,IAAI,EAAK,WAAW,IAAK,OAAO,EAAE,EAC3C,CAEA,SAAS,EAAU,EAAiB,EAAuD,CACzF,OAAO,IAAI,QAAS,GAAY,CAC9B,IAAM,EAAQ,EAAM,GAAG,EAAQ,GAAG,EAAkB,CAAI,IAAK,CAAE,MAAO,GAAM,MAAO,SAAU,CAAC,EAC1F,EAAU,GACR,EAAU,GAAiD,CAC3D,IACJ,EAAU,GACV,EAAQ,CAAM,EAChB,EACA,EAAM,KAAK,QAAU,GAAU,EAAO,CAAE,OAAQ,SAAU,QAAS,2BAA2B,EAAM,SAAU,CAAC,CAAC,EAChH,EAAM,KAAK,QAAS,EAAM,IAAW,CACnB,EAAZ,IAAS,EAAU,IAAA,GAEd,CACL,OAAQ,SACR,QAAS,+BAA+B,IAAS,KAAO,UAAU,OAAO,CAAM,IAAM,QAAQ,OAAO,CAAI,KAC1G,CAAC,CAEL,CAAC,CACH,CAAC,CACH,CAEA,eAAsB,EAAsB,EAA8C,CACxF,IAAM,EAAY,MAAM,EAAQ,EAAK,KAAK,EAAG,OAAO,EAAG,uBAAuB,CAAC,EACzE,EAAO,EAAK,KAAK,EAAW,aAAa,EAC/C,GAAI,CAIF,OAHA,MAAM,EAAU,EAAM,EAAS,CAAa,EAExC,MADkB,EAAU,EAAc,EAAG,CAAI,GAE9C,CAAE,OAAQ,WAAY,QAAS,MAAM,EAAS,EAAM,CAAa,CAAE,CAC5E,OAAS,EAAO,CACd,MAAO,CACL,OAAQ,SACR,QAAS,2BAA2B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAC3F,CACF,QAAU,CACR,MAAM,EAAG,EAAW,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACtD,CACF"}
@@ -0,0 +1,2 @@
1
+ let e=require("@earendil-works/pi-tui");const t=`tui.select.cancel`;var n=class{options;answers=new Map;selectedOptions=new Map;notes=new Map;editor;currentQuestion=0;currentOption=0;editorMode;collapsed=!1;settled=!1;externalEditorError;cachedLines;cachedWidth;isFocused=!1;constructor(t){this.options=t,this.editor=new e.Editor(t.tui,{borderColor:e=>t.theme.fg(`accent`,e),selectList:{selectedPrefix:e=>t.theme.fg(`accent`,e),selectedText:e=>t.theme.fg(`accent`,e),description:e=>t.theme.fg(`muted`,e),scrollInfo:e=>t.theme.fg(`dim`,e),noMatch:e=>t.theme.fg(`warning`,e)}}),this.editor.onSubmit=e=>this.submitEditor(e)}get focused(){return this.isFocused}set focused(e){this.isFocused=e,this.editor.focused=e,this.invalidate()}invalidate(){this.cachedLines=void 0,this.cachedWidth=void 0}cancel(){this.finish(!0)}handleInput(n){if(!this.settled){if(this.options.collapseKey!==`off`&&(0,e.matchesKey)(n,this.options.collapseKey)){this.collapsed=!this.collapsed,this.refresh();return}if(!this.collapsed){if(this.editorMode){if(this.options.keybindings.matches(n,t)){this.editorMode=void 0,this.externalEditorError=void 0,this.editor.setText(``),this.refresh();return}if(this.options.keybindings.matches(n,`app.editor.external`)){this.openExternalEditor();return}this.editor.handleInput(n),this.refresh();return}if(this.options.keybindings.matches(n,t)){this.cancel();return}if((0,e.matchesKey)(n,e.Key.up)){this.moveOption(-1);return}if((0,e.matchesKey)(n,e.Key.down)){this.moveOption(1);return}if((0,e.matchesKey)(n,e.Key.tab)||(0,e.matchesKey)(n,e.Key.right)){this.moveQuestion(1);return}if((0,e.matchesKey)(n,e.Key.shift(`tab`))||(0,e.matchesKey)(n,e.Key.left)){this.moveQuestion(-1);return}if((0,e.matchesKey)(n,`n`)||(0,e.matchesKey)(n,e.Key.shift(`n`))){this.openEditor(`notes`);return}if((0,e.matchesKey)(n,e.Key.space)||n===` `){this.toggleCurrentOption();return}(0,e.matchesKey)(n,e.Key.enter)&&this.confirmCurrentSelection()}}}render(e){let t=Math.max(1,e);if(this.cachedLines&&this.cachedWidth===t)return this.cachedLines;if(this.cachedWidth=t,this.collapsed){let e=[],n=this.answers.size;return this.addWrapped(e,``,this.options.theme.fg(`accent`,`▸ Questionnaire (${n}/${this.options.params.questions.length} answered) — ${this.options.collapseKey} to expand`),t),this.cachedLines=e,e}let n=[],r=this.question(),i=this.options.theme.fg(`accent`,`─`.repeat(t));return n.push(i),this.addWrapped(n,` `,this.renderTabs(),t),n.push(``),this.addWrapped(n,` `,this.options.theme.bold(r.question),t),n.push(``),this.renderOptions(n,r,t),this.renderPreview(n,r,t),this.renderNotes(n,t),this.renderEditor(n,t),n.push(``),this.addWrapped(n,` `,this.helpText(r),t),n.push(i),this.cachedLines=n,n}question(){let e=this.options.params.questions[this.currentQuestion];if(!e)throw Error(`Questionnaire has no active question`);return e}optionCount(e){return e.options.length+1}renderTabs(){return this.options.params.questions.map((e,t)=>{let n=` ${this.answers.has(t)?`✓`:`○`} ${e.header} `;return t===this.currentQuestion?this.options.theme.bg(`selectedBg`,this.options.theme.fg(`text`,n)):this.options.theme.fg(this.answers.has(t)?`success`:`muted`,n)}).join(` `)}renderOptions(e,t,n){let r=this.selectedOptions.get(this.currentQuestion)??new Set;t.options.forEach((i,a)=>{let o=a===this.currentOption,s=t.multiSelect?r.has(a)?`[x]`:`[ ]`:`${a+1}.`,c=o?this.options.theme.fg(`accent`,`> `):` `,l=`${s} ${i.label}`;this.addWrapped(e,c,this.options.theme.fg(o?`accent`:`text`,l),n),i.description&&this.addWrapped(e,` `,this.options.theme.fg(`muted`,i.description),n)});let i=t.options.length,a=this.currentOption===i;this.addWrapped(e,a?this.options.theme.fg(`accent`,`> `):` `,this.options.theme.fg(a?`accent`:`text`,`✎ Type something.`),n)}renderPreview(t,n,r){if(this.currentOption>=n.options.length)return;let i=n.options[this.currentOption]?.preview;if(!i)return;t.push(``),this.addWrapped(t,` `,this.options.theme.fg(`accent`,this.options.theme.bold(`Preview`)),r);let a=new e.Markdown(i,0,0,this.options.markdownTheme,{color:e=>this.options.theme.fg(`muted`,e)},{preserveOrderedListMarkers:!0,preserveBackslashEscapes:!0});for(let e of a.render(Math.max(1,r-3)))this.addWrapped(t,` │ `,e||` `,r)}renderNotes(e,t){let n=this.notes.get(this.currentQuestion);!n||this.editorMode===`notes`||(e.push(``),this.addWrapped(e,` `,this.options.theme.fg(`muted`,`Note: ${n}`),t))}renderEditor(t,n){if(!this.editorMode)return;t.push(``);let r=this.editorMode===`custom`?`Your answer:`:`Optional note:`;this.addWrapped(t,` `,this.options.theme.fg(`muted`,r),n);for(let r of this.editor.render(Math.max(1,n-2)))t.push((0,e.truncateToWidth)(` ${r}`,n,``));this.externalEditorError&&this.addWrapped(t,` `,this.options.theme.fg(`warning`,this.externalEditorError),n)}helpText(e){return this.editorMode?`Enter submit • Esc/Ctrl+C return • Ctrl+G external editor`:`Tab/←→ questions • ↑↓ options • ${e.multiSelect?`Space toggle • Enter confirm`:`Enter select`} • N note • Esc/Ctrl+C cancel`}addWrapped(t,n,r,i){let a=Math.min((0,e.visibleWidth)(n),Math.max(0,i-1)),o=(0,e.truncateToWidth)(n,a,``),s=Math.max(1,i-a),c=(0,e.wrapTextWithAnsi)(r,s);if(c.length===0){t.push(o);return}for(let n=0;n<c.length;n+=1){let r=n===0?o:` `.repeat(a);t.push((0,e.truncateToWidth)(`${r}${c[n]}`,i,``))}}moveOption(e){let t=this.optionCount(this.question());this.currentOption=(this.currentOption+e+t)%t,this.refresh()}moveQuestion(e){let t=this.options.params.questions.length;this.currentQuestion=(this.currentQuestion+e+t)%t,this.currentOption=0,this.refresh()}toggleCurrentOption(){let e=this.question();if(!e.multiSelect||this.currentOption>=e.options.length)return;let t=this.selectedOptions.get(this.currentQuestion)??new Set;t.has(this.currentOption)?t.delete(this.currentOption):t.add(this.currentOption),this.selectedOptions.set(this.currentQuestion,t),this.refresh()}confirmCurrentSelection(){let e=this.question();if(this.currentOption===e.options.length){this.openEditor(`custom`);return}if(e.multiSelect){let t=this.selectedOptions.get(this.currentQuestion)??new Set;t.size===0&&t.add(this.currentOption);let n=[...t].sort((e,t)=>e-t).flatMap(t=>e.options[t]?[e.options[t].label]:[]);this.recordAnswer({questionIndex:this.currentQuestion,question:e.question,kind:`multi`,answer:null,selected:n,...this.noteFields()});return}let t=e.options[this.currentOption];t&&this.recordAnswer({questionIndex:this.currentQuestion,question:e.question,kind:`option`,answer:t.label,...t.preview?{preview:t.preview}:{},...this.noteFields()})}openEditor(e){this.editorMode=e,this.externalEditorError=void 0,this.editor.setText(e===`notes`?this.notes.get(this.currentQuestion)??``:``),this.refresh()}async openExternalEditor(){let e=this.editorMode;if(e){this.options.tui.stop();try{let t=await this.options.editExternally(this.editor.getText());if(this.settled||this.editorMode!==e)return;t.status===`complete`?(this.editor.setText(t.content),this.externalEditorError=void 0):this.externalEditorError=t.message}catch(e){this.externalEditorError=`External editor failed: ${e instanceof Error?e.message:String(e)}`}finally{this.options.tui.start(),this.refresh()}}}submitEditor(e){let t=this.editorMode;if(this.editorMode=void 0,this.editor.setText(``),t===`notes`){let t=e.trim();t?this.notes.set(this.currentQuestion,t):this.notes.delete(this.currentQuestion);let n=this.answers.get(this.currentQuestion);n&&(this.answers.set(this.currentQuestion,{...n,...t?{notes:t}:{notes:void 0}}),this.reportProgress()),this.refresh();return}t===`custom`&&this.recordAnswer({questionIndex:this.currentQuestion,question:this.question().question,kind:`custom`,answer:e,...this.noteFields()})}noteFields(){let e=this.notes.get(this.currentQuestion);return e?{notes:e}:{}}recordAnswer(e){if(this.answers.set(this.currentQuestion,e),this.reportProgress(),this.answers.size===this.options.params.questions.length){this.finish(!1);return}let t=this.options.params.questions.findIndex((e,t)=>t>this.currentQuestion&&!this.answers.has(t));this.currentQuestion=t>=0?t:this.options.params.questions.findIndex((e,t)=>!this.answers.has(t)),this.currentOption=0,this.refresh()}orderedAnswers(){return[...this.answers.values()].sort((e,t)=>e.questionIndex-t.questionIndex).map(e=>({...e}))}reportProgress(){this.options.reportProgress?.({answers:this.orderedAnswers(),cancelled:!1})}finish(e){this.settled||(this.settled=!0,this.options.done({answers:this.orderedAnswers(),cancelled:e}))}refresh(){this.invalidate(),this.options.tui.requestRender()}};exports.QuestionnaireComponent=n;
2
+ //# sourceMappingURL=questionnaireComponent.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaireComponent.cjs","names":["Editor","matchesKey","Key","Markdown","truncateToWidth","visibleWidth","wrapTextWithAnsi"],"sources":["../../src/tui/questionnaireComponent.ts"],"sourcesContent":["import type { KeybindingsManager, Theme } from '@earendil-works/pi-coding-agent';\nimport {\n Editor,\n Key,\n Markdown,\n matchesKey,\n type Focusable,\n type MarkdownTheme,\n type TUI,\n truncateToWidth,\n visibleWidth,\n wrapTextWithAnsi,\n} from '@earendil-works/pi-tui';\nimport type { QuestionData, QuestionParams } from '../schemas/questionnaire.js';\nimport type { ExternalEditResult, QuestionAnswer, QuestionnaireResult } from '../types/questionnaire.js';\n\nexport interface QuestionnaireComponentOptions {\n tui: TUI;\n theme: Theme;\n markdownTheme: MarkdownTheme;\n keybindings: KeybindingsManager;\n params: QuestionParams;\n collapseKey: string;\n done: (result: QuestionnaireResult) => void;\n editExternally: (content: string) => Promise<ExternalEditResult>;\n reportProgress?: (result: QuestionnaireResult) => void;\n}\n\ntype EditorMode = 'custom' | 'notes';\n\nconst SELECT_CANCEL_KEYBINDING = 'tui.select.cancel';\nconst EXTERNAL_EDITOR_KEYBINDING = 'app.editor.external';\n\nexport class QuestionnaireComponent implements Focusable {\n private readonly answers = new Map<number, QuestionAnswer>();\n private readonly selectedOptions = new Map<number, Set<number>>();\n private readonly notes = new Map<number, string>();\n private readonly editor: Editor;\n private currentQuestion = 0;\n private currentOption = 0;\n private editorMode: EditorMode | undefined;\n private collapsed = false;\n private settled = false;\n private externalEditorError: string | undefined;\n private cachedLines: string[] | undefined;\n private cachedWidth: number | undefined;\n private isFocused = false;\n\n constructor(private readonly options: QuestionnaireComponentOptions) {\n this.editor = new Editor(options.tui, {\n borderColor: (text) => options.theme.fg('accent', text),\n selectList: {\n selectedPrefix: (text) => options.theme.fg('accent', text),\n selectedText: (text) => options.theme.fg('accent', text),\n description: (text) => options.theme.fg('muted', text),\n scrollInfo: (text) => options.theme.fg('dim', text),\n noMatch: (text) => options.theme.fg('warning', text),\n },\n });\n this.editor.onSubmit = (value) => this.submitEditor(value);\n }\n\n get focused(): boolean {\n return this.isFocused;\n }\n\n set focused(value: boolean) {\n this.isFocused = value;\n this.editor.focused = value;\n this.invalidate();\n }\n\n invalidate(): void {\n this.cachedLines = undefined;\n this.cachedWidth = undefined;\n }\n\n cancel(): void {\n this.finish(true);\n }\n\n handleInput(data: string): void {\n if (this.settled) return;\n\n if (\n this.options.collapseKey !== 'off' &&\n matchesKey(data, this.options.collapseKey as Parameters<typeof matchesKey>[1])\n ) {\n this.collapsed = !this.collapsed;\n this.refresh();\n return;\n }\n\n if (this.collapsed) return;\n\n if (this.editorMode) {\n if (this.options.keybindings.matches(data, SELECT_CANCEL_KEYBINDING)) {\n this.editorMode = undefined;\n this.externalEditorError = undefined;\n this.editor.setText('');\n this.refresh();\n return;\n }\n if (this.options.keybindings.matches(data, EXTERNAL_EDITOR_KEYBINDING)) {\n void this.openExternalEditor();\n return;\n }\n this.editor.handleInput(data);\n this.refresh();\n return;\n }\n\n if (this.options.keybindings.matches(data, SELECT_CANCEL_KEYBINDING)) {\n this.cancel();\n return;\n }\n if (matchesKey(data, Key.up)) {\n this.moveOption(-1);\n return;\n }\n if (matchesKey(data, Key.down)) {\n this.moveOption(1);\n return;\n }\n if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n this.moveQuestion(1);\n return;\n }\n if (matchesKey(data, Key.shift('tab')) || matchesKey(data, Key.left)) {\n this.moveQuestion(-1);\n return;\n }\n if (matchesKey(data, 'n') || matchesKey(data, Key.shift('n'))) {\n this.openEditor('notes');\n return;\n }\n if (matchesKey(data, Key.space) || data === ' ') {\n this.toggleCurrentOption();\n return;\n }\n if (matchesKey(data, Key.enter)) {\n this.confirmCurrentSelection();\n }\n }\n\n render(width: number): string[] {\n const renderWidth = Math.max(1, width);\n if (this.cachedLines && this.cachedWidth === renderWidth) return this.cachedLines;\n this.cachedWidth = renderWidth;\n if (this.collapsed) {\n const lines: string[] = [];\n const answerCount = this.answers.size;\n this.addWrapped(\n lines,\n '',\n this.options.theme.fg(\n 'accent',\n `▸ Questionnaire (${answerCount}/${this.options.params.questions.length} answered) — ${this.options.collapseKey} to expand`,\n ),\n renderWidth,\n );\n this.cachedLines = lines;\n return lines;\n }\n\n const lines: string[] = [];\n const question = this.question();\n const border = this.options.theme.fg('accent', '─'.repeat(renderWidth));\n lines.push(border);\n this.addWrapped(lines, ' ', this.renderTabs(), renderWidth);\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.bold(question.question), renderWidth);\n lines.push('');\n\n this.renderOptions(lines, question, renderWidth);\n this.renderPreview(lines, question, renderWidth);\n this.renderNotes(lines, renderWidth);\n this.renderEditor(lines, renderWidth);\n lines.push('');\n this.addWrapped(lines, ' ', this.helpText(question), renderWidth);\n lines.push(border);\n this.cachedLines = lines;\n return lines;\n }\n\n private question(): QuestionData {\n const question = this.options.params.questions[this.currentQuestion];\n if (!question) throw new Error('Questionnaire has no active question');\n return question;\n }\n\n private optionCount(question: QuestionData): number {\n return question.options.length + 1;\n }\n\n private renderTabs(): string {\n const tabs = this.options.params.questions.map((question, index) => {\n const mark = this.answers.has(index) ? '✓' : '○';\n const label = ` ${mark} ${question.header} `;\n return index === this.currentQuestion\n ? this.options.theme.bg('selectedBg', this.options.theme.fg('text', label))\n : this.options.theme.fg(this.answers.has(index) ? 'success' : 'muted', label);\n });\n return tabs.join(' ');\n }\n\n private renderOptions(lines: string[], question: QuestionData, width: number): void {\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n question.options.forEach((option, index) => {\n const focused = index === this.currentOption;\n const checked = question.multiSelect ? (selected.has(index) ? '[x]' : '[ ]') : `${index + 1}.`;\n const prefix = focused ? this.options.theme.fg('accent', '> ') : ' ';\n const label = `${checked} ${option.label}`;\n this.addWrapped(lines, prefix, this.options.theme.fg(focused ? 'accent' : 'text', label), width);\n if (option.description) {\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', option.description), width);\n }\n });\n\n const customIndex = question.options.length;\n const customFocused = this.currentOption === customIndex;\n this.addWrapped(\n lines,\n customFocused ? this.options.theme.fg('accent', '> ') : ' ',\n this.options.theme.fg(customFocused ? 'accent' : 'text', '✎ Type something.'),\n width,\n );\n }\n\n private renderPreview(lines: string[], question: QuestionData, width: number): void {\n if (this.currentOption >= question.options.length) return;\n const preview = question.options[this.currentOption]?.preview;\n if (!preview) return;\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.fg('accent', this.options.theme.bold('Preview')), width);\n const markdown = new Markdown(\n preview,\n 0,\n 0,\n this.options.markdownTheme,\n { color: (text) => this.options.theme.fg('muted', text) },\n { preserveOrderedListMarkers: true, preserveBackslashEscapes: true },\n );\n for (const previewLine of markdown.render(Math.max(1, width - 3))) {\n this.addWrapped(lines, ' │ ', previewLine || ' ', width);\n }\n }\n\n private renderNotes(lines: string[], width: number): void {\n const note = this.notes.get(this.currentQuestion);\n if (!note || this.editorMode === 'notes') return;\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', `Note: ${note}`), width);\n }\n\n private renderEditor(lines: string[], width: number): void {\n if (!this.editorMode) return;\n lines.push('');\n const label = this.editorMode === 'custom' ? 'Your answer:' : 'Optional note:';\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', label), width);\n for (const line of this.editor.render(Math.max(1, width - 2))) {\n lines.push(truncateToWidth(` ${line}`, width, ''));\n }\n if (this.externalEditorError) {\n this.addWrapped(lines, ' ', this.options.theme.fg('warning', this.externalEditorError), width);\n }\n }\n\n private helpText(question: QuestionData): string {\n if (this.editorMode) return 'Enter submit • Esc/Ctrl+C return • Ctrl+G external editor';\n const selectionHelp = question.multiSelect ? 'Space toggle • Enter confirm' : 'Enter select';\n return `Tab/←→ questions • ↑↓ options • ${selectionHelp} • N note • Esc/Ctrl+C cancel`;\n }\n\n private addWrapped(lines: string[], prefix: string, text: string, width: number): void {\n const prefixWidth = Math.min(visibleWidth(prefix), Math.max(0, width - 1));\n const renderedPrefix = truncateToWidth(prefix, prefixWidth, '');\n const available = Math.max(1, width - prefixWidth);\n const wrapped = wrapTextWithAnsi(text, available);\n if (wrapped.length === 0) {\n lines.push(renderedPrefix);\n return;\n }\n for (let index = 0; index < wrapped.length; index += 1) {\n const linePrefix = index === 0 ? renderedPrefix : ' '.repeat(prefixWidth);\n lines.push(truncateToWidth(`${linePrefix}${wrapped[index]}`, width, ''));\n }\n }\n\n private moveOption(delta: number): void {\n const count = this.optionCount(this.question());\n this.currentOption = (this.currentOption + delta + count) % count;\n this.refresh();\n }\n\n private moveQuestion(delta: number): void {\n const count = this.options.params.questions.length;\n this.currentQuestion = (this.currentQuestion + delta + count) % count;\n this.currentOption = 0;\n this.refresh();\n }\n\n private toggleCurrentOption(): void {\n const question = this.question();\n if (!question.multiSelect || this.currentOption >= question.options.length) return;\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n if (selected.has(this.currentOption)) selected.delete(this.currentOption);\n else selected.add(this.currentOption);\n this.selectedOptions.set(this.currentQuestion, selected);\n this.refresh();\n }\n\n private confirmCurrentSelection(): void {\n const question = this.question();\n if (this.currentOption === question.options.length) {\n this.openEditor('custom');\n return;\n }\n\n if (question.multiSelect) {\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n if (selected.size === 0) selected.add(this.currentOption);\n const labels = [...selected]\n .sort((left, right) => left - right)\n .flatMap((index) => (question.options[index] ? [question.options[index].label] : []));\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: labels,\n ...this.noteFields(),\n });\n return;\n }\n\n const option = question.options[this.currentOption];\n if (!option) return;\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n ...this.noteFields(),\n });\n }\n\n private openEditor(mode: EditorMode): void {\n this.editorMode = mode;\n this.externalEditorError = undefined;\n this.editor.setText(mode === 'notes' ? (this.notes.get(this.currentQuestion) ?? '') : '');\n this.refresh();\n }\n\n private async openExternalEditor(): Promise<void> {\n const editorMode = this.editorMode;\n if (!editorMode) return;\n this.options.tui.stop();\n try {\n const result = await this.options.editExternally(this.editor.getText());\n if (this.settled || this.editorMode !== editorMode) return;\n if (result.status === 'complete') {\n this.editor.setText(result.content);\n this.externalEditorError = undefined;\n } else {\n this.externalEditorError = result.message;\n }\n } catch (error) {\n this.externalEditorError = `External editor failed: ${error instanceof Error ? error.message : String(error)}`;\n } finally {\n this.options.tui.start();\n this.refresh();\n }\n }\n\n private submitEditor(value: string): void {\n const mode = this.editorMode;\n this.editorMode = undefined;\n this.editor.setText('');\n if (mode === 'notes') {\n const trimmed = value.trim();\n if (trimmed) this.notes.set(this.currentQuestion, trimmed);\n else this.notes.delete(this.currentQuestion);\n const answer = this.answers.get(this.currentQuestion);\n if (answer) {\n this.answers.set(this.currentQuestion, {\n ...answer,\n ...(trimmed ? { notes: trimmed } : { notes: undefined }),\n });\n this.reportProgress();\n }\n this.refresh();\n return;\n }\n if (mode === 'custom') {\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: this.question().question,\n kind: 'custom',\n answer: value,\n ...this.noteFields(),\n });\n }\n }\n\n private noteFields(): { notes?: string } {\n const note = this.notes.get(this.currentQuestion);\n return note ? { notes: note } : {};\n }\n\n private recordAnswer(answer: QuestionAnswer): void {\n this.answers.set(this.currentQuestion, answer);\n this.reportProgress();\n if (this.answers.size === this.options.params.questions.length) {\n this.finish(false);\n return;\n }\n const next = this.options.params.questions.findIndex(\n (_question, index) => index > this.currentQuestion && !this.answers.has(index),\n );\n this.currentQuestion =\n next >= 0 ? next : this.options.params.questions.findIndex((_question, index) => !this.answers.has(index));\n this.currentOption = 0;\n this.refresh();\n }\n\n private orderedAnswers(): QuestionAnswer[] {\n return [...this.answers.values()]\n .sort((left, right) => left.questionIndex - right.questionIndex)\n .map((answer) => ({ ...answer }));\n }\n\n private reportProgress(): void {\n this.options.reportProgress?.({ answers: this.orderedAnswers(), cancelled: false });\n }\n\n private finish(cancelled: boolean): void {\n if (this.settled) return;\n this.settled = true;\n this.options.done({ answers: this.orderedAnswers(), cancelled });\n }\n\n private refresh(): void {\n this.invalidate();\n this.options.tui.requestRender();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import{Editor as e,Key as t,Markdown as n,matchesKey as r,truncateToWidth as i,visibleWidth as a,wrapTextWithAnsi as o}from"@earendil-works/pi-tui";const s=`tui.select.cancel`;var c=class{options;answers=new Map;selectedOptions=new Map;notes=new Map;editor;currentQuestion=0;currentOption=0;editorMode;collapsed=!1;settled=!1;externalEditorError;cachedLines;cachedWidth;isFocused=!1;constructor(t){this.options=t,this.editor=new e(t.tui,{borderColor:e=>t.theme.fg(`accent`,e),selectList:{selectedPrefix:e=>t.theme.fg(`accent`,e),selectedText:e=>t.theme.fg(`accent`,e),description:e=>t.theme.fg(`muted`,e),scrollInfo:e=>t.theme.fg(`dim`,e),noMatch:e=>t.theme.fg(`warning`,e)}}),this.editor.onSubmit=e=>this.submitEditor(e)}get focused(){return this.isFocused}set focused(e){this.isFocused=e,this.editor.focused=e,this.invalidate()}invalidate(){this.cachedLines=void 0,this.cachedWidth=void 0}cancel(){this.finish(!0)}handleInput(e){if(!this.settled){if(this.options.collapseKey!==`off`&&r(e,this.options.collapseKey)){this.collapsed=!this.collapsed,this.refresh();return}if(!this.collapsed){if(this.editorMode){if(this.options.keybindings.matches(e,s)){this.editorMode=void 0,this.externalEditorError=void 0,this.editor.setText(``),this.refresh();return}if(this.options.keybindings.matches(e,`app.editor.external`)){this.openExternalEditor();return}this.editor.handleInput(e),this.refresh();return}if(this.options.keybindings.matches(e,s)){this.cancel();return}if(r(e,t.up)){this.moveOption(-1);return}if(r(e,t.down)){this.moveOption(1);return}if(r(e,t.tab)||r(e,t.right)){this.moveQuestion(1);return}if(r(e,t.shift(`tab`))||r(e,t.left)){this.moveQuestion(-1);return}if(r(e,`n`)||r(e,t.shift(`n`))){this.openEditor(`notes`);return}if(r(e,t.space)||e===` `){this.toggleCurrentOption();return}r(e,t.enter)&&this.confirmCurrentSelection()}}}render(e){let t=Math.max(1,e);if(this.cachedLines&&this.cachedWidth===t)return this.cachedLines;if(this.cachedWidth=t,this.collapsed){let e=[],n=this.answers.size;return this.addWrapped(e,``,this.options.theme.fg(`accent`,`▸ Questionnaire (${n}/${this.options.params.questions.length} answered) — ${this.options.collapseKey} to expand`),t),this.cachedLines=e,e}let n=[],r=this.question(),i=this.options.theme.fg(`accent`,`─`.repeat(t));return n.push(i),this.addWrapped(n,` `,this.renderTabs(),t),n.push(``),this.addWrapped(n,` `,this.options.theme.bold(r.question),t),n.push(``),this.renderOptions(n,r,t),this.renderPreview(n,r,t),this.renderNotes(n,t),this.renderEditor(n,t),n.push(``),this.addWrapped(n,` `,this.helpText(r),t),n.push(i),this.cachedLines=n,n}question(){let e=this.options.params.questions[this.currentQuestion];if(!e)throw Error(`Questionnaire has no active question`);return e}optionCount(e){return e.options.length+1}renderTabs(){return this.options.params.questions.map((e,t)=>{let n=` ${this.answers.has(t)?`✓`:`○`} ${e.header} `;return t===this.currentQuestion?this.options.theme.bg(`selectedBg`,this.options.theme.fg(`text`,n)):this.options.theme.fg(this.answers.has(t)?`success`:`muted`,n)}).join(` `)}renderOptions(e,t,n){let r=this.selectedOptions.get(this.currentQuestion)??new Set;t.options.forEach((i,a)=>{let o=a===this.currentOption,s=t.multiSelect?r.has(a)?`[x]`:`[ ]`:`${a+1}.`,c=o?this.options.theme.fg(`accent`,`> `):` `,l=`${s} ${i.label}`;this.addWrapped(e,c,this.options.theme.fg(o?`accent`:`text`,l),n),i.description&&this.addWrapped(e,` `,this.options.theme.fg(`muted`,i.description),n)});let i=t.options.length,a=this.currentOption===i;this.addWrapped(e,a?this.options.theme.fg(`accent`,`> `):` `,this.options.theme.fg(a?`accent`:`text`,`✎ Type something.`),n)}renderPreview(e,t,r){if(this.currentOption>=t.options.length)return;let i=t.options[this.currentOption]?.preview;if(!i)return;e.push(``),this.addWrapped(e,` `,this.options.theme.fg(`accent`,this.options.theme.bold(`Preview`)),r);let a=new n(i,0,0,this.options.markdownTheme,{color:e=>this.options.theme.fg(`muted`,e)},{preserveOrderedListMarkers:!0,preserveBackslashEscapes:!0});for(let t of a.render(Math.max(1,r-3)))this.addWrapped(e,` │ `,t||` `,r)}renderNotes(e,t){let n=this.notes.get(this.currentQuestion);!n||this.editorMode===`notes`||(e.push(``),this.addWrapped(e,` `,this.options.theme.fg(`muted`,`Note: ${n}`),t))}renderEditor(e,t){if(!this.editorMode)return;e.push(``);let n=this.editorMode===`custom`?`Your answer:`:`Optional note:`;this.addWrapped(e,` `,this.options.theme.fg(`muted`,n),t);for(let n of this.editor.render(Math.max(1,t-2)))e.push(i(` ${n}`,t,``));this.externalEditorError&&this.addWrapped(e,` `,this.options.theme.fg(`warning`,this.externalEditorError),t)}helpText(e){return this.editorMode?`Enter submit • Esc/Ctrl+C return • Ctrl+G external editor`:`Tab/←→ questions • ↑↓ options • ${e.multiSelect?`Space toggle • Enter confirm`:`Enter select`} • N note • Esc/Ctrl+C cancel`}addWrapped(e,t,n,r){let s=Math.min(a(t),Math.max(0,r-1)),c=i(t,s,``),l=Math.max(1,r-s),u=o(n,l);if(u.length===0){e.push(c);return}for(let t=0;t<u.length;t+=1){let n=t===0?c:` `.repeat(s);e.push(i(`${n}${u[t]}`,r,``))}}moveOption(e){let t=this.optionCount(this.question());this.currentOption=(this.currentOption+e+t)%t,this.refresh()}moveQuestion(e){let t=this.options.params.questions.length;this.currentQuestion=(this.currentQuestion+e+t)%t,this.currentOption=0,this.refresh()}toggleCurrentOption(){let e=this.question();if(!e.multiSelect||this.currentOption>=e.options.length)return;let t=this.selectedOptions.get(this.currentQuestion)??new Set;t.has(this.currentOption)?t.delete(this.currentOption):t.add(this.currentOption),this.selectedOptions.set(this.currentQuestion,t),this.refresh()}confirmCurrentSelection(){let e=this.question();if(this.currentOption===e.options.length){this.openEditor(`custom`);return}if(e.multiSelect){let t=this.selectedOptions.get(this.currentQuestion)??new Set;t.size===0&&t.add(this.currentOption);let n=[...t].sort((e,t)=>e-t).flatMap(t=>e.options[t]?[e.options[t].label]:[]);this.recordAnswer({questionIndex:this.currentQuestion,question:e.question,kind:`multi`,answer:null,selected:n,...this.noteFields()});return}let t=e.options[this.currentOption];t&&this.recordAnswer({questionIndex:this.currentQuestion,question:e.question,kind:`option`,answer:t.label,...t.preview?{preview:t.preview}:{},...this.noteFields()})}openEditor(e){this.editorMode=e,this.externalEditorError=void 0,this.editor.setText(e===`notes`?this.notes.get(this.currentQuestion)??``:``),this.refresh()}async openExternalEditor(){let e=this.editorMode;if(e){this.options.tui.stop();try{let t=await this.options.editExternally(this.editor.getText());if(this.settled||this.editorMode!==e)return;t.status===`complete`?(this.editor.setText(t.content),this.externalEditorError=void 0):this.externalEditorError=t.message}catch(e){this.externalEditorError=`External editor failed: ${e instanceof Error?e.message:String(e)}`}finally{this.options.tui.start(),this.refresh()}}}submitEditor(e){let t=this.editorMode;if(this.editorMode=void 0,this.editor.setText(``),t===`notes`){let t=e.trim();t?this.notes.set(this.currentQuestion,t):this.notes.delete(this.currentQuestion);let n=this.answers.get(this.currentQuestion);n&&(this.answers.set(this.currentQuestion,{...n,...t?{notes:t}:{notes:void 0}}),this.reportProgress()),this.refresh();return}t===`custom`&&this.recordAnswer({questionIndex:this.currentQuestion,question:this.question().question,kind:`custom`,answer:e,...this.noteFields()})}noteFields(){let e=this.notes.get(this.currentQuestion);return e?{notes:e}:{}}recordAnswer(e){if(this.answers.set(this.currentQuestion,e),this.reportProgress(),this.answers.size===this.options.params.questions.length){this.finish(!1);return}let t=this.options.params.questions.findIndex((e,t)=>t>this.currentQuestion&&!this.answers.has(t));this.currentQuestion=t>=0?t:this.options.params.questions.findIndex((e,t)=>!this.answers.has(t)),this.currentOption=0,this.refresh()}orderedAnswers(){return[...this.answers.values()].sort((e,t)=>e.questionIndex-t.questionIndex).map(e=>({...e}))}reportProgress(){this.options.reportProgress?.({answers:this.orderedAnswers(),cancelled:!1})}finish(e){this.settled||(this.settled=!0,this.options.done({answers:this.orderedAnswers(),cancelled:e}))}refresh(){this.invalidate(),this.options.tui.requestRender()}};export{c as QuestionnaireComponent};
2
+ //# sourceMappingURL=questionnaireComponent.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaireComponent.mjs","names":[],"sources":["../../src/tui/questionnaireComponent.ts"],"sourcesContent":["import type { KeybindingsManager, Theme } from '@earendil-works/pi-coding-agent';\nimport {\n Editor,\n Key,\n Markdown,\n matchesKey,\n type Focusable,\n type MarkdownTheme,\n type TUI,\n truncateToWidth,\n visibleWidth,\n wrapTextWithAnsi,\n} from '@earendil-works/pi-tui';\nimport type { QuestionData, QuestionParams } from '../schemas/questionnaire.js';\nimport type { ExternalEditResult, QuestionAnswer, QuestionnaireResult } from '../types/questionnaire.js';\n\nexport interface QuestionnaireComponentOptions {\n tui: TUI;\n theme: Theme;\n markdownTheme: MarkdownTheme;\n keybindings: KeybindingsManager;\n params: QuestionParams;\n collapseKey: string;\n done: (result: QuestionnaireResult) => void;\n editExternally: (content: string) => Promise<ExternalEditResult>;\n reportProgress?: (result: QuestionnaireResult) => void;\n}\n\ntype EditorMode = 'custom' | 'notes';\n\nconst SELECT_CANCEL_KEYBINDING = 'tui.select.cancel';\nconst EXTERNAL_EDITOR_KEYBINDING = 'app.editor.external';\n\nexport class QuestionnaireComponent implements Focusable {\n private readonly answers = new Map<number, QuestionAnswer>();\n private readonly selectedOptions = new Map<number, Set<number>>();\n private readonly notes = new Map<number, string>();\n private readonly editor: Editor;\n private currentQuestion = 0;\n private currentOption = 0;\n private editorMode: EditorMode | undefined;\n private collapsed = false;\n private settled = false;\n private externalEditorError: string | undefined;\n private cachedLines: string[] | undefined;\n private cachedWidth: number | undefined;\n private isFocused = false;\n\n constructor(private readonly options: QuestionnaireComponentOptions) {\n this.editor = new Editor(options.tui, {\n borderColor: (text) => options.theme.fg('accent', text),\n selectList: {\n selectedPrefix: (text) => options.theme.fg('accent', text),\n selectedText: (text) => options.theme.fg('accent', text),\n description: (text) => options.theme.fg('muted', text),\n scrollInfo: (text) => options.theme.fg('dim', text),\n noMatch: (text) => options.theme.fg('warning', text),\n },\n });\n this.editor.onSubmit = (value) => this.submitEditor(value);\n }\n\n get focused(): boolean {\n return this.isFocused;\n }\n\n set focused(value: boolean) {\n this.isFocused = value;\n this.editor.focused = value;\n this.invalidate();\n }\n\n invalidate(): void {\n this.cachedLines = undefined;\n this.cachedWidth = undefined;\n }\n\n cancel(): void {\n this.finish(true);\n }\n\n handleInput(data: string): void {\n if (this.settled) return;\n\n if (\n this.options.collapseKey !== 'off' &&\n matchesKey(data, this.options.collapseKey as Parameters<typeof matchesKey>[1])\n ) {\n this.collapsed = !this.collapsed;\n this.refresh();\n return;\n }\n\n if (this.collapsed) return;\n\n if (this.editorMode) {\n if (this.options.keybindings.matches(data, SELECT_CANCEL_KEYBINDING)) {\n this.editorMode = undefined;\n this.externalEditorError = undefined;\n this.editor.setText('');\n this.refresh();\n return;\n }\n if (this.options.keybindings.matches(data, EXTERNAL_EDITOR_KEYBINDING)) {\n void this.openExternalEditor();\n return;\n }\n this.editor.handleInput(data);\n this.refresh();\n return;\n }\n\n if (this.options.keybindings.matches(data, SELECT_CANCEL_KEYBINDING)) {\n this.cancel();\n return;\n }\n if (matchesKey(data, Key.up)) {\n this.moveOption(-1);\n return;\n }\n if (matchesKey(data, Key.down)) {\n this.moveOption(1);\n return;\n }\n if (matchesKey(data, Key.tab) || matchesKey(data, Key.right)) {\n this.moveQuestion(1);\n return;\n }\n if (matchesKey(data, Key.shift('tab')) || matchesKey(data, Key.left)) {\n this.moveQuestion(-1);\n return;\n }\n if (matchesKey(data, 'n') || matchesKey(data, Key.shift('n'))) {\n this.openEditor('notes');\n return;\n }\n if (matchesKey(data, Key.space) || data === ' ') {\n this.toggleCurrentOption();\n return;\n }\n if (matchesKey(data, Key.enter)) {\n this.confirmCurrentSelection();\n }\n }\n\n render(width: number): string[] {\n const renderWidth = Math.max(1, width);\n if (this.cachedLines && this.cachedWidth === renderWidth) return this.cachedLines;\n this.cachedWidth = renderWidth;\n if (this.collapsed) {\n const lines: string[] = [];\n const answerCount = this.answers.size;\n this.addWrapped(\n lines,\n '',\n this.options.theme.fg(\n 'accent',\n `▸ Questionnaire (${answerCount}/${this.options.params.questions.length} answered) — ${this.options.collapseKey} to expand`,\n ),\n renderWidth,\n );\n this.cachedLines = lines;\n return lines;\n }\n\n const lines: string[] = [];\n const question = this.question();\n const border = this.options.theme.fg('accent', '─'.repeat(renderWidth));\n lines.push(border);\n this.addWrapped(lines, ' ', this.renderTabs(), renderWidth);\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.bold(question.question), renderWidth);\n lines.push('');\n\n this.renderOptions(lines, question, renderWidth);\n this.renderPreview(lines, question, renderWidth);\n this.renderNotes(lines, renderWidth);\n this.renderEditor(lines, renderWidth);\n lines.push('');\n this.addWrapped(lines, ' ', this.helpText(question), renderWidth);\n lines.push(border);\n this.cachedLines = lines;\n return lines;\n }\n\n private question(): QuestionData {\n const question = this.options.params.questions[this.currentQuestion];\n if (!question) throw new Error('Questionnaire has no active question');\n return question;\n }\n\n private optionCount(question: QuestionData): number {\n return question.options.length + 1;\n }\n\n private renderTabs(): string {\n const tabs = this.options.params.questions.map((question, index) => {\n const mark = this.answers.has(index) ? '✓' : '○';\n const label = ` ${mark} ${question.header} `;\n return index === this.currentQuestion\n ? this.options.theme.bg('selectedBg', this.options.theme.fg('text', label))\n : this.options.theme.fg(this.answers.has(index) ? 'success' : 'muted', label);\n });\n return tabs.join(' ');\n }\n\n private renderOptions(lines: string[], question: QuestionData, width: number): void {\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n question.options.forEach((option, index) => {\n const focused = index === this.currentOption;\n const checked = question.multiSelect ? (selected.has(index) ? '[x]' : '[ ]') : `${index + 1}.`;\n const prefix = focused ? this.options.theme.fg('accent', '> ') : ' ';\n const label = `${checked} ${option.label}`;\n this.addWrapped(lines, prefix, this.options.theme.fg(focused ? 'accent' : 'text', label), width);\n if (option.description) {\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', option.description), width);\n }\n });\n\n const customIndex = question.options.length;\n const customFocused = this.currentOption === customIndex;\n this.addWrapped(\n lines,\n customFocused ? this.options.theme.fg('accent', '> ') : ' ',\n this.options.theme.fg(customFocused ? 'accent' : 'text', '✎ Type something.'),\n width,\n );\n }\n\n private renderPreview(lines: string[], question: QuestionData, width: number): void {\n if (this.currentOption >= question.options.length) return;\n const preview = question.options[this.currentOption]?.preview;\n if (!preview) return;\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.fg('accent', this.options.theme.bold('Preview')), width);\n const markdown = new Markdown(\n preview,\n 0,\n 0,\n this.options.markdownTheme,\n { color: (text) => this.options.theme.fg('muted', text) },\n { preserveOrderedListMarkers: true, preserveBackslashEscapes: true },\n );\n for (const previewLine of markdown.render(Math.max(1, width - 3))) {\n this.addWrapped(lines, ' │ ', previewLine || ' ', width);\n }\n }\n\n private renderNotes(lines: string[], width: number): void {\n const note = this.notes.get(this.currentQuestion);\n if (!note || this.editorMode === 'notes') return;\n lines.push('');\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', `Note: ${note}`), width);\n }\n\n private renderEditor(lines: string[], width: number): void {\n if (!this.editorMode) return;\n lines.push('');\n const label = this.editorMode === 'custom' ? 'Your answer:' : 'Optional note:';\n this.addWrapped(lines, ' ', this.options.theme.fg('muted', label), width);\n for (const line of this.editor.render(Math.max(1, width - 2))) {\n lines.push(truncateToWidth(` ${line}`, width, ''));\n }\n if (this.externalEditorError) {\n this.addWrapped(lines, ' ', this.options.theme.fg('warning', this.externalEditorError), width);\n }\n }\n\n private helpText(question: QuestionData): string {\n if (this.editorMode) return 'Enter submit • Esc/Ctrl+C return • Ctrl+G external editor';\n const selectionHelp = question.multiSelect ? 'Space toggle • Enter confirm' : 'Enter select';\n return `Tab/←→ questions • ↑↓ options • ${selectionHelp} • N note • Esc/Ctrl+C cancel`;\n }\n\n private addWrapped(lines: string[], prefix: string, text: string, width: number): void {\n const prefixWidth = Math.min(visibleWidth(prefix), Math.max(0, width - 1));\n const renderedPrefix = truncateToWidth(prefix, prefixWidth, '');\n const available = Math.max(1, width - prefixWidth);\n const wrapped = wrapTextWithAnsi(text, available);\n if (wrapped.length === 0) {\n lines.push(renderedPrefix);\n return;\n }\n for (let index = 0; index < wrapped.length; index += 1) {\n const linePrefix = index === 0 ? renderedPrefix : ' '.repeat(prefixWidth);\n lines.push(truncateToWidth(`${linePrefix}${wrapped[index]}`, width, ''));\n }\n }\n\n private moveOption(delta: number): void {\n const count = this.optionCount(this.question());\n this.currentOption = (this.currentOption + delta + count) % count;\n this.refresh();\n }\n\n private moveQuestion(delta: number): void {\n const count = this.options.params.questions.length;\n this.currentQuestion = (this.currentQuestion + delta + count) % count;\n this.currentOption = 0;\n this.refresh();\n }\n\n private toggleCurrentOption(): void {\n const question = this.question();\n if (!question.multiSelect || this.currentOption >= question.options.length) return;\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n if (selected.has(this.currentOption)) selected.delete(this.currentOption);\n else selected.add(this.currentOption);\n this.selectedOptions.set(this.currentQuestion, selected);\n this.refresh();\n }\n\n private confirmCurrentSelection(): void {\n const question = this.question();\n if (this.currentOption === question.options.length) {\n this.openEditor('custom');\n return;\n }\n\n if (question.multiSelect) {\n const selected = this.selectedOptions.get(this.currentQuestion) ?? new Set<number>();\n if (selected.size === 0) selected.add(this.currentOption);\n const labels = [...selected]\n .sort((left, right) => left - right)\n .flatMap((index) => (question.options[index] ? [question.options[index].label] : []));\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: question.question,\n kind: 'multi',\n answer: null,\n selected: labels,\n ...this.noteFields(),\n });\n return;\n }\n\n const option = question.options[this.currentOption];\n if (!option) return;\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: question.question,\n kind: 'option',\n answer: option.label,\n ...(option.preview ? { preview: option.preview } : {}),\n ...this.noteFields(),\n });\n }\n\n private openEditor(mode: EditorMode): void {\n this.editorMode = mode;\n this.externalEditorError = undefined;\n this.editor.setText(mode === 'notes' ? (this.notes.get(this.currentQuestion) ?? '') : '');\n this.refresh();\n }\n\n private async openExternalEditor(): Promise<void> {\n const editorMode = this.editorMode;\n if (!editorMode) return;\n this.options.tui.stop();\n try {\n const result = await this.options.editExternally(this.editor.getText());\n if (this.settled || this.editorMode !== editorMode) return;\n if (result.status === 'complete') {\n this.editor.setText(result.content);\n this.externalEditorError = undefined;\n } else {\n this.externalEditorError = result.message;\n }\n } catch (error) {\n this.externalEditorError = `External editor failed: ${error instanceof Error ? error.message : String(error)}`;\n } finally {\n this.options.tui.start();\n this.refresh();\n }\n }\n\n private submitEditor(value: string): void {\n const mode = this.editorMode;\n this.editorMode = undefined;\n this.editor.setText('');\n if (mode === 'notes') {\n const trimmed = value.trim();\n if (trimmed) this.notes.set(this.currentQuestion, trimmed);\n else this.notes.delete(this.currentQuestion);\n const answer = this.answers.get(this.currentQuestion);\n if (answer) {\n this.answers.set(this.currentQuestion, {\n ...answer,\n ...(trimmed ? { notes: trimmed } : { notes: undefined }),\n });\n this.reportProgress();\n }\n this.refresh();\n return;\n }\n if (mode === 'custom') {\n this.recordAnswer({\n questionIndex: this.currentQuestion,\n question: this.question().question,\n kind: 'custom',\n answer: value,\n ...this.noteFields(),\n });\n }\n }\n\n private noteFields(): { notes?: string } {\n const note = this.notes.get(this.currentQuestion);\n return note ? { notes: note } : {};\n }\n\n private recordAnswer(answer: QuestionAnswer): void {\n this.answers.set(this.currentQuestion, answer);\n this.reportProgress();\n if (this.answers.size === this.options.params.questions.length) {\n this.finish(false);\n return;\n }\n const next = this.options.params.questions.findIndex(\n (_question, index) => index > this.currentQuestion && !this.answers.has(index),\n );\n this.currentQuestion =\n next >= 0 ? next : this.options.params.questions.findIndex((_question, index) => !this.answers.has(index));\n this.currentOption = 0;\n this.refresh();\n }\n\n private orderedAnswers(): QuestionAnswer[] {\n return [...this.answers.values()]\n .sort((left, right) => left.questionIndex - right.questionIndex)\n .map((answer) => ({ ...answer }));\n }\n\n private reportProgress(): void {\n this.options.reportProgress?.({ answers: this.orderedAnswers(), cancelled: false });\n }\n\n private finish(cancelled: boolean): void {\n if (this.settled) return;\n this.settled = true;\n this.options.done({ answers: this.orderedAnswers(), cancelled });\n }\n\n private refresh(): void {\n this.invalidate();\n this.options.tui.requestRender();\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("./externalEditor.cjs"),t=require("./questionnaireComponent.cjs");let n=require("@earendil-works/pi-coding-agent");async function r(r,i,a,o,s){let c,l=()=>c?.cancel();o?.addEventListener(`abort`,l,{once:!0});try{return await r.ui.custom((r,l,u,d)=>(c=new t.QuestionnaireComponent({tui:r,theme:l,markdownTheme:(0,n.getMarkdownTheme)(),keybindings:u,params:i,collapseKey:a,done:d,editExternally:e.editQuestionnaireText,...s?{reportProgress:s}:{}}),o?.aborted&&c.cancel(),c))}finally{o?.removeEventListener(`abort`,l)}}exports.runTuiQuestionnaire=r;
2
+ //# sourceMappingURL=runQuestionnaire.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runQuestionnaire.cjs","names":["QuestionnaireComponent","getMarkdownTheme","editQuestionnaireText"],"sources":["../../src/tui/runQuestionnaire.ts"],"sourcesContent":["import { getMarkdownTheme, type ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { editQuestionnaireText } from './externalEditor.js';\nimport type { QuestionParams } from '../schemas/questionnaire.js';\nimport type { QuestionnaireResult } from '../types/questionnaire.js';\nimport { QuestionnaireComponent } from './questionnaireComponent.js';\n\nexport async function runTuiQuestionnaire(\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult | undefined> {\n let component: QuestionnaireComponent | undefined;\n const cancel = (): void => component?.cancel();\n signal?.addEventListener('abort', cancel, { once: true });\n try {\n return await context.ui.custom<QuestionnaireResult>((tui, theme, keybindings, done) => {\n component = new QuestionnaireComponent({\n tui,\n theme,\n markdownTheme: getMarkdownTheme(),\n keybindings,\n params,\n collapseKey,\n done,\n editExternally: editQuestionnaireText,\n ...(reportProgress ? { reportProgress } : {}),\n });\n if (signal?.aborted) component.cancel();\n return component;\n });\n } finally {\n signal?.removeEventListener('abort', cancel);\n }\n}\n"],"mappings":"mIAMA,eAAsB,EACpB,EACA,EACA,EACA,EACA,EAC0C,CAC1C,IAAI,EACE,MAAqB,GAAW,OAAO,EAC7C,GAAQ,iBAAiB,QAAS,EAAQ,CAAE,KAAM,EAAK,CAAC,EACxD,GAAI,CACF,OAAO,MAAM,EAAQ,GAAG,QAA6B,EAAK,EAAO,EAAa,KAC5E,EAAY,IAAIA,EAAAA,uBAAuB,CACrC,MACA,QACA,eAAA,EAAeC,EAAAA,iBAAAA,CAAiB,EAChC,cACA,SACA,cACA,OACA,eAAgBC,EAAAA,sBAChB,GAAI,EAAiB,CAAE,gBAAe,EAAI,CAAC,CAC7C,CAAC,EACG,GAAQ,SAAS,EAAU,OAAO,EAC/B,EACR,CACH,QAAU,CACR,GAAQ,oBAAoB,QAAS,CAAM,CAC7C,CACF"}
@@ -0,0 +1,2 @@
1
+ import{editQuestionnaireText as e}from"./externalEditor.mjs";import{QuestionnaireComponent as t}from"./questionnaireComponent.mjs";import{getMarkdownTheme as n}from"@earendil-works/pi-coding-agent";async function r(r,i,a,o,s){let c,l=()=>c?.cancel();o?.addEventListener(`abort`,l,{once:!0});try{return await r.ui.custom((r,l,u,d)=>(c=new t({tui:r,theme:l,markdownTheme:n(),keybindings:u,params:i,collapseKey:a,done:d,editExternally:e,...s?{reportProgress:s}:{}}),o?.aborted&&c.cancel(),c))}finally{o?.removeEventListener(`abort`,l)}}export{r as runTuiQuestionnaire};
2
+ //# sourceMappingURL=runQuestionnaire.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runQuestionnaire.mjs","names":[],"sources":["../../src/tui/runQuestionnaire.ts"],"sourcesContent":["import { getMarkdownTheme, type ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { editQuestionnaireText } from './externalEditor.js';\nimport type { QuestionParams } from '../schemas/questionnaire.js';\nimport type { QuestionnaireResult } from '../types/questionnaire.js';\nimport { QuestionnaireComponent } from './questionnaireComponent.js';\n\nexport async function runTuiQuestionnaire(\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n): Promise<QuestionnaireResult | undefined> {\n let component: QuestionnaireComponent | undefined;\n const cancel = (): void => component?.cancel();\n signal?.addEventListener('abort', cancel, { once: true });\n try {\n return await context.ui.custom<QuestionnaireResult>((tui, theme, keybindings, done) => {\n component = new QuestionnaireComponent({\n tui,\n theme,\n markdownTheme: getMarkdownTheme(),\n keybindings,\n params,\n collapseKey,\n done,\n editExternally: editQuestionnaireText,\n ...(reportProgress ? { reportProgress } : {}),\n });\n if (signal?.aborted) component.cancel();\n return component;\n });\n } finally {\n signal?.removeEventListener('abort', cancel);\n }\n}\n"],"mappings":"sMAMA,eAAsB,EACpB,EACA,EACA,EACA,EACA,EAC0C,CAC1C,IAAI,EACE,MAAqB,GAAW,OAAO,EAC7C,GAAQ,iBAAiB,QAAS,EAAQ,CAAE,KAAM,EAAK,CAAC,EACxD,GAAI,CACF,OAAO,MAAM,EAAQ,GAAG,QAA6B,EAAK,EAAO,EAAa,KAC5E,EAAY,IAAI,EAAuB,CACrC,MACA,QACA,cAAe,EAAiB,EAChC,cACA,SACA,cACA,OACA,eAAgB,EAChB,GAAI,EAAiB,CAAE,gBAAe,EAAI,CAAC,CAC7C,CAAC,EACG,GAAQ,SAAS,EAAU,OAAO,EAC/B,EACR,CACH,QAAU,CACR,GAAQ,oBAAoB,QAAS,CAAM,CAC7C,CACF"}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@agimon-ai/doompi-user-feedback",
3
+ "version": "0.0.1-alpha.0",
4
+ "description": "Structured user feedback for DoomPi agents",
5
+ "homepage": "https://agimon.ai",
6
+ "license": "MIT",
7
+ "author": "Vuong Ngo",
8
+ "files": [
9
+ "dist",
10
+ "README.md",
11
+ "LICENSE",
12
+ "package.json"
13
+ ],
14
+ "type": "module",
15
+ "main": "./dist/index.cjs",
16
+ "types": "./dist/index.d.mts",
17
+ "jsnext:main": "./dist/index.mjs",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.mts",
21
+ "import": "./dist/index.mjs",
22
+ "require": "./dist/index.cjs"
23
+ },
24
+ "./events": {
25
+ "types": "./dist/events.d.mts",
26
+ "import": "./dist/events.mjs",
27
+ "require": "./dist/events.cjs"
28
+ },
29
+ "./extensions/pi": {
30
+ "types": "./dist/extensions/pi.d.mts",
31
+ "import": "./dist/extensions/pi.mjs",
32
+ "require": "./dist/extensions/pi.cjs"
33
+ },
34
+ "./package.json": "./package.json"
35
+ },
36
+ "publishConfig": {
37
+ "access": "public"
38
+ },
39
+ "dependencies": {
40
+ "typebox": "1.1.38",
41
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.11"
42
+ },
43
+ "devDependencies": {
44
+ "@earendil-works/pi-coding-agent": "0.84.1",
45
+ "@earendil-works/pi-tui": "0.84.1",
46
+ "@types/node": "26.1.1",
47
+ "@vitest/coverage-v8": "4.1.10",
48
+ "tsdown": "0.22.14",
49
+ "typescript": "6.0.3",
50
+ "vitest": "4.1.10"
51
+ },
52
+ "peerDependencies": {
53
+ "@earendil-works/pi-coding-agent": "0.84.1",
54
+ "@earendil-works/pi-tui": "0.84.1"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@earendil-works/pi-coding-agent": {
58
+ "optional": true
59
+ },
60
+ "@earendil-works/pi-tui": {
61
+ "optional": true
62
+ }
63
+ },
64
+ "engines": {
65
+ "node": ">=22.19.0"
66
+ },
67
+ "pi": {
68
+ "extensions": [
69
+ "./dist/extensions/pi.mjs"
70
+ ]
71
+ },
72
+ "scripts": {
73
+ "build": "tsdown",
74
+ "test": "vitest --run",
75
+ "typecheck": "tsc --noEmit",
76
+ "lint": "oxlint . && oxfmt . --check",
77
+ "fixcode": "oxlint . --fix && oxfmt ."
78
+ }
79
+ }