# Commands And Feedback This document is the normative SDK contract for plugin commands that may 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 current human menu-click UI does not guarantee display of a command 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 Check preconditions before calling `runCommand()`: - `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`, `args` and `cwd` do not resolve package-relative plugin resources. Do not use `runCommand()` to launch scripts shipped inside the plugin package; there is no public runtime `pluginRoot` path in `activate(ctx)`. - `command` and `args` are separate values. Do not shell-join user input. 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: "skipped"; reason: string; message: string } | { status: "launched"; taskId: string; state: string; message: string }; ``` Use the same `status` vocabulary consistently: - `skipped`: no background task was created. - `launched`: a task was created; inspect the task state for later progress. - `failed`: the handler itself failed before it could return normally. 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()`. - 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; ``` 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.