@stablekernel/opencode-cursor 0.7.1-next.0 → 0.7.1-next.2
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/CHANGELOG.md +57 -11
- package/README.md +41 -0
- package/dist/plugin/index.js +20 -6
- package/dist/plugin/index.js.map +1 -1
- package/dist/provider/index.js +10 -4
- package/dist/provider/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,52 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.7.1-next.2] — 2026-08-04 (pre-release)
|
|
8
|
+
|
|
9
|
+
Pre-release of the auto-compaction suppression (#92). Not yet on `latest`; install with
|
|
10
|
+
`npm install @stablekernel/opencode-cursor@next` to test.
|
|
11
|
+
|
|
12
|
+
- **opencode's threshold-triggered auto-compaction is now suppressed for Cursor models by
|
|
13
|
+
default.** The Cursor agent runtime already self-compacts on its own context threshold
|
|
14
|
+
(`preCompact` hook with `trigger: "auto"`), so opencode-driven compaction was redundant —
|
|
15
|
+
and it caused two real failures. First, the compaction turn runs with zero tools declared
|
|
16
|
+
while the Cursor agent uses its own tools anyway, which opencode rejects (`Tool call not
|
|
17
|
+
allowed while generating summary`) — mitigated in 0.7.1-next.1 (#91), and now avoided
|
|
18
|
+
entirely for the automatic trigger. Second, compaction rewrites the transcript, which
|
|
19
|
+
classifies as a divergence and mints a **fresh Cursor agentId** — and every distinct
|
|
20
|
+
agentId permanently holds a guarded SQLite `store.db`/`-wal`/`-shm` triple that
|
|
21
|
+
`agent.close()` cannot release (it only flushes analytics and releases the executor lease).
|
|
22
|
+
That descriptor growth fed an uncatchable `EXC_GUARD` process kill.
|
|
23
|
+
|
|
24
|
+
Suppression uses a large `limit.input` — the value opencode uses as its compaction
|
|
25
|
+
threshold — leaving the real `limit.context` intact so the TUI context gauge and cost
|
|
26
|
+
reporting still work. Manual `/compact` is unaffected and still relies on #91's fix.
|
|
27
|
+
|
|
28
|
+
**Tradeoff:** this suppresses the proactive threshold trigger only, and opencode has no
|
|
29
|
+
reactive context-overflow recovery wired up for this provider, so its transcript is no
|
|
30
|
+
longer trimmed automatically. Ordinary turns send only the new message, but a cold replay
|
|
31
|
+
(new session, expired agent, changed MCP set) resends everything; if that overflows the
|
|
32
|
+
model the turn fails and `/compact` is the manual recovery. Opt back out with
|
|
33
|
+
`provider.cursor.options.autoCompaction: true`.
|
|
34
|
+
|
|
35
|
+
## [0.7.1-next.1] — 2026-08-03 (pre-release)
|
|
36
|
+
|
|
37
|
+
Pre-release of the compaction fix (#91). Not yet on `latest`; install with
|
|
38
|
+
`npm install @stablekernel/opencode-cursor@next` to test.
|
|
39
|
+
|
|
40
|
+
- **Fixed: auto-compaction (and manual `/compact`) failed with `Tool call not
|
|
41
|
+
allowed while generating summary` whenever the Cursor agent used a tool while
|
|
42
|
+
summarizing.** opencode declares zero tools on a compaction/summary turn, but
|
|
43
|
+
the Cursor agent runs its own tools regardless; the provider forwarded that
|
|
44
|
+
activity as provider-executed `tool-call` parts, which opencode's summary
|
|
45
|
+
guard rejects. The provider now routes no-tools turns through the existing
|
|
46
|
+
`"reasoning"` tool-display path, so Cursor's tool activity surfaces as
|
|
47
|
+
reasoning text instead of crossing the tool-execution boundary. Manual
|
|
48
|
+
`/compact` was affected all along; **auto**-compaction became reachable only
|
|
49
|
+
in 0.7.1-next.0, because #89 published real per-model context windows —
|
|
50
|
+
pre-0.7.1 opencode saw `limit.context: 0` for every Cursor model, and a zero
|
|
51
|
+
context limit structurally disables the auto-compaction trigger.
|
|
52
|
+
|
|
7
53
|
## [0.7.1-next.0] — 2026-08-03 (pre-release)
|
|
8
54
|
|
|
9
55
|
Pre-release of the skills bridge (#90) and per-model context limits + pricing
|
|
@@ -30,17 +76,17 @@ Pre-release of the skills bridge (#90) and per-model context limits + pricing
|
|
|
30
76
|
through opencode's structured plugin logging rather than the terminal,
|
|
31
77
|
matching 0.7.0's logging change. `config.skills.urls` (HTTP catalogs) and
|
|
32
78
|
skills bundled inside opencode plugin packages are not yet supported.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
the
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
79
|
+
Skills bridge contributed by Wayne Simpson (@WayneSimpson).
|
|
80
|
+
- **Per-model context limits and pricing generated from Cursor's docs.** Each
|
|
81
|
+
resolved model now carries a context-window limit and per-token pricing,
|
|
82
|
+
emitted on the config channel opencode reads so the TUI can show cost and
|
|
83
|
+
token counts. High-output frontier models carry a separate output-token
|
|
84
|
+
limit. The data ships as a generated `src/model-limits.ts`, produced from
|
|
85
|
+
Cursor's published tables by `npm run sync:model-limits`; a weekly CI job
|
|
86
|
+
re-checks the committed data against those tables and fails if it has fallen
|
|
87
|
+
behind (it verifies only — regenerating is a manual step).
|
|
88
|
+
- **Dependency bumps:** `@ai-sdk/provider` 3.0.13 → 3.0.14, `@types/node`
|
|
89
|
+
26.0.0 → 26.1.2, `vitest` 4.1.9 → 4.1.10 (dev-dependencies group, #88).
|
|
44
90
|
|
|
45
91
|
## [0.7.0] — 2026-07-30
|
|
46
92
|
|
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ See [SECURITY.md](./SECURITY.md) for the full threat model.
|
|
|
187
187
|
| `toolDisplay` | `"blocks"` | How Cursor's internal tool activity is shown — see [Tool display](#tool-display) |
|
|
188
188
|
| `systemPrompt` | `"rules"` | How opencode's system prompt reaches the agent — see [System prompt](#system-prompt) |
|
|
189
189
|
| `transport` | — | Cursor agent transport (`"http1"` \| `"http2-direct"` \| `"sidecar"`) — see [Transport](#transport) |
|
|
190
|
+
| `autoCompaction` | `false` | Let opencode drive auto-compaction. Off by default because the Cursor agent self-compacts — see [Compaction](#compaction) |
|
|
190
191
|
|
|
191
192
|
| Environment variable | Default | Meaning |
|
|
192
193
|
| --- | --- | --- |
|
|
@@ -413,12 +414,52 @@ open a PR.
|
|
|
413
414
|
- **`"reasoning"`** — compact inline lines (`[tool] write {"path":…}`). Works on any host; use
|
|
414
415
|
this on older opencode versions.
|
|
415
416
|
|
|
417
|
+
Turns where the host declares no tools at all — compaction/summary and title generation — always
|
|
418
|
+
use `"reasoning"` regardless of this setting. opencode rejects tool parts on a summary turn, so
|
|
419
|
+
Cursor's tool activity is folded into reasoning text there instead.
|
|
420
|
+
|
|
416
421
|
To force the fallback:
|
|
417
422
|
|
|
418
423
|
```json
|
|
419
424
|
{ "provider": { "cursor": { "options": { "toolDisplay": "reasoning" } } } }
|
|
420
425
|
```
|
|
421
426
|
|
|
427
|
+
## Compaction
|
|
428
|
+
|
|
429
|
+
**opencode's threshold-triggered auto-compaction is suppressed for Cursor models by default.** The
|
|
430
|
+
Cursor agent runtime compacts its own conversation as it approaches its context threshold, so a
|
|
431
|
+
second, opencode-driven pass is redundant — and it is actively harmful here:
|
|
432
|
+
|
|
433
|
+
- The compaction turn asks the model to summarize with **no tools available**. The Cursor agent runs
|
|
434
|
+
its own tools regardless, which opencode rejects outright
|
|
435
|
+
(`Tool call not allowed while generating summary`).
|
|
436
|
+
- Compaction rewrites the transcript, so the next turn no longer matches what the Cursor agent saw.
|
|
437
|
+
The plugin correctly treats that as a divergence and creates a **fresh Cursor agent** — and every
|
|
438
|
+
distinct agent permanently holds its own SQLite store open for the life of the process, which has
|
|
439
|
+
been observed to crash opencode outright.
|
|
440
|
+
|
|
441
|
+
The suppression works by emitting a very large `limit.input`, which is what opencode uses as its
|
|
442
|
+
compaction threshold. The real `limit.context` is left untouched, so the TUI's context-window gauge
|
|
443
|
+
and cost reporting keep working.
|
|
444
|
+
|
|
445
|
+
> [!IMPORTANT]
|
|
446
|
+
> This suppresses the **proactive** threshold trigger only, and opencode has no reactive
|
|
447
|
+
> context-overflow recovery wired up for this provider. In exchange, opencode's transcript is no
|
|
448
|
+
> longer trimmed automatically, so it grows for the life of the session. Ordinary turns send only
|
|
449
|
+
> the new message to an already-running agent, but a *cold replay* — a new session, an expired
|
|
450
|
+
> agent, or a changed MCP server set — resends the whole transcript. If that ever overflows the
|
|
451
|
+
> model, the turn fails with a provider error and the fix is to run `/compact` manually.
|
|
452
|
+
>
|
|
453
|
+
> Set `autoCompaction: true` if you would rather have opencode keep bounding the transcript for you.
|
|
454
|
+
|
|
455
|
+
Manual `/compact` is unaffected and still works — it has no threshold gate.
|
|
456
|
+
|
|
457
|
+
To hand compaction back to opencode:
|
|
458
|
+
|
|
459
|
+
```json
|
|
460
|
+
{ "provider": { "cursor": { "options": { "autoCompaction": true } } } }
|
|
461
|
+
```
|
|
462
|
+
|
|
422
463
|
## Transport
|
|
423
464
|
|
|
424
465
|
opencode runs on [Bun](https://bun.sh), whose `node:http2` client is incompatible with the Cursor
|
package/dist/plugin/index.js
CHANGED
|
@@ -55,6 +55,7 @@ var MODEL_CONTEXT_LIMITS = {
|
|
|
55
55
|
"grok-4.5": 256e3
|
|
56
56
|
};
|
|
57
57
|
var DEFAULT_CONTEXT_LIMIT = 2e5;
|
|
58
|
+
var NO_AUTO_COMPACTION_INPUT_LIMIT = 1e9;
|
|
58
59
|
function resolveContextLimit(modelId) {
|
|
59
60
|
let best;
|
|
60
61
|
let bestLen = 0;
|
|
@@ -313,7 +314,7 @@ async function discoverModels(options = {}) {
|
|
|
313
314
|
function modelSupportsReasoning(item) {
|
|
314
315
|
return (item.parameters ?? []).some((p) => /think|reason/i.test(p.id));
|
|
315
316
|
}
|
|
316
|
-
function toOpencodeModels(items) {
|
|
317
|
+
function toOpencodeModels(items, opts = {}) {
|
|
317
318
|
const out = {};
|
|
318
319
|
for (const item of items) {
|
|
319
320
|
const params = defaultModelParams(item);
|
|
@@ -329,6 +330,10 @@ function toOpencodeModels(items) {
|
|
|
329
330
|
options: Object.keys(params).length > 0 ? { params } : {},
|
|
330
331
|
limit: {
|
|
331
332
|
context: resolveContextLimit(item.id),
|
|
333
|
+
// Suppress opencode's auto-compaction unless the user opts in: the
|
|
334
|
+
// Cursor agent self-compacts, and opencode's compaction mints a
|
|
335
|
+
// fresh agentId per cycle, permanently leaking guarded SQLite fds.
|
|
336
|
+
...opts.autoCompaction ? {} : { input: NO_AUTO_COMPACTION_INPUT_LIMIT },
|
|
332
337
|
output: resolveOutputLimit(item.id)
|
|
333
338
|
},
|
|
334
339
|
cost: {
|
|
@@ -348,7 +353,7 @@ var NPM_PACKAGE = "@stablekernel/opencode-cursor";
|
|
|
348
353
|
function providerNpm() {
|
|
349
354
|
return process.env.OPENCODE_CURSOR_PROVIDER_NPM?.trim() || NPM_PACKAGE;
|
|
350
355
|
}
|
|
351
|
-
function buildModelV2Map(items) {
|
|
356
|
+
function buildModelV2Map(items, opts = {}) {
|
|
352
357
|
const out = {};
|
|
353
358
|
for (const item of items) {
|
|
354
359
|
const params = defaultModelParams(item);
|
|
@@ -370,7 +375,11 @@ function buildModelV2Map(items) {
|
|
|
370
375
|
const c = resolveCost(item.id);
|
|
371
376
|
return { input: c.input, output: c.output, cache: { read: c.cacheRead, write: c.cacheWrite } };
|
|
372
377
|
})(),
|
|
373
|
-
limit: {
|
|
378
|
+
limit: {
|
|
379
|
+
context: resolveContextLimit(item.id),
|
|
380
|
+
...opts.autoCompaction ? {} : { input: NO_AUTO_COMPACTION_INPUT_LIMIT },
|
|
381
|
+
output: resolveOutputLimit(item.id)
|
|
382
|
+
},
|
|
374
383
|
status: "active",
|
|
375
384
|
options: Object.keys(params).length > 0 ? { params } : {},
|
|
376
385
|
headers: {},
|
|
@@ -785,7 +794,7 @@ function clearVersionCache() {
|
|
|
785
794
|
}
|
|
786
795
|
}
|
|
787
796
|
function getLocalVersion() {
|
|
788
|
-
if (true) return "0.7.1-next.
|
|
797
|
+
if (true) return "0.7.1-next.2";
|
|
789
798
|
try {
|
|
790
799
|
const require2 = createRequire(import.meta.url);
|
|
791
800
|
const pkg = require2("../package.json");
|
|
@@ -1473,6 +1482,7 @@ var CursorPlugin = async (input) => {
|
|
|
1473
1482
|
let resolvedCwd = directory ?? process.cwd();
|
|
1474
1483
|
let forwardMcp = true;
|
|
1475
1484
|
let userMcp = {};
|
|
1485
|
+
let autoCompaction = false;
|
|
1476
1486
|
let forwardSkills = true;
|
|
1477
1487
|
let skillFilterOptions;
|
|
1478
1488
|
let lastSkillHash = "";
|
|
@@ -1505,6 +1515,7 @@ var CursorPlugin = async (input) => {
|
|
|
1505
1515
|
config.provider ??= {};
|
|
1506
1516
|
const existing = config.provider[PROVIDER_ID] ?? {};
|
|
1507
1517
|
const existingOptions = existing.options ?? {};
|
|
1518
|
+
autoCompaction = existingOptions["autoCompaction"] === true;
|
|
1508
1519
|
forwardMcp = existingOptions["forwardMcp"] !== false;
|
|
1509
1520
|
userMcp = existingOptions["mcpServers"] ?? {};
|
|
1510
1521
|
const mcpServers = forwardMcp ? { ...userMcp, ...translateMcpServers(config.mcp) } : userMcp;
|
|
@@ -1558,7 +1569,10 @@ var CursorPlugin = async (input) => {
|
|
|
1558
1569
|
...Object.keys(modelParamDefaults).length > 0 ? { modelParamDefaults } : {},
|
|
1559
1570
|
...currentSkillsCatalogue ? { skillsCatalogue: currentSkillsCatalogue } : {}
|
|
1560
1571
|
},
|
|
1561
|
-
models: {
|
|
1572
|
+
models: {
|
|
1573
|
+
...toOpencodeModels(models, { autoCompaction }),
|
|
1574
|
+
...existing.models ?? {}
|
|
1575
|
+
}
|
|
1562
1576
|
};
|
|
1563
1577
|
},
|
|
1564
1578
|
provider: {
|
|
@@ -1566,7 +1580,7 @@ var CursorPlugin = async (input) => {
|
|
|
1566
1580
|
models: async (_provider, ctx) => {
|
|
1567
1581
|
const apiKey = apiKeyFromAuth(ctx.auth);
|
|
1568
1582
|
const { models } = await discoverModels({ apiKey });
|
|
1569
|
-
return buildModelV2Map(models);
|
|
1583
|
+
return buildModelV2Map(models, { autoCompaction });
|
|
1570
1584
|
}
|
|
1571
1585
|
},
|
|
1572
1586
|
// Bridge opencode's session id to the provider: it lands in
|