@agimon-ai/doompi-profile 0.0.1-alpha.2 → 0.0.1-alpha.20

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 (43) hide show
  1. package/README.md +30 -10
  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 +1 -4
  5. package/dist/adapters/pi/extension.d.cts.map +1 -1
  6. package/dist/adapters/pi/extension.d.mts +1 -4
  7. package/dist/adapters/pi/extension.d.mts.map +1 -1
  8. package/dist/adapters/pi/extension.mjs +1 -1
  9. package/dist/adapters/pi/extension.mjs.map +1 -1
  10. package/dist/adapters/pi/persona.cjs +1 -1
  11. package/dist/adapters/pi/persona.cjs.map +1 -1
  12. package/dist/adapters/pi/persona.d.cts +2 -12
  13. package/dist/adapters/pi/persona.d.cts.map +1 -1
  14. package/dist/adapters/pi/persona.d.mts +2 -12
  15. package/dist/adapters/pi/persona.d.mts.map +1 -1
  16. package/dist/adapters/pi/persona.mjs +1 -1
  17. package/dist/adapters/pi/persona.mjs.map +1 -1
  18. package/dist/commands/profileCommand.cjs +1 -1
  19. package/dist/commands/profileCommand.cjs.map +1 -1
  20. package/dist/commands/profileCommand.mjs +1 -1
  21. package/dist/commands/profileCommand.mjs.map +1 -1
  22. package/dist/index.cjs +1 -1
  23. package/dist/index.d.cts +2 -2
  24. package/dist/index.d.mts +2 -2
  25. package/dist/index.mjs +1 -1
  26. package/dist/services/profileText.cjs +2 -2
  27. package/dist/services/profileText.cjs.map +1 -1
  28. package/dist/services/profileText.d.cts +9 -1
  29. package/dist/services/profileText.d.cts.map +1 -1
  30. package/dist/services/profileText.d.mts +9 -1
  31. package/dist/services/profileText.d.mts.map +1 -1
  32. package/dist/services/profileText.mjs +2 -2
  33. package/dist/services/profileText.mjs.map +1 -1
  34. package/dist/types/telemetry.d.cts +3 -3
  35. package/dist/types/telemetry.d.cts.map +1 -1
  36. package/dist/types/telemetry.d.mts +3 -3
  37. package/dist/types/telemetry.d.mts.map +1 -1
  38. package/llms.txt +8 -3
  39. package/package.json +31 -15
  40. package/src/prompts/doompi-author-profile/SKILL.md +20 -0
  41. package/src/prompts/doompi-author-profile/references/profiles-contract.md +100 -0
  42. package/web/index.ts +14 -0
  43. package/web/tsconfig.json +22 -0
package/README.md CHANGED
@@ -3,8 +3,8 @@
3
3
  Persona and environment profile switching for [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi)
4
4
  sessions.
5
5
 
6
- A **profile** is one of the four axes of a DoomPi selection, alongside domains, the major mode and
7
- the minor modes. It names a persona directory and a set of environment defaults:
6
+ A **profile** is one of the four axes of a DoomPi selection, alongside domains, the major mode,
7
+ and the minor modes. It names a persona directory and a set of environment defaults:
8
8
 
9
9
  ```yaml
10
10
  # .doom/profiles.yaml
@@ -17,21 +17,21 @@ profiles:
17
17
  REVIEW_STRICTNESS: high
18
18
  ```
19
19
 
20
- The persona is `profile.md`, `SOUL.md` and `AGENTS.md` concatenated from the named directory. The
20
+ The persona is `profile.md`, `SOUL.md`, and `AGENTS.md` concatenated from the named directory. The
21
21
  harness assembles them into one file in the run directory and records its path; the persona entry
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
25
  ## What it registers
26
26
 
27
- | Entry | Surface |
28
- | ---------------------- | --------------------------------------------------------------------------------------- |
29
- | `./extensions/pi` | the `/profile` command — lists profiles, switches persona and environment, then reloads |
30
- | `./extensions/persona` | a `before_agent_start` hook that appends the active persona to the system prompt |
27
+ | Entry | Surface |
28
+ | ---------------------- | -------------------------------------------------------------------------------------------- |
29
+ | `./extensions/pi` | the `/profile` command, which lists profiles, switches persona and environment, then reloads |
30
+ | `./extensions/persona` | a `before_agent_start` hook that appends the active persona to the system prompt |
31
31
 
32
- Two entries rather than one because a detached child session needs the persona but has no
33
- transition coordinator to run a switch through. DoomPi activates `./extensions/pi` in the parent
34
- and `./extensions/persona` in both.
32
+ DoomPi uses two entries because a detached child session needs the persona but has no transition
33
+ coordinator to run a switch through. DoomPi activates `./extensions/pi` in the parent and
34
+ `./extensions/persona` in both.
35
35
 
36
36
  ## Switching
37
37
 
@@ -40,6 +40,26 @@ A profile change is always a `pi-reload`: the extension set does not change, but
40
40
  have to be dropped so every extension re-reads the environment. Active minor modes are captured
41
41
  before the reload and restored after it.
42
42
 
