@khalilgharbaoui/opencode-claude-code-plugin 0.36.4 → 0.36.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 CHANGED
@@ -59,6 +59,7 @@ Nothing in the picker? [Troubleshooting](./docs/troubleshooting/symptoms.md) is
59
59
  | **Subagents: your account, their model** | `forceModel`, `reasoningEffort` and `cacheTtl` in an agent file, inheriting the caller's account. `task_batch` runs several subagents at once. [Subagents](./docs/configuration/subagents.md) |
60
60
  | **18 models, reasoning variants, fast mode** | Haiku 4.5 through Opus 5.5, Fable and Mythos, each with a `(N×)` list-price suffix, `low` to `max` effort variants, and a fallback chain for a model this account cannot run today. [Models](./docs/models.md) |
61
61
  | **`/btw` and `/claude-code-doctor`** | Side questions on the live process, and a health report whose `bundle` form is redacted by allowlist so it is safe to paste into a public issue. [`/btw`](./docs/guides/btw.md) · [Doctor](./docs/guides/doctor.md) |
62
+ | **Ships its own setup skill** | Ask Claude to configure it. The bundled `claude-code-plugin` skill knows every option, env var, model id and troubleshooting rule, is staged into Claude Code on every spawn, and tests keep it in step with the code. [Skills](./docs/configuration/skills.md) |
62
63
  | **Read-only preset, plan mode** | `"permissionPreset": "read-only"` holds at the CLI, the proxy and the permission layer at once. [Permissions](./docs/configuration/permissions.md) |
63
64
 
64
65
  ## How this compares
@@ -102,12 +103,12 @@ This README used to hold all of the above. Release notes and bookmarks point at
102
103
 
103
104
  ## Credits
104
105
 
