@khalilgharbaoui/opencode-claude-code-plugin 0.4.4 → 0.4.6

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 CHANGED
@@ -172,6 +172,7 @@ The account model IDs are internally suffixed, for example `claude-sonnet-4-6@wo
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
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. |
175
176
 
176
177
  ### Overriding model metadata
177
178
 
@@ -311,6 +312,7 @@ Set `permissionMode: "plan"` to forward `--permission-mode plan` to Claude. The
311
312
  ## Quirks worth knowing
312
313
 
313
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`.
314
316
  - **`AskUserQuestion`** from the CLI is converted into plain text content rather than forwarded as a tool call.
315
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.
316
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
@@ -116,6 +116,7 @@ interface ClaudeCodeConfig {
116
116
  hotReloadMcp?: boolean;
117
117
  proxyOpencodeMcpTools?: boolean;
118
118
  multiStepContinuation?: boolean;
119
+ autoContinueIncompleteTurns?: boolean | "smart";
119
120
  }
120
121
  type WebSearchRouting = "claude" | "disabled" | (string & {});
121
122
  interface ClaudeCodeProviderSettings {
@@ -219,6 +220,19 @@ interface ClaudeCodeProviderSettings {
219
220
  * decides when to end the turn entirely on its own).
220
221
  */
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";
222
236
  }
223
237
  type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
224
238
  type ControlRequestBehavior = "allow" | "deny";
package/dist/index.js CHANGED
@@ -2,7 +2,33 @@
2
2
  import { generateId } from "@ai-sdk/provider-utils";
3
3
 
4
4
  // src/logger.ts
5
+ import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { dirname, join } from "path";
5
8
  var DEBUG = process.env.DEBUG?.includes("opencode-claude-code") ?? false;
9
+ var LOG_DIR = process.env.OPENCODE_CLAUDE_CODE_LOG_DIR ?? join(homedir(), ".local", "share", "opencode-claude-code");
10
+ var LOG_FILE = join(LOG_DIR, "plugin.log");
11
+ var MAX_LOG_BYTES = 5 * 1024 * 1024;
12
+ var fileLoggingDisabled = false;
13
+ function rotateIfNeeded() {
14
+ try {
15
+ const stat = statSync(LOG_FILE);
16
+ if (stat.size > MAX_LOG_BYTES) {
17
+ renameSync(LOG_FILE, `${LOG_FILE}.1`);
18
+ }
19
+ } catch {
20
+ }
21
+ }
22
+ function writeToFile(line) {
23
+ if (fileLoggingDisabled) return;
24
+ try {
25
+ mkdirSync(dirname(LOG_FILE), { recursive: true });
26
+ rotateIfNeeded();
27
+ appendFileSync(LOG_FILE, line + "\n", "utf8");
28
+ } catch {
29
+ fileLoggingDisabled = true;
30
+ }
31
+ }
6
32
  function fmt(level, msg, data) {
7
33
  const ts = (/* @__PURE__ */ new Date()).toISOString();
8
34
  const base = `[${ts}] [opencode-claude-code] ${level}: ${msg}`;
@@ -11,21 +37,30 @@ function fmt(level, msg, data) {
11
37
  }
12
38
  return base;
13
39
  }
40
+ function emit(level, msg, data, alwaysStderr = false) {
41
+ const line = fmt(level, msg, data);
42
+ if (alwaysStderr || DEBUG) {
43
+ console.error(line);
44
+ }
45
+ writeToFile(line);
46
+ }
14
47
  var log = {
15
48
  info(msg, data) {
16
- if (DEBUG) console.error(fmt("INFO", msg, data));
49
+ if (DEBUG) emit("INFO", msg, data);
50
+ else writeToFile(fmt("INFO", msg, data));
17
51
  },
18
52
  notice(msg, data) {
19
- console.error(fmt("NOTICE", msg, data));
53
+ emit("NOTICE", msg, data, true);
20
54
  },
21
55
  warn(msg, data) {
22
- console.error(fmt("WARN", msg, data));
56
+ emit("WARN", msg, data, true);
23
57
  },
24
58
  error(msg, data) {
25
- console.error(fmt("ERROR", msg, data));
59
+ emit("ERROR", msg, data, true);
26
60
  },
27
61
  debug(msg, data) {
28
- if (DEBUG) console.error(fmt("DEBUG", msg, data));
62
+ if (DEBUG) emit("DEBUG", msg, data);
63
+ else writeToFile(fmt("DEBUG", msg, data));
29
64
  }
30
65
  };
