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

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 (32) hide show
  1. package/README.md +53 -9
  2. package/dist/adapters/doom/voiceQuestionHandoff.cjs +2 -2
  3. package/dist/adapters/doom/voiceQuestionHandoff.cjs.map +1 -1
  4. package/dist/adapters/doom/voiceQuestionHandoff.d.cts +32 -0
  5. package/dist/adapters/doom/voiceQuestionHandoff.d.cts.map +1 -0
  6. package/dist/adapters/doom/voiceQuestionHandoff.d.mts +32 -0
  7. package/dist/adapters/doom/voiceQuestionHandoff.d.mts.map +1 -0
  8. package/dist/adapters/doom/voiceQuestionHandoff.mjs +2 -2
  9. package/dist/adapters/doom/voiceQuestionHandoff.mjs.map +1 -1
  10. package/dist/adapters/pi/askUserQuestionAdapter.cjs +3 -3
  11. package/dist/adapters/pi/askUserQuestionAdapter.cjs.map +1 -1
  12. package/dist/adapters/pi/askUserQuestionAdapter.mjs +3 -3
  13. package/dist/adapters/pi/askUserQuestionAdapter.mjs.map +1 -1
  14. package/dist/extensions/pi.cjs +1 -1
  15. package/dist/extensions/pi.cjs.map +1 -1
  16. package/dist/extensions/pi.d.cts.map +1 -1
  17. package/dist/extensions/pi.d.mts.map +1 -1
  18. package/dist/extensions/pi.mjs +1 -1
  19. package/dist/extensions/pi.mjs.map +1 -1
  20. package/dist/schemas/questionnaire.d.cts +18 -0
  21. package/dist/schemas/questionnaire.d.cts.map +1 -0
  22. package/dist/schemas/questionnaire.d.mts +18 -0
  23. package/dist/schemas/questionnaire.d.mts.map +1 -0
  24. package/dist/types/questionnaire.d.cts +31 -0
  25. package/dist/types/questionnaire.d.cts.map +1 -0
  26. package/dist/types/questionnaire.d.mts +31 -0
  27. package/dist/types/questionnaire.d.mts.map +1 -0
  28. package/dist/voiceQuestionHandoff.cjs +1 -0
  29. package/dist/voiceQuestionHandoff.d.cts +4 -0
  30. package/dist/voiceQuestionHandoff.d.mts +4 -0
  31. package/dist/voiceQuestionHandoff.mjs +1 -0
  32. package/package.json +19 -6
package/README.md CHANGED
@@ -1,18 +1,62 @@
1
1
  # @agimon-ai/doompi-user-feedback
2
2
 
3
- An independent DoomPi extension for structured `ask_user_question` interactions.
3
+ **Sometimes the agent needs an answer. Make it ask.**
4
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.
5
+ This package provides the structured `ask_user_question` interaction for Pi. In a normal
6
+ terminal session it opens a blocking questionnaire and returns the selected answer to the
7
+ agent. The question does not get buried in prose and the turn does not continue on a
8
+ guess.
6
9
 
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.
10
+ In autonomous Voice mode there is no modal to click. The extension formats the choices as
11
+ plain text, hands them to narration, terminates the turn, and accepts the next spoken reply
12
+ as an ordinary user message.
8
13
 
9
- ## Development
14
+ This implementation is written for Pi and has no dependency on the old Juicesharp
15
+ packages. It keeps the established `rpiv:ask-user:prompt` and `rpiv:ask-user:blocked`
16
+ channel names so existing Doompi listeners can migrate without being rewritten.
17
+
18
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). The bundled
19
+ `ask-user` layer loads it where structured feedback is wanted.
20
+
21
+ ## Install
10
22
 
11
23
  ```bash
12
- pnpm build
13
- pnpm typecheck
14
- pnpm test
15
- pnpm lint
24
+ npm install @agimon-ai/doompi-user-feedback
25
+ ```
26
+
27
+ ## How it loads
28
+
29
+ Put the bare package name in a layer, then add that layer to a major mode:
30
+
31
+ ```yaml
32
+ layers:
33
+ ask-user:
34
+ packages: ['@agimon-ai/doompi-user-feedback']
35
+
36
+ majorMode:
37
+ dev:
38
+ description: Development mode with structured user clarification.
39
+ layers: [ask-user]
16
40
  ```
17
41
 
18
- The Pi extension entrypoint is `@agimon-ai/doompi-user-feedback/extensions/pi`.
42
+ The package's Pi manifest selects the Pi adapter. The explicit `/extensions/pi` subpath
43
+ remains available when an adapter path is required.
44
+
45
+ ## Reusable voice handoff
46
+
47
+ `@agimon-ai/doompi-user-feedback/voice-question-handoff` exposes the same minor-mode
48
+ detection, plain-text formatting, and narration handoff to other tools.
49
+ `createVoiceQuestionHandoff()` returns a session-scoped handle; call `dispose()` when its
50
+ session ends so subscriptions do not outlive their owner.
51
+
52
+ Voice results include the readable question, set `terminate: true`, and mark the turn as
53
+ waiting for a response. The answer then arrives through the normal user-message path.
54
+
55
+ ## Public API
56
+
57
+ The package exports the extension, event names, reusable voice handoff, and Pi adapter
58
+ through supported subpaths.
59
+
60
+ ## License
61
+
62
+ MIT
@@ -1,5 +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(`
1
+ const e=require("../../services/voiceQuestionNarration.cjs");let t=require("@agimon-ai/doompi-extension-contracts/mode"),n=require("@agimon-ai/doompi-extension-contracts/narration"),r=require("@agimon-ai/doompi-extension-contracts/protocol");const i=`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 a(e){return e.descriptor.source===`@agimon-ai/doompi-voice`&&e.descriptor.id===`voice-auto`&&e.state.activation===`active`}function o(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
2
  `)).join(`
3
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;
4
+ `)}var s=class{modes;narration;buildNarration;constructor(e,t,n){this.modes=e,this.narration=t,this.buildNarration=n}handoff(e){if(this.modes.list().some(a))return this.narration.narrate(this.buildNarration(e)),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:o(e)}}};function c(i){let a=(0,t.createMinorModeCatalogClient)(),o=a.bind((0,r.createProtocolRuntime)(i)),c=new s(a,(0,n.createNarrationRequester)(i),e.buildVoiceQuestionNarration),l=!1;return{handoff(e){return l?void 0:c.handoff(e)},dispose(){l||(l=!0,o(),a.dispose())}}}function l(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${i}`:i}],details:e,terminate:!0}}exports.DOOM_VOICE_AUTO_MODE_ID=`voice-auto`,exports.DOOM_VOICE_SOURCE=`@agimon-ai/doompi-voice`,exports.VOICE_WAIT_MESSAGE=i,exports.VoiceQuestionHandoff=s,exports.buildVoiceToolResult=l,exports.createVoiceQuestionHandoff=c,exports.formatVoiceQuestionPrompt=o;
5
5
  //# sourceMappingURL=voiceQuestionHandoff.cjs.map
