@khalilgharbaoui/opencode-claude-code-plugin 0.15.4 → 0.17.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
@@ -249,7 +249,8 @@ model: claude-code-appical/claude-opus-5@appical
249
249
  "skipPermissions": true,
250
250
  "permissionMode": "default",
251
251
  "bridgeOpencodeMcp": true,
252
- "strictMcpConfig": false
252
+ "strictMcpConfig": false,
253
+ "idleProcessTimeoutMs": 900000
253
254
  }
254
255
  }
255
256
  }
@@ -260,7 +261,7 @@ model: claude-code-appical/claude-opus-5@appical
260
261
  |---|---|---|---|
261
262
  | `cliPath` | string | `process.env.CLAUDE_CLI_PATH ?? "claude"` | Path to the `claude` binary. |
262
263
  | `accounts` | string[] | – | Optional account list. `default` is implicit. Expands into `Claude Code (Default)`, `Claude Code (Personal)`, etc. |
263
- | `cwd` | string | `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**, so opencode's project switching works. |
264
+ | `cwd` | string | session directory, then `process.cwd()` | Working directory for the spawned CLI. Resolved **lazily per request**: an explicit value wins, then the opencode session's own `directory` (so `opencode serve` and the web UI spawn in the right project even though one server handles many), then `process.cwd()`. Contributed by [@galvani](https://github.com/galvani). |
264
265
  | `skipPermissions` | boolean | `true` | Pass `--dangerously-skip-permissions` to `claude`. Ignored when `proxyTools` is set — the proxy handles permissions through opencode instead. |
265
266
  | `permissionMode` | `acceptEdits` \| `auto` \| `bypassPermissions` \| `default` \| `dontAsk` \| `plan` | – | Forwarded to `claude --permission-mode`. |
266
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). |
@@ -278,6 +279,8 @@ model: claude-code-appical/claude-opus-5@appical
278
279
  | `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