31
66
 
@@ -1460,9 +1495,9 @@ function rejectAllPendingProxyCallsForSession(sessionKey2, error) {
1460
1495
  // src/claude-code-language-model.ts
1461
1496
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1462
1497
  import { unlink as unlink2 } from "fs/promises";
1463
- import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
1498
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
1464
1499
  import { randomUUID as randomUUID2 } from "crypto";
1465
- import { dirname as dirname2, join as join4 } from "path";
1500
+ import { dirname as dirname3, join as join5 } from "path";
1466
1501
  function hasNewUserContent(prompt) {
1467
1502
  for (let i = prompt.length - 1; i >= 0; i--) {
1468
1503
  const msg = prompt[i];
@@ -1483,6 +1518,78 @@ function hasNewUserContent(prompt) {
1483
1518
  }
1484
1519
  return false;
1485
1520
  }
1521
+ var AUTO_CONTINUE_MAX_ATTEMPTS = 8;
1522
+ var AUTO_CONTINUE_MAX_ELAPSED_MS = 10 * 60 * 1e3;
1523
+ var AUTO_CONTINUE_NO_PROGRESS_LIMIT = 2;
1524
+ 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.";
1525
+ function normalizeVisibleText(text) {
1526
+ return text.replace(/\s+/g, " ").trim();
1527
+ }
1528
+ function looksLikeQuestion(text) {
1529
+ const normalized = normalizeVisibleText(text).toLowerCase();
1530
+ if (!normalized) return false;
1531
+ if (normalized.endsWith("?")) return true;
1532
+ 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);
1533
+ }
1534
+ function looksLikeBlocker(text) {
1535
+ const normalized = normalizeVisibleText(text).toLowerCase();
1536
+ if (!normalized) return false;
1537
+ 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);
1538
+ }
1539
+ function looksLikeFinalAnswer(text) {
1540
+ const normalized = normalizeVisibleText(text).toLowerCase();
1541
+ if (normalized.length < 40) return false;
1542
+ if (looksLikeQuestion(normalized) || looksLikeBlocker(normalized)) return false;
1543
+ 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);
1544
+ }
1545
+ function continuationSignature(snapshot) {
1546
+ const text = normalizeVisibleText(snapshot.text).slice(-500);
1547
+ return JSON.stringify({
1548
+ text,
1549
+ reasoning: snapshot.hadReasoning,
1550
+ tools: snapshot.hadToolActivity,
1551
+ proxy: snapshot.hadProxyActivity
1552
+ });
1553
+ }
1554
+ function shouldAutoContinueIncompleteTurn(state, snapshot) {
1555
+ if (state.enabled === false) return { continue: false, reason: "disabled" };
1556
+ if (snapshot.isError) return { continue: false, reason: "error" };
1557
+ if (state.aborted) return { continue: false, reason: "aborted" };
1558
+ if (state.attempts >= AUTO_CONTINUE_MAX_ATTEMPTS) {
1559
+ return { continue: false, reason: "max-attempts" };
1560
+ }
1561
+ const now = snapshot.now ?? Date.now();
1562
+ if (now - state.startedAt > AUTO_CONTINUE_MAX_ELAPSED_MS) {
1563
+ return { continue: false, reason: "max-elapsed" };
1564
+ }
1565
+ const text = normalizeVisibleText(snapshot.text);
1566
+ const lastText = normalizeVisibleText(snapshot.lastVisibleText);
1567
+ if (looksLikeQuestion(text)) return { continue: false, reason: "question" };
1568
+ if (looksLikeBlocker(text)) return { continue: false, reason: "blocker" };
1569
+ if (looksLikeFinalAnswer(lastText)) {
1570
+ return { continue: false, reason: "final-answer" };
1571
+ }
1572
+ const hadActivity = snapshot.hadReasoning || snapshot.hadToolActivity || snapshot.hadProxyActivity;
1573
+ if (!hadActivity) return { continue: false, reason: "no-activity" };
1574
+ const signature = continuationSignature(snapshot);
1575
+ const noProgress = signature === state.lastSignature;
1576
+ if (noProgress && state.noProgressCount + 1 >= AUTO_CONTINUE_NO_PROGRESS_LIMIT) {
1577
+ return { continue: false, reason: "no-progress" };
1578
+ }
1579
+ if (!text) {
1580
+ return { continue: true, reason: "activity-without-visible-answer" };
1581
+ }
1582
+ return { continue: true, reason: "non-final-progress" };
1583
+ }
1584
+ function makeAutoContinueMessage() {
1585
+ return JSON.stringify({
1586
+ type: "user",
1587
+ message: {
1588
+ role: "user",
1589
+ content: [{ type: "text", text: AUTO_CONTINUE_PROMPT }]
1590
+ }
1591
+ });
1592
+ }
1486
1593
  function readPromptFileIfPresent(path5) {
1487
1594
  try {
1488
1595
  const content = readFileSync2(path5, "utf8").trim();
@@ -1494,9 +1601,9 @@ function readPromptFileIfPresent(path5) {
1494
1601
  function nearestWorkspaceAgentsPrompt(cwd) {
1495
1602
  let dir = cwd;
1496
1603
  while (true) {
1497
- const content = readPromptFileIfPresent(join4(dir, "AGENTS.md"));
1604
+ const content = readPromptFileIfPresent(join5(dir, "AGENTS.md"));
1498
1605
  if (content) return content;
1499
- const parent = dirname2(dir);
1606
+ const parent = dirname3(dir);
1500
1607
  if (parent === dir) return void 0;
1501
1608
  dir = parent;
1502
1609
  }
@@ -1511,15 +1618,15 @@ blocker. The user can interrupt or abort at any time; turn endings should
1511
1618
  mark meaningful checkpoints, not every completed substep.`;
1512
1619
  function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true) {
1513
1620
  const parts = [];
1514
- const configRoot = process.env.XDG_CONFIG_HOME ?? join4(homedir2(), ".config");
1515
- const globalAgents = readPromptFileIfPresent(join4(configRoot, "opencode", "AGENTS.md"));
1621
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
1622
+ const globalAgents = readPromptFileIfPresent(join5(configRoot, "opencode", "AGENTS.md"));
1516
1623
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
1517
1624
  if (globalAgents) parts.push(globalAgents);
1518
1625
  if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
1519
1626
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
1520
1627
  const content = parts.join("\n\n");
1521
1628
  if (!content) return void 0;
1522
- const path5 = join4(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
1629
+ const path5 = join5(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
1523
1630
  try {
1524
1631
  writeFileSync3(path5, content, "utf8");
1525
1632
  return path5;
@@ -2420,6 +2527,17 @@ ${plan}
2420
2527
  let pendingProxyUnsubscribe = null;
2421
2528
  let resultFallbackTimer = null;
2422
2529
  let hasReceivedContent = false;
2530
+ let visibleTextSinceContinue = "";
2531
+ let lastVisibleTextSinceContinue = "";
2532
+ let hadReasoningSinceContinue = false;
2533
+ let hadToolActivitySinceContinue = false;
2534
+ let hadProxyActivitySinceContinue = false;
2535
+ const autoContinueState = {
2536
+ enabled: self.config.autoContinueIncompleteTurns,
2537
+ attempts: 0,
2538
+ startedAt: Date.now(),
2539
+ noProgressCount: 0
2540
+ };
2423
2541
  const clearFallbackTimer = () => {
2424
2542
  if (resultFallbackTimer) {
2425
2543
  clearTimeout(resultFallbackTimer);
@@ -2492,6 +2610,29 @@ ${plan}
2492
2610
  });
2493
2611
  finishWithToolCalls(batch);
2494
2612
  };
2613
+ const noteVisibleText = (text) => {
2614
+ visibleTextSinceContinue += text;
2615
+ lastVisibleTextSinceContinue += text;
2616
+ };
2617
+ const resetLastVisibleTextBlock = () => {
2618
+ lastVisibleTextSinceContinue = "";
2619
+ };
2620
+ const noteReasoning = () => {
2621
+ hadReasoningSinceContinue = true;
2622
+ };
2623
+ const noteToolActivity = () => {
2624
+ hadToolActivitySinceContinue = true;
2625
+ };
2626
+ const noteProxyActivity = () => {
2627
+ hadProxyActivitySinceContinue = true;
2628
+ };
2629
+ const resetAutoContinueWindow = () => {
2630
+ visibleTextSinceContinue = "";
2631
+ lastVisibleTextSinceContinue = "";
2632
+ hadReasoningSinceContinue = false;
2633
+ hadToolActivitySinceContinue = false;
2634
+ hadProxyActivitySinceContinue = false;
2635
+ };
2495
2636
  let gotPartialEvents = false;
2496
2637
  const lineHandler = (line) => {
2497
2638
  if (!line.trim()) return;
@@ -2522,6 +2663,7 @@ ${plan}
2522
2663
  const block = msg.content_block;
2523
2664
  const idx = msg.index;
2524
2665
  if (block.type === "thinking") {
2666
+ noteReasoning();
2525
2667
  const reasoningId = generateId();
2526
2668
  reasoningIds.set(idx, reasoningId);
2527
2669
  controller.enqueue({
@@ -2532,6 +2674,7 @@ ${plan}
2532
2674
  }
2533
2675
  if (block.type === "text") {
2534
2676
  textBlockIndices.add(idx);
2677
+ resetLastVisibleTextBlock();
2535
2678
  if (block.text) {
2536
2679
  if (!currentTextId) startTextBlock();
2537
2680
  controller.enqueue({
@@ -2539,10 +2682,12 @@ ${plan}
2539
2682
  id: currentTextId,
2540
2683
  delta: block.text
2541
2684
  });
2685
+ noteVisibleText(block.text);
2542
2686
  hasReceivedContent = true;
2543
2687
  }
2544
2688
  }
2545
2689
  if (block.type === "tool_use" && block.id && block.name) {
2690
+ noteToolActivity();
2546
2691
  toolCallMap.set(idx, {
2547
2692
  id: block.id,
2548
2693
  name: block.name,
@@ -2574,6 +2719,7 @@ ${plan}
2574
2719
  const delta = msg.delta;
2575
2720
  const idx = msg.index;
2576
2721
  if (delta.type === "thinking_delta" && delta.thinking) {
2722
+ noteReasoning();
2577
2723
  const reasoningId = reasoningIds.get(idx);
2578
2724
  if (reasoningId) {
2579
2725
  controller.enqueue({
@@ -2590,6 +2736,7 @@ ${plan}
2590
2736
  id: currentTextId,
2591
2737
  delta: delta.text
2592
2738
  });
2739
+ noteVisibleText(delta.text);
2593
2740
  hasReceivedContent = true;
2594
2741
  }
2595
2742
  if (delta.type === "input_json_delta" && delta.partial_json) {
@@ -2712,6 +2859,7 @@ ${plan}
2712
2859
  }
2713
2860
  for (const block of msg.message.content) {
2714
2861
  if (block.type === "text" && block.text) {
2862
+ resetLastVisibleTextBlock();
2715
2863
  const blockId = startTextBlock();
2716
2864
  controller.enqueue({
2717
2865
  type: "text-delta",
@@ -2719,9 +2867,11 @@ ${plan}
2719
2867
  delta: block.text
2720
2868
  });
2721
2869
  endTextBlock();
2870
+ noteVisibleText(block.text);
2722
2871
  hasReceivedContent = true;
2723
2872
  }
2724
2873
  if (block.type === "thinking" && block.thinking) {
2874
+ noteReasoning();
2725
2875
  const thinkingId = generateId();
2726
2876
  controller.enqueue({
2727
2877
  type: "reasoning-start",
@@ -2738,6 +2888,7 @@ ${plan}
2738
2888
  });
2739
2889
  }
2740
2890
  if (block.type === "tool_use" && block.id && block.name) {
2891
+ noteToolActivity();
2741
2892
  const parsedInput = block.input ?? {};
2742
2893
  toolCallsById.set(block.id, {
2743
2894
  id: block.id,
@@ -2851,6 +3002,7 @@ ${plan}
2851
3002
  },
2852
3003
  providerExecuted: true
2853
3004
  });
3005
+ noteToolActivity();
2854
3006
  log.info("tool result emitted", {
2855
3007
  toolUseId: block.tool_use_id,
2856
3008
  name: toolCall.name
@@ -2914,6 +3066,54 @@ ${plan}
2914
3066
  )
2915
3067
  );
2916
3068
  }
3069
+ const autoDecision = shouldAutoContinueIncompleteTurn(
3070
+ autoContinueState,
3071
+ {
3072
+ text: visibleTextSinceContinue,
3073
+ lastVisibleText: lastVisibleTextSinceContinue,
3074
+ hadReasoning: hadReasoningSinceContinue,
3075
+ hadToolActivity: hadToolActivitySinceContinue,
3076
+ hadProxyActivity: hadProxyActivitySinceContinue,
3077
+ isError: msg.is_error
3078
+ }
3079
+ );
3080
+ if (autoDecision.continue) {
3081
+ const signature = continuationSignature({
3082
+ text: visibleTextSinceContinue,
3083
+ lastVisibleText: lastVisibleTextSinceContinue,
3084
+ hadReasoning: hadReasoningSinceContinue,
3085
+ hadToolActivity: hadToolActivitySinceContinue,
3086
+ hadProxyActivity: hadProxyActivitySinceContinue,
3087
+ isError: msg.is_error
3088
+ });
3089
+ autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
3090
+ autoContinueState.lastSignature = signature;
3091
+ autoContinueState.attempts++;
3092
+ log.notice("auto-continuing incomplete claude result", {
3093
+ sessionKey: sk,
3094
+ reason: autoDecision.reason,
3095
+ attempts: autoContinueState.attempts,
3096
+ textLength: visibleTextSinceContinue.length,
3097
+ lastTextLength: lastVisibleTextSinceContinue.length,
3098
+ hadReasoning: hadReasoningSinceContinue,
3099
+ hadToolActivity: hadToolActivitySinceContinue,
3100
+ hadProxyActivity: hadProxyActivitySinceContinue
3101
+ });
3102
+ turnCompleted = false;
3103
+ resetAutoContinueWindow();
3104
+ proc.stdin?.write(makeAutoContinueMessage() + "\n");
3105
+ return;
3106
+ }
3107
+ log.notice("auto-continuation stopped", {
3108
+ sessionKey: sk,
3109
+ reason: autoDecision.reason,
3110
+ attempts: autoContinueState.attempts,
3111
+ textLength: visibleTextSinceContinue.length,
3112
+ lastTextLength: lastVisibleTextSinceContinue.length,
3113
+ hadReasoning: hadReasoningSinceContinue,
3114
+ hadToolActivity: hadToolActivitySinceContinue,
3115
+ hadProxyActivity: hadProxyActivitySinceContinue
3116
+ });
2917
3117
  for (const [idx, reasoningId] of reasoningIds) {
2918
3118
  if (reasoningStarted.get(idx)) {
2919
3119
  controller.enqueue({
@@ -3036,6 +3236,8 @@ ${plan}
3036
3236
  toolCallId: call.toolCallId,
3037
3237
  toolName: call.toolName
3038
3238
  });
3239
+ noteProxyActivity();
3240
+ noteToolActivity();
3039
3241
  drainBuffer.push(call);
3040
3242
  if (drainTimer) clearTimeout(drainTimer);
3041
3243
  drainTimer = setTimeout(drainNow, DRAIN_QUIET_MS);
@@ -3043,6 +3245,7 @@ ${plan}
3043
3245
  proc.on("error", procErrorHandler);
3044
3246
  if (options.abortSignal) {
3045
3247
  options.abortSignal.addEventListener("abort", () => {
3248
+ autoContinueState.aborted = true;
3046
3249
  if (turnCompleted || controllerClosed) return;
3047
3250
  if (!hasReceivedContent) {
3048
3251
  log.info(
@@ -3417,8 +3620,8 @@ import {
3417
3620
  rmSync as rmSync2,
3418
3621
  writeFileSync as writeFileSync4
3419
3622
  } from "fs";
3420
- import { homedir as homedir3 } from "os";
3421
- import { join as join5, resolve as resolve2 } from "path";
3623
+ import { homedir as homedir4 } from "os";
3624
+ import { join as join6, resolve as resolve2 } from "path";
3422
3625
  import { fileURLToPath } from "url";
3423
3626
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
3424
3627
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
@@ -3426,14 +3629,14 @@ var alreadyRan = false;
3426
3629
  function candidateCacheRoots() {
3427
3630
  const xdg = process.env.XDG_CACHE_HOME;
3428
3631
  return [
3429
- xdg ? join5(xdg, "opencode") : null,
3430
- join5(homedir3(), ".cache", "opencode"),
3431
- join5(homedir3(), "Library", "Caches", "opencode")
3632
+ xdg ? join6(xdg, "opencode") : null,
3633
+ join6(homedir4(), ".cache", "opencode"),
3634
+ join6(homedir4(), "Library", "Caches", "opencode")
3432
3635
  ].filter((p) => Boolean(p));
3433
3636
  }
3434
3637
  function userOpencodeJsonPath() {
3435
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
3436
- return join5(xdgConfig, "opencode", "opencode.json");
3638
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join6(homedir4(), ".config");
3639
+ return join6(xdgConfig, "opencode", "opencode.json");
3437
3640
  }
3438
3641
  function userIntendsToUseUnscoped() {
3439
3642
  const cfg = userOpencodeJsonPath();
@@ -3476,7 +3679,7 @@ function cleanupStaleUnscopedInstall() {
3476
3679
  }
3477
3680
  function cleanupOne(cacheRoot, ourDir) {
3478
3681
  if (!existsSync3(cacheRoot)) return;
3479
- const stalePath = join5(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
3682
+ const stalePath = join6(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
3480
3683
  if (!existsSync3(stalePath)) return;
3481
3684
  let realStalePath = stalePath;
3482
3685
  try {
@@ -3484,7 +3687,7 @@ function cleanupOne(cacheRoot, ourDir) {
3484
3687
  } catch {
3485
3688
  }
3486
3689
  if (ourDir && realStalePath === ourDir) return;
3487
- const pkgJsonPath = join5(stalePath, "package.json");
3690
+ const pkgJsonPath = join6(stalePath, "package.json");
3488
3691
  if (!existsSync3(pkgJsonPath)) return;
3489
3692
  let pkg = {};
3490
3693
  try {
@@ -3504,7 +3707,7 @@ function cleanupOne(cacheRoot, ourDir) {
3504
3707
  });
3505
3708
  return;
3506
3709
  }
3507
- const cachePkgJson = join5(cacheRoot, "package.json");
3710
+ const cachePkgJson = join6(cacheRoot, "package.json");
3508
3711
  if (!existsSync3(cachePkgJson)) return;
3509
3712
  try {
3510
3713
  const cfg = JSON.parse(readFileSync3(cachePkgJson, "utf8"));
@@ -3556,7 +3759,8 @@ function createClaudeCode(settings = {}) {
3556
3759
  webSearch: settings.webSearch,
3557
3760
  hotReloadMcp: settings.hotReloadMcp ?? true,
3558
3761
  proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
3559
- multiStepContinuation: settings.multiStepContinuation ?? true
3762
+ multiStepContinuation: settings.multiStepContinuation ?? true,
3763
+ autoContinueIncompleteTurns: settings.autoContinueIncompleteTurns ?? "smart"
3560
3764
  });
3561
3765
  };
3562
3766
  const provider = function(modelId) {