@khalilgharbaoui/opencode-claude-code-plugin 0.18.0 → 0.18.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -267,7 +267,7 @@ model: claude-code-appical/claude-opus-5@appical
267
267
  | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. Opt-in extras: `"Question"`, `"Compress"`. See [Selective tool proxy](#selective-tool-proxy). |
268
268
  | `extraDisallowedTools` | string[] | – | Extra Claude built-ins to switch off with `--disallowedTools`, on top of what `proxyTools` implies. Claude's names, e.g. `["NotebookEdit"]`. See [Closing a tool with no proxy](#closing-a-tool-with-no-proxy). |
269
269
  | `proxyToolTimeoutMs` | `Record<string, number>` | – | Per-tool proxy call deadline in ms, keyed by proxy tool name (`bash`, `task`, …). Defaults: 10 min flat, `task` → 60 min. For `bash`, the call's own `input.timeout` is honoured on top (`max(resolved, input.timeout)`). See [Selective tool proxy](#selective-tool-proxy). |
270
- | `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Off because opencode's question form is currently broken upstream. See [Plan mode](#plan-mode). |
270
+ | `planModeQuestion` | boolean | `false` | Route `ExitPlanMode` approval through opencode's native `question` tool instead of a text "(yes/no)" prompt. Opt-in; verify the form works in your installation first. See [Plan mode](#plan-mode). |
271
271
  | `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
272
272
  | `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
273
273
  | `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
@@ -660,6 +660,10 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
660
660
 
661
661
  Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The plugin handles `ExitPlanMode` specially — instead of forwarding it as a tool call, it converts it to a confirmation prompt that flows through opencode normally.
662
662
 
663
+ > **Plan mode never permits edits, and you do not have to configure anything for that.** The CLI lets `--dangerously-skip-permissions` override `--permission-mode plan` outright, and `skipPermissions` defaults to `true`, so until this was fixed anyone asking for plan mode silently got full write access (measured on CLI 2.1.258: the run wrote a file on request without a prompt). The plugin now drops the skip flag whenever `permissionMode` is `"plan"`; every other mode governs prompting, which is what that flag is for, so those still pass it.
664
+ >
665
+ > Two things to know. Nothing releases plan mode mid-session: headless Claude Code is not offered an `ExitPlanMode` tool, so approving a plan in chat does not unlock writes, and leaving plan mode means changing the config and restarting opencode. The plugin warns about this once at startup. And the CLI still writes its own plan document under `~/.claude*/plans/`, which is its own feature and outside your workspace; your files and commands are untouched.
666
+
663
667
  By default that prompt is text: the plan is rendered as markdown, followed by `**Do you want to proceed with this plan?** (yes/no)`, and you answer in your next message.
664
668
 
665
669
  ### Approval as a real form (`planModeQuestion`, opt-in)
@@ -675,7 +679,7 @@ Set `planModeQuestion: true` to route the approval through opencode's native `qu
675
679
 
676
680
  The plan is still rendered, but the turn then ends on `tool-calls` and opencode runs its own `question` tool, so approval is a form rather than prose. Your answer is fed back to the CLI as the `tool_result` for the original `ExitPlanMode` call, which is what actually unlocks plan mode on the Claude side. A "yes" typed as ordinary text never does that. Anything other than picking `yes` (including custom text) comes back as rejection feedback the model is told to act on.
677
681
 
678
- > **Leave this off for now.** It depends on the same opencode `question` form that is [broken upstream](#with-question-in-proxytools-currently-blocked-upstream--leave-it-off): with it on, a plan approval hangs until you interrupt the turn. On opencode builds with no `question` registry entry at all the plugin silently keeps the text path (look for `plan-mode question gate` in the log). Re-test when [anomalyco/opencode#36603](https://github.com/anomalyco/opencode/pull/36603) merges.
682
+ > **This cannot currently fire on the default headless transport, so leaving it off costs you nothing.** The form it delivers through works (see [AskUserQuestion](#askuserquestion)), but headless `--print` does not offer the model an `ExitPlanMode` tool at all on CLI 2.1.258, and the bridge keys on that tool call. Measured three ways: asked directly for its tool list in plan mode, the CLI returned `Agent, Bash, Edit, ListAgents, Read, ReportFindings, ScheduleWakeup, Skill, ToolSearch, Workflow, Write` and nothing else; asked to do work it said "I'm unable to exit plan mode from within the tool set available to me"; and a full probe through this plugin with `planModeQuestion: true` produced no `ExitPlanMode` anywhere in `plugin.log` while the model asked for approval in prose. The name is still known to the CLI (`--disallowedTools ExitPlanMode` validates silently, where a bogus name warns), so this reads as headless dormancy rather than removal, the same shape as the [`AskUserQuestion` fallback](#askuserquestion). The text path below is what you actually get, and it works. Re-run those probes on a newer CLI before assuming the bridge is reachable. On opencode builds with no `question` registry entry the plugin silently keeps the text path (look for `plan-mode question gate` in the log).
679
683
 
680
684
  Approval bridge contributed by [@CollieIsCute](https://github.com/CollieIsCute).
681
685
 
@@ -685,11 +689,17 @@ Approval bridge contributed by [@CollieIsCute](https://github.com/CollieIsCute).
685
689
 
686
690
  opencode ships a built-in `question` tool (`packages/opencode/src/tool/question.ts`) that renders a real TUI form with options and a custom-answer field — near-identical to Claude Code's `AskUserQuestion` (`multiSelect` → `multiple`). The plugin can route `AskUserQuestion` through it so the prompt becomes an actual form instead of plain text. Two modes:
687
691
 
688
- ### With `"Question"` in `proxyTools` (currently blocked upstream — leave it off)
692
+ ### With `"Question"` in `proxyTools` (opt-in)
693
+
694
+ > **Correction, September 6, 2026: this is no longer blocked, and earlier releases of this README were wrong about why.** The missing form was attributed to an upstream TUI regression. The real cause was local: a notification plugin awaited macOS `alerter` dismissal inside `tool.execute.before`, so the question tool never started. Native providers load that same global plugin, which is why their identical failure did not isolate the TUI. With the hook made non-blocking, the form renders, and the full path through this plugin is verified: on plugin 0.18.0 / Claude Code 2.1.258 / opencode 1.18.29, Claude called `mcp__opencode_proxy__question`, the request appeared in `GET /question`, the reply completed the tool, and Claude's answer contained a token it could only have read from the tool result. Confirmed in a real terminal too: with `"Question"` enabled and opencode relaunched, the proxied call rendered as a TUI form and the clicked answers came back into the turn.
695
+ >
696
+ > `"Question"` is still opt-in, because turning it on disables Claude's own `AskUserQuestion` (see the fallback below) and that trade should be deliberate. If your form does not render, see [question troubleshooting](#question-troubleshooting) before assuming an upstream bug.
697
+
698
+ #### Question troubleshooting
689
699
 
690
- > **Known upstream breakage (opencode 1.15.x through at least 1.18.5).** opencode's `question` TUI form does not render, so the tool blocks until you interrupt the turn. This is not specific to this plugin: native providers hit it identically, and a `--pure` headless server drives the same question end to end successfully (`question.asked` → `GET /question` → `POST /question/{id}/reply` → tool completes), which isolates the fault to the TUI. Tracked upstream as [anomalyco/opencode#36604](https://github.com/anomalyco/opencode/issues/36604) with fix [PR #36603](https://github.com/anomalyco/opencode/pull/36603) (unmerged). Until that lands, enabling `"Question"` trades the working fallback below for a hang. The instructions here describe the intended behavior for when it is fixed.
700
+ For a stalled call, inspect `GET /question` on the same opencode server and workspace. If no request exists, check awaited `tool.execute.before` hooks and custom tools replacing `question`, especially notification plugins: a hook opencode waits on runs *before* the tool, so the request cannot exist yet. If a request exists but no form appears, check session ownership, pending permissions, and event delivery. The separate detach/reattach issue [anomalyco/opencode#36604](https://github.com/anomalyco/opencode/issues/36604) remains open; [PR #36603](https://github.com/anomalyco/opencode/pull/36603) is closed without merging. Do not infer a universal platform or version failure from either symptom.
691
701
 
692
- Add `"Question"` to `proxyTools` and grant `permission.question: allow` to the calling agent. Claude's built-in `AskUserQuestion` is disabled via `--disallowedTools`, and the plugin exposes `mcp__opencode_proxy__question` in its place. The model calls the proxy, opencode renders the form, and the operator's answers come back as arrays of selected labels. On builds that lack the `question` registry entry the def is silently dropped at spawn (version gate), and the deny/markdown fallback below applies instead.
702
+ Add `"Question"` to `proxyTools`. Claude's built-in `AskUserQuestion` is disabled via `--disallowedTools`, and the plugin exposes `mcp__opencode_proxy__question` in its place. A primary agent needs no permission entry (verified on opencode 1.18.29 with no `permission` block at all); if a subagent's form is refused, grant it `permission.question: "allow"` on that agent, the same way [subagent todos](#subagent-todos) need `todowrite`. The model calls the proxy, opencode renders the form, and the operator's answers come back as arrays of selected labels. On builds that lack the `question` registry entry the def is silently dropped at spawn (version gate), and the deny/markdown fallback below applies instead.
693
703
 
694
704
  `proxyTools` replaces the default list rather than adding to it, so repeat the defaults you still want:
695
705
 
package/dist/index.d.ts CHANGED
@@ -768,6 +768,8 @@ declare const DEFAULT_PROXY_TOOL_NAMES: string[];
768
768
  * so a user-defined command keeps opencode's normal behaviour end to end.
769
769
  */
770
770
  declare function registerSideQuestionCommand(config: OpenCodeConfig): boolean;
771
+ declare function _resetPlanModeWarningForTests(): void;
772
+ declare function warnIfPlanModeCannotExit(permissionMode: string | undefined): void;
771
773
  declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
772
774
  /**
773
775
  * Build models in OpenCode's config schema format (flat properties like
@@ -786,4 +788,4 @@ declare const _default: {
786
788
  server: OpenCodePlugin;
787
789
  };
788
790
 
789
- export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, getAgentRegistry, getDefaultSubagentModel, registerSideQuestionCommand, resolveAgentModel };
791
+ export { type AgentRecord, type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, DEFAULT_PROXY_TOOL_NAMES, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, _resetPlanModeWarningForTests, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels, getAgentRegistry, getDefaultSubagentModel, registerSideQuestionCommand, resolveAgentModel, warnIfPlanModeCannotExit };
package/dist/index.js CHANGED
@@ -2255,7 +2255,7 @@ function buildCliArgs(opts) {
2255
2255
  if (fastMode && cliSupportsFastMode(cliVersion ?? null)) {
2256
2256
  args.push("--settings", JSON.stringify({ fastMode: true }));
2257
2257
  }
2258
- if (skipPermissions) {
2258
+ if (skipPermissions && permissionMode !== "plan") {
2259
2259
  args.push("--dangerously-skip-permissions");
2260
2260
  }
2261
2261
  return args;
@@ -7939,6 +7939,7 @@ function pickOpencodeDirectory(input) {
7939
7939
  return void 0;
7940
7940
  }
7941
7941
  var warnedAnthropicApiKey = false;
7942
+ var warnedPlanModeNoExit = false;
7942
7943
  var DEFAULT_PROXY_TOOL_NAMES = [
7943
7944
  "Bash",
7944
7945
  "Edit",
@@ -7970,6 +7971,18 @@ function warnIfAnthropicApiKey(ignore) {
7970
7971
  );
7971
7972
  }
7972
7973
  }
7974
+ function _resetPlanModeWarningForTests() {
7975
+ warnedPlanModeNoExit = false;
7976
+ }
7977
+ function warnIfPlanModeCannotExit(permissionMode) {
7978
+ if (permissionMode !== "plan") return;
7979
+ if (warnedPlanModeNoExit) return;
7980
+ warnedPlanModeNoExit = true;
7981
+ log.warn(
7982
+ 'permissionMode "plan" is enforced: claude cannot edit files or run commands, and --dangerously-skip-permissions is deliberately not passed so it stays that way. Headless Claude Code is not offered an ExitPlanMode tool, so nothing releases plan mode mid-session; approving a plan in chat does not unlock writes. Leaving plan mode means changing the config and restarting opencode.',
7983
+ { permissionMode, measuredOn: "claude-code 2.1.258" }
7984
+ );
7985
+ }
7973
7986
  function createClaudeCode(settings = {}) {
7974
7987
  if (settings.logging) {
7975
7988
  configureLogger({
@@ -7980,6 +7993,7 @@ function createClaudeCode(settings = {}) {
7980
7993
  });
7981
7994
  }
7982
7995
  warnIfAnthropicApiKey(settings.ignoreAnthropicApiKey);
7996
+ warnIfPlanModeCannotExit(settings.permissionMode);
7983
7997
  const cliPath = settings.cliPath ?? process.env.CLAUDE_CLI_PATH ?? "claude";
7984
7998
  const providerName = settings.providerID ?? settings.name ?? "claude-code";
7985
7999
  const proxyTools = settings.proxyTools ?? [...DEFAULT_PROXY_TOOL_NAMES];
@@ -8287,6 +8301,7 @@ var index_default = {
8287
8301
  export {
8288
8302
  ClaudeCodeLanguageModel,
8289
8303
  DEFAULT_PROXY_TOOL_NAMES,
8304
+ _resetPlanModeWarningForTests,
8290
8305
  bridgeOpencodeMcp,
8291
8306
  claudeCodeProviders,
8292
8307
  configModelsForProvider,
@@ -8296,6 +8311,7 @@ export {
8296
8311
  getAgentRegistry,
8297
8312
  getDefaultSubagentModel,
8298
8313
  registerSideQuestionCommand,
8299
- resolveAgentModel
8314
+ resolveAgentModel,
8315
+ warnIfPlanModeCannotExit
8300
8316
  };
8301
8317
  //# sourceMappingURL=index.js.map