Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
6.1 KiB
AGENTS.md
These instructions cover the plugin system in app/MindWork AI Studio/Tools/PluginSystem/ and what
configuration plugins can provide. They add to the AGENTS.md in the repository root, which applies here
as well.
Lua runtime
Every plugin runs in a Lua runtime of its own, created by PluginFactory.CreateLuaState, whatever its type
and whoever runs it: loading, starting, checking an import, and the assistant builder.
- A plugin loads the files of its own directory with
require, throughPluginLoader, also from subdirectories:require("lib/helpers")loadslib/helpers.lua. The dot notationrequire("lib.helpers")is not supported. - No other file is within reach. The runtime gets
NoLuaFileSystemas its file system,dofileandloadfileare removed, and code without a directory getsNoModuleLoader. With the default file system of Lua-CSharp,requirewould look for a module it does not find next to the plugin alongpackage.path, relative to the working directory of the app, anddofileandloadfilewould open any path. - Do not open the
io,os, ordebuglibrary, and do not give the runtime a file system. Whatever a plugin needs to reach outside of Lua comes as a host function which checks every access. PluginModuleLoadingTestschecks both sides: what plugins must load, and what they must not reach. Run it after every change to the runtime, the module loader, or the opened libraries.
Configuration plugins
Plugins can configure:
- Self-hosted LLM providers
- Update behavior
- Preview features visibility
- Preselected profiles
- Chat templates
- etc.
Configuration plugins provide three kinds of values:
- Managed settings: simple values such as booleans, numbers, strings, enums, lists, or sets handled through
ManagedConfiguration. These values may be locked or used as organization defaults. Which configuration plugin owns a locked setting is persisted inData.ManagedLockedConfigurations, and organization defaults are tracked inData.ManagedEditableDefaults. Both are cleaned up generically byManagedConfiguration.CleanupLeftOverManagedConfigurations(...)when the owning plugin is gone. The value a setting had before a configuration plugin took it over is kept inData.ManagedUserValueSnapshotsand restored by that same clean-up, so removing a plugin hands the user's own value back instead of the app default. - Managed configuration objects: complex Lua tables that are persisted into
SettingsManager.ConfigurationData, implementIConfigurationObject, and are described by aPluginConfigurationObjectDescriptorinPluginConfigurationObjectDescriptors. Reading them from a plugin and cleaning up the left-over ones go through these descriptors. Examples include providers, profiles, chat templates, data sources, and document analysis policies. - Live plugin content: complex Lua tables that implement
ILivePluginContentand are read live from running plugins instead of being persisted toConfigurationData. Examples includeMANDATORY_INFOSandINTRODUCTIONS. If live plugin content creates persistent side data, add a dedicated cleanup path for that side data, like mandatory-info acceptances.
When adding configuration plugin capabilities:
- For managed settings, update the corresponding data class in
app/MindWork AI Studio/Settings/DataModel/to callManagedConfiguration.Register(...)and process the setting inPluginConfiguration.TryProcessConfiguration. Cleaning up the setting when its configuration plugin was removed needs no extra step:ManagedConfiguration.CleanupLeftOverManagedConfigurations(...)iterates all registered settings. Do not add per-setting cleanup calls toPluginFactory.Loading.LoadAll. - For managed configuration objects, add the type to
PluginConfigurationObjectType.cs, persist the objects in the appropriateConfigurationDatacollection, and add a descriptor toPluginConfigurationObjectDescriptorsand its listALL: its Lua table, its parser, the collection, the next number, and where secrets are kept. Parsing and cleanup need no extra call;PluginConfigurationObjectDescriptorsTestsfails for a type without a descriptor. - For live plugin content, add a data type implementing
ILivePluginContent, parse it inPluginConfiguration, expose it throughPluginFactory, and add any required cleanup only for persistent side data. - Always document the new capability in
app/MindWork AI Studio/Plugins/configuration/plugin.lua.
Importing exported configuration objects
Users can paste a configuration object which somebody exported, such as a chat template, and keep it as one of their own. Three parts carry this:
ConfigurationSnippetParserreads the pasted text as data and never creates a Lua runtime: the text comes from somebody else, so it earns none of the trust a deployed plugin has. It accepts only what an export writes, and it reads that the way Lua does.ConfigurationSnippetParserTestscompares both for every export; extend the parser only together with that test, and never toward running code.ConfigurationSnippetImportreads the table throughTryParseSnippetof the descriptor, i.e., with the parser a configuration plugin goes through, as a dry run. It then makes the object local: an ID of its own, no plugin. Do not add a second description of the fields for the import.DataApp.CanImportConfigurationSnippetdecides who may import which type.
A snippet may name things outside of itself: a file, a server, another configuration object. None of them may exist on this computer, and none of them may be touched before the user agreed to the import. The importer therefore looks up no path on a network share and contacts no server; the forms do that once the user asks.
A new type of configuration object needs a decision on its import: a switch in DataApp, its case in
CanImportConfigurationSnippet, and its documentation in plugin.lua, plus its export in
ConfigurationSnippetParserTests and its case in ConfigurationSnippetImport.TryImport. The coverage tests in
DataAppImportPermissionTests, ConfigurationSnippetParserTests, and ConfigurationSnippetImportTests fail
for a type without them.