43
+ ## Configuration
44
+
45
+ Personal profiles load from `~/.pi/.doom/profiles.yaml`, then repository profiles load from
46
+ `.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
48
+ from personal discovery to repository discovery, then personal explicit entries to repository
49
+ explicit entries. A later profile replaces an earlier profile with the same name.
50
+
51
+ Persona paths must stay inside the declaring config's `agents/` directory or a profile root from
52
+ that same file. Directory and persona-file symlinks cannot escape those boundaries. See the
53
+ shipped [profiles contract](./src/prompts/doompi-author-profile/references/profiles-contract.md) for
54
+ the full configuration model and verification steps.
55
+
56
+ ## Help
57
+
58
+ The package registers `doompi-author-profile` with the session's `doom/help` Cordis service. Its
59
+ package guidance becomes visible to the AI only while the parent Help minor mode is active. The
60
+ registration follows the Help provider lifecycle and is withdrawn when this package or the provider
61
+ is disposed.
62
+
43
63
  ## Installation
44
64
 
45
65
  DoomPi depends on this package and activates it as fixed host core, so a DoomPi install already has
@@ -1,2 +1,2 @@
1
- const e=require("../telemetry/logSinkTelemetry.cjs"),t=require("../../commands/profileCommand.cjs");let n=require("@deepseek-ai/cordis");async function r(r,i=e.createProfileTelemetry()){let a=new n.Context,o,s=()=>(o??=a.fiber.dispose(),o);try{a.effect(function*(){t.registerProfileCommand(r,i),yield()=>void 0},`@agimon-ai/doompi-profile`)}catch(e){throw await s(),e}r.on(`session_shutdown`,s)}exports.default=r,exports.profileExtension=r;
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;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["createProfileTelemetry","Context"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport type { ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n registerProfileCommand(pi, telemetry);\n // Pi owns command deregistration across a reload, so the fiber only has to\n // drop this package's own reference to the session.\n yield () => undefined;\n }, PACKAGE_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default profileExtension;\n"],"mappings":"yIAeA,eAAsB,EACpB,EACA,EAA8BA,EAAAA,uBAAuB,EACtC,CACf,IAAM,EAAS,IAAIC,EAAAA,QACf,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,EAAA,uBAAuB,EAAI,CAAS,EAGpC,SAAY,IAAA,EACd,EAAG,2BAAc,CACnB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
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"}
@@ -2,10 +2,7 @@ import { ProfileTelemetry } from "../../types/telemetry.cjs";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/extension.d.ts
4
4
  /**
5
- * The package's single standard Pi factory, and the owner of the cordis Context.
6
- *
7
- * Pi can reload an extension in-process, so every registration is yielded to the
8
- * fiber and one dispose on session shutdown unwinds them in reverse.
5
+ * The package's single standard Pi factory.
9
6
  */
10
7
  declare function profileExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): Promise<void>;
11
8
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;iBAesB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;iBAuCsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
@@ -2,10 +2,7 @@ import { ProfileTelemetry } from "../../types/telemetry.mjs";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  //#region src/adapters/pi/extension.d.ts
4
4
  /**
5
- * The package's single standard Pi factory, and the owner of the cordis Context.
6
- *
7
- * Pi can reload an extension in-process, so every registration is yielded to the
8
- * fiber and one dispose on session shutdown unwinds them in reverse.
5
+ * The package's single standard Pi factory.
9
6
  */
10
7
  declare function profileExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): Promise<void>;
11
8
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;;;;iBAesB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;;;iBAuCsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
@@ -1,2 +1,2 @@
1
- import{createProfileTelemetry as e}from"../telemetry/logSinkTelemetry.mjs";import{registerProfileCommand as t}from"../../commands/profileCommand.mjs";import{Context as n}from"@deepseek-ai/cordis";async function r(r,i=e()){let a=new n,o,s=()=>(o??=a.fiber.dispose(),o);try{a.effect(function*(){t(r,i),yield()=>void 0},`@agimon-ai/doompi-profile`)}catch(e){throw await s(),e}r.on(`session_shutdown`,s)}export{r as default,r 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{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};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { registerProfileCommand } from '../../commands/profileCommand.ts';\nimport type { ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\n\n/**\n * The package's single standard Pi factory, and the owner of the cordis Context.\n *\n * Pi can reload an extension in-process, so every registration is yielded to the\n * fiber and one dispose on session shutdown unwinds them in reverse.\n */\nexport async function profileExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n cordis.effect(function* () {\n registerProfileCommand(pi, telemetry);\n // Pi owns command deregistration across a reload, so the fiber only has to\n // drop this package's own reference to the session.\n yield () => undefined;\n }, PACKAGE_SOURCE);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on(SESSION_SHUTDOWN_EVENT, dispose);\n}\n\nexport default profileExtension;\n"],"mappings":"oMAeA,eAAsB,EACpB,EACA,EAA8B,EAAuB,EACtC,CACf,IAAM,EAAS,IAAI,EACf,EACE,OACJ,IAAa,EAAO,MAAM,QAAQ,EAC3B,GAGT,GAAI,CACF,EAAO,OAAO,WAAa,CACzB,EAAuB,EAAI,CAAS,EAGpC,SAAY,IAAA,EACd,EAAG,2BAAc,CACnB,OAAS,EAAO,CAEd,MADA,MAAM,EAAQ,EACR,CACR,CAEA,EAAG,GAAG,mBAAwB,CAAO,CACvC"}
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,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../telemetry/logSinkTelemetry.cjs"),n=require("../../types/telemetry.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("@agimon-ai/doompi-config/piContext");function a(e,a=t.createProfileTelemetry()){e.on(`before_agent_start`,async(e,t)=>{let{personaFile:o}=(0,i.requireDoomConfigContext)(t).harness;if(o)try{let t=(await r.default.promises.readFile(o,`utf8`)).trim();return t?{systemPrompt:`${e.systemPrompt}\n\n${t}`}:void 0}catch(e){let t=e instanceof Error?e.message:String(e);a.recordError(n.PROFILE_EVENT.personaReadFailed,e),process.stderr.write(`[pi-persona] could not read ${o}: ${t}\n`);return}})}exports.default=a,exports.personaExtension=a;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../telemetry/logSinkTelemetry.cjs"),n=require("../../types/telemetry.cjs");let r=require("node:fs");r=e.__toESM(r,1);let i=require("@agimon-ai/doompi-config"),a=require("@agimon-ai/doompi-extension-contracts/config"),o=require("@agimon-ai/doompi-extension-contracts/cordis-host");function s(e,{pi:t,telemetry:n}){let r;e.inject([a.DOOM_CONFIG_SERVICE],e=>(r=e,()=>{r===e&&(r=void 0)})),c(t,n,()=>{if(!r)throw Error(`Doom persona runtime is waiting for the session config service.`);return r})}function c(e,t,a){e.on(`before_agent_start`,async(e,o)=>{let{personaFile:s}=(0,i.requireDoomConfigContext)(a()).harness;if(s)try{let t=(await r.default.promises.readFile(s,`utf8`)).trim();return t?{systemPrompt:`${e.systemPrompt}\n\n${t}`}:void 0}catch(e){let r=e instanceof Error?e.message:String(e);t.recordError(n.PROFILE_EVENT.personaReadFailed,e),process.stderr.write(`[pi-persona] could not read ${s}: ${r}\n`);return}})}async function l(e,n=t.createProfileTelemetry()){let r=await(0,o.connectDoomCordisHost)(e,`@agimon-ai/doompi-profile/persona`),i=r.root.plugin(s,{pi:e,telemetry:n});try{await i}catch(e){throw await i.dispose(),await r.dispose(),e}let a;e.on(`session_shutdown`,()=>a??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}exports.default=l,exports.personaExtension=l,exports.registerPersonaHandlers=c;
2
2
  //# sourceMappingURL=persona.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"persona.cjs","names":["createProfileTelemetry","requireDoomConfigContext","fs","PROFILE_EVENT"],"sources":["../../../src/adapters/pi/persona.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\n/**\n * Appends the selected profile's persona to the system prompt.\n *\n * The harness writes the assembled persona to a file in the run directory and\n * records the path in the harness state, so agents/ stays the single source of\n * persona material and is never copied into config.\n *\n * This is a separate Pi entry from the /profile command because detached\n * children need the persona without the command: they have no transition\n * coordinator to run a switch through.\n */\nexport function personaExtension(pi: ExtensionAPI, telemetry: ProfileTelemetry = createProfileTelemetry()): void {\n pi.on('before_agent_start', async (event, ctx) => {\n const { personaFile } = requireDoomConfigContext(ctx).harness;\n if (!personaFile) return undefined;\n\n try {\n const content = (await fs.promises.readFile(personaFile, 'utf8')).trim();\n return content ? { systemPrompt: `${event.systemPrompt}\\n\\n${content}` } : undefined;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.personaReadFailed, error);\n process.stderr.write(`[pi-persona] could not read ${personaFile}: ${reason}\\n`);\n return undefined;\n }\n });\n}\n\nexport default personaExtension;\n"],"mappings":"4OAiBA,SAAgB,EAAiB,EAAkB,EAA8BA,EAAAA,uBAAuB,EAAS,CAC/G,EAAG,GAAG,qBAAsB,MAAO,EAAO,IAAQ,CAChD,GAAM,CAAE,gBAAA,EAAgBC,EAAAA,yBAAAA,CAAyB,CAAG,CAAC,CAAC,QACjD,KAEL,GAAI,CACF,IAAM,GAAW,MAAMC,EAAAA,QAAG,SAAS,SAAS,EAAa,MAAM,EAAA,CAAG,KAAK,EACvE,OAAO,EAAU,CAAE,aAAc,GAAG,EAAM,aAAa,MAAM,GAAU,EAAI,IAAA,EAC7E,OAAS,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACpE,EAAe,YAAYC,EAAAA,cAAc,kBAAmB,CAAK,EACjE,QAAQ,OAAO,MAAM,+BAA+B,EAAY,IAAI,EAAO,GAAG,EAC9E,MACF,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"persona.cjs","names":["DOOM_CONFIG_SERVICE","requireDoomConfigContext","fs","PROFILE_EVENT","createProfileTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/persona.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\n/**\n * Appends the selected profile's persona to the system prompt.\n *\n * The harness writes the assembled persona to a file in the run directory and\n * records the path in the harness state, so agents/ stays the single source of\n * persona material and is never copied into config.\n *\n * This is a separate Pi entry from the /profile command because detached\n * children need the persona without the command: they have no transition\n * coordinator to run a switch through.\n */\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile/persona';\n\ninterface PersonaPluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction personaPlugin(cordis: Context, { pi, telemetry }: PersonaPluginConfig): void {\n let activeContext: Context | undefined;\n cordis.inject([DOOM_CONFIG_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n registerPersonaHandlers(pi, telemetry, () => {\n if (!activeContext) throw new Error('Doom persona runtime is waiting for the session config service.');\n return activeContext;\n });\n}\n\nexport function registerPersonaHandlers(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n): void {\n pi.on('before_agent_start', async (event, _ctx) => {\n const { personaFile } = requireDoomConfigContext(cordisContext()).harness;\n if (!personaFile) return undefined;\n\n try {\n const content = (await fs.promises.readFile(personaFile, 'utf8')).trim();\n return content ? { systemPrompt: `${event.systemPrompt}\\n\\n${content}` } : undefined;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.personaReadFailed, error);\n process.stderr.write(`[pi-persona] could not read ${personaFile}: ${reason}\\n`);\n return undefined;\n }\n });\n}\n\nexport async function personaExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(personaPlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\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\nexport default personaExtension;\n"],"mappings":"2VA2BA,SAAS,EAAc,EAAiB,CAAE,KAAI,aAAwC,CACpF,IAAI,EACJ,EAAO,OAAO,CAACA,EAAAA,mBAAmB,EAAI,IACpC,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACD,EAAwB,EAAI,MAAiB,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,iEAAiE,EACrG,OAAO,CACT,CAAC,CACH,CAEA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,GAAG,qBAAsB,MAAO,EAAO,IAAS,CACjD,GAAM,CAAE,gBAAA,EAAgBC,EAAAA,yBAAAA,CAAyB,EAAc,CAAC,CAAC,CAAC,QAC7D,KAEL,GAAI,CACF,IAAM,GAAW,MAAMC,EAAAA,QAAG,SAAS,SAAS,EAAa,MAAM,EAAA,CAAG,KAAK,EACvE,OAAO,EAAU,CAAE,aAAc,GAAG,EAAM,aAAa,MAAM,GAAU,EAAI,IAAA,EAC7E,OAAS,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACpE,EAAe,YAAYC,EAAAA,cAAc,kBAAmB,CAAK,EACjE,QAAQ,OAAO,MAAM,+BAA+B,EAAY,IAAI,EAAO,GAAG,EAC9E,MACF,CACF,CAAC,CACH,CAEA,eAAsB,EACpB,EACA,EAA8BC,EAAAA,uBAAuB,EACtC,CACf,IAAM,EAAa,MAAA,EAAMC,EAAAA,sBAAAA,CAAsB,EAAI,mCAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,WAAU,CAAC,EACrE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CAGd,MAFA,MAAM,EAAM,QAAQ,EACpB,MAAM,EAAW,QAAQ,EACnB,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"}
@@ -1,18 +1,8 @@
1
1
  import { ProfileTelemetry } from "../../types/telemetry.cjs";
2
+ import "@deepseek-ai/cordis";
2
3
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
4
  //#region src/adapters/pi/persona.d.ts
4
- /**
5
- * Appends the selected profile's persona to the system prompt.
6
- *
7
- * The harness writes the assembled persona to a file in the run directory and
8
- * records the path in the harness state, so agents/ stays the single source of
9
- * persona material and is never copied into config.
10
- *
11
- * This is a separate Pi entry from the /profile command because detached
12
- * children need the persona without the command: they have no transition
13
- * coordinator to run a switch through.
14
- */
15
- declare function personaExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): void;
5
+ declare function personaExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): Promise<void>;
16
6
  //#endregion
17
7
  export { personaExtension as default, personaExtension };
18
8
  //# sourceMappingURL=persona.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persona.d.cts","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"mappings":";;;;;;;;;;;;;;iBAiBgB,iBAAiB,IAAI,cAAc,YAAW"}
1
+ {"version":3,"file":"persona.d.cts","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"mappings":";;;;iBA8DsB,iBACpB,IAAI,cACJ,YAAW,mBACV"}
@@ -1,18 +1,8 @@
1
1
  import { ProfileTelemetry } from "../../types/telemetry.mjs";
2
+ import "@deepseek-ai/cordis";
2
3
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
4
  //#region src/adapters/pi/persona.d.ts
4
- /**
5
- * Appends the selected profile's persona to the system prompt.
6
- *
7
- * The harness writes the assembled persona to a file in the run directory and
8
- * records the path in the harness state, so agents/ stays the single source of
9
- * persona material and is never copied into config.
10
- *
11
- * This is a separate Pi entry from the /profile command because detached
12
- * children need the persona without the command: they have no transition
13
- * coordinator to run a switch through.
14
- */
15
- declare function personaExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): void;
5
+ declare function personaExtension(pi: ExtensionAPI, telemetry?: ProfileTelemetry): Promise<void>;
16
6
  //#endregion
17
7
  export { personaExtension as default, personaExtension };
18
8
  //# sourceMappingURL=persona.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"persona.d.mts","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"mappings":";;;;;;;;;;;;;;iBAiBgB,iBAAiB,IAAI,cAAc,YAAW"}
1
+ {"version":3,"file":"persona.d.mts","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"mappings":";;;;iBA8DsB,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 n from"node:fs";import{requireDoomConfigContext as r}from"@agimon-ai/doompi-config/piContext";function i(i,a=e()){i.on(`before_agent_start`,async(e,i)=>{let{personaFile:o}=r(i).harness;if(o)try{let t=(await n.promises.readFile(o,`utf8`)).trim();return t?{systemPrompt:`${e.systemPrompt}\n\n${t}`}:void 0}catch(e){let n=e instanceof Error?e.message:String(e);a.recordError(t.personaReadFailed,e),process.stderr.write(`[pi-persona] could not read ${o}: ${n}\n`);return}})}export{i as default,i as personaExtension};
1
+ import{createProfileTelemetry as e}from"../telemetry/logSinkTelemetry.mjs";import{PROFILE_EVENT as t}from"../../types/telemetry.mjs";import n from"node:fs";import{requireDoomConfigContext as r}from"@agimon-ai/doompi-config";import{DOOM_CONFIG_SERVICE as i}from"@agimon-ai/doompi-extension-contracts/config";import{connectDoomCordisHost as a}from"@agimon-ai/doompi-extension-contracts/cordis-host";function o(e,{pi:t,telemetry:n}){let r;e.inject([i],e=>(r=e,()=>{r===e&&(r=void 0)})),s(t,n,()=>{if(!r)throw Error(`Doom persona runtime is waiting for the session config service.`);return r})}function s(e,i,a){e.on(`before_agent_start`,async(e,o)=>{let{personaFile:s}=r(a()).harness;if(s)try{let t=(await n.promises.readFile(s,`utf8`)).trim();return t?{systemPrompt:`${e.systemPrompt}\n\n${t}`}:void 0}catch(e){let n=e instanceof Error?e.message:String(e);i.recordError(t.personaReadFailed,e),process.stderr.write(`[pi-persona] could not read ${s}: ${n}\n`);return}})}async function c(t,n=e()){let r=await a(t,`@agimon-ai/doompi-profile/persona`),i=r.root.plugin(o,{pi:t,telemetry:n});try{await i}catch(e){throw await i.dispose(),await r.dispose(),e}let s;t.on(`session_shutdown`,()=>s??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}export{c as default,c as personaExtension,s as registerPersonaHandlers};
2
2
  //# sourceMappingURL=persona.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"persona.mjs","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\n/**\n * Appends the selected profile's persona to the system prompt.\n *\n * The harness writes the assembled persona to a file in the run directory and\n * records the path in the harness state, so agents/ stays the single source of\n * persona material and is never copied into config.\n *\n * This is a separate Pi entry from the /profile command because detached\n * children need the persona without the command: they have no transition\n * coordinator to run a switch through.\n */\nexport function personaExtension(pi: ExtensionAPI, telemetry: ProfileTelemetry = createProfileTelemetry()): void {\n pi.on('before_agent_start', async (event, ctx) => {\n const { personaFile } = requireDoomConfigContext(ctx).harness;\n if (!personaFile) return undefined;\n\n try {\n const content = (await fs.promises.readFile(personaFile, 'utf8')).trim();\n return content ? { systemPrompt: `${event.systemPrompt}\\n\\n${content}` } : undefined;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.personaReadFailed, error);\n process.stderr.write(`[pi-persona] could not read ${personaFile}: ${reason}\\n`);\n return undefined;\n }\n });\n}\n\nexport default personaExtension;\n"],"mappings":"0OAiBA,SAAgB,EAAiB,EAAkB,EAA8B,EAAuB,EAAS,CAC/G,EAAG,GAAG,qBAAsB,MAAO,EAAO,IAAQ,CAChD,GAAM,CAAE,eAAgB,EAAyB,CAAG,CAAC,CAAC,QACjD,KAEL,GAAI,CACF,IAAM,GAAW,MAAM,EAAG,SAAS,SAAS,EAAa,MAAM,EAAA,CAAG,KAAK,EACvE,OAAO,EAAU,CAAE,aAAc,GAAG,EAAM,aAAa,MAAM,GAAU,EAAI,IAAA,EAC7E,OAAS,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACpE,EAAe,YAAY,EAAc,kBAAmB,CAAK,EACjE,QAAQ,OAAO,MAAM,+BAA+B,EAAY,IAAI,EAAO,GAAG,EAC9E,MACF,CACF,CAAC,CACH"}
1
+ {"version":3,"file":"persona.mjs","names":[],"sources":["../../../src/adapters/pi/persona.ts"],"sourcesContent":["import fs from 'node:fs';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { PROFILE_EVENT, type ProfileTelemetry } from '../../types/telemetry.ts';\nimport { createProfileTelemetry } from '../telemetry/logSinkTelemetry.ts';\n\n/**\n * Appends the selected profile's persona to the system prompt.\n *\n * The harness writes the assembled persona to a file in the run directory and\n * records the path in the harness state, so agents/ stays the single source of\n * persona material and is never copied into config.\n *\n * This is a separate Pi entry from the /profile command because detached\n * children need the persona without the command: they have no transition\n * coordinator to run a switch through.\n */\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-profile/persona';\n\ninterface PersonaPluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: ProfileTelemetry;\n}\n\nfunction personaPlugin(cordis: Context, { pi, telemetry }: PersonaPluginConfig): void {\n let activeContext: Context | undefined;\n cordis.inject([DOOM_CONFIG_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n registerPersonaHandlers(pi, telemetry, () => {\n if (!activeContext) throw new Error('Doom persona runtime is waiting for the session config service.');\n return activeContext;\n });\n}\n\nexport function registerPersonaHandlers(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry,\n cordisContext: () => Context,\n): void {\n pi.on('before_agent_start', async (event, _ctx) => {\n const { personaFile } = requireDoomConfigContext(cordisContext()).harness;\n if (!personaFile) return undefined;\n\n try {\n const content = (await fs.promises.readFile(personaFile, 'utf8')).trim();\n return content ? { systemPrompt: `${event.systemPrompt}\\n\\n${content}` } : undefined;\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n void telemetry.recordError(PROFILE_EVENT.personaReadFailed, error);\n process.stderr.write(`[pi-persona] could not read ${personaFile}: ${reason}\\n`);\n return undefined;\n }\n });\n}\n\nexport async function personaExtension(\n pi: ExtensionAPI,\n telemetry: ProfileTelemetry = createProfileTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(personaPlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n await fiber.dispose();\n await connection.dispose();\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\nexport default personaExtension;\n"],"mappings":"6YA2BA,SAAS,EAAc,EAAiB,CAAE,KAAI,aAAwC,CACpF,IAAI,EACJ,EAAO,OAAO,CAAC,CAAmB,EAAI,IACpC,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACD,EAAwB,EAAI,MAAiB,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,iEAAiE,EACrG,OAAO,CACT,CAAC,CACH,CAEA,SAAgB,EACd,EACA,EACA,EACM,CACN,EAAG,GAAG,qBAAsB,MAAO,EAAO,IAAS,CACjD,GAAM,CAAE,eAAgB,EAAyB,EAAc,CAAC,CAAC,CAAC,QAC7D,KAEL,GAAI,CACF,IAAM,GAAW,MAAM,EAAG,SAAS,SAAS,EAAa,MAAM,EAAA,CAAG,KAAK,EACvE,OAAO,EAAU,CAAE,aAAc,GAAG,EAAM,aAAa,MAAM,GAAU,EAAI,IAAA,EAC7E,OAAS,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACpE,EAAe,YAAY,EAAc,kBAAmB,CAAK,EACjE,QAAQ,OAAO,MAAM,+BAA+B,EAAY,IAAI,EAAO,GAAG,EAC9E,MACF,CACF,CAAC,CACH,CAEA,eAAsB,EACpB,EACA,EAA8B,EAAuB,EACtC,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,mCAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAe,CAAE,KAAI,WAAU,CAAC,EACrE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CAGd,MAFA,MAAM,EAAM,QAAQ,EACpB,MAAM,EAAW,QAAQ,EACnB,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"}
@@ -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){let d=s();l.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(s,f)=>{let p=(0,n.requireDoomConfigContext)(f).harness,[{requireHarnessRoot:m},{loadProfiles:h}]=await Promise.all([d.harnessStore(),d.profiles()]),g=m(p),_=p.profile,v;try{v=h(g)}catch(t){let n=t instanceof Error?t.message:String(t);u.recordError(e.PROFILE_EVENT.profileLoadFailed,t),f.ui.notify(`Could not load ${o}: ${n}`,i);return}if(v.length===0){f.ui.notify(`No profiles found in ${o}.`,i);return}let y=await c(f,v,_,d.picker),b=v.find(e=>e.name===y);if(!b)return;if(b.name===_){f.ui.notify(t.profileSummary(b),a);return}let x=(0,r.requireDoomTransitionCoordinator)(f),S,C=await x.execute({sessionId:f.sessionManager.getSessionId(),hostGeneration:x.hostGeneration,operationId:crypto.randomUUID(),source:f.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:b.name}},async(t,n)=>{S=(0,r.prepareMinorModeReloadHandoff)(f,x.hostGeneration,n.operationId,n.previous.minorModes);try{let{applyProfile:t}=await d.selection();f.ui.notify(await t(b,p),a);let{persistHarnessSelection:r}=await d.journal();return r(l,{...p,profile:b.name}),u.recordEvent(e.PROFILE_EVENT.profileApplied,{"harness.profile":b.name,..._?{"harness.profile.previous":_}:{},"harness.transition.disposition":n.disposition,"harness.transition.operation_id":n.operationId}),`applied`}catch(e){throw S?.discard(),S=void 0,e}});if(C.outcome===`rejected`||C.outcome===`stale`){S?.discard(),f.ui.notify(`Profile transition was ${C.outcome}: ${C.diagnostics.join(`, `)}`,i);return}if(C.outcome!==`applied`){S?.discard();return}try{await f.waitForIdle(),await f.reload()}catch(e){throw S?.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");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;
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 { 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(pi: ExtensionAPI, telemetry: ProfileTelemetry): 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 state = requireDoomConfigContext(ctx).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(ctx);\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 ctx,\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","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,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){let f=u();t.registerCommand(`profile`,{description:`Show profiles and load a different persona and environment`,handler:async(u,p)=>{let m=i(p).harness,[{requireHarnessRoot:h},{loadProfiles:g}]=await Promise.all([f.harnessStore(),f.profiles()]),_=h(m),v=m.profile,y;try{y=g(_)}catch(t){let n=t instanceof Error?t.message:String(t);r.recordError(e.profileLoadFailed,t),p.ui.notify(`Could not load ${l}: ${n}`,s);return}if(y.length===0){p.ui.notify(`No profiles found in ${l}.`,s);return}let b=await d(p,y,v,f.picker),x=y.find(e=>e.name===b);if(!x)return;if(x.name===v){p.ui.notify(n(x),c);return}let S=o(p),C,w=await S.execute({sessionId:p.sessionManager.getSessionId(),hostGeneration:S.hostGeneration,operationId:crypto.randomUUID(),source:p.mode===`tui`?`ui`:`command`,target:{axis:`profile`,profile:x.name}},async(n,i)=>{C=a(p,S.hostGeneration,i.operationId,i.previous.minorModes);try{let{applyProfile:n}=await f.selection();p.ui.notify(await n(x,m),c);let{persistHarnessSelection:a}=await f.journal();return a(t,{...m,profile:x.name}),r.recordEvent(e.profileApplied,{"harness.profile":x.name,...v?{"harness.profile.previous":v}:{},"harness.transition.disposition":i.disposition,"harness.transition.operation_id":i.operationId}),`applied`}catch(e){throw C?.discard(),C=void 0,e}});if(w.outcome===`rejected`||w.outcome===`stale`){C?.discard(),p.ui.notify(`Profile transition was ${w.outcome}: ${w.diagnostics.join(`, `)}`,s);return}if(w.outcome!==`applied`){C?.discard();return}try{await p.waitForIdle(),await p.reload()}catch(e){throw C?.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}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};
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 { 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(pi: ExtensionAPI, telemetry: ProfileTelemetry): 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 state = requireDoomConfigContext(ctx).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(ctx);\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 ctx,\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":"6WAWA,MAAM,EAAU,UACV,EAAO,OACP,EAAsB,sBAQ5B,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACA,EACA,EACJ,MAAO,CACL,iBAAqB,IAAiB,OAAO,yCAC7C,YAAgB,IAAY,OAAO,sCACnC,WAAe,IAAW,OAAO,gDACjC,aAAiB,IAAa,OAAO,qCACrC,cAAkB,IAAc,OAAO,2CACzC,CACF,CAEA,eAAe,EACb,EACA,EACA,EACA,EAC6B,CAC7B,IAAM,EAAQ,EAAa,CAAO,EAClC,GAAI,EAAI,OAAS,MACf,OAAO,EAAI,GAAG,OACZ,EACA,EAAS,IAAK,GAAY,EAAQ,IAAI,CACxC,EAEF,GAAM,CAAE,yBAA0B,MAAM,EAAW,EAUnD,OAAO,MATc,EAAI,GAAG,QACzB,EAAM,EAAO,EAAa,IACzB,IAAI,EACF,CAAE,QAAO,MAAO,EAAa,CAAQ,EAAG,SAAU,EAAU,CAAC,CAAO,EAAI,CAAC,EAAG,MAAO,EAAM,EACzF,EACA,EACA,CACF,CACJ,EAAA,GACgB,EAClB,CAEA,SAAgB,EAAuB,EAAkB,EAAmC,CAC1F,IAAM,EAAO,EAAY,EAEzB,EAAG,gBAAgB,UAAW,CAC5B,YAAa,6DACb,QAAS,MAAO,EAAO,IAAQ,CAC7B,IAAM,EAAQ,EAAyB,CAAG,CAAC,CAAC,QACtC,CAAC,CAAE,sBAAsB,CAAE,iBAAkB,MAAM,QAAQ,IAAI,CAAC,EAAK,aAAa,EAAG,EAAK,SAAS,CAAC,CAAC,EACrG,EAAO,EAAmB,CAAK,EAC/B,EAAU,EAAM,QAClB,EACJ,GAAI,CACF,EAAW,EAAa,CAAI,CAC9B,OAAS,EAAO,CACd,IAAM,EAAS,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EACpE,EAAe,YAAY,EAAc,kBAAmB,CAAK,EACjE,EAAI,GAAG,OAAO,kBAAkB,EAAoB,IAAI,IAAU,CAAO,EACzE,MACF,CAEA,GAAI,EAAS,SAAW,EAAG,CACzB,EAAI,GAAG,OAAO,wBAAwB,EAAoB,GAAI,CAAO,EACrE,MACF,CAEA,IAAM,EAAS,MAAM,EAAY,EAAK,EAAU,EAAS,EAAK,MAAM,EAC9D,EAAU,EAAS,KAAM,GAAc,EAAU,OAAS,CAAM,EACtE,GAAI,CAAC,EAAS,OACd,GAAI,EAAQ,OAAS,EAAS,CAC5B,EAAI,GAAG,OAAO,EAAe,CAAO,EAAG,CAAI,EAC3C,MACF,CAEA,IAAM,EAAc,EAAiC,CAAG,EACpD,EACE,EAAS,MAAM,EAAY,QAC/B,CACE,UAAW,EAAI,eAAe,aAAa,EAC3C,eAAgB,EAAY,eAC5B,YAAa,OAAO,WAAW,EAC/B,OAAQ,EAAI,OAAS,MAAQ,KAAO,UACpC,OAAQ,CAAE,KAAM,UAAW,QAAS,EAAQ,IAAK,CACnD,EACA,MAAO,EAAU,IAAS,CACxB,EAAyB,EACvB,EACA,EAAY,eACZ,EAAK,YACL,EAAK,SAAS,UAChB,EACA,GAAI,CACF,GAAM,CAAE,gBAAiB,MAAM,EAAK,UAAU,EAC9C,EAAI,GAAG,OAAO,MAAM,EAAa,EAAS,CAAK,EAAG,CAAI,EACtD,GAAM,CAAE,2BAA4B,MAAM,EAAK,QAAQ,EAQvD,OAPA,EAAwB,EAAI,CAAE,GAAG,EAAO,QAAS,EAAQ,IAAK,CAAC,EAC/D,EAAe,YAAY,EAAc,eAAgB,CACvD,kBAAmB,EAAQ,KAC3B,GAAI,EAAU,CAAE,2BAA4B,CAAQ,EAAI,CAAC,EACzD,iCAAkC,EAAK,YACvC,kCAAmC,EAAK,WAC1C,CAAC,EACM,SACT,OAAS,EAAO,CAGd,MAFA,GAAwB,QAAQ,EAChC,EAAyB,IAAA,GACnB,CACR,CACF,CACF,EACA,GAAI,EAAO,UAAY,YAAc,EAAO,UAAY,QAAS,CAC/D,GAAwB,QAAQ,EAChC,EAAI,GAAG,OAAO,0BAA0B,EAAO,QAAQ,IAAI,EAAO,YAAY,KAAK,IAAI,IAAK,CAAO,EACnG,MACF,CACA,GAAI,EAAO,UAAY,UAAW,CAChC,GAAwB,QAAQ,EAChC,MACF,CAEA,GAAI,CACF,MAAM,EAAI,YAAY,EAGtB,MAAM,EAAI,OAAO,CACnB,OAAS,EAAO,CAEd,MADA,GAAwB,QAAQ,EAC1B,CACR,CACF,CACF,CAAC,CACH"}
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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"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/telemetry/logSinkTelemetry.cjs"),t=require("./types/telemetry.cjs"),n=require("./adapters/pi/persona.cjs"),r=require("./services/profileText.cjs"),i=require("./adapters/pi/extension.cjs");exports.PROFILE_EVENT=t.PROFILE_EVENT,exports.createProfileTelemetry=e.createProfileTelemetry,exports.personaExtension=n.default,exports.profileDescription=r.profileDescription,exports.profileExtension=i.default,exports.profileItems=r.profileItems,exports.profileSummary=r.profileSummary,exports.profileTitle=r.profileTitle;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/telemetry/logSinkTelemetry.cjs"),t=require("./types/telemetry.cjs"),n=require("./adapters/pi/persona.cjs"),r=require("./services/profileText.cjs"),i=require("./adapters/pi/extension.cjs");exports.PROFILE_EVENT=t.PROFILE_EVENT,exports.PROFILE_STATUS_KEY=r.PROFILE_STATUS_KEY,exports.createProfileTelemetry=e.createProfileTelemetry,exports.personaExtension=n.default,exports.profileDescription=r.profileDescription,exports.profileExtension=i.default,exports.profileItems=r.profileItems,exports.profileStatus=r.profileStatus,exports.profileSummary=r.profileSummary,exports.profileTitle=r.profileTitle;
package/dist/index.d.cts CHANGED
@@ -2,5 +2,5 @@ import { PROFILE_EVENT, ProfileEventAttributes, ProfileEventName, ProfileTelemet
2
2
  import personaExtension from "./adapters/pi/persona.cjs";
3
3
  import profileExtension from "./adapters/pi/extension.cjs";
4
4
  import { ProfileTelemetryOptions, createProfileTelemetry } from "./adapters/telemetry/logSinkTelemetry.cjs";
5
- import { profileDescription, profileItems, profileSummary, profileTitle } from "./services/profileText.cjs";
6
- export { PROFILE_EVENT, type ProfileEventAttributes, type ProfileEventName, type ProfileTelemetry, type ProfileTelemetryOptions, createProfileTelemetry, personaExtension, profileDescription, profileExtension, profileItems, profileSummary, profileTitle };
5
+ import { PROFILE_STATUS_KEY, profileDescription, profileItems, profileStatus, profileSummary, profileTitle } from "./services/profileText.cjs";
6
+ export { PROFILE_EVENT, PROFILE_STATUS_KEY, type ProfileEventAttributes, type ProfileEventName, type ProfileTelemetry, type ProfileTelemetryOptions, createProfileTelemetry, personaExtension, profileDescription, profileExtension, profileItems, profileStatus, profileSummary, profileTitle };
package/dist/index.d.mts CHANGED
@@ -2,5 +2,5 @@ import { PROFILE_EVENT, ProfileEventAttributes, ProfileEventName, ProfileTelemet
2
2
  import personaExtension from "./adapters/pi/persona.mjs";
3
3
  import profileExtension from "./adapters/pi/extension.mjs";
4
4
  import { ProfileTelemetryOptions, createProfileTelemetry } from "./adapters/telemetry/logSinkTelemetry.mjs";
5
- import { profileDescription, profileItems, profileSummary, profileTitle } from "./services/profileText.mjs";
6
- export { PROFILE_EVENT, type ProfileEventAttributes, type ProfileEventName, type ProfileTelemetry, type ProfileTelemetryOptions, createProfileTelemetry, personaExtension, profileDescription, profileExtension, profileItems, profileSummary, profileTitle };
5
+ import { PROFILE_STATUS_KEY, profileDescription, profileItems, profileStatus, profileSummary, profileTitle } from "./services/profileText.mjs";
6
+ export { PROFILE_EVENT, PROFILE_STATUS_KEY, type ProfileEventAttributes, type ProfileEventName, type ProfileTelemetry, type ProfileTelemetryOptions, createProfileTelemetry, personaExtension, profileDescription, profileExtension, profileItems, profileStatus, profileSummary, profileTitle };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createProfileTelemetry as e}from"./adapters/telemetry/logSinkTelemetry.mjs";import{PROFILE_EVENT as t}from"./types/telemetry.mjs";import n from"./adapters/pi/persona.mjs";import{profileDescription as r,profileItems as i,profileSummary as a,profileTitle as o}from"./services/profileText.mjs";import s from"./adapters/pi/extension.mjs";export{t as PROFILE_EVENT,e as createProfileTelemetry,n as personaExtension,r as profileDescription,s as profileExtension,i as profileItems,a as profileSummary,o as profileTitle};
1
+ import{createProfileTelemetry as e}from"./adapters/telemetry/logSinkTelemetry.mjs";import{PROFILE_EVENT as t}from"./types/telemetry.mjs";import n from"./adapters/pi/persona.mjs";import{PROFILE_STATUS_KEY as r,profileDescription as i,profileItems as a,profileStatus as o,profileSummary as s,profileTitle as c}from"./services/profileText.mjs";import l from"./adapters/pi/extension.mjs";export{t as PROFILE_EVENT,r as PROFILE_STATUS_KEY,e as createProfileTelemetry,n as personaExtension,i as profileDescription,l as profileExtension,a as profileItems,o as profileStatus,s as profileSummary,c as profileTitle};
@@ -1,3 +1,3 @@
1
- const e=`(none)`;function t(e){return e.map(e=>({value:e.name,label:e.name,description:n(e)}))}function n(e){let t=Object.keys(e.env).sort();return t.length>0?`${e.persona} (env: ${t.join(`, `)})`:e.persona}function r(t){let n=Object.keys(t.env).sort();return[`Profile ${t.name}`,`Persona: ${t.persona}`,`Env: ${n.join(`, `)||e}`,`Already loaded.`].join(`
2
- `)}function i(t){return`Profile (current: ${t??e})`}exports.profileDescription=n,exports.profileItems=t,exports.profileSummary=r,exports.profileTitle=i;
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;
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/** 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,SAGb,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/**\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,6 +1,14 @@
1
1
  import { AgentProfile } from "@agimon-ai/doompi-config/profiles";
2
2
  import { SelectItem } from "@earendil-works/pi-tui";
3
3
  //#region src/services/profileText.d.ts
4
+ /** Footer status key the cockpit's profile axis reads. */
5
+ declare const PROFILE_STATUS_KEY = "doom-profile";
6
+ /**
7
+ * The published axis content: the active profile's name, empty while
8
+ * profiles exist with none active, and undefined to withhold the status so
9
+ * the cockpit keeps the axis off the bar entirely.
10
+ */
11
+ declare function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined;
4
12
  /** Rows for the single-select profile picker. */
5
13
  declare function profileItems(profiles: AgentProfile[]): SelectItem[];
6
14
  declare function profileDescription(profile: AgentProfile): string;
@@ -8,5 +16,5 @@ declare function profileDescription(profile: AgentProfile): string;
8
16
  declare function profileSummary(profile: AgentProfile): string;
9
17
  declare function profileTitle(current?: string): string;
10
18
  //#endregion
11
- export { profileDescription, profileItems, profileSummary, profileTitle };
19
+ export { PROFILE_STATUS_KEY, profileDescription, profileItems, profileStatus, profileSummary, profileTitle };
12
20
  //# sourceMappingURL=profileText.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.d.cts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;iBAMgB,aAAa,UAAU,iBAAiB;iBAQxC,mBAAmB,SAAS;;iBAM5B,eAAe,SAAS;iBAUxB,aAAa"}
1
+ {"version":3,"file":"profileText.d.cts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;cAMa;;;;;;iBAOG,cAAc,6BAA6B;;iBAM3C,aAAa,UAAU,iBAAiB;iBAQxC,mBAAmB,SAAS;;iBAM5B,eAAe,SAAS;iBAUxB,aAAa"}
@@ -1,6 +1,14 @@
1
1
  import { AgentProfile } from "@agimon-ai/doompi-config/profiles";
2
2
  import { SelectItem } from "@earendil-works/pi-tui";
3
3
  //#region src/services/profileText.d.ts
4
+ /** Footer status key the cockpit's profile axis reads. */
5
+ declare const PROFILE_STATUS_KEY = "doom-profile";
6
+ /**
7
+ * The published axis content: the active profile's name, empty while
8
+ * profiles exist with none active, and undefined to withhold the status so
9
+ * the cockpit keeps the axis off the bar entirely.
10
+ */
11
+ declare function profileStatus(current: string | undefined, hasProfiles: boolean): string | undefined;
4
12
  /** Rows for the single-select profile picker. */
5
13
  declare function profileItems(profiles: AgentProfile[]): SelectItem[];
6
14
  declare function profileDescription(profile: AgentProfile): string;
@@ -8,5 +16,5 @@ declare function profileDescription(profile: AgentProfile): string;
8
16
  declare function profileSummary(profile: AgentProfile): string;
9
17
  declare function profileTitle(current?: string): string;
10
18
  //#endregion
11
- export { profileDescription, profileItems, profileSummary, profileTitle };
19
+ export { PROFILE_STATUS_KEY, profileDescription, profileItems, profileStatus, profileSummary, profileTitle };
12
20
  //# sourceMappingURL=profileText.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"profileText.d.mts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;iBAMgB,aAAa,UAAU,iBAAiB;iBAQxC,mBAAmB,SAAS;;iBAM5B,eAAe,SAAS;iBAUxB,aAAa"}
1
+ {"version":3,"file":"profileText.d.mts","names":[],"sources":["../../src/services/profileText.ts"],"mappings":";;;;cAMa;;;;;;iBAOG,cAAc,6BAA6B;;iBAM3C,aAAa,UAAU,iBAAiB;iBAQxC,mBAAmB,SAAS;;iBAM5B,eAAe,SAAS;iBAUxB,aAAa"}
@@ -1,3 +1,3 @@
1
- const e=`(none)`;function t(e){return e.map(e=>({value:e.name,label:e.name,description:n(e)}))}function n(e){let t=Object.keys(e.env).sort();return t.length>0?`${e.persona} (env: ${t.join(`, `)})`:e.persona}function r(t){let n=Object.keys(t.env).sort();return[`Profile ${t.name}`,`Persona: ${t.persona}`,`Env: ${n.join(`, `)||e}`,`Already loaded.`].join(`
2
- `)}function i(t){return`Profile (current: ${t??e})`}export{n as profileDescription,t as profileItems,r as profileSummary,i as profileTitle};
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};
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/** 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,SAGb,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/**\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,9 +1,9 @@
1
1
  //#region src/types/telemetry.d.ts
2
2
  /** Telemetry events this package reports, and the port that records them. */
3
3
  declare const PROFILE_EVENT: {
4
- readonly profileApplied: "doom_pi_profile.applied";
5
- readonly profileLoadFailed: "doom_pi_profile.load_failed";
6
- readonly personaReadFailed: "doom_pi_profile.persona_read_failed";
4
+ readonly profileApplied: 'doom_pi_profile.applied';
5
+ readonly profileLoadFailed: 'doom_pi_profile.load_failed';
6
+ readonly personaReadFailed: 'doom_pi_profile.persona_read_failed';
7
7
  };
8
8
  type ProfileEventName = (typeof PROFILE_EVENT)[keyof typeof PROFILE_EVENT];
9
9
  type ProfileEventAttributes = Record<string, string | number | boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;;;;;KAMD,2BAA2B,4BAA4B;KACvD,yBAAyB;UAEpB;EACf,YAAY,OAAO,kBAAkB,gBAAgB,aAAa,yBAAyB;EAC3F,YAAY,OAAO,kBAAkB,aAAa,yBAAyB"}
1
+ {"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;WACA;;KAGU,2BAA2B,4BAA4B;KACvD,yBAAyB;UAEpB;EACf,YAAY,OAAO,kBAAkB,gBAAgB,aAAa,yBAAyB;EAC3F,YAAY,OAAO,kBAAkB,aAAa,yBAAyB"}
@@ -1,9 +1,9 @@
1
1
  //#region src/types/telemetry.d.ts
2
2
  /** Telemetry events this package reports, and the port that records them. */
3
3
  declare const PROFILE_EVENT: {
4
- readonly profileApplied: "doom_pi_profile.applied";
5
- readonly profileLoadFailed: "doom_pi_profile.load_failed";
6
- readonly personaReadFailed: "doom_pi_profile.persona_read_failed";
4
+ readonly profileApplied: 'doom_pi_profile.applied';
5
+ readonly profileLoadFailed: 'doom_pi_profile.load_failed';
6
+ readonly personaReadFailed: 'doom_pi_profile.persona_read_failed';
7
7
  };
8
8
  type ProfileEventName = (typeof PROFILE_EVENT)[keyof typeof PROFILE_EVENT];
9
9
  type ProfileEventAttributes = Record<string, string | number | boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;;;;;KAMD,2BAA2B,4BAA4B;KACvD,yBAAyB;UAEpB;EACf,YAAY,OAAO,kBAAkB,gBAAgB,aAAa,yBAAyB;EAC3F,YAAY,OAAO,kBAAkB,aAAa,yBAAyB"}
1
+ {"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;WACA;;KAGU,2BAA2B,4BAA4B;KACvD,yBAAyB;UAEpB;EACf,YAAY,OAAO,kBAAkB,gBAAgB,aAAa,yBAAyB;EAC3F,YAAY,OAAO,kBAAkB,aAAa,yBAAyB"}
package/llms.txt CHANGED
@@ -1,7 +1,12 @@
1
1
  # Doom Pi Profile
2
2
 
3
- Persona and environment profile switching for Doom Pi sessions.
3
+ Package-owned guidance for DoomPi profile discovery, personas, environment defaults, and precedence.
4
4
 
5
- ## Resources
5
+ ## How to author
6
6
 
7
- - [Package README](./README.md): the `/profile` command, the persona system-prompt entry, and how a profile resolves.
7
+ - [Author DoomPi profiles](./src/prompts/doompi-author-profile/SKILL.md): Configure personal and repository profiles.
8
+ - [Profiles authoring contract](./src/prompts/doompi-author-profile/references/profiles-contract.md): File locations, discovery, precedence, path safety, environment defaults, and verification.
9
+
10
+ ## Package reference
11
+
12
+ - [Package README](./README.md): Profile behavior, activation, and public package entries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-profile",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.20",
4
4
  "description": "Persona and environment profile switching for DoomPi sessions.",
5
5
  "keywords": [
6
6
  "agent-persona",
@@ -15,12 +15,22 @@
15
15
  "system-prompt"
16
16
  ],
17
17
  "homepage": "https://agimon.ai",
18
+ "bugs": {
19
+ "url": "https://github.com/AgiFlow/doompi/issues"
20
+ },
18
21
  "license": "MIT",
19
22
  "author": "Vuong Ngo",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/AgiFlow/doompi.git",
26
+ "directory": "packages/core/doompi-profile"
27
+ },
20
28
  "files": [
21
29
  "dist",
30
+ "web",
22
31
  "llms.txt",
23
32
  "README.md",
33
+ "src/prompts",
24
34
  "LICENSE",
25
35
  "package.json"
26
36
  ],
@@ -51,25 +61,26 @@
51
61
  },
52
62
  "dependencies": {
53
63
  "@deepseek-ai/cordis": "4.0.1",
54
- "@agimon-ai/doompi-config": "0.0.1-alpha.24",
55
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.24",
56
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.24",
57
- "@agimon-ai/doompi-ui": "0.0.1-alpha.24"
64
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.42",
65
+ "@agimon-ai/doompi-config": "0.0.1-alpha.42",
66
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.42",
67
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.5",
68
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.42"
58
69
  },
59
70
  "devDependencies": {
60
71
  "@deepseek-ai/cordis": "4.0.1",
61
- "@earendil-works/pi-coding-agent": "0.84.2",
62
- "@earendil-works/pi-tui": "0.84.2",
63
- "@types/node": "26.1.1",
64
- "@vitest/coverage-v8": "4.1.10",
72
+ "@earendil-works/pi-coding-agent": "0.84.4",
73
+ "@earendil-works/pi-tui": "0.84.4",
74
+ "@types/node": "26.2.0",
75
+ "@vitest/coverage-v8": "4.1.11",
65
76
  "tsdown": "0.22.14",
66
- "typescript": "6.0.3",
67
- "vitest": "4.1.10",
68
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.24"
77
+ "typescript": "7.0.2",
78
+ "vitest": "4.1.11",
79
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.40"
69
80
  },
70
81
  "peerDependencies": {
71
- "@earendil-works/pi-coding-agent": "0.84.2",
72
- "@earendil-works/pi-tui": "0.84.2"
82
+ "@earendil-works/pi-coding-agent": "0.84.4",
83
+ "@earendil-works/pi-tui": "0.84.4"
73
84
  },
74
85
  "peerDependenciesMeta": {
75
86
  "@earendil-works/pi-coding-agent": {
@@ -82,6 +93,11 @@
82
93
  "engines": {
83
94
  "node": ">=22.19.0"
84
95
  },
96
+ "doompiWeb": {
97
+ "pluginId": "profile",
98
+ "channels": [],
99
+ "client": "./web/index.ts"
100
+ },
85
101
  "pi": {
86
102
  "extensions": [
87
103
  "./dist/extensions/pi.mjs"
@@ -90,7 +106,7 @@
90
106
  "scripts": {
91
107
  "build": "tsdown",
92
108
  "test": "vitest --run",
93
- "typecheck": "tsc --noEmit",
109
+ "typecheck": "tsc --noEmit && tsc --noEmit -p web/tsconfig.json",
94
110
  "lint": "oxlint . && oxfmt . --check",
95
111
  "fixcode": "oxlint . --fix && oxfmt ."
96
112
  }
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: doompi-author-profile
3
+ 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.
4
+ ---
5
+
6
+ # Author DoomPi profiles
7
+
8
+ Configure a profile as one persona plus optional environment defaults.
9
+
10
+ ## Workflow
11
+
12
+ 1. Read both `~/.pi/.doom/profiles.yaml` and the repository's `.doom/profiles.yaml` when they exist.
13
+ 2. Use `profiles.roots` for one-level persona discovery. Use `profiles.entries` when a profile needs an explicit persona path or environment defaults.
14
+ 3. Resolve relative roots and persona paths against the file that declares them. Keep an explicit persona inside that file's `agents/` directory or one of its configured roots.
15
+ 4. Put instructions in `profile.md`, `SOUL.md`, or `AGENTS.md`. DoomPi concatenates non-empty files in that order.
16
+ 5. Keep every `env` value a string. Treat it as a default because an already exported value wins.
17
+ 6. Preserve the precedence rules. Explicit entries override discoveries, and repository entries override personal entries at the same precedence level.
18
+ 7. Verify the selection with `doompi --profile <name> --explain`, then smoke-test `/profile` when changing live switching behavior.
19
+
20
+ Read [references/profiles-contract.md](references/profiles-contract.md) before changing discovery, precedence, path handling, or profile application.
@@ -0,0 +1,100 @@
1
+ # DoomPi profiles authoring contract
2
+
3
+ ## Sources and shape
4
+
5
+ DoomPi reads profile declarations in this order:
6
+
7
+ 1. `~/.pi/.doom/profiles.yaml`
8
+ 2. `<repoRoot>/.doom/profiles.yaml`
9
+
10
+ The current catalog shape is:
11
+
12
+ ```yaml
13
+ profiles:
14
+ roots: [agents/acme]
15
+ entries:
16
+ writer:
17
+ persona: agents/special/writer
18
+ env:
19
+ BRAND: acme
20
+ TONE: concise
21
+ ```
22
+
23
+ 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.
26
+
27
+ Every relative root or persona path resolves against the file that declares it. Repository paths
28
+ resolve from the repository root. Personal paths resolve from `~/.pi/.doom`.
29
+
30
+ ## Root discovery
31
+
32
+ Each `profiles.roots` value must resolve to an existing directory.
33
+
34
+ - If the root directly contains `profile.md`, `SOUL.md`, or `AGENTS.md`, the root is one profile.
35
+ Its directory name is the profile name, and its children are not scanned.
36
+ - Otherwise, DoomPi inspects only the root's direct-child directories. A child becomes a profile
37
+ when it directly contains at least one persona file.
38
+ - Discovery does not recurse. Add another root or an explicit entry for a deeper persona.
39
+ - A discovered profile has an empty environment mapping.
40
+ - Unreadable candidates and candidates whose real path or persona files escape the configured root
41
+ are ignored. An invalid configured root itself is an error.
42
+
43
+ ## Explicit entries and path confinement
44
+
45
+ An explicit entry may contain only `persona` and `env`. `persona` is required, must be a non-empty
46
+ relative path, and must resolve to a directory containing readable persona content.
47
+
48
+ The persona must remain inside either:
49
+
50
+ - the `agents/` directory beside the declaring repository root or personal `.doom` directory, or
51
+ - one of the `profiles.roots` directories declared in that same file.
52
+
53
+ DoomPi checks both the lexical path and the real filesystem path. A persona-directory symlink cannot
54
+ escape an allowed root. Each resolved persona file must also remain inside the real persona
55
+ directory, so a file symlink cannot import instructions from outside it. Invalid explicit entries
56
+ fail loading instead of being skipped.
57
+
58
+ Persona content is assembled from non-empty files in this order:
59
+
60
+ 1. `profile.md`
61
+ 2. `SOUL.md`
62
+ 3. `AGENTS.md`
63
+
64
+ ## Precedence
65
+
66
+ DoomPi resolves names in four passes, from lowest to highest precedence:
67
+
68
+ 1. Personal root discoveries
69
+ 2. Repository root discoveries
70
+ 3. Personal explicit entries
71
+ 4. Repository explicit entries
72
+
73
+ A later profile replaces an earlier profile with the same name. This means any explicit entry
74
+ overrides discovery, including a personal explicit entry replacing a repository discovery. A
75
+ repository explicit entry wins over a personal explicit entry. Unrelated names from all sources
76
+ remain available, and the final list is sorted by profile name.
77
+
78
+ ## Environment defaults and activation
79
+
80
+ Every `env` key maps to a string. Applying a profile writes only variables that are not already
81
+ exported by the caller. When switching profiles, DoomPi removes a value contributed by the previous
82
+ profile only if that value has not since changed, then applies the next profile's defaults.
83
+
84
+ The `/profile` command resolves the named profile and executes the change through the session
85
+ transition coordinator. A profile change uses Pi reload so extensions re-read the environment.
86
+ DoomPi assembles the persona into a private run-directory file, and the persona Pi entry appends
87
+ that content to the system prompt after reload.
88
+
89
+ ## Verification
90
+
91
+ ```sh
92
+ pnpm nx test @agimon-ai/doompi-config
93
+ pnpm nx typecheck @agimon-ai/doompi-config
94
+ pnpm nx test @agimon-ai/doompi-profile
95
+ pnpm nx typecheck @agimon-ai/doompi-profile
96
+ pnpm --filter @agimon-ai/doompi exec doompi --profile writer --explain
97
+ ```
98
+
99
+ Use an actual parent session to smoke-test `/profile` when changing transition, environment, or
100
+ persona application behavior.
package/web/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
2
+
3
+ /**
4
+ * This package's cockpit presence: pure metadata. The selection bar renders
5
+ * the profile axis from what the session reports through the 'doom-profile'
6
+ * footer status; a session with no profiles publishes nothing and the axis
7
+ * stays off the bar.
8
+ */
9
+ export const webPlugin = defineWebPlugin({
10
+ id: 'profile',
11
+ selectionAxes: [
12
+ { name: 'profile', command: 'profile', statusKey: 'doom-profile', emptyLabel: 'no profile', order: 10 },
13
+ ],
14
+ });
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2023", "DOM", "DOM.Iterable"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "jsx": "react-jsx",
8
+ "strict": true,
9
+ "noImplicitAny": true,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "noImplicitReturns": true,
13
+ "noFallthroughCasesInSwitch": true,
14
+ "noEmit": true,
15
+ "resolveJsonModule": true,
16
+ "esModuleInterop": true,
17
+ "skipLibCheck": true,
18
+ "allowImportingTsExtensions": true,
19
+ "forceConsistentCasingInFileNames": true
20
+ },
21
+ "include": [".", "../src/types"]
22
+ }