Compare commits

..

2 Commits

9 changed files with 277 additions and 9 deletions

View File

@ -16,6 +16,7 @@ exports `activate(ctx)`.
- [Windows](docs/windows.md): opening plugin layouts in detached OS windows.
- [Services](docs/services.md): workspace, tasks, tooling, events, config, terminal and windows facades.
- [Packaging And Distribution](docs/packaging-distribution.md): build output, archive layout, hot reload and dependency rules.
- [Codex Skills And IdeA Plugins](docs/codex-skills-and-idea-plugins.md): separate IdeA-native plugin skills from Codex plugin discovery.
The installable example lives in [`examples/hello-plugin`](examples/hello-plugin).
It demonstrates a menu command, storage, background-task feedback, a React layout

View File

@ -0,0 +1,51 @@
# Codex Skills And IdeA Plugins
IdeA runtime plugins, IdeA-native plugin skills and Codex plugins are distinct
installation and discovery systems.
An IdeA runtime plugin is described by `idea-plugin.json`. Installing it through
IdeA makes its global runtime package available. Project settings then decide
which installed plugins are enabled for a project, and agent settings decide
which plugin skills and plugin MCP tools are assigned to each agent.
An IdeA-native plugin skill is declared in `idea-plugin.json` under
`contributes.skills`. Its body is a package-relative Markdown file such as
`skills/<name>/SKILL.md`. When the plugin is enabled for a project and the skill
is assigned to an agent, IdeA injects the skill affordance into that agent's
skill catalogue and serves the body through `idea_skill_read`. This is not
Codex-specific; it is projected by IdeA for every harness that supports IdeA
capabilities, including Claude Code, Codex, OpenCode local and OpenCode cloud.
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 makes the package available globally,
but does not automatically enable it in every project.
- Enabling a plugin for a project does not automatically assign every skill or
MCP tool to every agent.
- A plugin skill becomes visible to an agent only when the plugin is enabled for
the project and that skill is assigned to that agent.
- Installing or reloading an IdeA plugin does not automatically install a bundled
Codex plugin, and `.codex-plugin/plugin.json` remains inert for IdeA-native
plugin loading unless a separate bridge explicitly consumes it.
- Bundling `.codex-plugin/plugin.json` is still allowed as distribution content
for Codex-specific workflows, but it is not the multi-harness IdeA contract.
## Required Mechanisms
For multi-harness IdeA agent skills, declare `contributes.skills` in
`idea-plugin.json`, ship the referenced Markdown files inside the package,
install the plugin in IdeA, enable it in the target project, and assign the
skills to the target agents.
For Codex-specific plugin skills, install the Codex plugin through the Codex
plugin mechanism for the intended scope, then assign or enable those skills
according to Codex/IdeA agent configuration.
Do not rely on `.codex-plugin/plugin.json` as a cross-harness skill propagation
mechanism. Cross-harness plugin skill availability is driven by
`idea-plugin.json` and IdeA project/agent assignments.

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

@ -143,10 +143,99 @@ development loop.
## Contributions
`contributes.skills` declares read-only agent skills shipped inside the IdeA
plugin package. These skills are IdeA-native: IdeA can expose them in project
plugin settings, assign them to agents, inject them into the agent skill
catalogue, and serve their Markdown body through `idea_skill_read` for every
supported harness that receives IdeA capabilities, including Claude Code,
Codex, OpenCode local and OpenCode cloud.
```json
{
"contributes": {
"skills": [
{
"id": "unity.build-debug",
"name": "unity-build-debug",
"description": "Diagnose Unity build failures.",
"kind": "workflow",
"path": "skills/unity-build-debug/SKILL.md"
}
]
}
}
```
Skill contribution rules:
- `id` is stable within the plugin and is used by project/agent assignments.
- `name` is the agent-facing name shown in the injected skill catalogue and
accepted by `idea_skill_read`.
- `description` is the short affordance shown before the agent loads the skill.
- `kind` defaults to `"workflow"`; `"reference"` is also supported.
- `path` must be a package-relative Markdown file path ending in `.md`.
- Installing a plugin globally is not enough: a project must enable the plugin,
then assign the skill to the target agent.
`contributes.layouts` is the only surface for plugin layouts. The same layout
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." };
});
```

View File

@ -6,6 +6,8 @@ export type {
IdeAPluginLayoutContribution,
IdeAPluginMcpServerContribution,
IdeAPluginMenuItemContribution,
IdeAPluginSkillContribution,
IdeAPluginSkillKind,
IdeAPluginTopLevelMenuContribution
} from "./manifest.js";
export {

View File

@ -14,6 +14,7 @@ export interface IdeAPluginManifest {
*/
activationScope?: IdeAPluginActivationScope;
contributes?: {
skills?: IdeAPluginSkillContribution[];
menus?: IdeAPluginTopLevelMenuContribution[];
menuItems?: IdeAPluginMenuItemContribution[];
slashCommands?: IdeAPluginSlashCommandContribution[];
@ -26,10 +27,24 @@ export type IdeAPluginCapability = "ui" | "mcp" | "tooling";
export type IdeAPluginActivationScope = "app" | "project";
export type IdeAPluginSkillKind = "workflow" | "reference";
export interface IdeAPluginEngineConstraints {
idea?: string;
}
export interface IdeAPluginSkillContribution {
id: string;
/** Agent-facing name shown in the injected skill catalogue and accepted by idea_skill_read. */
name: string;
/** Short affordance shown to agents before they load the skill body. */
description: string;
/** Defaults to "workflow". */
kind?: IdeAPluginSkillKind;
/** Package-relative Markdown file, usually skills/<name>/SKILL.md. */
path: string;
}
export interface IdeAPluginTopLevelMenuContribution {
id: string;
label: string;
@ -192,6 +207,20 @@ function validateContributes(value: unknown, errors: string[]): void {
return;
}
validateArray(value, "skills", errors, (skill, index) => {
requireString(skill, "id", errors, `contributes.skills[${index}].id`);
requireString(skill, "name", errors, `contributes.skills[${index}].name`);
requireString(skill, "description", errors, `contributes.skills[${index}].description`);
requireString(skill, "path", errors, `contributes.skills[${index}].path`);
if (typeof skill.kind === "string" && skill.kind !== "workflow" && skill.kind !== "reference") {
errors.push(`contributes.skills[${index}].kind must be "workflow" or "reference" when provided`);
} else if (skill.kind !== undefined && typeof skill.kind !== "string") {
errors.push(`contributes.skills[${index}].kind must be a string when provided`);
}
if (typeof skill.path === "string" && !skill.path.endsWith(".md")) {
errors.push(`contributes.skills[${index}].path must point to a .md file`);
}
});
validateArray(value, "menus", errors, (menu, index) => {
requireString(menu, "id", errors, `contributes.menus[${index}].id`);
requireString(menu, "label", errors, `contributes.menus[${index}].label`);