Compare commits

..

2 Commits

Author SHA1 Message Date
d1c3c00b4d feat(manifest): un plugin peut contribuer des commandes slash via callback — #165 (QA verte)
Étend le manifeste et la validation SDK : un plugin déclare des commandes
slash (contributes.slashCommands) adossées à une callback (command id
enregistré via ctx.commands.registerCommand). Métadonnées UI exposées :
name, shortDescription, requiresConfirmation, when. Exemple hello-plugin
mis à jour avec une commande /hello.

Ces commandes transitent ensuite par le registry/contrat unifié (#162).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 15:46:05 +02:00
fe50219493 merge(sdk): intègre feature/plugin-hosted-windows — runtime React partagé + docs restructurées (vert QA)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-04 00:40:05 +02:00
3 changed files with 40 additions and 2 deletions

View File

@ -5,14 +5,20 @@ background work.
## Contract
Every human menu click follows this sequence:
Every menu click or plugin slash command follows this sequence:
```text
menu item -> command id -> registered command handler -> optional task -> feedback surfaces
manifest contribution -> command id -> registered command handler -> optional task -> feedback surfaces
```
- A manifest menu item declares a `command` id; it does not run tools directly.
- A manifest slash command declares a slash `name`, autocomplete metadata and a
`command` id; it does not run tools directly.
- Menu items and slash commands may share the same `command` id, or point to
different handlers. The plugin owns that choice.
- The command handler is the only place that decides whether work should start.
- The host slash-command registry only lists/filters metadata and returns a
callback dispatch effect. The plugin handler decides what the command does.
- A launched process is represented by a background task returned from
`ctx.services.tasks.runCommand()`.
- A skipped command is represented by the command handler return value and logs,

View File

@ -32,6 +32,14 @@
"order": 10
}
],
"slashCommands": [
{
"name": "/hello",
"shortDescription": "Run the hello-plugin callback",
"command": "hello-plugin",
"requiresConfirmation": false
}
],
"layouts": [
{
"type": "hello-plugin.hello-world",

View File

@ -16,6 +16,7 @@ export interface IdeAPluginManifest {
contributes?: {
menus?: IdeAPluginTopLevelMenuContribution[];
menuItems?: IdeAPluginMenuItemContribution[];
slashCommands?: IdeAPluginSlashCommandContribution[];
layouts?: IdeAPluginLayoutContribution[];
mcpServers?: IdeAPluginMcpServerContribution[];
};
@ -47,6 +48,19 @@ export interface IdeAPluginMenuItemContribution {
when?: string;
}
export interface IdeAPluginSlashCommandContribution {
/** Slash name shown in autocomplete. Must start with "/". */
name: string;
/** Short autocomplete/help description. */
shortDescription: string;
/** Command callback id registered through ctx.commands.registerCommand(). */
command: string;
/** Ask for host confirmation before dispatching the callback. */
requiresConfirmation?: boolean;
/** Reserved declarative condition for host-side availability. */
when?: string;
}
export interface IdeAPluginLayoutContribution {
type: string;
label: string;
@ -196,6 +210,16 @@ function validateContributes(value: unknown, errors: string[]): void {
optionalString(item, "icon", errors, `contributes.menuItems[${index}].icon`);
optionalString(item, "when", errors, `contributes.menuItems[${index}].when`);
});
validateArray(value, "slashCommands", errors, (command, index) => {
requireString(command, "name", errors, `contributes.slashCommands[${index}].name`);
requireString(command, "shortDescription", errors, `contributes.slashCommands[${index}].shortDescription`);
requireString(command, "command", errors, `contributes.slashCommands[${index}].command`);
if (typeof command.name === "string" && !command.name.startsWith("/")) {
errors.push(`contributes.slashCommands[${index}].name must start with "/"`);
}
optionalBoolean(command, "requiresConfirmation", errors, `contributes.slashCommands[${index}].requiresConfirmation`);
optionalString(command, "when", errors, `contributes.slashCommands[${index}].when`);
});
validateArray(value, "layouts", errors, (layout, index) => {
requireString(layout, "type", errors, `contributes.layouts[${index}].type`);
requireString(layout, "label", errors, `contributes.layouts[${index}].label`);