@agimon-ai/doompi-profile 0.0.1-alpha.43 → 0.0.1-alpha.45

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 (31) hide show
  1. package/README.md +31 -1
  2. package/dist/adapters/pi/extension.cjs +1 -1
  3. package/dist/adapters/pi/extension.cjs.map +1 -1
  4. package/dist/adapters/pi/extension.d.cts.map +1 -1
  5. package/dist/adapters/pi/extension.d.mts.map +1 -1
  6. package/dist/adapters/pi/extension.mjs +1 -1
  7. package/dist/adapters/pi/extension.mjs.map +1 -1
  8. package/dist/adapters/pi/identityEntry.cjs +2 -0
  9. package/dist/adapters/pi/identityEntry.cjs.map +1 -0
  10. package/dist/adapters/pi/identityEntry.mjs +2 -0
  11. package/dist/adapters/pi/identityEntry.mjs.map +1 -0
  12. package/dist/adapters/pi/voiceTool.cjs +2 -0
  13. package/dist/adapters/pi/voiceTool.cjs.map +1 -0
  14. package/dist/adapters/pi/voiceTool.mjs +2 -0
  15. package/dist/adapters/pi/voiceTool.mjs.map +1 -0
  16. package/dist/commands/profileCommand.cjs +1 -1
  17. package/dist/commands/profileCommand.cjs.map +1 -1
  18. package/dist/commands/profileCommand.mjs +1 -1
  19. package/dist/commands/profileCommand.mjs.map +1 -1
  20. package/dist/schemas/profileVoiceTools.cjs +2 -0
  21. package/dist/schemas/profileVoiceTools.cjs.map +1 -0
  22. package/dist/schemas/profileVoiceTools.mjs +2 -0
  23. package/dist/schemas/profileVoiceTools.mjs.map +1 -0
  24. package/dist/services/profileText.cjs +1 -1
  25. package/dist/services/profileText.cjs.map +1 -1
  26. package/dist/services/profileText.d.cts.map +1 -1
  27. package/dist/services/profileText.d.mts.map +1 -1
  28. package/dist/services/profileText.mjs +2 -2
  29. package/dist/services/profileText.mjs.map +1 -1
  30. package/package.json +8 -7
  31. package/src/prompts/doompi-author-profile/references/profiles-contract.md +57 -2
package/README.md CHANGED
@@ -22,6 +22,33 @@ harness assembles them into one file in the run directory and records its path;
22
22
  appends that file to the system prompt on every agent start, so the repository's own `AGENTS.md`
23
23
  keeps applying underneath it.
24
24
 
25
+ ## Persona identity
26
+
27
+ A persona can say who it is in YAML front-matter at the top of its `profile.md`:
28
+
29
+ ```markdown
30
+ ---
31
+ name: Rhea
32
+ icon: avatar.png
33
+ voice:
34
+ voice: Karen
35
+ rate: 175
36
+ ---
37
+
38
+ You are Rhea, a blunt systems architect.
39
+ ```
40
+
41
+ The cockpit shows `name` and `icon` beside each assistant message, and `voice` shadows the spoken
42
+ voice from `config.yaml` for this persona only. Every field is optional, front-matter is stripped
43
+ from the prompt, and a persona that declares none behaves exactly as before. Identity lives in the
44
+ persona folder rather than in `profiles.yaml`, so discovered profiles get it too.
45
+
46
+ Because a message keeps the identity it was written under, switching profiles mid-conversation
47
+ leaves earlier replies attributed to the persona that produced them.
48
+
49
+ See the [profiles contract](./src/prompts/doompi-author-profile/references/profiles-contract.md)
50
+ for the icon rules and the exact precedence.
51
+
25
52
  ## What it registers
26
53
 
27
54
  | Entry | Surface |
@@ -44,7 +71,10 @@ before the reload and restored after it.
44
71
 
45
72
  Personal profiles load from `~/.pi/.doom/profiles.yaml`, then repository profiles load from
46
73
  `.doom/profiles.yaml`. Roots discover either one persona folder or direct-child persona folders.
47
- Explicit entries provide a persona path and optional string environment defaults. Resolution runs
74
+ Explicit entries provide a persona path and optional string environment defaults. `defaultProfile`
75
+ names the persona a run starts on when nothing else does, mirroring `defaultMajorMode` in
76
+ `modes.yaml` and `defaultDomains` in `domains.yaml`; `--profile` and an inherited `DOOMPI_PROFILE`
77
+ both override it. Resolution runs
48
78
  from personal discovery to repository discovery, then personal explicit entries to repository
49
79
  explicit entries. A later profile replaces an earlier profile with the same name.
50
80
 