105
- This plugin absorbs work from its forks directly, cherry-picked with the original authorship preserved or reimplemented with the author named in the commit. The people behind the features you are using, and what each built, are on the [Credits](./docs/credits.md) page; `git log --author` on this repo shows the preserved authorship.
106
+ Made and maintained by [Khalil Gharbaoui (@khalilgharbaoui)](https://github.com/khalilgharbaoui). This plugin absorbs work from its forks directly, cherry-picked with the original authorship preserved or reimplemented with the author named in the commit. The people behind the features you are using, and what each built, are on the [Credits](./docs/credits.md) page; `git log --author` on this repo shows the preserved authorship.
106
107
 
107
- Free and MIT-licensed. If the plugin saves you time, you can buy its maintainer a coffee:
108
+ Free and MIT-licensed. If the plugin saves you time, you can buy [its maintainer](https://github.com/khalilgharbaoui) a coffee:
108
109
 
109
110
  <a href="https://www.buymeacoffee.com/khalilgharbaoui"><img src="site/public/buy-me-a-coffee.png" alt="Buy me a coffee" width="214" height="60"></a>
110
111
 
111
112
  ## License
112
113
 
113
- MIT. See [LICENSE](./LICENSE). The first version was written by Émilien ([@unixfox](https://github.com/unixfox)); everything since by Khalil Gharbaoui and the people in [Credits](./docs/credits.md).
114
+ MIT. See [LICENSE](./LICENSE). The first version was written by Émilien ([@unixfox](https://github.com/unixfox)); everything since by [Khalil Gharbaoui](https://github.com/khalilgharbaoui) and the people in [Credits](./docs/credits.md).
package/dist/index.js CHANGED
@@ -808,6 +808,13 @@ function formatSilentTurnNote(hadReasoning) {
808
808
  const what = hadReasoning ? "Claude Code finished the turn after thinking but never wrote an answer" : "Claude Code finished the turn without writing anything or calling a tool";
809
809
  return `
810
810
  ${SILENT_TURN_MARKER} ${what}, so there is no reply above. Nothing failed and nothing is pending: send the message again, or rephrase it.
811
+ `;
812
+ }
813
+ var UNATTENDED_REPLAY_MARKER = "\u258C **between turns:**";
814
+ function formatUnattendedReplayNote(dropped) {
815
+ const lost = dropped > 0 ? ` ${dropped} earlier line${dropped === 1 ? "" : "s"} were dropped.` : "";
816
+ return `${UNATTENDED_REPLAY_MARKER} The Claude Code CLI wrote this after the previous turn had already finished, so it is not an answer to the message above.${lost}
817
+
811
818
  `;
812
819
  }
813
820
  var UNRECOGNIZED_MODEL_STDERR_MARKER = "[claude-code:unrecognized_model]";
@@ -7286,7 +7293,8 @@ var PLUGIN_NOTE_MARKERS = [
7286
7293
  SILENT_TURN_MARKER,
7287
7294
  FAILOVER_MARKER,
7288
7295
  ACCOUNT_BLOCK_MARKER,
7289
- MODEL_FALLBACK_MARKER
7296
+ MODEL_FALLBACK_MARKER,
7297
+ UNATTENDED_REPLAY_MARKER
7290
7298
  ];
7291
7299
  function isPluginNote(part) {
7292
7300
  if (!part || part.type !== "text" || typeof part.text !== "string") return false;
@@ -9688,6 +9696,8 @@ function createTurnState(init) {
9688
9696
  toolCallMap: /* @__PURE__ */ new Map(),
9689
9697
  skipResultForIds: /* @__PURE__ */ new Set(),
9690
9698
  toolCallsById: /* @__PURE__ */ new Map(),
9699
+ toolCallEmittedIds: /* @__PURE__ */ new Set(),
9700
+ toolCallAnsweredIds: /* @__PURE__ */ new Set(),
9691
9701
  resultMeta: {},
9692
9702
  lastCallUsage: void 0,
9693
9703
  resultFailure: void 0,
@@ -9704,6 +9714,23 @@ function createTurnState(init) {
9704
9714
  };
9705
9715
  return state;
9706
9716
  }
9717
+ function cliToolCallInputJson(state, id) {
9718
+ for (const entry of state.toolCallMap.values()) {
9719
+ if (entry.id === id) return entry.inputJson || "{}";
9720
+ }
9721
+ return "{}";
9722
+ }
9723
+ function emitCliToolCall(state, id, name, inputJson, executed) {
9724
+ if (state.toolCallEmittedIds.has(id)) return;
9725
+ state.toolCallEmittedIds.add(id);
9726
+ state.controller.enqueue({
9727
+ type: "tool-call",
9728
+ toolCallId: id,
9729
+ toolName: name,
9730
+ input: inputJson,
9731
+ providerExecuted: executed
9732
+ });
9733
+ }
9707
9734
 
9708
9735
  // src/turn-abort.ts
9709
9736
  function watchTurnAbort(signal) {
@@ -10113,9 +10140,46 @@ function deliverPendingCompletions(state, force = false) {
10113
10140
  armStartWatchdog(state);
10114
10141
  return true;
10115
10142
  }
10143
+ var CLI_RESULT_DEFERRED_OUTPUT = "Claude Code ran this tool itself and kept its output in its own context. This step ended on a tool call opencode has to run, so the output was not available to show here.";
10144
+ function closeDeferredCliToolResults(state) {
10145
+ const answer = (id, name, stage) => {
10146
+ emitCliToolCall(state, id, name, cliToolCallInputJson(state, id), true);
10147
+ state.controller.enqueue({
10148
+ type: "tool-result",
10149
+ toolCallId: id,
10150
+ toolName: name,
10151
+ result: {
10152
+ output: CLI_RESULT_DEFERRED_OUTPUT,
10153
+ title: name,
10154
+ metadata: {}
10155
+ },
10156
+ providerExecuted: true
10157
+ });
10158
+ state.toolCallsById.delete(id);
10159
+ state.toolCallAnsweredIds.add(id);
10160
+ log.info("closing a CLI-executed tool call whose result this step cannot wait for", {
10161
+ sessionKey: state.sessionKey,
10162
+ toolUseId: id,
10163
+ name,
10164
+ stage
10165
+ });
10166
+ };
10167
+ for (const [id, toolCall] of [...state.toolCallsById]) {
10168
+ if (state.skipResultForIds.has(id)) continue;
10169
+ answer(id, toolCall.name, "registered");
10170
+ }
10171
+ for (const entry of state.toolCallMap.values()) {
10172
+ if (!entry.started || !entry.mappedName) continue;
10173
+ if (state.skipResultForIds.has(entry.id)) continue;
10174
+ if (state.toolCallAnsweredIds.has(entry.id)) continue;
10175
+ if (state.toolCallEmittedIds.has(entry.id)) continue;
10176
+ answer(entry.id, entry.mappedName, "open-block");
10177
+ }
10178
+ }
10116
10179
  function finishWithToolCalls(state, calls) {
10117
10180
  if (state.controllerClosed) return;
10118
10181
  if (calls.length === 0) return;
10182
+ closeDeferredCliToolResults(state);
10119
10183
  const enqueueToolCall = (toolCallId, toolName, input) => {
10120
10184
  state.controller.enqueue({
10121
10185
  type: "tool-input-start",
@@ -10321,6 +10385,26 @@ var KNOWN_DELTA_TYPES = /* @__PURE__ */ new Set([
10321
10385
  "signature_delta"
10322
10386
  ]);
10323
10387
  var PROXY_RESULT_BOUNDARY_GRACE_MS = 250;
10388
+ function adoptOpenCliToolCall(state, ctx, id) {
10389
+ for (const entry of state.toolCallMap.values()) {
10390
+ if (entry.id !== id || !entry.started || !entry.mappedName) continue;
10391
+ let parsedInput = {};
10392
+ try {
10393
+ parsedInput = JSON.parse(entry.inputJson || "{}");
10394
+ } catch {
10395
+ }
10396
+ const { input: mappedInput, executed } = mapTool(entry.name, parsedInput, {
10397
+ webSearch: ctx.config.webSearch,
10398
+ sessionId: getClaudeSessionId(state.sessionKey),
10399
+ toolUseId: entry.id
10400
+ });
10401
+ const registration = { id: entry.id, name: entry.mappedName, input: parsedInput };
10402
+ state.toolCallsById.set(entry.id, registration);
10403
+ emitCliToolCall(state, entry.id, entry.mappedName, JSON.stringify(mappedInput), executed);
10404
+ return registration;
10405
+ }
10406
+ return void 0;
10407
+ }
10324
10408
  function createLineHandler(state, ctx) {
10325
10409
  return (line) => {
10326
10410
  if (!line.trim()) return;
@@ -10449,6 +10533,7 @@ function createLineHandler(state, ctx) {
10449
10533
  );
10450
10534
  if (!skip) {
10451
10535
  entry.started = true;
10536
+ entry.mappedName = mappedName;
10452
10537
  state.controller.enqueue({
10453
10538
  type: "tool-input-start",
10454
10539
  id: block2.id,
@@ -10606,20 +10691,14 @@ ${plan}
10606
10691
  sessionId: getClaudeSessionId(state.sessionKey),
10607
10692
  toolUseId: tc.id
10608
10693
  });
10609
- if (!skip) {
10694
+ if (!skip && !state.toolCallAnsweredIds.has(tc.id)) {
10610
10695
  state.toolCallsById.set(tc.id, {
10611
10696
  id: tc.id,
10612
10697
  name: mappedName,
10613
10698
  input: parsedInput
10614
10699
  });
10615
10700
  if (!executed) state.skipResultForIds.add(tc.id);
10616
- state.controller.enqueue({
10617
- type: "tool-call",
10618
- toolCallId: tc.id,
10619
- toolName: mappedName,
10620
- input: JSON.stringify(mappedInput),
10621
- providerExecuted: executed
10622
- });
10701
+ emitCliToolCall(state, tc.id, mappedName, JSON.stringify(mappedInput), executed);
10623
10702
  }
10624
10703
  log.info("tool call complete", {
10625
10704
  name: tc.name,
@@ -10805,13 +10884,7 @@ ${plan}
10805
10884
  toolName: mappedName,
10806
10885
  providerExecuted: executed
10807
10886
  });
10808
- state.controller.enqueue({
10809
- type: "tool-call",
10810
- toolCallId: block2.id,
10811
- toolName: mappedName,
10812
- input: JSON.stringify(mappedInput),
10813
- providerExecuted: executed
10814
- });
10887
+ emitCliToolCall(state, block2.id, mappedName, JSON.stringify(mappedInput), executed);
10815
10888
  }
10816
10889
  log.info("tool_use from assistant message", {
10817
10890
  name: block2.name,
@@ -10877,7 +10950,7 @@ ${plan}
10877
10950
  noteToolActivity(state);
10878
10951
  }
10879
10952
  }
10880
- const toolCall = state.toolCallsById.get(block2.tool_use_id);
10953
+ const toolCall = state.toolCallsById.get(block2.tool_use_id) ?? adoptOpenCliToolCall(state, ctx, block2.tool_use_id);
10881
10954
  if (toolCall) {
10882
10955
  const isError = block2.is_error === true;
10883
10956
  state.controller.enqueue({
@@ -10899,6 +10972,7 @@ ${plan}
10899
10972
  isError
10900
10973
  });
10901
10974
  state.toolCallsById.delete(block2.tool_use_id);
10975
+ state.toolCallAnsweredIds.add(block2.tool_use_id);
10902
10976
  }
10903
10977
  }
10904
10978
  }
@@ -12561,16 +12635,12 @@ var ClaudeCodeLanguageModel = class {
12561
12635
  });
12562
12636
  let partialText = false;
12563
12637
  {
12564
- if (unattended.dropped > 0) {
12565
- const id = state.startTextBlock();
12566
- controller.enqueue({
12567
- type: "text-delta",
12568
- id,
12569
- delta: `> _${unattended.dropped} lines of output emitted between turns were dropped._
12570
-
12571
- `
12572
- });
12573
- }
12638
+ const replayId = state.startTextBlock();
12639
+ controller.enqueue({
12640
+ type: "text-delta",
12641
+ id: replayId,
12642
+ delta: formatUnattendedReplayNote(unattended.dropped)
12643
+ });
12574
12644
  for (const line of unattended.lines) {
12575
12645
  try {
12576
12646
  const outer = JSON.parse(line);