@rama_nigg/open-cursor 2.3.4 → 2.3.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.
package/README.md CHANGED
@@ -7,11 +7,6 @@
7
7
 
8
8
  No prompt limits. No broken streams. Full thinking + tool support in OpenCode. Your Cursor subscription, properly integrated.
9
9
 
10
- ## Prerequisites
11
-
12
- Required: [OpenCode](https://opencode.ai/) + [cursor-agent](https://cursor.com/) (`curl -fsSL https://cursor.com/install | bash && cursor-agent login`)
13
- Optional: [Bun](https://bun.sh/) (Options B-F), [Go 1.21+](https://go.dev/) (Option D)
14
-
15
10
  ## Installation
16
11
 
17
12
  ### Option A — One-line installer
@@ -20,7 +15,8 @@ Optional: [Bun](https://bun.sh/) (Options B-F), [Go 1.21+](https://go.dev/) (Opt
20
15
  curl -fsSL https://raw.githubusercontent.com/Nomadcxx/opencode-cursor/main/install.sh | bash
21
16
  ```
22
17
 
23
- ### Option B — Add to opencode.json
18
+ <details>
19
+ <summary><b>Option B</b> — Add to opencode.json</summary>
24
20
 
25
21
  Add to `~/.config/opencode/opencode.json`:
26
22
 
@@ -31,6 +27,9 @@ Add to `~/.config/opencode/opencode.json`:
31
27
  "cursor-acp": {
32
28
  "name": "Cursor ACP",
33
29
  "npm": "@ai-sdk/openai-compatible",
30
+ "options": {
31
+ "baseURL": "http://127.0.0.1:32124/v1"
32
+ },
34
33
  "models": {
35
34
  "cursor-acp/auto": { "name": "Auto" },
36
35
  "cursor-acp/composer-1.5": { "name": "Composer 1.5" },
@@ -72,8 +71,10 @@ Add to `~/.config/opencode/opencode.json`:
72
71
  ```
73
72
 
74
73
  > Update models anytime: `cursor-agent models`
74
+ </details>
75
75
 
76
- ### Option C — npm global + CLI
76
+ <details>
77
+ <summary><b>Option C</b> — npm global + CLI</summary>
77
78
 
78
79
  ```bash
79
80
  npm install -g @rama_nigg/open-cursor
@@ -81,22 +82,28 @@ open-cursor install
81
82
  ```
82
83
 
83
84
  Upgrade: `npm update -g @rama_nigg/open-cursor`
85
+ </details>
84
86
 
85
- ### Option D — Go TUI installer
87
+ <details>
88
+ <summary><b>Option D</b> — Go TUI installer</summary>
86
89
 
87
90
  ```bash
88
91
  git clone https://github.com/Nomadcxx/opencode-cursor.git
89
92
  cd opencode-cursor
90
93
  go build -o ./installer ./cmd/installer && ./installer
91
94
  ```
95
+ </details>
92
96
 
93
- ### Option E — LLM paste
97
+ <details>
98
+ <summary><b>Option E</b> — LLM paste</summary>
94
99
 
95
100
  ```
96
101
  Install open-cursor for OpenCode: edit ~/.config/opencode/opencode.json, add "@rama_nigg/open-cursor@latest" to "plugin", add a "cursor-acp" provider with npm "@ai-sdk/openai-compatible" and models from `cursor-agent models` prefixed with "cursor-acp/". Auth: `cursor-agent login`. Verify: `opencode models | grep cursor-acp`.
97
102
  ```
103
+ </details>
98
104
 
99
- ### Option F — Manual (from source)
105
+ <details>
106
+ <summary><b>Option F</b> — Manual (from source)</summary>
100
107
 
101
108
  ```bash
102
109
  git clone https://github.com/Nomadcxx/opencode-cursor.git && cd opencode-cursor
@@ -106,6 +113,7 @@ ln -sf $(pwd)/dist/plugin-entry.js ~/.config/opencode/plugin/cursor-acp.js
106
113
  ```
107
114
 
108
115
  Add `"cursor-acp"` to the `plugin` array and reuse the provider block from Option B.
116
+ </details>
109
117
 
110
118
  ## Authentication
111
119
 
@@ -170,6 +178,23 @@ Default mode: `CURSOR_ACP_TOOL_LOOP_MODE=opencode`. Legacy `proxy-exec` still av
170
178
 
171
179
  Debug logging: `CURSOR_ACP_LOG_LEVEL=debug opencode run "your prompt" --model cursor-acp/auto`
172
180
 
181
+ ## Roadmap
182
+
183
+ ```mermaid
184
+ flowchart LR
185
+ P1[/Stabilise/] --> P2[/MCP Server/] --> P3[/Simplify/] --> P4[/ACP + MCP/]
186
+
187
+ style P1 fill:#264653,stroke:#1d3557,color:#fff
188
+ style P2 fill:#264653,stroke:#1d3557,color:#fff
189
+ style P3 fill:#495057,stroke:#343a40,color:#adb5bd
190
+ style P4 fill:#495057,stroke:#343a40,color:#adb5bd
191
+ ```
192
+
193
+ [X] **Stabilise** — Clean up dead code, fix test isolation
194
+ [ ] **MCP Server** — Expose OpenCode tools via stdio transport
195
+ [ ] **Simplify** — Rip out serialisation layers
196
+ [ ] **ACP + MCP** — Structured protocols end-to-end
197
+
173
198
  ## License
174
199
 
175
200
  BSD-3-Clause
package/dist/index.js CHANGED
@@ -17408,6 +17408,7 @@ function parseToolLoopMaxRepeat(value) {
17408
17408
  return { value: Math.floor(parsed), valid: true };
17409
17409
  }
17410
17410
  function createToolLoopGuard(messages, maxRepeat) {
17411
+ const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
17411
17412
  const {
17412
17413
  byCallId,
17413
17414
  latest,
@@ -17447,13 +17448,13 @@ function createToolLoopGuard(messages, maxRepeat) {
17447
17448
  }
17448
17449
  const strictFingerprint = `${toolCall.function.name}|${argShape}|${errorClass}`;
17449
17450
  const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
17450
- return evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat);
17451
+ return evaluateWithFingerprints(toolCall.function.name, errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat, coarseMaxRepeat);
17451
17452
  },
17452
17453
  evaluateValidation(toolCall, validationSignature) {
17453
17454
  const normalizedSignature = normalizeValidationSignature(validationSignature);
17454
17455
  const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
17455
17456
  const coarseFingerprint = `${toolCall.function.name}|validation`;
17456
- return evaluateWithFingerprints("validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat);
17457
+ return evaluateWithFingerprints(toolCall.function.name, "validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat, coarseMaxRepeat);
17457
17458
  },
17458
17459
  resetFingerprint(fingerprint) {
17459
17460
  counts.delete(fingerprint);
@@ -17684,7 +17685,7 @@ function normalizeValidationSignature(signature) {
17684
17685
  const normalized = signature.trim().toLowerCase();
17685
17686
  return normalized.length > 0 ? normalized : "invalid";
17686
17687
  }
17687
- function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat) {
17688
+ function evaluateWithFingerprints(toolName, errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat, coarseMaxRepeat) {
17688
17689
  if (errorClass === "success") {
17689
17690
  return {
17690
17691
  fingerprint: strictFingerprint,
@@ -17697,15 +17698,26 @@ function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerpri
17697
17698
  }
17698
17699
  const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
17699
17700
  strictCounts.set(strictFingerprint, strictRepeatCount);
17701
+ const strictTriggered = strictRepeatCount > maxRepeat;
17702
+ const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
17703
+ if (isExplorationTool) {
17704
+ return {
17705
+ fingerprint: strictFingerprint,
17706
+ repeatCount: strictRepeatCount,
17707
+ maxRepeat,
17708
+ errorClass,
17709
+ triggered: strictTriggered,
17710
+ tracked: true
17711
+ };
17712
+ }
17700
17713
  const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
17701
17714
  coarseCounts.set(coarseFingerprint, coarseRepeatCount);
17702
- const strictTriggered = strictRepeatCount > maxRepeat;
17703
- const coarseTriggered = coarseRepeatCount > maxRepeat;
17715
+ const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
17704
17716
  const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
17705
17717
  return {
17706
17718
  fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
17707
17719
  repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
17708
- maxRepeat,
17720
+ maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
17709
17721
  errorClass,
17710
17722
  triggered: strictTriggered || coarseTriggered,
17711
17723
  tracked: true
@@ -17790,7 +17802,7 @@ function containsAny(text, patterns) {
17790
17802
  function isRecord4(value) {
17791
17803
  return typeof value === "object" && value !== null && !Array.isArray(value);
17792
17804
  }
17793
- var UNKNOWN_AS_SUCCESS_TOOLS;
17805
+ var UNKNOWN_AS_SUCCESS_TOOLS, EXPLORATION_TOOLS, COARSE_LIMIT_MULTIPLIER = 3;
17794
17806
  var init_tool_loop_guard = __esm(() => {
17795
17807
  UNKNOWN_AS_SUCCESS_TOOLS = new Set([
17796
17808
  "bash",
@@ -17802,9 +17814,19 @@ var init_tool_loop_guard = __esm(() => {
17802
17814
  "ls",
17803
17815
  "glob",
17804
17816
  "stat",
17805
- "webfetch",
17806
17817
  "mkdir",
17807
- "rm"
17818
+ "rm",
17819
+ "webfetch",
17820
+ "semsearch",
17821
+ "readlints"
17822
+ ]);
17823
+ EXPLORATION_TOOLS = new Set([
17824
+ "read",
17825
+ "grep",
17826
+ "glob",
17827
+ "ls",
17828
+ "stat",
17829
+ "semsearch"
17808
17830
  ]);
17809
17831
  });
17810
17832
 
@@ -17408,6 +17408,7 @@ function parseToolLoopMaxRepeat(value) {
17408
17408
  return { value: Math.floor(parsed), valid: true };
17409
17409
  }
17410
17410
  function createToolLoopGuard(messages, maxRepeat) {
17411
+ const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
17411
17412
  const {
17412
17413
  byCallId,
17413
17414
  latest,
@@ -17447,13 +17448,13 @@ function createToolLoopGuard(messages, maxRepeat) {
17447
17448
  }
17448
17449
  const strictFingerprint = `${toolCall.function.name}|${argShape}|${errorClass}`;
17449
17450
  const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
17450
- return evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat);
17451
+ return evaluateWithFingerprints(toolCall.function.name, errorClass, strictFingerprint, coarseFingerprint, counts, coarseCounts, maxRepeat, coarseMaxRepeat);
17451
17452
  },
17452
17453
  evaluateValidation(toolCall, validationSignature) {
17453
17454
  const normalizedSignature = normalizeValidationSignature(validationSignature);
17454
17455
  const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
17455
17456
  const coarseFingerprint = `${toolCall.function.name}|validation`;
17456
- return evaluateWithFingerprints("validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat);
17457
+ return evaluateWithFingerprints(toolCall.function.name, "validation", strictFingerprint, coarseFingerprint, validationCounts, validationCoarseCounts, maxRepeat, coarseMaxRepeat);
17457
17458
  },
17458
17459
  resetFingerprint(fingerprint) {
17459
17460
  counts.delete(fingerprint);
@@ -17684,7 +17685,7 @@ function normalizeValidationSignature(signature) {
17684
17685
  const normalized = signature.trim().toLowerCase();
17685
17686
  return normalized.length > 0 ? normalized : "invalid";
17686
17687
  }
17687
- function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat) {
17688
+ function evaluateWithFingerprints(toolName, errorClass, strictFingerprint, coarseFingerprint, strictCounts, coarseCounts, maxRepeat, coarseMaxRepeat) {
17688
17689
  if (errorClass === "success") {
17689
17690
  return {
17690
17691
  fingerprint: strictFingerprint,
@@ -17697,15 +17698,26 @@ function evaluateWithFingerprints(errorClass, strictFingerprint, coarseFingerpri
17697
17698
  }
17698
17699
  const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
17699
17700
  strictCounts.set(strictFingerprint, strictRepeatCount);
17701
+ const strictTriggered = strictRepeatCount > maxRepeat;
17702
+ const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
17703
+ if (isExplorationTool) {
17704
+ return {
17705
+ fingerprint: strictFingerprint,
17706
+ repeatCount: strictRepeatCount,
17707
+ maxRepeat,
17708
+ errorClass,
17709
+ triggered: strictTriggered,
17710
+ tracked: true
17711
+ };
17712
+ }
17700
17713
  const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
17701
17714
  coarseCounts.set(coarseFingerprint, coarseRepeatCount);
17702
- const strictTriggered = strictRepeatCount > maxRepeat;
17703
- const coarseTriggered = coarseRepeatCount > maxRepeat;
17715
+ const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
17704
17716
  const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
17705
17717
  return {
17706
17718
  fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
17707
17719
  repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
17708
- maxRepeat,
17720
+ maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
17709
17721
  errorClass,
17710
17722
  triggered: strictTriggered || coarseTriggered,
17711
17723
  tracked: true
@@ -17790,7 +17802,7 @@ function containsAny(text, patterns) {
17790
17802
  function isRecord4(value) {
17791
17803
  return typeof value === "object" && value !== null && !Array.isArray(value);
17792
17804
  }
17793
- var UNKNOWN_AS_SUCCESS_TOOLS;
17805
+ var UNKNOWN_AS_SUCCESS_TOOLS, EXPLORATION_TOOLS, COARSE_LIMIT_MULTIPLIER = 3;
17794
17806
  var init_tool_loop_guard = __esm(() => {
17795
17807
  UNKNOWN_AS_SUCCESS_TOOLS = new Set([
17796
17808
  "bash",
@@ -17802,9 +17814,19 @@ var init_tool_loop_guard = __esm(() => {
17802
17814
  "ls",
17803
17815
  "glob",
17804
17816
  "stat",
17805
- "webfetch",
17806
17817
  "mkdir",
17807
- "rm"
17818
+ "rm",
17819
+ "webfetch",
17820
+ "semsearch",
17821
+ "readlints"
17822
+ ]);
17823
+ EXPLORATION_TOOLS = new Set([
17824
+ "read",
17825
+ "grep",
17826
+ "glob",
17827
+ "ls",
17828
+ "stat",
17829
+ "semsearch"
17808
17830
  ]);
17809
17831
  });
17810
17832
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rama_nigg/open-cursor",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
4
4
  "description": "No prompt limits. No broken streams. Full thinking + tool support. Your Cursor subscription, properly integrated.",
5
5
  "type": "module",
6
6
  "main": "dist/plugin-entry.js",
@@ -10,6 +10,7 @@ type ToolLoopErrorClass =
10
10
  | "unknown";
11
11
 
12
12
  const UNKNOWN_AS_SUCCESS_TOOLS = new Set([
13
+ // Core filesystem tools
13
14
  "bash",
14
15
  "shell",
15
16
  "read",
@@ -19,9 +20,27 @@ const UNKNOWN_AS_SUCCESS_TOOLS = new Set([
19
20
  "ls",
20
21
  "glob",
21
22
  "stat",
22
- "webfetch",
23
23
  "mkdir",
24
24
  "rm",
25
+ // Web/network tools
26
+ "webfetch",
27
+ // cursor-agent specific tools (passthrough, but should not trigger loop guard)
28
+ // Discovered via tests/experiments/ harness - see docs/cursor-agent-tools.md
29
+ "semsearch", // semantic code search
30
+ "readlints", // lint/diagnostic reader
31
+ ]);
32
+
33
+ // Exploratory tools that commonly iterate over many files/patterns.
34
+ // These are exempt from COARSE fingerprint tracking (tool|errorClass) to allow
35
+ // legitimate multi-file exploration. Strict fingerprints (tool|args|errorClass)
36
+ // still apply to catch identical repeated failures.
37
+ const EXPLORATION_TOOLS = new Set([
38
+ "read",
39
+ "grep",
40
+ "glob",
41
+ "ls",
42
+ "stat",
43
+ "semsearch",
25
44
  ]);
26
45
 
27
46
  export interface ToolLoopGuardDecision {
@@ -52,10 +71,16 @@ export function parseToolLoopMaxRepeat(
52
71
  return { value: Math.floor(parsed), valid: true };
53
72
  }
54
73
 
74
+ // Coarse fingerprint (tool|errorClass without args) uses a higher multiplier
75
+ // to allow legitimate exploration across different files/targets while still
76
+ // catching spray-and-pray patterns.
77
+ const COARSE_LIMIT_MULTIPLIER = 3;
78
+
55
79
  export function createToolLoopGuard(
56
80
  messages: Array<unknown>,
57
81
  maxRepeat: number,
58
82
  ): ToolLoopGuard {
83
+ const coarseMaxRepeat = maxRepeat * COARSE_LIMIT_MULTIPLIER;
59
84
  const {
60
85
  byCallId,
61
86
  latest,
@@ -119,12 +144,14 @@ export function createToolLoopGuard(
119
144
  const coarseFingerprint = `${toolCall.function.name}|${errorClass}`;
120
145
 
121
146
  return evaluateWithFingerprints(
147
+ toolCall.function.name,
122
148
  errorClass,
123
149
  strictFingerprint,
124
150
  coarseFingerprint,
125
151
  counts,
126
152
  coarseCounts,
127
153
  maxRepeat,
154
+ coarseMaxRepeat,
128
155
  );
129
156
  },
130
157
 
@@ -133,12 +160,14 @@ export function createToolLoopGuard(
133
160
  const strictFingerprint = `${toolCall.function.name}|schema:${normalizedSignature}|validation`;
134
161
  const coarseFingerprint = `${toolCall.function.name}|validation`;
135
162
  return evaluateWithFingerprints(
163
+ toolCall.function.name,
136
164
  "validation",
137
165
  strictFingerprint,
138
166
  coarseFingerprint,
139
167
  validationCounts,
140
168
  validationCoarseCounts,
141
169
  maxRepeat,
170
+ coarseMaxRepeat,
142
171
  );
143
172
  },
144
173
 
@@ -448,12 +477,14 @@ function normalizeValidationSignature(signature: string): string {
448
477
  }
449
478
 
450
479
  function evaluateWithFingerprints(
480
+ toolName: string,
451
481
  errorClass: ToolLoopErrorClass,
452
482
  strictFingerprint: string,
453
483
  coarseFingerprint: string,
454
484
  strictCounts: Map<string, number>,
455
485
  coarseCounts: Map<string, number>,
456
486
  maxRepeat: number,
487
+ coarseMaxRepeat: number,
457
488
  ): ToolLoopGuardDecision {
458
489
  if (errorClass === "success") {
459
490
  return {
@@ -468,15 +499,28 @@ function evaluateWithFingerprints(
468
499
 
469
500
  const strictRepeatCount = (strictCounts.get(strictFingerprint) ?? 0) + 1;
470
501
  strictCounts.set(strictFingerprint, strictRepeatCount);
502
+ const strictTriggered = strictRepeatCount > maxRepeat;
503
+
504
+ const isExplorationTool = EXPLORATION_TOOLS.has(toolName.toLowerCase());
505
+ if (isExplorationTool) {
506
+ return {
507
+ fingerprint: strictFingerprint,
508
+ repeatCount: strictRepeatCount,
509
+ maxRepeat,
510
+ errorClass,
511
+ triggered: strictTriggered,
512
+ tracked: true,
513
+ };
514
+ }
515
+
471
516
  const coarseRepeatCount = (coarseCounts.get(coarseFingerprint) ?? 0) + 1;
472
517
  coarseCounts.set(coarseFingerprint, coarseRepeatCount);
473
- const strictTriggered = strictRepeatCount > maxRepeat;
474
- const coarseTriggered = coarseRepeatCount > maxRepeat;
518
+ const coarseTriggered = coarseRepeatCount > coarseMaxRepeat;
475
519
  const preferCoarseFingerprint = coarseTriggered && !strictTriggered;
476
520
  return {
477
521
  fingerprint: preferCoarseFingerprint ? coarseFingerprint : strictFingerprint,
478
522
  repeatCount: preferCoarseFingerprint ? coarseRepeatCount : strictRepeatCount,
479
- maxRepeat,
523
+ maxRepeat: preferCoarseFingerprint ? coarseMaxRepeat : maxRepeat,
480
524
  errorClass,
481
525
  triggered: strictTriggered || coarseTriggered,
482
526
  tracked: true,