docs(sdk): feedback visible via windows.open + séparation Codex skills documentées — commands-and-feedback (surface normative + exemple), layouts-react (type↔contributes.layouts, capacité ui), manifest (exemple complet menu+layout+ui), windows (contrat ui, retour structuré), packaging-distribution (idea_plugin_install_from_directory, sémantique plugin_reloaded/ESM), nouveau codex-skills-and-idea-plugins, README (lien) — #280 QA verte

This commit is contained in:
2026-09-08 13:40:52 +02:00
parent 9829ccdf2a
commit 1239104b0f
7 changed files with 202 additions and 9 deletions

View File

@ -0,0 +1,41 @@
# Codex Skills And IdeA Plugins
IdeA runtime plugins and Codex plugins are separate installation and discovery
systems.
An IdeA runtime plugin is described by `idea-plugin.json`. Installing it through
IdeA makes its IdeA runtime contributions available: menus, menu items, slash
commands, layouts, MCP servers and runtime services declared by the IdeA plugin
manifest.
A Codex plugin is described by `.codex-plugin/plugin.json`. Its `skills` entry
points Codex at skill directories such as `skills/*/SKILL.md`. This metadata is
for Codex plugin installation and skill discovery, not for IdeA runtime plugin
loading.
## Normative Behavior
- Installing or reloading an IdeA plugin does not automatically install a bundled
Codex plugin.
- Installing or reloading an IdeA plugin does not automatically make bundled
Codex skills available to agents in the current project.
- Installing or reloading an IdeA plugin does not automatically make bundled
Codex skills available to agents in other projects.
- No `idea-plugin.json` manifest field currently declares Codex skills for IdeA
agent assignment.
- Bundling `.codex-plugin/plugin.json` and `skills/*/SKILL.md` inside an IdeA
plugin package is allowed as distribution content, but it is inert for IdeA
runtime plugin loading unless a separate bridge explicitly consumes it.
## Required Mechanism
If a plugin needs Codex skills to be available to agents, install the Codex
plugin through the Codex plugin mechanism for the intended scope, then assign or
enable those skills for the relevant agents according to Codex/IdeA agent
configuration.
Do not rely on IdeA plugin installation as a cross-project skill propagation
mechanism. Cross-project skill availability needs an explicit product contract
covering scope, provenance, permissions, uninstall behavior, hot reload,
collisions and security. Until that bridge exists, document and perform Codex
skill installation separately from IdeA runtime plugin installation.

View File

@ -26,8 +26,9 @@ manifest contribution -> command id -> registered command handler -> optional ta
- Feedback objects must be stable enough for agents and programmatic callers to
parse, and their messages must be readable by humans.
- The current human menu-click UI does not guarantee display of a command
handler return value. Use logs, Work/task state or plugin-owned UI/files when
a human needs visible feedback today.
handler return value. When a human must see feedback from a menu action, open
or focus a plugin layout/window from the handler with
`ctx.services.windows.open({ layoutType, state })`.
## Preconditions
@ -76,6 +77,9 @@ Visible surfaces are intentionally distinct:
- Command return value: immediate feedback for programmatic callers, agents and
future host surfaces. It is not a guaranteed visible UI surface for current
human menu clicks.
- Plugin layout/window: the normative visible feedback surface for menu actions
that need to show status, results or next steps to a human. The layout must be
declared in `idea-plugin.json` and registered during activation.
- Plugin logs: diagnostics for developers and operators.
- Work/background-task surfaces: only for tasks actually launched through
`runCommand()`.
@ -105,6 +109,40 @@ state when invoked instead of assuming a watch was installed at activation.
## Example
Visible menu feedback:
```tsx
import type { IdeAPluginModule, PluginLayoutProps } from "@idea/plugin-sdk";
function HealthView(_props: PluginLayoutProps<{ source?: string }>) {
return <section>Unity tools are ready.</section>;
}
const plugin: IdeAPluginModule = {
activate(ctx) {
ctx.layouts?.register({
type: "unity-plugin.health",
component: HealthView
});
ctx.commands?.registerCommand("unity-plugin.health", async () => {
const win = await ctx.services?.windows.open({
layoutType: "unity-plugin.health",
state: { source: "menu" }
});
return win
? { status: "opened", message: "Opened Unity Health.", alreadyOpen: win.alreadyOpen }
: { status: "skipped", reason: "ui-service-unavailable", message: "UI service unavailable." };
});
}
};
export default plugin;
```
Background task feedback:
```ts
const project = await ctx.services?.workspace.getCurrentProject();
if (!project) {

View File

@ -32,6 +32,26 @@ ctx.layouts?.register({
});
```
The `type` must exactly match one entry in `contributes.layouts`. The host
rejects registrations for undeclared layout types. A plugin that opens this
layout for human feedback must also declare the `ui` capability so the UI
runtime and window services are part of the public contract.
```json
{
"capabilities": ["ui", "tooling"],
"contributes": {
"layouts": [
{
"type": "hello-plugin.dashboard",
"label": "Dashboard",
"component": "Dashboard"
}
]
}
}
```
## Props
- `projectId`: project hosting the layout.

