@agimon-ai/doompi-config 0.0.1-alpha.6 → 0.0.1-alpha.63

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 (332) hide show
  1. package/README.md +113 -15
  2. package/dist/adapters/agentPluginMcp.cjs +2 -0
  3. package/dist/adapters/agentPluginMcp.cjs.map +1 -0
  4. package/dist/adapters/agentPluginMcp.d.cts +37 -0
  5. package/dist/adapters/agentPluginMcp.d.cts.map +1 -0
  6. package/dist/adapters/agentPluginMcp.d.mts +37 -0
  7. package/dist/adapters/agentPluginMcp.d.mts.map +1 -0
  8. package/dist/adapters/agentPluginMcp.mjs +2 -0
  9. package/dist/adapters/agentPluginMcp.mjs.map +1 -0
  10. package/dist/adapters/atomicJson.cjs +2 -0
  11. package/dist/adapters/atomicJson.cjs.map +1 -0
  12. package/dist/adapters/atomicJson.d.cts +5 -0
  13. package/dist/adapters/atomicJson.d.cts.map +1 -0
  14. package/dist/adapters/atomicJson.d.mts +5 -0
  15. package/dist/adapters/atomicJson.d.mts.map +1 -0
  16. package/dist/adapters/atomicJson.mjs +2 -0
  17. package/dist/adapters/atomicJson.mjs.map +1 -0
  18. package/dist/adapters/config.cjs +2 -0
  19. package/dist/adapters/config.cjs.map +1 -0
  20. package/dist/adapters/config.d.cts +25 -0
  21. package/dist/adapters/config.d.cts.map +1 -0
  22. package/dist/adapters/config.d.mts +25 -0
  23. package/dist/adapters/config.d.mts.map +1 -0
  24. package/dist/adapters/config.mjs +2 -0
  25. package/dist/adapters/config.mjs.map +1 -0
  26. package/dist/adapters/configWriter.cjs +2 -0
  27. package/dist/adapters/configWriter.cjs.map +1 -0
  28. package/dist/adapters/configWriter.d.cts +65 -0
  29. package/dist/adapters/configWriter.d.cts.map +1 -0
  30. package/dist/adapters/configWriter.d.mts +65 -0
  31. package/dist/adapters/configWriter.d.mts.map +1 -0
  32. package/dist/adapters/configWriter.mjs +2 -0
  33. package/dist/adapters/configWriter.mjs.map +1 -0
  34. package/dist/adapters/domains.cjs +2 -0
  35. package/dist/adapters/domains.cjs.map +1 -0
  36. package/dist/adapters/domains.d.cts +35 -0
  37. package/dist/adapters/domains.d.cts.map +1 -0
  38. package/dist/adapters/domains.d.mts +35 -0
  39. package/dist/adapters/domains.d.mts.map +1 -0
  40. package/dist/adapters/domains.mjs +2 -0
  41. package/dist/adapters/domains.mjs.map +1 -0
  42. package/dist/adapters/harnessState.cjs +2 -0
  43. package/dist/adapters/harnessState.cjs.map +1 -0
  44. package/dist/adapters/harnessState.d.cts +49 -0
  45. package/dist/adapters/harnessState.d.cts.map +1 -0
  46. package/dist/adapters/harnessState.d.mts +49 -0
  47. package/dist/adapters/harnessState.d.mts.map +1 -0
  48. package/dist/adapters/harnessState.mjs +2 -0
  49. package/dist/adapters/harnessState.mjs.map +1 -0
  50. package/dist/adapters/harnessStore.cjs +2 -0
  51. package/dist/adapters/harnessStore.cjs.map +1 -0
  52. package/dist/adapters/harnessStore.d.cts +111 -0
  53. package/dist/adapters/harnessStore.d.cts.map +1 -0
  54. package/dist/adapters/harnessStore.d.mts +111 -0
  55. package/dist/adapters/harnessStore.d.mts.map +1 -0
  56. package/dist/adapters/harnessStore.mjs +2 -0
  57. package/dist/adapters/harnessStore.mjs.map +1 -0
  58. package/dist/adapters/init.cjs +232 -0
  59. package/dist/adapters/init.cjs.map +1 -0
  60. package/dist/adapters/init.d.cts +38 -0
  61. package/dist/adapters/init.d.cts.map +1 -0
  62. package/dist/adapters/init.d.mts +38 -0
  63. package/dist/adapters/init.d.mts.map +1 -0
  64. package/dist/adapters/init.mjs +232 -0
  65. package/dist/adapters/init.mjs.map +1 -0
  66. package/dist/adapters/layeredConfig.cjs +2 -0
  67. package/dist/{layeredConfig.mjs.map → adapters/layeredConfig.cjs.map} +1 -1
  68. package/dist/adapters/layeredConfig.d.cts +53 -0
  69. package/dist/adapters/layeredConfig.d.cts.map +1 -0
  70. package/dist/adapters/layeredConfig.d.mts +53 -0
  71. package/dist/adapters/layeredConfig.d.mts.map +1 -0
  72. package/dist/adapters/layeredConfig.mjs +2 -0
  73. package/dist/adapters/layeredConfig.mjs.map +1 -0
  74. package/dist/adapters/majorModes.cjs +2 -0
  75. package/dist/adapters/majorModes.cjs.map +1 -0
  76. package/dist/adapters/majorModes.d.cts +105 -0
  77. package/dist/adapters/majorModes.d.cts.map +1 -0
  78. package/dist/adapters/majorModes.d.mts +105 -0
  79. package/dist/adapters/majorModes.d.mts.map +1 -0
  80. package/dist/adapters/majorModes.mjs +2 -0
  81. package/dist/adapters/majorModes.mjs.map +1 -0
  82. package/dist/adapters/pi/configExtension.cjs +2 -0
  83. package/dist/adapters/pi/configExtension.cjs.map +1 -0
  84. package/dist/adapters/pi/configExtension.d.cts +7 -0
  85. package/dist/adapters/pi/configExtension.d.cts.map +1 -0
  86. package/dist/adapters/pi/configExtension.d.mts +7 -0
  87. package/dist/adapters/pi/configExtension.d.mts.map +1 -0
  88. package/dist/adapters/pi/configExtension.mjs +2 -0
  89. package/dist/adapters/pi/configExtension.mjs.map +1 -0
  90. package/dist/adapters/pi/helpContribution.cjs +2 -0
  91. package/dist/adapters/pi/helpContribution.cjs.map +1 -0
  92. package/dist/adapters/pi/helpContribution.mjs +2 -0
  93. package/dist/adapters/pi/helpContribution.mjs.map +1 -0
  94. package/dist/adapters/pi/piContext.cjs +2 -0
  95. package/dist/adapters/pi/piContext.cjs.map +1 -0
  96. package/dist/adapters/pi/piContext.d.cts +33 -0
  97. package/dist/adapters/pi/piContext.d.cts.map +1 -0
  98. package/dist/adapters/pi/piContext.d.mts +33 -0
  99. package/dist/adapters/pi/piContext.d.mts.map +1 -0
  100. package/dist/adapters/pi/piContext.mjs +2 -0
  101. package/dist/adapters/pi/piContext.mjs.map +1 -0
  102. package/dist/adapters/piConfig.cjs +2 -0
  103. package/dist/adapters/piConfig.cjs.map +1 -0
  104. package/dist/adapters/piConfig.d.cts +20 -0
  105. package/dist/adapters/piConfig.d.cts.map +1 -0
  106. package/dist/adapters/piConfig.d.mts +20 -0
  107. package/dist/adapters/piConfig.d.mts.map +1 -0
  108. package/dist/adapters/piConfig.mjs +2 -0
  109. package/dist/adapters/piConfig.mjs.map +1 -0
  110. package/dist/adapters/pluginCatalog.cjs +2 -0
  111. package/dist/adapters/pluginCatalog.cjs.map +1 -0
  112. package/dist/adapters/pluginCatalog.d.cts +11 -0
  113. package/dist/adapters/pluginCatalog.d.cts.map +1 -0
  114. package/dist/adapters/pluginCatalog.d.mts +11 -0
  115. package/dist/adapters/pluginCatalog.d.mts.map +1 -0
  116. package/dist/adapters/pluginCatalog.mjs +2 -0
  117. package/dist/adapters/pluginCatalog.mjs.map +1 -0
  118. package/dist/adapters/profiles.cjs +4 -0
  119. package/dist/adapters/profiles.cjs.map +1 -0
  120. package/dist/adapters/profiles.d.cts +35 -0
  121. package/dist/adapters/profiles.d.cts.map +1 -0
  122. package/dist/adapters/profiles.d.mts +35 -0
  123. package/dist/adapters/profiles.d.mts.map +1 -0
  124. package/dist/adapters/profiles.mjs +4 -0
  125. package/dist/adapters/profiles.mjs.map +1 -0
  126. package/dist/adapters/selectionSwitch.cjs +2 -0
  127. package/dist/adapters/selectionSwitch.cjs.map +1 -0
  128. package/dist/adapters/selectionSwitch.d.cts +30 -0
  129. package/dist/adapters/selectionSwitch.d.cts.map +1 -0
  130. package/dist/adapters/selectionSwitch.d.mts +30 -0
  131. package/dist/adapters/selectionSwitch.d.mts.map +1 -0
  132. package/dist/adapters/selectionSwitch.mjs +2 -0
  133. package/dist/adapters/selectionSwitch.mjs.map +1 -0
  134. package/dist/agentPluginMcp.cjs +1 -0
  135. package/dist/agentPluginMcp.d.cts +2 -0
  136. package/dist/agentPluginMcp.d.mts +2 -0
  137. package/dist/agentPluginMcp.mjs +1 -0
  138. package/dist/atomicJson.cjs +1 -2
  139. package/dist/atomicJson.d.cts +2 -6
  140. package/dist/atomicJson.d.mts +2 -6
  141. package/dist/atomicJson.mjs +1 -2
  142. package/dist/config/piConfig.cjs +1 -2
  143. package/dist/config/piConfig.d.cts +3 -7
  144. package/dist/config/piConfig.d.mts +3 -7
  145. package/dist/config/piConfig.mjs +1 -2
  146. package/dist/config/schema.d.cts +2 -33
  147. package/dist/config/schema.d.mts +2 -33
  148. package/dist/config.cjs +1 -2
  149. package/dist/config.d.cts +4 -24
  150. package/dist/config.d.mts +4 -24
  151. package/dist/config.mjs +1 -2
  152. package/dist/configWriter.cjs +1 -2
  153. package/dist/configWriter.d.cts +2 -52
  154. package/dist/configWriter.d.mts +2 -52
  155. package/dist/configWriter.mjs +1 -2
  156. package/dist/domains.cjs +1 -2
  157. package/dist/domains.d.cts +6 -77
  158. package/dist/domains.d.mts +6 -77
  159. package/dist/domains.mjs +1 -2
  160. package/dist/extensions/pi.cjs +1 -2
  161. package/dist/extensions/pi.d.cts +2 -8
  162. package/dist/extensions/pi.d.mts +2 -8
  163. package/dist/extensions/pi.mjs +1 -2
  164. package/dist/harnessState.cjs +1 -2
  165. package/dist/harnessState.d.cts +2 -50
  166. package/dist/harnessState.d.mts +2 -50
  167. package/dist/harnessState.mjs +1 -2
  168. package/dist/harnessStore.cjs +1 -2
  169. package/dist/harnessStore.d.cts +2 -102
  170. package/dist/harnessStore.d.mts +2 -102
  171. package/dist/harnessStore.mjs +1 -2
  172. package/dist/index.cjs +1 -1
  173. package/dist/index.d.cts +20 -16
  174. package/dist/index.d.mts +20 -16
  175. package/dist/index.mjs +1 -1
  176. package/dist/init.cjs +1 -76
  177. package/dist/init.d.cts +2 -33
  178. package/dist/init.d.mts +2 -33
  179. package/dist/init.mjs +1 -76
  180. package/dist/layeredConfig.cjs +1 -2
  181. package/dist/layeredConfig.d.cts +2 -49
  182. package/dist/layeredConfig.d.mts +2 -49
  183. package/dist/layeredConfig.mjs +1 -2
  184. package/dist/majorModes.cjs +1 -2
  185. package/dist/majorModes.d.cts +2 -75
  186. package/dist/majorModes.d.mts +2 -75
  187. package/dist/majorModes.mjs +1 -2
  188. package/dist/piContext.cjs +1 -2
  189. package/dist/piContext.d.cts +3 -19
  190. package/dist/piContext.d.mts +3 -19
  191. package/dist/piContext.mjs +1 -2
  192. package/dist/profiles.cjs +1 -4
  193. package/dist/profiles.d.cts +2 -35
  194. package/dist/profiles.d.mts +2 -35
  195. package/dist/profiles.mjs +1 -4
  196. package/dist/providers/doomConfigService.cjs +2 -0
  197. package/dist/providers/doomConfigService.cjs.map +1 -0
  198. package/dist/providers/doomConfigService.mjs +2 -0
  199. package/dist/providers/doomConfigService.mjs.map +1 -0
  200. package/dist/schemas/config/schema.d.cts +32 -0
  201. package/dist/schemas/config/schema.d.cts.map +1 -0
  202. package/dist/schemas/config/schema.d.mts +32 -0
  203. package/dist/schemas/config/schema.d.mts.map +1 -0
  204. package/dist/selectionSwitch.cjs +1 -0
  205. package/dist/selectionSwitch.d.cts +2 -0
  206. package/dist/selectionSwitch.d.mts +2 -0
  207. package/dist/selectionSwitch.mjs +1 -0
  208. package/dist/services/configPolicy.cjs +2 -0
  209. package/dist/services/configPolicy.cjs.map +1 -0
  210. package/dist/services/configPolicy.d.cts +56 -0
  211. package/dist/services/configPolicy.d.cts.map +1 -0
  212. package/dist/services/configPolicy.d.mts +56 -0
  213. package/dist/services/configPolicy.d.mts.map +1 -0
  214. package/dist/services/configPolicy.mjs +2 -0
  215. package/dist/services/configPolicy.mjs.map +1 -0
  216. package/dist/services/domains.cjs +2 -0
  217. package/dist/services/domains.cjs.map +1 -0
  218. package/dist/services/domains.d.cts +11 -0
  219. package/dist/services/domains.d.cts.map +1 -0
  220. package/dist/services/domains.d.mts +11 -0
  221. package/dist/services/domains.d.mts.map +1 -0
  222. package/dist/services/domains.mjs +2 -0
  223. package/dist/services/domains.mjs.map +1 -0
  224. package/dist/services/imageSettings.cjs +2 -0
  225. package/dist/services/imageSettings.cjs.map +1 -0
  226. package/dist/services/imageSettings.d.cts +22 -0
  227. package/dist/services/imageSettings.d.cts.map +1 -0
  228. package/dist/services/imageSettings.d.mts +22 -0
  229. package/dist/services/imageSettings.d.mts.map +1 -0
  230. package/dist/services/imageSettings.mjs +2 -0
  231. package/dist/services/imageSettings.mjs.map +1 -0
  232. package/dist/types/config.cjs +1 -0
  233. package/dist/types/config.d.cts +282 -0
  234. package/dist/types/config.d.cts.map +1 -0
  235. package/dist/types/config.d.mts +282 -0
  236. package/dist/types/config.d.mts.map +1 -0
  237. package/dist/types/config.mjs +1 -0
  238. package/dist/types/domains.d.cts +92 -0
  239. package/dist/types/domains.d.cts.map +1 -0
  240. package/dist/types/domains.d.mts +92 -0
  241. package/dist/types/domains.d.mts.map +1 -0
  242. package/dist/types.cjs +1 -2
  243. package/dist/types.d.cts +2 -132
  244. package/dist/types.d.mts +2 -132
  245. package/dist/types.mjs +1 -2
  246. package/llms.txt +12 -0
  247. package/package.json +46 -28
  248. package/src/prompts/doompi-author-config/SKILL.md +23 -0
  249. package/src/prompts/doompi-author-config/agents/openai.yaml +4 -0
  250. package/src/prompts/doompi-author-config/references/config-contract.md +200 -0
  251. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.cjs +0 -1
  252. package/dist/_virtual/_@oxc-project_runtime@0.143.0/helpers/esm/decorate.mjs +0 -1
  253. package/dist/atomicJson.cjs.map +0 -1
  254. package/dist/atomicJson.d.cts.map +0 -1
  255. package/dist/atomicJson.d.mts.map +0 -1
  256. package/dist/atomicJson.mjs.map +0 -1
  257. package/dist/config/piConfig.cjs.map +0 -1
  258. package/dist/config/piConfig.d.cts.map +0 -1
  259. package/dist/config/piConfig.d.mts.map +0 -1
  260. package/dist/config/piConfig.mjs.map +0 -1
  261. package/dist/config/schema.d.cts.map +0 -1
  262. package/dist/config/schema.d.mts.map +0 -1
  263. package/dist/config.cjs.map +0 -1
  264. package/dist/config.d.cts.map +0 -1
  265. package/dist/config.d.mts.map +0 -1
  266. package/dist/config.mjs.map +0 -1
  267. package/dist/configWriter.cjs.map +0 -1
  268. package/dist/configWriter.d.cts.map +0 -1
  269. package/dist/configWriter.d.mts.map +0 -1
  270. package/dist/configWriter.mjs.map +0 -1
  271. package/dist/container.cjs +0 -2
  272. package/dist/container.cjs.map +0 -1
  273. package/dist/container.d.cts +0 -12
  274. package/dist/container.d.cts.map +0 -1
  275. package/dist/container.d.mts +0 -12
  276. package/dist/container.d.mts.map +0 -1
  277. package/dist/container.mjs +0 -2
  278. package/dist/container.mjs.map +0 -1
  279. package/dist/domains.cjs.map +0 -1
  280. package/dist/domains.d.cts.map +0 -1
  281. package/dist/domains.d.mts.map +0 -1
  282. package/dist/domains.mjs.map +0 -1
  283. package/dist/extensions/doom.cjs +0 -2
  284. package/dist/extensions/doom.cjs.map +0 -1
  285. package/dist/extensions/doom.d.cts +0 -8
  286. package/dist/extensions/doom.d.cts.map +0 -1
  287. package/dist/extensions/doom.d.mts +0 -8
  288. package/dist/extensions/doom.d.mts.map +0 -1
  289. package/dist/extensions/doom.mjs +0 -2
  290. package/dist/extensions/doom.mjs.map +0 -1
  291. package/dist/extensions/pi.cjs.map +0 -1
  292. package/dist/extensions/pi.d.cts.map +0 -1
  293. package/dist/extensions/pi.d.mts.map +0 -1
  294. package/dist/extensions/pi.mjs.map +0 -1
  295. package/dist/harnessState.cjs.map +0 -1
  296. package/dist/harnessState.d.cts.map +0 -1
  297. package/dist/harnessState.d.mts.map +0 -1
  298. package/dist/harnessState.mjs.map +0 -1
  299. package/dist/harnessStore.cjs.map +0 -1
  300. package/dist/harnessStore.d.cts.map +0 -1
  301. package/dist/harnessStore.d.mts.map +0 -1
  302. package/dist/harnessStore.mjs.map +0 -1
  303. package/dist/init.cjs.map +0 -1
  304. package/dist/init.d.cts.map +0 -1
  305. package/dist/init.d.mts.map +0 -1
  306. package/dist/init.mjs.map +0 -1
  307. package/dist/layeredConfig.cjs.map +0 -1
  308. package/dist/layeredConfig.d.cts.map +0 -1
  309. package/dist/layeredConfig.d.mts.map +0 -1
  310. package/dist/majorModes.cjs.map +0 -1
  311. package/dist/majorModes.d.cts.map +0 -1
  312. package/dist/majorModes.d.mts.map +0 -1
  313. package/dist/majorModes.mjs.map +0 -1
  314. package/dist/pi.cjs +0 -1
  315. package/dist/pi.d.cts +0 -2
  316. package/dist/pi.d.mts +0 -2
  317. package/dist/pi.mjs +0 -1
  318. package/dist/piContext.cjs.map +0 -1
  319. package/dist/piContext.d.cts.map +0 -1
  320. package/dist/piContext.d.mts.map +0 -1
  321. package/dist/piContext.mjs.map +0 -1
  322. package/dist/profiles.cjs.map +0 -1
  323. package/dist/profiles.d.cts.map +0 -1
  324. package/dist/profiles.d.mts.map +0 -1
  325. package/dist/profiles.mjs.map +0 -1
  326. package/dist/types.cjs.map +0 -1
  327. package/dist/types.d.cts.map +0 -1
  328. package/dist/types.d.mts.map +0 -1
  329. package/dist/types.mjs.map +0 -1
  330. package/skills/doom-pi-config/SKILL.md +0 -26
  331. package/skills/doom-pi-config/agents/openai.yaml +0 -4
  332. package/skills/doom-pi-config/references/config-contract.md +0 -43
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @agimon-ai/doompi-config
2
2
 