@@ -1 +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"}
1
+ {"version":3,"file":"voiceQuestionHandoff.cjs","names":["createMinorModeCatalogClient","createProtocolRuntime","createNarrationRequester","buildVoiceQuestionNarration"],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import {\n createMinorModeCatalogClient,\n type MinorModeCatalogClient,\n type MinorModeRecord,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequester, type NarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport { createProtocolRuntime, type ProtocolRuntimeOptions } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.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(record: MinorModeRecord): boolean {\n return (\n record.descriptor.source === DOOM_VOICE_SOURCE &&\n record.descriptor.id === DOOM_VOICE_AUTO_MODE_ID &&\n record.state.activation === 'active'\n );\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<MinorModeCatalogClient, 'list'>,\n private readonly narration: NarrationRequester,\n private readonly buildNarration: (params: QuestionParams) => string,\n ) {}\n\n handoff(params: QuestionParams): QuestionnaireResult | undefined {\n if (!this.modes.list().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 interface VoiceQuestionHandoffHandle {\n handoff(params: QuestionParams): QuestionnaireResult | undefined;\n dispose(): void;\n}\n\n/**\n * Bind one session-scoped autonomous-voice handoff.\n *\n * The returned handle owns the external catalog subscription and must be\n * disposed when its session is replaced or shut down.\n */\nexport function createVoiceQuestionHandoff(options: ProtocolRuntimeOptions): VoiceQuestionHandoffHandle {\n const modes = createMinorModeCatalogClient();\n const disposeBinding = modes.bind(createProtocolRuntime(options));\n const handoff = new VoiceQuestionHandoff(modes, createNarrationRequester(options), buildVoiceQuestionNarration);\n let disposed = false;\n\n return {\n handoff(params) {\n return disposed ? undefined : handoff.handoff(params);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n disposeBinding();\n modes.dispose();\n },\n };\n}\n\nexport function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult {\n const prompt = details.voicePrompt?.trim();\n return {\n content: [{ type: 'text', text: prompt ? `${prompt}\\n\\n${VOICE_WAIT_MESSAGE}` : VOICE_WAIT_MESSAGE }],\n details,\n terminate: true,\n };\n}\n"],"mappings":"kPAWA,MAEa,EACX,iJAEF,SAAS,EAAkB,EAAkC,CAC3D,OACE,EAAO,WAAW,SAAA,2BAClB,EAAO,WAAW,KAAA,cAClB,EAAO,MAAM,aAAe,QAEhC,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,KAAK,CAAC,CAAC,KAAK,CAAiB,EAE7C,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,EAaA,SAAgB,EAA2B,EAA6D,CACtG,IAAM,GAAA,EAAQA,EAAAA,6BAAAA,CAA6B,EACrC,EAAiB,EAAM,MAAA,EAAKC,EAAAA,sBAAAA,CAAsB,CAAO,CAAC,EAC1D,EAAU,IAAI,EAAqB,GAAA,EAAOC,EAAAA,yBAAAA,CAAyB,CAAO,EAAGC,EAAAA,2BAA2B,EAC1G,EAAW,GAEf,MAAO,CACL,QAAQ,EAAQ,CACd,OAAO,EAAW,IAAA,GAAY,EAAQ,QAAQ,CAAM,CACtD,EACA,SAAU,CACJ,IACJ,EAAW,GACX,EAAe,EACf,EAAM,QAAQ,EAChB,CACF,CACF,CAEA,SAAgB,EAAqB,EAA8C,CACjF,IAAM,EAAS,EAAQ,aAAa,KAAK,EACzC,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,EAAS,GAAG,EAAO,MAAM,IAAuB,CAAmB,CAAC,EACpG,UACA,UAAW,EACb,CACF"}
@@ -0,0 +1,32 @@
1
+ import { QuestionParams } from "../../schemas/questionnaire.cjs";
2
+ import { QuestionnaireResult, ToolTextResult } from "../../types/questionnaire.cjs";
3
+ import { MinorModeCatalogClient } from "@agimon-ai/doompi-extension-contracts/mode";
4
+ import { NarrationRequester } from "@agimon-ai/doompi-extension-contracts/narration";
5
+ import { ProtocolRuntimeOptions } from "@agimon-ai/doompi-extension-contracts/protocol";
6
+ //#region src/adapters/doom/voiceQuestionHandoff.d.ts
7
+ declare const DOOM_VOICE_SOURCE = "@agimon-ai/doompi-voice";
8
+ declare const DOOM_VOICE_AUTO_MODE_ID = "voice-auto";
9
+ declare const VOICE_WAIT_MESSAGE = "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.";
10
+ declare function formatVoiceQuestionPrompt(params: QuestionParams): string;
11
+ declare class VoiceQuestionHandoff {
12
+ private readonly modes;
13
+ private readonly narration;
14
+ private readonly buildNarration;
15
+ constructor(modes: Pick<MinorModeCatalogClient, 'list'>, narration: NarrationRequester, buildNarration: (params: QuestionParams) => string);
16
+ handoff(params: QuestionParams): QuestionnaireResult | undefined;
17
+ }
18
+ interface VoiceQuestionHandoffHandle {
19
+ handoff(params: QuestionParams): QuestionnaireResult | undefined;
20
+ dispose(): void;
21
+ }
22
+ /**
23
+ * Bind one session-scoped autonomous-voice handoff.
24
+ *
25
+ * The returned handle owns the external catalog subscription and must be
26
+ * disposed when its session is replaced or shut down.
27
+ */
28
+ declare function createVoiceQuestionHandoff(options: ProtocolRuntimeOptions): VoiceQuestionHandoffHandle;
29
+ declare function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult;
30
+ //#endregion
31
+ export { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt };
32
+ //# sourceMappingURL=voiceQuestionHandoff.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionHandoff.d.cts","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"mappings":";;;;;;cAWa;cACA;cACA;iBAWG,0BAA0B,QAAQ;cAWrC;mBAEQ;mBACA;mBACA;EAFA,YAAA,OAAO,KAAK,iCACZ,WAAW,oBACX,iBAAiB,QAAQ;EAG5C,QAAQ,QAAQ,iBAAiB;;UAalB;EACf,QAAQ,QAAQ,iBAAiB;EACjC;;;;;;;;iBASc,2BAA2B,SAAS,yBAAyB;iBAmB7D,qBAAqB,SAAS,sBAAsB"}
@@ -0,0 +1,32 @@
1
+ import { QuestionParams } from "../../schemas/questionnaire.mjs";
2
+ import { QuestionnaireResult, ToolTextResult } from "../../types/questionnaire.mjs";
3
+ import { MinorModeCatalogClient } from "@agimon-ai/doompi-extension-contracts/mode";
4
+ import { NarrationRequester } from "@agimon-ai/doompi-extension-contracts/narration";
5
+ import { ProtocolRuntimeOptions } from "@agimon-ai/doompi-extension-contracts/protocol";
6
+ //#region src/adapters/doom/voiceQuestionHandoff.d.ts
7
+ declare const DOOM_VOICE_SOURCE = "@agimon-ai/doompi-voice";
8
+ declare const DOOM_VOICE_AUTO_MODE_ID = "voice-auto";
9
+ declare const VOICE_WAIT_MESSAGE = "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.";
10
+ declare function formatVoiceQuestionPrompt(params: QuestionParams): string;
11
+ declare class VoiceQuestionHandoff {
12
+ private readonly modes;
13
+ private readonly narration;
14
+ private readonly buildNarration;
15
+ constructor(modes: Pick<MinorModeCatalogClient, 'list'>, narration: NarrationRequester, buildNarration: (params: QuestionParams) => string);
16
+ handoff(params: QuestionParams): QuestionnaireResult | undefined;
17
+ }
18
+ interface VoiceQuestionHandoffHandle {
19
+ handoff(params: QuestionParams): QuestionnaireResult | undefined;
20
+ dispose(): void;
21
+ }
22
+ /**
23
+ * Bind one session-scoped autonomous-voice handoff.
24
+ *
25
+ * The returned handle owns the external catalog subscription and must be
26
+ * disposed when its session is replaced or shut down.
27
+ */
28
+ declare function createVoiceQuestionHandoff(options: ProtocolRuntimeOptions): VoiceQuestionHandoffHandle;
29
+ declare function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult;
30
+ //#endregion
31
+ export { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt };
32
+ //# sourceMappingURL=voiceQuestionHandoff.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceQuestionHandoff.d.mts","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"mappings":";;;;;;cAWa;cACA;cACA;iBAWG,0BAA0B,QAAQ;cAWrC;mBAEQ;mBACA;mBACA;EAFA,YAAA,OAAO,KAAK,iCACZ,WAAW,oBACX,iBAAiB,QAAQ;EAG5C,QAAQ,QAAQ,iBAAiB;;UAalB;EACf,QAAQ,QAAQ,iBAAiB;EACjC;;;;;;;;iBASc,2BAA2B,SAAS,yBAAyB;iBAmB7D,qBAAqB,SAAS,sBAAsB"}
@@ -1,5 +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(`
1
+ import{buildVoiceQuestionNarration as e}from"../../services/voiceQuestionNarration.mjs";import{createMinorModeCatalogClient as t}from"@agimon-ai/doompi-extension-contracts/mode";import{createNarrationRequester as n}from"@agimon-ai/doompi-extension-contracts/narration";import{createProtocolRuntime as r}from"@agimon-ai/doompi-extension-contracts/protocol";const i=`@agimon-ai/doompi-voice`,a=`voice-auto`,o=`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 s(e){return e.descriptor.source===`@agimon-ai/doompi-voice`&&e.descriptor.id===`voice-auto`&&e.state.activation===`active`}function c(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
2
  `)).join(`
3
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};
4
+ `)}var l=class{modes;narration;buildNarration;constructor(e,t,n){this.modes=e,this.narration=t,this.buildNarration=n}handoff(e){if(this.modes.list().some(s))return this.narration.narrate(this.buildNarration(e)),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:c(e)}}};function u(i){let a=t(),o=a.bind(r(i)),s=new l(a,n(i),e),c=!1;return{handoff(e){return c?void 0:s.handoff(e)},dispose(){c||(c=!0,o(),a.dispose())}}}function d(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${o}`:o}],details:e,terminate:!0}}export{a as DOOM_VOICE_AUTO_MODE_ID,i as DOOM_VOICE_SOURCE,o as VOICE_WAIT_MESSAGE,l as VoiceQuestionHandoff,d as buildVoiceToolResult,u as createVoiceQuestionHandoff,c as formatVoiceQuestionPrompt};
5
5
  //# sourceMappingURL=voiceQuestionHandoff.mjs.map
@@ -1 +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"}
1
+ {"version":3,"file":"voiceQuestionHandoff.mjs","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import {\n createMinorModeCatalogClient,\n type MinorModeCatalogClient,\n type MinorModeRecord,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequester, type NarrationRequester } from '@agimon-ai/doompi-extension-contracts/narration';\nimport { createProtocolRuntime, type ProtocolRuntimeOptions } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.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(record: MinorModeRecord): boolean {\n return (\n record.descriptor.source === DOOM_VOICE_SOURCE &&\n record.descriptor.id === DOOM_VOICE_AUTO_MODE_ID &&\n record.state.activation === 'active'\n );\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<MinorModeCatalogClient, 'list'>,\n private readonly narration: NarrationRequester,\n private readonly buildNarration: (params: QuestionParams) => string,\n ) {}\n\n handoff(params: QuestionParams): QuestionnaireResult | undefined {\n if (!this.modes.list().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 interface VoiceQuestionHandoffHandle {\n handoff(params: QuestionParams): QuestionnaireResult | undefined;\n dispose(): void;\n}\n\n/**\n * Bind one session-scoped autonomous-voice handoff.\n *\n * The returned handle owns the external catalog subscription and must be\n * disposed when its session is replaced or shut down.\n */\nexport function createVoiceQuestionHandoff(options: ProtocolRuntimeOptions): VoiceQuestionHandoffHandle {\n const modes = createMinorModeCatalogClient();\n const disposeBinding = modes.bind(createProtocolRuntime(options));\n const handoff = new VoiceQuestionHandoff(modes, createNarrationRequester(options), buildVoiceQuestionNarration);\n let disposed = false;\n\n return {\n handoff(params) {\n return disposed ? undefined : handoff.handoff(params);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n disposeBinding();\n modes.dispose();\n },\n };\n}\n\nexport function buildVoiceToolResult(details: QuestionnaireResult): ToolTextResult {\n const prompt = details.voicePrompt?.trim();\n return {\n content: [{ type: 'text', text: prompt ? `${prompt}\\n\\n${VOICE_WAIT_MESSAGE}` : VOICE_WAIT_MESSAGE }],\n details,\n terminate: true,\n };\n}\n"],"mappings":"oWAWA,MAAa,EAAoB,0BACpB,EAA0B,aAC1B,EACX,iJAEF,SAAS,EAAkB,EAAkC,CAC3D,OACE,EAAO,WAAW,SAAA,2BAClB,EAAO,WAAW,KAAA,cAClB,EAAO,MAAM,aAAe,QAEhC,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,KAAK,CAAC,CAAC,KAAK,CAAiB,EAE7C,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,EAaA,SAAgB,EAA2B,EAA6D,CACtG,IAAM,EAAQ,EAA6B,EACrC,EAAiB,EAAM,KAAK,EAAsB,CAAO,CAAC,EAC1D,EAAU,IAAI,EAAqB,EAAO,EAAyB,CAAO,EAAG,CAA2B,EAC1G,EAAW,GAEf,MAAO,CACL,QAAQ,EAAQ,CACd,OAAO,EAAW,IAAA,GAAY,EAAQ,QAAQ,CAAM,CACtD,EACA,SAAU,CACJ,IACJ,EAAW,GACX,EAAe,EACf,EAAM,QAAQ,EAChB,CACF,CACF,CAEA,SAAgB,EAAqB,EAA8C,CACjF,IAAM,EAAS,EAAQ,aAAa,KAAK,EACzC,MAAO,CACL,QAAS,CAAC,CAAE,KAAM,OAAQ,KAAM,EAAS,GAAG,EAAO,MAAM,IAAuB,CAAmB,CAAC,EACpG,UACA,UAAW,EACb,CACF"}
@@ -1,4 +1,4 @@
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:
1
+ const e=require("../doom/voiceQuestionHandoff.cjs"),t=require("../../schemas/questionnaire.cjs"),n=require("../../services/responseService.cjs"),r=require("../../services/validationService.cjs"),i=require("../config/configAdapter.cjs"),a=require("./rpcQuestionnaireAdapter.cjs");let o=require("@agimon-ai/doompi-extension-contracts"),s=require("@agimon-ai/doompi-ui/toolChrome");const c=`ask_user_question`,l=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,u=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function d(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};(0,o.publishAskUserPrompt)(e.events,n)}function f(e,t){let n={active:t};(0,o.publishAskUserBlocked)(e.events,n)}function p(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function m(o,m){let h=i.loadUserFeedbackConfig().guidance;o.registerTool({name:c,label:`Ask User Question`,renderShell:`self`,description:`Ask the user one or more structured questions during execution. Use when you need to:
2
2
  1. Gather user preferences or requirements
3
3
  2. Clarify ambiguous instructions
4
4
  3. Get decisions on implementation choices as you work
@@ -10,6 +10,6 @@ Usage notes:
10
10
  - If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
11
11
 
12
12
  Preview feature:
13
- Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:h?.promptSnippet??l,promptGuidelines:h?.promptGuidelines??u,parameters: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;
13
+ Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:h?.promptSnippet??l,promptGuidelines:h?.promptGuidelines??u,parameters:t.QuestionParamsSchema,executionMode:`sequential`,async execute(t,s,c,l,u){let h=s;if(!u.hasUI)return n.buildToolResult(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let g=r.validateQuestionnaire(h);if(!g.ok)return n.buildToolResult(g.message,{answers:[],cancelled:!0,error:g.error});try{let t=await m.enqueue(async({signal:e,reportProgress:t})=>{d(o,h);let n=m.tryVoice?.(h);if(n)return n;let r=!0,s=()=>{r&&(r=!1,f(o,!1))};e.addEventListener(`abort`,s,{once:!0}),f(o,!0);try{if(u.mode===`rpc`&&p(u))return await a.runRpcQuestionnaire(u,h,e,t);let n=await m.runTui(u,h,i.resolveCollapseKey(i.loadUserFeedbackConfig()),e,t);return n===void 0?p(u)?await a.runRpcQuestionnaire(u,h,e,t):{answers:[],cancelled:!0,error:`no_custom_ui`}:n}finally{e.removeEventListener(`abort`,s),s()}},c);return t.delivery===`voice`?e.buildVoiceToolResult(t):t.error===`no_custom_ui`?n.buildToolResult(`Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.`,t):n.buildQuestionnaireResponse(t,h)}catch(e){let t=e instanceof Error?e.message:String(e);return n.buildToolResult(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=`${n.questions.length} question${n.questions.length===1?``:`s`}`,a=(0,s.renderToolHeading)(`ask`,i,t);return new s.DoomToolCall(r?`${a} ${t.fg(`dim`,`(${r})`)}`:a)},renderResult(e,t,n,r){let i=e.details;if(!i){let t=e.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
14
+ `),i=r.isError?`error`:`toolOutput`,a=r.isError?`✗ failed`:`✓ submitted`;return new s.DoomToolResult([n.fg(i,t||a)],n,{wrap:!0})}if(i.cancelled)return new s.DoomToolResult([n.fg(`warning`,`◐`)+n.fg(`dim`,` cancelled`)],n);if(i.delivery===`voice`&&i.voicePrompt)return new s.DoomToolResult([n.fg(`toolOutput`,i.voicePrompt)],n,{wrap:!0});let a=i.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${n.fg(`toolOutput`,t??``)}`});return a.length===0&&a.push(n.fg(`success`,`✓`)+n.fg(`dim`,` submitted`)),new s.DoomToolResult(a,n,{wrap:!0})}})}exports.ASK_USER_QUESTION_TOOL_NAME=c,exports.DEFAULT_PROMPT_GUIDELINES=u,exports.DEFAULT_PROMPT_SNIPPET=l,exports.registerAskUserQuestionTool=m;
15
15
  //# sourceMappingURL=askUserQuestionAdapter.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"askUserQuestionAdapter.cjs","names":["loadUserFeedbackConfig","QuestionParamsSchema","buildToolResult","validateQuestionnaire","runRpcQuestionnaire","resolveCollapseKey","buildVoiceToolResult","buildQuestionnaireResponse","Text"],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"askUserQuestionAdapter.cjs","names":["loadUserFeedbackConfig","QuestionParamsSchema","buildToolResult","validateQuestionnaire","runRpcQuestionnaire","resolveCollapseKey","buildVoiceToolResult","buildQuestionnaireResponse","renderToolHeading","DoomToolCall","DoomToolResult"],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport { DoomToolCall, DoomToolResult, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n renderShell: 'self',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n const heading = renderToolHeading('ask', count, theme);\n return new DoomToolCall(headers ? `${heading} ${theme.fg('dim', `(${headers})`)}` : heading);\n },\n\n renderResult(result, _options, theme, context) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const raw = result.content\n .filter((block) => block.type === 'text')\n .map((block) => block.text)\n .join('\\n');\n const color = context.isError ? 'error' : 'toolOutput';\n const fallback = context.isError ? '✗ failed' : '✓ submitted';\n return new DoomToolResult([theme.fg(color, raw || fallback)], theme, { wrap: true });\n }\n if (details.cancelled) {\n return new DoomToolResult([theme.fg('warning', '◐') + theme.fg('dim', ' cancelled')], theme);\n }\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new DoomToolResult([theme.fg('toolOutput', details.voicePrompt)], theme, { wrap: true });\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${theme.fg(\n 'toolOutput',\n scalar ?? '',\n )}`;\n });\n if (lines.length === 0) lines.push(theme.fg('success', '✓') + theme.fg('dim', ' submitted'));\n return new DoomToolResult(lines, theme, { wrap: true });\n },\n });\n}\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{buildVoiceToolResult as e}from"../doom/voiceQuestionHandoff.mjs";import{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:
1
+ import{buildVoiceToolResult as e}from"../doom/voiceQuestionHandoff.mjs";import{QuestionParamsSchema as t}from"../../schemas/questionnaire.mjs";import{buildQuestionnaireResponse as n,buildToolResult as r}from"../../services/responseService.mjs";import{validateQuestionnaire as i}from"../../services/validationService.mjs";import{loadUserFeedbackConfig as a,resolveCollapseKey as o}from"../config/configAdapter.mjs";import{runRpcQuestionnaire as s}from"./rpcQuestionnaireAdapter.mjs";import{publishAskUserBlocked as c,publishAskUserPrompt as l}from"@agimon-ai/doompi-extension-contracts";import{DoomToolCall as u,DoomToolResult as d,renderToolHeading as f}from"@agimon-ai/doompi-ui/toolChrome";const p=`ask_user_question`,m=`Ask the user up to 4 structured questions (2-4 options each) when requirements are ambiguous`,h=[`Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to 4 questions per invocation.`,`Each question MUST have 2-4 options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended "Type something." row on every question, or press Esc to abandon the questionnaire. Do NOT author "Other" or "Type something." labels yourself — reserved labels are rejected at runtime.`,`Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append "(Recommended)" to its label.`,`Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.`];function g(e,t){let n={questions:t.questions.map(e=>({question:e.question,header:e.header,multiSelect:e.multiSelect??!1,options:e.options.map(e=>({label:e.label,description:e.description,hasPreview:!!e.preview}))}))};l(e.events,n)}function _(e,t){let n={active:t};c(e.events,n)}function v(e){let t=e.ui;return typeof t.select==`function`&&typeof t.input==`function`}function y(c,l){let y=a().guidance;c.registerTool({name:p,label:`Ask User Question`,renderShell:`self`,description:`Ask the user one or more structured questions during execution. Use when you need to:
2
2
  1. Gather user preferences or requirements
3
3
  2. Clarify ambiguous instructions
4
4
  3. Get decisions on implementation choices as you work
@@ -10,6 +10,6 @@ Usage notes:
10
10
  - If you recommend a specific option, make that the first option and add "(Recommended)" at the end of the label.
11
11
 
12
12
  Preview feature:
13
- Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:_?.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};
13
+ Use the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,promptSnippet:y?.promptSnippet??m,promptGuidelines:y?.promptGuidelines??h,parameters:t,executionMode:`sequential`,async execute(t,u,d,f,p){let m=u;if(!p.hasUI)return r(`Error: UI not available (running in non-interactive mode)`,{answers:[],cancelled:!0,error:`no_ui`});let h=i(m);if(!h.ok)return r(h.message,{answers:[],cancelled:!0,error:h.error});try{let t=await l.enqueue(async({signal:e,reportProgress:t})=>{g(c,m);let n=l.tryVoice?.(m);if(n)return n;let r=!0,i=()=>{r&&(r=!1,_(c,!1))};e.addEventListener(`abort`,i,{once:!0}),_(c,!0);try{if(p.mode===`rpc`&&v(p))return await s(p,m,e,t);let n=await l.runTui(p,m,o(a()),e,t);return n===void 0?v(p)?await s(p,m,e,t):{answers:[],cancelled:!0,error:`no_custom_ui`}:n}finally{e.removeEventListener(`abort`,i),i()}},d);return t.delivery===`voice`?e(t):t.error===`no_custom_ui`?r(`Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.`,t):n(t,m)}catch(e){let t=e instanceof Error?e.message:String(e);return r(`Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead. (cause: ${t})`,{answers:[],cancelled:!0,error:`session_load_failed`})}},renderCall(e,t){let n=e,r=n.questions.map(e=>e.header).join(`, `),i=`${n.questions.length} question${n.questions.length===1?``:`s`}`,a=f(`ask`,i,t);return new u(r?`${a} ${t.fg(`dim`,`(${r})`)}`:a)},renderResult(e,t,n,r){let i=e.details;if(!i){let t=e.content.filter(e=>e.type===`text`).map(e=>e.text).join(`
14
+ `),i=r.isError?`error`:`toolOutput`,a=r.isError?`✗ failed`:`✓ submitted`;return new d([n.fg(i,t||a)],n,{wrap:!0})}if(i.cancelled)return new d([n.fg(`warning`,`◐`)+n.fg(`dim`,` cancelled`)],n);if(i.delivery===`voice`&&i.voicePrompt)return new d([n.fg(`toolOutput`,i.voicePrompt)],n,{wrap:!0});let a=i.answers.map(e=>{let t=e.kind===`multi`?(e.selected??[]).join(`, `):e.answer;return`${n.fg(`success`,`✓`)} ${n.fg(`accent`,e.question)}: ${n.fg(`toolOutput`,t??``)}`});return a.length===0&&a.push(n.fg(`success`,`✓`)+n.fg(`dim`,` submitted`)),new d(a,n,{wrap:!0})}})}export{p as ASK_USER_QUESTION_TOOL_NAME,h as DEFAULT_PROMPT_GUIDELINES,m as DEFAULT_PROMPT_SNIPPET,y as registerAskUserQuestionTool};
15
15
  //# sourceMappingURL=askUserQuestionAdapter.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"askUserQuestionAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { Text } from '@earendil-works/pi-tui';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const title = theme.fg('toolTitle', theme.bold(ASK_USER_QUESTION_TOOL_NAME));\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n return new Text(\n `${title} ${theme.fg('muted', count)}${headers ? ` ${theme.fg('dim', `(${headers})`)}` : ''}`,\n 0,\n 0,\n );\n },\n\n renderResult(result, _options, theme) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const content = result.content[0];\n return new Text(content?.type === 'text' ? content.text : '', 0, 0);\n }\n if (details.cancelled) return new Text(theme.fg('warning', 'Questionnaire cancelled'), 0, 0);\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new Text(theme.fg('text', details.voicePrompt), 0, 0);\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${scalar ?? ''}`;\n });\n return new Text(lines.join('\\n'), 0, 0);\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"askUserQuestionAdapter.mjs","names":[],"sources":["../../../src/adapters/pi/askUserQuestionAdapter.ts"],"sourcesContent":["import { publishAskUserBlocked, publishAskUserPrompt } from '@agimon-ai/doompi-extension-contracts';\nimport { DoomToolCall, DoomToolResult, renderToolHeading } from '@agimon-ai/doompi-ui/toolChrome';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { AskUserBlockedEventPayload, AskUserPromptEventPayload } from '../../schemas/events.js';\nimport {\n MAX_OPTIONS,\n MAX_QUESTIONS,\n MIN_OPTIONS,\n type QuestionParams,\n QuestionParamsSchema,\n} from '../../schemas/questionnaire.js';\nimport type { QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport { buildQuestionnaireResponse, buildToolResult } from '../../services/responseService.js';\nimport { validateQuestionnaire } from '../../services/validationService.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { loadUserFeedbackConfig, resolveCollapseKey } from '../config/configAdapter.js';\nimport { buildVoiceToolResult } from '../doom/voiceQuestionHandoff.js';\nimport { runRpcQuestionnaire } from './rpcQuestionnaireAdapter.js';\n\nexport const ASK_USER_QUESTION_TOOL_NAME = 'ask_user_question';\n\nexport const DEFAULT_PROMPT_SNIPPET = `Ask the user up to ${MAX_QUESTIONS} structured questions (${MIN_OPTIONS}-${MAX_OPTIONS} options each) when requirements are ambiguous`;\nexport const DEFAULT_PROMPT_GUIDELINES = [\n `Use ask_user_question whenever the user's request is underspecified and you cannot proceed without concrete decisions — you can ask up to ${MAX_QUESTIONS} questions per invocation.`,\n `Each question MUST have ${MIN_OPTIONS}-${MAX_OPTIONS} options. Every option requires a concise label (1-5 words) and a description explaining what the choice means or its trade-offs. The user can additionally type a custom answer via the automatically appended \"Type something.\" row on every question, or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.`,\n 'Set multiSelect: true when multiple answers are valid. Provide an options[].preview markdown string when an option benefits from richer side-by-side context (mockups, code snippets, diagrams, configs) — single-select only. If you recommend a specific option, make that the first option and append \"(Recommended)\" to its label.',\n 'Do not stack multiple ask_user_question calls back-to-back — group all clarifying questions into one invocation.',\n];\n\nconst ERROR_NO_UI = 'Error: UI not available (running in non-interactive mode)';\nconst ERROR_NO_CUSTOM_UI =\n 'Error: this client cannot render the questionnaire (custom UI is unavailable). The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead, without using this tool.';\nconst ERROR_SESSION_LOAD_FAILED =\n 'Error: the questionnaire UI failed to load. The user never saw the questions — do NOT treat this as a decline. Ask the questions as plain chat text instead.';\n\nexport interface AskUserQuestionToolDependencies {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => Promise<QuestionnaireResult>;\n tryVoice?: (params: QuestionParams) => QuestionnaireResult | undefined;\n runTui: (\n context: ExtensionContext,\n params: QuestionParams,\n collapseKey: string,\n signal?: AbortSignal,\n reportProgress?: (result: QuestionnaireResult) => void,\n ) => Promise<QuestionnaireResult | undefined>;\n}\n\nfunction emitPrompt(pi: ExtensionAPI, params: QuestionParams): void {\n const payload: AskUserPromptEventPayload = {\n questions: params.questions.map((question) => ({\n question: question.question,\n header: question.header,\n multiSelect: question.multiSelect ?? false,\n options: question.options.map((option) => ({\n label: option.label,\n description: option.description,\n hasPreview: Boolean(option.preview),\n })),\n })),\n };\n publishAskUserPrompt(pi.events, payload);\n}\n\nfunction emitBlocked(pi: ExtensionAPI, active: boolean): void {\n const payload: AskUserBlockedEventPayload = { active };\n publishAskUserBlocked(pi.events, payload);\n}\n\nfunction hasDialogUI(context: ExtensionContext): boolean {\n const ui = context.ui as unknown as { select?: unknown; input?: unknown };\n return typeof ui.select === 'function' && typeof ui.input === 'function';\n}\n\nexport function registerAskUserQuestionTool(pi: ExtensionAPI, dependencies: AskUserQuestionToolDependencies): void {\n const configuredGuidance = loadUserFeedbackConfig().guidance;\n pi.registerTool({\n name: ASK_USER_QUESTION_TOOL_NAME,\n label: 'Ask User Question',\n renderShell: 'self',\n description: `Ask the user one or more structured questions during execution. Use when you need to:\n1. Gather user preferences or requirements\n2. Clarify ambiguous instructions\n3. Get decisions on implementation choices as you work\n4. Offer choices to the user about what direction to take\n\nUsage notes:\n- Users can type a custom answer via the automatically appended \"Type something.\" row on every question or press Esc to abandon the questionnaire. Do NOT author \"Other\" or \"Type something.\" labels yourself — reserved labels are rejected at runtime.\n- Use multiSelect: true when multiple answers are valid.\n- If you recommend a specific option, make that the first option and add \"(Recommended)\" at the end of the label.\n\nPreview feature:\nUse the optional preview field for ASCII mockups, code snippets, diagrams, or configuration examples that users should compare visually. Do not use previews for simple preference questions.`,\n promptSnippet: configuredGuidance?.promptSnippet ?? DEFAULT_PROMPT_SNIPPET,\n promptGuidelines: configuredGuidance?.promptGuidelines ?? DEFAULT_PROMPT_GUIDELINES,\n parameters: QuestionParamsSchema,\n executionMode: 'sequential',\n\n async execute(_toolCallId, params, signal, _onUpdate, context) {\n const typed = params as QuestionParams;\n if (!context.hasUI) {\n return buildToolResult(ERROR_NO_UI, { answers: [], cancelled: true, error: 'no_ui' });\n }\n\n const validation = validateQuestionnaire(typed);\n if (!validation.ok) {\n return buildToolResult(validation.message, {\n answers: [],\n cancelled: true,\n error: validation.error,\n });\n }\n\n try {\n const result = await dependencies.enqueue(async ({ signal: activeSignal, reportProgress }) => {\n emitPrompt(pi, typed);\n const voiceResult = dependencies.tryVoice?.(typed);\n if (voiceResult) return voiceResult;\n\n let blocked = true;\n const clearBlocked = (): void => {\n if (!blocked) return;\n blocked = false;\n emitBlocked(pi, false);\n };\n activeSignal.addEventListener('abort', clearBlocked, { once: true });\n emitBlocked(pi, true);\n try {\n if (context.mode === 'rpc' && hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n\n const tuiResult = await dependencies.runTui(\n context,\n typed,\n resolveCollapseKey(loadUserFeedbackConfig()),\n activeSignal,\n reportProgress,\n );\n if (tuiResult !== undefined) return tuiResult;\n if (hasDialogUI(context)) {\n return await runRpcQuestionnaire(context, typed, activeSignal, reportProgress);\n }\n return { answers: [], cancelled: true, error: 'no_custom_ui' };\n } finally {\n activeSignal.removeEventListener('abort', clearBlocked);\n clearBlocked();\n }\n }, signal);\n\n if (result.delivery === 'voice') return buildVoiceToolResult(result);\n if (result.error === 'no_custom_ui') return buildToolResult(ERROR_NO_CUSTOM_UI, result);\n return buildQuestionnaireResponse(result, typed);\n } catch (error) {\n const cause = error instanceof Error ? error.message : String(error);\n return buildToolResult(`${ERROR_SESSION_LOAD_FAILED} (cause: ${cause})`, {\n answers: [],\n cancelled: true,\n error: 'session_load_failed',\n });\n }\n },\n\n renderCall(args, theme) {\n const params = args as QuestionParams;\n const headers = params.questions.map((question) => question.header).join(', ');\n const count = `${params.questions.length} question${params.questions.length === 1 ? '' : 's'}`;\n const heading = renderToolHeading('ask', count, theme);\n return new DoomToolCall(headers ? `${heading} ${theme.fg('dim', `(${headers})`)}` : heading);\n },\n\n renderResult(result, _options, theme, context) {\n const details = result.details as QuestionnaireResult | undefined;\n if (!details) {\n const raw = result.content\n .filter((block) => block.type === 'text')\n .map((block) => block.text)\n .join('\\n');\n const color = context.isError ? 'error' : 'toolOutput';\n const fallback = context.isError ? '✗ failed' : '✓ submitted';\n return new DoomToolResult([theme.fg(color, raw || fallback)], theme, { wrap: true });\n }\n if (details.cancelled) {\n return new DoomToolResult([theme.fg('warning', '◐') + theme.fg('dim', ' cancelled')], theme);\n }\n if (details.delivery === 'voice' && details.voicePrompt) {\n return new DoomToolResult([theme.fg('toolOutput', details.voicePrompt)], theme, { wrap: true });\n }\n const lines = details.answers.map((answer) => {\n const scalar = answer.kind === 'multi' ? (answer.selected ?? []).join(', ') : answer.answer;\n return `${theme.fg('success', '✓')} ${theme.fg('accent', answer.question)}: ${theme.fg(\n 'toolOutput',\n scalar ?? '',\n )}`;\n });\n if (lines.length === 0) lines.push(theme.fg('success', '✓') + theme.fg('dim', ' submitted'));\n return new DoomToolResult(lines, theme, { wrap: true });\n },\n });\n}\n"],"mappings":"orBAmBA,MAAa,EAA8B,oBAE9B,EAAyB,+FACzB,EAA4B,CACvC,wKACA,uaACA,yUACA,kHACF,EAoBA,SAAS,EAAW,EAAkB,EAA8B,CAClE,IAAM,EAAqC,CACzC,UAAW,EAAO,UAAU,IAAK,IAAc,CAC7C,SAAU,EAAS,SACnB,OAAQ,EAAS,OACjB,YAAa,EAAS,aAAe,GACrC,QAAS,EAAS,QAAQ,IAAK,IAAY,CACzC,MAAO,EAAO,MACd,YAAa,EAAO,YACpB,WAAY,EAAQ,EAAO,OAC7B,EAAE,CACJ,EAAE,CACJ,EACA,EAAqB,EAAG,OAAQ,CAAO,CACzC,CAEA,SAAS,EAAY,EAAkB,EAAuB,CAC5D,IAAM,EAAsC,CAAE,QAAO,EACrD,EAAsB,EAAG,OAAQ,CAAO,CAC1C,CAEA,SAAS,EAAY,EAAoC,CACvD,IAAM,EAAK,EAAQ,GACnB,OAAO,OAAO,EAAG,QAAW,YAAc,OAAO,EAAG,OAAU,UAChE,CAEA,SAAgB,EAA4B,EAAkB,EAAqD,CACjH,IAAM,EAAqB,EAAuB,CAAC,CAAC,SACpD,EAAG,aAAa,CACd,KAAM,EACN,MAAO,oBACP,YAAa,OACb,YAAa;;;;;;;;;;;;+LAab,cAAe,GAAoB,eAAiB,EACpD,iBAAkB,GAAoB,kBAAoB,EAC1D,WAAY,EACZ,cAAe,aAEf,MAAM,QAAQ,EAAa,EAAQ,EAAQ,EAAW,EAAS,CAC7D,IAAM,EAAQ,EACd,GAAI,CAAC,EAAQ,MACX,OAAO,EAAgB,4DAAa,CAAE,QAAS,CAAC,EAAG,UAAW,GAAM,MAAO,OAAQ,CAAC,EAGtF,IAAM,EAAa,EAAsB,CAAK,EAC9C,GAAI,CAAC,EAAW,GACd,OAAO,EAAgB,EAAW,QAAS,CACzC,QAAS,CAAC,EACV,UAAW,GACX,MAAO,EAAW,KACpB,CAAC,EAGH,GAAI,CACF,IAAM,EAAS,MAAM,EAAa,QAAQ,MAAO,CAAE,OAAQ,EAAc,oBAAqB,CAC5F,EAAW,EAAI,CAAK,EACpB,IAAM,EAAc,EAAa,WAAW,CAAK,EACjD,GAAI,EAAa,OAAO,EAExB,IAAI,EAAU,GACR,MAA2B,CAC1B,IACL,EAAU,GACV,EAAY,EAAI,EAAK,EACvB,EACA,EAAa,iBAAiB,QAAS,EAAc,CAAE,KAAM,EAAK,CAAC,EACnE,EAAY,EAAI,EAAI,EACpB,GAAI,CACF,GAAI,EAAQ,OAAS,OAAS,EAAY,CAAO,EAC/C,OAAO,MAAM,EAAoB,EAAS,EAAO,EAAc,CAAc,EAG/E,IAAM,EAAY,MAAM,EAAa,OACnC,EACA,EACA,EAAmB,EAAuB,CAAC,EAC3C,EACA,CACF,EAKA,OAJI,IAAc,IAAA,GACd,EAAY,CAAO,EACd,MAAM,EAAoB,EAAS,EAAO,EAAc,CAAc,EAExE,CAAE,QAAS,CAAC,EAAG,UAAW,GAAM,MAAO,cAAe,EAJzB,CAKtC,QAAU,CACR,EAAa,oBAAoB,QAAS,CAAY,EACtD,EAAa,CACf,CACF,EAAG,CAAM,EAIT,OAFI,EAAO,WAAa,QAAgB,EAAqB,CAAM,EAC/D,EAAO,QAAU,eAAuB,EAAgB,2NAAoB,CAAM,EAC/E,EAA2B,EAAQ,CAAK,CACjD,OAAS,EAAO,CACd,IAAM,EAAQ,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACnE,OAAO,EAAgB,wKAAwC,EAAM,GAAI,CACvE,QAAS,CAAC,EACV,UAAW,GACX,MAAO,qBACT,CAAC,CACH,CACF,EAEA,WAAW,EAAM,EAAO,CACtB,IAAM,EAAS,EACT,EAAU,EAAO,UAAU,IAAK,GAAa,EAAS,MAAM,CAAC,CAAC,KAAK,IAAI,EACvE,EAAQ,GAAG,EAAO,UAAU,OAAO,WAAW,EAAO,UAAU,SAAW,EAAI,GAAK,MACnF,EAAU,EAAkB,MAAO,EAAO,CAAK,EACrD,OAAO,IAAI,EAAa,EAAU,GAAG,EAAQ,GAAG,EAAM,GAAG,MAAO,IAAI,EAAQ,EAAE,IAAM,CAAO,CAC7F,EAEA,aAAa,EAAQ,EAAU,EAAO,EAAS,CAC7C,IAAM,EAAU,EAAO,QACvB,GAAI,CAAC,EAAS,CACZ,IAAM,EAAM,EAAO,QAChB,OAAQ,GAAU,EAAM,OAAS,MAAM,CAAC,CACxC,IAAK,GAAU,EAAM,IAAI,CAAC,CAC1B,KAAK;CAAI,EACN,EAAQ,EAAQ,QAAU,QAAU,aACpC,EAAW,EAAQ,QAAU,WAAa,cAChD,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,EAAO,GAAO,CAAQ,CAAC,EAAG,EAAO,CAAE,KAAM,EAAK,CAAC,CACrF,CACA,GAAI,EAAQ,UACV,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,YAAY,CAAC,EAAG,CAAK,EAE7F,GAAI,EAAQ,WAAa,SAAW,EAAQ,YAC1C,OAAO,IAAI,EAAe,CAAC,EAAM,GAAG,aAAc,EAAQ,WAAW,CAAC,EAAG,EAAO,CAAE,KAAM,EAAK,CAAC,EAEhG,IAAM,EAAQ,EAAQ,QAAQ,IAAK,GAAW,CAC5C,IAAM,EAAS,EAAO,OAAS,SAAW,EAAO,UAAY,CAAC,EAAA,CAAG,KAAK,IAAI,EAAI,EAAO,OACrF,MAAO,GAAG,EAAM,GAAG,UAAW,GAAG,EAAE,GAAG,EAAM,GAAG,SAAU,EAAO,QAAQ,EAAE,IAAI,EAAM,GAClF,aACA,GAAU,EACZ,GACF,CAAC,EAED,OADI,EAAM,SAAW,GAAG,EAAM,KAAK,EAAM,GAAG,UAAW,GAAG,EAAI,EAAM,GAAG,MAAO,YAAY,CAAC,EACpF,IAAI,EAAe,EAAO,EAAO,CAAE,KAAM,EAAK,CAAC,CACxD,CACF,CAAC,CACH"}
@@ -1,2 +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;
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=`@agimon-ai/doompi-user-feedback`,a=Symbol.for(`${i}/installed-hosts`);function o(e){let t=globalThis,n=t[a]??new WeakSet;return t[a]=n,!n.has(e)&&(n.add(e),!0)}function s(a){if(!o(a))return;let s=new r.QuestionnaireCoordinator,c,l=()=>{c?.dispose(),c=void 0};t.registerAskUserQuestionTool(a,{enqueue:(e,t)=>s.enqueue(e,t),runTui:async(...e)=>{let{runTuiQuestionnaire:t}=await Promise.resolve().then(()=>require("../tui/runQuestionnaire.cjs"));return t(...e)},tryVoice:e=>c?.handoff(e)}),n.registerAskUserQuestionReconciler(a),a.on(`session_start`,(t,n)=>{s.shutdown(),s=new r.QuestionnaireCoordinator,l(),c=e.createVoiceQuestionHandoff({bus:a.events,source:i,sessionId:n.sessionManager.getSessionId()})}),a.on(`session_shutdown`,()=>{s.shutdown(),l()})}exports.default=s,exports.registerUserFeedbackExtension=s;
2
2
  //# sourceMappingURL=pi.cjs.map
@@ -1 +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"}
1
+ {"version":3,"file":"pi.cjs","names":["QuestionnaireCoordinator","createVoiceQuestionHandoff"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoffHandle } 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';\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 voiceHandoff: VoiceQuestionHandoffHandle | undefined;\n const resetVoiceHandoff = (): void => {\n voiceHandoff?.dispose();\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 voiceHandoff = createVoiceQuestionHandoff({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: context.sessionManager.getSessionId(),\n });\n });\n pi.on('session_shutdown', () => {\n coordinator.shutdown();\n resetVoiceHandoff();\n });\n}\n\nexport { registerUserFeedbackExtension as default };\n"],"mappings":"0TAMM,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,EACE,MAAgC,CACpC,GAAc,QAAQ,EACtB,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,EAAeC,EAAAA,2BAA2B,CACxC,IAAK,EAAG,OACR,OAAQ,EACR,UAAW,EAAQ,eAAe,aAAa,CACjD,CAAC,CACH,CAAC,EACD,EAAG,GAAG,uBAA0B,CAC9B,EAAY,SAAS,EACrB,EAAkB,CACpB,CAAC,CACH"}
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.cts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBA0BgB,8BAA8B,IAAI"}
1
+ {"version":3,"file":"pi.d.cts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBAsBgB,8BAA8B,IAAI"}
@@ -1 +1 @@
1
- {"version":3,"file":"pi.d.mts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBA0BgB,8BAA8B,IAAI"}
1
+ {"version":3,"file":"pi.d.mts","names":[],"sources":["../../src/extensions/pi.ts"],"mappings":";;iBAsBgB,8BAA8B,IAAI"}
@@ -1,2 +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};
1
+ import{createVoiceQuestionHandoff 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";const i=`@agimon-ai/doompi-user-feedback`,a=Symbol.for(`${i}/installed-hosts`);function o(e){let t=globalThis,n=t[a]??new WeakSet;return t[a]=n,!n.has(e)&&(n.add(e),!0)}function s(a){if(!o(a))return;let s=new r,c,l=()=>{c?.dispose(),c=void 0};t(a,{enqueue:(e,t)=>s.enqueue(e,t),runTui:async(...e)=>{let{runTuiQuestionnaire:t}=await import(`../tui/runQuestionnaire.mjs`);return t(...e)},tryVoice:e=>c?.handoff(e)}),n(a),a.on(`session_start`,(t,n)=>{s.shutdown(),s=new r,l(),c=e({bus:a.events,source:i,sessionId:n.sessionManager.getSessionId()})}),a.on(`session_shutdown`,()=>{s.shutdown(),l()})}export{s as default,s as registerUserFeedbackExtension};
2
2
  //# sourceMappingURL=pi.mjs.map
@@ -1 +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"}
1
+ {"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoffHandle } 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';\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 voiceHandoff: VoiceQuestionHandoffHandle | undefined;\n const resetVoiceHandoff = (): void => {\n voiceHandoff?.dispose();\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 voiceHandoff = createVoiceQuestionHandoff({\n bus: pi.events,\n source: PACKAGE_SOURCE,\n sessionId: context.sessionManager.getSessionId(),\n });\n });\n pi.on('session_shutdown', () => {\n coordinator.shutdown();\n resetVoiceHandoff();\n });\n}\n\nexport { registerUserFeedbackExtension as default };\n"],"mappings":"2VAMA,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,EACE,MAAgC,CACpC,GAAc,QAAQ,EACtB,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,EAAe,EAA2B,CACxC,IAAK,EAAG,OACR,OAAQ,EACR,UAAW,EAAQ,eAAe,aAAa,CACjD,CAAC,CACH,CAAC,EACD,EAAG,GAAG,uBAA0B,CAC9B,EAAY,SAAS,EACrB,EAAkB,CACpB,CAAC,CACH"}
@@ -0,0 +1,18 @@
1
+ import { Static, Type } from "typebox";
2
+ //#region src/schemas/questionnaire.d.ts
3
+ declare const QuestionParamsSchema: Type.TObject<{
4
+ questions: Type.TArray<Type.TObject<{
5
+ question: Type.TString;
6
+ header: Type.TString;
7
+ options: Type.TArray<Type.TObject<{
8
+ label: Type.TString;
9
+ description: Type.TString;
10
+ preview: Type.TOptional<Type.TString>;
11
+ }>>;
12
+ multiSelect: Type.TOptional<Type.TBoolean>;
13
+ }>>;
14
+ }>;
15
+ type QuestionParams = Static<typeof QuestionParamsSchema>;
16
+ //#endregion
17
+ export { QuestionParams, QuestionParamsSchema };
18
+ //# sourceMappingURL=questionnaire.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.d.cts","names":[],"sources":["../../src/schemas/questionnaire.ts"],"mappings":";;cA6Ca,sBAAoB,KAAA;;;;;;;;;;;;KAarB,iBAAiB,cAAc"}
@@ -0,0 +1,18 @@
1
+ import { Static, Type } from "typebox";
2
+ //#region src/schemas/questionnaire.d.ts
3
+ declare const QuestionParamsSchema: Type.TObject<{
4
+ questions: Type.TArray<Type.TObject<{
5
+ question: Type.TString;
6
+ header: Type.TString;
7
+ options: Type.TArray<Type.TObject<{
8
+ label: Type.TString;
9
+ description: Type.TString;
10
+ preview: Type.TOptional<Type.TString>;
11
+ }>>;
12
+ multiSelect: Type.TOptional<Type.TBoolean>;
13
+ }>>;
14
+ }>;
15
+ type QuestionParams = Static<typeof QuestionParamsSchema>;
16
+ //#endregion
17
+ export { QuestionParams, QuestionParamsSchema };
18
+ //# sourceMappingURL=questionnaire.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.d.mts","names":[],"sources":["../../src/schemas/questionnaire.ts"],"mappings":";;cA6Ca,sBAAoB,KAAA;;;;;;;;;;;;KAarB,iBAAiB,cAAc"}
@@ -0,0 +1,31 @@
1
+ //#region src/types/questionnaire.d.ts
2
+ type AnswerKind = 'option' | 'custom' | 'multi';
3
+ interface QuestionAnswer {
4
+ questionIndex: number;
5
+ question: string;
6
+ kind: AnswerKind;
7
+ answer: string | null;
8
+ selected?: string[];
9
+ notes?: string;
10
+ preview?: string;
11
+ }
12
+ type QuestionnaireError = 'no_ui' | 'no_custom_ui' | 'no_questions' | 'empty_options' | 'too_many_questions' | 'duplicate_question' | 'duplicate_option_label' | 'reserved_label' | 'session_load_failed' | 'stale_module_cache';
13
+ interface QuestionnaireResult {
14
+ answers: QuestionAnswer[];
15
+ cancelled: boolean;
16
+ error?: QuestionnaireError;
17
+ delivery?: 'voice';
18
+ awaitingResponse?: boolean;
19
+ voicePrompt?: string;
20
+ }
21
+ interface ToolTextResult {
22
+ content: Array<{
23
+ type: 'text';
24
+ text: string;
25
+ }>;
26
+ details: QuestionnaireResult;
27
+ terminate?: boolean;
28
+ }
29
+ //#endregion
30
+ export { AnswerKind, QuestionAnswer, QuestionnaireError, QuestionnaireResult, ToolTextResult };
31
+ //# sourceMappingURL=questionnaire.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.d.cts","names":[],"sources":["../../src/types/questionnaire.ts"],"mappings":";KAAY;UAEK;EACf;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;;KAGU;UAgBK;EACf,SAAS;EACT;EACA,QAAQ;EACR;EACA;EACA;;UAGe;EACf,SAAS;IAAQ;IAAc;;EAC/B,SAAS;EACT"}
@@ -0,0 +1,31 @@
1
+ //#region src/types/questionnaire.d.ts
2
+ type AnswerKind = 'option' | 'custom' | 'multi';
3
+ interface QuestionAnswer {
4
+ questionIndex: number;
5
+ question: string;
6
+ kind: AnswerKind;
7
+ answer: string | null;
8
+ selected?: string[];
9
+ notes?: string;
10
+ preview?: string;
11
+ }
12
+ type QuestionnaireError = 'no_ui' | 'no_custom_ui' | 'no_questions' | 'empty_options' | 'too_many_questions' | 'duplicate_question' | 'duplicate_option_label' | 'reserved_label' | 'session_load_failed' | 'stale_module_cache';
13
+ interface QuestionnaireResult {
14
+ answers: QuestionAnswer[];
15
+ cancelled: boolean;
16
+ error?: QuestionnaireError;
17
+ delivery?: 'voice';
18
+ awaitingResponse?: boolean;
19
+ voicePrompt?: string;
20
+ }
21
+ interface ToolTextResult {
22
+ content: Array<{
23
+ type: 'text';
24
+ text: string;
25
+ }>;
26
+ details: QuestionnaireResult;
27
+ terminate?: boolean;
28
+ }
29
+ //#endregion
30
+ export { AnswerKind, QuestionAnswer, QuestionnaireError, QuestionnaireResult, ToolTextResult };
31
+ //# sourceMappingURL=questionnaire.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"questionnaire.d.mts","names":[],"sources":["../../src/types/questionnaire.ts"],"mappings":";KAAY;UAEK;EACf;EACA;EACA,MAAM;EACN;EACA;EACA;EACA;;KAGU;UAgBK;EACf,SAAS;EACT;EACA,QAAQ;EACR;EACA;EACA;;UAGe;EACf,SAAS;IAAQ;IAAc;;EAC/B,SAAS;EACT"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/doom/voiceQuestionHandoff.cjs");exports.DOOM_VOICE_AUTO_MODE_ID=e.DOOM_VOICE_AUTO_MODE_ID,exports.DOOM_VOICE_SOURCE=e.DOOM_VOICE_SOURCE,exports.VOICE_WAIT_MESSAGE=e.VOICE_WAIT_MESSAGE,exports.VoiceQuestionHandoff=e.VoiceQuestionHandoff,exports.buildVoiceToolResult=e.buildVoiceToolResult,exports.createVoiceQuestionHandoff=e.createVoiceQuestionHandoff,exports.formatVoiceQuestionPrompt=e.formatVoiceQuestionPrompt;
@@ -0,0 +1,4 @@
1
+ import { QuestionParams } from "./schemas/questionnaire.cjs";
2
+ import { QuestionnaireResult, ToolTextResult } from "./types/questionnaire.cjs";
3
+ import { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt } from "./adapters/doom/voiceQuestionHandoff.cjs";
4
+ export { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, type QuestionParams, type QuestionnaireResult, type ToolTextResult, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, type VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt };
@@ -0,0 +1,4 @@
1
+ import { QuestionParams } from "./schemas/questionnaire.mjs";
2
+ import { QuestionnaireResult, ToolTextResult } from "./types/questionnaire.mjs";
3
+ import { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt } from "./adapters/doom/voiceQuestionHandoff.mjs";
4
+ export { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE, type QuestionParams, type QuestionnaireResult, type ToolTextResult, VOICE_WAIT_MESSAGE, VoiceQuestionHandoff, type VoiceQuestionHandoffHandle, buildVoiceToolResult, createVoiceQuestionHandoff, formatVoiceQuestionPrompt };
@@ -0,0 +1 @@
1
+ import{DOOM_VOICE_AUTO_MODE_ID as e,DOOM_VOICE_SOURCE as t,VOICE_WAIT_MESSAGE as n,VoiceQuestionHandoff as r,buildVoiceToolResult as i,createVoiceQuestionHandoff as a,formatVoiceQuestionPrompt as o}from"./adapters/doom/voiceQuestionHandoff.mjs";export{e as DOOM_VOICE_AUTO_MODE_ID,t as DOOM_VOICE_SOURCE,n as VOICE_WAIT_MESSAGE,r as VoiceQuestionHandoff,i as buildVoiceToolResult,a as createVoiceQuestionHandoff,o as formatVoiceQuestionPrompt};
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-user-feedback",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.10",
4
4
  "description": "Structured user feedback for DoomPi agents",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "developer-tools",
9
+ "doompi",
10
+ "pi-package"
11
+ ],
5
12
  "homepage": "https://agimon.ai",
