@khalilgharbaoui/opencode-claude-code-plugin 0.11.1 → 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 +14 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +79 -3
- 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:
|
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,
|