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; deactivate?(): void | Promise; } 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; 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(key: string): Promise; set(key: string, value: T): Promise; delete(key: string): Promise; } export type PluginLayoutState = JsonValue | undefined; export type PluginLayoutAvailability = "available"; export type PluginLayoutRenderResult = ReactNode; export interface PluginLayoutProps { /** 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 = ComponentType>; export interface PluginLayoutDefinition { /** Must match a layout `type` declared in this plugin's manifest. */ type: string; component: PluginLayoutComponent; } export interface LayoutRegistry { register( definition: PluginLayoutDefinition, ): 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; /** Returns the root path for the given project or for the current project. */ getProjectRoot(projectId?: string): Promise; /** Reads IdeA's shared project context for the given or current project. */ readProjectContext(projectId?: string): Promise; /** Updates IdeA's shared project context for the given or current project. */ updateProjectContext(content: string, projectId?: string): Promise; /** * 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; /** Reads a UTF-8 text file under the project root. */ readTextFile(path: string, projectId?: string): Promise; /** Reads raw bytes from a file under the project root. */ readBinaryFile(path: string, projectId?: string): Promise; /** Writes UTF-8 text under the project root using the host's controlled write path. */ writeTextFile(path: string, content: string, projectId?: string): Promise; /** Writes raw bytes under the project root using the host's controlled write path. */ writeBinaryFile(path: string, bytes: Uint8Array, projectId?: string): Promise; /** Lists one directory under the project root. Defaults to the workspace root. */ listDirectory(path?: string, projectId?: string): Promise; /** * Returns basic metadata. Missing paths resolve to `{ exists: false }`; invalid * paths and permission errors reject. */ stat(path: string, projectId?: string): Promise; /** * 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; /** Queries a bounded, generic project structure read model. */ queryStructure(query?: WorkspaceStructureQuery): Promise; } 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 | 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 | 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; } 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; } 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 { 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( options: ConfigDocumentReadOptions, ): Promise>; /** Writes a full replacement or JSON merge patch. First lot supports JSON only. */ updateDocument(options: ConfigDocumentUpdateOptions): Promise; } 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; /** Reads one command task directly from the host task store. */ getCommandStatus(taskId: string): Promise; /** Lists background tasks visible in the project work-state read model. */ list(projectId?: string): Promise; /** Reads one task status from the project work-state read model. */ getStatus(taskId: string, projectId?: string): Promise; /** Attaches to retained/live output for a task. */ attachOutput( taskId: string, onData: (bytes: Uint8Array) => void, ): Promise; /** Cancels a pending/running task. */ cancel(taskId: string): Promise; /** Retries a failed/cancelled task; future hosts may return the new task id. */ retry(taskId: string): Promise; } 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; resize(rows: number, cols: number): Promise; detach(): void; close(): Promise; } 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; /** Reattaches to an already-running PTY and returns retained scrollback. */ reattach(sessionId: string, options?: TerminalReattachOptions): Promise; /** Kills a PTY by id. */ close(sessionId: string): Promise; } 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; }