3
- Shared Doom configuration loading and dependency injection.
3
+ Typed runtime configuration and immutable session context for
4
+ [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi).
4
5
 
5
- Loads and validates `.doom/`: major modes and their layers, domains, profiles, and the hook
6
- registry. Owns the resolved harness state file that every other reader derives from.
6
+ This package is fixed core in the DoomPi distribution. It loads `config.yaml`, validates supported
7
+ runtime settings, publishes the `doom/config` Cordis service, and maintains selection state shared
8
+ by the major-mode, domain, and profile transition flows.
7
9
 
8
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
- only the skills and tools you name. This package is composed by Doompi and is not usually
10
- installed on its own.
10
+ > **Alpha:** configuration contracts may change between releases.
11
+
12
+ ## Requirements
13
+
14
+ - Node.js 22.19.0 or newer
15
+ - Pi 0.85.0 as a peer dependency
11
16
 
12
17
  ## Install
13
18
 
@@ -15,21 +20,114 @@ installed on its own.
15
20
  npm install @agimon-ai/doompi-config
16
21
  ```
17
22
 
18
- ## How it loads
23
+ Do not add this package to a DoomPi layer. The distribution activates it before selectable packages
24
+ so the rest of the parent session can consume one resolved configuration snapshot.
25
+
26
+ ## Runtime configuration
27
+
28
+ DoomPi reads `~/.pi/.doom/config.yaml` first and `<repoRoot>/.doom/config.yaml` second. Merge policy
29
+ is field-specific because some settings are personal and others are repository policy:
30
+
31
+ | Setting | Effective source policy |
32
+ | ------------------- | -------------------------------------------------------------------- |
33
+ | `projectTrust` | Repository value, defaulting to `ask` |
34
+ | `editor` | Personal config only |
35
+ | `modes.planning` | Personal and repository fields merge, with repository values winning |
36
+ | `voice` | Personal and repository fields merge, with repository values winning |
37
+ | `voice.autoCapture` | Personal config only |
38
+ | `selection` | Per-axis merge, with repository values winning |
39
+
40
+ Supported root keys are `modes`, `projectTrust`, `editor`, `voice`, and `selection`. Unknown keys and
41
+ invalid values fail during loading.
42
+
43
+ ```yaml
44
+ projectTrust: ask
45
+ modes:
46
+ planning:
47
+ main:
48
+ model: openai/gpt-5.4
49
+ thinking: high
50
+ plansDirectory: .doom/plans
51
+ voice:
52
+ engine: auto
53
+ language: en
54
+ selection:
55
+ majorMode: copilot
56
+ domains: [default]
57
+ profile: reviewer
58
+ ```
59
+
60
+ The `selection` mapping names defaults. Definitions for those axes live in `modes.yaml`,
61
+ `domains.yaml`, and `profiles.yaml` and are documented by their owning package Help skills.
62
+
63
+ See the shipped [configuration contract](./src/prompts/doompi-author-config/references/config-contract.md) for
64
+ the complete runtime schema and additional configuration details.
65
+
66
+ ## Typed session context
19
67
 
20
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
- `.doom/modes.yaml`: making it optional would only create broken configurations.
68
+ `@agimon-ai/doompi-config/extensions/pi` joins the runner-scoped Cordis host and provides
69
+ `DOOM_CONFIG_SERVICE`. Consumers must access it under an owning service injection:
70
+
71
+ ```ts
72
+ import { DOOM_CONFIG_SERVICE, type DoomConfigContext, requireDoomConfigContext } from '@agimon-ai/doompi-config';
73
+ import type { Context } from '@deepseek-ai/cordis';
74
+
75
+ export function consumeDoomConfig(context: Context, consume: (snapshot: DoomConfigContext) => void): void {
76
+ context.inject([DOOM_CONFIG_SERVICE], (configContext) => {
77
+ consume(requireDoomConfigContext(configContext));
78
+ });
79
+ }
80
+ ```
22
81
 
23
- Extension entry: `@agimon-ai/doompi-config/pi`
82
+ The snapshot is deeply frozen. It contains validated `settings`, active `harness` state, an optional
83
+ `pendingSelection`, and `requiresRelaunch`. Do not mutate it or retain it across provider or session
84
+ replacement. The next session publishes a new snapshot.
24
85
 
25
- ## Notes
86
+ ## Selection transitions
26
87
 
27
- This is the package that defines the config contract. Changing the shape of `.doom/*.yaml`
28
- starts here.
88
+ Major-mode, domain, and profile packages resolve their own requested changes, then use the shared
89
+ transition coordinator. Config owns the persisted selection and transition journal records used to
90
+ distinguish active state from pending intent.
29
91
 
30
- ## Entry points
92
+ Transitions are serialized and compared with the active composition fingerprint. A compatible
93
+ change can use Pi reload. A launcher-owned composition change records pending intent for process
94
+ relaunch. A fresh session acknowledges a pending transition only when its exact target selection and
95
+ composition are active.
96
+
97
+ Consumers must not edit `snapshot.harness`, bypass the coordinator, or cache launcher environment
98
+ state at module scope.
99
+
100
+ ## Public API
101
+
102
+ The root and focused `/config` and `/piContext` exports provide runtime parsing, merge policy, and
103
+ session integration:
104
+
105
+ ```ts
106
+ import { loadDoomConfig } from '@agimon-ai/doompi-config';
107
+
108
+ const settings = loadDoomConfig(process.cwd());
109
+ ```
110
+
111
+ The package also exposes lower-level loaders used by the fixed axis packages. User-facing guidance
112
+ for defining modes, domains, and profiles is intentionally owned by those packages instead of this
113
+ runtime Config authoring skill.
114
+
115
+ ## Help
116
+
117
+ The package publishes `src/prompts/doompi-author-config`, links it from `llms.txt`, and registers
118
+ `doompi-author-config` with the `doom/help` Cordis service. The guidance is visible to the AI only
119
+ while the parent Help minor mode is active.
120
+
121
+ ## Development
122
+
123
+ ```bash
124
+ pnpm build
125
+ pnpm typecheck
126
+ pnpm test
127
+ pnpm lint
128
+ ```
31
129
 
32
- 18 subpaths are declared in the `exports` field of `package.json`.
130
+ Maintained by [Agimon](https://agimon.ai/about).
33
131
 
34
132
  ## License
35
133
 
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:http"),i=require("node:net"),a=require("node:path");a=e.__toESM(a,1);let o=require("@agimon-ai/doompi-extension-contracts/mcp-projection");const s=new Set([`PLUGIN_ROOT`,`PLUGIN_DATA`]),c=new Set([`mcp-proxy`,`agiflow-proxy`]);function l(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function u(e,t,n){let r=Object.keys(e).find(e=>!t.includes(e));if(r)throw Error(`${n} contains unsupported field "${r}"`)}function d(e,t){if(typeof e!=`string`||e.length===0)throw Error(`${t} must be a non-empty string`);return e}function f(e,t){if(e!==void 0){if(!Array.isArray(e))throw Error(`${t} must be an array of strings`);return e.map((e,n)=>{if(typeof e!=`string`)throw Error(`${t}[${n}] must be a string`);return e})}}function p(e,t){if(e!==void 0){if(!l(e))throw Error(`${t} must be an object of string values`);return Object.fromEntries(Object.entries(e).map(([e,n])=>{if(typeof n!=`string`)throw Error(`${t}.${e} must be a string`);return[e,n]}))}}function m(e,t,n){return e.replace(/\$\{PLUGIN_(ROOT|DATA)\}/gu,e=>e==="${PLUGIN_ROOT}"?t:n)}function h(e,t){let n=a.default.relative(e,t);return n===``||!n.startsWith(`..${a.default.sep}`)&&n!==`..`&&!a.default.isAbsolute(n)}function g(e,t,r){let i;try{i=n.default.realpathSync(t)}catch{throw Error(`${r} does not exist`)}if(!h(e,i))throw Error(`${r} escapes its allowed plugin directory`);return i}function _(e,t){if(e.startsWith(`./`)){let r=g(t,a.default.resolve(t,e),`command`);if(!n.default.statSync(r).isFile())throw Error(`command must resolve to a regular file`);try{n.default.accessSync(r,n.default.constants.X_OK)}catch{throw Error(`command must resolve to an executable file`)}return r}if(e===`.`||e===`..`||/[\s/\\]/u.test(e))throw Error(`command must be a bare executable name or a ./ path inside the plugin`);return e}function v(e,t,r){if(e===void 0)return t;let i=d(e,`cwd`),o,s;if(i.startsWith(`./`))o=t,s=a.default.resolve(t,i);else if(i==="${PLUGIN_ROOT}"||i.startsWith("${PLUGIN_ROOT}/"))o=t,s=m(i,t,r);else if(i==="${PLUGIN_DATA}"||i.startsWith("${PLUGIN_DATA}/"))o=r,s=m(i,t,r);else throw Error("cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}");let c=g(o,a.default.resolve(s),`cwd`);if(!n.default.statSync(c).isDirectory())throw Error(`cwd must resolve to a directory`);return c}function y(e,t,n){let r=p(e,`env`)??{};for(let e of Object.keys(r)){let t=process.platform===`win32`?e.toUpperCase():e;if(s.has(t))throw Error(`env key "${e}" is reserved by Agent Plugins`)}return{...Object.fromEntries(Object.entries(r).map(([e,r])=>[e,m(r,t,n)])),PLUGIN_ROOT:t,PLUGIN_DATA:n}}function b(e){let t=e.startsWith(`[`)&&e.endsWith(`]`)?e.slice(1,-1):e;if(t.toLowerCase()===`localhost`)return!0;let n=(0,i.isIP)(t);return n===4?t.split(`.`)[0]===`127`:n===6&&t===`::1`}function x(e){let t=d(e,`url`),n;try{n=new URL(t)}catch{throw Error(`url must be an absolute HTTP(S) URL`)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw Error(`url must use http or https`);if(n.username||n.password)throw Error(`url must not contain user information`);if(n.hash)throw Error(`url must not contain a fragment`);if(n.protocol===`http:`&&!b(n.hostname))throw Error(`non-loopback MCP URLs must use https`);return t}function S(e){let t=p(e,`headers`);if(!t)return;let n=new Set;for(let[e,i]of Object.entries(t)){let t=e.toLowerCase();if(n.has(t))throw Error(`headers contain duplicate name "${e}"`);n.add(t);try{(0,r.validateHeaderName)(e),(0,r.validateHeaderValue)(e,i)}catch{throw Error(`header "${e}" is invalid`)}}return t}function C(e,t,n,r){if(!l(e))throw Error(`definition must be an object`);let i=d(e.type,`type`);if(i===`stdio`){u(e,[`type`,`command`,`args`,`env`,`cwd`],`server "${r}"`);let i=_(d(e.command,`command`),t),a=f(e.args,`args`)?.map(e=>m(e,t,n)),o=y(e.env,t,n),s=v(e.cwd,t,n),c={command:i,...a?{args:a}:{},env:o,cwd:s};return{internal:{name:r,transport:`stdio`,config:c},claude:{type:`stdio`,...c}}}if(i===`streamable-http`||i===`sse`){u(e,[`type`,`url`,`headers`],`server "${r}"`);let t=x(e.url),n=S(e.headers),a=i===`streamable-http`?`http`:`sse`,o={url:t,...n?{headers:n}:{}};return{internal:{name:r,transport:a,config:o},claude:{type:a,...o}}}throw Error(`unsupported transport type "${i}"`)}function w(e,n,r){let i=(0,t.createHash)(`sha256`).update(e.sourceId).update(`\0`).update(e.contentDigest).update(`\0`).update(n).digest(`hex`).slice(0,20);return a.default.join(r,`agent-plugin-mcp-${i}.internal.json`)}function T(e,r,i){n.default.mkdirSync(i,{mode:448,recursive:!0}),n.default.chmodSync(i,448);let a=`${e}.${process.pid}-${(0,t.randomUUID)()}.tmp`;try{n.default.writeFileSync(a,r,{flag:`wx`,mode:384});try{n.default.renameSync(a,e)}catch(t){let i=t.code;if(i!==`EEXIST`&&i!==`EPERM`)throw t;let a=n.default.lstatSync(e);if(a.isSymbolicLink()||!a.isFile()||n.default.readFileSync(e,`utf8`)!==r)throw Error(`content-addressed staging path is occupied by different content`)}n.default.chmodSync(e,384)}finally{n.default.rmSync(a,{force:!0})}}function E(e){return{claudeConfig:{mcpServers:{}},serverNames:[],droppedServers:[],diagnostics:[e]}}function D(e,r){let i=`Agent Plugin MCP source "${e.sourceId}"`;try{if(e.mcpSchemaUrl!==o.AGENT_PLUGIN_MCP_SCHEMA_URL)throw Error(`unsupported schema "${String(e.mcpSchemaUrl)}"`);if(!a.default.isAbsolute(e.pluginRoot)||!a.default.isAbsolute(e.pluginDataDirectory))throw Error(`pluginRoot and pluginDataDirectory must be absolute`);let s=n.default.realpathSync(e.pluginRoot);try{let t=n.default.lstatSync(e.pluginDataDirectory);if(t.isSymbolicLink()||!t.isDirectory())throw Error(`pluginDataDirectory must be a real directory`)}catch(t){if(t.code!==`ENOENT`)throw t;n.default.mkdirSync(e.pluginDataDirectory,{mode:448,recursive:!0})}n.default.chmodSync(e.pluginDataDirectory,448),n.default.accessSync(e.pluginDataDirectory,n.default.constants.W_OK);let d=n.default.realpathSync(e.pluginDataDirectory),f=g(s,e.configPath,`mcp.json`);if(a.default.dirname(f)!==s||a.default.basename(f)!==`mcp.json`)throw Error(`portable plugin config must be root mcp.json`);let p=n.default.readFileSync(f);if((0,t.createHash)(`sha256`).update(p).digest(`hex`)!==e.contentDigest)throw Error(`content digest does not match the projection`);let m=JSON.parse(p.toString(`utf8`));if(!l(m))throw Error(`document must be an object`);if(u(m,[`$schema`,`mcpServers`],i),m.$schema!==e.mcpSchemaUrl)throw Error(`$schema does not match the projected schema`);if(!l(m.mcpServers))throw Error(`mcpServers must be an object`);let h=r.allowedServers,_=!h||h.length===0,v=r.stripProxyWrappers??!0,y=Object.create(null),b=Object.create(null),x=[],S=[];for(let[e,t]of Object.entries(m.mcpServers)){if(!e){S.push(`${i} contains an empty server name; that entry was skipped.`);continue}if(!_&&!h.includes(e)||v&&c.has(e)){x.push(e);continue}try{let n=C(t,s,d,e);y[e]=n.internal,b[e]=n.claude}catch(t){x.push(e),S.push(`${i} skipped server "${e}": ${t instanceof Error?t.message:String(t)}`)}}let E=`${JSON.stringify({mcpServers:y},null,2)}\n`,D=w(e,E,r.stagingDirectory);return T(D,E,r.stagingDirectory),{configSource:{path:D,format:`internal`,cacheKey:(0,t.createHash)(`sha256`).update(`agent-plugin-v1\0`).update(e.sourceId).update(`\0`).update(e.contentDigest).update(`\0`).update(f).update(`\0`).update(E).digest(`hex`)},claudeConfig:{mcpServers:b},serverNames:Object.keys(y),droppedServers:x,diagnostics:S}}catch(e){return E(`${i} was disabled: ${e instanceof Error?e.message:String(e)}`)}}exports.normalizeAgentPluginMcpSource=D;
2
+ //# sourceMappingURL=agentPluginMcp.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.cjs","names":["path","fs","isIP","createHash","randomUUID","AGENT_PLUGIN_MCP_SCHEMA_URL"],"sources":["../../src/adapters/agentPluginMcp.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport { validateHeaderName, validateHeaderValue } from 'node:http';\nimport { isIP } from 'node:net';\nimport path from 'node:path';\nimport {\n AGENT_PLUGIN_MCP_SCHEMA_URL,\n type DoomMcpAgentPluginProjectionSource,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst RESERVED_ENVIRONMENT_KEYS = new Set(['PLUGIN_ROOT', 'PLUGIN_DATA']);\nconst PROXY_SERVER_ALIASES = new Set(['mcp-proxy', 'agiflow-proxy']);\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface AgentPluginMcpConfigSource {\n path: string;\n format: 'internal';\n cacheKey: string;\n}\n\nexport interface NormalizedAgentPluginMcpSource {\n /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */\n configSource?: AgentPluginMcpConfigSource;\n /** Claude-compatible definitions for an external merged config. */\n claudeConfig: { mcpServers: Record<string, JsonObject> };\n serverNames: string[];\n droppedServers: string[];\n diagnostics: string[];\n}\n\nexport interface NormalizeAgentPluginMcpOptions {\n stagingDirectory: string;\n /** Empty and absent lists both mean keep every server. */\n allowedServers?: readonly string[];\n /** Doom strips nested wrappers; external-only callers may opt out. */\n stripProxyWrappers?: boolean;\n}\n\ninterface NormalizedServer {\n internal: JsonObject;\n claude: JsonObject;\n}\n\nfunction isObject(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction onlyKeys(value: JsonObject, allowed: readonly string[], label: string): void {\n const unexpected = Object.keys(value).find((key) => !allowed.includes(key));\n if (unexpected) throw new Error(`${label} contains unsupported field \"${unexpected}\"`);\n}\n\nfunction nonEmptyString(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be a non-empty string`);\n return value;\n}\n\nfunction optionalStringArray(value: unknown, label: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) throw new Error(`${label} must be an array of strings`);\n return value.map((item, index) => {\n if (typeof item !== 'string') throw new Error(`${label}[${index}] must be a string`);\n return item;\n });\n}\n\nfunction optionalStringRecord(value: unknown, label: string): Record<string, string> | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`${label} must be an object of string values`);\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => {\n if (typeof item !== 'string') throw new Error(`${label}.${key} must be a string`);\n return [key, item];\n }),\n );\n}\n\nfunction expandPluginPlaceholders(value: string, pluginRoot: string, pluginDataDirectory: string): string {\n // The portable contract is a single, non-recursive replacement pass. An\n // unrecognised placeholder remains literal and mcp-proxy must not reinterpret it.\n return value.replace(/\\$\\{PLUGIN_(ROOT|DATA)\\}/gu, (placeholder) =>\n placeholder === '${PLUGIN_ROOT}' ? pluginRoot : pluginDataDirectory,\n );\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction realContainedPath(root: string, candidate: string, label: string): string {\n let realCandidate: string;\n try {\n realCandidate = fs.realpathSync(candidate);\n } catch {\n throw new Error(`${label} does not exist`);\n }\n if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);\n return realCandidate;\n}\n\nfunction normalizeCommand(command: string, pluginRoot: string): string {\n if (command.startsWith('./')) {\n const resolved = realContainedPath(pluginRoot, path.resolve(pluginRoot, command), 'command');\n if (!fs.statSync(resolved).isFile()) throw new Error('command must resolve to a regular file');\n try {\n fs.accessSync(resolved, fs.constants.X_OK);\n } catch {\n throw new Error('command must resolve to an executable file');\n }\n return resolved;\n }\n if (command === '.' || command === '..' || /[\\s/\\\\]/u.test(command)) {\n throw new Error('command must be a bare executable name or a ./ path inside the plugin');\n }\n return command;\n}\n\nfunction normalizeWorkingDirectory(declared: unknown, pluginRoot: string, pluginDataDirectory: string): string {\n if (declared === undefined) return pluginRoot;\n const cwd = nonEmptyString(declared, 'cwd');\n let base: string;\n let expanded: string;\n if (cwd.startsWith('./')) {\n base = pluginRoot;\n expanded = path.resolve(pluginRoot, cwd);\n } else if (cwd === '${PLUGIN_ROOT}' || cwd.startsWith('${PLUGIN_ROOT}/')) {\n base = pluginRoot;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else if (cwd === '${PLUGIN_DATA}' || cwd.startsWith('${PLUGIN_DATA}/')) {\n base = pluginDataDirectory;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else {\n throw new Error('cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}');\n }\n const resolved = realContainedPath(base, path.resolve(expanded), 'cwd');\n if (!fs.statSync(resolved).isDirectory()) throw new Error('cwd must resolve to a directory');\n return resolved;\n}\n\nfunction normalizeEnvironment(value: unknown, pluginRoot: string, pluginDataDirectory: string): Record<string, string> {\n const declared = optionalStringRecord(value, 'env') ?? {};\n for (const key of Object.keys(declared)) {\n const reservedKey = process.platform === 'win32' ? key.toUpperCase() : key;\n if (RESERVED_ENVIRONMENT_KEYS.has(reservedKey)) {\n throw new Error(`env key \"${key}\" is reserved by Agent Plugins`);\n }\n }\n return {\n ...Object.fromEntries(\n Object.entries(declared).map(([key, item]) => [\n key,\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n ]),\n ),\n PLUGIN_ROOT: pluginRoot,\n PLUGIN_DATA: pluginDataDirectory,\n };\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n const bareHostname = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n if (bareHostname.toLowerCase() === 'localhost') return true;\n const family = isIP(bareHostname);\n if (family === 4) return bareHostname.split('.')[0] === '127';\n return family === 6 && bareHostname === '::1';\n}\n\nfunction normalizeUrl(value: unknown): string {\n const raw = nonEmptyString(value, 'url');\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error('url must be an absolute HTTP(S) URL');\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error('url must use http or https');\n }\n if (parsed.username || parsed.password) throw new Error('url must not contain user information');\n if (parsed.hash) throw new Error('url must not contain a fragment');\n if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {\n throw new Error('non-loopback MCP URLs must use https');\n }\n return raw;\n}\n\nfunction normalizeHeaders(value: unknown): Record<string, string> | undefined {\n const headers = optionalStringRecord(value, 'headers');\n if (!headers) return undefined;\n const seen = new Set<string>();\n for (const [name, item] of Object.entries(headers)) {\n const canonical = name.toLowerCase();\n if (seen.has(canonical)) throw new Error(`headers contain duplicate name \"${name}\"`);\n seen.add(canonical);\n try {\n validateHeaderName(name);\n validateHeaderValue(name, item);\n } catch {\n throw new Error(`header \"${name}\" is invalid`);\n }\n }\n return headers;\n}\n\nfunction normalizeServer(\n raw: unknown,\n pluginRoot: string,\n pluginDataDirectory: string,\n name: string,\n): NormalizedServer {\n if (!isObject(raw)) throw new Error('definition must be an object');\n const type = nonEmptyString(raw.type, 'type');\n if (type === 'stdio') {\n onlyKeys(raw, ['type', 'command', 'args', 'env', 'cwd'], `server \"${name}\"`);\n const command = normalizeCommand(nonEmptyString(raw.command, 'command'), pluginRoot);\n const args = optionalStringArray(raw.args, 'args')?.map((item) =>\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n );\n const env = normalizeEnvironment(raw.env, pluginRoot, pluginDataDirectory);\n const cwd = normalizeWorkingDirectory(raw.cwd, pluginRoot, pluginDataDirectory);\n const config = { command, ...(args ? { args } : {}), env, cwd };\n return {\n internal: { name, transport: 'stdio', config },\n claude: { type: 'stdio', ...config },\n };\n }\n\n if (type === 'streamable-http' || type === 'sse') {\n onlyKeys(raw, ['type', 'url', 'headers'], `server \"${name}\"`);\n const url = normalizeUrl(raw.url);\n const headers = normalizeHeaders(raw.headers);\n const transport = type === 'streamable-http' ? 'http' : 'sse';\n const config = { url, ...(headers ? { headers } : {}) };\n return {\n internal: { name, transport, config },\n claude: { type: transport, ...config },\n };\n }\n\n throw new Error(`unsupported transport type \"${type}\"`);\n}\n\nfunction stagedInternalPath(\n source: DoomMcpAgentPluginProjectionSource,\n contents: string,\n stagingDirectory: string,\n): string {\n const digest = createHash('sha256')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(contents)\n .digest('hex')\n .slice(0, 20);\n return path.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);\n}\n\nfunction writeStagedInternalConfig(target: string, contents: string, stagingDirectory: string): void {\n fs.mkdirSync(stagingDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(stagingDirectory, PRIVATE_DIRECTORY_MODE);\n const temporaryPath = `${target}.${process.pid}-${randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporaryPath, contents, { flag: 'wx', mode: PRIVATE_FILE_MODE });\n try {\n fs.renameSync(temporaryPath, target);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'EEXIST' && code !== 'EPERM') throw error;\n const stat = fs.lstatSync(target);\n if (stat.isSymbolicLink() || !stat.isFile() || fs.readFileSync(target, 'utf8') !== contents) {\n throw new Error('content-addressed staging path is occupied by different content');\n }\n }\n fs.chmodSync(target, PRIVATE_FILE_MODE);\n } finally {\n fs.rmSync(temporaryPath, { force: true });\n }\n}\n\nfunction emptyResult(diagnostic: string): NormalizedAgentPluginMcpSource {\n return {\n claudeConfig: { mcpServers: {} },\n serverNames: [],\n droppedServers: [],\n diagnostics: [diagnostic],\n };\n}\n\n/**\n * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.\n *\n * Server validation is isolated: one malformed server is diagnosed and skipped.\n * Document/schema, source identity, or plugin-boundary failures reject the whole\n * source. Only the two portable plugin placeholders are expanded; the staged\n * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.\n */\nexport function normalizeAgentPluginMcpSource(\n source: DoomMcpAgentPluginProjectionSource,\n options: NormalizeAgentPluginMcpOptions,\n): NormalizedAgentPluginMcpSource {\n const label = `Agent Plugin MCP source \"${source.sourceId}\"`;\n try {\n if (source.mcpSchemaUrl !== AGENT_PLUGIN_MCP_SCHEMA_URL) {\n throw new Error(`unsupported schema \"${String(source.mcpSchemaUrl)}\"`);\n }\n if (!path.isAbsolute(source.pluginRoot) || !path.isAbsolute(source.pluginDataDirectory)) {\n throw new Error('pluginRoot and pluginDataDirectory must be absolute');\n }\n const pluginRoot = fs.realpathSync(source.pluginRoot);\n try {\n const dataStat = fs.lstatSync(source.pluginDataDirectory);\n if (dataStat.isSymbolicLink() || !dataStat.isDirectory()) {\n throw new Error('pluginDataDirectory must be a real directory');\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n fs.mkdirSync(source.pluginDataDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n }\n fs.chmodSync(source.pluginDataDirectory, PRIVATE_DIRECTORY_MODE);\n fs.accessSync(source.pluginDataDirectory, fs.constants.W_OK);\n const pluginDataDirectory = fs.realpathSync(source.pluginDataDirectory);\n const configPath = realContainedPath(pluginRoot, source.configPath, 'mcp.json');\n if (path.dirname(configPath) !== pluginRoot || path.basename(configPath) !== 'mcp.json') {\n throw new Error('portable plugin config must be root mcp.json');\n }\n\n const bytes = fs.readFileSync(configPath);\n const digest = createHash('sha256').update(bytes).digest('hex');\n if (digest !== source.contentDigest) throw new Error('content digest does not match the projection');\n const parsed: unknown = JSON.parse(bytes.toString('utf8'));\n if (!isObject(parsed)) throw new Error('document must be an object');\n onlyKeys(parsed, ['$schema', 'mcpServers'], label);\n if (parsed.$schema !== source.mcpSchemaUrl) throw new Error('$schema does not match the projected schema');\n if (!isObject(parsed.mcpServers)) throw new Error('mcpServers must be an object');\n\n const allowed = options.allowedServers;\n const keepAll = !allowed || allowed.length === 0;\n const stripProxyWrappers = options.stripProxyWrappers ?? true;\n const internalServers = Object.create(null) as Record<string, JsonObject>;\n const claudeServers = Object.create(null) as Record<string, JsonObject>;\n const droppedServers: string[] = [];\n const diagnostics: string[] = [];\n\n for (const [name, rawServer] of Object.entries(parsed.mcpServers)) {\n if (!name) {\n diagnostics.push(`${label} contains an empty server name; that entry was skipped.`);\n continue;\n }\n if ((!keepAll && !allowed.includes(name)) || (stripProxyWrappers && PROXY_SERVER_ALIASES.has(name))) {\n droppedServers.push(name);\n continue;\n }\n try {\n const normalized = normalizeServer(rawServer, pluginRoot, pluginDataDirectory, name);\n internalServers[name] = normalized.internal;\n claudeServers[name] = normalized.claude;\n } catch (error) {\n droppedServers.push(name);\n diagnostics.push(\n `${label} skipped server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const internalConfig = { mcpServers: internalServers };\n const contents = `${JSON.stringify(internalConfig, null, 2)}\\n`;\n const target = stagedInternalPath(source, contents, options.stagingDirectory);\n writeStagedInternalConfig(target, contents, options.stagingDirectory);\n const cacheKey = createHash('sha256')\n .update('agent-plugin-v1\\0')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(configPath)\n .update('\\0')\n .update(contents)\n .digest('hex');\n\n return {\n configSource: { path: target, format: 'internal', cacheKey },\n claudeConfig: { mcpServers: claudeServers },\n serverNames: Object.keys(internalServers),\n droppedServers,\n diagnostics,\n };\n } catch (error) {\n return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n"],"mappings":"uRAUA,MAEM,EAA4B,IAAI,IAAI,CAAC,cAAe,aAAa,CAAC,EAClE,EAAuB,IAAI,IAAI,CAAC,YAAa,eAAe,CAAC,EAiCnE,SAAS,EAAS,EAAqC,CACrD,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAS,EAAmB,EAA4B,EAAqB,CACpF,IAAM,EAAa,OAAO,KAAK,CAAK,CAAC,CAAC,KAAM,GAAQ,CAAC,EAAQ,SAAS,CAAG,CAAC,EAC1E,GAAI,EAAY,MAAU,MAAM,GAAG,EAAM,+BAA+B,EAAW,EAAE,CACvF,CAEA,SAAS,EAAe,EAAgB,EAAuB,CAC7D,GAAI,OAAO,GAAU,UAAY,EAAM,SAAW,EAAG,MAAU,MAAM,GAAG,EAAM,4BAA4B,EAC1G,OAAO,CACT,CAEA,SAAS,EAAoB,EAAgB,EAAqC,CAC5E,OAAU,IAAA,GACd,IAAI,CAAC,MAAM,QAAQ,CAAK,EAAG,MAAU,MAAM,GAAG,EAAM,6BAA6B,EACjF,OAAO,EAAM,KAAK,EAAM,IAAU,CAChC,GAAI,OAAO,GAAS,SAAU,MAAU,MAAM,GAAG,EAAM,GAAG,EAAM,mBAAmB,EACnF,OAAO,CACT,CAAC,CAJgF,CAKnF,CAEA,SAAS,EAAqB,EAAgB,EAAmD,CAC3F,OAAU,IAAA,GACd,IAAI,CAAC,EAAS,CAAK,EAAG,MAAU,MAAM,GAAG,EAAM,oCAAoC,EACnF,OAAO,OAAO,YACZ,OAAO,QAAQ,CAAK,CAAC,CAAC,KAAK,CAAC,EAAK,KAAU,CACzC,GAAI,OAAO,GAAS,SAAU,MAAU,MAAM,GAAG,EAAM,GAAG,EAAI,kBAAkB,EAChF,MAAO,CAAC,EAAK,CAAI,CACnB,CAAC,CACH,CANmF,CAOrF,CAEA,SAAS,EAAyB,EAAe,EAAoB,EAAqC,CAGxG,OAAO,EAAM,QAAQ,6BAA+B,GAClD,IAAgB,iBAAmB,EAAa,CAClD,CACF,CAEA,SAAS,EAAS,EAAc,EAA4B,CAC1D,IAAM,EAAWA,EAAAA,QAAK,SAAS,EAAM,CAAS,EAC9C,OAAO,IAAa,IAAO,CAAC,EAAS,WAAW,KAAKA,EAAAA,QAAK,KAAK,GAAK,IAAa,MAAQ,CAACA,EAAAA,QAAK,WAAW,CAAQ,CACpH,CAEA,SAAS,EAAkB,EAAc,EAAmB,EAAuB,CACjF,IAAI,EACJ,GAAI,CACF,EAAgBC,EAAAA,QAAG,aAAa,CAAS,CAC3C,MAAQ,CACN,MAAU,MAAM,GAAG,EAAM,gBAAgB,CAC3C,CACA,GAAI,CAAC,EAAS,EAAM,CAAa,EAAG,MAAU,MAAM,GAAG,EAAM,sCAAsC,EACnG,OAAO,CACT,CAEA,SAAS,EAAiB,EAAiB,EAA4B,CACrE,GAAI,EAAQ,WAAW,IAAI,EAAG,CAC5B,IAAM,EAAW,EAAkB,EAAYD,EAAAA,QAAK,QAAQ,EAAY,CAAO,EAAG,SAAS,EAC3F,GAAI,CAACC,EAAAA,QAAG,SAAS,CAAQ,CAAC,CAAC,OAAO,EAAG,MAAU,MAAM,wCAAwC,EAC7F,GAAI,CACF,EAAA,QAAG,WAAW,EAAUA,EAAAA,QAAG,UAAU,IAAI,CAC3C,MAAQ,CACN,MAAU,MAAM,4CAA4C,CAC9D,CACA,OAAO,CACT,CACA,GAAI,IAAY,KAAO,IAAY,MAAQ,WAAW,KAAK,CAAO,EAChE,MAAU,MAAM,uEAAuE,EAEzF,OAAO,CACT,CAEA,SAAS,EAA0B,EAAmB,EAAoB,EAAqC,CAC7G,GAAI,IAAa,IAAA,GAAW,OAAO,EACnC,IAAM,EAAM,EAAe,EAAU,KAAK,EACtC,EACA,EACJ,GAAI,EAAI,WAAW,IAAI,EACrB,EAAO,EACP,EAAWD,EAAAA,QAAK,QAAQ,EAAY,CAAG,OAClC,GAAI,IAAQ,kBAAoB,EAAI,WAAW,iBAAiB,EACrE,EAAO,EACP,EAAW,EAAyB,EAAK,EAAY,CAAmB,OACnE,GAAI,IAAQ,kBAAoB,EAAI,WAAW,iBAAiB,EACrE,EAAO,EACP,EAAW,EAAyB,EAAK,EAAY,CAAmB,OAExE,MAAU,MAAM,2DAA2D,EAE7E,IAAM,EAAW,EAAkB,EAAMA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,KAAK,EACtE,GAAI,CAACC,EAAAA,QAAG,SAAS,CAAQ,CAAC,CAAC,YAAY,EAAG,MAAU,MAAM,iCAAiC,EAC3F,OAAO,CACT,CAEA,SAAS,EAAqB,EAAgB,EAAoB,EAAqD,CACrH,IAAM,EAAW,EAAqB,EAAO,KAAK,GAAK,CAAC,EACxD,IAAK,IAAM,KAAO,OAAO,KAAK,CAAQ,EAAG,CACvC,IAAM,EAAc,QAAQ,WAAa,QAAU,EAAI,YAAY,EAAI,EACvE,GAAI,EAA0B,IAAI,CAAW,EAC3C,MAAU,MAAM,YAAY,EAAI,+BAA+B,CAEnE,CACA,MAAO,CACL,GAAG,OAAO,YACR,OAAO,QAAQ,CAAQ,CAAC,CAAC,KAAK,CAAC,EAAK,KAAU,CAC5C,EACA,EAAyB,EAAM,EAAY,CAAmB,CAChE,CAAC,CACH,EACA,YAAa,EACb,YAAa,CACf,CACF,CAEA,SAAS,EAAe,EAA2B,CACjD,IAAM,EAAe,EAAS,WAAW,GAAG,GAAK,EAAS,SAAS,GAAG,EAAI,EAAS,MAAM,EAAG,EAAE,EAAI,EAClG,GAAI,EAAa,YAAY,IAAM,YAAa,MAAO,GACvD,IAAM,GAAA,EAASC,EAAAA,KAAAA,CAAK,CAAY,EAEhC,OADI,IAAW,EAAU,EAAa,MAAM,GAAG,CAAC,CAAC,KAAO,MACjD,IAAW,GAAK,IAAiB,KAC1C,CAEA,SAAS,EAAa,EAAwB,CAC5C,IAAM,EAAM,EAAe,EAAO,KAAK,EACnC,EACJ,GAAI,CACF,EAAS,IAAI,IAAI,CAAG,CACtB,MAAQ,CACN,MAAU,MAAM,qCAAqC,CACvD,CACA,GAAI,EAAO,WAAa,SAAW,EAAO,WAAa,SACrD,MAAU,MAAM,4BAA4B,EAE9C,GAAI,EAAO,UAAY,EAAO,SAAU,MAAU,MAAM,uCAAuC,EAC/F,GAAI,EAAO,KAAM,MAAU,MAAM,iCAAiC,EAClE,GAAI,EAAO,WAAa,SAAW,CAAC,EAAe,EAAO,QAAQ,EAChE,MAAU,MAAM,sCAAsC,EAExD,OAAO,CACT,CAEA,SAAS,EAAiB,EAAoD,CAC5E,IAAM,EAAU,EAAqB,EAAO,SAAS,EACrD,GAAI,CAAC,EAAS,OACd,IAAM,EAAO,IAAI,IACjB,IAAK,GAAM,CAAC,EAAM,KAAS,OAAO,QAAQ,CAAO,EAAG,CAClD,IAAM,EAAY,EAAK,YAAY,EACnC,GAAI,EAAK,IAAI,CAAS,EAAG,MAAU,MAAM,mCAAmC,EAAK,EAAE,EACnF,EAAK,IAAI,CAAS,EAClB,GAAI,EACF,EAAA,EAAA,mBAAA,CAAmB,CAAI,GACvB,EAAA,EAAA,oBAAA,CAAoB,EAAM,CAAI,CAChC,MAAQ,CACN,MAAU,MAAM,WAAW,EAAK,aAAa,CAC/C,CACF,CACA,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACkB,CAClB,GAAI,CAAC,EAAS,CAAG,EAAG,MAAU,MAAM,8BAA8B,EAClE,IAAM,EAAO,EAAe,EAAI,KAAM,MAAM,EAC5C,GAAI,IAAS,QAAS,CACpB,EAAS,EAAK,CAAC,OAAQ,UAAW,OAAQ,MAAO,KAAK,EAAG,WAAW,EAAK,EAAE,EAC3E,IAAM,EAAU,EAAiB,EAAe,EAAI,QAAS,SAAS,EAAG,CAAU,EAC7E,EAAO,EAAoB,EAAI,KAAM,MAAM,CAAC,EAAE,IAAK,GACvD,EAAyB,EAAM,EAAY,CAAmB,CAChE,EACM,EAAM,EAAqB,EAAI,IAAK,EAAY,CAAmB,EACnE,EAAM,EAA0B,EAAI,IAAK,EAAY,CAAmB,EACxE,EAAS,CAAE,UAAS,GAAI,EAAO,CAAE,MAAK,EAAI,CAAC,EAAI,MAAK,KAAI,EAC9D,MAAO,CACL,SAAU,CAAE,OAAM,UAAW,QAAS,QAAO,EAC7C,OAAQ,CAAE,KAAM,QAAS,GAAG,CAAO,CACrC,CACF,CAEA,GAAI,IAAS,mBAAqB,IAAS,MAAO,CAChD,EAAS,EAAK,CAAC,OAAQ,MAAO,SAAS,EAAG,WAAW,EAAK,EAAE,EAC5D,IAAM,EAAM,EAAa,EAAI,GAAG,EAC1B,EAAU,EAAiB,EAAI,OAAO,EACtC,EAAY,IAAS,kBAAoB,OAAS,MAClD,EAAS,CAAE,MAAK,GAAI,EAAU,CAAE,SAAQ,EAAI,CAAC,CAAG,EACtD,MAAO,CACL,SAAU,CAAE,OAAM,YAAW,QAAO,EACpC,OAAQ,CAAE,KAAM,EAAW,GAAG,CAAO,CACvC,CACF,CAEA,MAAU,MAAM,+BAA+B,EAAK,EAAE,CACxD,CAEA,SAAS,EACP,EACA,EACA,EACQ,CACR,IAAM,GAAA,EAASC,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAChC,OAAO,EAAO,QAAQ,CAAC,CACvB,OAAO,IAAI,CAAC,CACZ,OAAO,EAAO,aAAa,CAAC,CAC5B,OAAO,IAAI,CAAC,CACZ,OAAO,CAAQ,CAAC,CAChB,OAAO,KAAK,CAAC,CACb,MAAM,EAAG,EAAE,EACd,OAAOH,EAAAA,QAAK,KAAK,EAAkB,oBAAoB,EAAO,eAAe,CAC/E,CAEA,SAAS,EAA0B,EAAgB,EAAkB,EAAgC,CACnG,EAAA,QAAG,UAAU,EAAkB,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EAChF,EAAA,QAAG,UAAU,EAAkB,GAAsB,EACrD,IAAM,EAAgB,GAAG,EAAO,GAAG,QAAQ,IAAI,IAAA,EAAGI,EAAAA,WAAAA,CAAW,EAAE,MAC/D,GAAI,CACF,EAAA,QAAG,cAAc,EAAe,EAAU,CAAE,KAAM,KAAM,KAAM,GAAkB,CAAC,EACjF,GAAI,CACF,EAAA,QAAG,WAAW,EAAe,CAAM,CACrC,OAAS,EAAO,CACd,IAAM,EAAQ,EAAgC,KAC9C,GAAI,IAAS,UAAY,IAAS,QAAS,MAAM,EACjD,IAAM,EAAOH,EAAAA,QAAG,UAAU,CAAM,EAChC,GAAI,EAAK,eAAe,GAAK,CAAC,EAAK,OAAO,GAAKA,EAAAA,QAAG,aAAa,EAAQ,MAAM,IAAM,EACjF,MAAU,MAAM,iEAAiE,CAErF,CACA,EAAA,QAAG,UAAU,EAAQ,GAAiB,CACxC,QAAU,CACR,EAAA,QAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,CAC1C,CACF,CAEA,SAAS,EAAY,EAAoD,CACvE,MAAO,CACL,aAAc,CAAE,WAAY,CAAC,CAAE,EAC/B,YAAa,CAAC,EACd,eAAgB,CAAC,EACjB,YAAa,CAAC,CAAU,CAC1B,CACF,CAUA,SAAgB,EACd,EACA,EACgC,CAChC,IAAM,EAAQ,4BAA4B,EAAO,SAAS,GAC1D,GAAI,CACF,GAAI,EAAO,eAAiBI,EAAAA,4BAC1B,MAAU,MAAM,uBAAuB,OAAO,EAAO,YAAY,EAAE,EAAE,EAEvE,GAAI,CAACL,EAAAA,QAAK,WAAW,EAAO,UAAU,GAAK,CAACA,EAAAA,QAAK,WAAW,EAAO,mBAAmB,EACpF,MAAU,MAAM,qDAAqD,EAEvE,IAAM,EAAaC,EAAAA,QAAG,aAAa,EAAO,UAAU,EACpD,GAAI,CACF,IAAM,EAAWA,EAAAA,QAAG,UAAU,EAAO,mBAAmB,EACxD,GAAI,EAAS,eAAe,GAAK,CAAC,EAAS,YAAY,EACrD,MAAU,MAAM,8CAA8C,CAElE,OAAS,EAAO,CACd,GAAK,EAAgC,OAAS,SAAU,MAAM,EAC9D,EAAA,QAAG,UAAU,EAAO,oBAAqB,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,CAC5F,CACA,EAAA,QAAG,UAAU,EAAO,oBAAqB,GAAsB,EAC/D,EAAA,QAAG,WAAW,EAAO,oBAAqBA,EAAAA,QAAG,UAAU,IAAI,EAC3D,IAAM,EAAsBA,EAAAA,QAAG,aAAa,EAAO,mBAAmB,EAChE,EAAa,EAAkB,EAAY,EAAO,WAAY,UAAU,EAC9E,GAAID,EAAAA,QAAK,QAAQ,CAAU,IAAM,GAAcA,EAAAA,QAAK,SAAS,CAAU,IAAM,WAC3E,MAAU,MAAM,8CAA8C,EAGhE,IAAM,EAAQC,EAAAA,QAAG,aAAa,CAAU,EAExC,IAAA,EADeE,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAK,CAAC,CAAC,OAAO,KAChD,IAAM,EAAO,cAAe,MAAU,MAAM,8CAA8C,EACnG,IAAM,EAAkB,KAAK,MAAM,EAAM,SAAS,MAAM,CAAC,EACzD,GAAI,CAAC,EAAS,CAAM,EAAG,MAAU,MAAM,4BAA4B,EAEnE,GADA,EAAS,EAAQ,CAAC,UAAW,YAAY,EAAG,CAAK,EAC7C,EAAO,UAAY,EAAO,aAAc,MAAU,MAAM,6CAA6C,EACzG,GAAI,CAAC,EAAS,EAAO,UAAU,EAAG,MAAU,MAAM,8BAA8B,EAEhF,IAAM,EAAU,EAAQ,eAClB,EAAU,CAAC,GAAW,EAAQ,SAAW,EACzC,EAAqB,EAAQ,oBAAsB,GACnD,EAAkB,OAAO,OAAO,IAAI,EACpC,EAAgB,OAAO,OAAO,IAAI,EAClC,EAA2B,CAAC,EAC5B,EAAwB,CAAC,EAE/B,IAAK,GAAM,CAAC,EAAM,KAAc,OAAO,QAAQ,EAAO,UAAU,EAAG,CACjE,GAAI,CAAC,EAAM,CACT,EAAY,KAAK,GAAG,EAAM,wDAAwD,EAClF,QACF,CACA,GAAK,CAAC,GAAW,CAAC,EAAQ,SAAS,CAAI,GAAO,GAAsB,EAAqB,IAAI,CAAI,EAAI,CACnG,EAAe,KAAK,CAAI,EACxB,QACF,CACA,GAAI,CACF,IAAM,EAAa,EAAgB,EAAW,EAAY,EAAqB,CAAI,EACnF,EAAgB,GAAQ,EAAW,SACnC,EAAc,GAAQ,EAAW,MACnC,OAAS,EAAO,CACd,EAAe,KAAK,CAAI,EACxB,EAAY,KACV,GAAG,EAAM,mBAAmB,EAAK,KAAK,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAC7F,CACF,CACF,CAGA,IAAM,EAAW,GAAG,KAAK,UAAU,CADV,WAAY,CACW,EAAG,KAAM,CAAC,EAAE,IACtD,EAAS,EAAmB,EAAQ,EAAU,EAAQ,gBAAgB,EAa5E,OAZA,EAA0B,EAAQ,EAAU,EAAQ,gBAAgB,EAY7D,CACL,aAAc,CAAE,KAAM,EAAQ,OAAQ,WAAY,UAAA,EAZnCA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAClC,OAAO,mBAAmB,CAAC,CAC3B,OAAO,EAAO,QAAQ,CAAC,CACvB,OAAO,IAAI,CAAC,CACZ,OAAO,EAAO,aAAa,CAAC,CAC5B,OAAO,IAAI,CAAC,CACZ,OAAO,CAAU,CAAC,CAClB,OAAO,IAAI,CAAC,CACZ,OAAO,CAAQ,CAAC,CAChB,OAAO,KAGiD,CAAE,EAC3D,aAAc,CAAE,WAAY,CAAc,EAC1C,YAAa,OAAO,KAAK,CAAe,EACxC,iBACA,aACF,CACF,OAAS,EAAO,CACd,OAAO,EAAY,GAAG,EAAM,iBAAiB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CACvG,CACF"}
@@ -0,0 +1,37 @@
1
+ import { DoomMcpAgentPluginProjectionSource } from "@agimon-ai/doompi-extension-contracts/mcp-projection";
2
+ //#region src/adapters/agentPluginMcp.d.ts
3
+ type JsonObject = Record<string, unknown>;
4
+ export interface AgentPluginMcpConfigSource {
5
+ path: string;
6
+ format: 'internal';
7
+ cacheKey: string;
8
+ }
9
+ export interface NormalizedAgentPluginMcpSource {
10
+ /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */
11
+ configSource?: AgentPluginMcpConfigSource;
12
+ /** Claude-compatible definitions for an external merged config. */
13
+ claudeConfig: {
14
+ mcpServers: Record<string, JsonObject>;
15
+ };
16
+ serverNames: string[];
17
+ droppedServers: string[];
18
+ diagnostics: string[];
19
+ }
20
+ export interface NormalizeAgentPluginMcpOptions {
21
+ stagingDirectory: string;
22
+ /** Empty and absent lists both mean keep every server. */
23
+ allowedServers?: readonly string[];
24
+ /** Doom strips nested wrappers; external-only callers may opt out. */
25
+ stripProxyWrappers?: boolean;
26
+ }
27
+ /**
28
+ * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.
29
+ *
30
+ * Server validation is isolated: one malformed server is diagnosed and skipped.
31
+ * Document/schema, source identity, or plugin-boundary failures reject the whole
32
+ * source. Only the two portable plugin placeholders are expanded; the staged
33
+ * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
34
+ */
35
+ export declare function normalizeAgentPluginMcpSource(source: DoomMcpAgentPluginProjectionSource, options: NormalizeAgentPluginMcpOptions): NormalizedAgentPluginMcpSource;
36
+ //#endregion
37
+ //# sourceMappingURL=agentPluginMcp.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.d.cts","names":[],"sources":["../../src/adapters/agentPluginMcp.ts"],"mappings":";;KAeK,aAAa;iBAED;EACf;EACA;EACA;;iBAGe;;EAEf,eAAe;;EAEf;IAAgB,YAAY,eAAe;;EAC3C;EACA;EACA;;iBAGe;EACf;;EAEA;;EAEA;;;;;;;;;;wBAuQc,8BACd,QAAQ,oCACR,SAAS,iCACR"}
@@ -0,0 +1,37 @@
1
+ import { DoomMcpAgentPluginProjectionSource } from "@agimon-ai/doompi-extension-contracts/mcp-projection";
2
+ //#region src/adapters/agentPluginMcp.d.ts
3
+ type JsonObject = Record<string, unknown>;
4
+ export interface AgentPluginMcpConfigSource {
5
+ path: string;
6
+ format: 'internal';
7
+ cacheKey: string;
8
+ }
9
+ export interface NormalizedAgentPluginMcpSource {
10
+ /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */
11
+ configSource?: AgentPluginMcpConfigSource;
12
+ /** Claude-compatible definitions for an external merged config. */
13
+ claudeConfig: {
14
+ mcpServers: Record<string, JsonObject>;
15
+ };
16
+ serverNames: string[];
17
+ droppedServers: string[];
18
+ diagnostics: string[];
19
+ }
20
+ export interface NormalizeAgentPluginMcpOptions {
21
+ stagingDirectory: string;
22
+ /** Empty and absent lists both mean keep every server. */
23
+ allowedServers?: readonly string[];
24
+ /** Doom strips nested wrappers; external-only callers may opt out. */
25
+ stripProxyWrappers?: boolean;
26
+ }
27
+ /**
28
+ * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.
29
+ *
30
+ * Server validation is isolated: one malformed server is diagnosed and skipped.
31
+ * Document/schema, source identity, or plugin-boundary failures reject the whole
32
+ * source. Only the two portable plugin placeholders are expanded; the staged
33
+ * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.
34
+ */
35
+ export declare function normalizeAgentPluginMcpSource(source: DoomMcpAgentPluginProjectionSource, options: NormalizeAgentPluginMcpOptions): NormalizedAgentPluginMcpSource;
36
+ //#endregion
37
+ //# sourceMappingURL=agentPluginMcp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.d.mts","names":[],"sources":["../../src/adapters/agentPluginMcp.ts"],"mappings":";;KAeK,aAAa;iBAED;EACf;EACA;EACA;;iBAGe;;EAEf,eAAe;;EAEf;IAAgB,YAAY,eAAe;;EAC3C;EACA;EACA;;iBAGe;EACf;;EAEA;;EAEA;;;;;;;;;;wBAuQc,8BACd,QAAQ,oCACR,SAAS,iCACR"}
@@ -0,0 +1,2 @@
1
+ import{createHash as e,randomUUID as t}from"node:crypto";import n from"node:fs";import{validateHeaderName as r,validateHeaderValue as i}from"node:http";import{isIP as a}from"node:net";import o from"node:path";import{AGENT_PLUGIN_MCP_SCHEMA_URL as s}from"@agimon-ai/doompi-extension-contracts/mcp-projection";const c=new Set([`PLUGIN_ROOT`,`PLUGIN_DATA`]),l=new Set([`mcp-proxy`,`agiflow-proxy`]);function u(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function d(e,t,n){let r=Object.keys(e).find(e=>!t.includes(e));if(r)throw Error(`${n} contains unsupported field "${r}"`)}function f(e,t){if(typeof e!=`string`||e.length===0)throw Error(`${t} must be a non-empty string`);return e}function p(e,t){if(e!==void 0){if(!Array.isArray(e))throw Error(`${t} must be an array of strings`);return e.map((e,n)=>{if(typeof e!=`string`)throw Error(`${t}[${n}] must be a string`);return e})}}function m(e,t){if(e!==void 0){if(!u(e))throw Error(`${t} must be an object of string values`);return Object.fromEntries(Object.entries(e).map(([e,n])=>{if(typeof n!=`string`)throw Error(`${t}.${e} must be a string`);return[e,n]}))}}function h(e,t,n){return e.replace(/\$\{PLUGIN_(ROOT|DATA)\}/gu,e=>e==="${PLUGIN_ROOT}"?t:n)}function g(e,t){let n=o.relative(e,t);return n===``||!n.startsWith(`..${o.sep}`)&&n!==`..`&&!o.isAbsolute(n)}function _(e,t,r){let i;try{i=n.realpathSync(t)}catch{throw Error(`${r} does not exist`)}if(!g(e,i))throw Error(`${r} escapes its allowed plugin directory`);return i}function v(e,t){if(e.startsWith(`./`)){let r=_(t,o.resolve(t,e),`command`);if(!n.statSync(r).isFile())throw Error(`command must resolve to a regular file`);try{n.accessSync(r,n.constants.X_OK)}catch{throw Error(`command must resolve to an executable file`)}return r}if(e===`.`||e===`..`||/[\s/\\]/u.test(e))throw Error(`command must be a bare executable name or a ./ path inside the plugin`);return e}function y(e,t,r){if(e===void 0)return t;let i=f(e,`cwd`),a,s;if(i.startsWith(`./`))a=t,s=o.resolve(t,i);else if(i==="${PLUGIN_ROOT}"||i.startsWith("${PLUGIN_ROOT}/"))a=t,s=h(i,t,r);else if(i==="${PLUGIN_DATA}"||i.startsWith("${PLUGIN_DATA}/"))a=r,s=h(i,t,r);else throw Error("cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}");let c=_(a,o.resolve(s),`cwd`);if(!n.statSync(c).isDirectory())throw Error(`cwd must resolve to a directory`);return c}function b(e,t,n){let r=m(e,`env`)??{};for(let e of Object.keys(r)){let t=process.platform===`win32`?e.toUpperCase():e;if(c.has(t))throw Error(`env key "${e}" is reserved by Agent Plugins`)}return{...Object.fromEntries(Object.entries(r).map(([e,r])=>[e,h(r,t,n)])),PLUGIN_ROOT:t,PLUGIN_DATA:n}}function x(e){let t=e.startsWith(`[`)&&e.endsWith(`]`)?e.slice(1,-1):e;if(t.toLowerCase()===`localhost`)return!0;let n=a(t);return n===4?t.split(`.`)[0]===`127`:n===6&&t===`::1`}function S(e){let t=f(e,`url`),n;try{n=new URL(t)}catch{throw Error(`url must be an absolute HTTP(S) URL`)}if(n.protocol!==`http:`&&n.protocol!==`https:`)throw Error(`url must use http or https`);if(n.username||n.password)throw Error(`url must not contain user information`);if(n.hash)throw Error(`url must not contain a fragment`);if(n.protocol===`http:`&&!x(n.hostname))throw Error(`non-loopback MCP URLs must use https`);return t}function C(e){let t=m(e,`headers`);if(!t)return;let n=new Set;for(let[e,a]of Object.entries(t)){let t=e.toLowerCase();if(n.has(t))throw Error(`headers contain duplicate name "${e}"`);n.add(t);try{r(e),i(e,a)}catch{throw Error(`header "${e}" is invalid`)}}return t}function w(e,t,n,r){if(!u(e))throw Error(`definition must be an object`);let i=f(e.type,`type`);if(i===`stdio`){d(e,[`type`,`command`,`args`,`env`,`cwd`],`server "${r}"`);let i=v(f(e.command,`command`),t),a=p(e.args,`args`)?.map(e=>h(e,t,n)),o=b(e.env,t,n),s=y(e.cwd,t,n),c={command:i,...a?{args:a}:{},env:o,cwd:s};return{internal:{name:r,transport:`stdio`,config:c},claude:{type:`stdio`,...c}}}if(i===`streamable-http`||i===`sse`){d(e,[`type`,`url`,`headers`],`server "${r}"`);let t=S(e.url),n=C(e.headers),a=i===`streamable-http`?`http`:`sse`,o={url:t,...n?{headers:n}:{}};return{internal:{name:r,transport:a,config:o},claude:{type:a,...o}}}throw Error(`unsupported transport type "${i}"`)}function T(t,n,r){let i=e(`sha256`).update(t.sourceId).update(`\0`).update(t.contentDigest).update(`\0`).update(n).digest(`hex`).slice(0,20);return o.join(r,`agent-plugin-mcp-${i}.internal.json`)}function E(e,r,i){n.mkdirSync(i,{mode:448,recursive:!0}),n.chmodSync(i,448);let a=`${e}.${process.pid}-${t()}.tmp`;try{n.writeFileSync(a,r,{flag:`wx`,mode:384});try{n.renameSync(a,e)}catch(t){let i=t.code;if(i!==`EEXIST`&&i!==`EPERM`)throw t;let a=n.lstatSync(e);if(a.isSymbolicLink()||!a.isFile()||n.readFileSync(e,`utf8`)!==r)throw Error(`content-addressed staging path is occupied by different content`)}n.chmodSync(e,384)}finally{n.rmSync(a,{force:!0})}}function D(e){return{claudeConfig:{mcpServers:{}},serverNames:[],droppedServers:[],diagnostics:[e]}}function O(t,r){let i=`Agent Plugin MCP source "${t.sourceId}"`;try{if(t.mcpSchemaUrl!==s)throw Error(`unsupported schema "${String(t.mcpSchemaUrl)}"`);if(!o.isAbsolute(t.pluginRoot)||!o.isAbsolute(t.pluginDataDirectory))throw Error(`pluginRoot and pluginDataDirectory must be absolute`);let a=n.realpathSync(t.pluginRoot);try{let e=n.lstatSync(t.pluginDataDirectory);if(e.isSymbolicLink()||!e.isDirectory())throw Error(`pluginDataDirectory must be a real directory`)}catch(e){if(e.code!==`ENOENT`)throw e;n.mkdirSync(t.pluginDataDirectory,{mode:448,recursive:!0})}n.chmodSync(t.pluginDataDirectory,448),n.accessSync(t.pluginDataDirectory,n.constants.W_OK);let c=n.realpathSync(t.pluginDataDirectory),f=_(a,t.configPath,`mcp.json`);if(o.dirname(f)!==a||o.basename(f)!==`mcp.json`)throw Error(`portable plugin config must be root mcp.json`);let p=n.readFileSync(f);if(e(`sha256`).update(p).digest(`hex`)!==t.contentDigest)throw Error(`content digest does not match the projection`);let m=JSON.parse(p.toString(`utf8`));if(!u(m))throw Error(`document must be an object`);if(d(m,[`$schema`,`mcpServers`],i),m.$schema!==t.mcpSchemaUrl)throw Error(`$schema does not match the projected schema`);if(!u(m.mcpServers))throw Error(`mcpServers must be an object`);let h=r.allowedServers,g=!h||h.length===0,v=r.stripProxyWrappers??!0,y=Object.create(null),b=Object.create(null),x=[],S=[];for(let[e,t]of Object.entries(m.mcpServers)){if(!e){S.push(`${i} contains an empty server name; that entry was skipped.`);continue}if(!g&&!h.includes(e)||v&&l.has(e)){x.push(e);continue}try{let n=w(t,a,c,e);y[e]=n.internal,b[e]=n.claude}catch(t){x.push(e),S.push(`${i} skipped server "${e}": ${t instanceof Error?t.message:String(t)}`)}}let C=`${JSON.stringify({mcpServers:y},null,2)}\n`,D=T(t,C,r.stagingDirectory);return E(D,C,r.stagingDirectory),{configSource:{path:D,format:`internal`,cacheKey:e(`sha256`).update(`agent-plugin-v1\0`).update(t.sourceId).update(`\0`).update(t.contentDigest).update(`\0`).update(f).update(`\0`).update(C).digest(`hex`)},claudeConfig:{mcpServers:b},serverNames:Object.keys(y),droppedServers:x,diagnostics:S}}catch(e){return D(`${i} was disabled: ${e instanceof Error?e.message:String(e)}`)}}export{O as normalizeAgentPluginMcpSource};
2
+ //# sourceMappingURL=agentPluginMcp.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agentPluginMcp.mjs","names":[],"sources":["../../src/adapters/agentPluginMcp.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport { validateHeaderName, validateHeaderValue } from 'node:http';\nimport { isIP } from 'node:net';\nimport path from 'node:path';\nimport {\n AGENT_PLUGIN_MCP_SCHEMA_URL,\n type DoomMcpAgentPluginProjectionSource,\n} from '@agimon-ai/doompi-extension-contracts/mcp-projection';\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst RESERVED_ENVIRONMENT_KEYS = new Set(['PLUGIN_ROOT', 'PLUGIN_DATA']);\nconst PROXY_SERVER_ALIASES = new Set(['mcp-proxy', 'agiflow-proxy']);\n\ntype JsonObject = Record<string, unknown>;\n\nexport interface AgentPluginMcpConfigSource {\n path: string;\n format: 'internal';\n cacheKey: string;\n}\n\nexport interface NormalizedAgentPluginMcpSource {\n /** Owner-only, content-addressed layer that bypasses Claude env interpolation. */\n configSource?: AgentPluginMcpConfigSource;\n /** Claude-compatible definitions for an external merged config. */\n claudeConfig: { mcpServers: Record<string, JsonObject> };\n serverNames: string[];\n droppedServers: string[];\n diagnostics: string[];\n}\n\nexport interface NormalizeAgentPluginMcpOptions {\n stagingDirectory: string;\n /** Empty and absent lists both mean keep every server. */\n allowedServers?: readonly string[];\n /** Doom strips nested wrappers; external-only callers may opt out. */\n stripProxyWrappers?: boolean;\n}\n\ninterface NormalizedServer {\n internal: JsonObject;\n claude: JsonObject;\n}\n\nfunction isObject(value: unknown): value is JsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction onlyKeys(value: JsonObject, allowed: readonly string[], label: string): void {\n const unexpected = Object.keys(value).find((key) => !allowed.includes(key));\n if (unexpected) throw new Error(`${label} contains unsupported field \"${unexpected}\"`);\n}\n\nfunction nonEmptyString(value: unknown, label: string): string {\n if (typeof value !== 'string' || value.length === 0) throw new Error(`${label} must be a non-empty string`);\n return value;\n}\n\nfunction optionalStringArray(value: unknown, label: string): string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) throw new Error(`${label} must be an array of strings`);\n return value.map((item, index) => {\n if (typeof item !== 'string') throw new Error(`${label}[${index}] must be a string`);\n return item;\n });\n}\n\nfunction optionalStringRecord(value: unknown, label: string): Record<string, string> | undefined {\n if (value === undefined) return undefined;\n if (!isObject(value)) throw new Error(`${label} must be an object of string values`);\n return Object.fromEntries(\n Object.entries(value).map(([key, item]) => {\n if (typeof item !== 'string') throw new Error(`${label}.${key} must be a string`);\n return [key, item];\n }),\n );\n}\n\nfunction expandPluginPlaceholders(value: string, pluginRoot: string, pluginDataDirectory: string): string {\n // The portable contract is a single, non-recursive replacement pass. An\n // unrecognised placeholder remains literal and mcp-proxy must not reinterpret it.\n return value.replace(/\\$\\{PLUGIN_(ROOT|DATA)\\}/gu, (placeholder) =>\n placeholder === '${PLUGIN_ROOT}' ? pluginRoot : pluginDataDirectory,\n );\n}\n\nfunction isInside(root: string, candidate: string): boolean {\n const relative = path.relative(root, candidate);\n return relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative));\n}\n\nfunction realContainedPath(root: string, candidate: string, label: string): string {\n let realCandidate: string;\n try {\n realCandidate = fs.realpathSync(candidate);\n } catch {\n throw new Error(`${label} does not exist`);\n }\n if (!isInside(root, realCandidate)) throw new Error(`${label} escapes its allowed plugin directory`);\n return realCandidate;\n}\n\nfunction normalizeCommand(command: string, pluginRoot: string): string {\n if (command.startsWith('./')) {\n const resolved = realContainedPath(pluginRoot, path.resolve(pluginRoot, command), 'command');\n if (!fs.statSync(resolved).isFile()) throw new Error('command must resolve to a regular file');\n try {\n fs.accessSync(resolved, fs.constants.X_OK);\n } catch {\n throw new Error('command must resolve to an executable file');\n }\n return resolved;\n }\n if (command === '.' || command === '..' || /[\\s/\\\\]/u.test(command)) {\n throw new Error('command must be a bare executable name or a ./ path inside the plugin');\n }\n return command;\n}\n\nfunction normalizeWorkingDirectory(declared: unknown, pluginRoot: string, pluginDataDirectory: string): string {\n if (declared === undefined) return pluginRoot;\n const cwd = nonEmptyString(declared, 'cwd');\n let base: string;\n let expanded: string;\n if (cwd.startsWith('./')) {\n base = pluginRoot;\n expanded = path.resolve(pluginRoot, cwd);\n } else if (cwd === '${PLUGIN_ROOT}' || cwd.startsWith('${PLUGIN_ROOT}/')) {\n base = pluginRoot;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else if (cwd === '${PLUGIN_DATA}' || cwd.startsWith('${PLUGIN_DATA}/')) {\n base = pluginDataDirectory;\n expanded = expandPluginPlaceholders(cwd, pluginRoot, pluginDataDirectory);\n } else {\n throw new Error('cwd must start with ./, ${PLUGIN_ROOT}, or ${PLUGIN_DATA}');\n }\n const resolved = realContainedPath(base, path.resolve(expanded), 'cwd');\n if (!fs.statSync(resolved).isDirectory()) throw new Error('cwd must resolve to a directory');\n return resolved;\n}\n\nfunction normalizeEnvironment(value: unknown, pluginRoot: string, pluginDataDirectory: string): Record<string, string> {\n const declared = optionalStringRecord(value, 'env') ?? {};\n for (const key of Object.keys(declared)) {\n const reservedKey = process.platform === 'win32' ? key.toUpperCase() : key;\n if (RESERVED_ENVIRONMENT_KEYS.has(reservedKey)) {\n throw new Error(`env key \"${key}\" is reserved by Agent Plugins`);\n }\n }\n return {\n ...Object.fromEntries(\n Object.entries(declared).map(([key, item]) => [\n key,\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n ]),\n ),\n PLUGIN_ROOT: pluginRoot,\n PLUGIN_DATA: pluginDataDirectory,\n };\n}\n\nfunction isLoopbackHost(hostname: string): boolean {\n const bareHostname = hostname.startsWith('[') && hostname.endsWith(']') ? hostname.slice(1, -1) : hostname;\n if (bareHostname.toLowerCase() === 'localhost') return true;\n const family = isIP(bareHostname);\n if (family === 4) return bareHostname.split('.')[0] === '127';\n return family === 6 && bareHostname === '::1';\n}\n\nfunction normalizeUrl(value: unknown): string {\n const raw = nonEmptyString(value, 'url');\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n throw new Error('url must be an absolute HTTP(S) URL');\n }\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n throw new Error('url must use http or https');\n }\n if (parsed.username || parsed.password) throw new Error('url must not contain user information');\n if (parsed.hash) throw new Error('url must not contain a fragment');\n if (parsed.protocol === 'http:' && !isLoopbackHost(parsed.hostname)) {\n throw new Error('non-loopback MCP URLs must use https');\n }\n return raw;\n}\n\nfunction normalizeHeaders(value: unknown): Record<string, string> | undefined {\n const headers = optionalStringRecord(value, 'headers');\n if (!headers) return undefined;\n const seen = new Set<string>();\n for (const [name, item] of Object.entries(headers)) {\n const canonical = name.toLowerCase();\n if (seen.has(canonical)) throw new Error(`headers contain duplicate name \"${name}\"`);\n seen.add(canonical);\n try {\n validateHeaderName(name);\n validateHeaderValue(name, item);\n } catch {\n throw new Error(`header \"${name}\" is invalid`);\n }\n }\n return headers;\n}\n\nfunction normalizeServer(\n raw: unknown,\n pluginRoot: string,\n pluginDataDirectory: string,\n name: string,\n): NormalizedServer {\n if (!isObject(raw)) throw new Error('definition must be an object');\n const type = nonEmptyString(raw.type, 'type');\n if (type === 'stdio') {\n onlyKeys(raw, ['type', 'command', 'args', 'env', 'cwd'], `server \"${name}\"`);\n const command = normalizeCommand(nonEmptyString(raw.command, 'command'), pluginRoot);\n const args = optionalStringArray(raw.args, 'args')?.map((item) =>\n expandPluginPlaceholders(item, pluginRoot, pluginDataDirectory),\n );\n const env = normalizeEnvironment(raw.env, pluginRoot, pluginDataDirectory);\n const cwd = normalizeWorkingDirectory(raw.cwd, pluginRoot, pluginDataDirectory);\n const config = { command, ...(args ? { args } : {}), env, cwd };\n return {\n internal: { name, transport: 'stdio', config },\n claude: { type: 'stdio', ...config },\n };\n }\n\n if (type === 'streamable-http' || type === 'sse') {\n onlyKeys(raw, ['type', 'url', 'headers'], `server \"${name}\"`);\n const url = normalizeUrl(raw.url);\n const headers = normalizeHeaders(raw.headers);\n const transport = type === 'streamable-http' ? 'http' : 'sse';\n const config = { url, ...(headers ? { headers } : {}) };\n return {\n internal: { name, transport, config },\n claude: { type: transport, ...config },\n };\n }\n\n throw new Error(`unsupported transport type \"${type}\"`);\n}\n\nfunction stagedInternalPath(\n source: DoomMcpAgentPluginProjectionSource,\n contents: string,\n stagingDirectory: string,\n): string {\n const digest = createHash('sha256')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(contents)\n .digest('hex')\n .slice(0, 20);\n return path.join(stagingDirectory, `agent-plugin-mcp-${digest}.internal.json`);\n}\n\nfunction writeStagedInternalConfig(target: string, contents: string, stagingDirectory: string): void {\n fs.mkdirSync(stagingDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n fs.chmodSync(stagingDirectory, PRIVATE_DIRECTORY_MODE);\n const temporaryPath = `${target}.${process.pid}-${randomUUID()}.tmp`;\n try {\n fs.writeFileSync(temporaryPath, contents, { flag: 'wx', mode: PRIVATE_FILE_MODE });\n try {\n fs.renameSync(temporaryPath, target);\n } catch (error) {\n const code = (error as NodeJS.ErrnoException).code;\n if (code !== 'EEXIST' && code !== 'EPERM') throw error;\n const stat = fs.lstatSync(target);\n if (stat.isSymbolicLink() || !stat.isFile() || fs.readFileSync(target, 'utf8') !== contents) {\n throw new Error('content-addressed staging path is occupied by different content');\n }\n }\n fs.chmodSync(target, PRIVATE_FILE_MODE);\n } finally {\n fs.rmSync(temporaryPath, { force: true });\n }\n}\n\nfunction emptyResult(diagnostic: string): NormalizedAgentPluginMcpSource {\n return {\n claudeConfig: { mcpServers: {} },\n serverNames: [],\n droppedServers: [],\n diagnostics: [diagnostic],\n };\n}\n\n/**\n * Validates one Agent Plugins v1 `mcp.json` and stages a native mcp-proxy layer.\n *\n * Server validation is isolated: one malformed server is diagnosed and skipped.\n * Document/schema, source identity, or plugin-boundary failures reject the whole\n * source. Only the two portable plugin placeholders are expanded; the staged\n * layer is tagged `internal`, so legacy `${ENV}` interpolation cannot run again.\n */\nexport function normalizeAgentPluginMcpSource(\n source: DoomMcpAgentPluginProjectionSource,\n options: NormalizeAgentPluginMcpOptions,\n): NormalizedAgentPluginMcpSource {\n const label = `Agent Plugin MCP source \"${source.sourceId}\"`;\n try {\n if (source.mcpSchemaUrl !== AGENT_PLUGIN_MCP_SCHEMA_URL) {\n throw new Error(`unsupported schema \"${String(source.mcpSchemaUrl)}\"`);\n }\n if (!path.isAbsolute(source.pluginRoot) || !path.isAbsolute(source.pluginDataDirectory)) {\n throw new Error('pluginRoot and pluginDataDirectory must be absolute');\n }\n const pluginRoot = fs.realpathSync(source.pluginRoot);\n try {\n const dataStat = fs.lstatSync(source.pluginDataDirectory);\n if (dataStat.isSymbolicLink() || !dataStat.isDirectory()) {\n throw new Error('pluginDataDirectory must be a real directory');\n }\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;\n fs.mkdirSync(source.pluginDataDirectory, { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n }\n fs.chmodSync(source.pluginDataDirectory, PRIVATE_DIRECTORY_MODE);\n fs.accessSync(source.pluginDataDirectory, fs.constants.W_OK);\n const pluginDataDirectory = fs.realpathSync(source.pluginDataDirectory);\n const configPath = realContainedPath(pluginRoot, source.configPath, 'mcp.json');\n if (path.dirname(configPath) !== pluginRoot || path.basename(configPath) !== 'mcp.json') {\n throw new Error('portable plugin config must be root mcp.json');\n }\n\n const bytes = fs.readFileSync(configPath);\n const digest = createHash('sha256').update(bytes).digest('hex');\n if (digest !== source.contentDigest) throw new Error('content digest does not match the projection');\n const parsed: unknown = JSON.parse(bytes.toString('utf8'));\n if (!isObject(parsed)) throw new Error('document must be an object');\n onlyKeys(parsed, ['$schema', 'mcpServers'], label);\n if (parsed.$schema !== source.mcpSchemaUrl) throw new Error('$schema does not match the projected schema');\n if (!isObject(parsed.mcpServers)) throw new Error('mcpServers must be an object');\n\n const allowed = options.allowedServers;\n const keepAll = !allowed || allowed.length === 0;\n const stripProxyWrappers = options.stripProxyWrappers ?? true;\n const internalServers = Object.create(null) as Record<string, JsonObject>;\n const claudeServers = Object.create(null) as Record<string, JsonObject>;\n const droppedServers: string[] = [];\n const diagnostics: string[] = [];\n\n for (const [name, rawServer] of Object.entries(parsed.mcpServers)) {\n if (!name) {\n diagnostics.push(`${label} contains an empty server name; that entry was skipped.`);\n continue;\n }\n if ((!keepAll && !allowed.includes(name)) || (stripProxyWrappers && PROXY_SERVER_ALIASES.has(name))) {\n droppedServers.push(name);\n continue;\n }\n try {\n const normalized = normalizeServer(rawServer, pluginRoot, pluginDataDirectory, name);\n internalServers[name] = normalized.internal;\n claudeServers[name] = normalized.claude;\n } catch (error) {\n droppedServers.push(name);\n diagnostics.push(\n `${label} skipped server \"${name}\": ${error instanceof Error ? error.message : String(error)}`,\n );\n }\n }\n\n const internalConfig = { mcpServers: internalServers };\n const contents = `${JSON.stringify(internalConfig, null, 2)}\\n`;\n const target = stagedInternalPath(source, contents, options.stagingDirectory);\n writeStagedInternalConfig(target, contents, options.stagingDirectory);\n const cacheKey = createHash('sha256')\n .update('agent-plugin-v1\\0')\n .update(source.sourceId)\n .update('\\0')\n .update(source.contentDigest)\n .update('\\0')\n .update(configPath)\n .update('\\0')\n .update(contents)\n .digest('hex');\n\n return {\n configSource: { path: target, format: 'internal', cacheKey },\n claudeConfig: { mcpServers: claudeServers },\n serverNames: Object.keys(internalServers),\n droppedServers,\n diagnostics,\n };\n } catch (error) {\n return emptyResult(`${label} was disabled: ${error instanceof Error ? error.message : String(error)}`);\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs");let t=require("node:crypto"),n=require("node:fs");n=e.__toESM(n,1);let r=require("node:path");r=e.__toESM(r,1);function i(e,i){n.default.mkdirSync(r.default.dirname(e),{mode:448,recursive:!0});let a=`${e}.${process.pid}.${(0,t.randomUUID)()}.tmp`;try{n.default.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),n.default.renameSync(a,e)}catch(e){throw n.default.rmSync(a,{force:!0}),e}}exports.writePrivateAtomicJson=i;
2
+ //# sourceMappingURL=atomicJson.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.cjs","names":["path","randomUUID"],"sources":["../../src/adapters/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"oKA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAA,QAAG,UAAUA,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,QAC/D,GAAI,CACF,EAAA,QAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAA,QAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAA,QAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +1,5 @@
1
+ //#region src/adapters/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ export declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ //# sourceMappingURL=atomicJson.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.cts","names":[],"sources":["../../src/adapters/atomicJson.ts"],"mappings":";;wBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,5 @@
1
+ //#region src/adapters/atomicJson.d.ts
2
+ /** Writes JSON readable by its owner only, replacing the target atomically. */
3
+ export declare function writePrivateAtomicJson(filePath: string, value: unknown): void;
4
+ //#endregion
5
+ //# sourceMappingURL=atomicJson.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.d.mts","names":[],"sources":["../../src/adapters/atomicJson.ts"],"mappings":";;wBA4BgB,uBAAuB,kBAAkB"}
@@ -0,0 +1,2 @@
1
+ import{randomUUID as e}from"node:crypto";import t from"node:fs";import n from"node:path";function r(r,i){t.mkdirSync(n.dirname(r),{mode:448,recursive:!0});let a=`${r}.${process.pid}.${e()}.tmp`;try{t.writeFileSync(a,`${JSON.stringify(i,null,2)}\n`,{mode:384}),t.renameSync(a,r)}catch(e){throw t.rmSync(a,{force:!0}),e}}export{r as writePrivateAtomicJson};
2
+ //# sourceMappingURL=atomicJson.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"atomicJson.mjs","names":[],"sources":["../../src/adapters/atomicJson.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\n/**\n * Private atomic JSON writes.\n *\n * Write to a uniquely named temporary file in the destination directory, then\n * rename onto the target. Rename is atomic within a filesystem, so a reader\n * sees either the previous file or the complete new one and never a partial\n * write. The temporary name carries the pid and a random suffix, so two\n * processes writing the same target cannot collide on it.\n *\n * This prevents torn reads. It does NOT prevent lost updates: two writers doing\n * read-modify-write still clobber each other, because the rename only publishes\n * what the caller already computed. Harness state avoids that by giving every\n * process a file it owns rather than by locking.\n *\n * AVOID:\n * - Treating this as a lock\n * - Leaving the temporary file behind when the write fails\n */\n\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\nconst TEMPORARY_SUFFIX = '.tmp';\n\n/** Writes JSON readable by its owner only, replacing the target atomically. */\nexport function writePrivateAtomicJson(filePath: string, value: unknown): void {\n fs.mkdirSync(path.dirname(filePath), { mode: PRIVATE_DIRECTORY_MODE, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}${TEMPORARY_SUFFIX}`;\n try {\n fs.writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}\\n`, { mode: PRIVATE_FILE_MODE });\n fs.renameSync(temporaryPath, filePath);\n } catch (error) {\n fs.rmSync(temporaryPath, { force: true });\n throw error;\n }\n}\n"],"mappings":"yFA4BA,SAAgB,EAAuB,EAAkB,EAAsB,CAC7E,EAAG,UAAU,EAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,IAAwB,UAAW,EAAK,CAAC,EACtF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,GAAG,EAAW,QAC/D,GAAI,CACF,EAAG,cAAc,EAAe,GAAG,KAAK,UAAU,EAAO,KAAM,CAAC,EAAE,IAAK,CAAE,KAAM,GAAkB,CAAC,EAClG,EAAG,WAAW,EAAe,CAAQ,CACvC,OAAS,EAAO,CAEd,MADA,EAAG,OAAO,EAAe,CAAE,MAAO,EAAK,CAAC,EAClC,CACR,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:crypto"),r=require("node:fs");r=e.__toESM(r,1);let i=require("node:path");i=e.__toESM(i,1);let a=require("node:os");a=e.__toESM(a,1);let o=require("yaml");const s=`.doom`,c=`config.yaml`;function l(e){return r.default.existsSync(e)?t.parseDoomConfig(r.default.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function u(e){try{return t.parseDoomConfig(await r.default.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function d(e=a.default.homedir()){return i.default.join(e,`.pi`,s)}function f(e=a.default.homedir()){return i.default.join(d(e),c)}function p(e,t,n=a.default.homedir()){if(!e)return i.default.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return i.default.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return i.default.isAbsolute(e)?i.default.normalize(e):i.default.resolve(t,e)}function m(e){return i.default.join(e,s,c)}function h(e,n=a.default.homedir()){return t.mergeDoomConfigs(l(f(n)),l(m(e)))}async function g(e,n=a.default.homedir()){let r=await u(f(n)),i=await u(m(e));return t.mergeDoomConfigs(r,i)}function _(e){let i;try{i=r.default.readFileSync(e,`utf8`)}catch(t){if(t.code===`ENOENT`)return{filePath:e,exists:!1,hash:``,keys:[]};throw t}let a=(0,o.parse)(i)??{};return{filePath:e,exists:!0,hash:(0,n.createHash)(`sha256`).update(i,`utf8`).digest(`hex`),keys:t.configLeafKeys(a)}}function v(e,n=a.default.homedir()){let r=_(f(n)),i=e===void 0?{filePath:``,exists:!1,hash:``,keys:[]}:_(m(e)),o=e===void 0?t.mergeDoomConfigs(l(f(n)),{projectTrust:`ask`}):h(e,n),s=(e,t)=>{let n=t.join(`.`);return e.keys.some(e=>e===n||e.startsWith(`${n}.`))};return{globalFile:r,repositoryFile:i,effective:o,originOf(e){let n=t.configScopeOf(e);return n!==`global`&&s(i,e)?`repository`:n!==`repository`&&s(r,e)?`global`:`default`},valueAt:e=>t.valueAtKeyPath(o,e)}}exports.globalDoomConfigDirectory=d,exports.globalDoomConfigPath=f,exports.loadDoomConfig=h,exports.loadDoomConfigAsync=g,exports.loadDoomConfigLayers=v,exports.repositoryDoomConfigPath=m,exports.resolvePlanningPlansDirectory=p;
2
+ //# sourceMappingURL=config.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.cjs","names":["fs","parseDoomConfig","os","path","mergeDoomConfigs","parseYaml","createHash","configLeafKeys","configScopeOf","valueAtKeyPath"],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n configLeafKeys,\n configScopeOf,\n mergeDoomConfigs,\n parseDoomConfig,\n valueAtKeyPath,\n} from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\nimport type { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from '../types/config.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\n\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: 'ask' };\n}\n\nasync function readConfigAsync(filePath: string): Promise<DoomConfig> {\n try {\n return parseDoomConfig(await fs.promises.readFile(filePath, 'utf8'), filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { projectTrust: 'ask' };\n throw error;\n }\n}\n\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\n\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\n\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\n\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\n\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n\nexport async function loadDoomConfigAsync(repoRoot: string, homeDirectory = os.homedir()): Promise<DoomConfig> {\n const globalConfig = await readConfigAsync(globalDoomConfigPath(homeDirectory));\n const repositoryConfig = await readConfigAsync(repositoryDoomConfigPath(repoRoot));\n return mergeDoomConfigs(globalConfig, repositoryConfig);\n}\n\n/**\n * Reads one config file as a layer: what it is, what it sets, and the bytes it\n * held. A file that does not exist is a layer that sets nothing, which is a\n * normal state rather than an error.\n */\nfunction readLayer(filePath: string): DoomConfigLayer {\n let text: string;\n try {\n text = fs.readFileSync(filePath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return { filePath, exists: false, hash: '', keys: [] };\n }\n throw error;\n }\n // The raw document, not the parsed config: this is the one read that has to\n // distinguish a key the file set from a key the parser defaulted.\n const document = (parseYaml(text) ?? {}) as unknown;\n return {\n filePath,\n exists: true,\n hash: createHash('sha256').update(text, 'utf8').digest('hex'),\n keys: configLeafKeys(document),\n };\n}\n\n/**\n * The two config files kept apart, with the merge alongside them.\n *\n * `repoRoot` is optional because the global file is complete on its own: a\n * settings surface with no repository in view still has settings to show, and\n * demanding one would leave it with nothing to render rather than with the\n * global half.\n *\n * The origin answer respects the merge's own per-field rules rather than simply\n * preferring the repository: `editor` is read from the global side whatever the\n * repository says, and `projectTrust` from the repository side whatever the\n * global one says. Reporting a file as the source of a value the merge discards\n * is the exact confusion this exists to remove.\n */\nexport function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory = os.homedir()): DoomConfigLayers {\n const globalLayer = readLayer(globalDoomConfigPath(homeDirectory));\n const repositoryLayer =\n repoRoot === undefined\n ? { filePath: '', exists: false, hash: '', keys: [] }\n : readLayer(repositoryDoomConfigPath(repoRoot));\n // With no repository there is nothing to merge, so the global file stands as\n // the effective config on its own.\n const effective =\n repoRoot === undefined\n ? mergeDoomConfigs(readConfig(globalDoomConfigPath(homeDirectory)), { projectTrust: 'ask' })\n : loadDoomConfig(repoRoot, homeDirectory);\n const sets = (layer: DoomConfigLayer, keyPath: readonly string[]): boolean => {\n const dotted = keyPath.join('.');\n return layer.keys.some((key) => key === dotted || key.startsWith(`${dotted}.`));\n };\n return {\n globalFile: globalLayer,\n repositoryFile: repositoryLayer,\n effective,\n originOf(keyPath: readonly string[]): ConfigValueOrigin {\n const scope = configScopeOf(keyPath);\n if (scope !== 'global' && sets(repositoryLayer, keyPath)) return 'repository';\n if (scope !== 'repository' && sets(globalLayer, keyPath)) return 'global';\n return 'default';\n },\n valueAt: (keyPath: readonly string[]) => valueAtKeyPath(effective, keyPath),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { DoomConfig, DoomConfigLayers } from "../types/config.cjs";
2
+ //#region src/adapters/config.d.ts
3
+ export declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
+ export declare function globalDoomConfigPath(homeDirectory?: string): string;
5
+ export declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
6
+ export declare function repositoryDoomConfigPath(repoRoot: string): string;
7
+ export declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
+ export declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ /**
10
+ * The two config files kept apart, with the merge alongside them.
11
+ *
12
+ * `repoRoot` is optional because the global file is complete on its own: a
13
+ * settings surface with no repository in view still has settings to show, and
14
+ * demanding one would leave it with nothing to render rather than with the
15
+ * global half.
16
+ *
17
+ * The origin answer respects the merge's own per-field rules rather than simply
18
+ * preferring the repository: `editor` is read from the global side whatever the
19
+ * repository says, and `projectTrust` from the repository side whatever the
20
+ * global one says. Reporting a file as the source of a value the merge discards
21
+ * is the exact confusion this exists to remove.
22
+ */
23
+ export declare function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory?: string): DoomConfigLayers;
24
+ //#endregion
25
+ //# sourceMappingURL=config.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.cts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;wBAmCgB,0BAA0B;wBAI1B,qBAAqB;wBAIrB,8BACd,gCACA,kBACA;wBAWc,yBAAyB;wBAIzB,eAAe,kBAAkB,yBAA+B;wBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ;;;;;;;;;;;;;;;wBA8CnF,qBAAqB,8BAA8B,yBAA+B"}
@@ -0,0 +1,25 @@
1
+ import { DoomConfig, DoomConfigLayers } from "../types/config.mjs";
2
+ //#region src/adapters/config.d.ts
3
+ export declare function globalDoomConfigDirectory(homeDirectory?: string): string;
4
+ export declare function globalDoomConfigPath(homeDirectory?: string): string;
5
+ export declare function resolvePlanningPlansDirectory(configured: string | undefined, repoRoot: string, homeDirectory?: string): string;
6
+ export declare function repositoryDoomConfigPath(repoRoot: string): string;
7
+ export declare function loadDoomConfig(repoRoot: string, homeDirectory?: string): DoomConfig;
8
+ export declare function loadDoomConfigAsync(repoRoot: string, homeDirectory?: string): Promise<DoomConfig>;
9
+ /**
10
+ * The two config files kept apart, with the merge alongside them.
11
+ *
12
+ * `repoRoot` is optional because the global file is complete on its own: a
13
+ * settings surface with no repository in view still has settings to show, and
14
+ * demanding one would leave it with nothing to render rather than with the
15
+ * global half.
16
+ *
17
+ * The origin answer respects the merge's own per-field rules rather than simply
18
+ * preferring the repository: `editor` is read from the global side whatever the
19
+ * repository says, and `projectTrust` from the repository side whatever the
20
+ * global one says. Reporting a file as the source of a value the merge discards
21
+ * is the exact confusion this exists to remove.
22
+ */
23
+ export declare function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory?: string): DoomConfigLayers;
24
+ //#endregion
25
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../../src/adapters/config.ts"],"mappings":";;wBAmCgB,0BAA0B;wBAI1B,qBAAqB;wBAIrB,8BACd,gCACA,kBACA;wBAWc,yBAAyB;wBAIzB,eAAe,kBAAkB,yBAA+B;wBAO1D,oBAAoB,kBAAkB,yBAA+B,QAAQ;;;;;;;;;;;;;;;wBA8CnF,qBAAqB,8BAA8B,yBAA+B"}
@@ -0,0 +1,2 @@
1
+ import{configLeafKeys as e,configScopeOf as t,mergeDoomConfigs as n,parseDoomConfig as r,valueAtKeyPath as i}from"../services/configPolicy.mjs";import{createHash as a}from"node:crypto";import o from"node:fs";import s from"node:path";import c from"node:os";import{parse as l}from"yaml";const u=`.doom`,d=`config.yaml`;function f(e){return o.existsSync(e)?r(o.readFileSync(e,`utf8`),e):{projectTrust:`ask`}}async function p(e){try{return r(await o.promises.readFile(e,`utf8`),e)}catch(e){if(e.code===`ENOENT`)return{projectTrust:`ask`};throw e}}function m(e=c.homedir()){return s.join(e,`.pi`,u)}function h(e=c.homedir()){return s.join(m(e),d)}function g(e,t,n=c.homedir()){if(!e)return s.join(n,`.pi`,`plans`);if(e===`~`)return n;if(e.startsWith(`~/`))return s.resolve(n,e.slice(2));if(e.startsWith(`~`))throw Error(`Doom planning plansDirectory supports only '~' or '~/' home aliases`);return s.isAbsolute(e)?s.normalize(e):s.resolve(t,e)}function _(e){return s.join(e,u,d)}function v(e,t=c.homedir()){return n(f(h(t)),f(_(e)))}async function y(e,t=c.homedir()){let r=await p(h(t)),i=await p(_(e));return n(r,i)}function b(t){let n;try{n=o.readFileSync(t,`utf8`)}catch(e){if(e.code===`ENOENT`)return{filePath:t,exists:!1,hash:``,keys:[]};throw e}let r=l(n)??{};return{filePath:t,exists:!0,hash:a(`sha256`).update(n,`utf8`).digest(`hex`),keys:e(r)}}function x(e,r=c.homedir()){let a=b(h(r)),o=e===void 0?{filePath:``,exists:!1,hash:``,keys:[]}:b(_(e)),s=e===void 0?n(f(h(r)),{projectTrust:`ask`}):v(e,r),l=(e,t)=>{let n=t.join(`.`);return e.keys.some(e=>e===n||e.startsWith(`${n}.`))};return{globalFile:a,repositoryFile:o,effective:s,originOf(e){let n=t(e);return n!==`global`&&l(o,e)?`repository`:n!==`repository`&&l(a,e)?`global`:`default`},valueAt:e=>i(s,e)}}export{m as globalDoomConfigDirectory,h as globalDoomConfigPath,v as loadDoomConfig,y as loadDoomConfigAsync,x as loadDoomConfigLayers,_ as repositoryDoomConfigPath,g as resolvePlanningPlansDirectory};
2
+ //# sourceMappingURL=config.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.mjs","names":["parseYaml"],"sources":["../../src/adapters/config.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport {\n configLeafKeys,\n configScopeOf,\n mergeDoomConfigs,\n parseDoomConfig,\n valueAtKeyPath,\n} from '../services/configPolicy.ts';\nimport type { DoomConfig } from '../types/config.ts';\nimport type { ConfigValueOrigin, DoomConfigLayer, DoomConfigLayers } from '../types/config.ts';\n\nconst DOOM_DIR = '.doom';\nconst CONFIG_FILE = 'config.yaml';\nconst GLOBAL_CONFIG_DIR = '.pi';\nconst PLANS_DIR = 'plans';\n\nfunction readConfig(filePath: string): DoomConfig {\n return fs.existsSync(filePath)\n ? parseDoomConfig(fs.readFileSync(filePath, 'utf8'), filePath)\n : { projectTrust: 'ask' };\n}\n\nasync function readConfigAsync(filePath: string): Promise<DoomConfig> {\n try {\n return parseDoomConfig(await fs.promises.readFile(filePath, 'utf8'), filePath);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { projectTrust: 'ask' };\n throw error;\n }\n}\n\nexport function globalDoomConfigDirectory(homeDirectory = os.homedir()): string {\n return path.join(homeDirectory, GLOBAL_CONFIG_DIR, DOOM_DIR);\n}\n\nexport function globalDoomConfigPath(homeDirectory = os.homedir()): string {\n return path.join(globalDoomConfigDirectory(homeDirectory), CONFIG_FILE);\n}\n\nexport function resolvePlanningPlansDirectory(\n configured: string | undefined,\n repoRoot: string,\n homeDirectory = os.homedir(),\n): string {\n if (!configured) return path.join(homeDirectory, GLOBAL_CONFIG_DIR, PLANS_DIR);\n if (configured === '~') return homeDirectory;\n if (configured.startsWith('~/')) return path.resolve(homeDirectory, configured.slice(2));\n if (configured.startsWith('~')) {\n throw new Error(\"Doom planning plansDirectory supports only '~' or '~/' home aliases\");\n }\n return path.isAbsolute(configured) ? path.normalize(configured) : path.resolve(repoRoot, configured);\n}\n\nexport function repositoryDoomConfigPath(repoRoot: string): string {\n return path.join(repoRoot, DOOM_DIR, CONFIG_FILE);\n}\n\nexport function loadDoomConfig(repoRoot: string, homeDirectory = os.homedir()): DoomConfig {\n return mergeDoomConfigs(\n readConfig(globalDoomConfigPath(homeDirectory)),\n readConfig(repositoryDoomConfigPath(repoRoot)),\n );\n}\n\nexport async function loadDoomConfigAsync(repoRoot: string, homeDirectory = os.homedir()): Promise<DoomConfig> {\n const globalConfig = await readConfigAsync(globalDoomConfigPath(homeDirectory));\n const repositoryConfig = await readConfigAsync(repositoryDoomConfigPath(repoRoot));\n return mergeDoomConfigs(globalConfig, repositoryConfig);\n}\n\n/**\n * Reads one config file as a layer: what it is, what it sets, and the bytes it\n * held. A file that does not exist is a layer that sets nothing, which is a\n * normal state rather than an error.\n */\nfunction readLayer(filePath: string): DoomConfigLayer {\n let text: string;\n try {\n text = fs.readFileSync(filePath, 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n return { filePath, exists: false, hash: '', keys: [] };\n }\n throw error;\n }\n // The raw document, not the parsed config: this is the one read that has to\n // distinguish a key the file set from a key the parser defaulted.\n const document = (parseYaml(text) ?? {}) as unknown;\n return {\n filePath,\n exists: true,\n hash: createHash('sha256').update(text, 'utf8').digest('hex'),\n keys: configLeafKeys(document),\n };\n}\n\n/**\n * The two config files kept apart, with the merge alongside them.\n *\n * `repoRoot` is optional because the global file is complete on its own: a\n * settings surface with no repository in view still has settings to show, and\n * demanding one would leave it with nothing to render rather than with the\n * global half.\n *\n * The origin answer respects the merge's own per-field rules rather than simply\n * preferring the repository: `editor` is read from the global side whatever the\n * repository says, and `projectTrust` from the repository side whatever the\n * global one says. Reporting a file as the source of a value the merge discards\n * is the exact confusion this exists to remove.\n */\nexport function loadDoomConfigLayers(repoRoot: string | undefined, homeDirectory = os.homedir()): DoomConfigLayers {\n const globalLayer = readLayer(globalDoomConfigPath(homeDirectory));\n const repositoryLayer =\n repoRoot === undefined\n ? { filePath: '', exists: false, hash: '', keys: [] }\n : readLayer(repositoryDoomConfigPath(repoRoot));\n // With no repository there is nothing to merge, so the global file stands as\n // the effective config on its own.\n const effective =\n repoRoot === undefined\n ? mergeDoomConfigs(readConfig(globalDoomConfigPath(homeDirectory)), { projectTrust: 'ask' })\n : loadDoomConfig(repoRoot, homeDirectory);\n const sets = (layer: DoomConfigLayer, keyPath: readonly string[]): boolean => {\n const dotted = keyPath.join('.');\n return layer.keys.some((key) => key === dotted || key.startsWith(`${dotted}.`));\n };\n return {\n globalFile: globalLayer,\n repositoryFile: repositoryLayer,\n effective,\n originOf(keyPath: readonly string[]): ConfigValueOrigin {\n const scope = configScopeOf(keyPath);\n if (scope !== 'global' && sets(repositoryLayer, keyPath)) return 'repository';\n if (scope !== 'repository' && sets(globalLayer, keyPath)) return 'global';\n return 'default';\n },\n valueAt: (keyPath: readonly string[]) => valueAtKeyPath(effective, keyPath),\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("../_virtual/_rolldown/runtime.cjs"),t=require("../services/configPolicy.cjs");let n=require("node:crypto"),r=require("node:path");r=e.__toESM(r,1);let i=require("yaml"),a=require("node:fs/promises");a=e.__toESM(a,1);function o(e){return e===`repository`?{file:420,directory:493}:{file:384,directory:448}}async function s(e){try{return await a.default.readFile(e,`utf8`)}catch(e){if(e instanceof Error&&`code`in e&&e.code===`ENOENT`)return``;throw e}}function c(e){if(typeof e!=`object`||!e)return!1;let t=e.items;return Array.isArray(t)&&t.length===0}function l(e,t){for(let n=t.length-1;n>0;--n){let r=t.slice(0,n);if(e.hasIn(r)){if(!c(e.getIn(r,!0)))return;e.deleteIn(r)}}}async function u(e,c,u={}){if(c.length===0)return;let d=await s(e),f=(0,i.parseDocument)(d,{keepSourceTokens:!0});if(f.errors.length>0)throw Error(`Could not parse Doom config at ${e}`);for(let e of c){let t=[...e.keyPath];if(e.value!==void 0){f.setIn(t,e.value);continue}f.hasIn(t)&&(f.deleteIn(t),u.prune!==!1&&l(f,t))}let p=f.toString();t.parseDoomConfig(p,e);let m=o(u.scope??`global`);await a.default.mkdir(r.default.dirname(e),{mode:m.directory,recursive:!0});let h=`${e}.${process.pid}.${(0,n.randomUUID)()}.tmp`;try{await a.default.writeFile(h,p,{encoding:`utf8`,mode:m.file}),await a.default.rename(h,e)}catch(e){throw await a.default.rm(h,{force:!0}),e}}async function d(e,t,n,r={}){await u(e,[{keyPath:t,value:n}],r)}async function f(e,t,n={}){await u(e,[{keyPath:t}],n)}exports.setDoomConfigValue=d,exports.unsetDoomConfigValue=f,exports.writeDoomConfigValues=u;
2
+ //# sourceMappingURL=configWriter.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configWriter.cjs","names":["fs","parseDocument","path","randomUUID"],"sources":["../../src/adapters/configWriter.ts"],"sourcesContent":["/**\n * Writes into the global Doom config file, one key path at a time.\n *\n * Extracted from doom-file-edit, which owned the only writer in the repo and had\n * it hard-coded to `editor.command`. Several extensions now edit their own\n * settings from the config panel, and every one of them needs the same four\n * properties: keep the user's comments, never publish a file the parser would\n * reject, never leave a half-written file behind, and apply a set of related\n * edits together.\n *\n * That last one is not a convenience. `voice.adapters.<engine>.model` accepts a\n * path or an id but not both, so switching between them is a set and an unset\n * that must land in the same write, or the file is briefly invalid.\n *\n * Atomicity is the same rename trick as `atomicJson.ts` next door, and carries\n * the same caveat: it prevents torn reads, not lost updates. Two sessions with\n * the panel open still clobber each other, because the rename only publishes\n * what the caller already computed.\n *\n * AVOID:\n * - Treating this as a lock\n * - Writing an empty string; the parser rejects blank values, so clearing a key\n * means removing it\n */\n\nimport { randomUUID } from 'node:crypto';\nimport fs from 'node:fs/promises';\nimport path from 'node:path';\nimport { parseDocument } from 'yaml';\nimport { parseDoomConfig } from '../services/configPolicy.ts';\n\n/**\n * The global file holds machine-wide credentials-adjacent settings and lives in\n * the user's home, so it stays private.\n */\nconst PRIVATE_FILE_MODE = 0o600;\nconst PRIVATE_DIRECTORY_MODE = 0o700;\n\n/**\n * The repository file is committed to git and read by everyone who checks the\n * project out, so it takes the modes `init.ts` already seeds it with. Writing it\n * private would produce a 0600 file in a working tree, which is both wrong and\n * invisible until someone else clones.\n */\nconst REPOSITORY_FILE_MODE = 0o644;\nconst REPOSITORY_DIRECTORY_MODE = 0o755;\n\n/** Which file a write is aimed at; the modes follow from it. */\nexport type DoomConfigScope = 'global' | 'repository';\n\n/** The permissions a scope's file and its parent directory are created with. */\nfunction modesForScope(scope: DoomConfigScope): { file: number; directory: number } {\n return scope === 'repository'\n ? { file: REPOSITORY_FILE_MODE, directory: REPOSITORY_DIRECTORY_MODE }\n : { file: PRIVATE_FILE_MODE, directory: PRIVATE_DIRECTORY_MODE };\n}\n\nexport interface DoomConfigEdit {\n /** Key path into the document, e.g. `['voice', 'adapters', 'mlx-whisper', 'model', 'id']`. */\n readonly keyPath: readonly string[];\n /**\n * The value to write. `undefined` removes the key.\n *\n * Not only strings: the config holds numbers (`utteranceIdleMs`) and lists\n * (`startPhrases`), and a panel that edits them as text has to hand back the\n * parsed value or the next load rejects what it wrote.\n */\n readonly value?: string | number | boolean | readonly string[];\n}\n\nexport interface WriteDoomConfigOptions {\n /**\n * Remove ancestors left holding nothing. Defaults to true, and is not\n * cosmetic: an adapter left as `{}` fails validation, because the parser\n * requires a model once the adapter exists at all.\n */\n readonly prune?: boolean;\n /**\n * Which file this is, so the write lands with the right permissions.\n * Defaults to `global`, which is what every caller wrote before the\n * repository file was writable at all.\n */\n readonly scope?: DoomConfigScope;\n}\n\nasync function readIfPresent(filePath: string): Promise<string> {\n try {\n return await fs.readFile(filePath, 'utf8');\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') return '';\n throw error;\n }\n}\n\nfunction isEmptyCollection(value: unknown): boolean {\n if (value === null || typeof value !== 'object') return false;\n const items = (value as { items?: unknown }).items;\n return Array.isArray(items) && items.length === 0;\n}\n\n/** Drops ancestors of a removed key that are now empty, deepest first. */\nfunction pruneEmptyAncestors(document: ReturnType<typeof parseDocument>, keyPath: readonly string[]): void {\n for (let depth = keyPath.length - 1; depth > 0; depth -= 1) {\n const ancestor = keyPath.slice(0, depth);\n if (!document.hasIn(ancestor)) continue;\n if (!isEmptyCollection(document.getIn(ancestor, true))) return;\n document.deleteIn(ancestor);\n }\n}\n\n/**\n * Applies every edit as one document mutation, or throws having written nothing.\n *\n * Validation runs against the serialized result rather than each edit, so a pair\n * of edits only valid together is accepted, and a single edit that breaks an\n * invariant elsewhere is still caught.\n */\nexport async function writeDoomConfigValues(\n filePath: string,\n edits: readonly DoomConfigEdit[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n if (edits.length === 0) return;\n const content = await readIfPresent(filePath);\n // Not `content || '{}\\n'`: seeding an absent file with a flow map makes yaml\n // keep that style, so the first write produces `{ editor: { command: ... } }`\n // and every later one grows that one line. An empty source starts a block map.\n const document = parseDocument(content, { keepSourceTokens: true });\n if (document.errors.length > 0) throw new Error(`Could not parse Doom config at ${filePath}`);\n\n for (const edit of edits) {\n const keyPath = [...edit.keyPath];\n if (edit.value !== undefined) {\n document.setIn(keyPath, edit.value);\n continue;\n }\n // deleteIn throws when an ancestor is missing rather than treating the key as\n // already absent, and clearing an unset key is worth supporting.\n if (!document.hasIn(keyPath)) continue;\n document.deleteIn(keyPath);\n if (options.prune !== false) pruneEmptyAncestors(document, keyPath);\n }\n\n const output = document.toString();\n parseDoomConfig(output, filePath);\n\n const modes = modesForScope(options.scope ?? 'global');\n await fs.mkdir(path.dirname(filePath), { mode: modes.directory, recursive: true });\n const temporaryPath = `${filePath}.${process.pid}.${randomUUID()}.tmp`;\n try {\n await fs.writeFile(temporaryPath, output, { encoding: 'utf8', mode: modes.file });\n await fs.rename(temporaryPath, filePath);\n } catch (error) {\n await fs.rm(temporaryPath, { force: true });\n throw error;\n }\n}\n\nexport async function setDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n value: string,\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath, value }], options);\n}\n\nexport async function unsetDoomConfigValue(\n filePath: string,\n keyPath: readonly string[],\n options: WriteDoomConfigOptions = {},\n): Promise<void> {\n await writeDoomConfigValues(filePath, [{ keyPath }], options);\n}\n"],"mappings":"yOAmDA,SAAS,EAAc,EAA6D,CAClF,OAAO,IAAU,aACb,CAAE,KAAM,IAAsB,UAAW,GAA0B,EACnE,CAAE,KAAM,IAAmB,UAAW,GAAuB,CACnE,CA8BA,eAAe,EAAc,EAAmC,CAC9D,GAAI,CACF,OAAO,MAAMA,EAAAA,QAAG,SAAS,EAAU,MAAM,CAC3C,OAAS,EAAO,CACd,GAAI,aAAiB,OAAS,SAAU,GAAS,EAAM,OAAS,SAAU,MAAO,GACjF,MAAM,CACR,CACF,CAEA,SAAS,EAAkB,EAAyB,CAClD,GAAsB,OAAO,GAAU,WAAnC,EAA6C,MAAO,GACxD,IAAM,EAAS,EAA8B,MAC7C,OAAO,MAAM,QAAQ,CAAK,GAAK,EAAM,SAAW,CAClD,CAGA,SAAS,EAAoB,EAA4C,EAAkC,CACzG,IAAK,IAAI,EAAQ,EAAQ,OAAS,EAAG,EAAQ,EAAG,IAAY,CAC1D,IAAM,EAAW,EAAQ,MAAM,EAAG,CAAK,EAClC,KAAS,MAAM,CAAQ,EAC5B,IAAI,CAAC,EAAkB,EAAS,MAAM,EAAU,EAAI,CAAC,EAAG,OACxD,EAAS,SAAS,CAAQ,CAD8B,CAE1D,CACF,CASA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,GAAI,EAAM,SAAW,EAAG,OACxB,IAAM,EAAU,MAAM,EAAc,CAAQ,EAItC,GAAA,EAAWC,EAAAA,cAAAA,CAAc,EAAS,CAAE,iBAAkB,EAAK,CAAC,EAClE,GAAI,EAAS,OAAO,OAAS,EAAG,MAAU,MAAM,kCAAkC,GAAU,EAE5F,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAM,EAAU,CAAC,GAAG,EAAK,OAAO,EAChC,GAAI,EAAK,QAAU,IAAA,GAAW,CAC5B,EAAS,MAAM,EAAS,EAAK,KAAK,EAClC,QACF,CAGK,EAAS,MAAM,CAAO,IAC3B,EAAS,SAAS,CAAO,EACrB,EAAQ,QAAU,IAAO,EAAoB,EAAU,CAAO,EACpE,CAEA,IAAM,EAAS,EAAS,SAAS,EACjC,EAAA,gBAAgB,EAAQ,CAAQ,EAEhC,IAAM,EAAQ,EAAc,EAAQ,OAAS,QAAQ,EACrD,MAAMD,EAAAA,QAAG,MAAME,EAAAA,QAAK,QAAQ,CAAQ,EAAG,CAAE,KAAM,EAAM,UAAW,UAAW,EAAK,CAAC,EACjF,IAAM,EAAgB,GAAG,EAAS,GAAG,QAAQ,IAAI,IAAA,EAAGC,EAAAA,WAAAA,CAAW,EAAE,MACjE,GAAI,CACF,MAAMH,EAAAA,QAAG,UAAU,EAAe,EAAQ,CAAE,SAAU,OAAQ,KAAM,EAAM,IAAK,CAAC,EAChF,MAAMA,EAAAA,QAAG,OAAO,EAAe,CAAQ,CACzC,OAAS,EAAO,CAEd,MADA,MAAMA,EAAAA,QAAG,GAAG,EAAe,CAAE,MAAO,EAAK,CAAC,EACpC,CACR,CACF,CAEA,eAAsB,EACpB,EACA,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,UAAS,OAAM,CAAC,EAAG,CAAO,CACrE,CAEA,eAAsB,EACpB,EACA,EACA,EAAkC,CAAC,EACpB,CACf,MAAM,EAAsB,EAAU,CAAC,CAAE,SAAQ,CAAC,EAAG,CAAO,CAC9D"}