@khalilgharbaoui/opencode-claude-code-plugin 0.33.2 → 0.34.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
@@ -468,7 +468,8 @@ reaches the CLI, so there is nothing there to fall back from.
468
468
  | `bridgeOpencodeMcp` | boolean | `true` | Auto-translate your opencode `mcp` block into Claude's `--mcp-config`. See [MCP bridge](#mcp-bridge). |
469
469
  | `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
470
470
  | `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
471
- | `hotReloadMcp` | boolean | `true` | With MCP bridging on, compare the merged MCP config and runtime status at the start of each turn and respawn the `claude` process when they drifted, so a server you just enabled or disabled becomes visible without restarting opencode or opening a new chat. Eviction waits for pending proxy calls, never happening mid tool-call, and the session id is preserved for `--resume`. Set `false` to keep a cached subprocess until the chat is reset. It does not reload other provider options and does not watch the contents of files named in `mcpConfig`. |
471
+ | `hotReloadMcp` | boolean | `true` | With MCP bridging on, compare the merged MCP config and runtime status at the start of each turn and respawn the `claude` process when they drifted, so a server you just enabled, disabled or finished connecting becomes visible without restarting opencode or opening a new chat. It only ever acts at a safe boundary: never during `/compact`, never on the interactive transport, never while a proxied call is still in the air, a turn is still running or a plan-mode approval is outstanding, and the Claude session id is preserved for `--resume` so the conversation continues. A log line at INFO names which servers joined and left. A server that flaps buys at most one respawn per minute per conversation (`CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS`). Set `false` to keep a cached subprocess until the chat is reset. It does not reload other provider options and does not watch the contents of files named in `mcpConfig`. |
472
+ | `mcpConnectWaitMs` | number | `3000` | How long the first turn of a conversation waits for MCP servers opencode reports as still connecting before planning the `claude` spawn without them. Only opencode 2 can report that state (`pending`); opencode 1's own status call blocks until every server has decided, so this budget is what makes the two majors behave alike. Set `0` to always plan with whatever the host says at that instant. Aborting the turn also ends the wait at once. A server slower than the budget is not lost either way: it is still bridged, and `hotReloadMcp` moves the conversation onto a process that has it on the next turn. |
472
473
  | `proxyOpencodeMcpTools` | boolean | `false` | Route opencode's MCP-backed tools through the in-process `opencode_proxy` server instead of bridging them straight into Claude's `--mcp-config`, so each call executes once, inside opencode, with its permission prompt and its tool row. **The default changed from `true` to `false` in this release, and no behaviour changed with it:** at `true` it used to route nothing at all, because discovery read opencode's tool registry, which contains built-ins and plugin-declared tools and has never contained an MCP tool. Discovery now reads the model tool set opencode passes the provider, which is where MCP tools actually are, so the option works, and turning it on is the operator's decision rather than a silent migration of traffic that the direct bridge is handling today. Two caveats before enabling it: pair it with `strictMcpConfig: true`, because a server also registered in Claude Code's own config is reached directly and bypasses the proxy entirely; and a routed call runs in opencode with the calling agent's permissions, the same trade [`proxyOpencodeTools`](#options-reference) makes. Servers whose tools are not found stay on the direct bridge, and a warning says so, so do not treat this as an exactly-once guarantee for write-capable tools. |
473
474
  | `proxyOpencodeTools` | string[] | `[]` | Forward explicitly named opencode tools through the proxy (for example, a plugin's `compress` or V2 Code Mode `execute`). V1 uses registry ids; V2 uses the current model tool snapshot, including its real JSON Schema and agent visibility, not the registry's empty schemas. A forwarded tool runs inside opencode with the calling agent's permissions. A name already held by a proxy def is dropped with a warning. The read-only preset refuses `execute`. See [Forwarding opencode's own tools](#forwarding-opencode-s-own-tools) and [V2 Code Mode](#v2-code-mode). |
474
475
  | `stripContextReminders` | boolean | `false` | Remove opencode-dcp's `<dcp-system-reminder>` blocks from message text when no `compress` tool is proxied, so an order the model cannot follow stops being re-sent with every message that carries it. Inert as soon as `compress` is reachable. See [Trimming unsatisfiable context reminders](#trimming-unsatisfiable-context-reminders). |
@@ -507,6 +508,7 @@ Every variable the plugin itself reads, in one place. Config is read once at ope
507
508
  | `OPENCODE_CLAUDE_CODE_PLUGIN_FORCE_CLEANUP` | startup cleanup | `1` runs that cleanup even when the marker at `$XDG_STATE_HOME/opencode-claude-code-plugin/cleanup-stale.json` (default `~/.local/state/...`) records that this plugin version already swept. Without it the cleanup walks opencode's plugin cache once per installed version rather than on every launch. |
508
509
  | `OPENCODE_CLAUDE_CODE_NO_TMP_SWEEP` | scratch directory | `1` skips the sweep of `<tmpdir>/opencode-claude-code-<pid>` directories left behind by plugin processes that were killed. See [Scratch files on disk](#scratch-files-on-disk). |
509
510
  | `OPENCODE_WORKTREE` | MCP bridge | Overrides worktree-root detection, which otherwise walks up from the working directory looking for a `.git` entry. |
511
+ | `CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS` | MCP bridge | Minimum gap between two `hotReloadMcp` respawns of one conversation, default `60000`. A server that flaps between connected and failed would otherwise cost a kill and a `--resume` spawn on every turn. `0` disables the guard; a real second change lands on the first turn after the gap. |
510
512
  | `OPENCODE_CONFIG` / `OPENCODE_CONFIG_DIR` | config discovery | Where the plugin looks for your opencode config when bridging MCP and skills. See [Discovery order](#discovery-order-highest-to-lowest-priority). |
511
513
  | `OPENCODE_VERSION` | startup diagnostics | Reported as the opencode version when set, sparing the plugin a `--version` spawn. Diagnostics only. |
512
514
  | `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` | spawn environment | Not set by the plugin: these are yours, and Claude Code authenticates with them in preference to your subscription login when present. `ignoreAnthropicApiKey: true` strips them from the spawn. See [Billing](#billing). |
@@ -849,7 +851,7 @@ A proxied call ends when something happens to it, not when a clock runs out. The
849
851
  | What happens | What the plugin does |
850
852
  |---|---|
851
853
  | opencode returns the tool's result | resolves the call; the CLI gets the result and carries on |
852
- | you abort the turn (Esc / Ctrl+C) | sends the CLI an `interrupt`, which answers with its own result, and rejects every call the turn had pending, whether the abort lands before content, mid-turn, or while opencode is running the tool between two stream boundaries |
854
+ | you abort the turn (Esc / Ctrl+C) | sends the CLI an `interrupt`, which answers with its own result, and rejects every call the turn had pending, whether the abort lands before content, mid-turn, or while opencode is running the tool between two stream boundaries. A stop that lands before the turn has asked the CLI for anything is the one exception: it interrupts nothing and releases nothing, because the calls still pending there belong to the previous step and the next message orphans them as usual |
853
855
  | you send the next message in that chat | rejects every call the previous turn left pending as orphaned, so the CLI gets an error result and the new turn starts clean |
854
856
  | the `claude` process closes its output or exits, mid-turn or between turns | rejects its pending calls; a mid-turn death also ends the turn as a visible error |
855
857
  | you delete the chat in opencode, or opencode exits | kills the worker and rejects its pending calls |
@@ -1071,6 +1073,22 @@ The disk bridge reads global config, then `OPENCODE_CONFIG`, then project direct
1071
1073
 
1072
1074
  On V2, project discovery walks to the filesystem root (including ancestors above the repo). Direct files are applied parent-first, then `.opencode` files parent-first: the closest file wins within each group, and all `.opencode` files override direct files. Each higher-precedence server entry **replaces the entire server object**, so repeat its type, URL/command and other required fields in an override. Both `.json` and `.jsonc` are read, with `.jsonc` winning within a directory. Runtime toggles continue to participate in the hot-reload hash.
1073
1075
 
1076
+ ### Servers that connect late
1077
+
1078
+ A server opencode has not finished connecting to when a turn is planned used to be dropped from that turn's `claude` spawn, and a reused process keeps the `--mcp-config` it was spawned with, so it stayed invisible for the rest of the conversation. Two things stop that now.
1079
+
1080
+ First, "still connecting" is no longer read as "not connected". opencode 2 reports such a server as `pending`, which is not a decision, so the bridge leaves its configured state alone instead of forcing it off, and the turn waits up to `mcpConnectWaitMs` (3 s by default, `0` to disable) for the host to decide. opencode 1 has no `pending` status: its own status call blocks until every server resolves, so nothing changes there and the wait costs one status call exactly as before.
1081
+
1082
+ Second, if the server joins later anyway, `hotReloadMcp` moves the conversation onto a `claude` process with the new config and `--resume`, at the start of a later turn. That only happens at a safe boundary: not during `/compact`, not on the interactive transport, and not while a proxied call is still in the air, a turn is still running or a plan-mode approval is outstanding. The log line names the servers:
1083
+
1084
+ ```
1085
+ INFO: opencode MCP servers changed, respawning claude {"joined":["slowmcp"],"left":[],...}
1086
+ ```
1087
+
1088
+ A server that flaps between connected and failed is capped at one respawn per minute per conversation; override with `CLAUDE_CODE_MCP_HOT_RELOAD_COOLDOWN_MS`.
1089
+
1090
+ This matters most where a turn can arrive before the host has started its servers: `opencode run`, scripted use and slow servers. The TUI normally connects everything before the first prompt.
1091
+
1074
1092
  ### V2 Code Mode
1075
1093
 
1076
1094
  V2 normally exposes MCP tools through Code Mode's `execute` and its catalog, rather than as individual server-prefixed model tools. `proxyOpencodeMcpTools` matches individual tools only; on a Code Mode-only snapshot it warns and falls back to the direct Claude MCP bridge. This fallback does **not** execute tools under opencode's permission policy.
@@ -1127,6 +1145,7 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
1127
1145
  - **New chat** → fresh process under the new session key.
1128
1146
  - **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
1129
1147
  - **Abort (Esc / Ctrl+C)** → the plugin sends the Claude CLI a stream-json `interrupt` control request, so the CLI actually stops generating and running tools instead of finishing the abandoned turn on your bill. The process stays alive for the next message in that chat, and any proxied call the aborted turn left behind is released when that message arrives (see [How a proxied call ends](#how-a-proxied-call-ends)). If a turn is somehow still running when the next one starts, it is interrupted first (5 s cap). Contributed by [@broskees](https://github.com/broskees).
1148
+ - **Abort during the first moment of a turn** → a turn spends a little time preparing before it asks the CLI for anything: resolving the spawn directory, probing the `claude` version, reading opencode's MCP status and tool registry. A stop pressed in that window used to be dropped, and the turn spawned, ran and billed anyway. It now ends the turn there: nothing is spawned, nothing is written, no running process is interrupted, and the reply is simply empty.
1130
1149
  - **Idle timeout** → when `idleProcessTimeoutMs` is set, a completed headless turn arms an eviction timer (unset or `0` keeps workers until LRU eviction). Reuse cancels it, a worker found mid-turn when it fires is left alone and re-timed, and eviction preserves the session id, so the next message resumes the same conversation with `--resume`. An idle `claude --print` holds around 250 MB, which is the reason to set it if you keep many chats open.
1131
1150
  - **Cap**: 16 active processes, LRU eviction. A process that is mid-turn is never the victim: eviction takes the oldest **idle** one, and when every process is busy it evicts nothing and warns instead, so a running answer is never truncated to make room.
1132
1151
  - **Deleted chat** → deleting a session in opencode kills its `claude` workers at once and forgets their session ids and per-chat state; there is nothing left to resume. Other chats, and the shared fallback bucket used when no session id is known, are untouched.
package/dist/index.d.ts CHANGED
@@ -406,6 +406,7 @@ interface ClaudeCodeConfig {
406
406
  planModeQuestion?: boolean;
407
407
  webSearch?: WebSearchRouting;
408
408
  hotReloadMcp?: boolean;
409
+ mcpConnectWaitMs?: number;
409
410
  proxyOpencodeMcpTools?: boolean;
410
411
  multiStepContinuation?: boolean;
411
412
  autoContinueIncompleteTurns?: boolean | "smart";
@@ -784,6 +785,25 @@ interface ClaudeCodeProviderSettings {
784
785
  * survives MCP changes until the chat is reset).
785
786
  */
786
787
  hotReloadMcp?: boolean;
788
+ /**
789
+ * How long a turn waits for opencode MCP servers it reports as still
790
+ * connecting (`pending`) before planning the spawn without them, in
791
+ * milliseconds. Defaults to 3000; `0` disables the wait.
792
+ *
793
+ * This only ever engages on opencode 2, which answers its MCP status call
794
+ * immediately and reports a server it has not finished connecting to as
795
+ * `pending`. opencode 1 has no such status: its own status call blocks
796
+ * until every server has reached a decision, so this budget is what makes
797
+ * a 2.x host behave like a 1.x one. Without it, a conversation whose first
798
+ * turn arrives while opencode is still starting a server spawns `claude`
799
+ * without that server, and only the next turn's hot reload brings it in.
800
+ *
801
+ * Raise it for a slow server, or set `0` to always plan with whatever the
802
+ * host says at that instant. A server slower than the budget is not lost
803
+ * either way: `hotReloadMcp` moves the conversation onto a process that
804
+ * has it on the next fresh turn.
805
+ */
806
+ mcpConnectWaitMs?: number;
787
807
  /**
788
808
  * Route opencode MCP server tools through the in-process `opencode_proxy`
789
809
  * MCP server instead of bridging them directly into Claude CLI's
@@ -1257,6 +1277,8 @@ interface BridgedMcp {
1257
1277
  *
1258
1278
  * Treatment per server:
1259
1279
  * - "connected" → force `enabled: true` (mirror opencode)
1280
+ * - "pending" → leave disk value (opencode 2 is still connecting;
1281
+ * see `MCP_PENDING_STATUS`)
1260
1282
  * - any other status → force `enabled: false` (don't ship a server
1261
1283
  * opencode can't run; user fixes it in opencode first)
1262
1284
  * - missing entry → leave disk value