cursor-opencode-provider 0.2.4 → 0.2.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.
@@ -1,3 +1,6 @@
1
+ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
1
4
  /** Cursor agent.v1 TimeoutBehavior enum values. */
2
5
  export const CURSOR_TIMEOUT_CANCEL = 1;
3
6
  export const CURSOR_TIMEOUT_BACKGROUND = 2;
@@ -7,16 +10,24 @@ const POLL_INTERVAL_MS = 100;
7
10
  const BACKGROUND_MARKER = "__CURSOR_SHELL_BACKGROUND__";
8
11
  const EXIT_MARKER = "__CURSOR_SHELL_EXIT__";
9
12
  const TIMEOUT_MARKER = "__CURSOR_SHELL_TIMEOUT__";
13
+ /** Private marker for Cursor `background_shell_spawn_args` detach wrappers. */
14
+ export const BACKGROUND_SHELL_MARKER = "__CURSOR_BACKGROUND_SHELL__";
10
15
  const policies = new Map();
11
16
  const outcomes = new Map();
12
- function remember(map, key, value) {
17
+ /** callIDs that need shell.env injectors (so args.command can stay display-original). */
18
+ const pendingEnvWraps = new Set();
19
+ const activeEnvWraps = new Map();
20
+ function remember(map, key, value, onEvict) {
13
21
  map.delete(key);
14
22
  map.set(key, value);
15
23
  while (map.size > MAX_TRACKED_SHELL_CALLS) {
16
24
  const oldest = map.keys().next().value;
17
25
  if (!oldest)
18
26
  break;
27
+ const evicted = map.get(oldest);
19
28
  map.delete(oldest);
29
+ if (evicted !== undefined && onEvict)
30
+ onEvict(evicted);
20
31
  }
21
32
  }
22
33
  function finiteNonNegative(value) {
@@ -26,7 +37,18 @@ function finiteNonNegative(value) {
26
37
  return Math.floor(n);
27
38
  }
28
39
  export function shellPolicyFromMetadata(metadata) {
29
- if (!metadata || metadata.shell_stream !== true)
40
+ if (!metadata)
41
+ return undefined;
42
+ if (metadata.background_shell_spawn === true) {
43
+ return {
44
+ command: typeof metadata.command === "string" ? metadata.command : "",
45
+ workingDirectory: typeof metadata.working_directory === "string" ? metadata.working_directory : "",
46
+ timeoutMs: 0,
47
+ timeoutBehavior: 0,
48
+ backgroundSpawn: true,
49
+ };
50
+ }
51
+ if (metadata.shell_stream !== true)
30
52
  return undefined;
31
53
  const timeoutMs = finiteNonNegative(metadata.timeout_ms) ?? 30_000;
32
54
  const timeoutBehavior = finiteNonNegative(metadata.timeout_behavior) ?? 0;
@@ -50,9 +72,16 @@ function shellQuote(value) {
50
72
  return `'${value.replace(/'/g, `'\\''`)}'`;
51
73
  }
52
74
  /**
75
+ * F11 / soft-background helper.
76
+ *
53
77
  * Run a Cursor soft-background command for its foreground window, then leave
54
- * it detached if still alive. The sentinel is removed by the after hook before
55
- * OpenCode stores/renders the result.
78
+ * it detached (`nohup`) if still alive. The sentinel is removed by the after
79
+ * hook before OpenCode stores/renders the result.
80
+ *
81
+ * This approximates Cursor's TIMEOUT_BACKGROUND semantics through OpenCode's
82
+ * foreground-only bash tool. Residual: after OpenCode returns, the child (and
83
+ * optional hard-timeout watchdog) may still be running; this provider does not
84
+ * reap leftover processes — cleanup is left to the user / OS.
56
85
  */
57
86
  export function buildSoftBackgroundCommand(policy) {
58
87
  const polls = Math.ceil(policy.timeoutMs / POLL_INTERVAL_MS);
@@ -70,15 +99,59 @@ export function buildSoftBackgroundCommand(policy) {
70
99
  else {
71
100
  lines.push('cursor_shell_status=""', 'cursor_shell_watchdog_pid=""');
72
101
  }
73
- lines.push("cursor_shell_poll=0", `while [ "$cursor_shell_poll" -lt ${polls} ] && kill -0 "$cursor_shell_pid" 2>/dev/null; do`, ` sleep ${POLL_INTERVAL_MS / 1000}`, " cursor_shell_poll=$((cursor_shell_poll + 1))", "done", 'if kill -0 "$cursor_shell_pid" 2>/dev/null; then', ' cat "$cursor_shell_log"', ` printf '\n${BACKGROUND_MARKER}%s:%s\n' "$cursor_shell_pid" "$cursor_shell_log"`, " exit 0", "fi", 'wait "$cursor_shell_pid"', "cursor_shell_code=$?", 'cat "$cursor_shell_log"', 'if [ -n "$cursor_shell_status" ] && [ "$(cat "$cursor_shell_status" 2>/dev/null)" = timeout ]; then', ` printf '\n${TIMEOUT_MARKER}%s\n' ${policy.hardTimeoutMs ?? policy.timeoutMs}`, "else", ' if [ -n "$cursor_shell_watchdog_pid" ]; then kill "$cursor_shell_watchdog_pid" 2>/dev/null || true; fi', ` printf '\n${EXIT_MARKER}%s\n' "$cursor_shell_code"`, "fi", 'rm -f -- "$cursor_shell_log"', 'if [ -n "$cursor_shell_status" ]; then rm -f -- "$cursor_shell_status"; fi');
102
+ lines.push(
103
+ // Avoid interactive job-control noise ("Terminated: 15 …") when we later
104
+ // reap the watchdog; that text can otherwise land after our private marker
105
+ // and leak into OpenCode's bash UI.
106
+ "set +m 2>/dev/null || true", 'if [ -n "$cursor_shell_watchdog_pid" ]; then disown "$cursor_shell_watchdog_pid" 2>/dev/null || true; fi', 'disown "$cursor_shell_pid" 2>/dev/null || true', "cursor_shell_poll=0", `while [ "$cursor_shell_poll" -lt ${polls} ] && kill -0 "$cursor_shell_pid" 2>/dev/null; do`, ` sleep ${POLL_INTERVAL_MS / 1000}`, " cursor_shell_poll=$((cursor_shell_poll + 1))", "done", 'if kill -0 "$cursor_shell_pid" 2>/dev/null; then', ' cat "$cursor_shell_log"', ` printf '\n${BACKGROUND_MARKER}%s:%s\n' "$cursor_shell_pid" "$cursor_shell_log"`, " exit 0", "fi", 'wait "$cursor_shell_pid" 2>/dev/null', "cursor_shell_code=$?", 'cat "$cursor_shell_log"', 'if [ -n "$cursor_shell_status" ] && [ "$(cat "$cursor_shell_status" 2>/dev/null)" = timeout ]; then',
107
+ // Reap the watchdog before printing the private marker so any residual
108
+ // shell diagnostics cannot trail the sentinel.
109
+ ' if [ -n "$cursor_shell_watchdog_pid" ]; then kill "$cursor_shell_watchdog_pid" 2>/dev/null || true; wait "$cursor_shell_watchdog_pid" 2>/dev/null || true; fi', ` printf '\n${TIMEOUT_MARKER}%s\n' ${policy.hardTimeoutMs ?? policy.timeoutMs}`, "else", ' if [ -n "$cursor_shell_watchdog_pid" ]; then kill "$cursor_shell_watchdog_pid" 2>/dev/null || true; wait "$cursor_shell_watchdog_pid" 2>/dev/null || true; fi', ` printf '\n${EXIT_MARKER}%s\n' "$cursor_shell_code"`, "fi", 'rm -f -- "$cursor_shell_log"', 'if [ -n "$cursor_shell_status" ]; then rm -f -- "$cursor_shell_status"; fi');
74
110
  return lines.join("\n");
75
111
  }
76
- /** Mutate OpenCode Bash args before execution when Cursor requested soft backgrounding. */
112
+ /**
113
+ * F11 / background_shell_spawn_args helper.
114
+ *
115
+ * OpenCode's bash tool is foreground-only. Detach the requested command inside
116
+ * that one foreground call (`nohup … &`) and print a private marker containing
117
+ * the spawned PID and log path. With stdin and all output redirected, the host
118
+ * shell can return immediately instead of retaining OpenCode's tool pipe.
119
+ *
120
+ * Residual: the detached child is not reaped by this provider after OpenCode
121
+ * completes the tool call; cleanup is left to the user / OS.
122
+ */
123
+ export function buildBackgroundShellCommand(command) {
124
+ return [
125
+ 'bg_log="$(mktemp "${TMPDIR:-/tmp}/cursor-opencode-bg.XXXXXX")" || exit 1',
126
+ `nohup sh -c ${shellQuote(command)} >"$bg_log" 2>&1 </dev/null &`,
127
+ "bg_pid=$!",
128
+ `printf '${BACKGROUND_SHELL_MARKER}%s:%s\\n' "$bg_pid" "$bg_log"`,
129
+ ].join("\n");
130
+ }
131
+ /**
132
+ * Prepare OpenCode Bash args before execution when Cursor requested wrapping.
133
+ *
134
+ * Important: do **not** replace `args.command` with the wrapper script.
135
+ * OpenCode's bash UI renders `state.input.command`, and `ctx.metadata()`
136
+ * persists the execute-time `args` object into that field. Mutating
137
+ * `args.command` therefore leaks the private wrapper into the TUI/GUI.
138
+ *
139
+ * Wrapping is applied later via {@link cursorShellEnvForCall} (`shell.env`),
140
+ * which uses BASH_ENV / ZDOTDIR injectors so bash/zsh `-c <original>` is
141
+ * replaced with the wrapper while the stored/displayed command stays original.
142
+ * Permissions also keep analyzing the real user command.
143
+ */
77
144
  export function prepareCursorShellArgs(toolCallId, args) {
78
145
  const policy = policies.get(toolCallId);
79
- if (!policy || policy.timeoutBehavior !== CURSOR_TIMEOUT_BACKGROUND)
146
+ if (!policy)
80
147
  return;
81
- args.command = buildSoftBackgroundCommand(policy);
148
+ if (policy.backgroundSpawn) {
149
+ pendingEnvWraps.add(toolCallId);
150
+ return;
151
+ }
152
+ if (policy.timeoutBehavior !== CURSOR_TIMEOUT_BACKGROUND)
153
+ return;
154
+ pendingEnvWraps.add(toolCallId);
82
155
  // The wrapper returns just after Cursor's foreground window. OpenCode's own
83
156
  // timeout is only an outer safety net and must not win the race.
84
157
  args.timeout = Math.max(OPENCODE_TIMEOUT_GRACE_MS, policy.timeoutMs + OPENCODE_TIMEOUT_GRACE_MS);
@@ -87,12 +160,127 @@ export function prepareCursorShellArgs(toolCallId, args) {
87
160
  export function cursorShellOriginalCommand(toolCallId) {
88
161
  return policies.get(toolCallId)?.command || undefined;
89
162
  }
163
+ function writeShellEnvInjector(wrapperBody) {
164
+ const dir = mkdtempSync(join(tmpdir(), "cursor-opencode-wrap-"));
165
+ const wrapperPath = join(dir, "wrapper.sh");
166
+ const bashEnvPath = join(dir, "bashenv.sh");
167
+ const zshenvPath = join(dir, ".zshenv");
168
+ writeFileSync(wrapperPath, `${wrapperBody}\n`, { mode: 0o700 });
169
+ // Sourced by non-interactive bash (BASH_ENV) or zsh (.zshenv via ZDOTDIR).
170
+ // `exec` replaces the host shell so OpenCode's `-c <original>` body never runs.
171
+ //
172
+ // Do not gate on a sticky env flag: soft-background children inherit the
173
+ // wrapper environment, and a leftover CURSOR_OPENCODE_WRAP_ACTIVE=1 would
174
+ // make later injectors no-op (original `-c` runs unwrapped). Unsetting the
175
+ // injector vars before `exec` is enough to prevent re-entry.
176
+ const injector = [
177
+ "unset BASH_ENV ZDOTDIR ENV CURSOR_OPENCODE_WRAP_ACTIVE",
178
+ `exec /bin/sh ${shellQuote(wrapperPath)}`,
179
+ "",
180
+ ].join("\n");
181
+ writeFileSync(bashEnvPath, injector, { mode: 0o600 });
182
+ writeFileSync(zshenvPath, injector, { mode: 0o600 });
183
+ return {
184
+ env: {
185
+ BASH_ENV: bashEnvPath,
186
+ ZDOTDIR: dir,
187
+ },
188
+ cleanup: () => {
189
+ try {
190
+ rmSync(dir, { recursive: true, force: true });
191
+ }
192
+ catch {
193
+ // best-effort temp cleanup
194
+ }
195
+ },
196
+ };
197
+ }
198
+ /** Drop injector temp files for a finished/abandoned Cursor shell call. */
199
+ export function releaseCursorShellEnv(toolCallId) {
200
+ pendingEnvWraps.delete(toolCallId);
201
+ const active = activeEnvWraps.get(toolCallId);
202
+ if (!active)
203
+ return;
204
+ activeEnvWraps.delete(toolCallId);
205
+ active.cleanup();
206
+ }
207
+ /**
208
+ * Env vars for OpenCode's `shell.env` hook so bash/zsh execute the Cursor
209
+ * wrapper while `args.command` (and therefore the bash UI) stay original.
210
+ */
211
+ export function cursorShellEnvForCall(toolCallId) {
212
+ if (typeof toolCallId !== "string" || !toolCallId || !pendingEnvWraps.has(toolCallId))
213
+ return undefined;
214
+ const policy = policies.get(toolCallId);
215
+ if (!policy)
216
+ return undefined;
217
+ const existing = activeEnvWraps.get(toolCallId);
218
+ if (existing)
219
+ return existing.env;
220
+ const wrapperBody = policy.backgroundSpawn
221
+ ? buildBackgroundShellCommand(policy.command)
222
+ : policy.timeoutBehavior === CURSOR_TIMEOUT_BACKGROUND
223
+ ? buildSoftBackgroundCommand(policy)
224
+ : undefined;
225
+ if (!wrapperBody)
226
+ return undefined;
227
+ const wrap = writeShellEnvInjector(wrapperBody);
228
+ remember(activeEnvWraps, toolCallId, wrap, (evicted) => evicted.cleanup());
229
+ pendingEnvWraps.delete(toolCallId);
230
+ return wrap.env;
231
+ }
90
232
  function withoutMarker(output, index) {
91
233
  let clean = output.slice(0, index).replace(/[\t ]+$/gm, "").replace(/\n{2,}$/, "\n");
92
234
  if (clean.trim() === "" || clean.trim() === "(no output)")
93
235
  clean = "";
94
236
  return clean;
95
237
  }
238
+ /**
239
+ * OpenCode only stores/renders text (`output` / `metadata.output`). Private
240
+ * markers become typed Cursor outcomes, but stripping them alone can leave a
241
+ * blank or partial bash bubble that looks like success. Append a short
242
+ * user-facing status so the UI explains background handoff / timeout.
243
+ */
244
+ function formatShellOutcomeDisplay(clean, outcome) {
245
+ let notice;
246
+ if (outcome.kind === "backgrounded") {
247
+ notice = outcome.msToWait > 0
248
+ ? `Still running in the background (pid ${outcome.pid}) after ${outcome.msToWait}ms.`
249
+ : `Started in the background (pid ${outcome.pid}).`;
250
+ }
251
+ else if (outcome.kind === "timeout") {
252
+ notice = `Timed out after ${outcome.timeoutMs}ms.`;
253
+ }
254
+ if (!notice)
255
+ return clean;
256
+ if (!clean)
257
+ return `${notice}\n`;
258
+ return clean.endsWith("\n") ? `${clean}${notice}\n` : `${clean}\n${notice}\n`;
259
+ }
260
+ /**
261
+ * Find the last private wrapper sentinel.
262
+ *
263
+ * Soft-background wrappers print the marker as the final intentional line, but
264
+ * the host shell can still append job-control diagnostics afterwards (e.g.
265
+ * "Terminated: 15 … nohup sh -c '…cursor-shell-watchdog…'"). Match the sentinel
266
+ * on its own line and discard everything from that point to EOF.
267
+ */
268
+ function lastPrivateMarker(output, marker, valuePattern) {
269
+ const re = new RegExp(`(?:^|\\r?\\n)(${marker}${valuePattern})`, "g");
270
+ let match;
271
+ let last;
272
+ while ((match = re.exec(output)) !== null) {
273
+ if (match.index === undefined || match[1] === undefined)
274
+ continue;
275
+ const index = match[0].startsWith("\r\n")
276
+ ? match.index + 2
277
+ : match[0].startsWith("\n")
278
+ ? match.index + 1
279
+ : match.index;
280
+ last = { index, values: match.slice(2) };
281
+ }
282
+ return last;
283
+ }
96
284
  function parseOpenCodeTimeout(output) {
97
285
  const re = /<shell_metadata>\r?\nshell tool terminated command after exceeding timeout (\d+) ms\.[\s\S]*?<\/shell_metadata>\s*$/;
98
286
  const match = re.exec(output);
@@ -100,10 +288,10 @@ function parseOpenCodeTimeout(output) {
100
288
  return undefined;
101
289
  return { output: withoutMarker(output, match.index), timeoutMs: Number(match[1]) };
102
290
  }
103
- function parseWrapperOutcome(output, policy) {
104
- const background = new RegExp(`${BACKGROUND_MARKER}(\\d+):([^\\r\\n]+)\\s*$`).exec(output);
105
- if (background?.index !== undefined) {
106
- const pid = Number(background[1]);
291
+ function parseSoftBackgroundOutcome(output, policy) {
292
+ const background = lastPrivateMarker(output, BACKGROUND_MARKER, "(\\d+):([^\\r\\n]+)");
293
+ if (background) {
294
+ const pid = Number(background.values[0]);
107
295
  if (Number.isSafeInteger(pid) && pid > 0 && pid <= 0xffff_ffff) {
108
296
  return {
109
297
  output: withoutMarker(output, background.index),
@@ -119,43 +307,101 @@ function parseWrapperOutcome(output, policy) {
119
307
  };
120
308
  }
121
309
  }
122
- const timeout = new RegExp(`${TIMEOUT_MARKER}(\\d+)\\s*$`).exec(output);
123
- if (timeout?.index !== undefined) {
310
+ const timeout = lastPrivateMarker(output, TIMEOUT_MARKER, "(\\d+)");
311
+ if (timeout) {
124
312
  return {
125
313
  output: withoutMarker(output, timeout.index),
126
- outcome: { kind: "timeout", timeoutMs: Number(timeout[1]) },
314
+ outcome: { kind: "timeout", timeoutMs: Number(timeout.values[0]) },
127
315
  };
128
316
  }
129
- const exit = new RegExp(`${EXIT_MARKER}(-?\\d+)\\s*$`).exec(output);
130
- if (exit?.index !== undefined) {
317
+ const exit = lastPrivateMarker(output, EXIT_MARKER, "(-?\\d+)");
318
+ if (exit) {
131
319
  return {
132
320
  output: withoutMarker(output, exit.index),
133
- outcome: { kind: "exit", code: Number(exit[1]) },
321
+ outcome: { kind: "exit", code: Number(exit.values[0]) },
134
322
  };
135
323
  }
136
324
  return undefined;
137
325
  }
326
+ function parseBackgroundSpawnOutcome(output, policy) {
327
+ const match = lastPrivateMarker(output, BACKGROUND_SHELL_MARKER, "(\\d+):([^\\r\\n]+)");
328
+ if (!match)
329
+ return undefined;
330
+ const pid = Number(match.values[0]);
331
+ if (!Number.isSafeInteger(pid) || pid <= 0 || pid > 0xffff_ffff)
332
+ return undefined;
333
+ return {
334
+ output: withoutMarker(output, match.index),
335
+ outcome: {
336
+ kind: "backgrounded",
337
+ shellId: pid,
338
+ pid,
339
+ command: policy?.command ?? "",
340
+ workingDirectory: policy?.workingDirectory ?? "",
341
+ msToWait: 0,
342
+ reason: 1,
343
+ },
344
+ };
345
+ }
346
+ /**
347
+ * Strip private wrapper sentinels / OpenCode timeout envelopes for display.
348
+ * Does not record outcomes — use {@link captureCursorShellResult} for that.
349
+ */
350
+ export function sanitizeCursorShellDisplayOutput(output, policy) {
351
+ if (policy?.backgroundSpawn) {
352
+ const spawn = parseBackgroundSpawnOutcome(output, policy);
353
+ if (spawn)
354
+ return formatShellOutcomeDisplay(spawn.output, spawn.outcome);
355
+ }
356
+ if (policy?.timeoutBehavior === CURSOR_TIMEOUT_BACKGROUND) {
357
+ const wrapper = parseSoftBackgroundOutcome(output, policy);
358
+ if (wrapper)
359
+ return formatShellOutcomeDisplay(wrapper.output, wrapper.outcome);
360
+ }
361
+ const timeout = parseOpenCodeTimeout(output);
362
+ if (timeout) {
363
+ return formatShellOutcomeDisplay(timeout.output, {
364
+ kind: "timeout",
365
+ timeoutMs: timeout.timeoutMs,
366
+ });
367
+ }
368
+ return output;
369
+ }
370
+ /** Sanitize a secondary display string (e.g. Bash `metadata.output`) for a registered call. */
371
+ export function sanitizeRegisteredCursorShellOutput(toolCallId, output) {
372
+ if (typeof toolCallId !== "string" || !toolCallId)
373
+ return output;
374
+ return sanitizeCursorShellDisplayOutput(output, policies.get(toolCallId));
375
+ }
138
376
  /**
139
377
  * Capture Bash completion in the classic plugin's after hook. Returns the
140
378
  * sanitized output that OpenCode should store and render.
141
379
  */
142
380
  export function captureCursorShellResult(toolCallId, output, metadata) {
143
- if (!toolCallId.startsWith("cursor_"))
381
+ if (typeof toolCallId !== "string" || !toolCallId.startsWith("cursor_"))
144
382
  return output;
145
383
  const policy = policies.get(toolCallId);
384
+ if (policy?.backgroundSpawn) {
385
+ const spawn = parseBackgroundSpawnOutcome(output, policy);
386
+ if (spawn) {
387
+ remember(outcomes, toolCallId, spawn.outcome);
388
+ return formatShellOutcomeDisplay(spawn.output, spawn.outcome);
389
+ }
390
+ }
146
391
  // Private wrapper sentinels are meaningful only for calls we transformed.
147
392
  // A normal foreground command is allowed to print the same text verbatim.
148
393
  const wrapper = policy?.timeoutBehavior === CURSOR_TIMEOUT_BACKGROUND
149
- ? parseWrapperOutcome(output, policy)
394
+ ? parseSoftBackgroundOutcome(output, policy)
150
395
  : undefined;
151
396
  if (wrapper) {
152
397
  remember(outcomes, toolCallId, wrapper.outcome);
153
- return wrapper.output;
398
+ return formatShellOutcomeDisplay(wrapper.output, wrapper.outcome);
154
399
  }
155
400
  const timeout = parseOpenCodeTimeout(output);
156
401
  if (timeout) {
157
- remember(outcomes, toolCallId, { kind: "timeout", timeoutMs: timeout.timeoutMs });
158
- return timeout.output;
402
+ const outcome = { kind: "timeout", timeoutMs: timeout.timeoutMs };
403
+ remember(outcomes, toolCallId, outcome);
404
+ return formatShellOutcomeDisplay(timeout.output, outcome);
159
405
  }
160
406
  const exitCode = finiteNonNegative(metadata?.exit);
161
407
  if (exitCode !== undefined)
@@ -164,16 +410,24 @@ export function captureCursorShellResult(toolCallId, output, metadata) {
164
410
  }
165
411
  /** Consume the structured result, with an inline fallback when no plugin hook ran. */
166
412
  export function consumeCursorShellResult(toolCallId, output) {
413
+ if (typeof toolCallId !== "string" || !toolCallId) {
414
+ return { output };
415
+ }
167
416
  let clean = output;
168
417
  if (!outcomes.has(toolCallId))
169
418
  clean = captureCursorShellResult(toolCallId, output);
170
419
  const outcome = outcomes.get(toolCallId);
171
420
  outcomes.delete(toolCallId);
172
421
  policies.delete(toolCallId);
422
+ releaseCursorShellEnv(toolCallId);
173
423
  return { output: clean, outcome };
174
424
  }
175
425
  /** Test/process cleanup. */
176
426
  export function resetCursorShellCalls() {
427
+ for (const wrap of activeEnvWraps.values())
428
+ wrap.cleanup();
429
+ activeEnvWraps.clear();
430
+ pendingEnvWraps.clear();
177
431
  policies.clear();
178
432
  outcomes.clear();
179
433
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cursor-opencode-provider",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Use Cursor subscription models from OpenCode via Cursor's Connect-RPC agent protocol",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -45,6 +45,7 @@
45
45
  "dist",
46
46
  "LICENSE",
47
47
  "DISCLAIMER.md",
48
+ "SECURITY.md",
48
49
  "README.md"
49
50
  ],
50
51
  "scripts": {
@@ -68,4 +69,4 @@
68
69
  "@types/node": "^22.15.3",
69
70
  "typescript": "^5.8.3"
70
71
  }
71
- }
72
+ }