Skip to content

Commit eaa3d5b

Browse files
committed
Merge origin/main into feat/skills-bridge
Two conflicts, both resolved to keep each side's intent: - language-model.ts: #85/#86 moved the resolveSystemDelivery call inside the withSessionLock callback. The skills-catalogue lookup was re-placed into that relocated call rather than restored to its old position. - CHANGELOG.md: the skills-bridge entry stays under [Unreleased]; main's 0.6.2 and 0.7.0 release sections are kept in full.
2 parents fb44fb6 + 4f49156 commit eaa3d5b

26 files changed

Lines changed: 2284 additions & 569 deletions

.github/dependabot.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,21 @@ updates:
1111
- "@opencode-ai/*"
1212
dev-dependencies:
1313
dependency-type: "development"
14+
ignore:
15+
# tsup's bundled rollup-plugin-dts (6.x, peer typescript
16+
# "^4.5 || ^5.0 || ^6.0") crashes declaration emit under TypeScript 7:
17+
# "Cannot read properties of undefined (reading
18+
# 'useCaseSensitiveFileNames')". No released tsup or rollup-plugin-dts
19+
# supports TS7 yet, so `npm run build` cannot pass with it. Drop this
20+
# entry once rollup-plugin-dts declares TS7 support.
21+
- dependency-name: "typescript"
22+
update-types: ["version-update:semver-major"]
23+
# The provider implements the V3 language-model spec (LanguageModelV3*)
24+
# and declares peerDependencies "@ai-sdk/provider": "^3.0.0". A v4 dev
25+
# dependency typechecks against types consumers don't install, so the
26+
# major has to be a deliberate spec migration, not a dependency bump.
27+
- dependency-name: "@ai-sdk/provider"
28+
update-types: ["version-update:semver-major"]
1429

1530
- package-ecosystem: "github-actions"
1631
directory: "/"

CHANGELOG.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,88 @@ All notable changes to this project will be documented in this file.
2323
`config.skills.urls` (HTTP catalogs) and skills bundled inside opencode
2424
plugin packages are not yet supported.
2525