@@ -1,2 +1,2 @@
1
- const e=require("../telemetry/logSinkTelemetry.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../services/profileText.cjs"),r=require("../../commands/profileCommand.cjs");let i=require("@agimon-ai/doompi-extension-contracts/config"),a=require("@agimon-ai/doompi-extension-contracts/cordis-host"),o=require("@agimon-ai/doompi-config/piContext"),s=require("@agimon-ai/doompi-extension-contracts/help"),c=require("@agimon-ai/doompi-extension-contracts/transition");const l=`@agimon-ai/doompi-profile`;async function u(e,n){try{let[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return n(t(e)).length>0}catch(e){return n.recordError(t.PROFILE_EVENT.profileLoadFailed,e),!0}}async function d(t,n=e.createProfileTelemetry()){let r=await(0,a.connectDoomCordisHost)(t,l),i=r.root.plugin(f,{pi:t,telemetry:n});try{await i}catch(e){try{await i.dispose()}finally{await r.dispose()}throw e}let o;t.on(`session_shutdown`,()=>o??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}function f(e,t){e.inject([s.DOOM_HELP_SERVICE],e=>{let t=(0,s.requireDoomHelpService)(e).register({source:l,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-profile`,description:`Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.`}]});return()=>t.dispose()});let a,d=e.inject([i.DOOM_CONFIG_SERVICE,c.DOOM_TRANSITION_SERVICE],e=>(a=e,()=>{a===e&&(a=void 0)})),f=()=>{if(!a)throw Error(`Doom profile runtime is waiting for the session config service.`);return a};e.effect(function*(){r.registerProfileCommand(t.pi,t.telemetry,f),t.pi.on(`session_start`,async(e,r)=>{await d.await();let i=(0,o.requireDoomConfigContext)(f()).harness,a=n.profileStatus(i.profile,await u(i,t.telemetry));a!==void 0&&r.ui.setStatus(n.PROFILE_STATUS_KEY,a)}),yield()=>void 0},l)}exports.default=d,exports.profileExtension=d;
1
+ const e=require("../telemetry/logSinkTelemetry.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../services/profileText.cjs"),r=require("../../commands/profileCommand.cjs"),i=require("./identityEntry.cjs"),a=require("./voiceTool.cjs");let o=require("@agimon-ai/doompi-extension-contracts/config"),s=require("@agimon-ai/doompi-extension-contracts/cordis-host"),c=require("@agimon-ai/doompi-config/piContext"),l=require("@agimon-ai/doompi-extension-contracts/help"),u=require("@agimon-ai/doompi-extension-contracts/transition"),d=require("@agimon-ai/doompi-extension-contracts/voice-reload-handoff"),f=require("@agimon-ai/doompi-extension-contracts/voice-tools");const p=`@agimon-ai/doompi-profile`;async function m(e,n){try{let[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return n(t(e)).length>0}catch(e){return n.recordError(t.PROFILE_EVENT.profileLoadFailed,e),!0}}async function h(t,n=e.createProfileTelemetry()){let r=await(0,s.connectDoomCordisHost)(t,p),i=r.root.plugin(g,{pi:t,telemetry:n});try{await i}catch(e){try{await i.dispose()}finally{await r.dispose()}throw e}let a;t.on(`session_shutdown`,()=>a??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}function g(e,t){e.inject([l.DOOM_HELP_SERVICE],e=>{let t=(0,l.requireDoomHelpService)(e).register({source:p,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-profile`,description:`Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.`}]});return()=>t.dispose()});let s,h=e.inject([o.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE],e=>(s=e,()=>{s===e&&(s=void 0)})),g=()=>{if(!s)throw Error(`Doom profile runtime is waiting for the session config service.`);return s},_=(0,d.createVoiceReloadHandoffStore)({now:()=>Date.now(),createToken:()=>crypto.randomUUID()}),v=async()=>{let e=(0,c.requireDoomConfigContext)(g()).harness,[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return{...e.profile===void 0?{}:{current:e.profile},profiles:n(t(e))}};e.inject([o.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE,f.DOOM_VOICE_TOOLS_SERVICE],e=>a.registerProfileVoiceCapability((0,f.requireDoomVoiceToolsService)(e),t.pi,v,_,()=>e)),e.effect(function*(){let e;r.registerProfileCommand(t.pi,t.telemetry,g,_),t.pi.on(`session_start`,async(r,a)=>{await h.await();let o=(0,c.requireDoomConfigContext)(g()).harness,s=n.profileStatus(o.profile,await m(o,t.telemetry));s!==void 0&&a.ui.setStatus(n.PROFILE_STATUS_KEY,s),e=i.publishProfileIdentity(t.pi,o.profile,o.profileIdentity,e)}),yield()=>void 0},p)}exports.default=h,exports.profileExtension=h;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["PROFILE_EVENT","createProfileTelemetry","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_CONFIG_SERVICE","DOOM_TRANSITION_SERVICE","requireDoomConfigContext","profileStatus","PROFILE_STATUS_KEY"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import type { HarnessState } from '@agimon-ai/doompi-config/types';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport { PROFILE_STATUS_KEY, profileStatus } from '../../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\n\n/**\n * Whether the session has any profiles to offer. Reading the catalogue is\n * the one startup cost the axis needs; a broken catalogue reports true so\n * the axis stays on the bar and /profile can explain the failure.\n */\nasync function profileCatalogueExists(\n state: Pick<HarnessState, 'root'>,\n telemetry: ProfileTelemetry,\n): Promise<boolean> {\n try {\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return loadProfiles(requireHarnessRoot(state)).length > 0;\n } catch (error) {\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n return true;\n }\n}\n\n/**\n * The package's single standard Pi factory.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(profilePlugin, { pi, telemetry });\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 ProfilePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction profilePlugin(cordis: Context, config: ProfilePluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-profile',\n description:\n 'Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom profile runtime is waiting for the session config service.');\n return activeContext;\n };\n cordis.effect(function* () {\n registerProfileCommand(config.pi, config.telemetry, requireRuntimeContext);\n config.pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const status = profileStatus(state.profile, await profileCatalogueExists(state, config.telemetry));\n if (status !== undefined) ctx.ui.setStatus(PROFILE_STATUS_KEY, status);\n });\n yield () => undefined;\n }, PACKAGE_SOURCE);\n}\n\nexport default profileExtension;\n"],"mappings":"0dAaA,MAAM,EAAiB,4BAOvB,eAAe,EACb,EACA,EACkB,CAClB,GAAI,CACF,GAAM,CAAC,CAAE,sBAAsB,CAAE,iBAAkB,MAAM,QAAQ,IAAI,CACnE,OAAO,yCACP,OAAO,oCACT,CAAC,EACD,OAAO,EAAa,EAAmB,CAAK,CAAC,CAAC,CAAC,OAAS,CAC1D,OAAS,EAAO,CAEd,OADA,EAAe,YAAYA,EAAAA,cAAc,kBAAmB,CAAK,EAC1D,EACT,CACF,CAKA,eAAsB,EACpB,EACA,EAA8BC,EAAAA,uBAAuB,EACtC,CACf,IAAM,EAAa,MAAA,EAAMC,EAAAA,sBAAAA,CAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,WAAU,CAAC,EACrE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAOA,SAAS,EAAc,EAAiB,EAAmC,CACzE,EAAO,OAAO,CAACC,EAAAA,iBAAiB,EAAI,GAAgB,CAClD,IAAM,GAAA,EAAeC,EAAAA,uBAAAA,CAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAA,QAAA,KAAA,CAAA,CAAA,cAAA,UAAA,CAAA,CAAA,KACA,OAAQ,CACN,CACE,KAAM,wBACN,YACE,+OACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,IAAI,EACE,EAAmB,EAAO,OAAO,CAACC,EAAAA,oBAAqBC,EAAAA,uBAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,iEAAiE,EACrG,OAAO,CACT,EACA,EAAO,OAAO,WAAa,CACzB,EAAA,uBAAuB,EAAO,GAAI,EAAO,UAAW,CAAqB,EACzE,EAAO,GAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CACnD,MAAM,EAAiB,MAAM,EAC7B,IAAM,GAAA,EAAQC,EAAAA,yBAAAA,CAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,EAASC,EAAAA,cAAc,EAAM,QAAS,MAAM,EAAuB,EAAO,EAAO,SAAS,CAAC,EAC7F,IAAW,IAAA,IAAW,EAAI,GAAG,UAAUC,EAAAA,mBAAoB,CAAM,CACvE,CAAC,EACD,SAAY,IAAA,EACd,EAAG,CAAc,CACnB"}
1
+ {"version":3,"file":"extension.cjs","names":["PROFILE_EVENT","createProfileTelemetry","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService","DOOM_CONFIG_SERVICE","DOOM_TRANSITION_SERVICE","createVoiceReloadHandoffStore","requireDoomConfigContext","DOOM_VOICE_TOOLS_SERVICE","registerProfileVoiceCapability","requireDoomVoiceToolsService","profileStatus","PROFILE_STATUS_KEY","publishProfileIdentity"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import type { HarnessState } from '@agimon-ai/doompi-config/types';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport { PROFILE_STATUS_KEY, profileStatus } from '../../services/profileText.ts';\nimport { publishProfileIdentity } from './identityEntry.ts';\nimport { registerProfileVoiceCapability, type ProfileVoiceView } from './voiceTool.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\n\n/**\n * Whether the session has any profiles to offer. Reading the catalogue is\n * the one startup cost the axis needs; a broken catalogue reports true so\n * the axis stays on the bar and /profile can explain the failure.\n */\nasync function profileCatalogueExists(\n state: Pick<HarnessState, 'root'>,\n telemetry: ProfileTelemetry,\n): Promise<boolean> {\n try {\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return loadProfiles(requireHarnessRoot(state)).length > 0;\n } catch (error) {\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n return true;\n }\n}\n\n/**\n * The package's single standard Pi factory.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(profilePlugin, { pi, telemetry });\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 ProfilePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction profilePlugin(cordis: Context, config: ProfilePluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-profile',\n description:\n 'Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom profile runtime is waiting for the session config service.');\n return activeContext;\n };\n\n // The store standing between the voice capability and the command handler:\n // the tool cannot reload the session itself, so it parks the chosen profile\n // here and hands the command an opaque token.\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n\n const currentView = async (): Promise<ProfileVoiceView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return {\n ...(state.profile === undefined ? {} : { current: state.profile }),\n profiles: loadProfiles(requireHarnessRoot(state)),\n };\n };\n\n // A separate injection, so the command keeps working in a session that has no\n // autonomous voice and the two-service inject above stays exactly as it was.\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerProfileVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n config.pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n cordis.effect(function* () {\n let publishedIdentity: string | undefined;\n registerProfileCommand(config.pi, config.telemetry, requireRuntimeContext, reloadHandoffs);\n config.pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const status = profileStatus(state.profile, await profileCatalogueExists(state, config.telemetry));\n if (status !== undefined) ctx.ui.setStatus(PROFILE_STATUS_KEY, status);\n publishedIdentity = publishProfileIdentity(config.pi, state.profile, state.profileIdentity, publishedIdentity);\n });\n yield () => undefined;\n }, PACKAGE_SOURCE);\n}\n\nexport default profileExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;wBAuCsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;wBA8CsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;wBAuCsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;wBA8CsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
@@ -1,2 +1,2 @@
1
- import{createProfileTelemetry as e}from"../telemetry/logSinkTelemetry.mjs";import{PROFILE_EVENT as t}from"../../types/telemetry.mjs";import{PROFILE_STATUS_KEY as n,profileStatus as r}from"../../services/profileText.mjs";import{registerProfileCommand as i}from"../../commands/profileCommand.mjs";import{DOOM_CONFIG_SERVICE as a}from"@agimon-ai/doompi-extension-contracts/config";import{connectDoomCordisHost as o}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{requireDoomConfigContext as s}from"@agimon-ai/doompi-config/piContext";import{DOOM_HELP_SERVICE as c,requireDoomHelpService as l}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_TRANSITION_SERVICE as u}from"@agimon-ai/doompi-extension-contracts/transition";const d=`@agimon-ai/doompi-profile`;async function f(e,n){try{let[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return n(t(e)).length>0}catch(e){return n.recordError(t.profileLoadFailed,e),!0}}async function p(t,n=e()){let r=await o(t,d),i=r.root.plugin(m,{pi:t,telemetry:n});try{await i}catch(e){try{await i.dispose()}finally{await r.dispose()}throw e}let a;t.on(`session_shutdown`,()=>a??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}function m(e,t){e.inject([c],e=>{let t=l(e).register({source:d,moduleUrl:import.meta.url,skills:[{name:`doompi-author-profile`,description:`Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.`}]});return()=>t.dispose()});let o,p=e.inject([a,u],e=>(o=e,()=>{o===e&&(o=void 0)})),m=()=>{if(!o)throw Error(`Doom profile runtime is waiting for the session config service.`);return o};e.effect(function*(){i(t.pi,t.telemetry,m),t.pi.on(`session_start`,async(e,i)=>{await p.await();let a=s(m()).harness,o=r(a.profile,await f(a,t.telemetry));o!==void 0&&i.ui.setStatus(n,o)}),yield()=>void 0},d)}export{p as default,p as profileExtension};
1
+ import{createProfileTelemetry as e}from"../telemetry/logSinkTelemetry.mjs";import{PROFILE_EVENT as t}from"../../types/telemetry.mjs";import{PROFILE_STATUS_KEY as n,profileStatus as r}from"../../services/profileText.mjs";import{registerProfileCommand as i}from"../../commands/profileCommand.mjs";import{publishProfileIdentity as a}from"./identityEntry.mjs";import{registerProfileVoiceCapability as o}from"./voiceTool.mjs";import{DOOM_CONFIG_SERVICE as s}from"@agimon-ai/doompi-extension-contracts/config";import{connectDoomCordisHost as c}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{requireDoomConfigContext as l}from"@agimon-ai/doompi-config/piContext";import{DOOM_HELP_SERVICE as u,requireDoomHelpService as d}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_TRANSITION_SERVICE as f}from"@agimon-ai/doompi-extension-contracts/transition";import{createVoiceReloadHandoffStore as p}from"@agimon-ai/doompi-extension-contracts/voice-reload-handoff";import{DOOM_VOICE_TOOLS_SERVICE as m,requireDoomVoiceToolsService as h}from"@agimon-ai/doompi-extension-contracts/voice-tools";const g=`@agimon-ai/doompi-profile`;async function _(e,n){try{let[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return n(t(e)).length>0}catch(e){return n.recordError(t.profileLoadFailed,e),!0}}async function v(t,n=e()){let r=await c(t,g),i=r.root.plugin(y,{pi:t,telemetry:n});try{await i}catch(e){try{await i.dispose()}finally{await r.dispose()}throw e}let a;t.on(`session_shutdown`,()=>a??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}function y(e,t){e.inject([u],e=>{let t=d(e).register({source:g,moduleUrl:import.meta.url,skills:[{name:`doompi-author-profile`,description:`Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.`}]});return()=>t.dispose()});let c,v=e.inject([s,f],e=>(c=e,()=>{c===e&&(c=void 0)})),y=()=>{if(!c)throw Error(`Doom profile runtime is waiting for the session config service.`);return c},b=p({now:()=>Date.now(),createToken:()=>crypto.randomUUID()}),x=async()=>{let e=l(y()).harness,[{requireHarnessRoot:t},{loadProfiles:n}]=await Promise.all([import(`@agimon-ai/doompi-config/harnessStore`),import(`@agimon-ai/doompi-config/profiles`)]);return{...e.profile===void 0?{}:{current:e.profile},profiles:n(t(e))}};e.inject([s,f,m],e=>o(h(e),t.pi,x,b,()=>e)),e.effect(function*(){let e;i(t.pi,t.telemetry,y,b),t.pi.on(`session_start`,async(i,o)=>{await v.await();let s=l(y()).harness,c=r(s.profile,await _(s,t.telemetry));c!==void 0&&o.ui.setStatus(n,c),e=a(t.pi,s.profile,s.profileIdentity,e)}),yield()=>void 0},g)}export{v as default,v as profileExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import type { HarnessState } from '@agimon-ai/doompi-config/types';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport { PROFILE_STATUS_KEY, profileStatus } from '../../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\n\n/**\n * Whether the session has any profiles to offer. Reading the catalogue is\n * the one startup cost the axis needs; a broken catalogue reports true so\n * the axis stays on the bar and /profile can explain the failure.\n */\nasync function profileCatalogueExists(\n state: Pick<HarnessState, 'root'>,\n telemetry: ProfileTelemetry,\n): Promise<boolean> {\n try {\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return loadProfiles(requireHarnessRoot(state)).length > 0;\n } catch (error) {\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n return true;\n }\n}\n\n/**\n * The package's single standard Pi factory.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(profilePlugin, { pi, telemetry });\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 ProfilePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction profilePlugin(cordis: Context, config: ProfilePluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-profile',\n description:\n 'Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom profile runtime is waiting for the session config service.');\n return activeContext;\n };\n cordis.effect(function* () {\n registerProfileCommand(config.pi, config.telemetry, requireRuntimeContext);\n config.pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const status = profileStatus(state.profile, await profileCatalogueExists(state, config.telemetry));\n if (status !== undefined) ctx.ui.setStatus(PROFILE_STATUS_KEY, status);\n });\n yield () => undefined;\n }, PACKAGE_SOURCE);\n}\n\nexport default profileExtension;\n"],"mappings":"wuBAaA,MAAM,EAAiB,4BAOvB,eAAe,EACb,EACA,EACkB,CAClB,GAAI,CACF,GAAM,CAAC,CAAE,sBAAsB,CAAE,iBAAkB,MAAM,QAAQ,IAAI,CACnE,OAAO,yCACP,OAAO,oCACT,CAAC,EACD,OAAO,EAAa,EAAmB,CAAK,CAAC,CAAC,CAAC,OAAS,CAC1D,OAAS,EAAO,CAEd,OADA,EAAe,YAAY,EAAc,kBAAmB,CAAK,EAC1D,EACT,CACF,CAKA,eAAsB,EACpB,EACA,EAA8B,EAAuB,EACtC,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,WAAU,CAAC,EACrE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAOA,SAAS,EAAc,EAAiB,EAAmC,CACzE,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,wBACN,YACE,+OACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,IAAI,EACE,EAAmB,EAAO,OAAO,CAAC,EAAqB,CAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,iEAAiE,EACrG,OAAO,CACT,EACA,EAAO,OAAO,WAAa,CACzB,EAAuB,EAAO,GAAI,EAAO,UAAW,CAAqB,EACzE,EAAO,GAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CACnD,MAAM,EAAiB,MAAM,EAC7B,IAAM,EAAQ,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,EAAS,EAAc,EAAM,QAAS,MAAM,EAAuB,EAAO,EAAO,SAAS,CAAC,EAC7F,IAAW,IAAA,IAAW,EAAI,GAAG,UAAU,EAAoB,CAAM,CACvE,CAAC,EACD,SAAY,IAAA,EACd,EAAG,CAAc,CACnB"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import type { HarnessState } from '@agimon-ai/doompi-config/types';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport { PROFILE_STATUS_KEY, profileStatus } from '../../services/profileText.ts';\nimport { publishProfileIdentity } from './identityEntry.ts';\nimport { registerProfileVoiceCapability, type ProfileVoiceView } from './voiceTool.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\n\n/**\n * Whether the session has any profiles to offer. Reading the catalogue is\n * the one startup cost the axis needs; a broken catalogue reports true so\n * the axis stays on the bar and /profile can explain the failure.\n */\nasync function profileCatalogueExists(\n state: Pick<HarnessState, 'root'>,\n telemetry: ProfileTelemetry,\n): Promise<boolean> {\n try {\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return loadProfiles(requireHarnessRoot(state)).length > 0;\n } catch (error) {\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n return true;\n }\n}\n\n/**\n * The package's single standard Pi factory.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(profilePlugin, { pi, telemetry });\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 ProfilePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction profilePlugin(cordis: Context, config: ProfilePluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-profile',\n description:\n 'Configure DoomPi profile discovery, personas, environment defaults, and precedence in profiles.yaml. Use when creating or changing personal or repository profiles. Do not use for config.yaml runtime settings, modes.yaml, or domains.yaml.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom profile runtime is waiting for the session config service.');\n return activeContext;\n };\n\n // The store standing between the voice capability and the command handler:\n // the tool cannot reload the session itself, so it parks the chosen profile\n // here and hands the command an opaque token.\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n\n const currentView = async (): Promise<ProfileVoiceView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([\n import('@agimon-ai/doompi-config/harnessStore'),\n import('@agimon-ai/doompi-config/profiles'),\n ]);\n return {\n ...(state.profile === undefined ? {} : { current: state.profile }),\n profiles: loadProfiles(requireHarnessRoot(state)),\n };\n };\n\n // A separate injection, so the command keeps working in a session that has no\n // autonomous voice and the two-service inject above stays exactly as it was.\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerProfileVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n config.pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n cordis.effect(function* () {\n let publishedIdentity: string | undefined;\n registerProfileCommand(config.pi, config.telemetry, requireRuntimeContext, reloadHandoffs);\n config.pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const status = profileStatus(state.profile, await profileCatalogueExists(state, config.telemetry));\n if (status !== undefined) ctx.ui.setStatus(PROFILE_STATUS_KEY, status);\n publishedIdentity = publishProfileIdentity(config.pi, state.profile, state.profileIdentity, publishedIdentity);\n });\n yield () => undefined;\n }, PACKAGE_SOURCE);\n}\n\nexport default profileExtension;\n"],"mappings":"glCAoBA,MAAM,EAAiB,4BAOvB,eAAe,EACb,EACA,EACkB,CAClB,GAAI,CACF,GAAM,CAAC,CAAE,sBAAsB,CAAE,iBAAkB,MAAM,QAAQ,IAAI,CACnE,OAAO,yCACP,OAAO,oCACT,CAAC,EACD,OAAO,EAAa,EAAmB,CAAK,CAAC,CAAC,CAAC,OAAS,CAC1D,OAAS,EAAO,CAEd,OADA,EAAe,YAAY,EAAc,kBAAmB,CAAK,EAC1D,EACT,CACF,CAKA,eAAsB,EACpB,EACA,EAA8B,EAAuB,EACtC,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,WAAU,CAAC,EACrE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAOA,SAAS,EAAc,EAAiB,EAAmC,CACzE,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,wBACN,YACE,+OACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,IAAI,EACE,EAAmB,EAAO,OAAO,CAAC,EAAqB,CAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,iEAAiE,EACrG,OAAO,CACT,EAKM,EAAiB,EAA8B,CACnD,QAAW,KAAK,IAAI,EACpB,gBAAmB,OAAO,WAAW,CACvC,CAAC,EAEK,EAAc,SAAuC,CACzD,IAAM,EAAQ,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,CAAC,CAAE,sBAAsB,CAAE,iBAAkB,MAAM,QAAQ,IAAI,CACnE,OAAO,yCACP,OAAO,oCACT,CAAC,EACD,MAAO,CACL,GAAI,EAAM,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAM,OAAQ,EAChE,SAAU,EAAa,EAAmB,CAAK,CAAC,CAClD,CACF,EAIA,EAAO,OAAO,CAAC,EAAqB,EAAyB,CAAwB,EAAI,GACvF,EACE,EAA6B,CAAY,EACzC,EAAO,GACP,EACA,MACM,CACR,CACF,EACA,EAAO,OAAO,WAAa,CACzB,IAAI,EACJ,EAAuB,EAAO,GAAI,EAAO,UAAW,EAAuB,CAAc,EACzF,EAAO,GAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CACnD,MAAM,EAAiB,MAAM,EAC7B,IAAM,EAAQ,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,EAAS,EAAc,EAAM,QAAS,MAAM,EAAuB,EAAO,EAAO,SAAS,CAAC,EAC7F,IAAW,IAAA,IAAW,EAAI,GAAG,UAAU,EAAoB,CAAM,EACrE,EAAoB,EAAuB,EAAO,GAAI,EAAM,QAAS,EAAM,gBAAiB,CAAiB,CAC/G,CAAC,EACD,SAAY,IAAA,EACd,EAAG,CAAc,CACnB"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/profile-identity");function t(t,n,r,i){if(!n)return i;let a={profile:n,...r?.name===void 0?{}:{name:r.name},...r?.icon===void 0?{}:{icon:r.icon}},o=JSON.stringify(a);return o===i?i:(t.appendEntry(e.DOOM_PROFILE_IDENTITY_ENTRY_TYPE,a),o)}exports.publishProfileIdentity=t;
2
+ //# sourceMappingURL=identityEntry.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identityEntry.cjs","names":["DOOM_PROFILE_IDENTITY_ENTRY_TYPE"],"sources":["../../../src/adapters/pi/identityEntry.ts"],"sourcesContent":["import {\n DOOM_PROFILE_IDENTITY_ENTRY_TYPE,\n type ProfileIdentityProjection,\n} from '@agimon-ai/doompi-extension-contracts/profile-identity';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { PersonaIdentity } from '@agimon-ai/doompi-config/profiles';\n\n/**\n * Journals the persona the session is speaking as.\n *\n * A profile switch reloads the session, so each activation publishes once on\n * session start and the entries accumulate in journal order. That order is the\n * whole mechanism: a cockpit folding the journal forward attributes every\n * assistant message to the identity most recently published before it, which is\n * how a message keeps the persona it was written under after a switch.\n *\n * For that to hold, the entry must not be treated as a projection. A projection\n * keeps only the latest record and replays that one, which would repaint the\n * entire transcript as the current persona.\n */\nexport function publishProfileIdentity(\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n profile: string | undefined,\n identity: PersonaIdentity | undefined,\n published: string | undefined,\n): string | undefined {\n if (!profile) return published;\n\n const projection: ProfileIdentityProjection = {\n profile,\n ...(identity?.name === undefined ? {} : { name: identity.name }),\n ...(identity?.icon === undefined ? {} : { icon: identity.icon }),\n };\n const serialized = JSON.stringify(projection);\n // Reload republishes on every session start. Re-announcing an identity the\n // transcript already carries would add a seam where nothing changed.\n if (serialized === published) return published;\n pi.appendEntry(DOOM_PROFILE_IDENTITY_ENTRY_TYPE, projection);\n return serialized;\n}\n"],"mappings":"wEAoBA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,GAAI,CAAC,EAAS,OAAO,EAErB,IAAM,EAAwC,CAC5C,UACA,GAAI,GAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAS,IAAK,EAC9D,GAAI,GAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAS,IAAK,CAChE,EACM,EAAa,KAAK,UAAU,CAAU,EAK5C,OAFI,IAAe,EAAkB,GACrC,EAAG,YAAYA,EAAAA,iCAAkC,CAAU,EACpD,EACT"}
@@ -0,0 +1,2 @@
1
+ import{DOOM_PROFILE_IDENTITY_ENTRY_TYPE as e}from"@agimon-ai/doompi-extension-contracts/profile-identity";function t(t,n,r,i){if(!n)return i;let a={profile:n,...r?.name===void 0?{}:{name:r.name},...r?.icon===void 0?{}:{icon:r.icon}},o=JSON.stringify(a);return o===i?i:(t.appendEntry(e,a),o)}export{t as publishProfileIdentity};
2
+ //# sourceMappingURL=identityEntry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"identityEntry.mjs","names":[],"sources":["../../../src/adapters/pi/identityEntry.ts"],"sourcesContent":["import {\n DOOM_PROFILE_IDENTITY_ENTRY_TYPE,\n type ProfileIdentityProjection,\n} from '@agimon-ai/doompi-extension-contracts/profile-identity';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport type { PersonaIdentity } from '@agimon-ai/doompi-config/profiles';\n\n/**\n * Journals the persona the session is speaking as.\n *\n * A profile switch reloads the session, so each activation publishes once on\n * session start and the entries accumulate in journal order. That order is the\n * whole mechanism: a cockpit folding the journal forward attributes every\n * assistant message to the identity most recently published before it, which is\n * how a message keeps the persona it was written under after a switch.\n *\n * For that to hold, the entry must not be treated as a projection. A projection\n * keeps only the latest record and replays that one, which would repaint the\n * entire transcript as the current persona.\n */\nexport function publishProfileIdentity(\n pi: Pick<ExtensionAPI, 'appendEntry'>,\n profile: string | undefined,\n identity: PersonaIdentity | undefined,\n published: string | undefined,\n): string | undefined {\n if (!profile) return published;\n\n const projection: ProfileIdentityProjection = {\n profile,\n ...(identity?.name === undefined ? {} : { name: identity.name }),\n ...(identity?.icon === undefined ? {} : { icon: identity.icon }),\n };\n const serialized = JSON.stringify(projection);\n // Reload republishes on every session start. Re-announcing an identity the\n // transcript already carries would add a seam where nothing changed.\n if (serialized === published) return published;\n pi.appendEntry(DOOM_PROFILE_IDENTITY_ENTRY_TYPE, projection);\n return serialized;\n}\n"],"mappings":"0GAoBA,SAAgB,EACd,EACA,EACA,EACA,EACoB,CACpB,GAAI,CAAC,EAAS,OAAO,EAErB,IAAM,EAAwC,CAC5C,UACA,GAAI,GAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAS,IAAK,EAC9D,GAAI,GAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,KAAM,EAAS,IAAK,CAChE,EACM,EAAa,KAAK,UAAU,CAAU,EAK5C,OAFI,IAAe,EAAkB,GACrC,EAAG,YAAY,EAAkC,CAAU,EACpD,EACT"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../services/profileText.cjs"),t=require("../../schemas/profileVoiceTools.cjs");let n=require("@agimon-ai/doompi-extension-contracts/transition");function r(t){return{status:`listed`,...t.current===void 0?{}:{current:t.current},profiles:t.profiles.map(t=>({name:t.name,description:e.profileDescription(t),...t.identity?.name===void 0?{}:{displayName:t.identity.name}}))}}function i(i,a,o,s,c){let l=i.register({descriptor:{source:e.PROFILE_SOURCE,id:t.PROFILE_VOICE_TOOL_NAME,name:t.PROFILE_VOICE_TOOL_NAME,label:`Profile`,description:`List the personas configured for this session with their display names, or queue a switch to one of them.`,order:20,inputSchema:t.PROFILE_VOICE_INPUT_SCHEMA,resultSchema:t.PROFILE_VOICE_RESULT_SCHEMA},async execute(t,i){let l=t,u=await o(i.context);if(l.action===`list`)return r(u);let d=u.profiles.find(e=>e.name===l.profile);if(!d)throw Error(`Unknown profile: ${l.profile}`);if(d.name===u.current)return{status:`unchanged`,profile:d.name};let f=(0,n.requireDoomTransitionCoordinator)(c());f.plan({sessionId:i.sessionId,hostGeneration:f.hostGeneration,operationId:i.operationId,source:`voice`,target:{axis:`profile`,profile:d.name},signal:i.signal});let p=s.prepare({active:!i.signal.aborted,sessionId:i.sessionId,hostGeneration:i.hostGeneration},{operationId:i.operationId,kind:e.PROFILE_SWITCH_HANDOFF_KIND,profile:d.name});try{a.sendUserMessage.bind(a)(`/${e.PROFILE_COMMAND} ${e.VOICE_SWITCH_TOKEN_PREFIX}${p.token}`,{deliverAs:`followUp`,expandPromptTemplates:!0})}catch(e){throw p.discard(),e}return{status:`queued`,profile:d.name,stopBatch:`session-reload`}}});return()=>l.dispose()}exports.registerProfileVoiceCapability=i;
2
+ //# sourceMappingURL=voiceTool.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceTool.cjs","names":["profileDescription","PROFILE_SOURCE","PROFILE_VOICE_TOOL_NAME","PROFILE_VOICE_INPUT_SCHEMA","PROFILE_VOICE_RESULT_SCHEMA","requireDoomTransitionCoordinator","PROFILE_SWITCH_HANDOFF_KIND","PROFILE_COMMAND","VOICE_SWITCH_TOKEN_PREFIX"],"sources":["../../../src/adapters/pi/voiceTool.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { requireDoomTransitionCoordinator } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { VoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { DoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n PROFILE_VOICE_INPUT_SCHEMA,\n PROFILE_VOICE_RESULT_SCHEMA,\n PROFILE_VOICE_TOOL_NAME,\n type ProfileVoiceInput,\n type ProfileVoiceResult,\n} from '../../schemas/profileVoiceTools.ts';\nimport {\n PROFILE_COMMAND,\n PROFILE_SOURCE,\n PROFILE_SWITCH_HANDOFF_KIND,\n profileDescription,\n VOICE_SWITCH_TOKEN_PREFIX,\n} from '../../services/profileText.ts';\n\ntype VoiceMessageSender = (\n content: string,\n options: { readonly deliverAs: 'followUp'; readonly expandPromptTemplates: true },\n) => void;\n\n/** What the capability needs from the session, kept narrow so tests need no harness. */\nexport interface ProfileVoiceView {\n readonly current?: string;\n readonly profiles: readonly AgentProfile[];\n}\n\nfunction profileListing(view: ProfileVoiceView): ProfileVoiceResult {\n return {\n status: 'listed',\n ...(view.current === undefined ? {} : { current: view.current }),\n profiles: view.profiles.map((profile) => ({\n name: profile.name,\n description: profileDescription(profile),\n ...(profile.identity?.name === undefined ? {} : { displayName: profile.identity.name }),\n })),\n };\n}\n\n/**\n * The model-facing half of the axis.\n *\n * A switch is never applied here. It plans the transition, mints an opaque\n * handoff token and sends `/profile --voice-switch-token=…` back as a follow-up,\n * so the reload happens inside a command handler where it can be the terminal\n * action. The profile name stays out of the message text on purpose: an edited\n * transcript must not be able to redirect the switch.\n */\nexport function registerProfileVoiceCapability(\n voiceTools: DoomVoiceToolsService<ExtensionContext>,\n pi: ExtensionAPI,\n currentView: (ctx: ExtensionContext) => Promise<ProfileVoiceView>,\n reloadHandoffs: VoiceReloadHandoffStore,\n cordisContext: () => Context,\n): () => void {\n const registration = voiceTools.register({\n descriptor: {\n source: PROFILE_SOURCE,\n id: PROFILE_VOICE_TOOL_NAME,\n name: PROFILE_VOICE_TOOL_NAME,\n label: 'Profile',\n description:\n 'List the personas configured for this session with their display names, or queue a switch to one of them.',\n order: 20,\n inputSchema: PROFILE_VOICE_INPUT_SCHEMA,\n resultSchema: PROFILE_VOICE_RESULT_SCHEMA,\n },\n async execute(input, execution) {\n const params = input as ProfileVoiceInput;\n const view = await currentView(execution.context);\n if (params.action === 'list') return profileListing(view);\n\n const profile = view.profiles.find((candidate) => candidate.name === params.profile);\n if (!profile) {\n throw new Error(`Unknown profile: ${params.profile}`);\n }\n if (profile.name === view.current) return { status: 'unchanged', profile: profile.name };\n\n const coordinator = requireDoomTransitionCoordinator(cordisContext());\n coordinator.plan({\n sessionId: execution.sessionId,\n hostGeneration: coordinator.hostGeneration,\n operationId: execution.operationId,\n source: 'voice',\n target: { axis: 'profile', profile: profile.name },\n signal: execution.signal,\n });\n\n const reloadHandoff = reloadHandoffs.prepare(\n {\n active: !execution.signal.aborted,\n sessionId: execution.sessionId,\n hostGeneration: execution.hostGeneration,\n },\n {\n operationId: execution.operationId,\n kind: PROFILE_SWITCH_HANDOFF_KIND,\n profile: profile.name,\n },\n );\n try {\n const sendUserMessage = pi.sendUserMessage.bind(pi) as unknown as VoiceMessageSender;\n sendUserMessage(`/${PROFILE_COMMAND} ${VOICE_SWITCH_TOKEN_PREFIX}${reloadHandoff.token}`, {\n deliverAs: 'followUp',\n expandPromptTemplates: true,\n });\n } catch (error) {\n reloadHandoff.discard();\n throw error;\n }\n return { status: 'queued', profile: profile.name, stopBatch: 'session-reload' };\n },\n });\n return () => registration.dispose();\n}\n"],"mappings":"qKAgCA,SAAS,EAAe,EAA4C,CAClE,MAAO,CACL,OAAQ,SACR,GAAI,EAAK,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAK,OAAQ,EAC9D,SAAU,EAAK,SAAS,IAAK,IAAa,CACxC,KAAM,EAAQ,KACd,YAAaA,EAAAA,mBAAmB,CAAO,EACvC,GAAI,EAAQ,UAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,YAAa,EAAQ,SAAS,IAAK,CACvF,EAAE,CACJ,CACF,CAWA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACY,CACZ,IAAM,EAAe,EAAW,SAAS,CACvC,WAAY,CACV,OAAQC,EAAAA,eACR,GAAIC,EAAAA,wBACJ,KAAMA,EAAAA,wBACN,MAAO,UACP,YACE,4GACF,MAAO,GACP,YAAaC,EAAAA,2BACb,aAAcC,EAAAA,2BAChB,EACA,MAAM,QAAQ,EAAO,EAAW,CAC9B,IAAM,EAAS,EACT,EAAO,MAAM,EAAY,EAAU,OAAO,EAChD,GAAI,EAAO,SAAW,OAAQ,OAAO,EAAe,CAAI,EAExD,IAAM,EAAU,EAAK,SAAS,KAAM,GAAc,EAAU,OAAS,EAAO,OAAO,EACnF,GAAI,CAAC,EACH,MAAU,MAAM,oBAAoB,EAAO,SAAS,EAEtD,GAAI,EAAQ,OAAS,EAAK,QAAS,MAAO,CAAE,OAAQ,YAAa,QAAS,EAAQ,IAAK,EAEvF,IAAM,GAAA,EAAcC,EAAAA,iCAAAA,CAAiC,EAAc,CAAC,EACpE,EAAY,KAAK,CACf,UAAW,EAAU,UACrB,eAAgB,EAAY,eAC5B,YAAa,EAAU,YACvB,OAAQ,QACR,OAAQ,CAAE,KAAM,UAAW,QAAS,EAAQ,IAAK,EACjD,OAAQ,EAAU,MACpB,CAAC,EAED,IAAM,EAAgB,EAAe,QACnC,CACE,OAAQ,CAAC,EAAU,OAAO,QAC1B,UAAW,EAAU,UACrB,eAAgB,EAAU,cAC5B,EACA,CACE,YAAa,EAAU,YACvB,KAAMC,EAAAA,4BACN,QAAS,EAAQ,IACnB,CACF,EACA,GAAI,CAEF,EAD2B,gBAAgB,KAAK,CAClC,CAAC,CAAC,IAAIC,EAAAA,gBAAgB,GAAGC,EAAAA,4BAA4B,EAAc,QAAS,CACxF,UAAW,WACX,sBAAuB,EACzB,CAAC,CACH,OAAS,EAAO,CAEd,MADA,EAAc,QAAQ,EAChB,CACR,CACA,MAAO,CAAE,OAAQ,SAAU,QAAS,EAAQ,KAAM,UAAW,gBAAiB,CAChF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC"}
@@ -0,0 +1,2 @@
1
+ import{PROFILE_COMMAND as e,PROFILE_SOURCE as t,PROFILE_SWITCH_HANDOFF_KIND as n,VOICE_SWITCH_TOKEN_PREFIX as r,profileDescription as i}from"../../services/profileText.mjs";import{PROFILE_VOICE_INPUT_SCHEMA as a,PROFILE_VOICE_RESULT_SCHEMA as o,PROFILE_VOICE_TOOL_NAME as s}from"../../schemas/profileVoiceTools.mjs";import{requireDoomTransitionCoordinator as c}from"@agimon-ai/doompi-extension-contracts/transition";function l(e){return{status:`listed`,...e.current===void 0?{}:{current:e.current},profiles:e.profiles.map(e=>({name:e.name,description:i(e),...e.identity?.name===void 0?{}:{displayName:e.identity.name}}))}}function u(i,u,d,f,p){let m=i.register({descriptor:{source:t,id:s,name:s,label:`Profile`,description:`List the personas configured for this session with their display names, or queue a switch to one of them.`,order:20,inputSchema:a,resultSchema:o},async execute(t,i){let a=t,o=await d(i.context);if(a.action===`list`)return l(o);let s=o.profiles.find(e=>e.name===a.profile);if(!s)throw Error(`Unknown profile: ${a.profile}`);if(s.name===o.current)return{status:`unchanged`,profile:s.name};let m=c(p());m.plan({sessionId:i.sessionId,hostGeneration:m.hostGeneration,operationId:i.operationId,source:`voice`,target:{axis:`profile`,profile:s.name},signal:i.signal});let h=f.prepare({active:!i.signal.aborted,sessionId:i.sessionId,hostGeneration:i.hostGeneration},{operationId:i.operationId,kind:n,profile:s.name});try{u.sendUserMessage.bind(u)(`/${e} ${r}${h.token}`,{deliverAs:`followUp`,expandPromptTemplates:!0})}catch(e){throw h.discard(),e}return{status:`queued`,profile:s.name,stopBatch:`session-reload`}}});return()=>m.dispose()}export{u as registerProfileVoiceCapability};
2
+ //# sourceMappingURL=voiceTool.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voiceTool.mjs","names":[],"sources":["../../../src/adapters/pi/voiceTool.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport { requireDoomTransitionCoordinator } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { VoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { DoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n PROFILE_VOICE_INPUT_SCHEMA,\n PROFILE_VOICE_RESULT_SCHEMA,\n PROFILE_VOICE_TOOL_NAME,\n type ProfileVoiceInput,\n type ProfileVoiceResult,\n} from '../../schemas/profileVoiceTools.ts';\nimport {\n PROFILE_COMMAND,\n PROFILE_SOURCE,\n PROFILE_SWITCH_HANDOFF_KIND,\n profileDescription,\n VOICE_SWITCH_TOKEN_PREFIX,\n} from '../../services/profileText.ts';\n\ntype VoiceMessageSender = (\n content: string,\n options: { readonly deliverAs: 'followUp'; readonly expandPromptTemplates: true },\n) => void;\n\n/** What the capability needs from the session, kept narrow so tests need no harness. */\nexport interface ProfileVoiceView {\n readonly current?: string;\n readonly profiles: readonly AgentProfile[];\n}\n\nfunction profileListing(view: ProfileVoiceView): ProfileVoiceResult {\n return {\n status: 'listed',\n ...(view.current === undefined ? {} : { current: view.current }),\n profiles: view.profiles.map((profile) => ({\n name: profile.name,\n description: profileDescription(profile),\n ...(profile.identity?.name === undefined ? {} : { displayName: profile.identity.name }),\n })),\n };\n}\n\n/**\n * The model-facing half of the axis.\n *\n * A switch is never applied here. It plans the transition, mints an opaque\n * handoff token and sends `/profile --voice-switch-token=…` back as a follow-up,\n * so the reload happens inside a command handler where it can be the terminal\n * action. The profile name stays out of the message text on purpose: an edited\n * transcript must not be able to redirect the switch.\n */\nexport function registerProfileVoiceCapability(\n voiceTools: DoomVoiceToolsService<ExtensionContext>,\n pi: ExtensionAPI,\n currentView: (ctx: ExtensionContext) => Promise<ProfileVoiceView>,\n reloadHandoffs: VoiceReloadHandoffStore,\n cordisContext: () => Context,\n): () => void {\n const registration = voiceTools.register({\n descriptor: {\n source: PROFILE_SOURCE,\n id: PROFILE_VOICE_TOOL_NAME,\n name: PROFILE_VOICE_TOOL_NAME,\n label: 'Profile',\n description:\n 'List the personas configured for this session with their display names, or queue a switch to one of them.',\n order: 20,\n inputSchema: PROFILE_VOICE_INPUT_SCHEMA,\n resultSchema: PROFILE_VOICE_RESULT_SCHEMA,\n },\n async execute(input, execution) {\n const params = input as ProfileVoiceInput;\n const view = await currentView(execution.context);\n if (params.action === 'list') return profileListing(view);\n\n const profile = view.profiles.find((candidate) => candidate.name === params.profile);\n if (!profile) {\n throw new Error(`Unknown profile: ${params.profile}`);\n }\n if (profile.name === view.current) return { status: 'unchanged', profile: profile.name };\n\n const coordinator = requireDoomTransitionCoordinator(cordisContext());\n coordinator.plan({\n sessionId: execution.sessionId,\n hostGeneration: coordinator.hostGeneration,\n operationId: execution.operationId,\n source: 'voice',\n target: { axis: 'profile', profile: profile.name },\n signal: execution.signal,\n });\n\n const reloadHandoff = reloadHandoffs.prepare(\n {\n active: !execution.signal.aborted,\n sessionId: execution.sessionId,\n hostGeneration: execution.hostGeneration,\n },\n {\n operationId: execution.operationId,\n kind: PROFILE_SWITCH_HANDOFF_KIND,\n profile: profile.name,\n },\n );\n try {\n const sendUserMessage = pi.sendUserMessage.bind(pi) as unknown as VoiceMessageSender;\n sendUserMessage(`/${PROFILE_COMMAND} ${VOICE_SWITCH_TOKEN_PREFIX}${reloadHandoff.token}`, {\n deliverAs: 'followUp',\n expandPromptTemplates: true,\n });\n } catch (error) {\n reloadHandoff.discard();\n throw error;\n }\n return { status: 'queued', profile: profile.name, stopBatch: 'session-reload' };\n },\n });\n return () => registration.dispose();\n}\n"],"mappings":"gaAgCA,SAAS,EAAe,EAA4C,CAClE,MAAO,CACL,OAAQ,SACR,GAAI,EAAK,UAAY,IAAA,GAAY,CAAC,EAAI,CAAE,QAAS,EAAK,OAAQ,EAC9D,SAAU,EAAK,SAAS,IAAK,IAAa,CACxC,KAAM,EAAQ,KACd,YAAa,EAAmB,CAAO,EACvC,GAAI,EAAQ,UAAU,OAAS,IAAA,GAAY,CAAC,EAAI,CAAE,YAAa,EAAQ,SAAS,IAAK,CACvF,EAAE,CACJ,CACF,CAWA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACY,CACZ,IAAM,EAAe,EAAW,SAAS,CACvC,WAAY,CACV,OAAQ,EACR,GAAI,EACJ,KAAM,EACN,MAAO,UACP,YACE,4GACF,MAAO,GACP,YAAa,EACb,aAAc,CAChB,EACA,MAAM,QAAQ,EAAO,EAAW,CAC9B,IAAM,EAAS,EACT,EAAO,MAAM,EAAY,EAAU,OAAO,EAChD,GAAI,EAAO,SAAW,OAAQ,OAAO,EAAe,CAAI,EAExD,IAAM,EAAU,EAAK,SAAS,KAAM,GAAc,EAAU,OAAS,EAAO,OAAO,EACnF,GAAI,CAAC,EACH,MAAU,MAAM,oBAAoB,EAAO,SAAS,EAEtD,GAAI,EAAQ,OAAS,EAAK,QAAS,MAAO,CAAE,OAAQ,YAAa,QAAS,EAAQ,IAAK,EAEvF,IAAM,EAAc,EAAiC,EAAc,CAAC,EACpE,EAAY,KAAK,CACf,UAAW,EAAU,UACrB,eAAgB,EAAY,eAC5B,YAAa,EAAU,YACvB,OAAQ,QACR,OAAQ,CAAE,KAAM,UAAW,QAAS,EAAQ,IAAK,EACjD,OAAQ,EAAU,MACpB,CAAC,EAED,IAAM,EAAgB,EAAe,QACnC,CACE,OAAQ,CAAC,EAAU,OAAO,QAC1B,UAAW,EAAU,UACrB,eAAgB,EAAU,cAC5B,EACA,CACE,YAAa,EAAU,YACvB,KAAM,EACN,QAAS,EAAQ,IACnB,CACF,EACA,GAAI,CAEF,EAD2B,gBAAgB,KAAK,CAClC,CAAC,CAAC,IAAI,EAAgB,GAAG,IAA4B,EAAc,QAAS,CACxF,UAAW,WACX,sBAAuB,EACzB,CAAC,CACH,OAAS,EAAO,CAEd,MADA,EAAc,QAAQ,EAChB,CACR,CACA,MAAO,CAAE,OAAQ,SAAU,QAAS,EAAQ,KAAM,UAAW,gBAAiB,CAChF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC"}
@@ -1,2 +1,2 @@
1
- const e=require("../types/telemetry.cjs"),t=require("../services/profileText.cjs");let n=require("@agimon-ai/doompi-config/piContext"),r=require("@agimon-ai/doompi-extension-contracts/transition");const i=`warning`,a=`info`,o=`.doom/profiles.yaml`;function s(){let e,t,n,r,i;return{harnessStore:()=>e??=import(`@agimon-ai/doompi-config/harnessStore`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),profiles:()=>r??=import(`@agimon-ai/doompi-config/profiles`),selection:()=>i??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}async function c(e,n,r,i){let a=t.profileTitle(r);if(e.mode!==`tui`)return e.ui.select(a,n.map(e=>e.name));let{MatrixPickerComponent:o}=await i();return(await e.ui.custom((e,i,s,c)=>new o({title:a,items:t.profileItems(n),selected:r?[r]:[],multi:!1},i,s,c)))?.[0]}function l(l,u,d){let f=s();l.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(s,p)=>{let m=d(),h=(0,n.requireDoomConfigContext)(m).harness,[{requireHarnessRoot:g},{loadProfiles:_}]=await Promise.all([f.harnessStore(),f.profiles()]),v=g(h),y=h.profile,b;try{b=_(v)}catch(t){let n=t instanceof Error?t.message:String(t);u.recordError(e.PROFILE_EVENT.profileLoadFailed,t),p.ui.notify(`Could not load ${o}: ${n}`,i);return}if(b.length===0){p.ui.notify(`No profiles found in ${o}.`,i);return}let x=await c(p,b,y,f.picker),S=b.find(e=>e.name===x);if(!S)return;if(S.name===y){p.ui.notify(t.profileSummary(S),a);return}let C=(0,r.requireDoomTransitionCoordinator)(m),w,T=await C.execute({sessionId:p.sessionManager.getSessionId(),hostGeneration:C.hostGeneration,operationId:crypto.randomUUID(),source:p.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:S.name}},async(t,n)=>{w=(0,r.prepareMinorModeReloadHandoff)(m,p.sessionManager.getSessionId(),C.hostGeneration,n.operationId,n.previous.minorModes);try{let{applyProfile:t}=await f.selection();p.ui.notify(await t(S,h),a);let{persistHarnessSelection:r}=await f.journal();return r(l,{...h,profile:S.name}),u.recordEvent(e.PROFILE_EVENT.profileApplied,{"harness.profile":S.name,...y?{"harness.profile.previous":y}:{},"harness.transition.disposition":n.disposition,"harness.transition.operation_id":n.operationId}),`applied`}catch(e){throw w?.discard(),w=void 0,e}});if(T.outcome===`rejected`||T.outcome===`stale`){w?.discard(),p.ui.notify(`Profile transition was ${T.outcome}: ${T.diagnostics.join(`, `)}`,i);return}if(T.outcome!==`applied`){w?.discard();return}try{await p.waitForIdle(),await p.reload()}catch(e){throw w?.discard(),e}}})}exports.registerProfileCommand=l;
1
+ const e=require("../types/telemetry.cjs"),t=require("../services/profileText.cjs");let n=require("@agimon-ai/doompi-config/piContext"),r=require("@agimon-ai/doompi-extension-contracts/transition"),i=require("@agimon-ai/doompi-extension-contracts/voice-tools");const a=`warning`,o=`info`,s=`.doom/profiles.yaml`;function c(){let e,t,n,r,i;return{harnessStore:()=>e??=import(`@agimon-ai/doompi-config/harnessStore`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),profiles:()=>r??=import(`@agimon-ai/doompi-config/profiles`),selection:()=>i??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}async function l(e,n,r,i){let a=t.profileTitle(r);if(e.mode!==`tui`)return e.ui.select(a,n.map(e=>e.name));let{MatrixPickerComponent:o}=await i();return(await e.ui.custom((e,i,s,c)=>new o({title:a,items:t.profileItems(n),selected:r?[r]:[],multi:!1},i,s,c)))?.[0]}function u(u,d,f,p){let m=c();u.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(c,h)=>{let g=f(),_=(0,n.requireDoomConfigContext)(g).harness,[{requireHarnessRoot:v},{loadProfiles:y}]=await Promise.all([m.harnessStore(),m.profiles()]),b=v(_),x=_.profile,S=h.sessionManager.getSessionId(),C=p?t.voiceSwitchToken(c):void 0,w,T;try{T=y(b)}catch(t){let n=t instanceof Error?t.message:String(t);d.recordError(e.PROFILE_EVENT.profileLoadFailed,t),h.ui.notify(`Could not load ${s}: ${n}`,a);return}if(T.length===0){h.ui.notify(`No profiles found in ${s}.`,a);return}let E;if(C&&p){let e=(0,i.readDoomVoiceToolsService)(g)?.readSession(S);if(w=e?p.accept(C,{sessionId:S,hostGeneration:e.hostGeneration}):void 0,w?.kind!==`profile-switch`||!w.profile)throw Error(`The voice profile switch token is stale or belongs to another session.`);E=w.profile}else E=await l(h,T,x,m.picker);let D=T.find(e=>e.name===E);if(!D)return;if(D.name===x){h.ui.notify(t.profileSummary(D),o);return}let O=(0,r.requireDoomTransitionCoordinator)(g),k,A=await O.execute({sessionId:h.sessionManager.getSessionId(),hostGeneration:O.hostGeneration,operationId:crypto.randomUUID(),source:h.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:D.name}},async(t,n)=>{k=(0,r.prepareMinorModeReloadHandoff)(g,h.sessionManager.getSessionId(),O.hostGeneration,n.operationId,n.previous.minorModes);try{let{applyProfile:t}=await m.selection();h.ui.notify(await t(D,_),o);let{persistHarnessSelection:r}=await m.journal();return r(u,{..._,profile:D.name}),d.recordEvent(e.PROFILE_EVENT.profileApplied,{"harness.profile":D.name,...x?{"harness.profile.previous":x}:{},"harness.transition.disposition":n.disposition,"harness.transition.operation_id":n.operationId}),`applied`}catch(e){throw k?.discard(),k=void 0,e}});if(A.outcome===`rejected`||A.outcome===`stale`){k?.discard(),h.ui.notify(`Profile transition was ${A.outcome}: ${A.diagnostics.join(`, `)}`,a);return}if(A.outcome!==`applied`){k?.discard();return}try{await h.waitForIdle(),await h.reload()}catch(e){throw k?.discard(),e}}})}exports.registerProfileCommand=u;
2
2
  //# sourceMappingURL=profileCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileCommand.cjs","names":["profileTitle","profileItems","requireDoomConfigContext","PROFILE_EVENT","profileSummary","requireDoomTransitionCoordinator","prepareMinorModeReloadHandoff"],"sources":["../../src/commands/profileCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport {\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { profileItems, profileSummary, profileTitle } from '../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../types/telemetry.ts';\n\nconst WARNING = 'warning';\nconst INFO = 'info';\nconst PROFILE_CONFIG_PATH = '.doom/profiles.yaml';\n\n/**\n * The picker and everything behind it load on first use.\n *\n * Every session registers this command, but almost none of them run it, so the\n * profile catalogue, the TUI component and the switch stay off the startup path.\n */\nfunction lazyModules() {\n let harnessStore: Promise<typeof import('@agimon-ai/doompi-config/harnessStore')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let profiles: Promise<typeof import('@agimon-ai/doompi-config/profiles')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n harnessStore: () => (harnessStore ??= import('@agimon-ai/doompi-config/harnessStore')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n profiles: () => (profiles ??= import('@agimon-ai/doompi-config/profiles')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\nasync function pickProfile(\n ctx: ExtensionContext,\n profiles: AgentProfile[],\n current: string | undefined,\n loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>,\n): Promise<string | undefined> {\n const title = profileTitle(current);\n if (ctx.mode !== 'tui') {\n return ctx.ui.select(\n title,\n profiles.map((profile) => profile.name),\n );\n }\n const { MatrixPickerComponent } = await loadPicker();\n const chosen = await ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: profileItems(profiles), selected: current ? [current] : [], multi: false },\n theme,\n keybindings,\n done,\n ),\n );\n return chosen?.[0];\n}\n\nexport function registerProfileCommand(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n): void {\n const load = lazyModules();\n\n pi.registerCommand('profile', {\n description: 'Show profiles and load a different persona and environment',\n handler: async (_args, ctx) => {\n const cordis = cordisContext();\n const state = requireDoomConfigContext(cordis).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([load.harnessStore(), load.profiles()]);\n const root = requireHarnessRoot(state);\n const current = state.profile;\n let profiles: AgentProfile[];\n try {\n profiles = loadProfiles(root);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n ctx.ui.notify(`Could not load ${PROFILE_CONFIG_PATH}: ${reason}`, WARNING);\n return;\n }\n\n if (profiles.length === 0) {\n ctx.ui.notify(`No profiles found in ${PROFILE_CONFIG_PATH}.`, WARNING);\n return;\n }\n\n const picked = await pickProfile(ctx, profiles, current, load.picker);\n const profile = profiles.find((candidate) => candidate.name === picked);\n if (!profile) return;\n if (profile.name === current) {\n ctx.ui.notify(profileSummary(profile), INFO);\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: crypto.randomUUID(),\n source: ctx.mode === 'tui' ? 'ui' : 'command',\n target: { axis: 'profile', profile: profile.name },\n },\n async (_request, plan) => {\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n const { applyProfile } = await load.selection();\n ctx.ui.notify(await applyProfile(profile, state), INFO);\n const { persistHarnessSelection } = await load.journal();\n persistHarnessSelection(pi, { ...state, profile: profile.name });\n void telemetry.recordEvent(PROFILE_EVENT.profileApplied, {\n 'harness.profile': profile.name,\n ...(current ? { 'harness.profile.previous': current } : {}),\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n return 'applied';\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n throw error;\n }\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') {\n minorModeReloadHandoff?.discard();\n ctx.ui.notify(`Profile transition was ${result.outcome}: ${result.diagnostics.join(', ')}`, WARNING);\n return;\n }\n if (result.outcome !== 'applied') {\n minorModeReloadHandoff?.discard();\n return;\n }\n\n try {\n await ctx.waitForIdle();\n // Reload replaces the session and invalidates this command context.\n // Keep it as the handler's final action.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"profileCommand.cjs","names":["profileTitle","profileItems","requireDoomConfigContext","voiceSwitchToken","PROFILE_EVENT","readDoomVoiceToolsService","profileSummary","requireDoomTransitionCoordinator","prepareMinorModeReloadHandoff"],"sources":["../../src/commands/profileCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport {\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n PROFILE_SWITCH_HANDOFF_KIND,\n profileItems,\n profileSummary,\n profileTitle,\n voiceSwitchToken,\n} from '../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../types/telemetry.ts';\n\nconst WARNING = 'warning';\nconst INFO = 'info';\nconst PROFILE_CONFIG_PATH = '.doom/profiles.yaml';\n\n/**\n * The picker and everything behind it load on first use.\n *\n * Every session registers this command, but almost none of them run it, so the\n * profile catalogue, the TUI component and the switch stay off the startup path.\n */\nfunction lazyModules() {\n let harnessStore: Promise<typeof import('@agimon-ai/doompi-config/harnessStore')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let profiles: Promise<typeof import('@agimon-ai/doompi-config/profiles')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n harnessStore: () => (harnessStore ??= import('@agimon-ai/doompi-config/harnessStore')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n profiles: () => (profiles ??= import('@agimon-ai/doompi-config/profiles')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\nasync function pickProfile(\n ctx: ExtensionContext,\n profiles: AgentProfile[],\n current: string | undefined,\n loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>,\n): Promise<string | undefined> {\n const title = profileTitle(current);\n if (ctx.mode !== 'tui') {\n return ctx.ui.select(\n title,\n profiles.map((profile) => profile.name),\n );\n }\n const { MatrixPickerComponent } = await loadPicker();\n const chosen = await ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: profileItems(profiles), selected: current ? [current] : [], multi: false },\n theme,\n keybindings,\n done,\n ),\n );\n return chosen?.[0];\n}\n\nexport function registerProfileCommand(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n reloadHandoffs?: VoiceReloadHandoffStore,\n): void {\n const load = lazyModules();\n\n pi.registerCommand('profile', {\n description: 'Show profiles and load a different persona and environment',\n handler: async (args, ctx) => {\n const cordis = cordisContext();\n const state = requireDoomConfigContext(cordis).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([load.harnessStore(), load.profiles()]);\n const root = requireHarnessRoot(state);\n const current = state.profile;\n const sessionId = ctx.sessionManager.getSessionId();\n // A voice switch arrives as a bare token: the name never rides the text.\n const token = reloadHandoffs ? voiceSwitchToken(args) : undefined;\n let voiceHandoff: VoiceReloadHandoff | undefined;\n let profiles: AgentProfile[];\n try {\n profiles = loadProfiles(root);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n ctx.ui.notify(`Could not load ${PROFILE_CONFIG_PATH}: ${reason}`, WARNING);\n return;\n }\n\n if (profiles.length === 0) {\n ctx.ui.notify(`No profiles found in ${PROFILE_CONFIG_PATH}.`, WARNING);\n return;\n }\n\n let picked: string | undefined;\n if (token && reloadHandoffs) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n voiceHandoff = voiceSession\n ? reloadHandoffs.accept(token, { sessionId, hostGeneration: voiceSession.hostGeneration })\n : undefined;\n if (voiceHandoff?.kind !== PROFILE_SWITCH_HANDOFF_KIND || !voiceHandoff.profile) {\n throw new Error('The voice profile switch token is stale or belongs to another session.');\n }\n picked = voiceHandoff.profile;\n } else {\n picked = await pickProfile(ctx, profiles, current, load.picker);\n }\n const profile = profiles.find((candidate) => candidate.name === picked);\n if (!profile) return;\n if (profile.name === current) {\n ctx.ui.notify(profileSummary(profile), INFO);\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: crypto.randomUUID(),\n source: ctx.mode === 'tui' ? 'ui' : 'command',\n target: { axis: 'profile', profile: profile.name },\n },\n async (_request, plan) => {\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n const { applyProfile } = await load.selection();\n ctx.ui.notify(await applyProfile(profile, state), INFO);\n const { persistHarnessSelection } = await load.journal();\n persistHarnessSelection(pi, { ...state, profile: profile.name });\n void telemetry.recordEvent(PROFILE_EVENT.profileApplied, {\n 'harness.profile': profile.name,\n ...(current ? { 'harness.profile.previous': current } : {}),\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n return 'applied';\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n throw error;\n }\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') {\n minorModeReloadHandoff?.discard();\n ctx.ui.notify(`Profile transition was ${result.outcome}: ${result.diagnostics.join(', ')}`, WARNING);\n return;\n }\n if (result.outcome !== 'applied') {\n minorModeReloadHandoff?.discard();\n return;\n }\n\n try {\n await ctx.waitForIdle();\n // Reload replaces the session and invalidates this command context.\n // Keep it as the handler's final action.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{PROFILE_EVENT as e}from"../types/telemetry.mjs";import{profileItems as t,profileSummary as n,profileTitle as r}from"../services/profileText.mjs";import{requireDoomConfigContext as i}from"@agimon-ai/doompi-config/piContext";import{prepareMinorModeReloadHandoff as a,requireDoomTransitionCoordinator as o}from"@agimon-ai/doompi-extension-contracts/transition";const s=`warning`,c=`info`,l=`.doom/profiles.yaml`;function u(){let e,t,n,r,i;return{harnessStore:()=>e??=import(`@agimon-ai/doompi-config/harnessStore`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),profiles:()=>r??=import(`@agimon-ai/doompi-config/profiles`),selection:()=>i??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}async function d(e,n,i,a){let o=r(i);if(e.mode!==`tui`)return e.ui.select(o,n.map(e=>e.name));let{MatrixPickerComponent:s}=await a();return(await e.ui.custom((e,r,a,c)=>new s({title:o,items:t(n),selected:i?[i]:[],multi:!1},r,a,c)))?.[0]}function f(t,r,f){let p=u();t.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(u,m)=>{let h=f(),g=i(h).harness,[{requireHarnessRoot:_},{loadProfiles:v}]=await Promise.all([p.harnessStore(),p.profiles()]),y=_(g),b=g.profile,x;try{x=v(y)}catch(t){let n=t instanceof Error?t.message:String(t);r.recordError(e.profileLoadFailed,t),m.ui.notify(`Could not load ${l}: ${n}`,s);return}if(x.length===0){m.ui.notify(`No profiles found in ${l}.`,s);return}let S=await d(m,x,b,p.picker),C=x.find(e=>e.name===S);if(!C)return;if(C.name===b){m.ui.notify(n(C),c);return}let w=o(h),T,E=await w.execute({sessionId:m.sessionManager.getSessionId(),hostGeneration:w.hostGeneration,operationId:crypto.randomUUID(),source:m.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:C.name}},async(n,i)=>{T=a(h,m.sessionManager.getSessionId(),w.hostGeneration,i.operationId,i.previous.minorModes);try{let{applyProfile:n}=await p.selection();m.ui.notify(await n(C,g),c);let{persistHarnessSelection:a}=await p.journal();return a(t,{...g,profile:C.name}),r.recordEvent(e.profileApplied,{"harness.profile":C.name,...b?{"harness.profile.previous":b}:{},"harness.transition.disposition":i.disposition,"harness.transition.operation_id":i.operationId}),`applied`}catch(e){throw T?.discard(),T=void 0,e}});if(E.outcome===`rejected`||E.outcome===`stale`){T?.discard(),m.ui.notify(`Profile transition was ${E.outcome}: ${E.diagnostics.join(`, `)}`,s);return}if(E.outcome!==`applied`){T?.discard();return}try{await m.waitForIdle(),await m.reload()}catch(e){throw T?.discard(),e}}})}export{f as registerProfileCommand};
1
+ import{PROFILE_EVENT as e}from"../types/telemetry.mjs";import{profileItems as t,profileSummary as n,profileTitle as r,voiceSwitchToken as i}from"../services/profileText.mjs";import{requireDoomConfigContext as a}from"@agimon-ai/doompi-config/piContext";import{prepareMinorModeReloadHandoff as o,requireDoomTransitionCoordinator as s}from"@agimon-ai/doompi-extension-contracts/transition";import{readDoomVoiceToolsService as c}from"@agimon-ai/doompi-extension-contracts/voice-tools";const l=`warning`,u=`info`,d=`.doom/profiles.yaml`;function f(){let e,t,n,r,i;return{harnessStore:()=>e??=import(`@agimon-ai/doompi-config/harnessStore`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),profiles:()=>r??=import(`@agimon-ai/doompi-config/profiles`),selection:()=>i??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}async function p(e,n,i,a){let o=r(i);if(e.mode!==`tui`)return e.ui.select(o,n.map(e=>e.name));let{MatrixPickerComponent:s}=await a();return(await e.ui.custom((e,r,a,c)=>new s({title:o,items:t(n),selected:i?[i]:[],multi:!1},r,a,c)))?.[0]}function m(t,r,m,h){let g=f();t.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(f,_)=>{let v=m(),y=a(v).harness,[{requireHarnessRoot:b},{loadProfiles:x}]=await Promise.all([g.harnessStore(),g.profiles()]),S=b(y),C=y.profile,w=_.sessionManager.getSessionId(),T=h?i(f):void 0,E,D;try{D=x(S)}catch(t){let n=t instanceof Error?t.message:String(t);r.recordError(e.profileLoadFailed,t),_.ui.notify(`Could not load ${d}: ${n}`,l);return}if(D.length===0){_.ui.notify(`No profiles found in ${d}.`,l);return}let O;if(T&&h){let e=c(v)?.readSession(w);if(E=e?h.accept(T,{sessionId:w,hostGeneration:e.hostGeneration}):void 0,E?.kind!==`profile-switch`||!E.profile)throw Error(`The voice profile switch token is stale or belongs to another session.`);O=E.profile}else O=await p(_,D,C,g.picker);let k=D.find(e=>e.name===O);if(!k)return;if(k.name===C){_.ui.notify(n(k),u);return}let A=s(v),j,M=await A.execute({sessionId:_.sessionManager.getSessionId(),hostGeneration:A.hostGeneration,operationId:crypto.randomUUID(),source:_.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:k.name}},async(n,i)=>{j=o(v,_.sessionManager.getSessionId(),A.hostGeneration,i.operationId,i.previous.minorModes);try{let{applyProfile:n}=await g.selection();_.ui.notify(await n(k,y),u);let{persistHarnessSelection:a}=await g.journal();return a(t,{...y,profile:k.name}),r.recordEvent(e.profileApplied,{"harness.profile":k.name,...C?{"harness.profile.previous":C}:{},"harness.transition.disposition":i.disposition,"harness.transition.operation_id":i.operationId}),`applied`}catch(e){throw j?.discard(),j=void 0,e}});if(M.outcome===`rejected`||M.outcome===`stale`){j?.discard(),_.ui.notify(`Profile transition was ${M.outcome}: ${M.diagnostics.join(`, `)}`,l);return}if(M.outcome!==`applied`){j?.discard();return}try{await _.waitForIdle(),await _.reload()}catch(e){throw j?.discard(),e}}})}export{m as registerProfileCommand};
2
2
  //# sourceMappingURL=profileCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileCommand.mjs","names":[],"sources":["../../src/commands/profileCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport {\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport { profileItems, profileSummary, profileTitle } from '../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../types/telemetry.ts';\n\nconst WARNING = 'warning';\nconst INFO = 'info';\nconst PROFILE_CONFIG_PATH = '.doom/profiles.yaml';\n\n/**\n * The picker and everything behind it load on first use.\n *\n * Every session registers this command, but almost none of them run it, so the\n * profile catalogue, the TUI component and the switch stay off the startup path.\n */\nfunction lazyModules() {\n let harnessStore: Promise<typeof import('@agimon-ai/doompi-config/harnessStore')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let profiles: Promise<typeof import('@agimon-ai/doompi-config/profiles')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n harnessStore: () => (harnessStore ??= import('@agimon-ai/doompi-config/harnessStore')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n profiles: () => (profiles ??= import('@agimon-ai/doompi-config/profiles')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\nasync function pickProfile(\n ctx: ExtensionContext,\n profiles: AgentProfile[],\n current: string | undefined,\n loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>,\n): Promise<string | undefined> {\n const title = profileTitle(current);\n if (ctx.mode !== 'tui') {\n return ctx.ui.select(\n title,\n profiles.map((profile) => profile.name),\n );\n }\n const { MatrixPickerComponent } = await loadPicker();\n const chosen = await ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: profileItems(profiles), selected: current ? [current] : [], multi: false },\n theme,\n keybindings,\n done,\n ),\n );\n return chosen?.[0];\n}\n\nexport function registerProfileCommand(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n): void {\n const load = lazyModules();\n\n pi.registerCommand('profile', {\n description: 'Show profiles and load a different persona and environment',\n handler: async (_args, ctx) => {\n const cordis = cordisContext();\n const state = requireDoomConfigContext(cordis).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([load.harnessStore(), load.profiles()]);\n const root = requireHarnessRoot(state);\n const current = state.profile;\n let profiles: AgentProfile[];\n try {\n profiles = loadProfiles(root);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n ctx.ui.notify(`Could not load ${PROFILE_CONFIG_PATH}: ${reason}`, WARNING);\n return;\n }\n\n if (profiles.length === 0) {\n ctx.ui.notify(`No profiles found in ${PROFILE_CONFIG_PATH}.`, WARNING);\n return;\n }\n\n const picked = await pickProfile(ctx, profiles, current, load.picker);\n const profile = profiles.find((candidate) => candidate.name === picked);\n if (!profile) return;\n if (profile.name === current) {\n ctx.ui.notify(profileSummary(profile), INFO);\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: crypto.randomUUID(),\n source: ctx.mode === 'tui' ? 'ui' : 'command',\n target: { axis: 'profile', profile: profile.name },\n },\n async (_request, plan) => {\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n const { applyProfile } = await load.selection();\n ctx.ui.notify(await applyProfile(profile, state), INFO);\n const { persistHarnessSelection } = await load.journal();\n persistHarnessSelection(pi, { ...state, profile: profile.name });\n void telemetry.recordEvent(PROFILE_EVENT.profileApplied, {\n 'harness.profile': profile.name,\n ...(current ? { 'harness.profile.previous': current } : {}),\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n return 'applied';\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n throw error;\n }\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') {\n minorModeReloadHandoff?.discard();\n ctx.ui.notify(`Profile transition was ${result.outcome}: ${result.diagnostics.join(', ')}`, WARNING);\n return;\n }\n if (result.outcome !== 'applied') {\n minorModeReloadHandoff?.discard();\n return;\n }\n\n try {\n await ctx.waitForIdle();\n // Reload replaces the session and invalidates this command context.\n // Keep it as the handler's final action.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"profileCommand.mjs","names":[],"sources":["../../src/commands/profileCommand.ts"],"sourcesContent":["import { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport {\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n PROFILE_SWITCH_HANDOFF_KIND,\n profileItems,\n profileSummary,\n profileTitle,\n voiceSwitchToken,\n} from '../services/profileText.ts';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../types/telemetry.ts';\n\nconst WARNING = 'warning';\nconst INFO = 'info';\nconst PROFILE_CONFIG_PATH = '.doom/profiles.yaml';\n\n/**\n * The picker and everything behind it load on first use.\n *\n * Every session registers this command, but almost none of them run it, so the\n * profile catalogue, the TUI component and the switch stay off the startup path.\n */\nfunction lazyModules() {\n let harnessStore: Promise<typeof import('@agimon-ai/doompi-config/harnessStore')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let profiles: Promise<typeof import('@agimon-ai/doompi-config/profiles')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n harnessStore: () => (harnessStore ??= import('@agimon-ai/doompi-config/harnessStore')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n profiles: () => (profiles ??= import('@agimon-ai/doompi-config/profiles')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\nasync function pickProfile(\n ctx: ExtensionContext,\n profiles: AgentProfile[],\n current: string | undefined,\n loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>,\n): Promise<string | undefined> {\n const title = profileTitle(current);\n if (ctx.mode !== 'tui') {\n return ctx.ui.select(\n title,\n profiles.map((profile) => profile.name),\n );\n }\n const { MatrixPickerComponent } = await loadPicker();\n const chosen = await ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: profileItems(profiles), selected: current ? [current] : [], multi: false },\n theme,\n keybindings,\n done,\n ),\n );\n return chosen?.[0];\n}\n\nexport function registerProfileCommand(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n reloadHandoffs?: VoiceReloadHandoffStore,\n): void {\n const load = lazyModules();\n\n pi.registerCommand('profile', {\n description: 'Show profiles and load a different persona and environment',\n handler: async (args, ctx) => {\n const cordis = cordisContext();\n const state = requireDoomConfigContext(cordis).harness;\n const [{ requireHarnessRoot }, { loadProfiles }] = await Promise.all([load.harnessStore(), load.profiles()]);\n const root = requireHarnessRoot(state);\n const current = state.profile;\n const sessionId = ctx.sessionManager.getSessionId();\n // A voice switch arrives as a bare token: the name never rides the text.\n const token = reloadHandoffs ? voiceSwitchToken(args) : undefined;\n let voiceHandoff: VoiceReloadHandoff | undefined;\n let profiles: AgentProfile[];\n try {\n profiles = loadProfiles(root);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.profileLoadFailed, error);\n ctx.ui.notify(`Could not load ${PROFILE_CONFIG_PATH}: ${reason}`, WARNING);\n return;\n }\n\n if (profiles.length === 0) {\n ctx.ui.notify(`No profiles found in ${PROFILE_CONFIG_PATH}.`, WARNING);\n return;\n }\n\n let picked: string | undefined;\n if (token && reloadHandoffs) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n voiceHandoff = voiceSession\n ? reloadHandoffs.accept(token, { sessionId, hostGeneration: voiceSession.hostGeneration })\n : undefined;\n if (voiceHandoff?.kind !== PROFILE_SWITCH_HANDOFF_KIND || !voiceHandoff.profile) {\n throw new Error('The voice profile switch token is stale or belongs to another session.');\n }\n picked = voiceHandoff.profile;\n } else {\n picked = await pickProfile(ctx, profiles, current, load.picker);\n }\n const profile = profiles.find((candidate) => candidate.name === picked);\n if (!profile) return;\n if (profile.name === current) {\n ctx.ui.notify(profileSummary(profile), INFO);\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: crypto.randomUUID(),\n source: ctx.mode === 'tui' ? 'ui' : 'command',\n target: { axis: 'profile', profile: profile.name },\n },\n async (_request, plan) => {\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n const { applyProfile } = await load.selection();\n ctx.ui.notify(await applyProfile(profile, state), INFO);\n const { persistHarnessSelection } = await load.journal();\n persistHarnessSelection(pi, { ...state, profile: profile.name });\n void telemetry.recordEvent(PROFILE_EVENT.profileApplied, {\n 'harness.profile': profile.name,\n ...(current ? { 'harness.profile.previous': current } : {}),\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n return 'applied';\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n throw error;\n }\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') {\n minorModeReloadHandoff?.discard();\n ctx.ui.notify(`Profile transition was ${result.outcome}: ${result.diagnostics.join(', ')}`, WARNING);\n return;\n }\n if (result.outcome !== 'applied') {\n minorModeReloadHandoff?.discard();\n return;\n }\n\n try {\n await ctx.waitForIdle();\n // Reload replaces the session and invalidates this command context.\n // Keep it as the handler's final action.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ let e=require("@agimon-ai/doompi-extension-contracts/voice-tools"),t=require("typebox");const n=t.Type.String({minLength:1,maxLength:e.VOICE_TOOL_MAX_IDENTIFIER_LENGTH}),r=t.Type.Union([t.Type.Object({action:t.Type.Literal(`list`)},{additionalProperties:!1}),t.Type.Object({action:t.Type.Literal(`switch`),profile:n},{additionalProperties:!1})]),i=t.Type.Union([t.Type.Object({status:t.Type.Literal(`listed`),current:t.Type.Optional(n),profiles:t.Type.Array(t.Type.Object({name:n,description:t.Type.String(),displayName:t.Type.Optional(t.Type.String())},{additionalProperties:!1}))},{additionalProperties:!1}),t.Type.Object({status:t.Type.Literal(`unchanged`),profile:n},{additionalProperties:!1}),t.Type.Object({status:t.Type.Literal(`queued`),profile:n,stopBatch:t.Type.Literal(`session-reload`)},{additionalProperties:!1})]);exports.PROFILE_VOICE_INPUT_SCHEMA=r,exports.PROFILE_VOICE_RESULT_SCHEMA=i,exports.PROFILE_VOICE_TOOL_NAME=`profile`;
2
+ //# sourceMappingURL=profileVoiceTools.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profileVoiceTools.cjs","names":["Type","VOICE_TOOL_MAX_IDENTIFIER_LENGTH"],"sources":["../../src/schemas/profileVoiceTools.ts"],"sourcesContent":["import { VOICE_TOOL_MAX_IDENTIFIER_LENGTH } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { type Static, Type } from 'typebox';\n\n/**\n * The profile axis as one voice capability.\n *\n * Modelled on the major-mode capability rather than the domains pair: a profile\n * is a single name, so listing and switching fit one discriminated input and the\n * selected value rides the shared reload handoff instead of a second store.\n */\nexport const PROFILE_VOICE_TOOL_NAME = 'profile';\n\nconst PROFILE_NAME_SCHEMA = Type.String({\n minLength: 1,\n maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH,\n});\n\nexport const PROFILE_VOICE_INPUT_SCHEMA = Type.Union([\n Type.Object({ action: Type.Literal('list') }, { additionalProperties: false }),\n Type.Object({ action: Type.Literal('switch'), profile: PROFILE_NAME_SCHEMA }, { additionalProperties: false }),\n]);\n\nexport const PROFILE_VOICE_RESULT_SCHEMA = Type.Union([\n Type.Object(\n {\n status: Type.Literal('listed'),\n current: Type.Optional(PROFILE_NAME_SCHEMA),\n profiles: Type.Array(\n Type.Object(\n {\n name: PROFILE_NAME_SCHEMA,\n description: Type.String(),\n displayName: Type.Optional(Type.String()),\n },\n { additionalProperties: false },\n ),\n ),\n },\n { additionalProperties: false },\n ),\n Type.Object(\n {\n status: Type.Literal('unchanged'),\n profile: PROFILE_NAME_SCHEMA,\n },\n { additionalProperties: false },\n ),\n Type.Object(\n {\n status: Type.Literal('queued'),\n profile: PROFILE_NAME_SCHEMA,\n stopBatch: Type.Literal('session-reload'),\n },\n { additionalProperties: false },\n ),\n]);\n\nexport type ProfileVoiceInput = Static<typeof PROFILE_VOICE_INPUT_SCHEMA>;\nexport type ProfileVoiceResult = Static<typeof PROFILE_VOICE_RESULT_SCHEMA>;\n"],"mappings":"wFAUA,MAEM,EAAsBA,EAAAA,KAAK,OAAO,CACtC,UAAW,EACX,UAAWC,EAAAA,gCACb,CAAC,EAEY,EAA6BD,EAAAA,KAAK,MAAM,CACnDA,EAAAA,KAAK,OAAO,CAAE,OAAQA,EAAAA,KAAK,QAAQ,MAAM,CAAE,EAAG,CAAE,qBAAsB,EAAM,CAAC,EAC7EA,EAAAA,KAAK,OAAO,CAAE,OAAQA,EAAAA,KAAK,QAAQ,QAAQ,EAAG,QAAS,CAAoB,EAAG,CAAE,qBAAsB,EAAM,CAAC,CAC/G,CAAC,EAEY,EAA8BA,EAAAA,KAAK,MAAM,CACpDA,EAAAA,KAAK,OACH,CACE,OAAQA,EAAAA,KAAK,QAAQ,QAAQ,EAC7B,QAASA,EAAAA,KAAK,SAAS,CAAmB,EAC1C,SAAUA,EAAAA,KAAK,MACbA,EAAAA,KAAK,OACH,CACE,KAAM,EACN,YAAaA,EAAAA,KAAK,OAAO,EACzB,YAAaA,EAAAA,KAAK,SAASA,EAAAA,KAAK,OAAO,CAAC,CAC1C,EACA,CAAE,qBAAsB,EAAM,CAChC,CACF,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EACAA,EAAAA,KAAK,OACH,CACE,OAAQA,EAAAA,KAAK,QAAQ,WAAW,EAChC,QAAS,CACX,EACA,CAAE,qBAAsB,EAAM,CAChC,EACAA,EAAAA,KAAK,OACH,CACE,OAAQA,EAAAA,KAAK,QAAQ,QAAQ,EAC7B,QAAS,EACT,UAAWA,EAAAA,KAAK,QAAQ,gBAAgB,CAC1C,EACA,CAAE,qBAAsB,EAAM,CAChC,CACF,CAAC"}
@@ -0,0 +1,2 @@
1
+ import{VOICE_TOOL_MAX_IDENTIFIER_LENGTH as e}from"@agimon-ai/doompi-extension-contracts/voice-tools";import{Type as t}from"typebox";const n=`profile`,r=t.String({minLength:1,maxLength:e}),i=t.Union([t.Object({action:t.Literal(`list`)},{additionalProperties:!1}),t.Object({action:t.Literal(`switch`),profile:r},{additionalProperties:!1})]),a=t.Union([t.Object({status:t.Literal(`listed`),current:t.Optional(r),profiles:t.Array(t.Object({name:r,description:t.String(),displayName:t.Optional(t.String())},{additionalProperties:!1}))},{additionalProperties:!1}),t.Object({status:t.Literal(`unchanged`),profile:r},{additionalProperties:!1}),t.Object({status:t.Literal(`queued`),profile:r,stopBatch:t.Literal(`session-reload`)},{additionalProperties:!1})]);export{i as PROFILE_VOICE_INPUT_SCHEMA,a as PROFILE_VOICE_RESULT_SCHEMA,n as PROFILE_VOICE_TOOL_NAME};
2
+ //# sourceMappingURL=profileVoiceTools.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profileVoiceTools.mjs","names":[],"sources":["../../src/schemas/profileVoiceTools.ts"],"sourcesContent":["import { VOICE_TOOL_MAX_IDENTIFIER_LENGTH } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { type Static, Type } from 'typebox';\n\n/**\n * The profile axis as one voice capability.\n *\n * Modelled on the major-mode capability rather than the domains pair: a profile\n * is a single name, so listing and switching fit one discriminated input and the\n * selected value rides the shared reload handoff instead of a second store.\n */\nexport const PROFILE_VOICE_TOOL_NAME = 'profile';\n\nconst PROFILE_NAME_SCHEMA = Type.String({\n minLength: 1,\n maxLength: VOICE_TOOL_MAX_IDENTIFIER_LENGTH,\n});\n\nexport const PROFILE_VOICE_INPUT_SCHEMA = Type.Union([\n Type.Object({ action: Type.Literal('list') }, { additionalProperties: false }),\n Type.Object({ action: Type.Literal('switch'), profile: PROFILE_NAME_SCHEMA }, { additionalProperties: false }),\n]);\n\nexport const PROFILE_VOICE_RESULT_SCHEMA = Type.Union([\n Type.Object(\n {\n status: Type.Literal('listed'),\n current: Type.Optional(PROFILE_NAME_SCHEMA),\n profiles: Type.Array(\n Type.Object(\n {\n name: PROFILE_NAME_SCHEMA,\n description: Type.String(),\n displayName: Type.Optional(Type.String()),\n },\n { additionalProperties: false },\n ),\n ),\n },\n { additionalProperties: false },\n ),\n Type.Object(\n {\n status: Type.Literal('unchanged'),\n profile: PROFILE_NAME_SCHEMA,\n },\n { additionalProperties: false },\n ),\n Type.Object(\n {\n status: Type.Literal('queued'),\n profile: PROFILE_NAME_SCHEMA,\n stopBatch: Type.Literal('session-reload'),\n },\n { additionalProperties: false },\n ),\n]);\n\nexport type ProfileVoiceInput = Static<typeof PROFILE_VOICE_INPUT_SCHEMA>;\nexport type ProfileVoiceResult = Static<typeof PROFILE_VOICE_RESULT_SCHEMA>;\n"],"mappings":"oIAUA,MAAa,EAA0B,UAEjC,EAAsB,EAAK,OAAO,CACtC,UAAW,EACX,UAAW,CACb,CAAC,EAEY,EAA6B,EAAK,MAAM,CACnD,EAAK,OAAO,CAAE,OAAQ,EAAK,QAAQ,MAAM,CAAE,EAAG,CAAE,qBAAsB,EAAM,CAAC,EAC7E,EAAK,OAAO,CAAE,OAAQ,EAAK,QAAQ,QAAQ,EAAG,QAAS,CAAoB,EAAG,CAAE,qBAAsB,EAAM,CAAC,CAC/G,CAAC,EAEY,EAA8B,EAAK,MAAM,CACpD,EAAK,OACH,CACE,OAAQ,EAAK,QAAQ,QAAQ,EAC7B,QAAS,EAAK,SAAS,CAAmB,EAC1C,SAAU,EAAK,MACb,EAAK,OACH,CACE,KAAM,EACN,YAAa,EAAK,OAAO,EACzB,YAAa,EAAK,SAAS,EAAK,OAAO,CAAC,CAC1C,EACA,CAAE,qBAAsB,EAAM,CAChC,CACF,CACF,EACA,CAAE,qBAAsB,EAAM,CAChC,EACA,EAAK,OACH,CACE,OAAQ,EAAK,QAAQ,WAAW,EAChC,QAAS,CACX,EACA,CAAE,qBAAsB,EAAM,CAChC,EACA,EAAK,OACH,CACE,OAAQ,EAAK,QAAQ,QAAQ,EAC7B,QAAS,EACT,UAAW,EAAK,QAAQ,gBAAgB,CAC1C,EACA,CAAE,qBAAsB,EAAM,CAChC,CACF,CAAC"}
@@ -1,3 +1,3 @@
1
1
  const e=`(none)`;function t(e,t){return e||(t?``:void 0)}function n(e){return e.map(e=>({value:e.name,label:e.name,description:r(e)}))}function r(e){let t=Object.keys(e.env).sort();return t.length>0?`${e.persona} (env: ${t.join(`, `)})`:e.persona}function i(t){let n=Object.keys(t.env).sort();return[`Profile ${t.name}`,`Persona: ${t.persona}`,`Env: ${n.join(`, `)||e}`,`Already loaded.`].join(`
2
- `)}function a(t){return`Profile (current: ${t??e})`}exports.PROFILE_STATUS_KEY=`doom-profile`,exports.profileDescription=r,exports.profileItems=n,exports.profileStatus=t,exports.profileSummary=i,exports.profileTitle=a;
2
+ `)}function a(t){return`Profile (current: ${t??e})`}function o(e){let t=e.trim();if(!t.startsWith(`--voice-switch-token=`))return;let n=t.split(/\s+/u);if(n.length!==1)throw Error(`The voice profile switch token must be the only command argument.`);let r=n[0]?.slice(21).trim();if(!r)throw Error(`The voice profile switch token is missing.`);return r}exports.PROFILE_COMMAND=`profile`,exports.PROFILE_SOURCE=`@agimon-ai/doompi-profile`,exports.PROFILE_STATUS_KEY=`doom-profile`,exports.PROFILE_SWITCH_HANDOFF_KIND=`profile-switch`,exports.VOICE_SWITCH_TOKEN_PREFIX=`--voice-switch-token=`,exports.profileDescription=r,exports.profileItems=n,exports.profileStatus=t,exports.profileSummary=i,exports.profileTitle=a,exports.voiceSwitchToken=o;
3
3
  //# sourceMappingURL=profileText.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.cjs","names":[],"sources":["../../src/services/profileText.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst NONE = '(none)';\n\n/** Footer status key the cockpit's profile axis reads. */\nexport const PROFILE_STATUS_KEY = 'doom-profile';\n\n/**\n * The published axis content: the active profile's name, empty while\n * profiles exist with none active, and undefined to withhold the status so\n * the cockpit keeps the axis off the bar entirely.\n */\nexport function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined {\n if (current) return current;\n return hasProfiles ? '' : undefined;\n}\n\n/** Rows for the single-select profile picker. */\nexport function profileItems(profiles: AgentProfile[]): SelectItem[] {\n return profiles.map((profile) => ({\n value: profile.name,\n label: profile.name,\n description: profileDescription(profile),\n }));\n}\n\nexport function profileDescription(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return environmentKeys.length > 0 ? `${profile.persona} (env: ${environmentKeys.join(', ')})` : profile.persona;\n}\n\n/** Shown when the picked profile is already active. */\nexport function profileSummary(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return [\n `Profile ${profile.name}`,\n `Persona: ${profile.persona}`,\n `Env: ${environmentKeys.join(', ') || NONE}`,\n 'Already loaded.',\n ].join('\\n');\n}\n\nexport function profileTitle(current?: string): string {\n return `Profile (current: ${current ?? NONE})`;\n}\n"],"mappings":"AAGA,MAAM,EAAO,SAUb,SAAgB,EAAc,EAA6B,EAA0C,CAEnG,OADI,IACG,EAAc,GAAK,IAAA,GAC5B,CAGA,SAAgB,EAAa,EAAwC,CACnE,OAAO,EAAS,IAAK,IAAa,CAChC,MAAO,EAAQ,KACf,MAAO,EAAQ,KACf,YAAa,EAAmB,CAAO,CACzC,EAAE,CACJ,CAEA,SAAgB,EAAmB,EAA+B,CAChE,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,OAAO,EAAgB,OAAS,EAAI,GAAG,EAAQ,QAAQ,SAAS,EAAgB,KAAK,IAAI,EAAE,GAAK,EAAQ,OAC1G,CAGA,SAAgB,EAAe,EAA+B,CAC5D,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,MAAO,CACL,WAAW,EAAQ,OACnB,YAAY,EAAQ,UACpB,QAAQ,EAAgB,KAAK,IAAI,GAAK,IACtC,iBACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAa,EAA0B,CACrD,MAAO,qBAAqB,GAAW,EAAK,EAC9C"}
1
+ {"version":3,"file":"profileText.cjs","names":[],"sources":["../../src/services/profileText.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst NONE = '(none)';\n\n/** Footer status key the cockpit's profile axis reads. */\nexport const PROFILE_STATUS_KEY = 'doom-profile';\n\n/** Identity this package registers voice capabilities under. */\nexport const PROFILE_SOURCE = '@agimon-ai/doompi-profile';\n\n/** The command a queued voice switch invokes, and the reload handoff kind it parks. */\nexport const PROFILE_COMMAND = 'profile';\nexport const PROFILE_SWITCH_HANDOFF_KIND = 'profile-switch';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/**\n * The published axis content: the active profile's name, empty while\n * profiles exist with none active, and undefined to withhold the status so\n * the cockpit keeps the axis off the bar entirely.\n */\nexport function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined {\n if (current) return current;\n return hasProfiles ? '' : undefined;\n}\n\n/** Rows for the single-select profile picker. */\nexport function profileItems(profiles: AgentProfile[]): SelectItem[] {\n return profiles.map((profile) => ({\n value: profile.name,\n label: profile.name,\n description: profileDescription(profile),\n }));\n}\n\nexport function profileDescription(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return environmentKeys.length > 0 ? `${profile.persona} (env: ${environmentKeys.join(', ')})` : profile.persona;\n}\n\n/** Shown when the picked profile is already active. */\nexport function profileSummary(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return [\n `Profile ${profile.name}`,\n `Persona: ${profile.persona}`,\n `Env: ${environmentKeys.join(', ') || NONE}`,\n 'Already loaded.',\n ].join('\\n');\n}\n\nexport function profileTitle(current?: string): string {\n return `Profile (current: ${current ?? NONE})`;\n}\n\n/**\n * The opaque token a voice switch hands to the follow-up command invocation.\n *\n * The profile name is deliberately absent from the command text, so an edited\n * transcript cannot be turned into a switch to a different persona.\n */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice profile switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice profile switch token is missing.');\n return token;\n}\n"],"mappings":"AAGA,MAAM,EAAO,SAkBb,SAAgB,EAAc,EAA6B,EAA0C,CAEnG,OADI,IACG,EAAc,GAAK,IAAA,GAC5B,CAGA,SAAgB,EAAa,EAAwC,CACnE,OAAO,EAAS,IAAK,IAAa,CAChC,MAAO,EAAQ,KACf,MAAO,EAAQ,KACf,YAAa,EAAmB,CAAO,CACzC,EAAE,CACJ,CAEA,SAAgB,EAAmB,EAA+B,CAChE,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,OAAO,EAAgB,OAAS,EAAI,GAAG,EAAQ,QAAQ,SAAS,EAAgB,KAAK,IAAI,EAAE,GAAK,EAAQ,OAC1G,CAGA,SAAgB,EAAe,EAA+B,CAC5D,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,MAAO,CACL,WAAW,EAAQ,OACnB,YAAY,EAAQ,UACpB,QAAQ,EAAgB,KAAK,IAAI,GAAK,IACtC,iBACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAa,EAA0B,CACrD,MAAO,qBAAqB,GAAW,EAAK,EAC9C,CAQA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OAEpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,mEAAmE,EAC3G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,4CAA4C,EACxE,OAAO,CACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.d.cts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;qBAMa;;;;;;wBAOG,cAAc,6BAA6B;;wBAM3C,aAAa,UAAU,iBAAiB;wBAQxC,mBAAmB,SAAS;;wBAM5B,eAAe,SAAS;wBAUxB,aAAa"}
1
+ {"version":3,"file":"profileText.d.cts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;qBAMa;;;;;;wBAeG,cAAc,6BAA6B;;wBAM3C,aAAa,UAAU,iBAAiB;wBAQxC,mBAAmB,SAAS;;wBAM5B,eAAe,SAAS;wBAUxB,aAAa"}
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.d.mts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;qBAMa;;;;;;wBAOG,cAAc,6BAA6B;;wBAM3C,aAAa,UAAU,iBAAiB;wBAQxC,mBAAmB,SAAS;;wBAM5B,eAAe,SAAS;wBAUxB,aAAa"}
1
+ {"version":3,"file":"profileText.d.mts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;qBAMa;;;;;;wBAeG,cAAc,6BAA6B;;wBAM3C,aAAa,UAAU,iBAAiB;wBAQxC,mBAAmB,SAAS;;wBAM5B,eAAe,SAAS;wBAUxB,aAAa"}
@@ -1,3 +1,3 @@
1
- const e=`(none)`,t=`doom-profile`;function n(e,t){return e||(t?``:void 0)}function r(e){return e.map(e=>({value:e.name,label:e.name,description:i(e)}))}function i(e){let t=Object.keys(e.env).sort();return t.length>0?`${e.persona} (env: ${t.join(`, `)})`:e.persona}function a(t){let n=Object.keys(t.env).sort();return[`Profile ${t.name}`,`Persona: ${t.persona}`,`Env: ${n.join(`, `)||e}`,`Already loaded.`].join(`
2
- `)}function o(t){return`Profile (current: ${t??e})`}export{t as PROFILE_STATUS_KEY,i as profileDescription,r as profileItems,n as profileStatus,a as profileSummary,o as profileTitle};
1
+ const e=`(none)`,t=`doom-profile`,n=`@agimon-ai/doompi-profile`,r=`profile`,i=`profile-switch`,a=`--voice-switch-token=`;function o(e,t){return e||(t?``:void 0)}function s(e){return e.map(e=>({value:e.name,label:e.name,description:c(e)}))}function c(e){let t=Object.keys(e.env).sort();return t.length>0?`${e.persona} (env: ${t.join(`, `)})`:e.persona}function l(t){let n=Object.keys(t.env).sort();return[`Profile ${t.name}`,`Persona: ${t.persona}`,`Env: ${n.join(`, `)||e}`,`Already loaded.`].join(`
2
+ `)}function u(t){return`Profile (current: ${t??e})`}function d(e){let t=e.trim();if(!t.startsWith(`--voice-switch-token=`))return;let n=t.split(/\s+/u);if(n.length!==1)throw Error(`The voice profile switch token must be the only command argument.`);let r=n[0]?.slice(21).trim();if(!r)throw Error(`The voice profile switch token is missing.`);return r}export{r as PROFILE_COMMAND,n as PROFILE_SOURCE,t as PROFILE_STATUS_KEY,i as PROFILE_SWITCH_HANDOFF_KIND,a as VOICE_SWITCH_TOKEN_PREFIX,c as profileDescription,s as profileItems,o as profileStatus,l as profileSummary,u as profileTitle,d as voiceSwitchToken};
3
3
  //# sourceMappingURL=profileText.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.mjs","names":[],"sources":["../../src/services/profileText.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst NONE = '(none)';\n\n/** Footer status key the cockpit's profile axis reads. */\nexport const PROFILE_STATUS_KEY = 'doom-profile';\n\n/**\n * The published axis content: the active profile's name, empty while\n * profiles exist with none active, and undefined to withhold the status so\n * the cockpit keeps the axis off the bar entirely.\n */\nexport function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined {\n if (current) return current;\n return hasProfiles ? '' : undefined;\n}\n\n/** Rows for the single-select profile picker. */\nexport function profileItems(profiles: AgentProfile[]): SelectItem[] {\n return profiles.map((profile) => ({\n value: profile.name,\n label: profile.name,\n description: profileDescription(profile),\n }));\n}\n\nexport function profileDescription(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return environmentKeys.length > 0 ? `${profile.persona} (env: ${environmentKeys.join(', ')})` : profile.persona;\n}\n\n/** Shown when the picked profile is already active. */\nexport function profileSummary(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return [\n `Profile ${profile.name}`,\n `Persona: ${profile.persona}`,\n `Env: ${environmentKeys.join(', ') || NONE}`,\n 'Already loaded.',\n ].join('\\n');\n}\n\nexport function profileTitle(current?: string): string {\n return `Profile (current: ${current ?? NONE})`;\n}\n"],"mappings":"AAGA,MAAM,EAAO,SAGA,EAAqB,eAOlC,SAAgB,EAAc,EAA6B,EAA0C,CAEnG,OADI,IACG,EAAc,GAAK,IAAA,GAC5B,CAGA,SAAgB,EAAa,EAAwC,CACnE,OAAO,EAAS,IAAK,IAAa,CAChC,MAAO,EAAQ,KACf,MAAO,EAAQ,KACf,YAAa,EAAmB,CAAO,CACzC,EAAE,CACJ,CAEA,SAAgB,EAAmB,EAA+B,CAChE,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,OAAO,EAAgB,OAAS,EAAI,GAAG,EAAQ,QAAQ,SAAS,EAAgB,KAAK,IAAI,EAAE,GAAK,EAAQ,OAC1G,CAGA,SAAgB,EAAe,EAA+B,CAC5D,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,MAAO,CACL,WAAW,EAAQ,OACnB,YAAY,EAAQ,UACpB,QAAQ,EAAgB,KAAK,IAAI,GAAK,IACtC,iBACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAa,EAA0B,CACrD,MAAO,qBAAqB,GAAW,EAAK,EAC9C"}
1
+ {"version":3,"file":"profileText.mjs","names":[],"sources":["../../src/services/profileText.ts"],"sourcesContent":["import type { AgentProfile } from '@agimon-ai/doompi-config/profiles';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst NONE = '(none)';\n\n/** Footer status key the cockpit's profile axis reads. */\nexport const PROFILE_STATUS_KEY = 'doom-profile';\n\n/** Identity this package registers voice capabilities under. */\nexport const PROFILE_SOURCE = '@agimon-ai/doompi-profile';\n\n/** The command a queued voice switch invokes, and the reload handoff kind it parks. */\nexport const PROFILE_COMMAND = 'profile';\nexport const PROFILE_SWITCH_HANDOFF_KIND = 'profile-switch';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/**\n * The published axis content: the active profile's name, empty while\n * profiles exist with none active, and undefined to withhold the status so\n * the cockpit keeps the axis off the bar entirely.\n */\nexport function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined {\n if (current) return current;\n return hasProfiles ? '' : undefined;\n}\n\n/** Rows for the single-select profile picker. */\nexport function profileItems(profiles: AgentProfile[]): SelectItem[] {\n return profiles.map((profile) => ({\n value: profile.name,\n label: profile.name,\n description: profileDescription(profile),\n }));\n}\n\nexport function profileDescription(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return environmentKeys.length > 0 ? `${profile.persona} (env: ${environmentKeys.join(', ')})` : profile.persona;\n}\n\n/** Shown when the picked profile is already active. */\nexport function profileSummary(profile: AgentProfile): string {\n const environmentKeys = Object.keys(profile.env).sort();\n return [\n `Profile ${profile.name}`,\n `Persona: ${profile.persona}`,\n `Env: ${environmentKeys.join(', ') || NONE}`,\n 'Already loaded.',\n ].join('\\n');\n}\n\nexport function profileTitle(current?: string): string {\n return `Profile (current: ${current ?? NONE})`;\n}\n\n/**\n * The opaque token a voice switch hands to the follow-up command invocation.\n *\n * The profile name is deliberately absent from the command text, so an edited\n * transcript cannot be turned into a switch to a different persona.\n */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice profile switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice profile switch token is missing.');\n return token;\n}\n"],"mappings":"AAGA,MAAM,EAAO,SAGA,EAAqB,eAGrB,EAAiB,4BAGjB,EAAkB,UAClB,EAA8B,iBAC9B,EAA4B,wBAOzC,SAAgB,EAAc,EAA6B,EAA0C,CAEnG,OADI,IACG,EAAc,GAAK,IAAA,GAC5B,CAGA,SAAgB,EAAa,EAAwC,CACnE,OAAO,EAAS,IAAK,IAAa,CAChC,MAAO,EAAQ,KACf,MAAO,EAAQ,KACf,YAAa,EAAmB,CAAO,CACzC,EAAE,CACJ,CAEA,SAAgB,EAAmB,EAA+B,CAChE,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,OAAO,EAAgB,OAAS,EAAI,GAAG,EAAQ,QAAQ,SAAS,EAAgB,KAAK,IAAI,EAAE,GAAK,EAAQ,OAC1G,CAGA,SAAgB,EAAe,EAA+B,CAC5D,IAAM,EAAkB,OAAO,KAAK,EAAQ,GAAG,CAAC,CAAC,KAAK,EACtD,MAAO,CACL,WAAW,EAAQ,OACnB,YAAY,EAAQ,UACpB,QAAQ,EAAgB,KAAK,IAAI,GAAK,IACtC,iBACF,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAa,EAA0B,CACrD,MAAO,qBAAqB,GAAW,EAAK,EAC9C,CAQA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OAEpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,mEAAmE,EAC3G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,4CAA4C,EACxE,OAAO,CACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-profile",
3
- "version": "0.0.1-alpha.43",
3
+ "version": "0.0.1-alpha.45",
4
4
  "description": "Persona and environment profile switching for DoomPi sessions.",
5
5
  "keywords": [
6
6
  "agent-persona",
@@ -62,11 +62,12 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@deepseek-ai/cordis": "4.0.2",
65
- "@agimon-ai/doompi-config": "0.0.1-alpha.64",
66
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.64",
67
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.63",
68
- "@agimon-ai/doompi-ui": "0.0.1-alpha.65",
69
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.27"
65
+ "typebox": "1.3.25",
66
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.66",
67
+ "@agimon-ai/doompi-config": "0.0.1-alpha.66",
68
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.65",
69
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.67",
70
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.29"
70
71
  },
71
72
  "devDependencies": {
72
73
  "@deepseek-ai/cordis": "4.0.2",
@@ -77,7 +78,7 @@
77
78
  "tsdown": "0.23.0",
78
79
  "typescript": "7.0.2",
79
80
  "vitest": "5.0.0",
80
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.61"
81
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.63"
81
82
  },
82
83
  "peerDependencies": {
83
84
  "@earendil-works/pi-coding-agent": "0.85.1",
@@ -18,11 +18,19 @@ profiles:
18
18
  env:
19
19
  BRAND: acme
20
20
  TONE: concise
21
+ defaultProfile: writer
21
22
  ```
22
23
 
23
24
  The document may contain only the `profiles` mapping. In catalog form, `profiles` may contain only
24
- `roots` and `entries`. DoomPi still accepts the earlier flat form, where profile names appear
25
- directly below `profiles`, but new configuration should use catalog form.
25
+ `roots`, `entries` and `defaultProfile`. DoomPi still accepts the earlier flat form, where profile
26
+ names appear directly below `profiles`, but new configuration should use catalog form.
27
+
28
+ `defaultProfile` names the persona a run starts on when nothing else names one, the same way
29
+ `modes.yaml` declares `defaultMajorMode` and `domains.yaml` declares `defaultDomains`. A repository
30
+ declaration replaces a personal one. `doompi --profile <name>` overrides it for one run, and an
31
+ inherited `DOOMPI_PROFILE` wins so a nested run stays on the persona its parent chose. A
32
+ `defaultProfile` naming a profile that does not exist fails the launch rather than starting with no
33
+ persona.
26
34
 
27
35
  Every relative root or persona path resolves against the file that declares it. Repository paths
28
36
  resolve from the repository root. Personal paths resolve from `~/.pi/.doom`.
@@ -61,6 +69,46 @@ Persona content is assembled from non-empty files in this order:
61
69
  2. `SOUL.md`
62
70
  3. `AGENTS.md`
63
71
 
72
+ ## Persona identity front-matter
73
+
74
+ A persona may open `profile.md` with a YAML front-matter block declaring who it is and how it
75
+ sounds. Only `profile.md` is read this way; `SOUL.md` and `AGENTS.md` are always prose.
76
+
77
+ ```markdown
78
+ ---
79
+ name: Rhea
80
+ icon: avatar.png
81
+ voice:
82
+ voice: Karen
83
+ rate: 175
84
+ ---
85
+
86
+ You are Rhea, a blunt systems architect.
87
+ ```
88
+
89
+ All three fields are optional. `name` replaces the profile's directory name in the cockpit,
90
+ `icon` is an image beside `profile.md`, and `voice` shadows `voice.autoCapture.tts` from
91
+ `config.yaml` per field: a persona setting only `rate` keeps the configured voice. The TTS `engine`
92
+ is never overridable, because it selects the synthesiser rather than the persona.
93
+
94
+ Because identity lives here rather than in `profiles.yaml`, it applies to profiles found by root
95
+ discovery as well as to explicit entries.
96
+
97
+ A block is only treated as identity when it is fenced on the very first line, closed, parses as a
98
+ YAML mapping, and declares at least one of `name`, `icon`, or `voice`. Anything else is left in the
99
+ persona text byte for byte, so a persona that legitimately opens with a horizontal rule is
100
+ unaffected. A recognised block is removed from the prompt: it describes the persona rather than
101
+ instructing it. A `profile.md` holding nothing but front-matter therefore has no persona text, and
102
+ the profile fails to load unless `SOUL.md` or `AGENTS.md` supplies it.
103
+
104
+ The icon is read once when the profile loads and travels to the cockpit as a `data:` URL, so no
105
+ file is served at request time. It must be a PNG, JPEG, GIF, or WebP, checked by content rather
106
+ than by file extension. SVG is refused because an SVG rendered in the cockpit executes script. The
107
+ icon must stay inside the persona directory under the same real-path rule as persona files, so a
108
+ symlink cannot smuggle a file out of it. An icon that is missing, unreadable, of another type, or
109
+ larger than 256 KiB once encoded is dropped: the persona keeps its name and the cockpit falls back
110
+ to the default avatar rather than failing the profile.
111
+
64
112
  ## Precedence
65
113
 
66
114
  DoomPi resolves names in four passes, from lowest to highest precedence:
@@ -86,6 +134,13 @@ transition coordinator. A profile change uses Pi reload so extensions re-read th
86
134
  DoomPi assembles the persona into a private run-directory file, and the persona Pi entry appends
87
135
  that content to the system prompt after reload.
88
136
 
137
+ Autonomous voice can switch profiles too. The `profile` voice capability lists the configured
138
+ personas and queues a switch, which arrives as `/profile --voice-switch-token=…`. The chosen name
139
+ travels in a TTL-bounded handoff rather than in the command text, so an edited transcript cannot be
140
+ turned into a switch to a different persona. A spoken switch therefore takes the same reload path
141
+ as the command, which is also what lets the new persona's voice take effect: the spoken voice is
142
+ read when autonomous voice activates.
143
+
89
144
  ## Verification
90
145
 
91
146
  ```sh