@khalilgharbaoui/opencode-claude-code-plugin 0.53.2 → 0.53.4
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/dist/index.d.ts +1 -1
- package/dist/index.js +229 -42
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +13 -2
package/dist/index.d.ts
CHANGED
|
@@ -1364,7 +1364,7 @@ declare class ClaudeCodeLanguageModel implements LanguageModelV3 {
|
|
|
1364
1364
|
private effectiveMcpConfig;
|
|
1365
1365
|
/** Resolve ProxyToolDef[] for the configured proxyTools names. */
|
|
1366
1366
|
private resolvedProxyTools;
|
|
1367
|
-
/** Resolve ProxyToolDef[] for opencode's MCP-backed tools. */
|
|
1367
|
+
/** Resolve ProxyToolDef[] for opencode's MCP-backed tools, and route the rest. */
|
|
1368
1368
|
private resolvedProxyMcpTools;
|
|
1369
1369
|
/** One `client.tool.list()` fetch, shaped for this turn's gates. */
|
|
1370
1370
|
private fetchLiveToolInfo;
|
package/dist/index.js
CHANGED
|
@@ -3860,8 +3860,8 @@ function buildProxyTimeoutError(toolName, ms) {
|
|
|
3860
3860
|
return new Error(base);
|
|
3861
3861
|
}
|
|
3862
3862
|
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).";
|
|
3863
|
-
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.';
|
|
3864
|
-
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.';
|
|
3863
|
+
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. The dispatch result may end with how many background subagents are running now: that is for information, never a reason to poll or wait.';
|
|
3864
|
+
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. The dispatch result may end with how many background subagents are running now: that is for information, never a reason to poll or wait.';
|
|
3865
3865
|
var TASK_BATCH_TOOL_NAME = "task_batch";
|
|
3866
3866
|
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).";
|
|
3867
3867
|
var TASK_INPUT_REQUIRED = ["description", "prompt", "subagent_type"];
|
|
@@ -4679,7 +4679,11 @@ function resolveProxyOpencodeToolDefs(options) {
|
|
|
4679
4679
|
return out;
|
|
4680
4680
|
}
|
|
4681
4681
|
function resolveMcpProxyToolDefs(options) {
|
|
4682
|
-
const empty = {
|
|
4682
|
+
const empty = {
|
|
4683
|
+
defs: [],
|
|
4684
|
+
coveredServers: /* @__PURE__ */ new Set(),
|
|
4685
|
+
matchedServers: /* @__PURE__ */ new Set()
|
|
4686
|
+
};
|
|
4683
4687
|
const serverNames = options.serverNames ?? [];
|
|
4684
4688
|
if (serverNames.length === 0) return empty;
|
|
4685
4689
|
const tools = options.tools;
|
|
@@ -4688,6 +4692,7 @@ function resolveMcpProxyToolDefs(options) {
|
|
|
4688
4692
|
const taken = options.taken ?? /* @__PURE__ */ new Set();
|
|
4689
4693
|
const defs = [];
|
|
4690
4694
|
const coveredServers = /* @__PURE__ */ new Set();
|
|
4695
|
+
const matchedServers = /* @__PURE__ */ new Set();
|
|
4691
4696
|
const seen = /* @__PURE__ */ new Set();
|
|
4692
4697
|
const collided = [];
|
|
4693
4698
|
for (const tool of tools) {
|
|
@@ -4699,6 +4704,7 @@ function resolveMcpProxyToolDefs(options) {
|
|
|
4699
4704
|
(server2) => name === server2 || name.startsWith(`${server2}_`)
|
|
4700
4705
|
);
|
|
4701
4706
|
if (!matchedServer) continue;
|
|
4707
|
+
matchedServers.add(matchedServer);
|
|
4702
4708
|
if (seen.has(name)) continue;
|
|
4703
4709
|
if (taken.has(name)) {
|
|
4704
4710
|
collided.push(name);
|
|
@@ -4718,7 +4724,7 @@ function resolveMcpProxyToolDefs(options) {
|
|
|
4718
4724
|
{ collided }
|
|
4719
4725
|
);
|
|
4720
4726
|
}
|
|
4721
|
-
return { defs, coveredServers };
|
|
4727
|
+
return { defs, coveredServers, matchedServers };
|
|
4722
4728
|
}
|
|
4723
4729
|
function readBody(req) {
|
|
4724
4730
|
return new Promise((resolve6, reject) => {
|
|
@@ -5707,7 +5713,7 @@ async function fetchSessionRunState(sessionID, directory) {
|
|
|
5707
5713
|
async function settleSessionRunState(sessionID, options = {}) {
|
|
5708
5714
|
const pollMs = options.pollMs ?? 150;
|
|
5709
5715
|
const windowMs = options.windowMs ?? 1500;
|
|
5710
|
-
const
|
|
5716
|
+
const started2 = Date.now();
|
|
5711
5717
|
let last = "unknown";
|
|
5712
5718
|
let directory = options.directory;
|
|
5713
5719
|
for (; ; ) {
|
|
@@ -5715,7 +5721,7 @@ async function settleSessionRunState(sessionID, options = {}) {
|
|
|
5715
5721
|
const state = await fetchSessionRunState(sessionID, directory ?? "");
|
|
5716
5722
|
if (state === "busy") return "busy";
|
|
5717
5723
|
if (state !== "unknown") last = state;
|
|
5718
|
-
if (Date.now() -
|
|
5724
|
+
if (Date.now() - started2 >= windowMs) return last;
|
|
5719
5725
|
await new Promise((resolve6) => setTimeout(resolve6, pollMs));
|
|
5720
5726
|
}
|
|
5721
5727
|
}
|
|
@@ -5765,9 +5771,9 @@ async function waitForPendingMcpServers(first, budgetMs, signal) {
|
|
|
5765
5771
|
let current = first;
|
|
5766
5772
|
const waitedFor = pendingMcpServers(current);
|
|
5767
5773
|
if (waitedFor.length === 0) return current;
|
|
5768
|
-
const
|
|
5774
|
+
const started2 = Date.now();
|
|
5769
5775
|
for (; ; ) {
|
|
5770
|
-
if (Date.now() -
|
|
5776
|
+
if (Date.now() - started2 >= budgetMs) break;
|
|
5771
5777
|
if (signal?.aborted) break;
|
|
5772
5778
|
await new Promise((resolve6) => setTimeout(resolve6, MCP_CONNECT_POLL_MS));
|
|
5773
5779
|
if (signal?.aborted) break;
|
|
@@ -5781,7 +5787,7 @@ async function waitForPendingMcpServers(first, budgetMs, signal) {
|
|
|
5781
5787
|
waitedFor,
|
|
5782
5788
|
stillPending,
|
|
5783
5789
|
budgetMs,
|
|
5784
|
-
elapsedMs: Date.now() -
|
|
5790
|
+
elapsedMs: Date.now() - started2,
|
|
5785
5791
|
aborted: signal?.aborted === true
|
|
5786
5792
|
});
|
|
5787
5793
|
return current;
|
|
@@ -5909,6 +5915,123 @@ function clearBackgroundTasks(sessionKey2) {
|
|
|
5909
5915
|
collected.delete(sessionKey2);
|
|
5910
5916
|
cancelled.delete(sessionKey2);
|
|
5911
5917
|
}
|
|
5918
|
+
var MAX_STARTED = 128;
|
|
5919
|
+
var started = /* @__PURE__ */ new Map();
|
|
5920
|
+
function backgroundTaskIdsIn(text) {
|
|
5921
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5922
|
+
for (const match of text.matchAll(/<task id="([^"\s]+)" state="running">/g)) {
|
|
5923
|
+
ids.add(match[1]);
|
|
5924
|
+
}
|
|
5925
|
+
for (const match of text.matchAll(/in the background \(sessionID: ([^)\s]+)\)/g)) {
|
|
5926
|
+
ids.add(match[1]);
|
|
5927
|
+
}
|
|
5928
|
+
return [...ids];
|
|
5929
|
+
}
|
|
5930
|
+
function noteBackgroundDispatchResult(sessionKey2, result, now = Date.now()) {
|
|
5931
|
+
if (result.kind !== "text" || result.isError === true) return;
|
|
5932
|
+
for (const taskId of backgroundTaskIdsIn(result.text)) {
|
|
5933
|
+
started.delete(taskId);
|
|
5934
|
+
started.set(taskId, { sessionKey: sessionKey2, at: now });
|
|
5935
|
+
while (started.size > MAX_STARTED) {
|
|
5936
|
+
const oldest = started.keys().next();
|
|
5937
|
+
if (oldest.done) break;
|
|
5938
|
+
started.delete(oldest.value);
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
}
|
|
5942
|
+
async function countRunningBackgroundTasks() {
|
|
5943
|
+
const entries = [...started.entries()];
|
|
5944
|
+
const cancelledIds = cancelledTaskIds();
|
|
5945
|
+
const states = await Promise.all(
|
|
5946
|
+
entries.map(([taskId]) => backgroundTaskState(taskId, cancelledIds))
|
|
5947
|
+
);
|
|
5948
|
+
return {
|
|
5949
|
+
running: states.filter((state) => state === "running").length,
|
|
5950
|
+
started: entries.length,
|
|
5951
|
+
unreadable: states.filter((state) => state === "unreadable").length
|
|
5952
|
+
};
|
|
5953
|
+
}
|
|
5954
|
+
function cancelledTaskIds() {
|
|
5955
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5956
|
+
for (const set of cancelled.values()) for (const id of set) ids.add(id);
|
|
5957
|
+
return ids;
|
|
5958
|
+
}
|
|
5959
|
+
async function backgroundTaskState(taskId, cancelledIds) {
|
|
5960
|
+
if (cancelledIds.has(taskId)) return "stopped";
|
|
5961
|
+
try {
|
|
5962
|
+
const runState = await fetchSessionRunState(taskId);
|
|
5963
|
+
if (runState !== "unknown") {
|
|
5964
|
+
return isBackgroundTaskRunning(runState, []) ? "running" : "stopped";
|
|
5965
|
+
}
|
|
5966
|
+
const replies = await fetchSessionReplies(taskId);
|
|
5967
|
+
if (replies === void 0) return "unreadable";
|
|
5968
|
+
return isBackgroundTaskRunning(runState, replies) ? "running" : "stopped";
|
|
5969
|
+
} catch {
|
|
5970
|
+
return "unreadable";
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
var BACKGROUND_COUNT_BUDGET_MS = 500;
|
|
5974
|
+
var BACKGROUND_COUNT_CONCURRENCY = 4;
|
|
5975
|
+
async function countOtherRunningWithin(exclude, budgetMs) {
|
|
5976
|
+
const taskIds = [...started.keys()].filter((taskId) => !exclude.has(taskId));
|
|
5977
|
+
if (taskIds.length === 0) return 0;
|
|
5978
|
+
const cancelledIds = cancelledTaskIds();
|
|
5979
|
+
let overBudget = false;
|
|
5980
|
+
let unreadable = false;
|
|
5981
|
+
let next = 0;
|
|
5982
|
+
let running = 0;
|
|
5983
|
+
const worker = async () => {
|
|
5984
|
+
while (!overBudget && !unreadable && next < taskIds.length) {
|
|
5985
|
+
const taskId = taskIds[next++];
|
|
5986
|
+
const state = await backgroundTaskState(taskId, cancelledIds);
|
|
5987
|
+
if (state === "running") running++;
|
|
5988
|
+
if (state === "unreadable") unreadable = true;
|
|
5989
|
+
}
|
|
5990
|
+
};
|
|
5991
|
+
const workers = Array.from(
|
|
5992
|
+
{ length: Math.min(BACKGROUND_COUNT_CONCURRENCY, taskIds.length) },
|
|
5993
|
+
worker
|
|
5994
|
+
);
|
|
5995
|
+
let timer;
|
|
5996
|
+
const deadline = new Promise((resolve6) => {
|
|
5997
|
+
timer = setTimeout(() => resolve6("over"), budgetMs);
|
|
5998
|
+
timer.unref?.();
|
|
5999
|
+
});
|
|
6000
|
+
try {
|
|
6001
|
+
const outcome = await Promise.race([Promise.all(workers).then(() => "done"), deadline]);
|
|
6002
|
+
if (outcome === "over") {
|
|
6003
|
+
overBudget = true;
|
|
6004
|
+
return void 0;
|
|
6005
|
+
}
|
|
6006
|
+
return unreadable ? void 0 : running;
|
|
6007
|
+
} catch {
|
|
6008
|
+
overBudget = true;
|
|
6009
|
+
return void 0;
|
|
6010
|
+
} finally {
|
|
6011
|
+
clearTimeout(timer);
|
|
6012
|
+
}
|
|
6013
|
+
}
|
|
6014
|
+
async function withBackgroundRunningCount(result, budgetMs = BACKGROUND_COUNT_BUDGET_MS) {
|
|
6015
|
+
if (result.kind !== "text" || result.isError === true) return result;
|
|
6016
|
+
const dispatched = backgroundTaskIdsIn(result.text);
|
|
6017
|
+
if (dispatched.length === 0) return result;
|
|
6018
|
+
const others = await countOtherRunningWithin(new Set(dispatched), budgetMs);
|
|
6019
|
+
if (others === void 0) {
|
|
6020
|
+
log.info("background running count omitted: a lookup was slow or unreadable", {
|
|
6021
|
+
budgetMs,
|
|
6022
|
+
dispatched: dispatched.length
|
|
6023
|
+
});
|
|
6024
|
+
return result;
|
|
6025
|
+
}
|
|
6026
|
+
return { ...result, text: result.text + formatBackgroundRunningLine(others, dispatched.length) };
|
|
6027
|
+
}
|
|
6028
|
+
function formatBackgroundRunningLine(others, dispatched) {
|
|
6029
|
+
const total = others + dispatched;
|
|
6030
|
+
const including = dispatched === 1 ? "this one" : `the ${dispatched} just started`;
|
|
6031
|
+
return `
|
|
6032
|
+
|
|
6033
|
+
Background subagents running now: ${total} (including ${including}).`;
|
|
6034
|
+
}
|
|
5912
6035
|
var lastGate;
|
|
5913
6036
|
function recordBackgroundSubagentGate(gate, now = Date.now()) {
|
|
5914
6037
|
lastGate = { ...gate, at: now };
|
|
@@ -7635,21 +7758,21 @@ async function sessionStatus(client, sessionID) {
|
|
|
7635
7758
|
async function waitForSessionIdle(client, sessionID, options = {}) {
|
|
7636
7759
|
const pollMs = options.pollMs ?? IDLE_POLL_MS;
|
|
7637
7760
|
const timeoutMs = options.timeoutMs ?? IDLE_WAIT_MAX_MS;
|
|
7638
|
-
const
|
|
7761
|
+
const started2 = Date.now();
|
|
7639
7762
|
for (; ; ) {
|
|
7640
7763
|
if (options.stop?.()) return true;
|
|
7641
7764
|
if (await sessionStatus(client, sessionID) !== "busy") return true;
|
|
7642
|
-
if (Date.now() -
|
|
7765
|
+
if (Date.now() - started2 >= timeoutMs) return false;
|
|
7643
7766
|
await new Promise((resolve6) => setTimeout(resolve6, pollMs));
|
|
7644
7767
|
}
|
|
7645
7768
|
}
|
|
7646
7769
|
async function deliverAsideInline(client, sessionID, text, options = {}) {
|
|
7647
7770
|
const pollMs = options.inlinePollMs ?? INLINE_POLL_MS;
|
|
7648
7771
|
const timeoutMs = options.inlineWaitMs ?? INLINE_WAIT_MAX_MS;
|
|
7649
|
-
const
|
|
7772
|
+
const started2 = Date.now();
|
|
7650
7773
|
for (; ; ) {
|
|
7651
7774
|
if (emitAsideInline(sessionID, text)) return true;
|
|
7652
|
-
if (Date.now() -
|
|
7775
|
+
if (Date.now() - started2 >= timeoutMs) return false;
|
|
7653
7776
|
if (await sessionStatus(client, sessionID) !== "busy") return false;
|
|
7654
7777
|
await new Promise((resolve6) => setTimeout(resolve6, pollMs));
|
|
7655
7778
|
}
|
|
@@ -7658,12 +7781,12 @@ async function waitForAsideProcess(client, sessionID, options = {}) {
|
|
|
7658
7781
|
const pollMs = options.pollMs ?? IDLE_POLL_MS;
|
|
7659
7782
|
const settleMs = options.settleMs ?? BUSY_SETTLE_MS;
|
|
7660
7783
|
const spawnWaitMs = options.spawnWaitMs ?? SPAWN_WAIT_MAX_MS;
|
|
7661
|
-
const
|
|
7784
|
+
const started2 = Date.now();
|
|
7662
7785
|
for (; ; ) {
|
|
7663
7786
|
const active = findActiveProcessBySessionId(sessionID);
|
|
7664
7787
|
if (active) return active;
|
|
7665
7788
|
const busy = await sessionStatus(client, sessionID) === "busy";
|
|
7666
|
-
const waitedMs = Date.now() -
|
|
7789
|
+
const waitedMs = Date.now() - started2;
|
|
7667
7790
|
if (!busy && waitedMs >= settleMs) {
|
|
7668
7791
|
log.info("btw: no live claude process for session, leaving it to the turn", { sessionID, waitedMs });
|
|
7669
7792
|
return void 0;
|
|
@@ -7678,12 +7801,12 @@ async function waitForAsideProcess(client, sessionID, options = {}) {
|
|
|
7678
7801
|
async function settleSessionBusy(client, sessionID, active, options = {}) {
|
|
7679
7802
|
const pollMs = options.pollMs ?? IDLE_POLL_MS;
|
|
7680
7803
|
const settleMs = options.settleMs ?? BUSY_SETTLE_MS;
|
|
7681
|
-
const
|
|
7804
|
+
const started2 = Date.now();
|
|
7682
7805
|
for (; ; ) {
|
|
7683
7806
|
const status = await sessionStatus(client, sessionID);
|
|
7684
7807
|
if (status === "busy") return true;
|
|
7685
7808
|
if (status === "unknown") return isProcessBusy(active);
|
|
7686
|
-
if (Date.now() -
|
|
7809
|
+
if (Date.now() - started2 >= settleMs) return false;
|
|
7687
7810
|
await new Promise((resolve6) => setTimeout(resolve6, pollMs));
|
|
7688
7811
|
}
|
|
7689
7812
|
}
|
|
@@ -7787,7 +7910,7 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
7787
7910
|
);
|
|
7788
7911
|
}
|
|
7789
7912
|
if (!busy) return;
|
|
7790
|
-
const
|
|
7913
|
+
const started2 = Date.now();
|
|
7791
7914
|
const outcome = await Promise.race([
|
|
7792
7915
|
inlineDelivered,
|
|
7793
7916
|
waitForSessionIdle(client, input.sessionID, { ...options, stop: () => inlineDone }).then(
|
|
@@ -7797,14 +7920,14 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
7797
7920
|
if (outcome === "inline") {
|
|
7798
7921
|
log.info("btw: answered inside the running turn, dropping the /btw message", {
|
|
7799
7922
|
sessionID: input.sessionID,
|
|
7800
|
-
waitedMs: Date.now() -
|
|
7923
|
+
waitedMs: Date.now() - started2
|
|
7801
7924
|
});
|
|
7802
7925
|
throw new BtwHandledError(BTW_INLINE_HANDLED_MESSAGE);
|
|
7803
7926
|
}
|
|
7804
7927
|
log.info("btw: turn over, releasing the /btw message", {
|
|
7805
7928
|
sessionID: input.sessionID,
|
|
7806
7929
|
idle: outcome === "idle",
|
|
7807
|
-
waitedMs: Date.now() -
|
|
7930
|
+
waitedMs: Date.now() - started2
|
|
7808
7931
|
});
|
|
7809
7932
|
if (outcome === "timeout") {
|
|
7810
7933
|
showToast(client, { title: "btw", message: BTW_TURN_TOO_LONG_MESSAGE, variant: "warning", duration: 8e3 });
|
|
@@ -7933,6 +8056,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
|
|
|
7933
8056
|
"proxy-mcp interceptor failed",
|
|
7934
8057
|
"proxy-mcp rejected a request",
|
|
7935
8058
|
"proxy-mcp tool call timed out",
|
|
8059
|
+
"proxyOpencodeMcpTools cannot route V2 Code Mode's MCP tools, which reach the model through execute; for Code Mode, explicitly allowlist execute in proxyOpencodeTools and use bridgeOpencodeMcp: false with strictMcpConfig: true",
|
|
7936
8060
|
"proxyOpencodeTools entry dropped: a proxy tool already holds that name, and it keeps it",
|
|
7937
8061
|
"proxyOpencodeTools is set but opencode's tool registry did not answer; forwarding nothing this spawn",
|
|
7938
8062
|
"refusing the pid-named scratch directory",
|
|
@@ -9168,6 +9292,10 @@ function formatDoctorReport(report) {
|
|
|
9168
9292
|
}
|
|
9169
9293
|
function formatBackgroundSubagents(state) {
|
|
9170
9294
|
const lines = [];
|
|
9295
|
+
if (state.running) {
|
|
9296
|
+
lines.push(...formatRunningCount(state.running));
|
|
9297
|
+
lines.push("");
|
|
9298
|
+
}
|
|
9171
9299
|
const gate = state.gate;
|
|
9172
9300
|
if (!gate) {
|
|
9173
9301
|
lines.push(
|
|
@@ -9213,6 +9341,19 @@ function formatBackgroundSubagents(state) {
|
|
|
9213
9341
|
}
|
|
9214
9342
|
return lines;
|
|
9215
9343
|
}
|
|
9344
|
+
function formatRunningCount(count) {
|
|
9345
|
+
if (count.started === 0) {
|
|
9346
|
+
return ["running now: 0 (none started by this process)"];
|
|
9347
|
+
}
|
|
9348
|
+
const lines = [`running now: ${count.running} (of ${count.started} started by this process)`];
|
|
9349
|
+
if (count.unreadable > 0) {
|
|
9350
|
+
lines.push("");
|
|
9351
|
+
lines.push(
|
|
9352
|
+
`${count.unreadable} could not be read from opencode (deleted, or no session route on this build) and are not counted as running.`
|
|
9353
|
+
);
|
|
9354
|
+
}
|
|
9355
|
+
return lines;
|
|
9356
|
+
}
|
|
9216
9357
|
function describeBackgroundGateSource(gate) {
|
|
9217
9358
|
if (gate.hostApi === "v2") {
|
|
9218
9359
|
return "opencode 2 offers `background` unconditionally, so the registry is not consulted";
|
|
@@ -9297,7 +9438,8 @@ async function gatherDoctorReport(options) {
|
|
|
9297
9438
|
planUsage,
|
|
9298
9439
|
backgroundSubagents: {
|
|
9299
9440
|
gate: snapshotBackgroundSubagentGate(),
|
|
9300
|
-
ledgers: snapshotBackgroundTasks()
|
|
9441
|
+
ledgers: snapshotBackgroundTasks(),
|
|
9442
|
+
running: await countRunningBackgroundTasks().catch(() => void 0)
|
|
9301
9443
|
}
|
|
9302
9444
|
};
|
|
9303
9445
|
}
|
|
@@ -11968,12 +12110,12 @@ function spawnInteractiveProcess(opts) {
|
|
|
11968
12110
|
}
|
|
11969
12111
|
}
|
|
11970
12112
|
closeProxyServer();
|
|
11971
|
-
const
|
|
12113
|
+
const started2 = startPromise !== null;
|
|
11972
12114
|
try {
|
|
11973
12115
|
session.dispose();
|
|
11974
12116
|
} catch {
|
|
11975
12117
|
}
|
|
11976
|
-
if (!
|
|
12118
|
+
if (!started2 && proc.exitCode === null && proc.signalCode === null) {
|
|
11977
12119
|
proc.signalCode = "SIGTERM";
|
|
11978
12120
|
proc.emit("exit", null, "SIGTERM");
|
|
11979
12121
|
proc.emit("close", null, "SIGTERM");
|
|
@@ -12125,6 +12267,7 @@ This opencode host runs background subagents, so \`mcp__opencode_proxy__task\` t
|
|
|
12125
12267
|
- A background dispatch returns at once with \`<task id="ses_..." state="running">\` instead of the subagent's answer. That is success, not a truncated result.
|
|
12126
12268
|
- After starting one, keep working on something that does not overlap it, then end your turn normally. When the subagent finishes, opencode delivers its result into this conversation as a new message on its own. Do not sleep, poll, or ask the operator to wait.
|
|
12127
12269
|
- The \`id\` in that envelope is the task_id. \`mcp__opencode_proxy__task_status\` reads a result back if the automatic delivery did not arrive (an interrupted or errored turn); \`mcp__opencode_proxy__task_cancel\` stops a background subagent you no longer want. Neither is a progress poll.
|
|
12270
|
+
- A background dispatch's result may end with \`Background subagents running now: N\`. That number is for information, so you can avoid starting more than the work needs; it is never a reason to poll, sleep or wait.
|
|
12128
12271
|
- Foreground is still the default and still the right choice when you need the answer before you can continue. Use background only for work that genuinely runs alongside yours.`;
|
|
12129
12272
|
var BACKGROUND_SUBAGENT_HINT_V2 = `## Background subagents
|
|
12130
12273
|
|
|
@@ -12133,6 +12276,7 @@ This opencode host runs background subagents, so \`mcp__opencode_proxy__task\` t
|
|
|
12133
12276
|
- A background dispatch returns at once with \`The subagent is working in the background (sessionID: ses_...)\` instead of the subagent's answer. That is success, not a truncated result.
|
|
12134
12277
|
- After starting one, keep working on something that does not overlap it, then end your turn normally. When the subagent finishes, opencode delivers its result into this conversation on its own, as a \`<subagent sessionID="..." state="completed">\` message. Do not sleep, poll, or ask the operator to wait.
|
|
12135
12278
|
- That \`sessionID\` is the task_id. \`mcp__opencode_proxy__task_status\` reads a result back if the automatic delivery did not arrive (an interrupted or errored turn); \`mcp__opencode_proxy__task_cancel\` stops a background subagent you no longer want. Neither is a progress poll.
|
|
12279
|
+
- A background dispatch's result may end with \`Background subagents running now: N\`. That number is for information, so you can avoid starting more than the work needs; it is never a reason to poll, sleep or wait.
|
|
12136
12280
|
- Foreground is still the default and still the right choice when you need the answer before you can continue. Use background only for work that genuinely runs alongside yours.`;
|
|
12137
12281
|
function backgroundSubagentHint(dialect) {
|
|
12138
12282
|
return dialect === "v2" ? BACKGROUND_SUBAGENT_HINT_V2 : BACKGROUND_SUBAGENT_HINT;
|
|
@@ -12737,7 +12881,13 @@ function resolvedProxyTools(config) {
|
|
|
12737
12881
|
}
|
|
12738
12882
|
return picked.length > 0 ? picked : null;
|
|
12739
12883
|
}
|
|
12740
|
-
function
|
|
12884
|
+
function decideMcpServerRoute(input) {
|
|
12885
|
+
if (input.covered) return "proxy";
|
|
12886
|
+
if (input.inToolSet) return "bridge";
|
|
12887
|
+
if (input.toolSetKnown && input.status === "connected") return "withhold";
|
|
12888
|
+
return "bridge";
|
|
12889
|
+
}
|
|
12890
|
+
function resolvedProxyMcpTools(config, allEnabledServerNames, modelTools, taken, runtimeStatus) {
|
|
12741
12891
|
if (config.proxyOpencodeMcpTools !== true) return null;
|
|
12742
12892
|
if (config.bridgeOpencodeMcp === false) return null;
|
|
12743
12893
|
if (allEnabledServerNames.length === 0) return null;
|
|
@@ -12746,18 +12896,47 @@ function resolvedProxyMcpTools(config, allEnabledServerNames, modelTools, taken)
|
|
|
12746
12896
|
tools: modelTools,
|
|
12747
12897
|
taken
|
|
12748
12898
|
});
|
|
12749
|
-
|
|
12899
|
+
const codeMode = config.hostApi === "v2" && (modelTools?.some((tool) => tool.name === "execute") ?? false);
|
|
12900
|
+
const toolSetKnown = (modelTools?.length ?? 0) > 0 && !codeMode;
|
|
12901
|
+
const withheld = [];
|
|
12902
|
+
const bridged = [];
|
|
12903
|
+
for (const server2 of allEnabledServerNames) {
|
|
12904
|
+
const route = decideMcpServerRoute({
|
|
12905
|
+
covered: resolution.coveredServers.has(server2),
|
|
12906
|
+
inToolSet: resolution.matchedServers.has(server2),
|
|
12907
|
+
status: runtimeStatus?.[server2],
|
|
12908
|
+
toolSetKnown
|
|
12909
|
+
});
|
|
12910
|
+
if (route === "withhold") withheld.push(server2);
|
|
12911
|
+
else if (route === "bridge") bridged.push(server2);
|
|
12912
|
+
}
|
|
12913
|
+
if (codeMode && resolution.defs.length === 0) {
|
|
12750
12914
|
log.warn(
|
|
12751
|
-
"proxyOpencodeMcpTools
|
|
12752
|
-
{ servers: allEnabledServerNames
|
|
12915
|
+
"proxyOpencodeMcpTools cannot route V2 Code Mode's MCP tools, which reach the model through execute; for Code Mode, explicitly allowlist execute in proxyOpencodeTools and use bridgeOpencodeMcp: false with strictMcpConfig: true",
|
|
12916
|
+
{ servers: allEnabledServerNames }
|
|
12753
12917
|
);
|
|
12754
|
-
return null;
|
|
12755
12918
|
}
|
|
12756
|
-
|
|
12757
|
-
|
|
12758
|
-
|
|
12759
|
-
|
|
12760
|
-
|
|
12919
|
+
if (withheld.length > 0 || bridged.length > 0) {
|
|
12920
|
+
log.info("proxyOpencodeMcpTools: MCP servers outside this agent's tool set", {
|
|
12921
|
+
proxied: [...resolution.coveredServers],
|
|
12922
|
+
withheld,
|
|
12923
|
+
bridged,
|
|
12924
|
+
modelTools: modelTools?.length ?? 0,
|
|
12925
|
+
statusKnown: runtimeStatus !== void 0
|
|
12926
|
+
});
|
|
12927
|
+
} else {
|
|
12928
|
+
log.debug("routing opencode MCP tools through the proxy", {
|
|
12929
|
+
servers: [...resolution.coveredServers],
|
|
12930
|
+
tools: resolution.defs.map((def) => def.name)
|
|
12931
|
+
});
|
|
12932
|
+
}
|
|
12933
|
+
const exclude = /* @__PURE__ */ new Set([...resolution.coveredServers, ...withheld]);
|
|
12934
|
+
return {
|
|
12935
|
+
resolution,
|
|
12936
|
+
withheld,
|
|
12937
|
+
bridged,
|
|
12938
|
+
excludeServers: exclude.size > 0 ? exclude : void 0
|
|
12939
|
+
};
|
|
12761
12940
|
}
|
|
12762
12941
|
async function fetchLiveToolInfo(config, modelId) {
|
|
12763
12942
|
const items = await fetchOpencodeToolList(
|
|
@@ -14684,13 +14863,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
14684
14863
|
resolvedProxyTools() {
|
|
14685
14864
|
return resolvedProxyTools(this.config);
|
|
14686
14865
|
}
|
|
14687
|
-
/** Resolve ProxyToolDef[] for opencode's MCP-backed tools. */
|
|
14688
|
-
resolvedProxyMcpTools(allEnabledServerNames, modelTools, taken) {
|
|
14866
|
+
/** Resolve ProxyToolDef[] for opencode's MCP-backed tools, and route the rest. */
|
|
14867
|
+
resolvedProxyMcpTools(allEnabledServerNames, modelTools, taken, runtimeStatus) {
|
|
14689
14868
|
return resolvedProxyMcpTools(
|
|
14690
14869
|
this.config,
|
|
14691
14870
|
allEnabledServerNames,
|
|
14692
14871
|
modelTools,
|
|
14693
|
-
taken
|
|
14872
|
+
taken,
|
|
14873
|
+
runtimeStatus
|
|
14694
14874
|
);
|
|
14695
14875
|
}
|
|
14696
14876
|
/** One `client.tool.list()` fetch, shaped for this turn's gates. */
|
|
@@ -15999,13 +16179,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
15999
16179
|
void 0,
|
|
16000
16180
|
runtimeStatus
|
|
16001
16181
|
);
|
|
16002
|
-
const
|
|
16182
|
+
const mcpRouting = self.resolvedProxyMcpTools(
|
|
16003
16183
|
discovery.allEnabledServerNames,
|
|
16004
16184
|
options.tools,
|
|
16005
|
-
new Set((resolvedProxy ?? []).map((def) => def.name))
|
|
16185
|
+
new Set((resolvedProxy ?? []).map((def) => def.name)),
|
|
16186
|
+
runtimeStatus
|
|
16006
16187
|
);
|
|
16007
|
-
const proxyMcpTools =
|
|
16008
|
-
const excludeServers =
|
|
16188
|
+
const proxyMcpTools = mcpRouting && mcpRouting.resolution.defs.length > 0 ? mcpRouting.resolution.defs : null;
|
|
16189
|
+
const excludeServers = mcpRouting?.excludeServers;
|
|
16009
16190
|
const taskProxyEnabled = resolvedProxy?.some((t) => t.name === "task") ?? false;
|
|
16010
16191
|
const questionProxyEnabled = resolvedProxy?.some((t) => t.name === "question") ?? false;
|
|
16011
16192
|
const opencodeToolsRequested = (self.config.proxyOpencodeTools?.length ?? 0) > 0;
|
|
@@ -16844,8 +17025,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
16844
17025
|
await new Promise((resolve6) => setTimeout(resolve6, FORWARD_SETTLE_MS));
|
|
16845
17026
|
if (state.controllerClosed) return;
|
|
16846
17027
|
}
|
|
16847
|
-
for (const { call, result } of previousPendingProxyMatches) {
|
|
16848
|
-
if (
|
|
17028
|
+
for (const { call, result: opencodeResult } of previousPendingProxyMatches) {
|
|
17029
|
+
if (opencodeResult) {
|
|
17030
|
+
let result = opencodeResult;
|
|
17031
|
+
if (call.toolName === "task" || call.toolName === TASK_BATCH_TOOL_NAME) {
|
|
17032
|
+
noteBackgroundDispatchResult(sk, result);
|
|
17033
|
+
result = await withBackgroundRunningCount(result);
|
|
17034
|
+
if (state.controllerClosed) return;
|
|
17035
|
+
}
|
|
16849
17036
|
const channelClosed = isPendingProxyCallChannelClosed(call);
|
|
16850
17037
|
log.info("resolving pending proxy call from tool result prompt", {
|
|
16851
17038
|
sessionKey: sk,
|