@khalilgharbaoui/opencode-claude-code-plugin 0.15.2 → 0.15.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/README.md +8 -1
- package/dist/index.js +43 -43
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -524,7 +524,14 @@ The plugin registers the command without replacing an existing user-defined `btw
|
|
|
524
524
|
|
|
525
525
|
Where the answer appears, in the conversation either way:
|
|
526
526
|
|
|
527
|
-
- **A receipt, straight away**, when you asked while a turn was running
|
|
527
|
+
- **A receipt, straight away**, when you asked while a turn was running, in the reply you are watching, so a `/btw` typed mid-turn is visibly taken rather than looking swallowed until the answer arrives:
|
|
528
|
+
|
|
529
|
+
```text
|
|
530
|
+
▌ **btw:** <your question, in full>
|
|
531
|
+
▌ *sent to Claude on the side*
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
It quotes the question back untruncated because the prompt box clears on submit and no `/btw` message is ever created, so this is the only place you can read back what you sent. If opencode is between two streams at that moment (it was running a tool), the receipt lands when the next one opens.
|
|
528
535
|
- **Inside the running turn's own reply**, as soon as the answer arrives, when you asked while Claude was working. It is written into the reply you are already watching as its own block, headed `▌ **btw:** <your question>`, so it stays there and is easy to pick out. Every line of the aside, answer included, carries that `▌` bar, so it reads as one block down its whole height. Nothing is queued and the `/btw` message itself is dropped, because the answer is already in the transcript. The turn goes on to deliver its own reply as usual.
|
|
529
536
|
- **As its own `/btw` message and answer** when the conversation is idle, or when the turn had no stream open to write into at that moment (opencode was running a tool between two of them). In the second case the pair lands when the turn ends; nothing is announced in the meantime, because the answer itself is what arrives.
|
|
530
537
|
- Follow-ups work: earlier asides in the conversation are sent along as the aside's history.
|
package/dist/index.js
CHANGED
|
@@ -2100,8 +2100,11 @@ var LEGACY_INLINE_ASIDE_MARKERS = ["> **btw:**"];
|
|
|
2100
2100
|
function barEveryLine(text) {
|
|
2101
2101
|
return text.split("\n").map((line) => line.trim() === "" ? "\u258C" : `\u258C ${line}`).join("\n");
|
|
2102
2102
|
}
|
|
2103
|
+
function oneLine(question) {
|
|
2104
|
+
return question.replace(/\s+/g, " ").trim();
|
|
2105
|
+
}
|
|
2103
2106
|
function asideHeader(question) {
|
|
2104
|
-
return `${INLINE_ASIDE_MARKER} ${question
|
|
2107
|
+
return `${INLINE_ASIDE_MARKER} ${oneLine(question)}`;
|
|
2105
2108
|
}
|
|
2106
2109
|
function formatInlineAside(question, answer) {
|
|
2107
2110
|
return `
|
|
@@ -2110,10 +2113,11 @@ ${asideHeader(question)}
|
|
|
2110
2113
|
${barEveryLine(answer.trim())}
|
|
2111
2114
|
`;
|
|
2112
2115
|
}
|
|
2113
|
-
var
|
|
2114
|
-
function formatInlineAsideAsk() {
|
|
2116
|
+
var INLINE_ASIDE_SENT_NOTE = "*sent to Claude on the side*";
|
|
2117
|
+
function formatInlineAsideAsk(question) {
|
|
2115
2118
|
return `
|
|
2116
|
-
${
|
|
2119
|
+
${asideHeader(question)}
|
|
2120
|
+
\u258C ${INLINE_ASIDE_SENT_NOTE}
|
|
2117
2121
|
`;
|
|
2118
2122
|
}
|
|
2119
2123
|
var asideSinks = /* @__PURE__ */ new Map();
|
|
@@ -2282,7 +2286,7 @@ async function handleBtwCommand(client, input, options = {}) {
|
|
|
2282
2286
|
questionLength: question.length,
|
|
2283
2287
|
history: history.length
|
|
2284
2288
|
});
|
|
2285
|
-
const asked = busy ? deliverAsideInline(client, input.sessionID, formatInlineAsideAsk(), options).catch(() => false) : Promise.resolve(false);
|
|
2289
|
+
const asked = busy ? deliverAsideInline(client, input.sessionID, formatInlineAsideAsk(question), options).catch(() => false) : Promise.resolve(false);
|
|
2286
2290
|
answer.then(
|
|
2287
2291
|
async (result) => {
|
|
2288
2292
|
log.info("btw: early answer arrived", { sessionID: input.sessionID, busy, responseLength: result.response.length });
|
|
@@ -2507,7 +2511,7 @@ function compactConversationHistory(prompt, opts = {}) {
|
|
|
2507
2511
|
return buildCompactionHistory(prompt);
|
|
2508
2512
|
}
|
|
2509
2513
|
const conversationMessages = prompt.filter(
|
|
2510
|
-
(m) => m.role === "user" || m.role === "assistant"
|
|
2514
|
+
(m) => m.role === "user" || m.role === "assistant" || m.role === "tool"
|
|
2511
2515
|
);
|
|
2512
2516
|
if (conversationMessages.length <= 1) {
|
|
2513
2517
|
return null;
|
|
@@ -2515,28 +2519,8 @@ function compactConversationHistory(prompt, opts = {}) {
|
|
|
2515
2519
|
const historyParts = [];
|
|
2516
2520
|
for (let i = 0; i < conversationMessages.length - 1; i++) {
|
|
2517
2521
|
const msg = conversationMessages[i];
|
|
2518
|
-
const role = msg.role === "user" ? "User" : "Assistant";
|
|
2519
|
-
|
|
2520
|
-
if (typeof msg.content === "string") {
|
|
2521
|
-
text = msg.content;
|
|
2522
|
-
} else if (Array.isArray(msg.content)) {
|
|
2523
|
-
const textParts = msg.content.filter((p) => p.type === "text" && p.text).map((p) => p.text);
|
|
2524
|
-
text = textParts.join("\n");
|
|
2525
|
-
const toolCalls = msg.content.filter(
|
|
2526
|
-
(p) => p.type === "tool-call"
|
|
2527
|
-
);
|
|
2528
|
-
const toolResults = msg.content.filter(
|
|
2529
|
-
(p) => p.type === "tool-result"
|
|
2530
|
-
);
|
|
2531
|
-
if (toolCalls.length > 0) {
|
|
2532
|
-
text += `
|
|
2533
|
-
[Called ${toolCalls.length} tool(s): ${toolCalls.map((t) => t.toolName).join(", ")}]`;
|
|
2534
|
-
}
|
|
2535
|
-
if (toolResults.length > 0) {
|
|
2536
|
-
text += `
|
|
2537
|
-
[Received ${toolResults.length} tool result(s)]`;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2522
|
+
const role = msg.role === "user" ? "User" : msg.role === "assistant" ? "Assistant" : "Tool";
|
|
2523
|
+
const { text } = renderMessageContentForCompaction(msg);
|
|
2540
2524
|
if (text.trim()) {
|
|
2541
2525
|
const truncated = text.length > 2e3 ? text.slice(0, 2e3) + "..." : text;
|
|
2542
2526
|
historyParts.push(`${role}: ${truncated}`);
|
|
@@ -2579,7 +2563,27 @@ function buildCompactionHistory(prompt) {
|
|
|
2579
2563
|
}
|
|
2580
2564
|
function getClaudeUserMessage(prompt, includeHistoryContext = false, opts = {}) {
|
|
2581
2565
|
const compactionMode = opts.compactionMode === true;
|
|
2566
|
+
const cliToolCallIds = opts.cliToolCallIds;
|
|
2582
2567
|
const content = [];
|
|
2568
|
+
const pushToolResult = (part) => {
|
|
2569
|
+
const id = part.toolCallId;
|
|
2570
|
+
const text = getToolResultText(part);
|
|
2571
|
+
if (!cliToolCallIds || cliToolCallIds.has(id)) {
|
|
2572
|
+
content.push({ type: "tool_result", tool_use_id: id, content: text });
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
log.info("rendering opencode-side tool result as text", {
|
|
2576
|
+
toolCallId: id,
|
|
2577
|
+
toolName: part.toolName,
|
|
2578
|
+
chars: text.length
|
|
2579
|
+
});
|
|
2580
|
+
content.push({
|
|
2581
|
+
type: "text",
|
|
2582
|
+
text: `<opencode_tool_result tool="${part.toolName ?? "unknown"}">
|
|
2583
|
+
${text}
|
|
2584
|
+
</opencode_tool_result>`
|
|
2585
|
+
});
|
|
2586
|
+
};
|
|
2583
2587
|
if (compactionMode) {
|
|
2584
2588
|
const transcript = compactConversationHistory(prompt, {
|
|
2585
2589
|
mode: "compaction"
|
|
@@ -2649,12 +2653,7 @@ Now continuing with the current message:
|
|
|
2649
2653
|
});
|
|
2650
2654
|
}
|
|
2651
2655
|
} else if (part.type === "tool-result") {
|
|
2652
|
-
|
|
2653
|
-
content.push({
|
|
2654
|
-
type: "tool_result",
|
|
2655
|
-
tool_use_id: p.toolCallId,
|
|
2656
|
-
content: getToolResultText(p)
|
|
2657
|
-
});
|
|
2656
|
+
pushToolResult(part);
|
|
2658
2657
|
}
|
|
2659
2658
|
}
|
|
2660
2659
|
}
|
|
@@ -2662,12 +2661,7 @@ Now continuing with the current message:
|
|
|
2662
2661
|
if (Array.isArray(msg.content)) {
|
|
2663
2662
|
for (const part of msg.content) {
|
|
2664
2663
|
if (part?.type === "tool-result") {
|
|
2665
|
-
|
|
2666
|
-
content.push({
|
|
2667
|
-
type: "tool_result",
|
|
2668
|
-
tool_use_id: p.toolCallId,
|
|
2669
|
-
content: getToolResultText(p)
|
|
2670
|
-
});
|
|
2664
|
+
pushToolResult(part);
|
|
2671
2665
|
}
|
|
2672
2666
|
}
|
|
2673
2667
|
}
|
|
@@ -5054,7 +5048,12 @@ var ClaudeCodeLanguageModel = class {
|
|
|
5054
5048
|
}
|
|
5055
5049
|
const hasExistingSession = !!getClaudeSessionId(sk);
|
|
5056
5050
|
const includeHistoryContext = !hasExistingSession && hasPriorConversation;
|
|
5057
|
-
const userMsg = consumeExitPlanModeQuestionResult(sk, options.prompt) ??
|
|
5051
|
+
const userMsg = consumeExitPlanModeQuestionResult(sk, options.prompt) ?? // doGenerate has no proxy wiring, so this process issued no tool calls
|
|
5052
|
+
// at all: every tool result reaching it belongs to opencode and must be
|
|
5053
|
+
// rendered as text rather than an orphaned `tool_result` (issue #29).
|
|
5054
|
+
getClaudeUserMessage(options.prompt, includeHistoryContext, {
|
|
5055
|
+
cliToolCallIds: /* @__PURE__ */ new Set()
|
|
5056
|
+
});
|
|
5058
5057
|
const [runtimeStatus, cliVersion, planModeQuestionActive] = await Promise.all([
|
|
5059
5058
|
getRuntimeMcpStatus(),
|
|
5060
5059
|
detectCliVersion(this.config.cliPath),
|
|
@@ -5510,8 +5509,10 @@ ${plan}
|
|
|
5510
5509
|
if (exitPlanModeQuestionResult) {
|
|
5511
5510
|
log.info("sending plan approval decision to claude", { sk });
|
|
5512
5511
|
}
|
|
5512
|
+
const previousPendingProxyCalls = compactionMode ? [] : getPendingProxyCalls(sk);
|
|
5513
5513
|
const userMsg = exitPlanModeQuestionResult ?? getClaudeUserMessage(options.prompt, includeHistoryContext, {
|
|
5514
|
-
compactionMode
|
|
5514
|
+
compactionMode,
|
|
5515
|
+
cliToolCallIds: new Set(previousPendingProxyCalls.map((c) => c.toolCallId))
|
|
5515
5516
|
});
|
|
5516
5517
|
const resolvedProxy = compactionMode ? null : this.resolvedProxyTools();
|
|
5517
5518
|
const loadLiveToolInfo = this.createLiveToolInfoLoader();
|
|
@@ -5520,7 +5521,6 @@ ${plan}
|
|
|
5520
5521
|
loadLiveToolInfo
|
|
5521
5522
|
);
|
|
5522
5523
|
const self = this;
|
|
5523
|
-
const previousPendingProxyCalls = compactionMode ? [] : getPendingProxyCalls(sk);
|
|
5524
5524
|
const previousPendingProxyMatches = previousPendingProxyCalls.map((call) => ({
|
|
5525
5525
|
call,
|
|
5526
5526
|
result: this.extractPendingProxyResult(options.prompt, call.toolCallId)
|