# Commands And Feedback This document is the normative SDK contract for plugin commands, including commands that open a view or launch background work. ## Contract Every menu click or plugin slash command follows this sequence: ```text manifest contribution -> command id -> registered command handler -> optional task -> feedback surfaces ``` - A manifest menu item declares a `command` id; it does not run tools directly. - A manifest slash command declares a slash `name`, autocomplete metadata and a `command` id; it does not run tools directly. - Menu items and slash commands may share the same `command` id, or point to different handlers. The plugin owns that choice. - The command handler is the only place that decides whether work should start. - The host slash-command registry only lists/filters metadata and returns a callback dispatch effect. The plugin handler decides what the command does. - A launched process is represented by a background task returned from `ctx.services.tasks.runCommand()`. - A skipped command is represented by the command handler return value and logs, not by a fake task. - Feedback objects must be stable enough for agents and programmatic callers to parse, and their messages must be readable by humans. - The human menu-click UI presents minimal host-owned feedback for `skipped` and `failed` results. It intentionally does not expose the raw result payload or assume which tool (Git, ADB, Gradle, and so on) the plugin uses. For successful actions and richer status, open or focus a plugin layout/window from the handler with `ctx.services.windows.open({ layoutType, state })`. ## Preconditions Check preconditions before calling `runCommand()`. These are task-launch preconditions, not prerequisites for opening an explanatory view: - `ctx.services` exists. Plugins need the `ui` or `tooling` capability for the service facade. - `ctx.services.workspace.getCurrentProject()` returned a project, or the caller supplied a valid `projectId`. - Required executables, environment variables and workspace files were validated, preferably with `ctx.services.tooling.diagnose()`. - `ownerAgentId` is a real agent id when the work is owned by an agent workflow. Do not use all-zero placeholders in production. - `cwd` is relative to the project root. - `command` and `args` do not resolve package-relative plugin resources implicitly. Build an explicit absolute script path from `ctx.pluginRoot` when launching read-only files shipped in the installed package. - Do not use `ctx.pluginRoot` as `cwd`; `cwd` remains relative to the project root. - `command` and `args` are separate values. Do not shell-join user input. A command whose purpose is to open or focus a diagnostic/configuration view should call `services.windows.open(...)` before probing optional executables. Missing Git, ADB, Gradle, or another tool must not block that view when the view can explain the degraded state and the next step. Probe tools from the view (or from its data loader) and render the degraded state there. If any required precondition fails, return a skipped result: ```ts return { status: "skipped", reason: "missing-owner-agent", message: "Configure an owner agent before launching the hello-plugin task." }; ``` ## Feedback Surfaces Command handlers should return one of these shapes, or a plugin-specific object with equivalent fields: ```ts type CommandFeedback = | { status: "opened"; message?: string; alreadyOpen: boolean } | { status: "skipped"; reason: string; message: string } | { status: "failed"; reason?: string; message?: string } | { status: "launched"; taskId: string; state: string; message: string }; ``` Use the same `status` vocabulary consistently: - `opened`: the requested view was opened or focused. `alreadyOpen: true` is a successful focus operation, not a failure or skip. - `skipped`: no background task was created. - `launched`: a task was created; inspect the task state for later progress. - `failed`: the handler caught a failure and returned structured failure feedback. An uncaught/rejected handler is also treated as a failure by the host. Visible surfaces are intentionally distinct: - Command return value: immediate feedback for programmatic callers and agents. The human menu host maps `skipped` and `failed` results to concise feedback; plugin-specific fields and messages are not displayed verbatim. `opened` results, including `alreadyOpen: true`, succeed without an alert. The host logs structured diagnostics with `pluginId`, `commandId`, `status`, `reason` and `message`; raw objects, stack traces and internal ids are not user-facing. - 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()`. - Plugin layouts or files: optional plugin-owned user feedback. `recordOnly: true` affects completion delivery to the owning agent. It does not mean hidden, skipped or UI-silent. A record-only command task can still appear in Work and can still emit background-task events. ## Best-Effort Watches `ctx.services.workspace.watch()` is not a baseline precondition for commands. The host may reject it until workspace watch support is delivered. Treat watch setup as optional and non-fatal: ```ts try { const watch = await ctx.services.workspace.watch(".ideai", refresh, project.id); ctx.subscriptions.push(watch); } catch (error) { ctx.logger.info("workspace watch unavailable", { error }); } ``` Commands that depend on fresh workspace state should refresh or re-read that 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
Unity tools are ready.
; } 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; ``` The `opened` branch above must remain successful even when an optional tool diagnosed by `HealthView` is absent. If `windows.open(...)` itself rejects, log the technical cause and either let the promise reject or return `failed`; the host will show generic, plugin-neutral feedback rather than the raw message. Background task feedback: ```ts const project = await ctx.services?.workspace.getCurrentProject(); if (!project) { return { status: "skipped", reason: "no-focused-project", message: "Open a project before running this command." }; } const ownerAgentId = await ctx.storage?.get("myPlugin.ownerAgentId"); if (!ownerAgentId) { return { status: "skipped", reason: "missing-owner-agent", message: "Configure an owner agent before launching this task." }; } const task = await ctx.services.tasks.runCommand({ projectId: project.id, ownerAgentId, label: "Run my tool", command: "npm", args: ["--version"], cwd: ".", recordOnly: true }); return { status: "launched", taskId: task.taskId, state: task.state, message: "Started Run my tool." }; ``` ## Counterexample Do not launch a shell command just to produce feedback: ```ts await ctx.services?.tasks.runCommand({ ownerAgentId: "00000000-0000-0000-0000-000000000000", label: "Skipped: missing config", command: "echo", args: ["missing config"], recordOnly: true }); ``` This creates misleading Work history, uses a placeholder owner and turns a precondition failure into a fake task. Return `status: "skipped"` instead. Do not try to reach packaged plugin scripts through workspace-relative paths: ```ts await ctx.services?.tasks.runCommand({ projectId: project.id, ownerAgentId, label: "Run packaged tests", command: "scripts/run-unity-tests.sh", cwd: "." }); ``` `scripts/run-unity-tests.sh` above is resolved like any other executable visible from the project task environment; it is not resolved relative to the plugin package. Use a project-owned script or an executable available on `PATH` until a dedicated packaged-resource API exists.