feat(sdk,plugins): ajoute capability tooling et services runtime publics (workspace/terminal/tasks)
- capability manifeste tooling supportée backend + transport runtime catalog - ctx.services publique côté SDK/runtime, gated par tooling - surface honnête: workspace, terminal, et tasks en observation/contrôle uniquement - docs/exemple/tests mis à jour - QA PASS sur feature/sdk-plugin-tooling-build-debug-surface
This commit is contained in:
@ -6,6 +6,7 @@ This first version intentionally stays small:
|
||||
|
||||
- public manifest types for `idea-plugin.json`;
|
||||
- public runtime types for plugin modules exposing `activate(ctx)`;
|
||||
- a stable `ctx.services` facade for workspace, background task and terminal operations;
|
||||
- a lightweight manifest validator;
|
||||
- a minimal `examples/hello-plugin` plugin.
|
||||
|
||||
@ -69,6 +70,42 @@ export function activate(ctx: ActivateContext): void {
|
||||
}
|
||||
```
|
||||
|
||||
## Runtime Services
|
||||
|
||||
Plugins declaring the `tooling` capability receive `ctx.services`. Plugins
|
||||
without that capability do not receive this facade. Prefer `ctx.services` over
|
||||
IdeA's internal runtime objects when it is available:
|
||||
|
||||
```ts
|
||||
import type { ActivateContext } from "@idea/plugin-sdk";
|
||||
|
||||
export async function activate(ctx: ActivateContext): Promise<void> {
|
||||
const project = await ctx.services?.workspace.getCurrentProject();
|
||||
ctx.logger.info("current project", project);
|
||||
|
||||
const task = await ctx.services?.tasks.getStatus("task-id");
|
||||
ctx.logger.info("task status", task?.status);
|
||||
|
||||
const terminal = await ctx.services?.terminal.open({ rows: 24, cols: 80 });
|
||||
await terminal?.write(new TextEncoder().encode("echo hello\\r"));
|
||||
}
|
||||
```
|
||||
|
||||
Current terminal scope is intentionally minimal: it opens or reattaches a shell
|
||||
PTY, writes bytes, resizes, detaches and closes. The background task service is
|
||||
observation/control only in this SDK version: `list`, `getStatus`, `attachOutput`,
|
||||
`cancel` and `retry` operate on existing tasks visible through IdeA's Work read
|
||||
model. Starting new background tasks is not part of the public plugin API in this
|
||||
lot.
|
||||
|
||||
Declare the additive `tooling` capability to receive `ctx.services` at runtime:
|
||||
|
||||
```json
|
||||
{
|
||||
"capabilities": ["ui", "tooling"]
|
||||
}
|
||||
```
|
||||
|
||||
## Manifest Validation
|
||||
|
||||
```ts
|
||||
|
||||
@ -8,6 +8,7 @@ It exercises the current plugin primitives end to end:
|
||||
- menu entry: `hello-plugin`;
|
||||
- command: `hello-plugin`, returning `hello-world`;
|
||||
- layout contribution: `hello-plugin.hello-world`, rendered as `hello-world`.
|
||||
- tooling capability: logs the focused workspace project when `ctx.services` is available.
|
||||
|
||||
```sh
|
||||
npm run typecheck:examples
|
||||
@ -25,6 +26,7 @@ During activation the plugin logs:
|
||||
- whether the command and layout runtime registries are available;
|
||||
- successful registration of the `hello-plugin` command;
|
||||
- successful registration of the `hello-plugin.hello-world` layout;
|
||||
- availability of the workspace service from the `tooling` runtime capability;
|
||||
- the first layout render, including project/node identifiers.
|
||||
|
||||
These messages are intentionally small and stable so installation, bundle import, activation and
|
||||
|
||||
@ -11,7 +11,8 @@
|
||||
},
|
||||
"trustLevel": "full",
|
||||
"capabilities": [
|
||||
"ui"
|
||||
"ui",
|
||||
"tooling"
|
||||
],
|
||||
"contributes": {
|
||||
"menus": [
|
||||
|
||||
@ -71,6 +71,13 @@ export function activate(ctx: ActivateContext): void {
|
||||
} else {
|
||||
ctx.logger.warn("layout registry unavailable", { layoutType: LAYOUT_TYPE });
|
||||
}
|
||||
|
||||
void ctx.services?.workspace.getCurrentProject().then((project) => {
|
||||
ctx.logger.info("workspace service available", {
|
||||
projectId: project?.id ?? null,
|
||||
hasProjectRoot: Boolean(project?.root)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const plugin: IdeAPluginModule = {
|
||||
|
||||
@ -17,7 +17,19 @@ export type {
|
||||
CommandDisposable,
|
||||
CommandHandler,
|
||||
CommandRegistry,
|
||||
BackgroundTaskOutputAttachment,
|
||||
BackgroundTaskRetryResult,
|
||||
BackgroundTaskService,
|
||||
BackgroundTaskStatus,
|
||||
IdeAPluginModule,
|
||||
PluginLogger,
|
||||
PluginStorage
|
||||
PluginServices,
|
||||
PluginStorage,
|
||||
TerminalOpenOptions,
|
||||
TerminalReattachOptions,
|
||||
TerminalReattachResult,
|
||||
TerminalService,
|
||||
TerminalSession,
|
||||
WorkspaceProject,
|
||||
WorkspaceService
|
||||
} from "./runtime.js";
|
||||
|
||||
@ -59,8 +59,8 @@ function validateCapabilities(value, errors) {
|
||||
return;
|
||||
}
|
||||
value.forEach((capability, index) => {
|
||||
if (capability !== "ui" && capability !== "mcp") {
|
||||
errors.push(`capabilities[${index}] must be "ui" or "mcp"`);
|
||||
if (capability !== "ui" && capability !== "mcp" && capability !== "tooling") {
|
||||
errors.push(`capabilities[${index}] must be "ui", "mcp" or "tooling"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ export interface IdeAPluginManifest {
|
||||
};
|
||||
}
|
||||
|
||||
export type IdeAPluginCapability = "ui" | "mcp";
|
||||
export type IdeAPluginCapability = "ui" | "mcp" | "tooling";
|
||||
|
||||
export interface IdeAPluginEngineConstraints {
|
||||
idea?: string;
|
||||
@ -145,8 +145,8 @@ function validateCapabilities(value: unknown, errors: string[]): void {
|
||||
}
|
||||
|
||||
value.forEach((capability, index) => {
|
||||
if (capability !== "ui" && capability !== "mcp") {
|
||||
errors.push(`capabilities[${index}] must be "ui" or "mcp"`);
|
||||
if (capability !== "ui" && capability !== "mcp" && capability !== "tooling") {
|
||||
errors.push(`capabilities[${index}] must be "ui", "mcp" or "tooling"`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -4,6 +4,12 @@ export interface ActivateContext {
|
||||
subscriptions: CommandDisposable[];
|
||||
commands?: CommandRegistry;
|
||||
storage?: PluginStorage;
|
||||
/**
|
||||
* Stable public service facade for plugins that need workspace, background
|
||||
* task, or terminal operations. This intentionally does not expose IdeA's
|
||||
* internal runtime/gateway objects.
|
||||
*/
|
||||
services?: PluginServices;
|
||||
}
|
||||
|
||||
export interface IdeAPluginModule {
|
||||
@ -34,3 +40,103 @@ export interface PluginStorage {
|
||||
delete(key: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface PluginServices {
|
||||
workspace: WorkspaceService;
|
||||
tasks: BackgroundTaskService;
|
||||
terminal: TerminalService;
|
||||
}
|
||||
|
||||
export interface WorkspaceProject {
|
||||
id: string;
|
||||
name: string;
|
||||
root: string;
|
||||
}
|
||||
|
||||
export interface WorkspaceService {
|
||||
/** Returns the currently focused project, or null when no project is active. */
|
||||
getCurrentProject(): Promise<WorkspaceProject | null>;
|
||||
/** Returns the root path for the given project or for the current project. */
|
||||
getProjectRoot(projectId?: string): Promise<string>;
|
||||
/** Reads IdeA's shared project context for the given or current project. */
|
||||
readProjectContext(projectId?: string): Promise<string>;
|
||||
/** Updates IdeA's shared project context for the given or current project. */
|
||||
updateProjectContext(content: string, projectId?: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface BackgroundTaskStatus {
|
||||
taskId: string;
|
||||
ownerAgentId: string;
|
||||
projectId: string;
|
||||
kind: string;
|
||||
status: "pending" | "running" | "completed" | "failed" | "cancelled" | "delivered";
|
||||
exitCode: number | null;
|
||||
summary: string | null;
|
||||
stdoutTail: string | null;
|
||||
stderrTail: string | null;
|
||||
updatedAtMs: number;
|
||||
}
|
||||
|
||||
export interface BackgroundTaskOutputAttachment {
|
||||
taskId: string;
|
||||
scrollback: Uint8Array;
|
||||
live: boolean;
|
||||
detach(): void;
|
||||
}
|
||||
|
||||
export interface BackgroundTaskRetryResult {
|
||||
/** Present when the host reports the replacement task id. */
|
||||
taskId?: string;
|
||||
}
|
||||
|
||||
export interface BackgroundTaskService {
|
||||
/** Lists background tasks visible in the project work-state read model. */
|
||||
list(projectId?: string): Promise<BackgroundTaskStatus[]>;
|
||||
/** Reads one task status from the project work-state read model. */
|
||||
getStatus(taskId: string, projectId?: string): Promise<BackgroundTaskStatus | null>;
|
||||
/** Attaches to retained/live output for a task. */
|
||||
attachOutput(
|
||||
taskId: string,
|
||||
onData: (bytes: Uint8Array) => void,
|
||||
): Promise<BackgroundTaskOutputAttachment>;
|
||||
/** Cancels a pending/running task. */
|
||||
cancel(taskId: string): Promise<void>;
|
||||
/** Retries a failed/cancelled task; future hosts may return the new task id. */
|
||||
retry(taskId: string): Promise<BackgroundTaskRetryResult>;
|
||||
}
|
||||
|
||||
export interface TerminalOpenOptions {
|
||||
cwd?: string;
|
||||
rows?: number;
|
||||
cols?: number;
|
||||
onData?: (bytes: Uint8Array) => void;
|
||||
}
|
||||
|
||||
export interface TerminalReattachOptions {
|
||||
onData?: (bytes: Uint8Array) => void;
|
||||
}
|
||||
|
||||
export interface TerminalSession {
|
||||
readonly sessionId: string;
|
||||
write(data: Uint8Array): Promise<void>;
|
||||
resize(rows: number, cols: number): Promise<void>;
|
||||
detach(): void;
|
||||
close(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface TerminalReattachResult {
|
||||
session: TerminalSession;
|
||||
scrollback: Uint8Array;
|
||||
}
|
||||
|
||||
export interface TerminalService {
|
||||
/**
|
||||
* Opens a shell PTY in the requested/current project directory. This MVP is a
|
||||
* terminal control surface, not a command runner; use tasks for build/test
|
||||
* commands that should be tracked in the Work panel.
|
||||
*/
|
||||
open(options?: TerminalOpenOptions): Promise<TerminalSession>;
|
||||
/** Reattaches to an already-running PTY and returns retained scrollback. */
|
||||
reattach(sessionId: string, options?: TerminalReattachOptions): Promise<TerminalReattachResult>;
|
||||
/** Kills a PTY by id. */
|
||||
close(sessionId: string): Promise<void>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user