Feat/plugin instance actions (#2209)

* feat(shared): scope plugin action descriptors

* feat(plugin-sdk): instance-scoped settings actions

* feat(plugins): run instance-scoped actions from the admin settings dialog

* feat(admin): instance-scoped plugin actions in the settings dialog

* fix(plugins): surface instance actions without instance fields and harden the action gate

* docs(plugin-sdk): note that hosts before 4.2.0 ignore action scope

* fix(admin): open the dialog for instance actions, explain inactive 404s, guard the save window

* docs(wiki): plugin pages for instance actions and the admin panel changes
This commit is contained in:
jubnl
2026-09-02 23:06:03 +02:00
committed by GitHub
parent b1ce7c303d
commit b82962f95b
59 changed files with 1133 additions and 128 deletions
+27 -1
View File
@@ -31,11 +31,37 @@ export const pluginSettingsFieldSchema = z.object({
});
export type PluginSettingsField = z.infer<typeof pluginSettingsFieldSchema>;
/**
* A settings-form action button a plugin declared in its manifest (`actions`). Which
* form renders it is the `scope`, exactly like a settings field: `'user'` on the user
* Settings tab (runs as the clicking user), `'instance'` in the admin instance-settings
* dialog (runs as the clicking admin). One action lives in exactly one form.
*/
export const pluginActionScopeSchema = z.enum(['user', 'instance']);
export type PluginActionScope = z.infer<typeof pluginActionScopeSchema>;
export const pluginActionDescriptorSchema = z.object({
key: z.string(),
label: z.string(),
hint: z.string().optional(),
danger: z.boolean(),
scope: pluginActionScopeSchema,
});
export type PluginActionDescriptor = z.infer<typeof pluginActionDescriptorSchema>;
/** What an action answers, after the host bounded the plugin-supplied message. */
export const pluginActionResultSchema = z.object({
ok: z.boolean(),
message: z.string().optional(),
});
export type PluginActionResult = z.infer<typeof pluginActionResultSchema>;
/** GET /api/admin/plugins/:id/config — the declared `scope:'instance'` fields
* plus the stored values, secrets masked. */
* plus the stored values, secrets masked, plus the `scope:'instance'` actions. */
export const pluginInstanceConfigResponseSchema = z.object({
fields: z.array(pluginSettingsFieldSchema),
config: z.record(z.string(), z.unknown()),
actions: z.array(pluginActionDescriptorSchema),
});
export type PluginInstanceConfigResponse = z.infer<typeof pluginInstanceConfigResponseSchema>;