@khalilgharbaoui/opencode-claude-code-plugin 0.53.3 → 0.53.5
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.js +108 -25
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/claude-code-plugin/SKILL.md +6 -0
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"];
|
|
@@ -5927,9 +5927,20 @@ function backgroundTaskIdsIn(text) {
|
|
|
5927
5927
|
}
|
|
5928
5928
|
return [...ids];
|
|
5929
5929
|
}
|
|
5930
|
+
var BATCH_SECTION = /^## task \d+ of \d+: /m;
|
|
5931
|
+
function acceptedBackgroundTaskIds(result) {
|
|
5932
|
+
if (result.kind !== "text") return [];
|
|
5933
|
+
if (result.isError !== true) return backgroundTaskIdsIn(result.text);
|
|
5934
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5935
|
+
for (const section of result.text.split(BATCH_SECTION).slice(1)) {
|
|
5936
|
+
const body = section.slice(section.indexOf("\n") + 1);
|
|
5937
|
+
if (body.startsWith("[error] ") || body.startsWith("[missing] ")) continue;
|
|
5938
|
+
for (const id of backgroundTaskIdsIn(body)) ids.add(id);
|
|
5939
|
+
}
|
|
5940
|
+
return [...ids];
|
|
5941
|
+
}
|
|
5930
5942
|
function noteBackgroundDispatchResult(sessionKey2, result, now = Date.now()) {
|
|
5931
|
-
|
|
5932
|
-
for (const taskId of backgroundTaskIdsIn(result.text)) {
|
|
5943
|
+
for (const taskId of acceptedBackgroundTaskIds(result)) {
|
|
5933
5944
|
started.delete(taskId);
|
|
5934
5945
|
started.set(taskId, { sessionKey: sessionKey2, at: now });
|
|
5935
5946
|
while (started.size > MAX_STARTED) {
|
|
@@ -5941,23 +5952,9 @@ function noteBackgroundDispatchResult(sessionKey2, result, now = Date.now()) {
|
|
|
5941
5952
|
}
|
|
5942
5953
|
async function countRunningBackgroundTasks() {
|
|
5943
5954
|
const entries = [...started.entries()];
|
|
5944
|
-
const cancelledIds =
|
|
5945
|
-
for (const ids of cancelled.values()) for (const id of ids) cancelledIds.add(id);
|
|
5955
|
+
const cancelledIds = cancelledTaskIds();
|
|
5946
5956
|
const states = await Promise.all(
|
|
5947
|
-
entries.map(
|
|
5948
|
-
if (cancelledIds.has(taskId)) return "stopped";
|
|
5949
|
-
try {
|
|
5950
|
-
const runState = await fetchSessionRunState(taskId);
|
|
5951
|
-
if (runState !== "unknown") {
|
|
5952
|
-
return isBackgroundTaskRunning(runState, []) ? "running" : "stopped";
|
|
5953
|
-
}
|
|
5954
|
-
const replies = await fetchSessionReplies(taskId);
|
|
5955
|
-
if (replies === void 0) return "unreadable";
|
|
5956
|
-
return isBackgroundTaskRunning(runState, replies) ? "running" : "stopped";
|
|
5957
|
-
} catch {
|
|
5958
|
-
return "unreadable";
|
|
5959
|
-
}
|
|
5960
|
-
})
|
|
5957
|
+
entries.map(([taskId]) => backgroundTaskState(taskId, cancelledIds))
|
|
5961
5958
|
);
|
|
5962
5959
|
return {
|
|
5963
5960
|
running: states.filter((state) => state === "running").length,
|
|
@@ -5965,6 +5962,87 @@ async function countRunningBackgroundTasks() {
|
|
|
5965
5962
|
unreadable: states.filter((state) => state === "unreadable").length
|
|
5966
5963
|
};
|
|
5967
5964
|
}
|
|
5965
|
+
function cancelledTaskIds() {
|
|
5966
|
+
const ids = /* @__PURE__ */ new Set();
|
|
5967
|
+
for (const set of cancelled.values()) for (const id of set) ids.add(id);
|
|
5968
|
+
return ids;
|
|
5969
|
+
}
|
|
5970
|
+
async function backgroundTaskState(taskId, cancelledIds) {
|
|
5971
|
+
if (cancelledIds.has(taskId)) return "stopped";
|
|
5972
|
+
try {
|
|
5973
|
+
const runState = await fetchSessionRunState(taskId);
|
|
5974
|
+
if (runState !== "unknown") {
|
|
5975
|
+
return isBackgroundTaskRunning(runState, []) ? "running" : "stopped";
|
|
5976
|
+
}
|
|
5977
|
+
const replies = await fetchSessionReplies(taskId);
|
|
5978
|
+
if (replies === void 0) return "unreadable";
|
|
5979
|
+
return isBackgroundTaskRunning(runState, replies) ? "running" : "stopped";
|
|
5980
|
+
} catch {
|
|
5981
|
+
return "unreadable";
|
|
5982
|
+
}
|
|
5983
|
+
}
|
|
5984
|
+
var BACKGROUND_COUNT_BUDGET_MS = 500;
|
|
5985
|
+
var BACKGROUND_COUNT_CONCURRENCY = 4;
|
|
5986
|
+
async function countOtherRunningWithin(exclude, budgetMs) {
|
|
5987
|
+
const taskIds = [...started.keys()].filter((taskId) => !exclude.has(taskId));
|
|
5988
|
+
if (taskIds.length === 0) return 0;
|
|
5989
|
+
const cancelledIds = cancelledTaskIds();
|
|
5990
|
+
let overBudget = false;
|
|
5991
|
+
let unreadable = false;
|
|
5992
|
+
let next = 0;
|
|
5993
|
+
let running = 0;
|
|
5994
|
+
const worker = async () => {
|
|
5995
|
+
while (!overBudget && !unreadable && next < taskIds.length) {
|
|
5996
|
+
const taskId = taskIds[next++];
|
|
5997
|
+
const state = await backgroundTaskState(taskId, cancelledIds);
|
|
5998
|
+
if (state === "running") running++;
|
|
5999
|
+
if (state === "unreadable") unreadable = true;
|
|
6000
|
+
}
|
|
6001
|
+
};
|
|
6002
|
+
const workers = Array.from(
|
|
6003
|
+
{ length: Math.min(BACKGROUND_COUNT_CONCURRENCY, taskIds.length) },
|
|
6004
|
+
worker
|
|
6005
|
+
);
|
|
6006
|
+
let timer;
|
|
6007
|
+
const deadline = new Promise((resolve6) => {
|
|
6008
|
+
timer = setTimeout(() => resolve6("over"), budgetMs);
|
|
6009
|
+
timer.unref?.();
|
|
6010
|
+
});
|
|
6011
|
+
try {
|
|
6012
|
+
const outcome = await Promise.race([Promise.all(workers).then(() => "done"), deadline]);
|
|
6013
|
+
if (outcome === "over") {
|
|
6014
|
+
overBudget = true;
|
|
6015
|
+
return void 0;
|
|
6016
|
+
}
|
|
6017
|
+
return unreadable ? void 0 : running;
|
|
6018
|
+
} catch {
|
|
6019
|
+
overBudget = true;
|
|
6020
|
+
return void 0;
|
|
6021
|
+
} finally {
|
|
6022
|
+
clearTimeout(timer);
|
|
6023
|
+
}
|
|
6024
|
+
}
|
|
6025
|
+
async function withBackgroundRunningCount(result, budgetMs = BACKGROUND_COUNT_BUDGET_MS) {
|
|
6026
|
+
if (result.kind !== "text") return result;
|
|
6027
|
+
const dispatched = acceptedBackgroundTaskIds(result);
|
|
6028
|
+
if (dispatched.length === 0) return result;
|
|
6029
|
+
const others = await countOtherRunningWithin(new Set(dispatched), budgetMs);
|
|
6030
|
+
if (others === void 0) {
|
|
6031
|
+
log.info("background running count omitted: a lookup was slow or unreadable", {
|
|
6032
|
+
budgetMs,
|
|
6033
|
+
dispatched: dispatched.length
|
|
6034
|
+
});
|
|
6035
|
+
return result;
|
|
6036
|
+
}
|
|
6037
|
+
return { ...result, text: result.text + formatBackgroundRunningLine(others, dispatched.length) };
|
|
6038
|
+
}
|
|
6039
|
+
function formatBackgroundRunningLine(others, dispatched) {
|
|
6040
|
+
const total = others + dispatched;
|
|
6041
|
+
const including = dispatched === 1 ? "this one" : `the ${dispatched} just started`;
|
|
6042
|
+
return `
|
|
6043
|
+
|
|
6044
|
+
Background subagents running now: ${total} (including ${including}).`;
|
|
6045
|
+
}
|
|
5968
6046
|
var lastGate;
|
|
5969
6047
|
function recordBackgroundSubagentGate(gate, now = Date.now()) {
|
|
5970
6048
|
lastGate = { ...gate, at: now };
|
|
@@ -12200,6 +12278,7 @@ This opencode host runs background subagents, so \`mcp__opencode_proxy__task\` t
|
|
|
12200
12278
|
- 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.
|
|
12201
12279
|
- 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.
|
|
12202
12280
|
- 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.
|
|
12281
|
+
- 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.
|
|
12203
12282
|
- 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.`;
|
|
12204
12283
|
var BACKGROUND_SUBAGENT_HINT_V2 = `## Background subagents
|
|
12205
12284
|
|
|
@@ -12208,6 +12287,7 @@ This opencode host runs background subagents, so \`mcp__opencode_proxy__task\` t
|
|
|
12208
12287
|
- 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.
|
|
12209
12288
|
- 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.
|
|
12210
12289
|
- 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.
|
|
12290
|
+
- 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.
|
|
12211
12291
|
- 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.`;
|
|
12212
12292
|
function backgroundSubagentHint(dialect) {
|
|
12213
12293
|
return dialect === "v2" ? BACKGROUND_SUBAGENT_HINT_V2 : BACKGROUND_SUBAGENT_HINT;
|
|
@@ -16956,8 +17036,14 @@ var ClaudeCodeLanguageModel = class {
|
|
|
16956
17036
|
await new Promise((resolve6) => setTimeout(resolve6, FORWARD_SETTLE_MS));
|
|
16957
17037
|
if (state.controllerClosed) return;
|
|
16958
17038
|
}
|
|
16959
|
-
for (const { call, result } of previousPendingProxyMatches) {
|
|
16960
|
-
if (
|
|
17039
|
+
for (const { call, result: opencodeResult } of previousPendingProxyMatches) {
|
|
17040
|
+
if (opencodeResult) {
|
|
17041
|
+
let result = opencodeResult;
|
|
17042
|
+
if (call.toolName === "task" || call.toolName === TASK_BATCH_TOOL_NAME) {
|
|
17043
|
+
noteBackgroundDispatchResult(sk, result);
|
|
17044
|
+
result = await withBackgroundRunningCount(result);
|
|
17045
|
+
if (state.controllerClosed) return;
|
|
17046
|
+
}
|
|
16961
17047
|
const channelClosed = isPendingProxyCallChannelClosed(call);
|
|
16962
17048
|
log.info("resolving pending proxy call from tool result prompt", {
|
|
16963
17049
|
sessionKey: sk,
|
|
@@ -16973,9 +17059,6 @@ var ClaudeCodeLanguageModel = class {
|
|
|
16973
17059
|
recoveryRequired: channelClosed || state.unattendedTurnEnded
|
|
16974
17060
|
});
|
|
16975
17061
|
}
|
|
16976
|
-
if (call.toolName === "task" || call.toolName === TASK_BATCH_TOOL_NAME) {
|
|
16977
|
-
noteBackgroundDispatchResult(sk, result);
|
|
16978
|
-
}
|
|
16979
17062
|
resolvePendingProxyCallById(call.toolCallId, result);
|
|
16980
17063
|
} else {
|
|
16981
17064
|
log.info(
|