@khalilgharbaoui/opencode-claude-code-plugin 0.32.0 → 0.33.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 +47 -11
- package/dist/index.d.ts +2 -1
- package/dist/index.js +283 -78
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/skills/claude-code-plugin/SKILL.md +55 -1
package/dist/index.js
CHANGED
|
@@ -1633,12 +1633,12 @@ function setProxyDeadlineGuard(guard) {
|
|
|
1633
1633
|
var PROXY_DEADLINE_RECHECK_MS = 6e4;
|
|
1634
1634
|
var deadlineRecheckMs = PROXY_DEADLINE_RECHECK_MS;
|
|
1635
1635
|
function armProxyDeadline(opts) {
|
|
1636
|
-
let
|
|
1636
|
+
let cancelled2 = false;
|
|
1637
1637
|
let extended = false;
|
|
1638
1638
|
let timer = null;
|
|
1639
1639
|
const check = async () => {
|
|
1640
1640
|
timer = null;
|
|
1641
|
-
if (
|
|
1641
|
+
if (cancelled2) return;
|
|
1642
1642
|
let keep = false;
|
|
1643
1643
|
const guard = proxyDeadlineGuard;
|
|
1644
1644
|
if (guard) {
|
|
@@ -1648,7 +1648,7 @@ function armProxyDeadline(opts) {
|
|
|
1648
1648
|
keep = false;
|
|
1649
1649
|
}
|
|
1650
1650
|
}
|
|
1651
|
-
if (
|
|
1651
|
+
if (cancelled2) return;
|
|
1652
1652
|
if (keep) {
|
|
1653
1653
|
if (!extended && opts.logExtension) {
|
|
1654
1654
|
log.warn("proxy call past its deadline, but opencode is still serving it; waiting", {
|
|
@@ -1667,7 +1667,7 @@ function armProxyDeadline(opts) {
|
|
|
1667
1667
|
timer = setTimeout(() => void check(), opts.deadlineMs);
|
|
1668
1668
|
return {
|
|
1669
1669
|
cancel() {
|
|
1670
|
-
|
|
1670
|
+
cancelled2 = true;
|
|
1671
1671
|
if (timer) clearTimeout(timer);
|
|
1672
1672
|
timer = null;
|
|
1673
1673
|
}
|
|
@@ -1685,6 +1685,7 @@ function buildProxyTimeoutError(toolName, ms) {
|
|
|
1685
1685
|
}
|
|
1686
1686
|
var TASK_PROXY_NOTE = "This and task_batch are the ONLY tools that dispatch 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: invalid types fail fast with a clear error. Calls block until the subagent finishes. For two or more independent subagents in one response use task_batch, not several task calls: those run one after another. Task calls have no proxy deadline by default: the call waits for the subagent to finish (a positive proxyToolTimeoutMs override adds a deadline).";
|
|
1687
1687
|
var TASK_BACKGROUND_NOTE = 'Fire-and-collect: set `background: true` and the call returns at once with `<task id="ses_..." state="running">` instead of the subagent\'s answer. Keep working on something that does not overlap it, then end your turn: when the subagent finishes, opencode delivers its result to this conversation on its own as a new message. Do NOT poll, sleep or loop waiting for it. The `id` in that envelope is the task_id: pass it to task_status to read a result the notification did not deliver, or to task_cancel to stop a background subagent you no longer want.';
|
|
1688
|
+
var TASK_BACKGROUND_NOTE_V2 = 'Fire-and-collect: set `background: true` and the call returns at once with `The subagent is working in the background (sessionID: ses_...)` instead of the subagent\'s answer. Keep working on something that does not overlap it, then end your turn: when the subagent finishes, opencode delivers its result to this conversation on its own, as a `<subagent sessionID="..." state="completed">` message. Do NOT poll, sleep or loop waiting for it. That sessionID is the task_id: pass it to task_status to read a result the notification did not deliver, or to task_cancel to stop a background subagent you no longer want.';
|
|
1688
1689
|
var TASK_BATCH_TOOL_NAME = "task_batch";
|
|
1689
1690
|
var TASK_BATCH_PROXY_NOTE = "Use this instead of several task calls in one response: Claude Code runs MCP tool calls one at a time, so separate task calls run serially even when emitted together, while one task_batch call fans them out as parallel opencode task calls. Each task takes the same fields as the task tool. Results come back in task order, each labelled. Like task it has no proxy deadline by default (a positive proxyToolTimeoutMs override adds one).";
|
|
1690
1691
|
var TASK_INPUT_REQUIRED = ["description", "prompt", "subagent_type"];
|
|
@@ -1785,7 +1786,7 @@ var BACKGROUND_TASK_TOOL_DEFS = [
|
|
|
1785
1786
|
properties: {
|
|
1786
1787
|
task_id: {
|
|
1787
1788
|
type: "string",
|
|
1788
|
-
description: 'The id
|
|
1789
|
+
description: 'The background subagent\'s own opencode session id: the `id` in the `<task id="..." state="running">` envelope on opencode 1.x, or the `sessionID` the background dispatch reported on opencode 2.'
|
|
1789
1790
|
}
|
|
1790
1791
|
},
|
|
1791
1792
|
required: ["task_id"]
|
|
@@ -1799,21 +1800,22 @@ var BACKGROUND_TASK_TOOL_DEFS = [
|
|
|
1799
1800
|
properties: {
|
|
1800
1801
|
task_id: {
|
|
1801
1802
|
type: "string",
|
|
1802
|
-
description: 'The id
|
|
1803
|
+
description: 'The background subagent\'s own opencode session id: the `id` in the `<task id="..." state="running">` envelope on opencode 1.x, or the `sessionID` the background dispatch reported on opencode 2.'
|
|
1803
1804
|
}
|
|
1804
1805
|
},
|
|
1805
1806
|
required: ["task_id"]
|
|
1806
1807
|
}
|
|
1807
1808
|
}
|
|
1808
1809
|
];
|
|
1809
|
-
function applyBackgroundSubagentSupport(tools, supported) {
|
|
1810
|
+
function applyBackgroundSubagentSupport(tools, supported, dialect = "v1") {
|
|
1810
1811
|
if (!tools.some((t) => t.name === "task")) return tools;
|
|
1812
|
+
const note = dialect === "v2" ? TASK_BACKGROUND_NOTE_V2 : TASK_BACKGROUND_NOTE;
|
|
1811
1813
|
const adjusted = tools.map((t) => {
|
|
1812
1814
|
if (t.name !== "task" && t.name !== TASK_BATCH_TOOL_NAME) return t;
|
|
1813
1815
|
if (supported) {
|
|
1814
1816
|
return { ...t, description: `${t.description}
|
|
1815
1817
|
|
|
1816
|
-
${
|
|
1818
|
+
${note}` };
|
|
1817
1819
|
}
|
|
1818
1820
|
return { ...t, inputSchema: stripBackgroundFromSchema(t.name, t.inputSchema) };
|
|
1819
1821
|
});
|
|
@@ -2432,7 +2434,9 @@ function resolveDisallowedTools(options) {
|
|
|
2432
2434
|
function resolveProxyOpencodeToolDefs(options) {
|
|
2433
2435
|
const requested = options.requested ?? [];
|
|
2434
2436
|
if (requested.length === 0) return [];
|
|
2435
|
-
const items = options.
|
|
2437
|
+
const items = options.modelTools !== void 0 ? options.modelTools.flatMap(
|
|
2438
|
+
(tool) => (tool.type === void 0 || tool.type === "function") && typeof tool.name === "string" ? [{ id: tool.name, description: tool.description, parameters: tool.inputSchema }] : []
|
|
2439
|
+
) : options.items;
|
|
2436
2440
|
if (!items) {
|
|
2437
2441
|
log.warn(
|
|
2438
2442
|
"proxyOpencodeTools is set but opencode's tool registry did not answer; forwarding nothing this spawn",
|
|
@@ -2458,12 +2462,20 @@ function resolveProxyOpencodeToolDefs(options) {
|
|
|
2458
2462
|
unknown.push(name);
|
|
2459
2463
|
continue;
|
|
2460
2464
|
}
|
|
2465
|
+
if (item.id === "execute" && options.allowCodeExecution === false) {
|
|
2466
|
+
log.warn("Code Mode execute not forwarded: read-only permission preset");
|
|
2467
|
+
continue;
|
|
2468
|
+
}
|
|
2461
2469
|
if (taken.has(item.id)) {
|
|
2462
2470
|
collided.push(item.id);
|
|
2463
2471
|
continue;
|
|
2464
2472
|
}
|
|
2465
2473
|
if (seen.has(item.id)) continue;
|
|
2466
2474
|
seen.add(item.id);
|
|
2475
|
+
if (item.id === "execute" && (!item.parameters || typeof item.parameters !== "object" || Array.isArray(item.parameters) || !Object.keys(item.parameters).length)) {
|
|
2476
|
+
log.warn("Code Mode execute not forwarded: no model-visible input schema");
|
|
2477
|
+
continue;
|
|
2478
|
+
}
|
|
2467
2479
|
out.push({
|
|
2468
2480
|
name: item.id,
|
|
2469
2481
|
description: typeof item.description === "string" ? item.description : "",
|
|
@@ -2500,6 +2512,7 @@ function resolveMcpProxyToolDefs(options) {
|
|
|
2500
2512
|
if (tool?.type !== void 0 && tool.type !== "function") continue;
|
|
2501
2513
|
const name = typeof tool?.name === "string" ? tool.name.trim() : "";
|
|
2502
2514
|
if (!name) continue;
|
|
2515
|
+
if (name === "execute") continue;
|
|
2503
2516
|
const matchedServer = serversByLengthDesc.find(
|
|
2504
2517
|
(server2) => name === server2 || name.startsWith(`${server2}_`)
|
|
2505
2518
|
);
|
|
@@ -3132,8 +3145,8 @@ async function abortSession(sessionID) {
|
|
|
3132
3145
|
const client = opencodeClient;
|
|
3133
3146
|
if (!client?.session?.abort) return false;
|
|
3134
3147
|
try {
|
|
3135
|
-
await client.session.abort({ path: { id: sessionID } });
|
|
3136
|
-
return
|
|
3148
|
+
const res = await client.session.abort({ path: { id: sessionID } });
|
|
3149
|
+
return res.data !== false;
|
|
3137
3150
|
} catch (err) {
|
|
3138
3151
|
log.warn("failed to abort opencode session", {
|
|
3139
3152
|
sessionID,
|
|
@@ -3149,21 +3162,22 @@ var TASK_CANCEL_TOOL_NAME = "task_cancel";
|
|
|
3149
3162
|
var MAX_LEDGERS = 32;
|
|
3150
3163
|
var MAX_COLLECTED_PER_SESSION = 64;
|
|
3151
3164
|
var collected = /* @__PURE__ */ new Map();
|
|
3152
|
-
|
|
3153
|
-
|
|
3165
|
+
var cancelled = /* @__PURE__ */ new Map();
|
|
3166
|
+
function ledgerIn(ledgers2, sessionKey2) {
|
|
3167
|
+
let ids = ledgers2.get(sessionKey2);
|
|
3154
3168
|
if (!ids) {
|
|
3155
3169
|
ids = /* @__PURE__ */ new Set();
|
|
3156
|
-
|
|
3157
|
-
while (
|
|
3158
|
-
const oldest =
|
|
3170
|
+
ledgers2.set(sessionKey2, ids);
|
|
3171
|
+
while (ledgers2.size > MAX_LEDGERS) {
|
|
3172
|
+
const oldest = ledgers2.keys().next();
|
|
3159
3173
|
if (oldest.done) break;
|
|
3160
|
-
|
|
3174
|
+
ledgers2.delete(oldest.value);
|
|
3161
3175
|
}
|
|
3162
3176
|
}
|
|
3163
3177
|
return ids;
|
|
3164
3178
|
}
|
|
3165
|
-
function
|
|
3166
|
-
const ids =
|
|
3179
|
+
function mark(ledgers2, sessionKey2, taskId) {
|
|
3180
|
+
const ids = ledgerIn(ledgers2, sessionKey2);
|
|
3167
3181
|
ids.add(taskId);
|
|
3168
3182
|
while (ids.size > MAX_COLLECTED_PER_SESSION) {
|
|
3169
3183
|
const oldest = ids.values().next();
|
|
@@ -3171,11 +3185,30 @@ function markCollected(sessionKey2, taskId) {
|
|
|
3171
3185
|
ids.delete(oldest.value);
|
|
3172
3186
|
}
|
|
3173
3187
|
}
|
|
3188
|
+
function markCollected(sessionKey2, taskId) {
|
|
3189
|
+
mark(collected, sessionKey2, taskId);
|
|
3190
|
+
}
|
|
3174
3191
|
function hasCollectedBackgroundTask(sessionKey2, taskId) {
|
|
3175
3192
|
return collected.get(sessionKey2)?.has(taskId) === true;
|
|
3176
3193
|
}
|
|
3177
3194
|
function clearBackgroundTasks(sessionKey2) {
|
|
3178
3195
|
collected.delete(sessionKey2);
|
|
3196
|
+
cancelled.delete(sessionKey2);
|
|
3197
|
+
}
|
|
3198
|
+
var lastGate;
|
|
3199
|
+
function recordBackgroundSubagentGate(gate, now = Date.now()) {
|
|
3200
|
+
lastGate = { ...gate, at: now };
|
|
3201
|
+
}
|
|
3202
|
+
function snapshotBackgroundSubagentGate() {
|
|
3203
|
+
return lastGate ? { ...lastGate } : void 0;
|
|
3204
|
+
}
|
|
3205
|
+
function snapshotBackgroundTasks() {
|
|
3206
|
+
const keys = /* @__PURE__ */ new Set([...collected.keys(), ...cancelled.keys()]);
|
|
3207
|
+
return [...keys].map((sessionKey2) => ({
|
|
3208
|
+
sessionKey: sessionKey2,
|
|
3209
|
+
collected: [...collected.get(sessionKey2) ?? []],
|
|
3210
|
+
cancelled: [...cancelled.get(sessionKey2) ?? []]
|
|
3211
|
+
}));
|
|
3179
3212
|
}
|
|
3180
3213
|
function readTaskId(input) {
|
|
3181
3214
|
const raw = input.task_id ?? input.taskId ?? input.sessionID;
|
|
@@ -3183,14 +3216,20 @@ function readTaskId(input) {
|
|
|
3183
3216
|
const id = raw.trim();
|
|
3184
3217
|
return id.length > 0 ? id : null;
|
|
3185
3218
|
}
|
|
3186
|
-
var MISSING_TASK_ID = 'task_id is required: pass the id from the `<task id="...">` envelope the background dispatch
|
|
3219
|
+
var MISSING_TASK_ID = 'task_id is required: pass the background subagent\'s own opencode session id, from the `<task id="...">` envelope on opencode 1.x or the `sessionID` the background dispatch reported on opencode 2.';
|
|
3187
3220
|
async function guardParent(taskId, callerSessionId) {
|
|
3188
3221
|
if (!callerSessionId || callerSessionId === "default") {
|
|
3189
3222
|
return `Cannot check that task_id ${taskId} belongs to this conversation (this turn has no opencode session id), so it is not inspected or cancelled.`;
|
|
3190
3223
|
}
|
|
3191
3224
|
const parent = await fetchSessionParentId(taskId);
|
|
3192
3225
|
if (parent === callerSessionId) return null;
|
|
3193
|
-
return `task_id ${taskId} is not a subagent of this conversation, so it cannot be inspected or cancelled from here. Use the id from a
|
|
3226
|
+
return `task_id ${taskId} is not a subagent of this conversation, so it cannot be inspected or cancelled from here. Use the session id from a background dispatch this conversation itself made.`;
|
|
3227
|
+
}
|
|
3228
|
+
function isBackgroundTaskRunning(runState, replies) {
|
|
3229
|
+
if (runState === "busy") return true;
|
|
3230
|
+
if (runState === "idle") return false;
|
|
3231
|
+
const reply = lastAssistantReply(replies);
|
|
3232
|
+
return reply !== void 0 && !reply.completed && reply.error === void 0;
|
|
3194
3233
|
}
|
|
3195
3234
|
function lastAssistantReply(replies) {
|
|
3196
3235
|
for (let index = replies.length - 1; index >= 0; index--) {
|
|
@@ -3212,7 +3251,7 @@ async function collectBackgroundTask(input, options) {
|
|
|
3212
3251
|
};
|
|
3213
3252
|
}
|
|
3214
3253
|
const runState = await fetchSessionRunState(taskId);
|
|
3215
|
-
if (runState
|
|
3254
|
+
if (isBackgroundTaskRunning(runState, replies)) {
|
|
3216
3255
|
return {
|
|
3217
3256
|
kind: "text",
|
|
3218
3257
|
text: `<task id="${taskId}" state="running">
|
|
@@ -3267,7 +3306,10 @@ async function cancelBackgroundTask(input, options) {
|
|
|
3267
3306
|
if (!taskId) return { kind: "error", message: MISSING_TASK_ID };
|
|
3268
3307
|
const refused = await guardParent(taskId, options.callerSessionId);
|
|
3269
3308
|
if (refused) return { kind: "error", message: refused };
|
|
3270
|
-
const
|
|
3309
|
+
const running = isBackgroundTaskRunning(
|
|
3310
|
+
await fetchSessionRunState(taskId),
|
|
3311
|
+
await fetchSessionReplies(taskId) ?? []
|
|
3312
|
+
);
|
|
3271
3313
|
const aborted = await abortSession(taskId);
|
|
3272
3314
|
if (!aborted) {
|
|
3273
3315
|
return {
|
|
@@ -3276,15 +3318,16 @@ async function cancelBackgroundTask(input, options) {
|
|
|
3276
3318
|
};
|
|
3277
3319
|
}
|
|
3278
3320
|
collected.get(options.sessionKey)?.delete(taskId);
|
|
3321
|
+
mark(cancelled, options.sessionKey, taskId);
|
|
3279
3322
|
log.info("cancelled background subagent", {
|
|
3280
3323
|
sessionKey: options.sessionKey,
|
|
3281
3324
|
taskId,
|
|
3282
|
-
wasRunning:
|
|
3325
|
+
wasRunning: running
|
|
3283
3326
|
});
|
|
3284
3327
|
return {
|
|
3285
3328
|
kind: "text",
|
|
3286
3329
|
text: `<task id="${taskId}" state="cancelled">
|
|
3287
|
-
` + (
|
|
3330
|
+
` + (running ? "Stopped. No completion notification will arrive for it." : "It was not running when the cancel was sent, so nothing was interrupted. No completion notification will arrive for it.") + "\n</task>"
|
|
3288
3331
|
};
|
|
3289
3332
|
}
|
|
3290
3333
|
|
|
@@ -4677,24 +4720,24 @@ function globalConfigDir() {
|
|
|
4677
4720
|
const xdg = process.env.XDG_CONFIG_HOME ?? path4.join(os2.homedir(), ".config");
|
|
4678
4721
|
return path4.join(xdg, "opencode");
|
|
4679
4722
|
}
|
|
4680
|
-
function loadGlobalConfig() {
|
|
4723
|
+
function loadGlobalConfig(hostApi = "v1") {
|
|
4681
4724
|
const dir = globalConfigDir();
|
|
4682
4725
|
let merged = {};
|
|
4683
4726
|
for (const name of FILE_NAMES.slice().reverse()) {
|
|
4684
4727
|
const file = path4.join(dir, name);
|
|
4685
4728
|
if (!fileExists(file)) continue;
|
|
4686
4729
|
const parsed = readAndParse(file);
|
|
4687
|
-
if (parsed) merged =
|
|
4730
|
+
if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
|
|
4688
4731
|
}
|
|
4689
4732
|
return merged;
|
|
4690
4733
|
}
|
|
4691
|
-
function loadProjectFilesInDir(dir) {
|
|
4734
|
+
function loadProjectFilesInDir(dir, hostApi = "v1") {
|
|
4692
4735
|
let merged = {};
|
|
4693
4736
|
for (const name of PROJECT_FILE_NAMES) {
|
|
4694
4737
|
const file = path4.join(dir, name);
|
|
4695
4738
|
if (!fileExists(file)) continue;
|
|
4696
4739
|
const parsed = readAndParse(file);
|
|
4697
|
-
if (parsed) merged =
|
|
4740
|
+
if (parsed) merged = mergeConfigLayer(merged, parsed, hostApi);
|
|
4698
4741
|
}
|
|
4699
4742
|
return merged;
|
|
4700
4743
|
}
|
|
@@ -4781,8 +4824,29 @@ function translateServer(name, spec) {
|
|
|
4781
4824
|
}
|
|
4782
4825
|
function extractMcpBlock(config) {
|
|
4783
4826
|
const mcp = config.mcp;
|
|
4784
|
-
if (!
|
|
4785
|
-
return
|
|
4827
|
+
if (!isPlainObject(mcp)) return {};
|
|
4828
|
+
if ("servers" in mcp && !isPlainObject(mcp.servers)) return {};
|
|
4829
|
+
const native = isPlainObject(mcp.servers) && typeof mcp.servers.type !== "string" && typeof mcp.servers.enabled !== "boolean" && typeof mcp.servers.disabled !== "boolean";
|
|
4830
|
+
const block = native ? mcp.servers : mcp;
|
|
4831
|
+
return Object.fromEntries(
|
|
4832
|
+
Object.entries(block).filter(([name, spec]) => isPlainObject(spec) && (native || name !== "timeout" || typeof spec.type === "string")).map(([name, spec]) => {
|
|
4833
|
+
const server2 = spec;
|
|
4834
|
+
return [name, {
|
|
4835
|
+
...server2,
|
|
4836
|
+
...typeof server2.disabled === "boolean" ? { enabled: !server2.disabled } : {}
|
|
4837
|
+
}];
|
|
4838
|
+
})
|
|
4839
|
+
);
|
|
4840
|
+
}
|
|
4841
|
+
function mergeConfigLayer(target, source, hostApi) {
|
|
4842
|
+
const merged = deepMerge(target, source);
|
|
4843
|
+
if (hostApi === "v2" && isPlainObject(source.mcp)) {
|
|
4844
|
+
merged.mcp = {
|
|
4845
|
+
...isPlainObject(merged.mcp) ? merged.mcp : {},
|
|
4846
|
+
servers: { ...extractMcpBlock(target), ...extractMcpBlock(source) }
|
|
4847
|
+
};
|
|
4848
|
+
}
|
|
4849
|
+
return merged;
|
|
4786
4850
|
}
|
|
4787
4851
|
function mergeMcp(target, source) {
|
|
4788
4852
|
const out = { ...target };
|
|
@@ -4800,12 +4864,12 @@ function mergeMcp(target, source) {
|
|
|
4800
4864
|
}
|
|
4801
4865
|
return out;
|
|
4802
4866
|
}
|
|
4803
|
-
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
4867
|
+
function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, hostApi = "v1") {
|
|
4804
4868
|
const {
|
|
4805
4869
|
servers: merged,
|
|
4806
4870
|
enabledServerNames: allEnabledServerNames,
|
|
4807
4871
|
hash
|
|
4808
|
-
} = mergeOpencodeMcp(cwd, runtimeStatus);
|
|
4872
|
+
} = mergeOpencodeMcp(cwd, runtimeStatus, hostApi);
|
|
4809
4873
|
const servers = {};
|
|
4810
4874
|
const bridgedServerNames = [];
|
|
4811
4875
|
for (const [name, spec] of Object.entries(merged)) {
|
|
@@ -4825,10 +4889,10 @@ function bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers) {
|
|
|
4825
4889
|
excludeServers
|
|
4826
4890
|
});
|
|
4827
4891
|
}
|
|
4828
|
-
function opencodeConfigLayers(cwd) {
|
|
4829
|
-
const worktree = detectWorktree(cwd);
|
|
4892
|
+
function opencodeConfigLayers(cwd, hostApi = "v1") {
|
|
4893
|
+
const worktree = hostApi === "v2" ? void 0 : detectWorktree(cwd);
|
|
4830
4894
|
const layers = [];
|
|
4831
|
-
layers.push(loadGlobalConfig());
|
|
4895
|
+
layers.push(loadGlobalConfig(hostApi));
|
|
4832
4896
|
const explicitConfig = process.env.OPENCODE_CONFIG;
|
|
4833
4897
|
if (explicitConfig && fileExists(explicitConfig)) {
|
|
4834
4898
|
const parsed = readAndParse(explicitConfig);
|
|
@@ -4850,22 +4914,30 @@ function opencodeConfigLayers(cwd) {
|
|
|
4850
4914
|
}
|
|
4851
4915
|
}
|
|
4852
4916
|
for (const dir of projectDirs.slice().reverse()) {
|
|
4853
|
-
layers.push(loadProjectFilesInDir(dir));
|
|
4917
|
+
layers.push(loadProjectFilesInDir(dir, hostApi));
|
|
4854
4918
|
}
|
|
4855
|
-
|
|
4856
|
-
|
|
4919
|
+
if (hostApi === "v2") {
|
|
4920
|
+
const dirs = walkUp({ start: cwd, targets: [".opencode"], predicate: dirExists }).reverse();
|
|
4921
|
+
const extra = process.env.OPENCODE_CONFIG_DIR;
|
|
4922
|
+
if (extra && dirExists(extra)) layers.push(loadProjectFilesInDir(extra, hostApi));
|
|
4923
|
+
for (const dir of dirs) layers.push(loadProjectFilesInDir(dir, hostApi));
|
|
4924
|
+
} else {
|
|
4925
|
+
for (const dir of dotOpencodeDirs(cwd, worktree)) {
|
|
4926
|
+
layers.push(loadProjectFilesInDir(dir));
|
|
4927
|
+
}
|
|
4857
4928
|
}
|
|
4858
4929
|
return layers;
|
|
4859
4930
|
}
|
|
4860
|
-
function loadMergedOpencodeConfig(cwd) {
|
|
4931
|
+
function loadMergedOpencodeConfig(cwd, hostApi = "v1") {
|
|
4861
4932
|
let merged = {};
|
|
4862
|
-
for (const layer of opencodeConfigLayers(cwd)) merged =
|
|
4933
|
+
for (const layer of opencodeConfigLayers(cwd, hostApi)) merged = mergeConfigLayer(merged, layer, hostApi);
|
|
4863
4934
|
return merged;
|
|
4864
4935
|
}
|
|
4865
|
-
function mergeOpencodeMcp(cwd, runtimeStatus) {
|
|
4936
|
+
function mergeOpencodeMcp(cwd, runtimeStatus, hostApi = "v1") {
|
|
4866
4937
|
let merged = {};
|
|
4867
|
-
for (const layer of opencodeConfigLayers(cwd)) {
|
|
4868
|
-
|
|
4938
|
+
for (const layer of opencodeConfigLayers(cwd, hostApi)) {
|
|
4939
|
+
const servers = extractMcpBlock(layer);
|
|
4940
|
+
merged = hostApi === "v2" ? { ...merged, ...servers } : mergeMcp(merged, servers);
|
|
4869
4941
|
}
|
|
4870
4942
|
if (runtimeStatus) {
|
|
4871
4943
|
for (const name of Object.keys(merged)) {
|
|
@@ -5005,7 +5077,8 @@ function collectStartupDiagnostics(providers, opencodeVersion) {
|
|
|
5005
5077
|
const cwd = describeSpawnCwd(firstOption(providers, "cwd"));
|
|
5006
5078
|
let mcpServers = [];
|
|
5007
5079
|
try {
|
|
5008
|
-
|
|
5080
|
+
const hostApi = firstOption(providers, "hostApi") === "v2" || opencodeVersion?.replace(/^v/, "").startsWith("2.") ? "v2" : "v1";
|
|
5081
|
+
mcpServers = mergeOpencodeMcp(cwd.resolved, void 0, hostApi).enabledServerNames;
|
|
5009
5082
|
} catch (err) {
|
|
5010
5083
|
log.debug("startup diagnostics could not read MCP config", {
|
|
5011
5084
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -5219,6 +5292,10 @@ function formatDoctorReport(report) {
|
|
|
5219
5292
|
}
|
|
5220
5293
|
}
|
|
5221
5294
|
lines.push("");
|
|
5295
|
+
lines.push("**Background subagents**");
|
|
5296
|
+
lines.push("");
|
|
5297
|
+
lines.push(...formatBackgroundSubagents(report.backgroundSubagents));
|
|
5298
|
+
lines.push("");
|
|
5222
5299
|
lines.push("**Plan usage**");
|
|
5223
5300
|
lines.push("");
|
|
5224
5301
|
switch (report.planUsage.status) {
|
|
@@ -5251,6 +5328,59 @@ function formatDoctorReport(report) {
|
|
|
5251
5328
|
}
|
|
5252
5329
|
return lines.join("\n");
|
|
5253
5330
|
}
|
|
5331
|
+
function formatBackgroundSubagents(state) {
|
|
5332
|
+
const lines = [];
|
|
5333
|
+
const gate = state.gate;
|
|
5334
|
+
if (!gate) {
|
|
5335
|
+
lines.push(
|
|
5336
|
+
"Not read yet this process. The gate is resolved the first time a turn plans its proxy tools, so send one message and run this again."
|
|
5337
|
+
);
|
|
5338
|
+
} else {
|
|
5339
|
+
lines.push("| Field | Value |");
|
|
5340
|
+
lines.push("|---|---|");
|
|
5341
|
+
lines.push(`| \`background\` offered to Claude | ${gate.supported ? "yes" : "no"} |`);
|
|
5342
|
+
lines.push(
|
|
5343
|
+
`| \`task_status\` / \`task_cancel\` | ${gate.supported ? "registered" : "not registered"} |`
|
|
5344
|
+
);
|
|
5345
|
+
lines.push(`| opencode API | ${gate.hostApi} |`);
|
|
5346
|
+
lines.push(`| decided from | ${describeBackgroundGateSource(gate)} |`);
|
|
5347
|
+
lines.push(`| read | ${formatAge(Math.max(0, Date.now() - gate.at))} ago |`);
|
|
5348
|
+
if (!gate.supported && gate.hostApi === "v1") {
|
|
5349
|
+
lines.push("");
|
|
5350
|
+
lines.push(
|
|
5351
|
+
"opencode 1.x keeps background subagents behind a flag on its OWN process: set `OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true` (or the blanket `OPENCODE_EXPERIMENTAL`) in opencode's environment and restart it. The plugin never reads that variable itself; it reads whether opencode's advertised `task` schema carries a `background` property, which is how opencode publishes the flag."
|
|
5352
|
+
);
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
const active = state.ledgers.filter(
|
|
5356
|
+
(ledger) => ledger.collected.length > 0 || ledger.cancelled.length > 0
|
|
5357
|
+
);
|
|
5358
|
+
lines.push("");
|
|
5359
|
+
if (active.length === 0) {
|
|
5360
|
+
lines.push("No background task has been collected or cancelled by this process.");
|
|
5361
|
+
} else {
|
|
5362
|
+
lines.push("| session | collected | cancelled |");
|
|
5363
|
+
lines.push("|---|---|---|");
|
|
5364
|
+
for (const ledger of active) {
|
|
5365
|
+
lines.push(
|
|
5366
|
+
`| ${describeSessionKey(ledger.sessionKey).session} | ${list(ledger.collected)} | ${list(
|
|
5367
|
+
ledger.cancelled
|
|
5368
|
+
)} |`
|
|
5369
|
+
);
|
|
5370
|
+
}
|
|
5371
|
+
lines.push("");
|
|
5372
|
+
lines.push(
|
|
5373
|
+
"A collected task is one this conversation read back with `task_status`, or was told about by opencode's own completion notification; a result is handed over once."
|
|
5374
|
+
);
|
|
5375
|
+
}
|
|
5376
|
+
return lines;
|
|
5377
|
+
}
|
|
5378
|
+
function describeBackgroundGateSource(gate) {
|
|
5379
|
+
if (gate.hostApi === "v2") {
|
|
5380
|
+
return "opencode 2 offers `background` unconditionally, so the registry is not consulted";
|
|
5381
|
+
}
|
|
5382
|
+
return gate.registryResolved ? "opencode's live `task` schema" : "opencode's live tool registry did not answer, so the answer defaulted to no";
|
|
5383
|
+
}
|
|
5254
5384
|
async function checkProxyAuth(url, fetchImpl = fetch, timeoutMs = 3e3) {
|
|
5255
5385
|
let authority;
|
|
5256
5386
|
try {
|
|
@@ -5317,7 +5447,11 @@ async function gatherDoctorReport(options) {
|
|
|
5317
5447
|
proxyServers,
|
|
5318
5448
|
mcpServerErrors: snapshotMcpServerErrors(),
|
|
5319
5449
|
pluginLoadFailures: snapshotPluginLoadFailures(),
|
|
5320
|
-
planUsage
|
|
5450
|
+
planUsage,
|
|
5451
|
+
backgroundSubagents: {
|
|
5452
|
+
gate: snapshotBackgroundSubagentGate(),
|
|
5453
|
+
ledgers: snapshotBackgroundTasks()
|
|
5454
|
+
}
|
|
5321
5455
|
};
|
|
5322
5456
|
}
|
|
5323
5457
|
async function buildDoctorReport(options) {
|
|
@@ -5332,7 +5466,8 @@ async function buildDoctorReport(options) {
|
|
|
5332
5466
|
proxyServers: report.proxyServers.map((server2) => server2.auth.status),
|
|
5333
5467
|
mcpServerErrors: report.mcpServerErrors.length,
|
|
5334
5468
|
pluginLoadFailures: report.pluginLoadFailures.length,
|
|
5335
|
-
planUsage: report.planUsage.status
|
|
5469
|
+
planUsage: report.planUsage.status,
|
|
5470
|
+
backgroundSubagents: report.backgroundSubagents.gate?.supported ?? "not read"
|
|
5336
5471
|
});
|
|
5337
5472
|
return formatDoctorReport(report);
|
|
5338
5473
|
} catch (error) {
|
|
@@ -6820,7 +6955,13 @@ function resolveClaude(cmd = "claude") {
|
|
|
6820
6955
|
throw new Error(`Could not resolve command on PATH: ${cmd}`);
|
|
6821
6956
|
}
|
|
6822
6957
|
function encodeCwd(cwd) {
|
|
6823
|
-
|
|
6958
|
+
const resolved = path8.resolve(cwd);
|
|
6959
|
+
let real = resolved;
|
|
6960
|
+
try {
|
|
6961
|
+
real = fs6.realpathSync.native(resolved);
|
|
6962
|
+
} catch {
|
|
6963
|
+
}
|
|
6964
|
+
return real.replace(/[^a-zA-Z0-9]/g, "-");
|
|
6824
6965
|
}
|
|
6825
6966
|
function interactiveSpawnEnv(opts) {
|
|
6826
6967
|
return {
|
|
@@ -7341,16 +7482,18 @@ function spawnInteractiveProcess(opts) {
|
|
|
7341
7482
|
if (!startPromise) startPromise = session.start();
|
|
7342
7483
|
return startPromise;
|
|
7343
7484
|
};
|
|
7344
|
-
const emitResult = (
|
|
7485
|
+
const emitResult = (opts2) => {
|
|
7345
7486
|
lineEmitter.emit(
|
|
7346
7487
|
"line",
|
|
7347
7488
|
JSON.stringify({
|
|
7348
7489
|
type: "result",
|
|
7349
|
-
subtype,
|
|
7350
|
-
is_error: isError,
|
|
7351
|
-
|
|
7490
|
+
subtype: opts2.subtype,
|
|
7491
|
+
is_error: opts2.isError,
|
|
7492
|
+
stop_reason: opts2.stopReason,
|
|
7493
|
+
terminal_reason: opts2.terminalReason,
|
|
7494
|
+
result: opts2.result,
|
|
7352
7495
|
session_id: session.sessionId,
|
|
7353
|
-
usage: usage ?? {},
|
|
7496
|
+
usage: opts2.usage ?? {},
|
|
7354
7497
|
total_cost_usd: null,
|
|
7355
7498
|
duration_ms: 0
|
|
7356
7499
|
})
|
|
@@ -7373,20 +7516,24 @@ function spawnInteractiveProcess(opts) {
|
|
|
7373
7516
|
lastCallCacheWriteTokens: lastCallUsage?.cache_creation_input_tokens
|
|
7374
7517
|
});
|
|
7375
7518
|
const timedOut = !stopReason;
|
|
7376
|
-
emitResult(
|
|
7377
|
-
timedOut ? "error_during_execution" :
|
|
7378
|
-
timedOut,
|
|
7379
|
-
timedOut ?
|
|
7519
|
+
emitResult({
|
|
7520
|
+
subtype: timedOut ? "error_during_execution" : "success",
|
|
7521
|
+
isError: timedOut,
|
|
7522
|
+
stopReason: timedOut ? null : stopReason,
|
|
7523
|
+
terminalReason: timedOut ? "error_during_execution" : "completed",
|
|
7524
|
+
result: timedOut ? "Interactive transport: the turn ended without a terminal stop_reason (turn timeout or claude exit). Output above may be incomplete." : void 0,
|
|
7380
7525
|
usage
|
|
7381
|
-
);
|
|
7526
|
+
});
|
|
7382
7527
|
} catch (err) {
|
|
7383
7528
|
const e = err instanceof Error ? err : new Error(String(err));
|
|
7384
7529
|
log.error("interactive turn failed", { error: e.message });
|
|
7385
|
-
emitResult(
|
|
7386
|
-
"error_during_execution",
|
|
7387
|
-
true,
|
|
7388
|
-
|
|
7389
|
-
|
|
7530
|
+
emitResult({
|
|
7531
|
+
subtype: "error_during_execution",
|
|
7532
|
+
isError: true,
|
|
7533
|
+
stopReason: null,
|
|
7534
|
+
terminalReason: "error_during_execution",
|
|
7535
|
+
result: `Interactive transport failed: ${e.message}`
|
|
7536
|
+
});
|
|
7390
7537
|
if (errorHandlers.size > 0) {
|
|
7391
7538
|
for (const h of errorHandlers) h(e);
|
|
7392
7539
|
} else {
|
|
@@ -7503,6 +7650,13 @@ Structured questions in this environment go through exactly one tool: \`mcp__ope
|
|
|
7503
7650
|
- If that tool is not in your visible tool list it is deferred \u2014 load it with ToolSearch (\`select:mcp__opencode_proxy__question\`), then call it by its FULL name.
|
|
7504
7651
|
- Do NOT call bare \`question\` \u2014 that is not a tool. Always use the full \`mcp__opencode_proxy__question\` name when invoking it.
|
|
7505
7652
|
- Claude Code's built-in \`AskUserQuestion\` is disabled in this environment; the proxy is the only way to ask structured questions.`;
|
|
7653
|
+
function codeModeProxyHint(active) {
|
|
7654
|
+
return active ? `## opencode Code Mode
|
|
7655
|
+
|
|
7656
|
+
Instructions referring to the execute tool mean \`mcp__opencode_proxy__execute\` in this Claude Code session. Use its full name; if deferred, load it with ToolSearch (\`select:mcp__opencode_proxy__execute\`). Pass the code argument unchanged, using the provided catalog's exact \`search(...)\` and \`tools[...]\` signatures inside that code. The catalog tools are not separate Claude tools. Calls execute in opencode under this session's permissions.` : `## opencode Code Mode unavailable
|
|
7657
|
+
|
|
7658
|
+
The forwarded opencode catalog may mention execute, but this session does not expose an execute proxy. Do not invent an execute tool or claim a catalog call ran. For MCP servers available directly in Claude Code, discover their actual MCP tools with ToolSearch instead. If no matching tool exists, report that limitation; a shell/CLI call is not a test of MCP.`;
|
|
7659
|
+
}
|
|
7506
7660
|
var CLAUDE_CLI_CONTEXT_NOTE = `## Runtime environment: Claude Code CLI
|
|
7507
7661
|
|
|
7508
7662
|
You are running via the Claude Code CLI (not a direct API call). This affects context management:
|
|
@@ -7988,7 +8142,7 @@ function effectiveMcpConfig(config, cwd, proxyConfigPath, runtimeStatus, exclude
|
|
|
7988
8142
|
let bridgedHash = null;
|
|
7989
8143
|
let allEnabledServerNames = [];
|
|
7990
8144
|
if (config.bridgeOpencodeMcp !== false) {
|
|
7991
|
-
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers);
|
|
8145
|
+
const bridged = bridgeOpencodeMcp(cwd, runtimeStatus, excludeServers, config.hostApi);
|
|
7992
8146
|
if (bridged) {
|
|
7993
8147
|
if (bridged.path) paths.push(bridged.path);
|
|
7994
8148
|
bridgedHash = bridged.hash;
|
|
@@ -8048,7 +8202,7 @@ function resolvedProxyMcpTools(config, allEnabledServerNames, modelTools, taken)
|
|
|
8048
8202
|
});
|
|
8049
8203
|
if (resolution.defs.length === 0) {
|
|
8050
8204
|
log.warn(
|
|
8051
|
-
"proxyOpencodeMcpTools is on but no MCP tool was found in opencode's tool set; those servers stay on the direct bridge this spawn",
|
|
8205
|
+
"proxyOpencodeMcpTools is on but no MCP tool was found in opencode's tool set; those servers stay on the direct bridge this spawn" + (config.hostApi === "v2" && modelTools?.some((tool) => tool.name === "execute") ? ". For V2 Code Mode, explicitly allowlist execute in proxyOpencodeTools and use bridgeOpencodeMcp: false with strictMcpConfig: true" : ""),
|
|
8052
8206
|
{ servers: allEnabledServerNames, modelTools: modelTools?.length ?? 0 }
|
|
8053
8207
|
);
|
|
8054
8208
|
return null;
|
|
@@ -10547,18 +10701,25 @@ var ClaudeCodeLanguageModel = class {
|
|
|
10547
10701
|
)
|
|
10548
10702
|
)
|
|
10549
10703
|
});
|
|
10704
|
+
const hostDialect = self.config.hostApi === "v2" ? "v2" : "v1";
|
|
10550
10705
|
backgroundSubagentsSupported = liveTaskSupportsBackground(
|
|
10551
10706
|
liveToolInfo.taskParameters,
|
|
10552
|
-
|
|
10707
|
+
hostDialect
|
|
10553
10708
|
);
|
|
10554
10709
|
enrichedProxy = applyBackgroundSubagentSupport(
|
|
10555
10710
|
enrichedProxy,
|
|
10556
|
-
backgroundSubagentsSupported
|
|
10711
|
+
backgroundSubagentsSupported,
|
|
10712
|
+
hostDialect
|
|
10557
10713
|
);
|
|
10714
|
+
recordBackgroundSubagentGate({
|
|
10715
|
+
supported: backgroundSubagentsSupported,
|
|
10716
|
+
hostApi: hostDialect,
|
|
10717
|
+
registryResolved: liveToolInfo.resolved
|
|
10718
|
+
});
|
|
10558
10719
|
log.info("background subagent gate", {
|
|
10559
10720
|
supported: backgroundSubagentsSupported,
|
|
10560
10721
|
registryResolved: liveToolInfo.resolved,
|
|
10561
|
-
hostApi:
|
|
10722
|
+
hostApi: hostDialect,
|
|
10562
10723
|
note: backgroundSubagentsSupported ? "task accepts `background`; task_status and task_cancel are registered" : "`background` stripped from the task schema; set OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true in opencode's environment to enable it"
|
|
10563
10724
|
});
|
|
10564
10725
|
}
|
|
@@ -10579,6 +10740,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
10579
10740
|
const opencodeToolDefs = resolveProxyOpencodeToolDefs({
|
|
10580
10741
|
requested: self.config.proxyOpencodeTools,
|
|
10581
10742
|
items: liveToolInfo.items,
|
|
10743
|
+
modelTools: self.config.hostApi === "v2" ? options.tools ?? [] : void 0,
|
|
10744
|
+
allowCodeExecution: self.config.permissionPreset !== "read-only",
|
|
10582
10745
|
taken: new Set(
|
|
10583
10746
|
[...enrichedProxy ?? [], ...proxyMcpTools ?? []].map(
|
|
10584
10747
|
(t) => t.name
|
|
@@ -10624,7 +10787,8 @@ var ClaudeCodeLanguageModel = class {
|
|
|
10624
10787
|
...extractSystemMessages(options.prompt),
|
|
10625
10788
|
...taskProxyEnabled ? [SUBAGENT_DISPATCH_HINT] : [],
|
|
10626
10789
|
...backgroundSubagentsSupported ? [BACKGROUND_SUBAGENT_HINT] : [],
|
|
10627
|
-
...questionProxyActive ? [QUESTION_PROXY_HINT] : []
|
|
10790
|
+
...questionProxyActive ? [QUESTION_PROXY_HINT] : [],
|
|
10791
|
+
...self.config.hostApi === "v2" && options.tools?.some((t) => t.name === "execute") ? [codeModeProxyHint(opencodeToolDefs.some((t) => t.name === "execute"))] : []
|
|
10628
10792
|
],
|
|
10629
10793
|
{
|
|
10630
10794
|
compressEnabled: pluginCompressEnabled,
|
|
@@ -11203,7 +11367,7 @@ import {
|
|
|
11203
11367
|
existsSync as existsSync3,
|
|
11204
11368
|
mkdirSync as mkdirSync4,
|
|
11205
11369
|
readFileSync as readFileSync6,
|
|
11206
|
-
realpathSync as
|
|
11370
|
+
realpathSync as realpathSync3,
|
|
11207
11371
|
rmSync as rmSync3,
|
|
11208
11372
|
writeFileSync as writeFileSync5
|
|
11209
11373
|
} from "fs";
|
|
@@ -11242,7 +11406,7 @@ function userIntendsToUseUnscoped() {
|
|
|
11242
11406
|
function ourLoadedDir() {
|
|
11243
11407
|
try {
|
|
11244
11408
|
const filePath = fileURLToPath3(import.meta.url);
|
|
11245
|
-
return
|
|
11409
|
+
return realpathSync3(resolve5(filePath, "..", ".."));
|
|
11246
11410
|
} catch {
|
|
11247
11411
|
return null;
|
|
11248
11412
|
}
|
|
@@ -11311,7 +11475,7 @@ function cleanupOne(cacheRoot, ourDir) {
|
|
|
11311
11475
|
if (!existsSync3(stalePath)) return;
|
|
11312
11476
|
let realStalePath = stalePath;
|
|
11313
11477
|
try {
|
|
11314
|
-
realStalePath =
|
|
11478
|
+
realStalePath = realpathSync3(stalePath);
|
|
11315
11479
|
} catch {
|
|
11316
11480
|
}
|
|
11317
11481
|
if (ourDir && realStalePath === ourDir) return;
|
|
@@ -11384,6 +11548,30 @@ function toV1Session(info) {
|
|
|
11384
11548
|
const directory = typeof root === "string" ? typeof session.subpath === "string" && session.subpath.length > 0 ? path9.resolve(root, session.subpath) : root : void 0;
|
|
11385
11549
|
return { ...info, directory, parentID: session.parentID };
|
|
11386
11550
|
}
|
|
11551
|
+
function toV1Messages(messages) {
|
|
11552
|
+
if (!Array.isArray(messages)) return [];
|
|
11553
|
+
return messages.flatMap((entry) => {
|
|
11554
|
+
if (!entry || typeof entry !== "object") return [];
|
|
11555
|
+
const message = entry;
|
|
11556
|
+
const parts = Array.isArray(message.content) ? message.content.flatMap((part) => {
|
|
11557
|
+
if (!part || typeof part !== "object") return [];
|
|
11558
|
+
const piece = part;
|
|
11559
|
+
return piece.type === "text" && typeof piece.text === "string" ? [{ type: "text", text: piece.text }] : [];
|
|
11560
|
+
}) : typeof message.text === "string" ? [{ type: "text", text: message.text }] : [];
|
|
11561
|
+
const error = message.error;
|
|
11562
|
+
const info = {
|
|
11563
|
+
role: typeof message.type === "string" ? message.type : "",
|
|
11564
|
+
time: message.time && typeof message.time === "object" ? message.time : {},
|
|
11565
|
+
...error && typeof error === "object" ? {
|
|
11566
|
+
error: {
|
|
11567
|
+
name: typeof error.type === "string" ? error.type : "error",
|
|
11568
|
+
data: { message: typeof error.message === "string" ? error.message : void 0 }
|
|
11569
|
+
}
|
|
11570
|
+
} : {}
|
|
11571
|
+
};
|
|
11572
|
+
return [{ info, parts }];
|
|
11573
|
+
});
|
|
11574
|
+
}
|
|
11387
11575
|
function createV1ClientShim(ctx) {
|
|
11388
11576
|
const client = {};
|
|
11389
11577
|
const mcpList = ctx.mcp?.list;
|
|
@@ -11408,7 +11596,8 @@ function createV1ClientShim(ctx) {
|
|
|
11408
11596
|
client.tool = {
|
|
11409
11597
|
// V1: `{ data: [{ id, description, parameters }] }`. V2 returns the tools
|
|
11410
11598
|
// themselves, with an Effect schema for input rather than JSON Schema, so
|
|
11411
|
-
// `parameters` is empty
|
|
11599
|
+
// `parameters` is empty. V2 proxyOpencodeTools uses the per-turn model
|
|
11600
|
+
// snapshot instead, which also includes synthesized Code Mode execute.
|
|
11412
11601
|
list: async () => {
|
|
11413
11602
|
const tools = await toolList.call(ctx.tool);
|
|
11414
11603
|
const data = (tools ?? []).flatMap((tool) => {
|
|
@@ -11436,13 +11625,27 @@ ${agentList}` : subagent.description;
|
|
|
11436
11625
|
};
|
|
11437
11626
|
}
|
|
11438
11627
|
const sessionGet = ctx.session?.get;
|
|
11628
|
+
const sessionContext = ctx.session?.context;
|
|
11629
|
+
const sessionInterrupt = ctx.session?.interrupt;
|
|
11630
|
+
const session = {};
|
|
11439
11631
|
if (typeof sessionGet === "function") {
|
|
11440
|
-
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11632
|
+
session.get = async (options) => ({
|
|
11633
|
+
data: toV1Session(await sessionGet.call(ctx.session, { sessionID: options.path.id }))
|
|
11634
|
+
});
|
|
11635
|
+
}
|
|
11636
|
+
if (typeof sessionContext === "function") {
|
|
11637
|
+
session.messages = async (options) => ({
|
|
11638
|
+
data: toV1Messages(await sessionContext.call(ctx.session, { sessionID: options.path.id }))
|
|
11639
|
+
});
|
|
11640
|
+
}
|
|
11641
|
+
if (typeof sessionInterrupt === "function") {
|
|
11642
|
+
session.abort = async (options) => {
|
|
11643
|
+
const result = await sessionInterrupt.call(ctx.session, { sessionID: options.path.id });
|
|
11644
|
+
const interrupted = result?.interrupted;
|
|
11645
|
+
return { data: typeof interrupted === "boolean" ? interrupted : true };
|
|
11444
11646
|
};
|
|
11445
11647
|
}
|
|
11648
|
+
if (Object.keys(session).length > 0) client.session = session;
|
|
11446
11649
|
return client;
|
|
11447
11650
|
}
|
|
11448
11651
|
|
|
@@ -11575,9 +11778,11 @@ function plainObject(value) {
|
|
|
11575
11778
|
}
|
|
11576
11779
|
function configuredSeedSettings(config, pluginOptions) {
|
|
11577
11780
|
const provider = plainObject(plainObject(config.provider)?.[BASE_PROVIDER_ID]);
|
|
11781
|
+
const nativeProvider = plainObject(plainObject(config.providers)?.[BASE_PROVIDER_ID]);
|
|
11578
11782
|
return {
|
|
11579
11783
|
...plainObject(provider?.options) ?? {},
|
|
11580
11784
|
...plainObject(provider?.settings) ?? {},
|
|
11785
|
+
...plainObject(nativeProvider?.settings) ?? {},
|
|
11581
11786
|
...pluginOptions ?? {}
|
|
11582
11787
|
};
|
|
11583
11788
|
}
|
|
@@ -12225,7 +12430,7 @@ var index_default = {
|
|
|
12225
12430
|
setup: createV2Setup({
|
|
12226
12431
|
createProvider: createClaudeCode,
|
|
12227
12432
|
defaultProxyTools: DEFAULT_PROXY_TOOL_NAMES,
|
|
12228
|
-
loadConfig: loadMergedOpencodeConfig,
|
|
12433
|
+
loadConfig: (directory) => loadMergedOpencodeConfig(directory, "v2"),
|
|
12229
12434
|
buildAgentRegistry: (config) => buildAgentRegistry(config)
|
|
12230
12435
|
})
|
|
12231
12436
|
};
|