279
280
  | `compactionModel` | string | `"claude-haiku-4-5"` | Model used when opencode invokes `/compact`. Override per-process via the `CLAUDE_CODE_COMPACTION_MODEL` env var (env wins over config). See [Compaction](#compaction). |
280
281
  | `ignoreAnthropicApiKey` | boolean | `false` | Strip `ANTHROPIC_API_KEY` / `ANTHROPIC_AUTH_TOKEN` from every spawned `claude` process so it authenticates with your logged-in subscription instead of pay-as-you-go API billing. The plugin warns once at startup whenever an API key is detected, regardless of this setting. See [Billing](#billing-change-june-15-2026-agent-sdk-credit). |
282
+ | `idleProcessTimeoutMs` | number | – | Kill a retained headless Claude worker after this many idle milliseconds following a completed turn. The session id is preserved for `--resume`; a new turn cancels the timer. Values above Node's maximum timer delay (`2147483647`) are ignored. Omit or set to `0` to retain workers until LRU eviction. Interactive transport is excluded. Contributed by [@bernardofortes](https://github.com/bernardofortes). |
283
+ | `bridgeOpencodeSkills` | boolean | `false` | Expose your opencode skills to Claude's native `Skill` tool. See [Skill bridge](#skill-bridge). Written by [@broskees](https://github.com/broskees). |
281
284
  | `interactive` | boolean | `false` | **Experimental.** Drive the interactive `claude` TUI (subscription billing) instead of headless `--print`. Requires opencode running under Bun with PTY support; silently falls back to headless otherwise. Env: `CLAUDE_CODE_INTERACTIVE_TRANSPORT=1`. See [Interactive transport](#interactive-transport-experimental). |
282
285
  | `interactiveBypass` | boolean | `false` | Deprecated/no-op with `interactive`: Claude Code's TUI shows a manual safety confirmation for `bypassPermissions`, so the plugin intentionally does not pass it. |
283
286
  | `interactiveAllowTools` | string[] | `["Bash", "Edit", "Write", "Read", "WebFetch"]` | With `interactive`: built-in tools pre-allowed without prompting (replaces the default list). MCP server wildcards (`mcp__<server>__*`) are always added from the bridged config. |
@@ -354,7 +357,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
354
357
  | `"Edit"` | `Edit`, `MultiEdit` | `mcp__opencode_proxy__edit` |
355
358
  | `"Write"` | `Write` | `mcp__opencode_proxy__write` |
356
359
  | `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
357
- | `"Task"` | `Agent` | `mcp__opencode_proxy__task` |
360
+ | `"Task"` | `Agent` | `mcp__opencode_proxy__task`, `mcp__opencode_proxy__task_batch` |
358
361
  | `"Question"` | `AskUserQuestion` | `mcp__opencode_proxy__question` |
359
362
  | `"Compress"` | none | `mcp__opencode_proxy__compress` |
360
363
 
@@ -366,6 +369,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
366
369
  - **Resume:** pass the child session ID back as `task_id` to continue that subagent session. Omit it to create a fresh child.
367
370
  - **Nested tasks:** current opencode defaults `subagent_depth` to `1`, so a first-level child cannot launch another child. Increase top-level `subagent_depth` to permit deeper nesting, and explicitly grant `permission.task` on every subagent that should delegate; opencode otherwise adds a task deny to spawned subagent sessions.
368
371
  - **Background:** `background: true` returns after starting the child and lets opencode notify the parent when it finishes. Current opencode requires `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` in the environment of the opencode process. Foreground is the default.
372
+ - **Several at once:** `mcp__opencode_proxy__task_batch` takes a `tasks` array of ordinary task inputs and runs them concurrently. It exists because Claude Code sends MCP requests one at a time: when the model emits two `task` calls in one response, the second only leaves the CLI after the first has returned (measured live, 2026-09-06), so "launch two subagents" was always serial. The plugin turns one `task_batch` call into N opencode `task` calls inside a single tool boundary, which opencode executes in parallel, then hands the model every result together, labelled in task order. Same permissions, same 60-minute deadline, same `subagent_type` list. Enabled whenever `Task` is proxied. Designed and first implemented by [@broskees](https://github.com/broskees) on his fork.
369
373
 
370
374
  **Steering models to it.** Headless Claude Code CLIs expose no `Agent`/`Task`
371
375
  dispatch tool of their own (verified on 2.1.211), while they *do* expose
@@ -549,6 +553,25 @@ Notes:
549
553
 
550
554
  Fully restart opencode after upgrading to load the command and runtime changes. Other providers do not gain Claude's native side-question behavior from this command.
551
555
 
556
+ ## Skill bridge
557
+
558
+ opencode and Claude Code use the same on-disk skill format, a `<name>/SKILL.md` whose frontmatter carries `name` and `description`, but they read from different directories. opencode looks in `.opencode/skills/` and `~/.config/opencode/skills/`; the Claude CLI looks in `~/.claude/skills/` and its own plugins. So opencode advertises your skills in the system prompt it forwards, the model calls `Skill("browser-automation")`, and Claude answers `Unknown skill`.
559
+
560
+ With `bridgeOpencodeSkills: true` the plugin discovers your opencode skills, stages a throwaway Claude Code plugin directory that links them, and passes it as `claude --plugin-dir`. They register natively, prefixed with the plugin name:
561
+
562
+ ```text
563
+ opencode-skills:browser-automation
564
+ opencode-skills:rtk
565
+ ```
566
+
567
+ Claude can invoke them with the Skill tool or as `/opencode-skills:<name>`. `--plugin-dir` is scoped to the spawned session, so nothing is written into your `~/.claude`.
568
+
569
+ Discovery order, first match wins: `.opencode/skills/` walking up from the working directory, then `~/.opencode/skills/`, then `$OPENCODE_CONFIG_DIR/skills/`, then `~/.config/opencode/skills/`. A project skill shadows a global one of the same name. If the skill set is unchanged the staged directory is reused between spawns.
570
+
571
+ It is **off by default** here, unlike on the fork it came from: every bridged skill is also listed in the system prompt opencode already forwards, so a large skill set is paid for twice on every turn. Turn it on when you see `Unknown skill`. It no-ops on the compaction path and on a Claude CLI without `--plugin-dir` (the plugin probes `claude --help` and logs a notice).
572
+
573
+ This bridge was written by [@broskees](https://github.com/broskees) (Joseph Roberts) on his fork and absorbed here with credit; see [Credits](#credits).
574
+
552
575
  ## WebSearch routing
553
576
 
554
577
  Claude Code ships a built-in `WebSearch` tool. The `webSearch` option controls who actually executes those calls:
@@ -613,7 +636,8 @@ Each chat keeps a long-lived `claude` subprocess so the model retains its native
613
636
  - **Same chat, multiple turns** → process reused, full Claude context retained.
614
637
  - **New chat** → fresh process under the new session key.
615
638
  - **Resumed chat after restart** → in-memory state is gone; a new process spawns and the conversation history is summarized and prepended.
616
- - **Abort (Ctrl+C)** → stream closes, process stays alive for the next message in that chat.
639
+ - **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. 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).
640
+ - **Idle timeout** → when `idleProcessTimeoutMs` is configured, a completed headless turn arms an eviction timer; reuse cancels it, and eviction preserves the session id for `--resume`.
617
641
  - **Cap**: 16 active processes, LRU eviction.
618
642
 
619
643
  ---
@@ -933,6 +957,26 @@ The GitHub Actions workflow at `.github/workflows/publish.yml` runs `npm publish
933
957
  </picture>
934
958
  </a>
935
959
 
960
+ ## Credits
961
+
962
+ This plugin absorbs work from its forks directly, cherry-picked with the original authorship preserved or reimplemented with the author named in the commit, rather than waiting on pull requests. The people behind the features you are using:
963
+
964
+ | Who | What | Where |
965
+ |---|---|---|
966
+ | [@galvani](https://github.com/galvani) (Jan Kozak) | Per-session working directory for `opencode serve`, so one server spawns each project's `claude` in the right place. Also found the stale `toolCallMap` re-emission three months before it was fixed here. | `9e02ce4`, `2238ed0` |
967
+ | [@HeikoAtGitHub](https://github.com/HeikoAtGitHub) | Stopped sending `AGENTS.md` to the model twice (opencode already forwards it). Independently diagnosed the 5-minute proxy wall. | `25260a4`, `42f426d` |
968
+ | [@bernardofortes](https://github.com/bernardofortes) (Bernardo Fortes) | `idleProcessTimeoutMs`, idle eviction of retained `claude` workers. | `a5f723a` |
969
+ | [@broskees](https://github.com/broskees) (Joseph Roberts) | Task proxy default-on (PR #18), the abort `interrupt` so Esc really stops the CLI, the skill bridge, `task_batch` for concurrent subagents (and the measurement that the CLI serialises MCP calls), and the undici 300 s diagnosis of the proxy wall. | PR #18, `68ed142` |
970
+ | [@jknlsn](https://github.com/jknlsn) (Jake Nelson) | Per-tool proxy timeouts, subagent dispatch steering, the question proxy, the start watchdog respawn. | `84f3db9`, `94980a6`, `47501d0`, `ffefc24` |
971
+ | [@CollieIsCute](https://github.com/CollieIsCute) (Collie Tsai) | The plan-mode approval bridge. | `8c5b583` |
972
+ | [@flupkede](https://github.com/flupkede) | The compress proxy tool design and the AI-SDK v4 image-part fix. | `4ac319f`, `60a6e9a` |
973
+ | [@CNQQC](https://github.com/CNQQC) | Cost units corrected to dollars per million tokens (PR #25). | PR #25 |
974
+ | [@willmcginnis](https://github.com/willmcginnis) | The proxy endpoint authentication (PR #28, GHSA-3mxm-w7gf-3c5x). | PR #28 |
975
+ | [@nic-lan](https://github.com/nic-lan) | The issue #29 diagnosis of subagent output lost across the CLI resume boundary. | #29 |
976
+ | [@JWebCoder](https://github.com/JWebCoder) (joao moura) | Diagnosed that auto-continue never fires on current CLIs (PR #15). | PR #15 |
977
+
978
+ Commit hashes are on the contributors' forks where the work was cherry-picked; `git log --author` on this repo shows the preserved authorship.
979
+
936
980
  ## License
937
981
 
938
982
  MIT. See [LICENSE](./LICENSE).
package/dist/index.d.ts CHANGED
@@ -194,6 +194,10 @@ interface ClaudeCodeConfig {
194
194
  autoContinueIncompleteTurns?: boolean | "smart";
195
195
  compactionModel?: string;
196
196
  ignoreAnthropicApiKey?: boolean;
197
+ /** Kill an idle headless Claude worker after this many milliseconds. */
198
+ idleProcessTimeoutMs?: number;
199
+ /** Stage opencode skills as a `--plugin-dir` so Claude's Skill tool can run them. */
200
+ bridgeOpencodeSkills?: boolean;
197
201
  logging?: LoggingConfig;
198
202
  }
199
203
  interface LoggingConfig {
@@ -365,6 +369,23 @@ interface ClaudeCodeProviderSettings {
365
369
  * plugin logs a one-time warning at startup when an API key is detected.
366
370
  */
367
371
  ignoreAnthropicApiKey?: boolean;
372
+ /**
373
+ * Kill a retained headless Claude worker after this many milliseconds of
374
+ * inactivity following a completed turn. Starting another turn cancels the
375
+ * timer, and the Claude session id is retained for a transparent resume.
376
+ * Omit or set to 0 to keep workers until LRU eviction. Interactive transport
377
+ * is excluded because it does not currently guarantee session-id resume.
378
+ */
379
+ idleProcessTimeoutMs?: number;
380
+ /**
381
+ * Expose your opencode skills (`.opencode/skills`, `~/.config/opencode/skills`)
382
+ * to Claude Code's native Skill tool by staging them as a session-scoped
383
+ * `--plugin-dir`. Off by default: every bridged skill is also listed in the
384
+ * system prompt opencode already forwards, so a large skill set is paid for
385
+ * twice per turn. Turn it on when the model tries `Skill("<name>")` and gets
386
+ * `Unknown skill`. No-op on CLIs without `--plugin-dir`.
387
+ */
388
+ bridgeOpencodeSkills?: boolean;
368
389
  /**
369
390
  * Routing for Claude's built-in `WebSearch` tool.
370
391
  *
@@ -382,7 +403,7 @@ interface ClaudeCodeProviderSettings {
382
403
  * underlying claude process so newly enabled / disabled MCPs become
383
404
  * visible to the model without restarting opencode or starting a new
384
405
  * chat. Eviction happens at the start of the next user turn (never mid
385
- * tool-call) and `--session-id` is preserved so the conversation
406
+ * tool-call) and the session id is preserved for `--resume` so the conversation
386
407
  * continues seamlessly. Defaults to `true`.
387
408
  *
388
409
  * Set to `false` to keep the previous behavior (cached subprocess
@@ -609,6 +630,16 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
609
630
  */
610
631
  private ensureProxyServer;
611
632
  private extractPendingProxyResult;
633
+ /**
634
+ * The result opencode produced for a pending proxy call, if the prompt
635
+ * carries it. For `task_batch` that means every child's result gathered
636
+ * back onto the parent: opencode runs the children in one step and hands
637
+ * all their results to the next call together, so a partial set is not
638
+ * expected. If it ever happens the batch still resolves, with the gap
639
+ * named in the text, because leaving the parent pending would send this
640
+ * turn down the fresh-envelope path and reject the call as orphaned.
641
+ */
642
+ private extractPendingProxyResultForCall;
612
643
  /**
613
644
  * Resolve the session affinity token for this LLM call. Delegates to the
614
645
  * exported `resolveSessionAffinity` helper so the logic is unit-testable.