@khalilgharbaoui/opencode-claude-code-plugin 0.31.1 → 0.32.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
@@ -576,6 +576,7 @@ This is the part to read before turning it on. Three whole features of this plug
576
576
  - **Permissions:** the interactive TUI has no `can_use_tool` control channel, so tools can't be approved per-call through opencode. Built-in tools are pre-allowed via a settings allow list (default `Bash, Edit, Write, Read, WebFetch`; override with `interactiveAllowTools`). `bypassPermissions` is intentionally not used here because Claude Code shows a manual safety confirmation in the TUI and defaults to exit.
577
577
  - **Input is text-only:** images and other non-text blocks are dropped (with a logged warning); tool results are rendered as labeled text.
578
578
  - **Output granularity:** text arrives per transcript record, not token-by-token, so it can feel chunkier than headless streaming.
579
+ - **Token counts come from the transcript, one count per API call.** The session JSONL writes one record per content block (thinking, text, tool_use) and every record of a call repeats that call's final usage, so the transport counts each call once, keyed by its message id. The numbers then mean exactly what they do on the headless transport: [`turnStats`](#per-turn-stats) gets the turn's totals and opencode gets the last call's context plus the turn's output. Before this was fixed a four-tool turn reported 1,306 output tokens against a real 653, and its input and cache counts were one call's instead of the turn's. An all-zero `<synthetic>` record (how the CLI writes "Login expired" or a session limit into the transcript) is not counted as a call.
579
580
  - **Turn timeout:** a turn that produces no terminal stop within 30 minutes is reported honestly as an error result (visible truncation), not silently ended.
580
581
  - **No idle eviction:** `idleProcessTimeoutMs` does not apply to interactive sessions.
581
582
  - `/compact` always uses the headless transport regardless of this setting.
@@ -596,7 +597,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
596
597
  | `"Edit"` | `Edit`, `MultiEdit` | `mcp__opencode_proxy__edit` |
597
598
  | `"Write"` | `Write` | `mcp__opencode_proxy__write` |
598
599
  | `"WebFetch"` | `WebFetch` | `mcp__opencode_proxy__webfetch` |
599
- | `"Task"` | `Agent` | `mcp__opencode_proxy__task`, `mcp__opencode_proxy__task_batch` |
600
+ | `"Task"` | `Agent` | `mcp__opencode_proxy__task`, `mcp__opencode_proxy__task_batch`, and on a host that runs background subagents `mcp__opencode_proxy__task_status`, `mcp__opencode_proxy__task_cancel` |
600
601
  | `"Question"` | `AskUserQuestion` | `mcp__opencode_proxy__question` |
601
602
  | `"Compress"` | none | `mcp__opencode_proxy__compress` |
602
603
 
@@ -607,7 +608,7 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
607
608
  - **Permissions:** the calling agent's `permission.task` rule applies to the target `subagent_type`. Grant `task: "allow"` on agents that should delegate without a prompt; an `ask` or `deny` rule remains authoritative. The plugin never bypasses this decision.
608
609
  - **Resume:** pass the child session ID back as `task_id` to continue that subagent session. Omit it to create a fresh child.
609
610
  - **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.
610
- - **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.
611
+ - **Background:** see [Background subagents](#background-subagents) below. Foreground is the default.
611
612
  - **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 no-deadline default, same `subagent_type` list. Enabled whenever `Task` is proxied. Designed and first implemented by [@broskees](https://github.com/broskees) on his fork.
612
613
 
613
614
  **Steering models to it.** Headless Claude Code CLIs expose no `Agent`/`Task`
@@ -624,6 +625,42 @@ recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
624
625
  process at spawn, and provider options are read once at opencode startup, so
625
626
  `proxyTools` changes need a full opencode restart.
626
627
 
628
+ ### Background subagents
629
+
630
+ A foreground `task` call blocks the conversation until the subagent finishes, and so does `task_batch`. Background dispatch is the other shape: start a subagent, keep working, collect the result later. opencode owns it, this plugin surfaces it, and it is **off unless the opencode process has `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`** (or the blanket `OPENCODE_EXPERIMENTAL=true`) in its environment on opencode 1.x. On opencode 2.x it is unconditional.
631
+
632
+ ```sh
633
+ OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
634
+ ```
635
+
636
+ With it set, `mcp__opencode_proxy__task` takes `background: true` and the call comes straight back:
637
+
638
+ ```xml
639
+ <task id="ses_f10789724ffes9OfQApCB04IRe" state="running">
640
+ <summary>Background task started</summary>
641
+ <task_result>
642
+ The task is working in the background. You will be notified automatically when it finishes.
643
+ </task_result>
644
+ </task>
645
+ ```
646
+
647
+ Claude keeps working. When the subagent finishes, opencode prompts the same conversation with the result as a new message, so it arrives as its own turn rather than as that call's result. Measured end to end on opencode 1.18.33 with claude-haiku-4-5: the dispatch returned in 14 s while the child's 30-second command was still running, Claude ran another tool and ended its turn 18 s in, and the `<task ... state="completed">` message landed 43 s later. That notification is automatic, so the right thing after a background dispatch is to end the turn, not to wait or poll.
648
+
649
+ **The gate is enforced at the schema, not at the call.** On a host without the flag opencode rejects `background: true` outright with `Background subagents require OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true`, which costs a whole dispatch. So the plugin reads the host's own `task` schema (the same registry fetch that supplies the agent-type list) and, when it has no `background` property, strips the field before Claude ever sees it. Nothing about a default install changes: the model is shown exactly `description`, `prompt`, `subagent_type`, `task_id`, `command`. Either way `plugin.log` says which:
650
+
651
+ ```
652
+ background subagent gate {"supported":true,"registryResolved":true,"hostApi":"v1", ...}
653
+ ```
654
+
655
+ **Collect and cancel.** opencode delivers a background result by pushing it into the conversation and offers nothing else: no route reads a result back, and nothing stops a background child. A notification that never lands (an interrupted turn, an errored turn, a compaction across it) would lose the work, and a subagent running away could only be stopped from another pane. So on a host that runs background subagents, and only there, two more proxy tools ride along with `Task` in the same way `task_batch` does:
656
+
657
+ | Tool | What it does |
658
+ | --- | --- |
659
+ | `mcp__opencode_proxy__task_status` | Reads the state of a background subagent by its `task_id` and returns its result if it has finished. A recovery path, not a progress poll: a healthy background task delivers its own result. A result is handed over once, so asking again reports the state without repeating the output. |
660
+ | `mcp__opencode_proxy__task_cancel` | Stops a background subagent. A cancelled subagent sends no completion notification. |
661
+
662
+ The `task_id` is the `id` in the `<task …>` envelope, which is the child's own opencode session id. Both tools are answered inside the plugin rather than executed by opencode, because opencode has no tools of these names, and both refuse any session whose parent is not the conversation doing the asking. Neither can be named in `proxyTools`: they appear only when the host advertises background support, so upgrading changes nothing about what the model can do or spend on a default install.
663
+
627
664
  ### Proxy endpoint security
628
665
 
629
666
  The proxy is a small HTTP MCP server on an ephemeral loopback port, and calling it runs Bash, Edit and Write through opencode's executor. Since 0.13.2 it requires a 256-bit bearer token, generated per server and handed to Claude in the `headers` block of the `0600` MCP config file the plugin writes. Requests are also rejected unless the `Host` header matches the bound `127.0.0.1:<port>` authority, no `Origin` header is present, and the content type is `application/json`.