@khalilgharbaoui/opencode-claude-code-plugin 0.12.0 → 0.13.0

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
@@ -181,8 +181,9 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
181
181
  | `cwd` | string | `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**, so opencode's project switching works. |
182
182
  | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
183
183
  | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
184
- | `proxyTools` | string[] | `["Bash", "Edit", "Write", "WebFetch", "Task"]` | Claude built-in tools to route through opencode's executor + permission UI. See [Selective tool proxy](#selective-tool-proxy). |
184
+ | `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). |
185
185
  | `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). |
186
+ | `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). |
186
187
  | `controlRequestBehavior` | `allow` \| `deny` | `allow` | Default response when `skipPermissions: false` and Claude sends a `can_use_tool` control request. |
187
188
  | `controlRequestToolBehaviors` | `Record<string, "allow" \| "deny">` | – | Per-tool override for `can_use_tool`. Example: `{ "Bash": "deny", "Read": "allow" }`. |
188
189
  | `controlRequestDenyMessage` | string | built-in message | Message returned to Claude on a deny. |
@@ -272,6 +273,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
272
273
  | `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
273
274
  | `"Task"` | `Agent` | `mcp__opencode_proxy__task` |
274
275
  | `"Question"` | `AskUserQuestion` | `mcp__opencode_proxy__question` |
276
+ | `"Compress"` | none | `mcp__opencode_proxy__compress` |
275
277
 
276
278
  ### OpenCode-native subagents
277
279
 
@@ -296,7 +298,21 @@ recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
296
298
  process at spawn, and provider options are read once at opencode startup, so
297
299
  `proxyTools` changes need a full opencode restart.
298
300
 
299
- Only those six values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI. The `"Question"` proxy is version-gated on opencode's built-in `question` tool: on builds that lack the registry entry the def is silently dropped (a forwarded call would otherwise render as `⚙ invalid`), so add it only on opencode versions that ship the `question` tool.
301
+ ### Context compression
302
+
303
+ `"Compress"` is off by default. Add it when you run a harness that expects the model to manage its own context (opencode-dcp injects exactly those instructions), and the plugin exposes `mcp__opencode_proxy__compress`:
304
+
305
+ ```json
306
+ "options": {
307
+ "proxyTools": ["Bash", "Edit", "Write", "WebFetch", "Task", "Compress"]
308
+ }
309
+ ```
310
+
311
+ It is the one proxy tool opencode never sees. The call is answered inside the plugin: the model passes a `summary`, the plugin stores it, and the turn continues normally. At the start of the **next** turn the Claude Code session is discarded and a fresh `claude` starts with that summary prepended to its system prompt, and nothing else. The earlier conversation is not replayed, so a thin summary means real lost context. The reset waits if the incoming turn is carrying tool results for the running process.
312
+
313
+ Without it, the appended system prompt tells the model that `compress` is unavailable and to ignore instructions that ask for it, which is the right answer when nothing implements it.
314
+
315
+ Only those seven values are actually proxied; anything else you put in `proxyTools` is ignored. Proxying `Edit` also disables `MultiEdit` — opencode has no batched-edit equivalent, so Claude is forced to fan out into single `Edit` calls that each flow through the permission UI. The `"Question"` proxy is version-gated on opencode's built-in `question` tool: on builds that lack the registry entry the def is silently dropped (a forwarded call would otherwise render as `⚙ invalid`), so add it only on opencode versions that ship the `question` tool.
300
316
 
301
317
  Without `"Task"` in `proxyTools`, Claude's built-in `Agent` tool stays enabled and Claude orchestrates subagents internally with no opencode child-session visibility. To opt out of all proxying, including Task, use an explicit empty list:
302
318
 
@@ -450,6 +466,25 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
450
466
 
451
467
  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.
452
468
 
469
+ 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.
470
+
471
+ ### Approval as a real form (`planModeQuestion`, opt-in)
472
+
473
+ Set `planModeQuestion: true` to route the approval through opencode's native `question` tool instead:
474
+
475
+ ```json
476
+ "options": {
477
+ "permissionMode": "plan",
478
+ "planModeQuestion": true
479
+ }
480
+ ```
481
+
482
+ 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.
483
+
484
+ > **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.
485
+
486
+ Approval bridge contributed by [@CollieIsCute](https://github.com/CollieIsCute).
487
+
453
488
  ---
454
489
 
455
490
  ## AskUserQuestion
package/dist/index.d.ts CHANGED
@@ -162,6 +162,14 @@ interface ClaudeCodeConfig {
162
162
  controlRequestDenyMessage?: string;
163
163
  proxyTools?: string[];
164
164
  proxyToolTimeoutMs?: Record<string, number>;
165
+ /**
166
+ * Route `ExitPlanMode` through opencode's native `question` tool so plan
167
+ * approval is a real form instead of a "(yes/no)" line the operator has to
168
+ * answer in prose. Off by default: opencode's question form is currently
169
+ * broken upstream, so enabling this trades a working text prompt for a
170
+ * silent hang. See the plan-mode gotcha in AGENTS.md.
171
+ */
172
+ planModeQuestion?: boolean;
165
173
  webSearch?: WebSearchRouting;
166
174
  hotReloadMcp?: boolean;
167
175
  proxyOpencodeMcpTools?: boolean;
@@ -292,6 +300,23 @@ interface ClaudeCodeProviderSettings {
292
300
  * long build the caller explicitly asked to run is never undercut.
293
301
  */
294
302
  proxyToolTimeoutMs?: Record<string, number>;
303
+ /**
304
+ * Route Claude's `ExitPlanMode` through opencode's native `question` tool.
305
+ *
306
+ * Off (default): the plan is rendered as markdown followed by
307
+ * `**Do you want to proceed with this plan?** (yes/no)` and the operator
308
+ * answers in prose. On: the plan is rendered, the turn ends on
309
+ * `tool-calls`, and opencode runs its own `question` tool so approval is a
310
+ * real form; the answer is fed back to the CLI as the `tool_result` for
311
+ * the original `ExitPlanMode` call, which is what unlocks plan mode.
312
+ *
313
+ * Two reasons it is opt-in. opencode's `question` form does not currently
314
+ * render (upstream anomalyco/opencode#36604), so an enabled bridge hangs
315
+ * the turn until the operator interrupts; and older opencode builds have
316
+ * no `question` registry entry at all, in which case the plugin silently
317
+ * keeps the text path. See the plan-mode gotcha in AGENTS.md.
318
+ */
319
+ planModeQuestion?: boolean;
295
320
  /**
296
321
  * Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from the environment of
297
322
  * every spawned `claude` process. When an API key is present, Claude Code
@@ -525,9 +550,20 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
525
550
  * call resolves to `⚙ invalid`; the version gate drops the def.
526
551
  *
527
552
  * Returns undefined/false when the SDK client is unavailable (direct
528
- * AI-SDK use, tests) so the static defs stand.
553
+ * AI-SDK use, tests) so the static defs stand. `resolved` distinguishes
554
+ * "the registry answered and has no `question` entry" from "nobody
555
+ * answered": only the former is a real version-gate signal.
529
556
  */
530
557
  private fetchLiveToolInfo;
558
+ /** Share one lazy registry request within a turn without making it stale. */
559
+ private createLiveToolInfoLoader;
560
+ /**
561
+ * Whether the ExitPlanMode approval bridge is live for this turn: the
562
+ * operator opted in AND opencode's registry actually has the `question`
563
+ * tool. Without the registry entry the emitted tool-call would render as
564
+ * `⚙ invalid` and wedge the turn, so the plugin keeps the text path.
565
+ */
566
+ private resolvePlanModeQuestion;
531
567
  /**
532
568
  * Create a proxy MCP server for a single active Claude process/session.
533
569
  * The process lifecycle owns the server lifecycle via session-manager.
@@ -619,6 +655,7 @@ interface ClaudeCodeProvider {
619
655
  (modelId: string): LanguageModelV3;
620
656
  languageModel(modelId: string): LanguageModelV3;
621
657
  }
658
+ declare const DEFAULT_PROXY_TOOL_NAMES: string[];
622
659
  declare function createClaudeCode(settings?: ClaudeCodeProviderSettings): ClaudeCodeProvider;
623
660
  /**
624
661
  * Build models in OpenCode's config schema format (flat properties like
@@ -637,4 +674,4 @@ declare const _default: {
637
674
  server: OpenCodePlugin;
638
675
  };
639
676
 
640
- export { type ClaudeCodeConfig, ClaudeCodeLanguageModel, type ClaudeCodeProvider, type ClaudeCodeProviderSettings, type ClaudeStreamMessage, type OpenCodeHooks, type OpenCodeModel, type OpenCodePlugin, bridgeOpencodeMcp, claudeCodeProviders, configModelsForProvider, createClaudeCode, _default as default, defaultModels };
677
+ export { 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 };