26+
## [0.7.0] — 2026-07-30
27+
28+
Structured logging (#85), the stream-watchdog tool-phase budget (#86), and the
29+
session-pool title-generation race fix (#84).
30+
31+
- **Structured logging via `client.app.log()` instead of raw `console.*`.** The
32+
plugin's own diagnostics (transport fallback warnings, per-turn debug traces
33+
gated on `OPENCODE_CURSOR_DEBUG=1`) now route through opencode's plugin
34+
logging API (`service: "opencode-cursor"`) rather than `console.warn`/
35+
`console.error`. Falls back to `console.*` when no client is available
36+
(e.g. running the provider standalone).
37+
- **Cursor SDK's own "rules"/"skills" load diagnostics captured and forwarded.**
38+
`@cursor/sdk`'s bundled local-exec runtime writes internal messages like
39+
`LocalCursorRulesService load completed meta={durationMs, ruleCount}` and
40+
`AgentSkillsCursorRulesService load completed meta={durationMs, ruleCount,
41+
skillCount}` straight to `console.log`, with no public logger hook to
42+
redirect it. These are now recognized (in-process transport via a narrowly
43+
scoped `console.log` interceptor; sidecar transport via the child process's
44+
own interceptor forwarding over the existing JSONL protocol) and re-emitted
45+
as structured opencode logs instead of raw terminal noise. Every other
46+
`console.log` call passes through unchanged.
47+
- **Fixed: the stream watchdog killed healthy runs during long tool execution.** The watchdog
48+
re-armed only on mapped event types, so a long shell command, build, or test suite that streamed
49+
nothing for 60s was cancelled and the turn lost. It now uses two budgets — an idle budget
50+
(`OPENCODE_CURSOR_STALL_MS`, default raised to `120000`) and a larger tool-phase budget
51+
(`OPENCODE_CURSOR_TOOL_STALL_MS`, default `600000`) applied while a tool call is in flight — and
52+
re-arms on **any** SDK update, including types the plugin doesn't model (progress/heartbeats). A
53+
tool-phase stall is terminal and names the in-flight tool. `OPENCODE_CURSOR_STALL_MS=0` still
54+
disables the whole watchdog; the tool-phase bound is independently disabled with
55+
`OPENCODE_CURSOR_TOOL_STALL_MS=0`. Open tool calls are reconciled on `turn-ended` and on a forced
56+
resend, so a dropped completion can't pin a turn to the 10-minute budget.
57+
- **Fixed: a non-numeric `OPENCODE_CURSOR_STALL_MS` stalled every turn immediately.**
58+
`Number("abc")` is `NaN`; `NaN <= 0` is `false`, so the guard passed and `setTimeout(fn, NaN)`
59+
fired at once. Env parsing now falls back to the default for non-finite values (an empty string
60+
still disables, preserving the historical escape hatch).
61+
- **Fixed: an over-large stall budget overflowed to a ~1 ms deadline.** A `setTimeout` delay is
62+
stored as a signed 32-bit int, so anything above `2147483647` is silently clamped to `1` — and the
63+
tool-phase stall message tells operators to *raise* `OPENCODE_CURSOR_TOOL_STALL_MS`, making the
64+
trap reachable by following the plugin's own advice. Setting it to e.g. `999999999999` stalled
65+
every tool-bearing turn within milliseconds while reporting `no events for 999999999999ms`. Both
66+
budgets are now capped at `2147483647`.
67+
- **Fixed: opencode's title-generation call could poison a session's pool entry.** opencode forks a
68+
title-generation call on the same `sessionID` as the session's real first turn, concurrently and
69+
with an empty system prompt. `classifyTurn`'s side-call detection only fires once a prior pool
70+
record exists, so on turn 1 both calls classified as "new" and both wrote to the pool — whichever
71+
agent-creation round-trip resolved last silently overwrote the other, leaving the session
72+
fingerprinted against the title prompt. Two fixes: the plugin's `chat.params` hook now marks
73+
opencode's `title` agent call as `providerOptions.cursor.ephemeral = true` (the provider already
74+
honored this flag but nothing set it), and `withSessionLock` (a per-`sessionID` async lock) now
75+
wraps `agentRun`'s classify-then-acquire span so concurrent turns for one session serialize and
76+
the second call always observes the first's completed pool write.
77+
- **Dependency bumps:** `@cursor/sdk` 1.0.24 → 1.0.26, `@opencode-ai/plugin` (opencode-ai group).
78+
79+
## [0.6.2] — 2026-07-28
80+
81+
Version-check UX cleanup from #79.
82+
83+
- **Fixed: startup toast no longer suspends into the user's first prompt on slow networks.**
84+
The version-check toast previously ran `setTimeout(callback, 2000)` and then `await
85+
_versionCheckPromise` inside the callback, so a slow npm registry fetch could block the
86+
callback until after the user's first message was sent. The delay now runs *after* the
87+
promise resolves: `_versionCheckPromise.then(async (result) => { await sleep(2000); showToast() })`.
88+
The 2 s TUI-init pause is preserved; only the ordering changes.
89+
- **Removed: terminal `console.warn` for update notifications.** The `warnIfStale` function
90+
previously printed a multi-line warning to stderr on every startup when the plugin was
91+
outdated. This message is removed — the UI toast (introduced in 0.4.5) is the sole
92+
notification channel, avoiding duplicate noise in the terminal.
93+
- **New: `scripts/opencode-plugins-refresh`.** Helper script that compares cached `@latest` plugin
94+
versions against npm and optionally clears outdated caches so opencode re-fetches the latest on
95+
next launch. Supports `--check` (exit 1 if outdated, CI/cron-friendly) and `--force` (clear
96+
without prompting).
97+
- **`install.sh` now offers to install `opencode-plugins-refresh` to `~/.local/bin` (step 4).**
98+
- **`PLUGIN_CACHE_PATH` exported from `src/version-check.ts`.** Single source of truth for the
99+
opencode plugin cache path (cross-platform). Used by both the startup warning and the
100+
`cursor_update_plugin` tool to build the removal command / actually clear the cache — removes
101+
the duplication that could cause them to diverge.
102+
- **`warnIfStale` accepts an optional pre-fetched version string.** `warnIfStale(prefetchedLatest?)`
103+
now skips the registry call when the caller has already resolved it. Paired with a single
104+
`_latestVersionPromise` in the plugin that is shared by the console warning, the UI toast, and
105+
the system-prompt notice — so only one npm registry fetch happens per startup regardless of how
106+
many paths consume it.
107+
26108
## [0.6.1] — 2026-07-24
27109

