diff --git a/apps/marketing/src/content/docs/guides/opencode.md b/apps/marketing/src/content/docs/guides/opencode.md index b5adf297..755c7f51 100644 --- a/apps/marketing/src/content/docs/guides/opencode.md +++ b/apps/marketing/src/content/docs/guides/opencode.md @@ -22,10 +22,11 @@ The OpenCode plugin (`@plannotator/opencode`) hooks into OpenCode's plugin syste ## Workflow modes -OpenCode support has three explicit modes: +OpenCode support has four explicit modes: - **`plan-agent`** (default): `submit_plan` is available to OpenCode's built-in `plan` agent plus any extra agents listed in `planningAgents`. - **`manual`**: `submit_plan` is not registered. Use `/plannotator-last`, `/plannotator-annotate`, `/plannotator-review`, and `/plannotator-archive` when you want Plannotator. +- **`user-managed`**: `submit_plan` is registered but no prompts or agent permissions are modified. You configure which agents can call `submit_plan` via OpenCode's agent configuration. - **`all-agents`**: legacy broad behavior. Primary agents can see and call `submit_plan`. Default config: @@ -87,6 +88,19 @@ If you want commands only: } ``` +If you want the tool registered but want to manage prompts and permissions yourself: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + ["@plannotator/opencode@latest", { + "workflow": "user-managed" + }] + ] +} +``` + ## Custom planning agents OpenCode's built-in `plan` agent is always included in `plan-agent` mode. If you use another planning agent, add its OpenCode agent name to `planningAgents`: diff --git a/apps/opencode-plugin/README.md b/apps/opencode-plugin/README.md index 8ef1347c..23dd048a 100644 --- a/apps/opencode-plugin/README.md +++ b/apps/opencode-plugin/README.md @@ -38,10 +38,11 @@ Restart OpenCode. By default, the `submit_plan` tool is available to OpenCode's ## Workflow Modes -Plannotator supports three OpenCode workflows: +Plannotator supports four OpenCode workflows: - **`plan-agent`** (default): `submit_plan` is available to OpenCode's built-in `plan` agent plus any extra agents listed in `planningAgents`. This keeps Plannotator integrated with OpenCode plan mode without nudging `build` to call it. - **`manual`**: `submit_plan` is not registered. Use `/plannotator-last`, `/plannotator-annotate`, `/plannotator-review`, and `/plannotator-archive` when you want Plannotator. +- **`user-managed`**: `submit_plan` is registered but no prompts or agent permissions are modified. You manage which agents can call `submit_plan` via OpenCode's native agent configuration. - **`all-agents`**: legacy broad behavior. Primary agents can see and call `submit_plan`. Default config: @@ -103,6 +104,19 @@ Use commands only: } ``` +Register the tool but manage prompts and permissions yourself: + +```json +{ + "$schema": "https://opencode.ai/config.json", + "plugin": [ + ["@plannotator/opencode@latest", { + "workflow": "user-managed" + }] + ] +} +``` + ## How It Works 1. The configured planning agent calls `submit_plan` → Plannotator opens in your browser diff --git a/apps/opencode-plugin/index.ts b/apps/opencode-plugin/index.ts index 112b4185..34c78a75 100644 --- a/apps/opencode-plugin/index.ts +++ b/apps/opencode-plugin/index.ts @@ -74,6 +74,7 @@ import { shouldApplyToolDefinitionRewrites, shouldInjectFullPlanningPrompt, shouldInjectGenericPlanReminder, + shouldModifyPrompts, shouldRegisterSubmitPlan, shouldRejectSubmitPlanForAgent, type PlannotatorOpenCodeOptions, @@ -259,7 +260,7 @@ export const PlannotatorPlugin: Plugin = async (ctx, rawOptions?: PlannotatorOpe // that allows markdown file writing. OpenCode's original blocks ALL file edits, // but we need the agent to write plans, specs, docs, etc. "experimental.chat.messages.transform": async (input, output) => { - if (workflowOptions.workflow === "manual") return; + if (!shouldModifyPrompts(workflowOptions)) return; const lastUserAgent = getLastUserAgentFromMessages(output.messages); if ( @@ -313,7 +314,7 @@ tools (except writing markdown files), or otherwise make changes to the system. // Inject planning instructions into system prompt "experimental.chat.system.transform": async (input, output) => { - if (workflowOptions.workflow === "manual") return; + if (!shouldModifyPrompts(workflowOptions)) return; const systemText = output.system.join("\n"); if (systemText.toLowerCase().includes("title generator") || systemText.toLowerCase().includes("generate a title")) { diff --git a/apps/opencode-plugin/workflow.test.ts b/apps/opencode-plugin/workflow.test.ts index 4280525e..b74c71de 100644 --- a/apps/opencode-plugin/workflow.test.ts +++ b/apps/opencode-plugin/workflow.test.ts @@ -5,6 +5,7 @@ import { shouldApplyToolDefinitionRewrites, shouldInjectFullPlanningPrompt, shouldInjectGenericPlanReminder, + shouldModifyPrompts, shouldRegisterSubmitPlan, shouldRejectSubmitPlanForAgent, } from "./workflow"; @@ -53,6 +54,16 @@ describe("workflow gates", () => { expect(shouldRejectSubmitPlanForAgent("build", options)).toBe(false); }); + test("user-managed mode registers tool but skips prompt/config modifications", () => { + const options = normalizeWorkflowOptions({ workflow: "user-managed" }); + + expect(shouldRegisterSubmitPlan(options)).toBe(true); + expect(shouldModifyPrompts(options)).toBe(false); + expect(shouldApplyToolDefinitionRewrites(options)).toBe(false); + expect(shouldInjectFullPlanningPrompt("plan", options)).toBe(false); + expect(shouldRejectSubmitPlanForAgent("build", options)).toBe(false); + }); + test("plan-agent mode injects only for configured planning agents", () => { const options = normalizeWorkflowOptions({ workflow: "plan-agent", @@ -96,6 +107,14 @@ describe("applyWorkflowConfig", () => { expect(config).toEqual({}); }); + test("user-managed mode leaves OpenCode config untouched", () => { + const config: any = {}; + + applyWorkflowConfig(config, normalizeWorkflowOptions({ workflow: "user-managed" }), false); + + expect(config).toEqual({}); + }); + test("plan-agent mode exposes submit_plan to plan and denies build", () => { const config: any = { experimental: { diff --git a/apps/opencode-plugin/workflow.ts b/apps/opencode-plugin/workflow.ts index 0679c294..6a2e4647 100644 --- a/apps/opencode-plugin/workflow.ts +++ b/apps/opencode-plugin/workflow.ts @@ -1,6 +1,6 @@ import { normalizeEditPermission } from "./plan-mode"; -export type WorkflowMode = "manual" | "plan-agent" | "all-agents"; +export type WorkflowMode = "manual" | "user-managed" | "plan-agent" | "all-agents"; export interface PlannotatorOpenCodeOptions { workflow?: unknown; @@ -13,7 +13,7 @@ export interface NormalizedWorkflowOptions { planningAgentSet: Set; } -const WORKFLOWS = new Set(["manual", "plan-agent", "all-agents"]); +const WORKFLOWS = new Set(["manual", "user-managed", "plan-agent", "all-agents"]); const DEFAULT_WORKFLOW: WorkflowMode = "plan-agent"; const DEFAULT_PLANNING_AGENTS = ["plan"]; const BUILTIN_PLAN_AGENT = "plan"; @@ -80,15 +80,19 @@ export function shouldRegisterSubmitPlan(options: NormalizedWorkflowOptions): bo return options.workflow !== "manual"; } +export function shouldModifyPrompts(options: NormalizedWorkflowOptions): boolean { + return options.workflow !== "manual" && options.workflow !== "user-managed"; +} + export function shouldApplyToolDefinitionRewrites(options: NormalizedWorkflowOptions): boolean { - return options.workflow !== "manual"; + return options.workflow !== "manual" && options.workflow !== "user-managed"; } export function shouldInjectFullPlanningPrompt( agentName: string | undefined, options: NormalizedWorkflowOptions, ): boolean { - return options.workflow !== "manual" && isPlanningAgent(agentName, options); + return shouldModifyPrompts(options) && isPlanningAgent(agentName, options); } export function shouldInjectGenericPlanReminder( @@ -114,7 +118,7 @@ export function applyWorkflowConfig( options: NormalizedWorkflowOptions, allowSubagents: boolean, ): void { - if (options.workflow === "manual") return; + if (options.workflow === "manual" || options.workflow === "user-managed") return; if (!allowSubagents) { const existingPrimaryTools = opencodeConfig.experimental?.primary_tools ?? [];