View File

@ -147,6 +147,61 @@ development loop.
contribution can be mounted inside an IdeA layout cell or opened in a detached OS
window. Do not add a separate manifest contribution type for windows.
For a command that opens a visible plugin window, declare both the menu command
and the layout type:
```json
{
"ideaPluginManifestVersion": 1,
"id": "com.example.unity-plugin",
"displayName": "Unity Developer Tools",
"publisher": "Example",
"version": "0.1.0",
"description": "Unity integration for IdeA.",
"main": "dist/index.js",
"engines": {
"idea": ">=0.1.0"
},
"trustLevel": "full",
"capabilities": ["ui", "tooling"],
"activationScope": "app",
"contributes": {
"menus": [
{
"id": "unity-plugin.menu",
"label": "Unity Developer Tools",
"topLevel": true,
"order": 100
}
],
"menuItems": [
{
"id": "unity-plugin.health.item",
"targetMenuId": "unity-plugin.menu",
"label": "Health",
"command": "unity-plugin.health",
"order": 10,
"when": "projectOpen"
}
],
"layouts": [
{
"type": "unity-plugin.health",
"label": "Unity Health",
"component": "UnityHealth",
"order": 10
}
]
}
}
```
`capabilities: ["ui"]` is required when a plugin relies on layouts or windows as
human-facing UI. Add `"tooling"` when the same plugin uses workspace, task,
tooling or terminal services. The `contributes.layouts[].type` value is the
public id used by both `ctx.layouts.register({ type, component })` and
`ctx.services.windows.open({ layoutType })`.
Contribution ids are part of the runtime contract:
- commands can only register ids declared by `contributes.menuItems[*].command`;

View File

@ -102,8 +102,23 @@ After editing plugin source files, rebuild the plugin output first:
npm run build
```
Then ask an IdeA agent that has the plugin administration tool available to run
`idea_plugin_reload` with the installed plugin id:
An IdeA agent can install a development plugin directory only when that agent has
the plugin administration MCP tool explicitly allowed. The public tool is
`idea_plugin_install_from_directory`:
```json
{
"path": "relative/path/to/hello-plugin",
"expectedPluginId": "com.example.hello-plugin"
}
```
The path must resolve under the requester's project root. This installs the
directory as the recorded development source for later reloads.
After source edits, rebuild the plugin output and ask an IdeA agent that has the
plugin administration tool available to run `idea_plugin_reload` with the
installed plugin id:
```json
{
@ -122,7 +137,17 @@ reload. Archive installs are fixed package snapshots. They are appropriate for
distribution, but the reload command is only defined for plugins installed from
a directory source.
Current limitation: backend registry state and plugin MCP/tool contributions are
reloaded without restarting IdeA. Frontend React contributions that are already
loaded in the current UI session may keep their existing module instance until
the relevant plugin surface is recreated or the app session is restarted.
On reload, `plugin_reloaded` tells the UI to rebuild the plugin runtime registry.
When the package content hash changes, plugin ESM URLs change and React layout
components are loaded from the new package. Existing plugin layout instances may
remount and lose local React state; host-persisted layout state written through
`setState` remains the durable state channel.
JavaScript module instances cannot be forcibly removed from the browser ESM
cache. Register handlers, layouts, watches and subscriptions through the SDK
registries and push disposable side effects into `ctx.subscriptions`; global
module side effects can otherwise survive a reload. If the package hash did not
change, the reload is idempotent and may reuse the same module instance.
IdeA runtime plugin installation is separate from Codex plugin/skill discovery.
See [Codex Skills And IdeA Plugins](codex-skills-and-idea-plugins.md).

View File

@ -10,9 +10,18 @@ await ctx.services?.windows.open({
});
```
`windows.open` is the public API for commands that need to make feedback visible
to a human. A command handler may still return structured data, but menu clicks
do not currently guarantee that return value is displayed.
## Contract
- `layoutType` must match a layout type declared by the calling plugin.
- The plugin must declare `capabilities: ["ui"]` when it relies on plugin
layouts/windows as a human-facing surface. Add `"tooling"` only when the
plugin also uses tooling/workspace/task services.
- There is no `contributes.windows` manifest key. Windows always host an
existing `contributes.layouts` layout.
- The host validates the plugin is runtime-active and the layout exists before
opening or focusing the window.
- Reopening the same plugin/layout pair focuses the existing window.
@ -38,9 +47,13 @@ The returned `label` is a host-owned window identity. Treat it as opaque.
```ts
ctx.commands?.registerCommand("hello-plugin.open-dashboard", async () => {
return ctx.services?.windows.open({
const win = await ctx.services?.windows.open({
layoutType: "hello-plugin.dashboard",
state: { source: "menu" }
});
return win
? { status: "opened", message: "Opened dashboard.", alreadyOpen: win.alreadyOpen }
: { status: "skipped", reason: "ui-service-unavailable", message: "UI service unavailable." };
});
```