@agimon-ai/doompi-user-feedback 0.0.1-alpha.22 → 0.0.1-alpha.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,15 +76,23 @@ cancellation rather than waiting forever.
76
76
  Questionnaires are coordinated one at a time. Cancellation, session replacement, or shutdown
77
77
  aborts the active prompt and settles queued work.
78
78
 
79
- ## Autonomous Voice handoff
79
+ ## Autonomous Voice
80
80
 
81
- When autonomous Voice mode is active, the extension formats the questions as plain text, requests
82
- narration, and returns `terminate: true`. The current turn ends while waiting; the next spoken
83
- answer arrives as a normal user message rather than a synthetic questionnaire selection.
81
+ While autonomous Voice mode is active, `ask_user_question` leaves Pi's active tool set. Voice already
82
+ speaks its own question through `narrate` and then waits for the spoken reply, so keeping a
83
+ questionnaire tool alongside it asks the same thing twice. The agent asks by narrating, and the
84
+ answer arrives as an ordinary user message.
84
85
 
85
- The handoff uses the shared `doom/minor-mode-catalog` and `doom/narration` Cordis services. If either
86
- provider unloads, User Feedback immediately falls back to its interactive questionnaire without
87
- retaining the old provider.
86
+ The gate tracks its own removal. A tool switched off by the user stays off, no other package's tools
87
+ are touched, and the removal is restored when Voice deactivates, when the Voice services unload, or
88
+ when this runtime shuts down.
89
+
90
+ A call already in flight when Voice activates still hands off: the extension formats the questions as
91
+ plain text, requests narration, and returns `terminate: true`, ending the turn while it waits.
92
+
93
+ Both paths use the shared `doom/minor-mode-catalog` and `doom/narration` Cordis services. If a
94
+ provider unloads, User Feedback restores the tool and falls back to its interactive questionnaire
95
+ without retaining the old provider.
88
96
 
89
97
  ## Public API
90
98
 
@@ -1,5 +1,5 @@
1
- const e=require("../../services/voiceQuestionNarration.cjs");let t=require("@agimon-ai/doompi-extension-contracts/narration");const 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.descriptor.source===t.DOOM_VOICE_SOURCE&&e.descriptor.id===t.DOOM_VOICE_AUTO_MODE_ID&&e.state.activation===`active`}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
+ const e=require("../../services/autonomousVoiceMode.cjs"),t=require("../../services/voiceQuestionNarration.cjs");let n=require("@agimon-ai/doompi-extension-contracts/narration");const r=`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 i(e){let t=e.questions.length>1;return e.questions.map((e,n)=>[`${t?`${n+1}. `:``}${e.question}${e.multiSelect?` (Select all that apply.)`:``}`,...e.options.map((e,t)=>` ${t+1}. ${e.label}`),` Type something.`].join(`
2
2
  `)).join(`
3
3
 
4
- `)}var a=class{modes;narration;buildNarration;constructor(t,n,r=e.buildVoiceQuestionNarration){this.modes=t,this.narration=n,this.buildNarration=r}async handoff(e){if(!this.modes.list().some(r))return;let n=(0,t.createNarrationRequest)(this.buildNarration(e));if(n)return await this.narration.request(n),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:i(e)}}};function o(e,t){return new a(e,t)}function s(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${n}`:n}],details:e,terminate:!0}}exports.VOICE_WAIT_MESSAGE=n,exports.VoiceQuestionHandoff=a,exports.buildVoiceToolResult=s,exports.createVoiceQuestionHandoff=o,exports.formatVoiceQuestionPrompt=i;
4
+ `)}var a=class{modes;narration;buildNarration;constructor(e,n,r=t.buildVoiceQuestionNarration){this.modes=e,this.narration=n,this.buildNarration=r}async handoff(t){if(!this.modes.list().some(t=>e.isAutonomousVoiceRecord(t)))return;let r=(0,n.createNarrationRequest)(this.buildNarration(t));if(r)return await this.narration.request(r),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:i(t)}}};function o(e,t){return new a(e,t)}function s(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${r}`:r}],details:e,terminate:!0}}exports.VOICE_WAIT_MESSAGE=r,exports.VoiceQuestionHandoff=a,exports.buildVoiceToolResult=s,exports.createVoiceQuestionHandoff=o,exports.formatVoiceQuestionPrompt=i;
5
5
  //# sourceMappingURL=voiceQuestionHandoff.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"voiceQuestionHandoff.cjs","names":["DOOM_VOICE_SOURCE","DOOM_VOICE_AUTO_MODE_ID","buildVoiceQuestionNarration","createNarrationRequest"],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import type { MinorModeCatalogService, MinorModeRecord } from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n createNarrationRequest,\n type DoomNarrationService,\n DOOM_VOICE_AUTO_MODE_ID,\n DOOM_VOICE_SOURCE,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\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<MinorModeCatalogService, 'list'>,\n private readonly narration: Pick<DoomNarrationService, 'request'>,\n private readonly buildNarration: (params: QuestionParams) => string = buildVoiceQuestionNarration,\n ) {}\n\n async handoff(params: QuestionParams): Promise<QuestionnaireResult | undefined> {\n if (!this.modes.list().some(isAutonomousVoice)) return undefined;\n const narration = createNarrationRequest(this.buildNarration(params));\n if (!narration) return undefined;\n await this.narration.request(narration);\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\n/** Create a handoff from services whose lifecycle is owned by a Cordis injection. */\nexport function createVoiceQuestionHandoff(\n modes: Pick<MinorModeCatalogService, 'list'>,\n narration: Pick<DoomNarrationService, 'request'>,\n): VoiceQuestionHandoff {\n return new VoiceQuestionHandoff(modes, narration);\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":"8HAWA,MAAa,EACX,iJAEF,SAAS,EAAkB,EAAkC,CAC3D,OACE,EAAO,WAAW,SAAWA,EAAAA,mBAC7B,EAAO,WAAW,KAAOC,EAAAA,yBACzB,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,EAAsEC,EAAAA,4BACtE,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,MAAM,QAAQ,EAAkE,CAC9E,GAAI,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC,KAAK,CAAiB,EAAG,OAChD,IAAM,GAAA,EAAYC,EAAAA,uBAAAA,CAAuB,KAAK,eAAe,CAAM,CAAC,EAC/D,KAEL,OADA,MAAM,KAAK,UAAU,QAAQ,CAAS,EAC/B,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAGA,SAAgB,EACd,EACA,EACsB,CACtB,OAAO,IAAI,EAAqB,EAAO,CAAS,CAClD,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
