Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion apps/marketing/src/content/docs/guides/opencode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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`:
Expand Down
16 changes: 15 additions & 1 deletion apps/opencode-plugin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions apps/opencode-plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
shouldApplyToolDefinitionRewrites,
shouldInjectFullPlanningPrompt,
shouldInjectGenericPlanReminder,
shouldModifyPrompts,
shouldRegisterSubmitPlan,
shouldRejectSubmitPlanForAgent,
type PlannotatorOpenCodeOptions,
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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")) {
Expand Down
19 changes: 19 additions & 0 deletions apps/opencode-plugin/workflow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
shouldApplyToolDefinitionRewrites,
shouldInjectFullPlanningPrompt,
shouldInjectGenericPlanReminder,
shouldModifyPrompts,
shouldRegisterSubmitPlan,
shouldRejectSubmitPlanForAgent,
} from "./workflow";
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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: {
Expand Down
14 changes: 9 additions & 5 deletions apps/opencode-plugin/workflow.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -13,7 +13,7 @@ export interface NormalizedWorkflowOptions {
planningAgentSet: Set<string>;
}

const WORKFLOWS = new Set<WorkflowMode>(["manual", "plan-agent", "all-agents"]);
const WORKFLOWS = new Set<WorkflowMode>(["manual", "user-managed", "plan-agent", "all-agents"]);
const DEFAULT_WORKFLOW: WorkflowMode = "plan-agent";
const DEFAULT_PLANNING_AGENTS = ["plan"];
const BUILTIN_PLAN_AGENT = "plan";
Expand Down Expand Up @@ -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(
Expand All @@ -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 ?? [];
Expand Down
Loading