Le runtime SDK expose désormais react/react-dom résolus contre l'instance hébergée par IdeA plutôt qu'une copie embarquée, pour que les layouts plugin en JSX/hooks partagent le même arbre React que l'host. hello-plugin migre son layout d'exemple en .tsx pour illustrer le contrat. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
604 lines
19 KiB
TypeScript
604 lines
19 KiB
TypeScript
import type { ComponentType, ReactNode } from "react";
|
|
|
|
export interface ActivateContext {
|
|
pluginId: string;
|
|
logger: PluginLogger;
|
|
subscriptions: CommandDisposable[];
|
|
commands?: CommandRegistry;
|
|
layouts?: LayoutRegistry;
|
|
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 {
|
|
activate(ctx: ActivateContext): void | Promise<void>;
|
|
deactivate?(): void | Promise<void>;
|
|
}
|
|
|
|
export interface PluginLogger {
|
|
debug(message: string, ...args: unknown[]): void;
|
|
info(message: string, ...args: unknown[]): void;
|
|
warn(message: string, ...args: unknown[]): void;
|
|
error(message: string, ...args: unknown[]): void;
|
|
}
|
|
|
|
export type CommandHandler = (...args: unknown[]) => unknown | Promise<unknown>;
|
|
|
|
export interface CommandRegistry {
|
|
registerCommand(commandId: string, handler: CommandHandler): CommandDisposable;
|
|
}
|
|
|
|
export interface CommandDisposable {
|
|
dispose(): void;
|
|
}
|
|
|
|
export interface PluginStorage {
|
|
/**
|
|
* Plugin-owned persistent state. Use this for internal counters, flags,
|
|
* preferences and caches that should not be written into the user's project.
|
|
*/
|
|
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
set<T = unknown>(key: string, value: T): Promise<void>;
|
|
delete(key: string): Promise<void>;
|
|
}
|
|
|
|
export type PluginLayoutState = JsonValue | undefined;
|
|
export type PluginLayoutAvailability = "available";
|
|
export type PluginLayoutRenderResult = ReactNode;
|
|
|
|
export interface PluginLayoutProps<TState extends PluginLayoutState = PluginLayoutState> {
|
|
/** Project currently hosting this layout cell. */
|
|
projectId: string;
|
|
/** Stable layout node id for this cell instance. */
|
|
nodeId: string;
|
|
/** Layout contribution type declared in `idea-plugin.json`. */
|
|
layoutType: string;
|
|
/** Opaque JSON-serializable state persisted by the host for this cell. */
|
|
state: TState;
|
|
/** Replaces the opaque state for this cell. Values must be JSON-serializable. */
|
|
setState(next: TState): void;
|
|
/** Present layouts are only mounted when available; fallback UI is host-owned. */
|
|
availability: PluginLayoutAvailability;
|
|
}
|
|
|
|
export type PluginLayoutComponent<TState extends PluginLayoutState = PluginLayoutState> =
|
|
ComponentType<PluginLayoutProps<TState>>;
|
|
|
|
export interface PluginLayoutDefinition<TState extends PluginLayoutState = PluginLayoutState> {
|
|
/** Must match a layout `type` declared in this plugin's manifest. */
|
|
type: string;
|
|
component: PluginLayoutComponent<TState>;
|
|
}
|
|
|
|
export interface LayoutRegistry {
|
|
register<TState extends PluginLayoutState = PluginLayoutState>(
|
|
definition: PluginLayoutDefinition<TState>,
|
|
): CommandDisposable;
|
|
}
|
|
|
|
export interface PluginServices {
|
|
workspace: WorkspaceService;
|
|
tasks: BackgroundTaskService;
|
|
tooling: ToolingService;
|
|
events: EventService;
|
|
config: ConfigDocumentService;
|
|
terminal: TerminalService;
|
|
windows: WindowService;
|
|
}
|
|
|
|
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>;
|
|
/**
|
|
* Resolves and normalizes a plugin-visible path under the project root.
|
|
* Rejects absolute paths, `..`, empty segments and other paths the host
|
|
* considers outside the workspace sandbox.
|
|
*/
|
|
resolvePath(path: string, projectId?: string): Promise<WorkspaceResolvedPath>;
|
|
/** Reads a UTF-8 text file under the project root. */
|
|
readTextFile(path: string, projectId?: string): Promise<WorkspaceTextFile>;
|
|
/** Reads raw bytes from a file under the project root. */
|
|
readBinaryFile(path: string, projectId?: string): Promise<WorkspaceBinaryFile>;
|
|
/** Writes UTF-8 text under the project root using the host's controlled write path. */
|
|
writeTextFile(path: string, content: string, projectId?: string): Promise<void>;
|
|
/** Writes raw bytes under the project root using the host's controlled write path. */
|
|
writeBinaryFile(path: string, bytes: Uint8Array, projectId?: string): Promise<void>;
|
|
/** Lists one directory under the project root. Defaults to the workspace root. */
|
|
listDirectory(path?: string, projectId?: string): Promise<WorkspaceDirectoryListing>;
|
|
/**
|
|
* Returns basic metadata. Missing paths resolve to `{ exists: false }`; invalid
|
|
* paths and permission errors reject.
|
|
*/
|
|
stat(path: string, projectId?: string): Promise<WorkspaceStat>;
|
|
/**
|
|
* Extension point for host file watching. The MVP SDK reserves the public
|
|
* shape; hosts may reject with a clear not-implemented error until #127 lands.
|
|
* Plugins must treat watch setup as best-effort and non-fatal.
|
|
*/
|
|
watch(path: string, handler: WorkspaceWatchHandler, projectId?: string): Promise<WorkspaceWatch>;
|
|
/** Queries a bounded, generic project structure read model. */
|
|
queryStructure(query?: WorkspaceStructureQuery): Promise<ProjectStructure>;
|
|
}
|
|
|
|
export interface WorkspaceResolvedPath {
|
|
projectId: string;
|
|
root: string;
|
|
path: string;
|
|
}
|
|
|
|
export interface WorkspaceTextFile {
|
|
path: string;
|
|
content: string;
|
|
}
|
|
|
|
export interface WorkspaceBinaryFile {
|
|
path: string;
|
|
bytes: Uint8Array;
|
|
}
|
|
|
|
export interface WorkspaceDirEntry {
|
|
name: string;
|
|
path: string;
|
|
isDir: boolean;
|
|
}
|
|
|
|
export interface WorkspaceDirectoryListing {
|
|
path: string;
|
|
entries: WorkspaceDirEntry[];
|
|
}
|
|
|
|
export interface WorkspaceStat {
|
|
path: string;
|
|
exists: boolean;
|
|
isFile: boolean;
|
|
isDir: boolean;
|
|
len: number | null;
|
|
}
|
|
|
|
export interface WorkspaceWatchEvent {
|
|
path: string;
|
|
kind: "created" | "modified" | "deleted" | "renamed" | "unknown";
|
|
operation: string;
|
|
projectId: string;
|
|
}
|
|
|
|
export type WorkspaceWatchHandler = (event: WorkspaceWatchEvent) => void;
|
|
|
|
export interface WorkspaceWatch {
|
|
dispose(): void;
|
|
}
|
|
|
|
export interface WorkspaceStructureQuery {
|
|
projectId?: string;
|
|
path?: string;
|
|
maxDepth?: number;
|
|
maxEntries?: number;
|
|
}
|
|
|
|
export type ProjectStructureEntryKind = "file" | "directory";
|
|
|
|
export interface ProjectStructureEntry {
|
|
path: string;
|
|
name: string;
|
|
kind: ProjectStructureEntryKind;
|
|
}
|
|
|
|
export interface ProjectConvention {
|
|
id: string;
|
|
markerPath: string;
|
|
}
|
|
|
|
export interface ProjectModule {
|
|
path: string;
|
|
markerPath: string;
|
|
conventionId: string;
|
|
}
|
|
|
|
export interface ProjectStructure {
|
|
projectId: string;
|
|
rootPath: string;
|
|
entries: ProjectStructureEntry[];
|
|
conventions: ProjectConvention[];
|
|
modules: ProjectModule[];
|
|
truncated: boolean;
|
|
}
|
|
|
|
export interface BackgroundTaskStatus {
|
|
taskId: string;
|
|
/**
|
|
* Agent that owns completion delivery and Work attribution for this task.
|
|
* This is host-assigned for existing tasks and should be treated as an opaque
|
|
* agent id by plugins.
|
|
*/
|
|
ownerAgentId: string;
|
|
projectId: string;
|
|
kind: string;
|
|
/**
|
|
* Work read-model status. A skipped plugin command is not a background task
|
|
* and therefore never appears here; skipped commands should be reported by
|
|
* the command handler return value and plugin logs.
|
|
*/
|
|
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 RunCommandTaskOptions {
|
|
/** Project that owns the command workspace. Defaults to the focused project. */
|
|
projectId?: string;
|
|
/**
|
|
* Real agent id used by IdeA Work for ownership, cancellation and completion
|
|
* delivery. Plugins must obtain this from host/plugin state for the workflow
|
|
* they are serving; placeholder ids are only acceptable in isolated examples.
|
|
*/
|
|
ownerAgentId: string;
|
|
/** Human-facing label shown in Work. Defaults to the command line. */
|
|
label?: string;
|
|
/** Executable to run. Arguments are passed separately, without shell parsing. */
|
|
command: string;
|
|
/** Arguments passed to the executable. */
|
|
args?: string[];
|
|
/** Relative working directory under the project root. Defaults to the root. */
|
|
cwd?: string;
|
|
/** Extra environment variables for the command. */
|
|
env?: Record<string, string> | Array<[string, string]>;
|
|
/**
|
|
* When true, completion is recorded without waking the owner agent. This does
|
|
* not hide the task from Work/background-task surfaces and does not represent
|
|
* a skipped command. If preconditions fail, return readable command feedback
|
|
* instead of launching a record-only task.
|
|
*/
|
|
recordOnly?: boolean;
|
|
/** Optional absolute deadline, epoch milliseconds. */
|
|
deadlineMs?: number;
|
|
}
|
|
|
|
export interface CommandTaskStatus {
|
|
taskId: string;
|
|
/**
|
|
* Agent that owns this command task. The host uses it for correlation,
|
|
* cancellation and completion delivery.
|
|
*/
|
|
ownerAgentId: string;
|
|
projectId: string;
|
|
kind: string;
|
|
/**
|
|
* Lifecycle state of a command task that was actually launched. There is no
|
|
* `skipped` state: skipped commands are command-handler feedback, not tasks.
|
|
*/
|
|
state: "queued" | "running" | "waiting" | "completed" | "failed" | "cancelled" | "expired";
|
|
exitCode: number | null;
|
|
summary: string | null;
|
|
stdoutTail: string | null;
|
|
stderrTail: string | null;
|
|
createdAtMs: number;
|
|
updatedAtMs: number;
|
|
}
|
|
|
|
export interface ToolRequirement {
|
|
/** Stable id chosen by the plugin for this executable prerequisite. */
|
|
id: string;
|
|
/** Executable name or path to probe. */
|
|
executable: string;
|
|
/** Version/diagnostic arguments. Defaults host-side to `--version`. */
|
|
versionArgs?: string[];
|
|
/** Whether this tool must pass for the whole diagnostic to be ok. */
|
|
required?: boolean;
|
|
/** Extra environment variables for this probe. */
|
|
env?: Record<string, string> | Array<[string, string]>;
|
|
}
|
|
|
|
export interface EnvRequirement {
|
|
/** Environment variable name. */
|
|
name: string;
|
|
/** Whether the variable must be present and match. */
|
|
required?: boolean;
|
|
/** Optional exact expected value. */
|
|
equals?: string;
|
|
}
|
|
|
|
export interface FileRequirement {
|
|
/** Relative workspace path. */
|
|
path: string;
|
|
/** Whether the path must exist and match `kind`. */
|
|
required?: boolean;
|
|
/** Expected workspace path kind. */
|
|
kind?: "file" | "directory" | "any";
|
|
}
|
|
|
|
export interface ToolchainDiagnosticRequest {
|
|
/** Project to inspect. Defaults to the focused project. */
|
|
projectId?: string;
|
|
/** Relative working directory under the project root. Defaults to the root. */
|
|
cwd?: string;
|
|
/** Executable probes to run. */
|
|
tools?: ToolRequirement[];
|
|
/** Environment variable prerequisites to inspect. */
|
|
env?: EnvRequirement[];
|
|
/** Workspace file prerequisites to validate. */
|
|
files?: FileRequirement[];
|
|
}
|
|
|
|
export interface ToolchainDiagnostic {
|
|
projectId: string;
|
|
cwd: string;
|
|
ok: boolean;
|
|
tools: ToolDiagnostic[];
|
|
env: EnvDiagnostic[];
|
|
files: FileDiagnostic[];
|
|
messages: DiagnosticMessage[];
|
|
}
|
|
|
|
export interface ToolDiagnostic {
|
|
id: string;
|
|
executable: string;
|
|
present: boolean;
|
|
ok: boolean;
|
|
status: "ok" | "failed" | "missing";
|
|
required: boolean;
|
|
exitCode: number | null;
|
|
version: string | null;
|
|
stdout: string | null;
|
|
stderr: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export interface EnvDiagnostic {
|
|
name: string;
|
|
present: boolean;
|
|
ok: boolean;
|
|
required: boolean;
|
|
value: string | null;
|
|
status: "ok" | "missing" | "mismatch";
|
|
}
|
|
|
|
export interface FileDiagnostic {
|
|
path: string;
|
|
exists: boolean;
|
|
ok: boolean;
|
|
required: boolean;
|
|
kind: "file" | "directory" | "other" | "missing";
|
|
expectedKind: "file" | "directory" | "any" | null;
|
|
len: number | null;
|
|
}
|
|
|
|
export interface DiagnosticMessage {
|
|
level: "info" | "warning" | "error";
|
|
message: string;
|
|
}
|
|
|
|
export interface ToolingService {
|
|
/** Runs generic external-toolchain diagnostics for executables, env and files. */
|
|
diagnose(request: ToolchainDiagnosticRequest): Promise<ToolchainDiagnostic>;
|
|
}
|
|
|
|
export type PublicEventType = "workspaceFileChanged" | "backgroundTaskChanged";
|
|
|
|
export type PublicEvent = WorkspaceFileChangedEvent | BackgroundTaskChangedEvent;
|
|
|
|
export interface WorkspaceFileChangedEvent {
|
|
type: "workspaceFileChanged";
|
|
sequence: number;
|
|
occurredAtMs: number;
|
|
projectId: string;
|
|
path: string;
|
|
operation: string;
|
|
}
|
|
|
|
export interface BackgroundTaskChangedEvent {
|
|
type: "backgroundTaskChanged";
|
|
sequence: number;
|
|
occurredAtMs: number;
|
|
projectId: string;
|
|
taskId: string;
|
|
ownerAgentId: string;
|
|
state: string;
|
|
}
|
|
|
|
export interface EventSubscribeOptions {
|
|
/** Project to observe. Defaults to the focused project. */
|
|
projectId?: string;
|
|
/** Public event types to retain. Empty/omitted means every supported event. */
|
|
eventTypes?: PublicEventType[];
|
|
/** Per-subscription retained capacity. Host clamps to its supported bounds. */
|
|
capacity?: number;
|
|
/** Polling cadence used by the runtime facade. Defaults to 1000 ms. */
|
|
pollIntervalMs?: number;
|
|
/** Maximum events drained per poll. Host clamps to its supported bounds. */
|
|
maxEventsPerPoll?: number;
|
|
/** Called when the host reports dropped retained events for this subscription. */
|
|
onDropped?: (count: number) => void;
|
|
}
|
|
|
|
export interface EventSubscription {
|
|
readonly subscriptionId: string;
|
|
readonly projectId: string;
|
|
readonly eventTypes: PublicEventType[];
|
|
readonly retention: string;
|
|
dispose(): void;
|
|
}
|
|
|
|
export type EventHandler = (event: PublicEvent) => void;
|
|
|
|
export interface EventService {
|
|
/** Subscribes to stable, best-effort bounded public host/project events. */
|
|
subscribe(options: EventSubscribeOptions, handler: EventHandler): Promise<EventSubscription>;
|
|
}
|
|
|
|
export type JsonValue =
|
|
| null
|
|
| boolean
|
|
| number
|
|
| string
|
|
| JsonValue[]
|
|
| { [key: string]: JsonValue };
|
|
|
|
export type ConfigDocumentFormat = "json";
|
|
export type ConfigUpdateMode = "mergePatch" | "replace";
|
|
|
|
export interface ConfigDocumentReadOptions {
|
|
/** Project that owns the config document. Defaults to the focused project. */
|
|
projectId?: string;
|
|
/** Relative path under the project root. */
|
|
path: string;
|
|
/** Explicit format. Omit to infer from extension. First lot supports only `json`. */
|
|
format?: ConfigDocumentFormat;
|
|
}
|
|
|
|
export interface ConfigDocumentUpdateOptions extends ConfigDocumentReadOptions {
|
|
/** Update mode. Defaults host-side to `mergePatch`. */
|
|
mode?: ConfigUpdateMode;
|
|
/** Replacement value or JSON merge patch. */
|
|
value: JsonValue;
|
|
}
|
|
|
|
export interface ConfigDocument<T extends JsonValue = JsonValue> {
|
|
projectId: string;
|
|
path: string;
|
|
format: ConfigDocumentFormat;
|
|
value: T;
|
|
}
|
|
|
|
export interface ConfigDocumentWriteResult {
|
|
projectId: string;
|
|
path: string;
|
|
format: ConfigDocumentFormat;
|
|
mode: ConfigUpdateMode;
|
|
bytesWritten: number;
|
|
}
|
|
|
|
export interface ConfigDocumentService {
|
|
/** Reads and parses a structured config document. First lot supports JSON only. */
|
|
readDocument<T extends JsonValue = JsonValue>(
|
|
options: ConfigDocumentReadOptions,
|
|
): Promise<ConfigDocument<T>>;
|
|
/** Writes a full replacement or JSON merge patch. First lot supports JSON only. */
|
|
updateDocument(options: ConfigDocumentUpdateOptions): Promise<ConfigDocumentWriteResult>;
|
|
}
|
|
|
|
export interface BackgroundTaskService {
|
|
/**
|
|
* Launches a non-interactive command as a first-class IdeA background task.
|
|
*
|
|
* Call this only after command preconditions are satisfied. The returned
|
|
* `CommandTaskStatus` means a task exists and can be inspected through command
|
|
* status APIs and Work/background-task surfaces. A plugin command that decides
|
|
* not to launch work should return readable command feedback, for example
|
|
* `{ status: "skipped", reason, message }`, and should not call `runCommand()`.
|
|
*/
|
|
runCommand(options: RunCommandTaskOptions): Promise<CommandTaskStatus>;
|
|
/** Reads one command task directly from the host task store. */
|
|
getCommandStatus(taskId: string): Promise<CommandTaskStatus | null>;
|
|
/** 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>;
|
|
}
|
|
|
|
export interface OpenPluginWindowOptions {
|
|
/** Layout `type` declared by this plugin in `contributes.layouts`. */
|
|
layoutType: string;
|
|
/** Initial opaque state copied into the detached window surface. */
|
|
state?: JsonValue;
|
|
}
|
|
|
|
export interface PluginWindow {
|
|
label: string;
|
|
url: string;
|
|
alreadyOpen: boolean;
|
|
providerPluginDisplayName: string;
|
|
layoutLabel: string;
|
|
surface: {
|
|
pluginId: string;
|
|
layoutType: string;
|
|
state: JsonValue;
|
|
};
|
|
}
|
|
|
|
export interface WindowService {
|
|
/**
|
|
* Opens or focuses a detached IdeA OS window hosting one of this plugin's
|
|
* declared layout contributions. The host rejects layout ids absent from this
|
|
* plugin's manifest.
|
|
*/
|
|
open(options: OpenPluginWindowOptions): Promise<PluginWindow>;
|
|
}
|