+ {"version":3,"file":"voiceQuestionHandoff.cjs","names":["buildVoiceQuestionNarration","isAutonomousVoiceRecord","createNarrationRequest"],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import type { MinorModeCatalogService } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequest, type DoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { isAutonomousVoiceRecord } from '../../services/autonomousVoiceMode.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\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\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<MinorModeCatalogService, 'list'>,\n private readonly narration: Pick<DoomNarrationService, 'request'>,\n private readonly buildNarration: (params: QuestionParams) => string = buildVoiceQuestionNarration,\n ) {}\n\n async handoff(params: QuestionParams): Promise<QuestionnaireResult | undefined> {\n if (!this.modes.list().some((record) => isAutonomousVoiceRecord(record))) return undefined;\n const narration = createNarrationRequest(this.buildNarration(params));\n if (!narration) return undefined;\n await this.narration.request(narration);\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\n/** Create a handoff from services whose lifecycle is owned by a Cordis injection. */\nexport function createVoiceQuestionHandoff(\n modes: Pick<MinorModeCatalogService, 'list'>,\n narration: Pick<DoomNarrationService, 'request'>,\n): VoiceQuestionHandoff {\n return new VoiceQuestionHandoff(modes, narration);\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":"kLAOA,MAAa,EACX,iJAEF,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,EAAsEA,EAAAA,4BACtE,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,MAAM,QAAQ,EAAkE,CAC9E,GAAI,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC,KAAM,GAAWC,EAAAA,wBAAwB,CAAM,CAAC,EAAG,OAC1E,IAAM,GAAA,EAAYC,EAAAA,uBAAAA,CAAuB,KAAK,eAAe,CAAM,CAAC,EAC/D,KAEL,OADA,MAAM,KAAK,UAAU,QAAQ,CAAS,EAC/B,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAGA,SAAgB,EACd,EACA,EACsB,CACtB,OAAO,IAAI,EAAqB,EAAO,CAAS,CAClD,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,5 +1,5 @@
1
- import{buildVoiceQuestionNarration as e}from"../../services/voiceQuestionNarration.mjs";import{DOOM_VOICE_AUTO_MODE_ID as t,DOOM_VOICE_SOURCE as n,createNarrationRequest as r}from"@agimon-ai/doompi-extension-contracts/narration";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===n&&e.descriptor.id===t&&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(`
1
+ import{isAutonomousVoiceRecord as e}from"../../services/autonomousVoiceMode.mjs";import{buildVoiceQuestionNarration as t}from"../../services/voiceQuestionNarration.mjs";import{createNarrationRequest as n}from"@agimon-ai/doompi-extension-contracts/narration";const r=`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 i(e){let t=e.questions.length>1;return e.questions.map((e,n)=>[`${t?`${n+1}. `:``}${e.question}${e.multiSelect?` (Select all that apply.)`:``}`,...e.options.map((e,t)=>` ${t+1}. ${e.label}`),` Type something.`].join(`
2
2
  `)).join(`
3
3
 
4
- `)}var s=class{modes;narration;buildNarration;constructor(t,n,r=e){this.modes=t,this.narration=n,this.buildNarration=r}async handoff(e){if(!this.modes.list().some(a))return;let t=r(this.buildNarration(e));if(t)return await this.narration.request(t),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:o(e)}}};function c(e,t){return new s(e,t)}function l(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${i}`:i}],details:e,terminate:!0}}export{i as VOICE_WAIT_MESSAGE,s as VoiceQuestionHandoff,l as buildVoiceToolResult,c as createVoiceQuestionHandoff,o as formatVoiceQuestionPrompt};
4
+ `)}var a=class{modes;narration;buildNarration;constructor(e,n,r=t){this.modes=e,this.narration=n,this.buildNarration=r}async handoff(t){if(!this.modes.list().some(t=>e(t)))return;let r=n(this.buildNarration(t));if(r)return await this.narration.request(r),{answers:[],cancelled:!1,delivery:`voice`,awaitingResponse:!0,voicePrompt:i(t)}}};function o(e,t){return new a(e,t)}function s(e){let t=e.voicePrompt?.trim();return{content:[{type:`text`,text:t?`${t}\n\n${r}`:r}],details:e,terminate:!0}}export{r as VOICE_WAIT_MESSAGE,a as VoiceQuestionHandoff,s as buildVoiceToolResult,o as createVoiceQuestionHandoff,i 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 { MinorModeCatalogService, MinorModeRecord } from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n createNarrationRequest,\n type DoomNarrationService,\n DOOM_VOICE_AUTO_MODE_ID,\n DOOM_VOICE_SOURCE,\n} from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\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<MinorModeCatalogService, 'list'>,\n private readonly narration: Pick<DoomNarrationService, 'request'>,\n private readonly buildNarration: (params: QuestionParams) => string = buildVoiceQuestionNarration,\n ) {}\n\n async handoff(params: QuestionParams): Promise<QuestionnaireResult | undefined> {\n if (!this.modes.list().some(isAutonomousVoice)) return undefined;\n const narration = createNarrationRequest(this.buildNarration(params));\n if (!narration) return undefined;\n await this.narration.request(narration);\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\n/** Create a handoff from services whose lifecycle is owned by a Cordis injection. */\nexport function createVoiceQuestionHandoff(\n modes: Pick<MinorModeCatalogService, 'list'>,\n narration: Pick<DoomNarrationService, 'request'>,\n): VoiceQuestionHandoff {\n return new VoiceQuestionHandoff(modes, narration);\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":"qOAWA,MAAa,EACX,iJAEF,SAAS,EAAkB,EAAkC,CAC3D,OACE,EAAO,WAAW,SAAW,GAC7B,EAAO,WAAW,KAAO,GACzB,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,EAAsE,EACtE,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,MAAM,QAAQ,EAAkE,CAC9E,GAAI,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC,KAAK,CAAiB,EAAG,OAChD,IAAM,EAAY,EAAuB,KAAK,eAAe,CAAM,CAAC,EAC/D,KAEL,OADA,MAAM,KAAK,UAAU,QAAQ,CAAS,EAC/B,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAGA,SAAgB,EACd,EACA,EACsB,CACtB,OAAO,IAAI,EAAqB,EAAO,CAAS,CAClD,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
+ {"version":3,"file":"voiceQuestionHandoff.mjs","names":[],"sources":["../../../src/adapters/doom/voiceQuestionHandoff.ts"],"sourcesContent":["import type { MinorModeCatalogService } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { createNarrationRequest, type DoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { QuestionParams } from '../../schemas/questionnaire.js';\nimport { isAutonomousVoiceRecord } from '../../services/autonomousVoiceMode.js';\nimport { buildVoiceQuestionNarration } from '../../services/voiceQuestionNarration.js';\nimport type { QuestionnaireResult, ToolTextResult } from '../../types/questionnaire.js';\n\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\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<MinorModeCatalogService, 'list'>,\n private readonly narration: Pick<DoomNarrationService, 'request'>,\n private readonly buildNarration: (params: QuestionParams) => string = buildVoiceQuestionNarration,\n ) {}\n\n async handoff(params: QuestionParams): Promise<QuestionnaireResult | undefined> {\n if (!this.modes.list().some((record) => isAutonomousVoiceRecord(record))) return undefined;\n const narration = createNarrationRequest(this.buildNarration(params));\n if (!narration) return undefined;\n await this.narration.request(narration);\n return {\n answers: [],\n cancelled: false,\n delivery: 'voice',\n awaitingResponse: true,\n voicePrompt: formatVoiceQuestionPrompt(params),\n };\n }\n}\n\n/** Create a handoff from services whose lifecycle is owned by a Cordis injection. */\nexport function createVoiceQuestionHandoff(\n modes: Pick<MinorModeCatalogService, 'list'>,\n narration: Pick<DoomNarrationService, 'request'>,\n): VoiceQuestionHandoff {\n return new VoiceQuestionHandoff(modes, narration);\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":"kQAOA,MAAa,EACX,iJAEF,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,EAAsE,EACtE,CAHiB,KAAA,MAAA,EACA,KAAA,UAAA,EACA,KAAA,eAAA,CAChB,CAEH,MAAM,QAAQ,EAAkE,CAC9E,GAAI,CAAC,KAAK,MAAM,KAAK,CAAC,CAAC,KAAM,GAAW,EAAwB,CAAM,CAAC,EAAG,OAC1E,IAAM,EAAY,EAAuB,KAAK,eAAe,CAAM,CAAC,EAC/D,KAEL,OADA,MAAM,KAAK,UAAU,QAAQ,CAAS,EAC/B,CACL,QAAS,CAAC,EACV,UAAW,GACX,SAAU,QACV,iBAAkB,GAClB,YAAa,EAA0B,CAAM,CAC/C,CACF,CACF,EAGA,SAAgB,EACd,EACA,EACsB,CACtB,OAAO,IAAI,EAAqB,EAAO,CAAS,CAClD,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,2 @@
1
+ function e(e){let t=e;if(typeof t.getActiveTools==`function`&&typeof t.setActiveTools==`function`)return{getActiveTools:()=>e.getActiveTools(),setActiveTools:t=>e.setActiveTools(t)}}exports.readActiveToolRegistry=e;
2
+ //# sourceMappingURL=activeToolRegistry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activeToolRegistry.cjs","names":[],"sources":["../../../src/adapters/pi/activeToolRegistry.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { ActiveToolRegistry } from '../../types/toolActivation.js';\n\n/**\n * Adapts Pi's extension API to the gate's port.\n *\n * Returns undefined for a host that does not expose the runtime tool list. Those hosts\n * keep every registered tool active, so there is nothing for the gate to drive and no\n * reason to fail the install over it.\n */\nexport function readActiveToolRegistry(pi: ExtensionAPI): ActiveToolRegistry | undefined {\n const candidate = pi as Partial<ActiveToolRegistry>;\n if (typeof candidate.getActiveTools !== 'function' || typeof candidate.setActiveTools !== 'function') {\n return undefined;\n }\n return {\n getActiveTools: () => pi.getActiveTools(),\n setActiveTools: (toolNames) => pi.setActiveTools(toolNames),\n };\n}\n"],"mappings":"AAUA,SAAgB,EAAuB,EAAkD,CACvF,IAAM,EAAY,EACd,UAAO,EAAU,gBAAmB,YAAc,OAAO,EAAU,gBAAmB,WAG1F,MAAO,CACL,mBAAsB,EAAG,eAAe,EACxC,eAAiB,GAAc,EAAG,eAAe,CAAS,CAC5D,CACF"}
@@ -0,0 +1,2 @@
1
+ function e(e){let t=e;if(typeof t.getActiveTools==`function`&&typeof t.setActiveTools==`function`)return{getActiveTools:()=>e.getActiveTools(),setActiveTools:t=>e.setActiveTools(t)}}export{e as readActiveToolRegistry};
2
+ //# sourceMappingURL=activeToolRegistry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"activeToolRegistry.mjs","names":[],"sources":["../../../src/adapters/pi/activeToolRegistry.ts"],"sourcesContent":["import type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { ActiveToolRegistry } from '../../types/toolActivation.js';\n\n/**\n * Adapts Pi's extension API to the gate's port.\n *\n * Returns undefined for a host that does not expose the runtime tool list. Those hosts\n * keep every registered tool active, so there is nothing for the gate to drive and no\n * reason to fail the install over it.\n */\nexport function readActiveToolRegistry(pi: ExtensionAPI): ActiveToolRegistry | undefined {\n const candidate = pi as Partial<ActiveToolRegistry>;\n if (typeof candidate.getActiveTools !== 'function' || typeof candidate.setActiveTools !== 'function') {\n return undefined;\n }\n return {\n getActiveTools: () => pi.getActiveTools(),\n setActiveTools: (toolNames) => pi.setActiveTools(toolNames),\n };\n}\n"],"mappings":"AAUA,SAAgB,EAAuB,EAAkD,CACvF,IAAM,EAAY,EACd,UAAO,EAAU,gBAAmB,YAAc,OAAO,EAAU,gBAAmB,WAG1F,MAAO,CACL,mBAAsB,EAAG,eAAe,EACxC,eAAiB,GAAc,EAAG,eAAe,CAAS,CAC5D,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../../services/questionnaireCoordinator.cjs"),t=require("../doom/voiceQuestionHandoff.cjs"),n=require("./askUserQuestionAdapter.cjs"),r=require("./reconcileAdapter.cjs");let i=require("@agimon-ai/doompi-extension-contracts/cordis-host"),a=require("@agimon-ai/doompi-extension-contracts/mode"),o=require("@agimon-ai/doompi-extension-contracts/narration");const s=`@agimon-ai/doompi-user-feedback`;function c(){return{answers:[],cancelled:!0}}function l(i,l){let u=!0,d=0,f,p=!1,m=new e.QuestionnaireCoordinator,h,g=Promise.resolve(),_=new Set,v=e=>(_.add(e),e.then(()=>_.delete(e),()=>_.delete(e)),e),y=(e,t)=>u&&e===d&&t===f,b=(e,t)=>u&&p&&t?.aborted!==!0&&e.sessionManager.getSessionId()===f,x=async()=>{if(!u)return;let e=m;e.shutdown(),u=!1,d+=1,p=!1,f=void 0,await Promise.allSettled(_),await e.waitForIdle()};i.effect(()=>x,`${s}/runtime`),i.inject([a.DOOM_MINOR_MODE_CATALOG_SERVICE,o.DOOM_NARRATION_SERVICE],e=>{let n=t.createVoiceQuestionHandoff((0,a.requireMinorModeCatalog)(e),(0,o.requireDoomNarrationService)(e));return h=n,()=>{h===n&&(h=void 0)}}),n.registerAskUserQuestionTool(l,i,{enqueue:(e,t)=>{let n=m,r=d,i=f;return!p||!y(r,i)||t?.aborted?Promise.resolve(c()):n.enqueue(async t=>{if(!p||!y(r,i)||t.signal.aborted)return c();let n=await e({...t,reportProgress:e=>{y(r,i)&&!t.signal.aborted&&t.reportProgress(e)}});return p&&y(r,i)&&!t.signal.aborted?n:c()},t)},isActive:b,runTui:async(e,t,n,r,i)=>{if(!b(e,r))return;let{runTuiQuestionnaire:a}=await Promise.resolve().then(()=>require("../../tui/runQuestionnaire.cjs"));if(!b(e,r))return;let o=await a(e,t,n,r,i);return b(e,r)?o:void 0},tryVoice:e=>u&&p?h?.handoff(e):void 0}),r.registerAskUserQuestionReconciler(l,b),l.on(`session_start`,(t,n)=>{if(!u)return;let r=m;r.shutdown();let i=n.sessionManager.getSessionId(),a=++d;m=new e.QuestionnaireCoordinator,f=i,p=!1;let o=async()=>{await r.waitForIdle(),y(a,i)&&(p=!0)},s=g.then(o,o);return g=s.catch(()=>void 0),v(s)})}async function u(e){let t=await(0,i.connectDoomCordisHost)(e,s),n=t.root.plugin(d,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function d(e,t){l(e,t.pi)}exports.default=u,exports.userFeedbackExtension=u,exports.installUserFeedbackRuntime=l;
1
+ const e=require("../../services/askUserToolGate.cjs"),t=require("../../services/autonomousVoiceMode.cjs"),n=require("../../services/questionnaireCoordinator.cjs"),r=require("../doom/voiceQuestionHandoff.cjs"),i=require("./activeToolRegistry.cjs"),a=require("./askUserQuestionAdapter.cjs"),o=require("./reconcileAdapter.cjs");let s=require("@agimon-ai/doompi-extension-contracts/cordis-host"),c=require("@agimon-ai/doompi-extension-contracts/mode"),l=require("@agimon-ai/doompi-extension-contracts/narration");const u=`@agimon-ai/doompi-user-feedback`;function d(){return{answers:[],cancelled:!0}}function f(s,f){let p=!0,m=0,h,g=!1,_=new n.QuestionnaireCoordinator,v,y=i.readActiveToolRegistry(f),b=y?new e.AskUserToolGate(y,a.ASK_USER_QUESTION_TOOL_NAME):void 0,x=()=>void 0,S=Promise.resolve(),C=new Set,w=e=>(C.add(e),e.then(()=>C.delete(e),()=>C.delete(e)),e),T=(e,t)=>p&&e===m&&t===h,E=(e,t)=>p&&g&&t?.aborted!==!0&&e.sessionManager.getSessionId()===h,D=async()=>{if(!p)return;b?.release();let e=_;e.shutdown(),p=!1,m+=1,g=!1,h=void 0,await Promise.allSettled(C),await e.waitForIdle()};s.effect(()=>D,`${u}/runtime`),s.inject([c.DOOM_MINOR_MODE_CATALOG_SERVICE,l.DOOM_NARRATION_SERVICE],e=>{let t=r.createVoiceQuestionHandoff((0,c.requireMinorModeCatalog)(e),(0,l.requireDoomNarrationService)(e));return v=t,()=>{v===t&&(v=void 0)}}),s.inject([c.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{if(!b)return;let n=(0,c.requireMinorModeCatalog)(e),r=()=>{p&&g&&b.sync(t.isAutonomousVoiceActive(n.list()))};x=r;let i=n.subscribe(r);return r(),()=>{i(),x===r&&(x=()=>void 0),b.release()}}),a.registerAskUserQuestionTool(f,s,{enqueue:(e,t)=>{let n=_,r=m,i=h;return!g||!T(r,i)||t?.aborted?Promise.resolve(d()):n.enqueue(async t=>{if(!g||!T(r,i)||t.signal.aborted)return d();let n=await e({...t,reportProgress:e=>{T(r,i)&&!t.signal.aborted&&t.reportProgress(e)}});return g&&T(r,i)&&!t.signal.aborted?n:d()},t)},isActive:E,runTui:async(e,t,n,r,i)=>{if(!E(e,r))return;let{runTuiQuestionnaire:a}=await Promise.resolve().then(()=>require("../../tui/runQuestionnaire.cjs"));if(!E(e,r))return;let o=await a(e,t,n,r,i);return E(e,r)?o:void 0},tryVoice:e=>p&&g?v?.handoff(e):void 0}),o.registerAskUserQuestionReconciler(f,E),f.on(`session_start`,(e,t)=>{if(!p)return;let r=_;r.shutdown();let i=t.sessionManager.getSessionId(),a=++m;_=new n.QuestionnaireCoordinator,h=i,g=!1;let o=async()=>{await r.waitForIdle(),T(a,i)&&(g=!0,x())},s=S.then(o,o);return S=s.catch(()=>void 0),w(s)})}async function p(e){let t=await(0,s.connectDoomCordisHost)(e,u),n=t.root.plugin(m,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function m(e,t){f(e,t.pi)}exports.default=p,exports.userFeedbackExtension=p,exports.installUserFeedbackRuntime=f;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["QuestionnaireCoordinator","DOOM_MINOR_MODE_CATALOG_SERVICE","DOOM_NARRATION_SERVICE","createVoiceQuestionHandoff","requireMinorModeCatalog","requireDoomNarrationService","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_MINOR_MODE_CATALOG_SERVICE, requireMinorModeCatalog } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_NARRATION_SERVICE, requireDoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { QuestionnaireCoordinator, type QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoff } from '../doom/voiceQuestionHandoff.js';\nimport { registerAskUserQuestionTool } from './askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from './reconcileAdapter.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst SESSION_START_EVENT = 'session_start';\n\nfunction cancelledResult(): QuestionnaireResult {\n return { answers: [], cancelled: true };\n}\n\n/** Install one user-feedback runtime into its host-owned Cordis plugin fiber. */\nexport function installUserFeedbackRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let sessionReady = false;\n let coordinator = new QuestionnaireCoordinator();\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const ownsGeneration = (generation: number, expectedSessionId: string | undefined): boolean =>\n active && generation === sessionGeneration && expectedSessionId === sessionId;\n const isContextActive = (context: ExtensionContext, signal?: AbortSignal): boolean =>\n active && sessionReady && signal?.aborted !== true && context.sessionManager.getSessionId() === sessionId;\n\n const shutdownRuntime = async (): Promise<void> => {\n if (!active) return;\n const ownedCoordinator = coordinator;\n ownedCoordinator.shutdown();\n active = false;\n sessionGeneration += 1;\n sessionReady = false;\n sessionId = undefined;\n await Promise.allSettled(pendingOperations);\n await ownedCoordinator.waitForIdle();\n };\n cordis.effect(() => shutdownRuntime, `${PACKAGE_SOURCE}/runtime`);\n\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE, DOOM_NARRATION_SERVICE], (serviceContext) => {\n const handoff = createVoiceQuestionHandoff(\n requireMinorModeCatalog(serviceContext),\n requireDoomNarrationService(serviceContext),\n );\n voiceHandoff = handoff;\n return () => {\n if (voiceHandoff === handoff) voiceHandoff = undefined;\n };\n });\n\n registerAskUserQuestionTool(pi, cordis, {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => {\n const ownedCoordinator = coordinator;\n const generation = sessionGeneration;\n const expectedSessionId = sessionId;\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || signal?.aborted) {\n return Promise.resolve(cancelledResult());\n }\n return ownedCoordinator.enqueue(async (context) => {\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || context.signal.aborted) {\n return cancelledResult();\n }\n const result = await runner({\n ...context,\n reportProgress: (progress) => {\n if (ownsGeneration(generation, expectedSessionId) && !context.signal.aborted) {\n context.reportProgress(progress);\n }\n },\n });\n return sessionReady && ownsGeneration(generation, expectedSessionId) && !context.signal.aborted\n ? result\n : cancelledResult();\n }, signal);\n },\n isActive: isContextActive,\n runTui: async (context, params, collapseKey, signal, reportProgress) => {\n if (!isContextActive(context, signal)) return undefined;\n const { runTuiQuestionnaire } = await import('../../tui/runQuestionnaire.js');\n if (!isContextActive(context, signal)) return undefined;\n const result = await runTuiQuestionnaire(context, params, collapseKey, signal, reportProgress);\n return isContextActive(context, signal) ? result : undefined;\n },\n tryVoice: (params) => (active && sessionReady ? voiceHandoff?.handoff(params) : undefined),\n });\n registerAskUserQuestionReconciler(pi, isContextActive);\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active) return;\n const previousCoordinator = coordinator;\n previousCoordinator.shutdown();\n const activeSessionId = context.sessionManager.getSessionId();\n const generation = ++sessionGeneration;\n const nextCoordinator = new QuestionnaireCoordinator();\n coordinator = nextCoordinator;\n sessionId = activeSessionId;\n sessionReady = false;\n\n const initializeSession = async (): Promise<void> => {\n await previousCoordinator.waitForIdle();\n if (!ownsGeneration(generation, activeSessionId)) return;\n sessionReady = true;\n };\n\n const operation = lifecycleQueue.then(initializeSession, initializeSession);\n lifecycleQueue = operation.catch(() => undefined);\n return trackOperation(operation);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function userFeedbackExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(userFeedbackPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface UserFeedbackPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction userFeedbackPlugin(cordis: Context, config: UserFeedbackPluginConfig): void {\n installUserFeedbackRuntime(cordis, config.pi);\n}\n\nexport default userFeedbackExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["QuestionnaireCoordinator","readActiveToolRegistry","AskUserToolGate","ASK_USER_QUESTION_TOOL_NAME","DOOM_MINOR_MODE_CATALOG_SERVICE","DOOM_NARRATION_SERVICE","createVoiceQuestionHandoff","requireMinorModeCatalog","requireDoomNarrationService","isAutonomousVoiceActive","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_MINOR_MODE_CATALOG_SERVICE, requireMinorModeCatalog } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_NARRATION_SERVICE, requireDoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { AskUserToolGate } from '../../services/askUserToolGate.js';\nimport { isAutonomousVoiceActive } from '../../services/autonomousVoiceMode.js';\nimport { QuestionnaireCoordinator, type QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoff } from '../doom/voiceQuestionHandoff.js';\nimport { readActiveToolRegistry } from './activeToolRegistry.js';\nimport { ASK_USER_QUESTION_TOOL_NAME, registerAskUserQuestionTool } from './askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from './reconcileAdapter.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst SESSION_START_EVENT = 'session_start';\n\nfunction cancelledResult(): QuestionnaireResult {\n return { answers: [], cancelled: true };\n}\n\n/** Install one user-feedback runtime into its host-owned Cordis plugin fiber. */\nexport function installUserFeedbackRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let sessionReady = false;\n let coordinator = new QuestionnaireCoordinator();\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n const registry = readActiveToolRegistry(pi);\n const toolGate = registry ? new AskUserToolGate(registry, ASK_USER_QUESTION_TOOL_NAME) : undefined;\n let syncToolGate = (): void => undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const ownsGeneration = (generation: number, expectedSessionId: string | undefined): boolean =>\n active && generation === sessionGeneration && expectedSessionId === sessionId;\n const isContextActive = (context: ExtensionContext, signal?: AbortSignal): boolean =>\n active && sessionReady && signal?.aborted !== true && context.sessionManager.getSessionId() === sessionId;\n\n const shutdownRuntime = async (): Promise<void> => {\n if (!active) return;\n // Released before the flags drop, so a runtime torn down while Voice is still active\n // cannot leave the tool hidden: Pi re-activates a refreshed tool only when its name is\n // new to the registry, and after a reload this one is not.\n toolGate?.release();\n const ownedCoordinator = coordinator;\n ownedCoordinator.shutdown();\n active = false;\n sessionGeneration += 1;\n sessionReady = false;\n sessionId = undefined;\n await Promise.allSettled(pendingOperations);\n await ownedCoordinator.waitForIdle();\n };\n cordis.effect(() => shutdownRuntime, `${PACKAGE_SOURCE}/runtime`);\n\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE, DOOM_NARRATION_SERVICE], (serviceContext) => {\n const handoff = createVoiceQuestionHandoff(\n requireMinorModeCatalog(serviceContext),\n requireDoomNarrationService(serviceContext),\n );\n voiceHandoff = handoff;\n return () => {\n if (voiceHandoff === handoff) voiceHandoff = undefined;\n };\n });\n\n // Separate from the handoff binding above: the gate needs only the mode catalog, so it\n // still applies to a Voice build that publishes modes without providing narration.\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n if (!toolGate) return undefined;\n const modes = requireMinorModeCatalog(modeContext);\n const apply = (): void => {\n // Pi binds the active-tool accessors to a started session runtime and throws before\n // that, so nothing is applied until the session is ready.\n if (active && sessionReady) toolGate.sync(isAutonomousVoiceActive(modes.list()));\n };\n syncToolGate = apply;\n const unsubscribe = modes.subscribe(apply);\n apply();\n return () => {\n unsubscribe();\n if (syncToolGate === apply) syncToolGate = (): void => undefined;\n toolGate.release();\n };\n });\n\n registerAskUserQuestionTool(pi, cordis, {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => {\n const ownedCoordinator = coordinator;\n const generation = sessionGeneration;\n const expectedSessionId = sessionId;\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || signal?.aborted) {\n return Promise.resolve(cancelledResult());\n }\n return ownedCoordinator.enqueue(async (context) => {\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || context.signal.aborted) {\n return cancelledResult();\n }\n const result = await runner({\n ...context,\n reportProgress: (progress) => {\n if (ownsGeneration(generation, expectedSessionId) && !context.signal.aborted) {\n context.reportProgress(progress);\n }\n },\n });\n return sessionReady && ownsGeneration(generation, expectedSessionId) && !context.signal.aborted\n ? result\n : cancelledResult();\n }, signal);\n },\n isActive: isContextActive,\n runTui: async (context, params, collapseKey, signal, reportProgress) => {\n if (!isContextActive(context, signal)) return undefined;\n const { runTuiQuestionnaire } = await import('../../tui/runQuestionnaire.js');\n if (!isContextActive(context, signal)) return undefined;\n const result = await runTuiQuestionnaire(context, params, collapseKey, signal, reportProgress);\n return isContextActive(context, signal) ? result : undefined;\n },\n tryVoice: (params) => (active && sessionReady ? voiceHandoff?.handoff(params) : undefined),\n });\n registerAskUserQuestionReconciler(pi, isContextActive);\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active) return;\n const previousCoordinator = coordinator;\n previousCoordinator.shutdown();\n const activeSessionId = context.sessionManager.getSessionId();\n const generation = ++sessionGeneration;\n const nextCoordinator = new QuestionnaireCoordinator();\n coordinator = nextCoordinator;\n sessionId = activeSessionId;\n sessionReady = false;\n\n const initializeSession = async (): Promise<void> => {\n await previousCoordinator.waitForIdle();\n if (!ownsGeneration(generation, activeSessionId)) return;\n sessionReady = true;\n // A session can start with Voice already active, for example across a Voice reload.\n syncToolGate();\n };\n\n const operation = lifecycleQueue.then(initializeSession, initializeSession);\n lifecycleQueue = operation.catch(() => undefined);\n return trackOperation(operation);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function userFeedbackExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(userFeedbackPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface UserFeedbackPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction userFeedbackPlugin(cordis: Context, config: UserFeedbackPluginConfig): void {\n installUserFeedbackRuntime(cordis, config.pi);\n}\n\nexport default userFeedbackExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+HsB,sBAAsB,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+JsB,sBAAsB,IAAI,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+HsB,sBAAsB,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+JsB,sBAAsB,IAAI,eAAe"}
@@ -1,2 +1,2 @@
1
- import{QuestionnaireCoordinator as e}from"../../services/questionnaireCoordinator.mjs";import{createVoiceQuestionHandoff as t}from"../doom/voiceQuestionHandoff.mjs";import{registerAskUserQuestionTool as n}from"./askUserQuestionAdapter.mjs";import{registerAskUserQuestionReconciler as r}from"./reconcileAdapter.mjs";import{connectDoomCordisHost as i}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_MINOR_MODE_CATALOG_SERVICE as a,requireMinorModeCatalog as o}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_NARRATION_SERVICE as s,requireDoomNarrationService as c}from"@agimon-ai/doompi-extension-contracts/narration";const l=`@agimon-ai/doompi-user-feedback`;function u(){return{answers:[],cancelled:!0}}function d(i,d){let f=!0,p=0,m,h=!1,g=new e,_,v=Promise.resolve(),y=new Set,b=e=>(y.add(e),e.then(()=>y.delete(e),()=>y.delete(e)),e),x=(e,t)=>f&&e===p&&t===m,S=(e,t)=>f&&h&&t?.aborted!==!0&&e.sessionManager.getSessionId()===m,C=async()=>{if(!f)return;let e=g;e.shutdown(),f=!1,p+=1,h=!1,m=void 0,await Promise.allSettled(y),await e.waitForIdle()};i.effect(()=>C,`${l}/runtime`),i.inject([a,s],e=>{let n=t(o(e),c(e));return _=n,()=>{_===n&&(_=void 0)}}),n(d,i,{enqueue:(e,t)=>{let n=g,r=p,i=m;return!h||!x(r,i)||t?.aborted?Promise.resolve(u()):n.enqueue(async t=>{if(!h||!x(r,i)||t.signal.aborted)return u();let n=await e({...t,reportProgress:e=>{x(r,i)&&!t.signal.aborted&&t.reportProgress(e)}});return h&&x(r,i)&&!t.signal.aborted?n:u()},t)},isActive:S,runTui:async(e,t,n,r,i)=>{if(!S(e,r))return;let{runTuiQuestionnaire:a}=await import(`../../tui/runQuestionnaire.mjs`);if(!S(e,r))return;let o=await a(e,t,n,r,i);return S(e,r)?o:void 0},tryVoice:e=>f&&h?_?.handoff(e):void 0}),r(d,S),d.on(`session_start`,(t,n)=>{if(!f)return;let r=g;r.shutdown();let i=n.sessionManager.getSessionId(),a=++p;g=new e,m=i,h=!1;let o=async()=>{await r.waitForIdle(),x(a,i)&&(h=!0)},s=v.then(o,o);return v=s.catch(()=>void 0),b(s)})}async function f(e){let t=await i(e,l),n=t.root.plugin(p,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function p(e,t){d(e,t.pi)}export{f as default,f as userFeedbackExtension,d as installUserFeedbackRuntime};
1
+ import{AskUserToolGate as e}from"../../services/askUserToolGate.mjs";import{isAutonomousVoiceActive as t}from"../../services/autonomousVoiceMode.mjs";import{QuestionnaireCoordinator as n}from"../../services/questionnaireCoordinator.mjs";import{createVoiceQuestionHandoff as r}from"../doom/voiceQuestionHandoff.mjs";import{readActiveToolRegistry as i}from"./activeToolRegistry.mjs";import{ASK_USER_QUESTION_TOOL_NAME as a,registerAskUserQuestionTool as o}from"./askUserQuestionAdapter.mjs";import{registerAskUserQuestionReconciler as s}from"./reconcileAdapter.mjs";import{connectDoomCordisHost as c}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_MINOR_MODE_CATALOG_SERVICE as l,requireMinorModeCatalog as u}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_NARRATION_SERVICE as d,requireDoomNarrationService as f}from"@agimon-ai/doompi-extension-contracts/narration";const p=`@agimon-ai/doompi-user-feedback`;function m(){return{answers:[],cancelled:!0}}function h(c,h){let g=!0,_=0,v,y=!1,b=new n,x,S=i(h),C=S?new e(S,a):void 0,w=()=>void 0,T=Promise.resolve(),E=new Set,D=e=>(E.add(e),e.then(()=>E.delete(e),()=>E.delete(e)),e),O=(e,t)=>g&&e===_&&t===v,k=(e,t)=>g&&y&&t?.aborted!==!0&&e.sessionManager.getSessionId()===v,A=async()=>{if(!g)return;C?.release();let e=b;e.shutdown(),g=!1,_+=1,y=!1,v=void 0,await Promise.allSettled(E),await e.waitForIdle()};c.effect(()=>A,`${p}/runtime`),c.inject([l,d],e=>{let t=r(u(e),f(e));return x=t,()=>{x===t&&(x=void 0)}}),c.inject([l],e=>{if(!C)return;let n=u(e),r=()=>{g&&y&&C.sync(t(n.list()))};w=r;let i=n.subscribe(r);return r(),()=>{i(),w===r&&(w=()=>void 0),C.release()}}),o(h,c,{enqueue:(e,t)=>{let n=b,r=_,i=v;return!y||!O(r,i)||t?.aborted?Promise.resolve(m()):n.enqueue(async t=>{if(!y||!O(r,i)||t.signal.aborted)return m();let n=await e({...t,reportProgress:e=>{O(r,i)&&!t.signal.aborted&&t.reportProgress(e)}});return y&&O(r,i)&&!t.signal.aborted?n:m()},t)},isActive:k,runTui:async(e,t,n,r,i)=>{if(!k(e,r))return;let{runTuiQuestionnaire:a}=await import(`../../tui/runQuestionnaire.mjs`);if(!k(e,r))return;let o=await a(e,t,n,r,i);return k(e,r)?o:void 0},tryVoice:e=>g&&y?x?.handoff(e):void 0}),s(h,k),h.on(`session_start`,(e,t)=>{if(!g)return;let r=b;r.shutdown();let i=t.sessionManager.getSessionId(),a=++_;b=new n,v=i,y=!1;let o=async()=>{await r.waitForIdle(),O(a,i)&&(y=!0,w())},s=T.then(o,o);return T=s.catch(()=>void 0),D(s)})}async function g(e){let t=await c(e,p),n=t.root.plugin(_,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function _(e,t){h(e,t.pi)}export{g as default,g as userFeedbackExtension,h as installUserFeedbackRuntime};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_MINOR_MODE_CATALOG_SERVICE, requireMinorModeCatalog } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_NARRATION_SERVICE, requireDoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { QuestionnaireCoordinator, type QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoff } from '../doom/voiceQuestionHandoff.js';\nimport { registerAskUserQuestionTool } from './askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from './reconcileAdapter.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst SESSION_START_EVENT = 'session_start';\n\nfunction cancelledResult(): QuestionnaireResult {\n return { answers: [], cancelled: true };\n}\n\n/** Install one user-feedback runtime into its host-owned Cordis plugin fiber. */\nexport function installUserFeedbackRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let sessionReady = false;\n let coordinator = new QuestionnaireCoordinator();\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const ownsGeneration = (generation: number, expectedSessionId: string | undefined): boolean =>\n active && generation === sessionGeneration && expectedSessionId === sessionId;\n const isContextActive = (context: ExtensionContext, signal?: AbortSignal): boolean =>\n active && sessionReady && signal?.aborted !== true && context.sessionManager.getSessionId() === sessionId;\n\n const shutdownRuntime = async (): Promise<void> => {\n if (!active) return;\n const ownedCoordinator = coordinator;\n ownedCoordinator.shutdown();\n active = false;\n sessionGeneration += 1;\n sessionReady = false;\n sessionId = undefined;\n await Promise.allSettled(pendingOperations);\n await ownedCoordinator.waitForIdle();\n };\n cordis.effect(() => shutdownRuntime, `${PACKAGE_SOURCE}/runtime`);\n\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE, DOOM_NARRATION_SERVICE], (serviceContext) => {\n const handoff = createVoiceQuestionHandoff(\n requireMinorModeCatalog(serviceContext),\n requireDoomNarrationService(serviceContext),\n );\n voiceHandoff = handoff;\n return () => {\n if (voiceHandoff === handoff) voiceHandoff = undefined;\n };\n });\n\n registerAskUserQuestionTool(pi, cordis, {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => {\n const ownedCoordinator = coordinator;\n const generation = sessionGeneration;\n const expectedSessionId = sessionId;\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || signal?.aborted) {\n return Promise.resolve(cancelledResult());\n }\n return ownedCoordinator.enqueue(async (context) => {\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || context.signal.aborted) {\n return cancelledResult();\n }\n const result = await runner({\n ...context,\n reportProgress: (progress) => {\n if (ownsGeneration(generation, expectedSessionId) && !context.signal.aborted) {\n context.reportProgress(progress);\n }\n },\n });\n return sessionReady && ownsGeneration(generation, expectedSessionId) && !context.signal.aborted\n ? result\n : cancelledResult();\n }, signal);\n },\n isActive: isContextActive,\n runTui: async (context, params, collapseKey, signal, reportProgress) => {\n if (!isContextActive(context, signal)) return undefined;\n const { runTuiQuestionnaire } = await import('../../tui/runQuestionnaire.js');\n if (!isContextActive(context, signal)) return undefined;\n const result = await runTuiQuestionnaire(context, params, collapseKey, signal, reportProgress);\n return isContextActive(context, signal) ? result : undefined;\n },\n tryVoice: (params) => (active && sessionReady ? voiceHandoff?.handoff(params) : undefined),\n });\n registerAskUserQuestionReconciler(pi, isContextActive);\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active) return;\n const previousCoordinator = coordinator;\n previousCoordinator.shutdown();\n const activeSessionId = context.sessionManager.getSessionId();\n const generation = ++sessionGeneration;\n const nextCoordinator = new QuestionnaireCoordinator();\n coordinator = nextCoordinator;\n sessionId = activeSessionId;\n sessionReady = false;\n\n const initializeSession = async (): Promise<void> => {\n await previousCoordinator.waitForIdle();\n if (!ownsGeneration(generation, activeSessionId)) return;\n sessionReady = true;\n };\n\n const operation = lifecycleQueue.then(initializeSession, initializeSession);\n lifecycleQueue = operation.catch(() => undefined);\n return trackOperation(operation);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function userFeedbackExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(userFeedbackPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface UserFeedbackPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction userFeedbackPlugin(cordis: Context, config: UserFeedbackPluginConfig): void {\n installUserFeedbackRuntime(cordis, config.pi);\n}\n\nexport default userFeedbackExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_MINOR_MODE_CATALOG_SERVICE, requireMinorModeCatalog } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_NARRATION_SERVICE, requireDoomNarrationService } from '@agimon-ai/doompi-extension-contracts/narration';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { AskUserToolGate } from '../../services/askUserToolGate.js';\nimport { isAutonomousVoiceActive } from '../../services/autonomousVoiceMode.js';\nimport { QuestionnaireCoordinator, type QuestionnaireRunner } from '../../services/questionnaireCoordinator.js';\nimport type { QuestionnaireResult } from '../../types/questionnaire.js';\nimport { createVoiceQuestionHandoff, type VoiceQuestionHandoff } from '../doom/voiceQuestionHandoff.js';\nimport { readActiveToolRegistry } from './activeToolRegistry.js';\nimport { ASK_USER_QUESTION_TOOL_NAME, registerAskUserQuestionTool } from './askUserQuestionAdapter.js';\nimport { registerAskUserQuestionReconciler } from './reconcileAdapter.js';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-user-feedback';\nconst SESSION_START_EVENT = 'session_start';\n\nfunction cancelledResult(): QuestionnaireResult {\n return { answers: [], cancelled: true };\n}\n\n/** Install one user-feedback runtime into its host-owned Cordis plugin fiber. */\nexport function installUserFeedbackRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let sessionReady = false;\n let coordinator = new QuestionnaireCoordinator();\n let voiceHandoff: VoiceQuestionHandoff | undefined;\n const registry = readActiveToolRegistry(pi);\n const toolGate = registry ? new AskUserToolGate(registry, ASK_USER_QUESTION_TOOL_NAME) : undefined;\n let syncToolGate = (): void => undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n const ownsGeneration = (generation: number, expectedSessionId: string | undefined): boolean =>\n active && generation === sessionGeneration && expectedSessionId === sessionId;\n const isContextActive = (context: ExtensionContext, signal?: AbortSignal): boolean =>\n active && sessionReady && signal?.aborted !== true && context.sessionManager.getSessionId() === sessionId;\n\n const shutdownRuntime = async (): Promise<void> => {\n if (!active) return;\n // Released before the flags drop, so a runtime torn down while Voice is still active\n // cannot leave the tool hidden: Pi re-activates a refreshed tool only when its name is\n // new to the registry, and after a reload this one is not.\n toolGate?.release();\n const ownedCoordinator = coordinator;\n ownedCoordinator.shutdown();\n active = false;\n sessionGeneration += 1;\n sessionReady = false;\n sessionId = undefined;\n await Promise.allSettled(pendingOperations);\n await ownedCoordinator.waitForIdle();\n };\n cordis.effect(() => shutdownRuntime, `${PACKAGE_SOURCE}/runtime`);\n\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE, DOOM_NARRATION_SERVICE], (serviceContext) => {\n const handoff = createVoiceQuestionHandoff(\n requireMinorModeCatalog(serviceContext),\n requireDoomNarrationService(serviceContext),\n );\n voiceHandoff = handoff;\n return () => {\n if (voiceHandoff === handoff) voiceHandoff = undefined;\n };\n });\n\n // Separate from the handoff binding above: the gate needs only the mode catalog, so it\n // still applies to a Voice build that publishes modes without providing narration.\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n if (!toolGate) return undefined;\n const modes = requireMinorModeCatalog(modeContext);\n const apply = (): void => {\n // Pi binds the active-tool accessors to a started session runtime and throws before\n // that, so nothing is applied until the session is ready.\n if (active && sessionReady) toolGate.sync(isAutonomousVoiceActive(modes.list()));\n };\n syncToolGate = apply;\n const unsubscribe = modes.subscribe(apply);\n apply();\n return () => {\n unsubscribe();\n if (syncToolGate === apply) syncToolGate = (): void => undefined;\n toolGate.release();\n };\n });\n\n registerAskUserQuestionTool(pi, cordis, {\n enqueue: (runner: QuestionnaireRunner, signal?: AbortSignal) => {\n const ownedCoordinator = coordinator;\n const generation = sessionGeneration;\n const expectedSessionId = sessionId;\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || signal?.aborted) {\n return Promise.resolve(cancelledResult());\n }\n return ownedCoordinator.enqueue(async (context) => {\n if (!sessionReady || !ownsGeneration(generation, expectedSessionId) || context.signal.aborted) {\n return cancelledResult();\n }\n const result = await runner({\n ...context,\n reportProgress: (progress) => {\n if (ownsGeneration(generation, expectedSessionId) && !context.signal.aborted) {\n context.reportProgress(progress);\n }\n },\n });\n return sessionReady && ownsGeneration(generation, expectedSessionId) && !context.signal.aborted\n ? result\n : cancelledResult();\n }, signal);\n },\n isActive: isContextActive,\n runTui: async (context, params, collapseKey, signal, reportProgress) => {\n if (!isContextActive(context, signal)) return undefined;\n const { runTuiQuestionnaire } = await import('../../tui/runQuestionnaire.js');\n if (!isContextActive(context, signal)) return undefined;\n const result = await runTuiQuestionnaire(context, params, collapseKey, signal, reportProgress);\n return isContextActive(context, signal) ? result : undefined;\n },\n tryVoice: (params) => (active && sessionReady ? voiceHandoff?.handoff(params) : undefined),\n });\n registerAskUserQuestionReconciler(pi, isContextActive);\n\n pi.on(SESSION_START_EVENT, (_event, context) => {\n if (!active) return;\n const previousCoordinator = coordinator;\n previousCoordinator.shutdown();\n const activeSessionId = context.sessionManager.getSessionId();\n const generation = ++sessionGeneration;\n const nextCoordinator = new QuestionnaireCoordinator();\n coordinator = nextCoordinator;\n sessionId = activeSessionId;\n sessionReady = false;\n\n const initializeSession = async (): Promise<void> => {\n await previousCoordinator.waitForIdle();\n if (!ownsGeneration(generation, activeSessionId)) return;\n sessionReady = true;\n // A session can start with Voice already active, for example across a Voice reload.\n syncToolGate();\n };\n\n const operation = lifecycleQueue.then(initializeSession, initializeSession);\n lifecycleQueue = operation.catch(() => undefined);\n return trackOperation(operation);\n });\n}\n\n/** The package's sole Pi factory; Pi reloads it and Cordis owns all package resources. */\nexport async function userFeedbackExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(userFeedbackPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface UserFeedbackPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction userFeedbackPlugin(cordis: Context, config: UserFeedbackPluginConfig): void {\n installUserFeedbackRuntime(cordis, config.pi);\n}\n\nexport default userFeedbackExtension;\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ var e=class{registry;toolName;removed=!1;constructor(e,t){this.registry=e,this.toolName=t}sync(e){e?this.hide():this.release()}release(){if(!this.removed)return;this.removed=!1;let e=this.registry.getActiveTools();e.includes(this.toolName)||this.registry.setActiveTools([...e,this.toolName])}hide(){let e=this.registry.getActiveTools();e.includes(this.toolName)&&(this.registry.setActiveTools(e.filter(e=>e!==this.toolName)),this.removed=!0)}};exports.AskUserToolGate=e;
2
+ //# sourceMappingURL=askUserToolGate.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askUserToolGate.cjs","names":[],"sources":["../../src/services/askUserToolGate.ts"],"sourcesContent":["import type { ActiveToolRegistry } from '../types/toolActivation.js';\n\n/**\n * Drops one tool from Pi's active list while autonomous Voice is speaking.\n *\n * Voice narrates its own question and then waits for the spoken reply, so a questionnaire\n * tool alongside it asks the same thing twice. Ownership is tracked rather than recomputed\n * because the active list is global: the gate restores only a removal it made itself, so a\n * tool the user switched off stays off, and no other package's tools are touched.\n */\nexport class AskUserToolGate {\n private removed = false;\n\n constructor(\n private readonly registry: ActiveToolRegistry,\n private readonly toolName: string,\n ) {}\n\n /** Applies the tool state matching the current autonomous-voice activation. */\n sync(voiceActive: boolean): void {\n if (voiceActive) this.hide();\n else this.release();\n }\n\n /**\n * Restores a gate-owned removal when the voice binding or the runtime goes away.\n *\n * Safe before the session runtime is bound: without an owned removal it never reaches\n * the registry, and an owned removal proves a bound runtime already accepted a write.\n */\n release(): void {\n if (!this.removed) return;\n this.removed = false;\n const active = this.registry.getActiveTools();\n if (active.includes(this.toolName)) return;\n this.registry.setActiveTools([...active, this.toolName]);\n }\n\n private hide(): void {\n const active = this.registry.getActiveTools();\n // Read the list rather than trust the flag: Pi rebuilds the active set on a tool\n // refresh, so a stale flag would leave the tool showing for the rest of the session.\n // Already absent means nothing to claim, and claiming it would re-enable a tool the\n // gate never hid.\n if (!active.includes(this.toolName)) return;\n this.registry.setActiveTools(active.filter((name) => name !== this.toolName));\n this.removed = true;\n }\n}\n"],"mappings":"AAUA,IAAa,EAAb,KAA6B,CAIR,SACA,SAJnB,QAAkB,GAElB,YACE,EACA,EACA,CAFiB,KAAA,SAAA,EACA,KAAA,SAAA,CAChB,CAGH,KAAK,EAA4B,CAC3B,EAAa,KAAK,KAAK,EACtB,KAAK,QAAQ,CACpB,CAQA,SAAgB,CACd,GAAI,CAAC,KAAK,QAAS,OACnB,KAAK,QAAU,GACf,IAAM,EAAS,KAAK,SAAS,eAAe,EACxC,EAAO,SAAS,KAAK,QAAQ,GACjC,KAAK,SAAS,eAAe,CAAC,GAAG,EAAQ,KAAK,QAAQ,CAAC,CACzD,CAEA,MAAqB,CACnB,IAAM,EAAS,KAAK,SAAS,eAAe,EAKvC,EAAO,SAAS,KAAK,QAAQ,IAClC,KAAK,SAAS,eAAe,EAAO,OAAQ,GAAS,IAAS,KAAK,QAAQ,CAAC,EAC5E,KAAK,QAAU,GACjB,CACF"}
@@ -0,0 +1,2 @@
1
+ var e=class{registry;toolName;removed=!1;constructor(e,t){this.registry=e,this.toolName=t}sync(e){e?this.hide():this.release()}release(){if(!this.removed)return;this.removed=!1;let e=this.registry.getActiveTools();e.includes(this.toolName)||this.registry.setActiveTools([...e,this.toolName])}hide(){let e=this.registry.getActiveTools();e.includes(this.toolName)&&(this.registry.setActiveTools(e.filter(e=>e!==this.toolName)),this.removed=!0)}};export{e as AskUserToolGate};
2
+ //# sourceMappingURL=askUserToolGate.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"askUserToolGate.mjs","names":[],"sources":["../../src/services/askUserToolGate.ts"],"sourcesContent":["import type { ActiveToolRegistry } from '../types/toolActivation.js';\n\n/**\n * Drops one tool from Pi's active list while autonomous Voice is speaking.\n *\n * Voice narrates its own question and then waits for the spoken reply, so a questionnaire\n * tool alongside it asks the same thing twice. Ownership is tracked rather than recomputed\n * because the active list is global: the gate restores only a removal it made itself, so a\n * tool the user switched off stays off, and no other package's tools are touched.\n */\nexport class AskUserToolGate {\n private removed = false;\n\n constructor(\n private readonly registry: ActiveToolRegistry,\n private readonly toolName: string,\n ) {}\n\n /** Applies the tool state matching the current autonomous-voice activation. */\n sync(voiceActive: boolean): void {\n if (voiceActive) this.hide();\n else this.release();\n }\n\n /**\n * Restores a gate-owned removal when the voice binding or the runtime goes away.\n *\n * Safe before the session runtime is bound: without an owned removal it never reaches\n * the registry, and an owned removal proves a bound runtime already accepted a write.\n */\n release(): void {\n if (!this.removed) return;\n this.removed = false;\n const active = this.registry.getActiveTools();\n if (active.includes(this.toolName)) return;\n this.registry.setActiveTools([...active, this.toolName]);\n }\n\n private hide(): void {\n const active = this.registry.getActiveTools();\n // Read the list rather than trust the flag: Pi rebuilds the active set on a tool\n // refresh, so a stale flag would leave the tool showing for the rest of the session.\n // Already absent means nothing to claim, and claiming it would re-enable a tool the\n // gate never hid.\n if (!active.includes(this.toolName)) return;\n this.registry.setActiveTools(active.filter((name) => name !== this.toolName));\n this.removed = true;\n }\n}\n"],"mappings":"AAUA,IAAa,EAAb,KAA6B,CAIR,SACA,SAJnB,QAAkB,GAElB,YACE,EACA,EACA,CAFiB,KAAA,SAAA,EACA,KAAA,SAAA,CAChB,CAGH,KAAK,EAA4B,CAC3B,EAAa,KAAK,KAAK,EACtB,KAAK,QAAQ,CACpB,CAQA,SAAgB,CACd,GAAI,CAAC,KAAK,QAAS,OACnB,KAAK,QAAU,GACf,IAAM,EAAS,KAAK,SAAS,eAAe,EACxC,EAAO,SAAS,KAAK,QAAQ,GACjC,KAAK,SAAS,eAAe,CAAC,GAAG,EAAQ,KAAK,QAAQ,CAAC,CACzD,CAEA,MAAqB,CACnB,IAAM,EAAS,KAAK,SAAS,eAAe,EAKvC,EAAO,SAAS,KAAK,QAAQ,IAClC,KAAK,SAAS,eAAe,EAAO,OAAQ,GAAS,IAAS,KAAK,QAAQ,CAAC,EAC5E,KAAK,QAAU,GACjB,CACF"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/narration");function t(t){return t.descriptor.source===e.DOOM_VOICE_SOURCE&&t.descriptor.id===e.DOOM_VOICE_AUTO_MODE_ID&&t.state.activation===`active`}function n(e){return e.some(e=>t(e))}exports.isAutonomousVoiceActive=n,exports.isAutonomousVoiceRecord=t;
2
+ //# sourceMappingURL=autonomousVoiceMode.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autonomousVoiceMode.cjs","names":["DOOM_VOICE_SOURCE","DOOM_VOICE_AUTO_MODE_ID"],"sources":["../../src/services/autonomousVoiceMode.ts"],"sourcesContent":["import type { MinorModeRecord } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE } from '@agimon-ai/doompi-extension-contracts/narration';\n\n/**\n * Matches only the Voice package's own autonomous mode, and only while it reports active.\n *\n * Both the source and the mode id are checked because another package may register a mode\n * under the same id, and a transitional `activating` or `deactivating` record is not yet\n * (or no longer) speaking.\n */\nexport function isAutonomousVoiceRecord(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 isAutonomousVoiceActive(records: readonly MinorModeRecord[]): boolean {\n return records.some((record) => isAutonomousVoiceRecord(record));\n}\n"],"mappings":"iEAUA,SAAgB,EAAwB,EAAkC,CACxE,OACE,EAAO,WAAW,SAAWA,EAAAA,mBAC7B,EAAO,WAAW,KAAOC,EAAAA,yBACzB,EAAO,MAAM,aAAe,QAEhC,CAEA,SAAgB,EAAwB,EAA8C,CACpF,OAAO,EAAQ,KAAM,GAAW,EAAwB,CAAM,CAAC,CACjE"}
@@ -0,0 +1,2 @@
1
+ import{DOOM_VOICE_AUTO_MODE_ID as e,DOOM_VOICE_SOURCE as t}from"@agimon-ai/doompi-extension-contracts/narration";function n(n){return n.descriptor.source===t&&n.descriptor.id===e&&n.state.activation===`active`}function r(e){return e.some(e=>n(e))}export{r as isAutonomousVoiceActive,n as isAutonomousVoiceRecord};
2
+ //# sourceMappingURL=autonomousVoiceMode.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"autonomousVoiceMode.mjs","names":[],"sources":["../../src/services/autonomousVoiceMode.ts"],"sourcesContent":["import type { MinorModeRecord } from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_VOICE_AUTO_MODE_ID, DOOM_VOICE_SOURCE } from '@agimon-ai/doompi-extension-contracts/narration';\n\n/**\n * Matches only the Voice package's own autonomous mode, and only while it reports active.\n *\n * Both the source and the mode id are checked because another package may register a mode\n * under the same id, and a transitional `activating` or `deactivating` record is not yet\n * (or no longer) speaking.\n */\nexport function isAutonomousVoiceRecord(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 isAutonomousVoiceActive(records: readonly MinorModeRecord[]): boolean {\n return records.some((record) => isAutonomousVoiceRecord(record));\n}\n"],"mappings":"iHAUA,SAAgB,EAAwB,EAAkC,CACxE,OACE,EAAO,WAAW,SAAW,GAC7B,EAAO,WAAW,KAAO,GACzB,EAAO,MAAM,aAAe,QAEhC,CAEA,SAAgB,EAAwB,EAA8C,CACpF,OAAO,EAAQ,KAAM,GAAW,EAAwB,CAAM,CAAC,CACjE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-user-feedback",
3
- "version": "0.0.1-alpha.22",
3
+ "version": "0.0.1-alpha.24",
4
4
  "description": "Structured user questions with interactive and autonomous Voice handoff for Pi agents.",
5
5
  "keywords": [
6
6
  "ai",
@@ -46,8 +46,8 @@
46
46
  "dependencies": {
47
47
  "@deepseek-ai/cordis": "4.0.1",
48
48
  "typebox": "1.3.16",
49
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.33",
50
- "@agimon-ai/doompi-ui": "0.0.1-alpha.33"
49
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.35",
50
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.35"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@earendil-works/pi-coding-agent": "0.84.2",