6
13
  "license": "MIT",
7
14
  "author": "Vuong Ngo",
@@ -26,6 +33,11 @@
26
33
  "import": "./dist/events.mjs",
27
34
  "require": "./dist/events.cjs"
28
35
  },
36
+ "./voice-question-handoff": {
37
+ "types": "./dist/voiceQuestionHandoff.d.mts",
38
+ "import": "./dist/voiceQuestionHandoff.mjs",
39
+ "require": "./dist/voiceQuestionHandoff.cjs"
40
+ },
29
41
  "./extensions/pi": {
30
42
  "types": "./dist/extensions/pi.d.mts",
31
43
  "import": "./dist/extensions/pi.mjs",
@@ -38,11 +50,12 @@
38
50
  },
39
51
  "dependencies": {
40
52
  "typebox": "1.1.38",
41
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.11"
53
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.21",
54
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.21"
42
55
  },
43
56
  "devDependencies": {
44
- "@earendil-works/pi-coding-agent": "0.84.1",
45
- "@earendil-works/pi-tui": "0.84.1",
57
+ "@earendil-works/pi-coding-agent": "0.84.2",
58
+ "@earendil-works/pi-tui": "0.84.2",
46
59
  "@types/node": "26.1.1",
47
60
  "@vitest/coverage-v8": "4.1.10",
48
61
  "tsdown": "0.22.14",
@@ -50,8 +63,8 @@
50
63
  "vitest": "4.1.10"
51
64
  },
52
65
  "peerDependencies": {
53
- "@earendil-works/pi-coding-agent": "0.84.1",
54
- "@earendil-works/pi-tui": "0.84.1"
66
+ "@earendil-works/pi-coding-agent": "0.84.2",
67
+ "@earendil-works/pi-tui": "0.84.2"
55
68
  },
56
69
  "peerDependenciesMeta": {
57
70
  "@earendil-works/pi-coding-agent": {