Compare commits
2 Commits
9829ccdf2a
...
6eb8db3791
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eb8db3791 | |||
| 1239104b0f |
@ -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
|
||||
|
||||
51
docs/codex-skills-and-idea-plugins.md
Normal file
51
docs/codex-skills-and-idea-plugins.md
Normal 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.
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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`;
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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." };
|
||||
});
|
||||
```
|
||||
|
||||
@ -6,6 +6,8 @@ export type {
|
||||
IdeAPluginLayoutContribution,
|
||||
IdeAPluginMcpServerContribution,
|
||||
IdeAPluginMenuItemContribution,
|
||||
IdeAPluginSkillContribution,
|
||||
IdeAPluginSkillKind,
|
||||
IdeAPluginTopLevelMenuContribution
|
||||
} from "./manifest.js";
|
||||
export {
|
||||
|
||||
@ -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`);
|
||||
|
||||
Reference in New Issue
Block a user