@khalilgharbaoui/opencode-claude-code-plugin 0.11.0 → 0.11.2
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 +19 -4
- package/dist/index.d.ts +11 -0
- package/dist/index.js +106 -7
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -281,6 +281,20 @@ By default, the plugin proxies `Bash`, `Edit`, `Write`, `WebFetch`, and `Task`.
|
|
|
281
281
|
- **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.
|
|
282
282
|
- **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.
|
|
283
283
|
|
|
284
|
+
**Steering models to it.** Headless Claude Code CLIs expose no `Agent`/`Task`
|
|
285
|
+
dispatch tool of their own (verified on 2.1.211), while they *do* expose
|
|
286
|
+
`TaskCreate` — a todo tool. So "use a subagent" requests get mis-resolved:
|
|
287
|
+
a todo appears, nothing runs, and the model may still narrate a successful
|
|
288
|
+
dispatch. Two spawn-time countermeasures prevent that. The plugin injects
|
|
289
|
+
opencode's live agent-type list into the `task` proxy description (so the model
|
|
290
|
+
picks a real `subagent_type` instead of guessing a Claude Code name like
|
|
291
|
+
`general-purpose`, and doesn't grep configs to check a subagent exists), and
|
|
292
|
+
appends a system-prompt note naming
|
|
293
|
+
`mcp__opencode_proxy__task` as the only dispatch path — with the ToolSearch
|
|
294
|
+
recovery step for harnesses that defer MCP tool schemas. Both apply per Claude
|
|
295
|
+
process at spawn, and provider options are read once at opencode startup, so
|
|
296
|
+
`proxyTools` changes need a full opencode restart.
|
|
297
|
+
|
|
284
298
|
Only those five 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.
|
|
285
299
|
|
|
286
300
|
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:
|
|
@@ -544,8 +558,8 @@ grep "plugin ready" ~/.local/share/opencode-claude-code/plugin.log
|
|
|
544
558
|
|
|
545
559
|
```json
|
|
546
560
|
{
|
|
547
|
-
"plugin": "0.
|
|
548
|
-
"opencode": "
|
|
561
|
+
"plugin": "0.11.1",
|
|
562
|
+
"opencode": "1.18.5",
|
|
549
563
|
"cwd": { "resolved": "/Users/you/code/app", "source": "process" },
|
|
550
564
|
"providers": ["claude-code-default", "claude-code-work"],
|
|
551
565
|
"accounts": ["default", "work"],
|
|
@@ -568,8 +582,9 @@ Reading it:
|
|
|
568
582
|
flags like `--thinking-display`.
|
|
569
583
|
- **`mcpServers`** is the on-disk merge, before opencode's runtime toggles
|
|
570
584
|
are applied (those aren't settled yet at startup).
|
|
571
|
-
- **`opencode`**
|
|
572
|
-
not
|
|
585
|
+
- **`opencode`** is read from the running opencode binary (`--version`), since
|
|
586
|
+
opencode still does not hand its version to plugins. It reads `unknown` when
|
|
587
|
+
opencode is run from source rather than as the packaged binary.
|
|
573
588
|
|
|
574
589
|
### Default behavior (no config, no env)
|
|
575
590
|
|
package/dist/index.d.ts
CHANGED
|
@@ -505,6 +505,17 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
505
505
|
* client is unavailable, or no MCP servers are configured.
|
|
506
506
|
*/
|
|
507
507
|
private resolvedProxyMcpTools;
|
|
508
|
+
/**
|
|
509
|
+
* Live description of opencode's `task` tool for the current
|
|
510
|
+
* provider/model, exactly as opencode's registry renders it for native
|
|
511
|
+
* models — including the "Available agent types" list (built from the
|
|
512
|
+
* default agent's permissions). Overlaid onto the static `task` proxy
|
|
513
|
+
* def so Claude sees the same subagent catalog native opencode models
|
|
514
|
+
* see, instead of hunting through config files. Returns undefined when
|
|
515
|
+
* the SDK client is unavailable (direct AI-SDK use, tests) so the
|
|
516
|
+
* static def stands.
|
|
517
|
+
*/
|
|
518
|
+
private fetchLiveTaskDescription;
|
|
508
519
|
/**
|
|
509
520
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
510
521
|
* The process lifecycle owns the server lifecycle via session-manager.
|
package/dist/index.js
CHANGED
|
@@ -2117,6 +2117,37 @@ function buildProxyTimeoutError(toolName, ms) {
|
|
|
2117
2117
|
}
|
|
2118
2118
|
return new Error(base);
|
|
2119
2119
|
}
|
|
2120
|
+
var TASK_PROXY_NOTE = "This is the ONLY tool that dispatches opencode subagents (including user @-mentions). Claude Code's built-in TaskCreate/TaskUpdate manage a local todo list and cannot dispatch subagents. Do not search config files to verify a subagent type exists \u2014 invalid types fail fast with a clear error. Foreground calls block until the subagent finishes; set `background` to request opencode's background execution mode. Task calls get a 60-minute proxy deadline by default (configurable via proxyToolTimeoutMs).";
|
|
2121
|
+
var AGENT_TYPES_HEADING = "Available agent types";
|
|
2122
|
+
var AGENT_BLURB_LIMIT = 140;
|
|
2123
|
+
function extractAgentTypeList(liveDescription) {
|
|
2124
|
+
const live = liveDescription?.trim();
|
|
2125
|
+
if (!live) return void 0;
|
|
2126
|
+
const start = live.indexOf(AGENT_TYPES_HEADING);
|
|
2127
|
+
if (start === -1) return void 0;
|
|
2128
|
+
const entries = [];
|
|
2129
|
+
for (const raw of live.slice(start).split("\n")) {
|
|
2130
|
+
const match = /^-\s*([^:]+):\s*(.+)$/.exec(raw.trim());
|
|
2131
|
+
if (!match) continue;
|
|
2132
|
+
const name = match[1].trim();
|
|
2133
|
+
const blurb = match[2].trim();
|
|
2134
|
+
entries.push(
|
|
2135
|
+
`- ${name}: ${blurb.length > AGENT_BLURB_LIMIT ? `${blurb.slice(0, AGENT_BLURB_LIMIT).trimEnd()}\u2026` : blurb}`
|
|
2136
|
+
);
|
|
2137
|
+
}
|
|
2138
|
+
if (entries.length === 0) return void 0;
|
|
2139
|
+
return `Valid subagent_type values, from opencode's live registry \u2014 anything else fails:
|
|
2140
|
+
${entries.join("\n")}`;
|
|
2141
|
+
}
|
|
2142
|
+
function overlayTaskProxyDescription(tools, liveDescription) {
|
|
2143
|
+
const agentTypes = extractAgentTypeList(liveDescription);
|
|
2144
|
+
if (!agentTypes) return tools;
|
|
2145
|
+
return tools.map(
|
|
2146
|
+
(t) => t.name === "task" ? { ...t, description: `${agentTypes}
|
|
2147
|
+
|
|
2148
|
+
${t.description}` } : t
|
|
2149
|
+
);
|
|
2150
|
+
}
|
|
2120
2151
|
var DEFAULT_PROXY_TOOLS = [
|
|
2121
2152
|
{
|
|
2122
2153
|
name: "bash",
|
|
@@ -2209,7 +2240,7 @@ var DEFAULT_PROXY_TOOLS = [
|
|
|
2209
2240
|
},
|
|
2210
2241
|
{
|
|
2211
2242
|
name: "task",
|
|
2212
|
-
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json).
|
|
2243
|
+
description: "Launch an opencode subagent to handle a complex multi-step task autonomously. Routed through opencode's task tool so subagent orchestration, permission, and lifecycle are handled by opencode. Use `subagent_type` to pick which configured subagent runs (e.g. `build`, `general`, `explore`, or any custom subagent declared in opencode.json). " + TASK_PROXY_NOTE,
|
|
2213
2244
|
inputSchema: {
|
|
2214
2245
|
type: "object",
|
|
2215
2246
|
properties: {
|
|
@@ -2884,6 +2915,14 @@ than pausing for user confirmation between subtasks. End the turn only
|
|
|
2884
2915
|
when the task is done, you need clarification on intent, or you hit a real
|
|
2885
2916
|
blocker. The user can interrupt or abort at any time; turn endings should
|
|
2886
2917
|
mark meaningful checkpoints, not every completed substep.`;
|
|
2918
|
+
var SUBAGENT_DISPATCH_HINT = `## opencode subagents
|
|
2919
|
+
|
|
2920
|
+
Subagent dispatch in this environment goes through exactly one tool: \`mcp__opencode_proxy__task\`.
|
|
2921
|
+
|
|
2922
|
+
- When the user mentions \`@<agent>\` or an instruction says "call the task tool with subagent: <name>", call \`mcp__opencode_proxy__task\` with \`subagent_type: "<name>"\`.
|
|
2923
|
+
- If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__task\`), then call it.
|
|
2924
|
+
- Claude Code's built-in TaskCreate/TaskUpdate/TaskList manage a local todo list. They cannot dispatch subagents; creating a task there runs nothing. Never report a subagent as dispatched unless \`mcp__opencode_proxy__task\` returned its result.
|
|
2925
|
+
- Do not verify a subagent's existence by searching config files \u2014 the tool's description lists the available agent types, and invalid types fail fast with a clear error.`;
|
|
2887
2926
|
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
2888
2927
|
|
|
2889
2928
|
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
@@ -3058,6 +3097,24 @@ var ClaudeCodeLanguageModel = class {
|
|
|
3058
3097
|
}
|
|
3059
3098
|
return out.length > 0 ? out : null;
|
|
3060
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Live description of opencode's `task` tool for the current
|
|
3102
|
+
* provider/model, exactly as opencode's registry renders it for native
|
|
3103
|
+
* models — including the "Available agent types" list (built from the
|
|
3104
|
+
* default agent's permissions). Overlaid onto the static `task` proxy
|
|
3105
|
+
* def so Claude sees the same subagent catalog native opencode models
|
|
3106
|
+
* see, instead of hunting through config files. Returns undefined when
|
|
3107
|
+
* the SDK client is unavailable (direct AI-SDK use, tests) so the
|
|
3108
|
+
* static def stands.
|
|
3109
|
+
*/
|
|
3110
|
+
async fetchLiveTaskDescription() {
|
|
3111
|
+
const items = await fetchOpencodeToolList(
|
|
3112
|
+
this.config.provider,
|
|
3113
|
+
this.modelId,
|
|
3114
|
+
this.config.cwd
|
|
3115
|
+
);
|
|
3116
|
+
return items?.find((item) => item.id === "task")?.description || void 0;
|
|
3117
|
+
}
|
|
3061
3118
|
/**
|
|
3062
3119
|
* Create a proxy MCP server for a single active Claude process/session.
|
|
3063
3120
|
* The process lifecycle owns the server lifecycle via session-manager.
|
|
@@ -3929,7 +3986,23 @@ ${plan}
|
|
|
3929
3986
|
discovery.allEnabledServerNames
|
|
3930
3987
|
);
|
|
3931
3988
|
const excludeServers = proxyMcpTools ? new Set(discovery.allEnabledServerNames) : void 0;
|
|
3932
|
-
const
|
|
3989
|
+
const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
|
|
3990
|
+
let enrichedProxy = resolvedProxy;
|
|
3991
|
+
if (resolvedProxy && taskProxyEnabled) {
|
|
3992
|
+
const liveTaskDescription = await self.fetchLiveTaskDescription();
|
|
3993
|
+
enrichedProxy = overlayTaskProxyDescription(
|
|
3994
|
+
resolvedProxy,
|
|
3995
|
+
liveTaskDescription
|
|
3996
|
+
);
|
|
3997
|
+
log.info("task proxy description overlay", {
|
|
3998
|
+
applied: Boolean(liveTaskDescription),
|
|
3999
|
+
liveDescriptionLength: liveTaskDescription?.length ?? 0,
|
|
4000
|
+
listsAgentTypes: Boolean(
|
|
4001
|
+
liveTaskDescription?.includes("Available agent types")
|
|
4002
|
+
)
|
|
4003
|
+
});
|
|
4004
|
+
}
|
|
4005
|
+
const combinedProxyTools = enrichedProxy || proxyMcpTools ? [...enrichedProxy ?? [], ...proxyMcpTools ?? []] : null;
|
|
3933
4006
|
if (!proxyServer && combinedProxyTools) {
|
|
3934
4007
|
proxyServer = await self.ensureProxyServer(combinedProxyTools, sk);
|
|
3935
4008
|
}
|
|
@@ -3946,7 +4019,10 @@ ${plan}
|
|
|
3946
4019
|
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
3947
4020
|
cwd,
|
|
3948
4021
|
self.config.multiStepContinuation !== false,
|
|
3949
|
-
|
|
4022
|
+
[
|
|
4023
|
+
...extractSystemMessages(options.prompt),
|
|
4024
|
+
...taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : []
|
|
4025
|
+
]
|
|
3950
4026
|
);
|
|
3951
4027
|
cliArgs = buildCliArgs({
|
|
3952
4028
|
sessionKey: sk,
|
|
@@ -5537,8 +5613,10 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
5537
5613
|
}
|
|
5538
5614
|
|
|
5539
5615
|
// src/startup-diagnostics.ts
|
|
5616
|
+
import { execFile as execFile2 } from "child_process";
|
|
5540
5617
|
import * as fs5 from "fs";
|
|
5541
5618
|
import * as path6 from "path";
|
|
5619
|
+
import { promisify as promisify2 } from "util";
|
|
5542
5620
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5543
5621
|
var cachedPluginVersion;
|
|
5544
5622
|
function pluginVersion() {
|
|
@@ -5564,6 +5642,29 @@ function pickOpencodeVersion(input) {
|
|
|
5564
5642
|
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
5565
5643
|
return void 0;
|
|
5566
5644
|
}
|
|
5645
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
5646
|
+
var opencodeVersionProbe;
|
|
5647
|
+
function detectOpencodeVersion(execPath = process.execPath) {
|
|
5648
|
+
if (opencodeVersionProbe) return opencodeVersionProbe;
|
|
5649
|
+
opencodeVersionProbe = (async () => {
|
|
5650
|
+
if (!path6.basename(execPath).toLowerCase().includes("opencode")) {
|
|
5651
|
+
log.debug("skipping opencode version probe: execPath is not opencode", { execPath });
|
|
5652
|
+
return void 0;
|
|
5653
|
+
}
|
|
5654
|
+
try {
|
|
5655
|
+
const { stdout } = await execFileAsync2(execPath, ["--version"], { timeout: 5e3 });
|
|
5656
|
+
const match = /\d+\.\d+\.\d+\S*/.exec(stdout.trim());
|
|
5657
|
+
return match ? match[0] : void 0;
|
|
5658
|
+
} catch (err) {
|
|
5659
|
+
log.debug("opencode version probe failed", {
|
|
5660
|
+
execPath,
|
|
5661
|
+
error: err instanceof Error ? err.message : String(err)
|
|
5662
|
+
});
|
|
5663
|
+
return void 0;
|
|
5664
|
+
}
|
|
5665
|
+
})();
|
|
5666
|
+
return opencodeVersionProbe;
|
|
5667
|
+
}
|
|
5567
5668
|
function describeSpawnCwd(configured, live = process.cwd(), captured = getOpencodeProjectDirectory()) {
|
|
5568
5669
|
if (typeof configured === "string" && configured.length > 0) {
|
|
5569
5670
|
return { resolved: configured, source: "configured" };
|
|
@@ -5619,10 +5720,8 @@ function logStartupDiagnostics(providers, opencodeVersion) {
|
|
|
5619
5720
|
logged = true;
|
|
5620
5721
|
void (async () => {
|
|
5621
5722
|
try {
|
|
5622
|
-
const
|
|
5623
|
-
|
|
5624
|
-
opencodeVersion
|
|
5625
|
-
);
|
|
5723
|
+
const version = opencodeVersion ?? process.env.OPENCODE_VERSION ?? await detectOpencodeVersion();
|
|
5724
|
+
const { claudeCliPath, ...rest } = collectStartupDiagnostics(providers, version);
|
|
5626
5725
|
const cli = await detectCliVersion(claudeCliPath);
|
|
5627
5726
|
const diagnostics = {
|
|
5628
5727
|
...rest,
|