@khalilgharbaoui/opencode-claude-code-plugin 0.4.2 → 0.4.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 CHANGED
@@ -171,6 +171,7 @@ 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. |
174
175
 
175
176
  ### Overriding model metadata
176
177
 
package/dist/index.d.ts CHANGED
@@ -115,6 +115,7 @@ interface ClaudeCodeConfig {
115
115
  webSearch?: WebSearchRouting;
116
116
  hotReloadMcp?: boolean;
117
117
  proxyOpencodeMcpTools?: boolean;
118
+ multiStepContinuation?: boolean;
118
119
  }
119
120
  type WebSearchRouting = "claude" | "disabled" | (string & {});
120
121
  interface ClaudeCodeProviderSettings {
@@ -207,6 +208,17 @@ interface ClaudeCodeProviderSettings {
207
208
  * an opencode round-trip).
208
209
  */
209
210
  proxyOpencodeMcpTools?: boolean;
211
+ /**
212
+ * Append a short system-prompt hint that nudges Claude to chain
213
+ * multiple tool calls within a single turn instead of pausing for user
214
+ * confirmation between subtasks. Each turn boundary in opencode
215
+ * requires the user to manually press "continue" to resume, so for
216
+ * multi-step tasks this option reduces friction. Defaults to `true`.
217
+ *
218
+ * Set to `false` if you prefer the un-nudged model behavior (Claude
219
+ * decides when to end the turn entirely on its own).
220
+ */
221
+ multiStepContinuation?: boolean;
210
222
  }
211
223
  type PermissionMode = "acceptEdits" | "auto" | "bypassPermissions" | "default" | "dontAsk" | "plan";
212
224
  type ControlRequestBehavior = "allow" | "deny";
package/dist/index.js CHANGED
@@ -577,6 +577,17 @@ function dotOpencodeDirs(cwd, worktree) {
577
577
  if (envDir && dirExists(envDir)) push(envDir);
578
578
  return dirs;
579
579
  }
580
+ function substituteEnvPlaceholders(source) {
581
+ const out = {};
582
+ for (const [k, v] of Object.entries(source)) {
583
+ if (typeof v !== "string") continue;
584
+ out[k] = v.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_match, name) => {
585
+ const resolved = process.env[name];
586
+ return typeof resolved === "string" ? resolved : "";
587
+ });
588
+ }
589
+ return out;
590
+ }
580
591
  function translateServer(name, spec) {
581
592
  if (spec.enabled === false) return null;
582
593
  const type = spec.type;
@@ -592,7 +603,9 @@ function translateServer(name, spec) {
592
603
  };
593
604
  if (cmd.length > 1) out.args = cmd.slice(1).map((s) => String(s));
594
605
  if (spec.environment && typeof spec.environment === "object") {
595
- out.env = spec.environment;
606
+ out.env = substituteEnvPlaceholders(
607
+ spec.environment
608
+ );
596
609
  }
597
610
  return out;
598
611
  }
@@ -606,7 +619,9 @@ function translateServer(name, spec) {
606
619
  url: spec.url
607
620
  };
608
621
  if (spec.headers && typeof spec.headers === "object") {
609
- out.headers = spec.headers;
622
+ out.headers = substituteEnvPlaceholders(
623
+ spec.headers
624
+ );
610
625
  }
611
626
  return out;
612
627
  }
@@ -1486,13 +1501,22 @@ function nearestWorkspaceAgentsPrompt(cwd) {
1486
1501
  dir = parent;
1487
1502
  }
1488
1503
  }
1489
- function buildAppendedSystemPrompt(cwd) {
1504
+ var MULTI_STEP_TASK_HINT = `## Continuing through multi-step tasks
1505
+
1506
+ opencode requires the user to press "continue" after each turn ends. When a
1507
+ task has multiple steps, do them all in one turn \u2014 chain tool calls rather
1508
+ than pausing for user confirmation between subtasks. End the turn only
1509
+ when the task is done, you need clarification on intent, or you hit a real
1510
+ blocker. The user can interrupt or abort at any time; turn endings should
1511
+ mark meaningful checkpoints, not every completed substep.`;
1512
+ function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true) {
1490
1513
  const parts = [];
1491
1514
  const configRoot = process.env.XDG_CONFIG_HOME ?? join4(homedir2(), ".config");
1492
1515
  const globalAgents = readPromptFileIfPresent(join4(configRoot, "opencode", "AGENTS.md"));
1493
1516
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
1494
1517
  if (globalAgents) parts.push(globalAgents);
1495
1518
  if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
1519
+ if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
1496
1520
  const content = parts.join("\n\n");
1497
1521
  if (!content) return void 0;
1498
1522
  const path5 = join4(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
@@ -1966,7 +1990,10 @@ var ClaudeCodeLanguageModel = class {
1966
1990
  reasoningEffort
1967
1991
  );
1968
1992
  const runtimeStatus = await getRuntimeMcpStatus();
1969
- const systemPromptFile = buildAppendedSystemPrompt(cwd);
1993
+ const systemPromptFile = buildAppendedSystemPrompt(
1994
+ cwd,
1995
+ this.config.multiStepContinuation !== false
1996
+ );
1970
1997
  const cliArgs = buildCliArgs({
1971
1998
  sessionKey: sk,
1972
1999
  skipPermissions: this.config.skipPermissions !== false,
@@ -2336,7 +2363,10 @@ ${plan}
2336
2363
  runtimeStatus,
2337
2364
  excludeServers
2338
2365
  );
2339
- const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(cwd);
2366
+ const systemPromptFile = activeProcess ? void 0 : buildAppendedSystemPrompt(
2367
+ cwd,
2368
+ self.config.multiStepContinuation !== false
2369
+ );
2340
2370
  const cliArgs = buildCliArgs({
2341
2371
  sessionKey: sk,
2342
2372
  skipPermissions,
@@ -2857,6 +2887,33 @@ ${plan}
2857
2887
  });
2858
2888
  turnCompleted = true;
2859
2889
  endTextBlock();
2890
+ if (drainBuffer.length > 0) {
2891
+ log.info(
2892
+ "draining pending proxy calls at turn-result boundary",
2893
+ {
2894
+ sessionKey: sk,
2895
+ count: drainBuffer.length
2896
+ }
2897
+ );
2898
+ drainNow();
2899
+ return;
2900
+ }
2901
+ const orphanPending = getPendingProxyCalls(sk);
2902
+ if (orphanPending.length > 0) {
2903
+ log.warn(
2904
+ "rejecting orphan pending proxy calls at turn-result boundary",
2905
+ {
2906
+ sessionKey: sk,
2907
+ count: orphanPending.length
2908
+ }
2909
+ );
2910
+ rejectAllPendingProxyCallsForSession(
2911
+ sk,
2912
+ new Error(
2913
+ "Claude CLI emitted result with pending proxy calls not in drain buffer"
2914
+ )
2915
+ );
2916
+ }
2860
2917
  for (const [idx, reasoningId] of reasoningIds) {
2861
2918
  if (reasoningStarted.get(idx)) {
2862
2919
  controller.enqueue({
@@ -3498,7 +3555,8 @@ function createClaudeCode(settings = {}) {
3498
3555
  proxyTools,
3499
3556
  webSearch: settings.webSearch,
3500
3557
  hotReloadMcp: settings.hotReloadMcp ?? true,
3501
- proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true
3558
+ proxyOpencodeMcpTools: settings.proxyOpencodeMcpTools ?? true,
3559
+ multiStepContinuation: settings.multiStepContinuation ?? true
3502
3560
  });
3503
3561
  };
3504
3562
  const provider = function(modelId) {