28110
- **Fixed: reasoning/thinking variants showed as meaningless numbered entries for

README.md

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ Drop `@latest` (`"@stablekernel/opencode-cursor"`) or pin a version
7171
The stale-version check is skipped when the `CI` or `NO_UPDATE_NOTIFIER`
7272
environment variable is set.
7373

74+
To keep the plugin up to date easily, install the `opencode-plugins-refresh` helper (offered by
75+
the one-line installer, or install manually — see [Keeping the plugin up to date](#keeping-the-plugin-up-to-date)).
76+
7477
The plugin injects the `provider` block automatically. If you need explicit control:
7578

7679
```json
@@ -85,6 +88,41 @@ The plugin injects the `provider` block automatically. If you need explicit cont
8588
}
8689
```
8790

91+
## Keeping the plugin up to date
92+
93+
opencode pins `@latest` plugins on first install and never auto-updates them. When the installed
94+
version falls behind the latest release, the plugin shows a warning once every 24 hours at startup.
95+
The warning tells you what to do:
96+
97+
- If `opencode-plugins-refresh` is on your `PATH`, the warning says `run: opencode-plugins-refresh`.
98+
- Otherwise it shows the raw `rm -rf` command and suggests re-running the installer to get the
99+
helper script.
100+
101+
### opencode-plugins-refresh
102+
103+
`opencode-plugins-refresh` is a shell script that checks all `@latest` plugin caches for updates
104+
by comparing pinned versions against npm, and optionally clears outdated caches so opencode
105+
re-fetches the latest on next launch.
106+
107+
```bash
108+
opencode-plugins-refresh # check for updates, prompt to clear cache
109+
opencode-plugins-refresh --check # check only, exit 1 if outdated
110+
opencode-plugins-refresh --force # clear all outdated caches without prompting
111+
```
112+
113+
The `--check` flag exits with code `1` when any cache is outdated, making it suitable for CI jobs
114+
or cron checks.
115+
116+
The one-line installer offers to install `opencode-plugins-refresh` to `~/.local/bin` (step 4).
117+
To install it manually at any time:
118+
119+
```bash
120+
curl -fsSL https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh \
121+
-o ~/.local/bin/opencode-plugins-refresh && chmod +x ~/.local/bin/opencode-plugins-refresh
122+
```
123+
124+
Make sure `~/.local/bin` is on your `PATH`.
125+
88126
## Authenticate
89127

90128
```bash
@@ -156,7 +194,8 @@ See [SECURITY.md](./SECURITY.md) for the full threat model.
156194
| `OPENCODE_CURSOR_MODEL_CACHE_TTL_MS` | `86400000` | Model-list cache lifetime (ms) |
157195
| `OPENCODE_CURSOR_DEBUG` || Set to `1` for trace logging on stderr |
158196
| `OPENCODE_CURSOR_TRANSPORT` || Force a transport: `http1` \| `http2-direct` \| `sidecar` — see [Transport](#transport) |
159-
| `OPENCODE_CURSOR_STALL_MS` | `60000` | Stream watchdog timeout (ms); `0` disables — see [Reliability](#reliability) |
197+
| `OPENCODE_CURSOR_STALL_MS` | `120000` | Idle stream-watchdog timeout in ms (no tool call open). `0` disables the whole watchdog; an empty string also disables — see [Reliability](#reliability) |
198+
| `OPENCODE_CURSOR_TOOL_STALL_MS` | `600000` | Stream-watchdog timeout in ms while a tool call is in flight (e.g. a long build or test suite). `0` disables the bound during tool execution only — see [Reliability](#reliability) |
160199
| `OPENCODE_CURSOR_SIDECAR` || Legacy: `1` maps to `sidecar`, `0` maps to `http2-direct` (superseded by `OPENCODE_CURSOR_TRANSPORT`) |
161200
| `OPENCODE_CURSOR_TOOL_INPUT_STREAM` | on | Set to `0` to disable live tool-input streaming (`tool-input-start`/`-delta`/`-end` parts) |
162201

@@ -418,10 +457,20 @@ The provider classifies Cursor SDK errors into typed kinds (`agent-not-found`, `
418457

419458
Sends carry an idempotency key so a retry is a server-side dedupe, not a duplicate turn.
420459

421-
A **stream watchdog** guards against a wedged run that streams nothing: if no event arrives within
422-
`OPENCODE_CURSOR_STALL_MS` (default `60000`), a pre-first-event stall cancels and force-resends
423-
once; a stall after partial output is surfaced as a terminal error rather than re-emitting the
424-
already-yielded prefix. Set `OPENCODE_CURSOR_STALL_MS=0` to disable.
460+
A **stream watchdog** guards against a wedged run that streams nothing. It uses two budgets:
461+
462+
- **Idle** (`OPENCODE_CURSOR_STALL_MS`, default `120000`): when no tool call is open. A
463+
pre-first-event stall cancels and force-resends once; a stall after partial output is surfaced
464+
as a terminal error rather than re-emitting the already-yielded prefix.
465+
- **Tool-phase** (`OPENCODE_CURSOR_TOOL_STALL_MS`, default `600000`): while at least one Cursor
466+
tool call is in flight. A long shell command, build, or test suite legitimately streams nothing
467+
for minutes; the larger budget stops a healthy run from being killed mid-tool. A tool-phase
468+
stall is terminal and names the in-flight tool. Set `0` to disable the bound during tool
469+
execution only.
470+
471+
The watchdog re-arms on **any** SDK update — including types the plugin doesn't model — so
472+
progress/heartbeat updates count as liveness. Set `OPENCODE_CURSOR_STALL_MS=0` to disable the whole
473+
watchdog (an empty string also disables, for backward compatibility).
425474

426475
## Troubleshooting
427476

install.sh

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,11 +394,38 @@ else
394394
info "Set it with ${DIM}export CURSOR_API_KEY=\"key_...\"${RESET} or run ${DIM}opencode auth login${RESET} (choose \"Cursor\")."
395395
fi
396396

397+
# ---- 4. opencode-plugins-refresh ---------------------------------------------
398+
step "Installing opencode-plugins-refresh helper (optional)"
399+
LOCAL_BIN="$HOME/.local/bin"
400+
mkdir -p "$LOCAL_BIN"
401+
SCRIPT_URL="https://raw.githubusercontent.com/stablekernel/opencode-cursor/main/scripts/opencode-plugins-refresh"
402+
if have_tty; then
403+
printf 'Install opencode-plugins-refresh to %s? [y/N] ' "$LOCAL_BIN"
404+
read -r REPLY <"$TTY" || REPLY=""
405+
case "$REPLY" in
406+
[yY]*)
407+
if curl -fsSL "$SCRIPT_URL" -o "$LOCAL_BIN/opencode-plugins-refresh" 2>/dev/null; then
408+
chmod +x "$LOCAL_BIN/opencode-plugins-refresh"
409+
ok "Installed opencode-plugins-refresh → ${DIM}${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
410+
info "Make sure ${DIM}${LOCAL_BIN}${RESET} is on your PATH."
411+
else
412+
err "Failed to download opencode-plugins-refresh from ${SCRIPT_URL}"
413+
warn "You can install it manually later by re-running this installer."
414+
fi
415+
;;
416+
*) info "Skipped. Install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}" ;;
417+
esac
418+
else
419+
info "Non-interactive install — skipping opencode-plugins-refresh."
420+
info "To install manually: ${DIM}curl -fsSL ${SCRIPT_URL} -o ${LOCAL_BIN}/opencode-plugins-refresh && chmod +x ${LOCAL_BIN}/opencode-plugins-refresh${RESET}"
421+
fi
422+
397423
# ---- done --------------------------------------------------------------------
398424
step "Done"
399425
ok "opencode-cursor is installed."
400426
info "Next:"
401427
info " 1. ${DIM}Ensure CURSOR_API_KEY is set, or run: opencode auth login${RESET}"
402428
info " 2. ${DIM}Restart opencode, then run: opencode models${RESET} (lists cursor/* models)"
429+
info " 3. ${DIM}Run: opencode-plugins-refresh --check${RESET} (check for plugin updates)"
403430
info ""
404431
info "Docs: ${DIM}${REPO_URL}#readme${RESET}"

0 commit comments

Comments
 (0)