@khalilgharbaoui/opencode-claude-code-plugin 0.4.3 → 0.4.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/README.md +3 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +172 -4
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -171,6 +171,8 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
|
|
|
171
171
|
| `mcpConfig` | string \| string[] | – | Extra `--mcp-config` paths/JSON passed alongside the bridged config. |
|
|
172
172
|
| `strictMcpConfig` | boolean | `false` | Pass `--strict-mcp-config` so Claude loads **only** the configured servers and ignores `~/.claude/settings.json`. |
|
|
173
173
|
| `webSearch` | `"claude"` \| `"disabled"` \| `<tool>` | `"claude"` | Routing for Claude's built-in `WebSearch`. See [WebSearch routing](#websearch-routing). |
|
|
174
|
+
| `multiStepContinuation` | boolean | `true` | Append a system-prompt hint nudging Claude to chain tool calls within one turn instead of pausing between subtasks. Each opencode turn boundary requires the user to manually press "continue", so for multi-step tasks this reduces friction. Set `false` to disable. |
|
|
175
|
+
| `autoContinueIncompleteTurns` | boolean \| `"smart"` | `"smart"` | Smartly continue incomplete Claude CLI results inside the same opencode turn. Reduces manual "continue" presses when Claude ends after reasoning/tool activity without a useful final answer. Set `false` to disable. |
|
|
174
176
|
|
|
175
177
|
### Overriding model metadata
|
|
176
178
|
|
|
@@ -310,6 +312,7 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
|
|
|
310
312
|
## Quirks worth knowing
|
|
311
313
|
|
|
312
314
|
- **Empty text blocks are dropped.** Claude sometimes opens a `content_block_start` for text but never sends a delta. The plugin no longer emits the empty block (which was triggering Anthropic 400s like `cache_control cannot be set for empty text blocks`).
|
|
315
|
+
- **Smart incomplete-turn continuation.** By default, the plugin keeps the current opencode stream open and feeds Claude CLI a small internal continuation message when Claude emits a `result` after reasoning/tool activity without a useful visible answer. It still stops normally on final-looking answers, questions, blockers, errors, aborts, or internal safety-budget exhaustion. Disable with `"autoContinueIncompleteTurns": false`.
|
|
313
316
|
- **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
|
|
314
317
|
- **Wire-inactivity watchdog.** Once the CLI has produced any content, the stream closes gracefully if stdout goes silent for 60 seconds without a `result` message arriving. Resets on every line received, so long mid-turn pauses (Sonnet between text-end and the next tool_use, for example) are tolerated. On a user-initiated abort, the watchdog shortens to 5 seconds.
|
|
315
318
|
- **Per-iteration usage.** When the CLI internally retries with tools, the plugin only counts the last iteration's usage so opencode's context accounting stays accurate.
|
package/dist/index.d.ts
CHANGED
|
@@ -115,6 +115,8 @@ interface ClaudeCodeConfig {
|
|
|
115
115
|
webSearch?: WebSearchRouting;
|
|
116
116
|
hotReloadMcp?: boolean;
|
|
117
117
|
proxyOpencodeMcpTools?: boolean;
|
|
118
|
+
multiStepContinuation?: boolean;
|
|
119
|
+
autoContinueIncompleteTurns?: boolean | "smart";
|
|
118
120
|
}
|
|
119
121
|
type WebSearchRouting = "claude" | "disabled" | (string & {});
|
|
120
122
|
interface ClaudeCodeProviderSettings {
|
|
@@ -207,6 +209,30 @@ interface ClaudeCodeProviderSettings {
|
|
|
207
209
|
* an opencode round-trip).
|
|
208
210
|
*/
|
|
209
211
|
proxyOpencodeMcpTools?: boolean;
|
|
212
|
+
/**
|
|
213
|
+
* Append a short system-prompt hint that nudges Claude to chain
|
|
214
|
+
* multiple tool calls within a single turn instead of pausing for user
|
|
215
|
+
* confirmation between subtasks. Each turn boundary in opencode
|
|
216
|
+
* requires the user to manually press "continue" to resume, so for
|
|
217
|
+
* multi-step tasks this option reduces friction. Defaults to `true`.
|
|
218
|
+
*
|
|
219
|
+
* Set to `false` if you prefer the un-nudged model behavior (Claude
|
|
220
|
+
* decides when to end the turn entirely on its own).
|
|
221
|
+
*/
|
|
222
|
+
multiStepContinuation?: boolean;
|
|
223
|
+
/**
|
|
224
|
+
* Smartly continue incomplete Claude CLI results inside the same opencode
|
|
225
|
+
* turn. Claude CLI sometimes emits `result` after reasoning/tool activity
|
|
226
|
+
* without a useful final answer, which makes opencode stop and wait for the
|
|
227
|
+
* user to type "continue". With the default `"smart"`, the plugin detects
|
|
228
|
+
* those incomplete result boundaries, feeds Claude a small continuation
|
|
229
|
+
* message internally, and keeps the opencode stream open. Final answers,
|
|
230
|
+
* questions, blockers, errors, aborts, and safety-budget exhaustion still
|
|
231
|
+
* stop normally.
|
|
232
|
+
*
|
|
233
|
+
* Set to `false` to disable.
|
|
234
|
+
*/
|
|
235
|
+
autoContinueIncompleteTurns?: boolean | "smart";
|
|
210
236
|
}
|
|
211
237
|
type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
|
|
212
238
|
type ControlRequestBehavior = "allow" | "deny";
|
package/dist/index.js
CHANGED
|
@@ -1483,6 +1483,77 @@ function hasNewUserContent(prompt) {
|
|
|
1483
1483
|
}
|
|
1484
1484
|
return false;
|
|
1485
1485
|
}
|
|
1486
|
+
var AUTO_CONTINUE_MAX_ATTEMPTS = 8;
|
|
1487
|
+
var AUTO_CONTINUE_MAX_ELAPSED_MS = 10 * 60 * 1e3;
|
|
1488
|
+
var AUTO_CONTINUE_NO_PROGRESS_LIMIT = 2;
|
|
1489
|
+
var AUTO_CONTINUE_PROMPT = "Continue the task from where you stopped. Do not summarize; keep working until the requested task is complete, you need clarification, or you hit a real blocker.";
|
|
1490
|
+
function normalizeVisibleText(text) {
|
|
1491
|
+
return text.replace(/\s+/g, " ").trim();
|
|
1492
|
+
}
|
|
1493
|
+
function looksLikeQuestion(text) {
|
|
1494
|
+
const normalized = normalizeVisibleText(text).toLowerCase();
|
|
1495
|
+
if (!normalized) return false;
|
|
1496
|
+
if (normalized.endsWith("?")) return true;
|
|
1497
|
+
return /\b(please confirm|can you confirm|should i|would you like|do you want|which option|choose|pick one|need your|need you to|what would you like)\b/.test(normalized);
|
|
1498
|
+
}
|
|
1499
|
+
function looksLikeBlocker(text) {
|
|
1500
|
+
const normalized = normalizeVisibleText(text).toLowerCase();
|
|
1501
|
+
if (!normalized) return false;
|
|
1502
|
+
return /\b(blocked|blocker|cannot proceed|can't proceed|unable to proceed|need clarification|need more information|permission denied|failed and needs|requires your|manual step|required from you)\b/.test(normalized);
|
|
1503
|
+
}
|
|
1504
|
+
function looksLikeFinalAnswer(text) {
|
|
1505
|
+
const normalized = normalizeVisibleText(text).toLowerCase();
|
|
1506
|
+
if (normalized.length < 40) return false;
|
|
1507
|
+
if (looksLikeQuestion(normalized) || looksLikeBlocker(normalized)) return false;
|
|
1508
|
+
return /\b(done|completed|fixed|implemented|verified|published|released|sent|delivered|updated)\b/.test(normalized) || /\b(checks?|tests?) passed\b/.test(normalized) || /\b(summary|what changed|verification)\b/.test(normalized);
|
|
1509
|
+
}
|
|
1510
|
+
function continuationSignature(snapshot) {
|
|
1511
|
+
const text = normalizeVisibleText(snapshot.text).slice(-500);
|
|
1512
|
+
return JSON.stringify({
|
|
1513
|
+
text,
|
|
1514
|
+
reasoning: snapshot.hadReasoning,
|
|
1515
|
+
tools: snapshot.hadToolActivity,
|
|
1516
|
+
proxy: snapshot.hadProxyActivity
|
|
1517
|
+
});
|
|
1518
|
+
}
|
|
1519
|
+
function shouldAutoContinueIncompleteTurn(state, snapshot) {
|
|
1520
|
+
if (state.enabled === false) return { continue: false, reason: "disabled" };
|
|
1521
|
+
if (snapshot.isError) return { continue: false, reason: "error" };
|
|
1522
|
+
if (state.aborted) return { continue: false, reason: "aborted" };
|
|
1523
|
+
if (state.attempts >= AUTO_CONTINUE_MAX_ATTEMPTS) {
|
|
1524
|
+
return { continue: false, reason: "max-attempts" };
|
|
1525
|
+
}
|
|
1526
|
+
const now = snapshot.now ?? Date.now();
|
|
1527
|
+
if (now - state.startedAt > AUTO_CONTINUE_MAX_ELAPSED_MS) {
|
|
1528
|
+
return { continue: false, reason: "max-elapsed" };
|
|
1529
|
+
}
|
|
1530
|
+
const text = normalizeVisibleText(snapshot.text);
|
|
1531
|
+
if (looksLikeQuestion(text)) return { continue: false, reason: "question" };
|
|
1532
|
+
if (looksLikeBlocker(text)) return { continue: false, reason: "blocker" };
|
|
1533
|
+
if (looksLikeFinalAnswer(text)) {
|
|
1534
|
+
return { continue: false, reason: "final-answer" };
|
|
1535
|
+
}
|
|
1536
|
+
const hadActivity = snapshot.hadReasoning || snapshot.hadToolActivity || snapshot.hadProxyActivity;
|
|
1537
|
+
if (!hadActivity) return { continue: false, reason: "no-activity" };
|
|
1538
|
+
const signature = continuationSignature(snapshot);
|
|
1539
|
+
const noProgress = signature === state.lastSignature;
|
|
1540
|
+
if (noProgress && state.noProgressCount + 1 >= AUTO_CONTINUE_NO_PROGRESS_LIMIT) {
|
|
1541
|
+
return { continue: false, reason: "no-progress" };
|
|
1542
|
+
}
|
|
1543
|
+
if (!text) {
|
|
1544
|
+
return { continue: true, reason: "activity-without-visible-answer" };
|
|
1545
|
+
}
|
|
1546
|
+
return { continue: true, reason: "non-final-progress" };
|
|
1547
|
+
}
|
|
1548
|
+
function makeAutoContinueMessage() {
|
|
1549
|
+
return JSON.stringify({
|
|
1550
|
+
type: "user",
|
|
1551
|
+
message: {
|
|
1552
|
+
role: "user",
|
|
1553
|
+
content: [{ type: "text", text: AUTO_CONTINUE_PROMPT }]
|
|
1554
|
+
}
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1486
1557
|
function readPromptFileIfPresent(path5) {
|
|
1487
1558
|
try {
|
|
1488
1559
|
const content = readFileSync2(path5, "utf8").trim();
|
|
@@ -1501,13 +1572,22 @@ function nearestWorkspaceAgentsPrompt(cwd) {
|
|
|
1501
1572
|
dir = parent;
|
|
1502
1573
|
}
|
|
1503
1574
|
}
|
|
1504
|
-
|
|
1575
|
+
var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
|
|
1576
|
+
|
|
1577
|
+
opencode requires the user to press "continue" after each turn ends. When a
|
|
1578
|
+
task has multiple steps, do them all in one turn \u2014 chain tool calls rather
|
|
1579
|
+
than pausing for user confirmation between subtasks. End the turn only
|
|
1580
|
+
when the task is done, you need clarification on intent, or you hit a real
|
|
1581
|
+
blocker. The user can interrupt or abort at any time; turn endings should
|
|
1582
|
+
mark meaningful checkpoints, not every completed substep.`;
|
|
1583
|
+
function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true) {
|
|
1505
1584
|
const parts = [];
|
|
1506
1585
|
const configRoot = process.env.XDG_CONFIG_HOME ?? join4(homedir2(), ".config");
|
|
1507
1586
|
const globalAgents = readPromptFileIfPresent(join4(configRoot, "opencode", "AGENTS.md"));
|
|
1508
1587
|
const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
|
|
1509
1588
|
if (globalAgents) parts.push(globalAgents);
|
|
1510
1589
|
if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
|
|
1590
|
+
if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
|
|
1511
1591
|
const content = parts.join("\n\n");
|
|
1512
1592
|
if (!content) return void 0;
|
|
1513
1593
|
const path5 = join4(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
|
|
@@ -1981,7 +2061,10 @@ var ClaudeCodeLanguageModel = class {
|
|
|
1981
2061
|
reasoningEffort
|
|
1982
2062
|
);
|
|
1983
2063
|
const runtimeStatus = await getRuntimeMcpStatus();
|
|
1984
|
-
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2064
|
+
const systemPromptFile = buildAppendedSystemPrompt(
|
|
2065
|
+
cwd,
|
|
2066
|
+
this.config.multiStepContinuation !== false
|
|
2067
|
+
);
|
|
1985
2068
|
const cliArgs = buildCliArgs({
|
|
1986
2069
|
sessionKey: sk,
|
|
1987
2070
|
skipPermissions: this.config.skipPermissions !== false,
|
|
@@ -2351,7 +2434,10 @@ ${plan}
|
|
|
2351
2434
|
runtimeStatus,
|
|
2352
2435
|
excludeServers
|
|
2353
2436
|
);
|
|
2354
|
-
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
2437
|
+
const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
|
|
2438
|
+
cwd,
|
|
2439
|
+
self.config.multiStepContinuation !== false
|
|
2440
|
+
);
|
|
2355
2441
|
const cliArgs = buildCliArgs({
|
|
2356
2442
|
sessionKey: sk,
|
|
2357
2443
|
skipPermissions,
|
|
@@ -2405,6 +2491,16 @@ ${plan}
|
|
|
2405
2491
|
let pendingProxyUnsubscribe = null;
|
|
2406
2492
|
let resultFallbackTimer = null;
|
|
2407
2493
|
let hasReceivedContent = false;
|
|
2494
|
+
let visibleTextSinceContinue = "";
|
|
2495
|
+
let hadReasoningSinceContinue = false;
|
|
2496
|
+
let hadToolActivitySinceContinue = false;
|
|
2497
|
+
let hadProxyActivitySinceContinue = false;
|
|
2498
|
+
const autoContinueState = {
|
|
2499
|
+
enabled: self.config.autoContinueIncompleteTurns,
|
|
2500
|
+
attempts: 0,
|
|
2501
|
+
startedAt: Date.now(),
|
|
2502
|
+
noProgressCount: 0
|
|
2503
|
+
};
|
|
2408
2504
|
const clearFallbackTimer = () => {
|
|
2409
2505
|
if (resultFallbackTimer) {
|
|
2410
2506
|
clearTimeout(resultFallbackTimer);
|
|
@@ -2477,6 +2573,24 @@ ${plan}
|
|
|
2477
2573
|
});
|
|
2478
2574
|
finishWithToolCalls(batch);
|
|
2479
2575
|
};
|
|
2576
|
+
const noteVisibleText = (text) => {
|
|
2577
|
+
visibleTextSinceContinue += text;
|
|
2578
|
+
};
|
|
2579
|
+
const noteReasoning = () => {
|
|
2580
|
+
hadReasoningSinceContinue = true;
|
|
2581
|
+
};
|
|
2582
|
+
const noteToolActivity = () => {
|
|
2583
|
+
hadToolActivitySinceContinue = true;
|
|
2584
|
+
};
|
|
2585
|
+
const noteProxyActivity = () => {
|
|
2586
|
+
hadProxyActivitySinceContinue = true;
|
|
2587
|
+
};
|
|
2588
|
+
const resetAutoContinueWindow = () => {
|
|
2589
|
+
visibleTextSinceContinue = "";
|
|
2590
|
+
hadReasoningSinceContinue = false;
|
|
2591
|
+
hadToolActivitySinceContinue = false;
|
|
2592
|
+
hadProxyActivitySinceContinue = false;
|
|
2593
|
+
};
|
|
2480
2594
|
let gotPartialEvents = false;
|
|
2481
2595
|
const lineHandler = (line) => {
|
|
2482
2596
|
if (!line.trim()) return;
|
|
@@ -2507,6 +2621,7 @@ ${plan}
|
|
|
2507
2621
|
const block = msg.content_block;
|
|
2508
2622
|
const idx = msg.index;
|
|
2509
2623
|
if (block.type === "thinking") {
|
|
2624
|
+
noteReasoning();
|
|
2510
2625
|
const reasoningId = generateId();
|
|
2511
2626
|
reasoningIds.set(idx, reasoningId);
|
|
2512
2627
|
controller.enqueue({
|
|
@@ -2524,10 +2639,12 @@ ${plan}
|
|
|
2524
2639
|
id: currentTextId,
|
|
2525
2640
|
delta: block.text
|
|
2526
2641
|
});
|
|
2642
|
+
noteVisibleText(block.text);
|
|
2527
2643
|
hasReceivedContent = true;
|
|
2528
2644
|
}
|
|
2529
2645
|
}
|
|
2530
2646
|
if (block.type === "tool_use" && block.id && block.name) {
|
|
2647
|
+
noteToolActivity();
|
|
2531
2648
|
toolCallMap.set(idx, {
|
|
2532
2649
|
id: block.id,
|
|
2533
2650
|
name: block.name,
|
|
@@ -2559,6 +2676,7 @@ ${plan}
|
|
|
2559
2676
|
const delta = msg.delta;
|
|
2560
2677
|
const idx = msg.index;
|
|
2561
2678
|
if (delta.type === "thinking_delta" && delta.thinking) {
|
|
2679
|
+
noteReasoning();
|
|
2562
2680
|
const reasoningId = reasoningIds.get(idx);
|
|
2563
2681
|
if (reasoningId) {
|
|
2564
2682
|
controller.enqueue({
|
|
@@ -2575,6 +2693,7 @@ ${plan}
|
|
|
2575
2693
|
id: currentTextId,
|
|
2576
2694
|
delta: delta.text
|
|
2577
2695
|
});
|
|
2696
|
+
noteVisibleText(delta.text);
|
|
2578
2697
|
hasReceivedContent = true;
|
|
2579
2698
|
}
|
|
2580
2699
|
if (delta.type === "input_json_delta" && delta.partial_json) {
|
|
@@ -2704,9 +2823,11 @@ ${plan}
|
|
|
2704
2823
|
delta: block.text
|
|
2705
2824
|
});
|
|
2706
2825
|
endTextBlock();
|
|
2826
|
+
noteVisibleText(block.text);
|
|
2707
2827
|
hasReceivedContent = true;
|
|
2708
2828
|
}
|
|
2709
2829
|
if (block.type === "thinking" && block.thinking) {
|
|
2830
|
+
noteReasoning();
|
|
2710
2831
|
const thinkingId = generateId();
|
|
2711
2832
|
controller.enqueue({
|
|
2712
2833
|
type: "reasoning-start",
|
|
@@ -2723,6 +2844,7 @@ ${plan}
|
|
|
2723
2844
|
});
|
|
2724
2845
|
}
|
|
2725
2846
|
if (block.type === "tool_use" && block.id && block.name) {
|
|
2847
|
+
noteToolActivity();
|
|
2726
2848
|
const parsedInput = block.input ?? {};
|
|
2727
2849
|
toolCallsById.set(block.id, {
|
|
2728
2850
|
id: block.id,
|
|
@@ -2836,6 +2958,7 @@ ${plan}
|
|
|
2836
2958
|
},
|
|
2837
2959
|
providerExecuted: true
|
|
2838
2960
|
});
|
|
2961
|
+
noteToolActivity();
|
|
2839
2962
|
log.info("tool result emitted", {
|
|
2840
2963
|
toolUseId: block.tool_use_id,
|
|
2841
2964
|
name: toolCall.name
|
|
@@ -2899,6 +3022,46 @@ ${plan}
|
|
|
2899
3022
|
)
|
|
2900
3023
|
);
|
|
2901
3024
|
}
|
|
3025
|
+
const autoDecision = shouldAutoContinueIncompleteTurn(
|
|
3026
|
+
autoContinueState,
|
|
3027
|
+
{
|
|
3028
|
+
text: visibleTextSinceContinue,
|
|
3029
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
3030
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
3031
|
+
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
3032
|
+
isError: msg.is_error
|
|
3033
|
+
}
|
|
3034
|
+
);
|
|
3035
|
+
if (autoDecision.continue) {
|
|
3036
|
+
const signature = continuationSignature({
|
|
3037
|
+
text: visibleTextSinceContinue,
|
|
3038
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
3039
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
3040
|
+
hadProxyActivity: hadProxyActivitySinceContinue,
|
|
3041
|
+
isError: msg.is_error
|
|
3042
|
+
});
|
|
3043
|
+
autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
|
|
3044
|
+
autoContinueState.lastSignature = signature;
|
|
3045
|
+
autoContinueState.attempts++;
|
|
3046
|
+
log.info("auto-continuing incomplete claude result", {
|
|
3047
|
+
sessionKey: sk,
|
|
3048
|
+
reason: autoDecision.reason,
|
|
3049
|
+
attempts: autoContinueState.attempts,
|
|
3050
|
+
textLength: visibleTextSinceContinue.length,
|
|
3051
|
+
hadReasoning: hadReasoningSinceContinue,
|
|
3052
|
+
hadToolActivity: hadToolActivitySinceContinue,
|
|
3053
|
+
hadProxyActivity: hadProxyActivitySinceContinue
|
|
3054
|
+
});
|
|
3055
|
+
turnCompleted = false;
|
|
3056
|
+
resetAutoContinueWindow();
|
|
3057
|
+
proc.stdin?.write(makeAutoContinueMessage() + "\n");
|
|
3058
|
+
return;
|
|
3059
|
+
}
|
|
3060
|
+
log.info("auto-continuation stopped", {
|
|
3061
|
+
sessionKey: sk,
|
|
3062
|
+
reason: autoDecision.reason,
|
|
3063
|
+
attempts: autoContinueState.attempts
|
|
3064
|
+
});
|
|
2902
3065
|
for (const [idx, reasoningId] of reasoningIds) {
|
|
2903
3066
|
if (reasoningStarted.get(idx)) {
|
|
2904
3067
|
controller.enqueue({
|
|
@@ -3021,6 +3184,8 @@ ${plan}
|
|
|
3021
3184
|
toolCallId: call.toolCallId,
|
|
3022
3185
|
toolName: call.toolName
|
|
3023
3186
|
});
|
|
3187
|
+
noteProxyActivity();
|
|
3188
|
+
noteToolActivity();
|
|
3024
3189
|
drainBuffer.push(call);
|
|
3025
3190
|
if (drainTimer) clearTimeout(drainTimer);
|
|
3026
3191
|
drainTimer = setTimeout(drainNow, DRAIN_QUIET_MS);
|
|
@@ -3028,6 +3193,7 @@ ${plan}
|
|
|
3028
3193
|
proc.on("error", procErrorHandler);
|
|
3029
3194
|
if (options.abortSignal) {
|
|
3030
3195
|
options.abortSignal.addEventListener("abort", () => {
|
|
3196
|
+
autoContinueState.aborted = true;
|
|
3031
3197
|
if (turnCompleted || controllerClosed) return;
|
|
3032
3198
|
if (!hasReceivedContent) {
|
|
3033
3199
|
log.info(
|
|
@@ -3540,7 +3706,9 @@ function createClaudeCode(settings = {}) {
|
|
|
3540
3706
|
proxyTools,
|
|
3541
3707
|
webSearch: settings.webSearch,
|
|
3542
3708
|
hotReloadMcp: settings.hotReloadMcp ?? true,
|
|
3543
|
-
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true
|
|
3709
|
+
proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
|
|
3710
|
+
multiStepContinuation: settings.multiStepContinuation ?? true,
|
|
3711
|
+
autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart"
|
|
3544
3712
|
});
|
|
3545
3713
|
};
|
|
3546
3714
|
const provider = function(modelId) {
|