@rama_nigg/open-cursor 2.3.17 → 2.3.19

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/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@rama_nigg/open-cursor",
3
- "version": "2.3.17",
3
+ "version": "2.3.19",
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",
7
7
  "module": "src/plugin-entry.ts",
8
8
  "scripts": {
9
- "build": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node",
10
- "dev": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node --watch",
9
+ "build": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node --external @opencode-ai/plugin --external zod",
10
+ "dev": "bun build ./src/index.ts ./src/plugin-entry.ts ./src/cli/discover.ts ./src/cli/opencode-cursor.ts ./src/cli/mcptool.ts --outdir ./dist --target node --watch --external @opencode-ai/plugin --external zod",
11
11
  "test": "bun test",
12
12
  "test:unit": "bun test tests/unit",
13
13
  "test:integration": "bun test tests/integration",
@@ -59,6 +59,7 @@ export interface ToolLoopGuardTermination {
59
59
  maxRepeat: number;
60
60
  errorClass: string;
61
61
  silent?: boolean;
62
+ soft?: boolean;
62
63
  }
63
64
 
64
65
  export interface ToolSchemaValidationTermination {
@@ -177,6 +178,15 @@ export async function handleToolLoopEventLegacy(
177
178
  compat.validation,
178
179
  );
179
180
  if (validationTermination) {
181
+ if (validationTermination.soft) {
182
+ const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, validationTermination);
183
+ log.debug("Soft-blocking schema validation loop guard in legacy (emitting hint)", {
184
+ tool: normalizedToolCall.function.name,
185
+ fingerprint: validationTermination.fingerprint,
186
+ });
187
+ await onToolResult(hintChunk);
188
+ return { intercepted: false, skipConverter: true };
189
+ }
180
190
  return { intercepted: false, skipConverter: true, terminate: validationTermination };
181
191
  }
182
192
 
@@ -211,6 +221,15 @@ export async function handleToolLoopEventLegacy(
211
221
 
212
222
  const termination = evaluateToolLoopGuard(toolLoopGuard, normalizedToolCall);
213
223
  if (termination) {
224
+ if (termination.soft) {
225
+ const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination);
226
+ log.debug("Soft-blocking tool loop guard in legacy (emitting hint)", {
227
+ tool: normalizedToolCall.function.name,
228
+ fingerprint: termination.fingerprint,
229
+ });
230
+ await onToolResult(hintChunk);
231
+ return { intercepted: false, skipConverter: true };
232
+ }
214
233
  return { intercepted: false, skipConverter: true, terminate: termination };
215
234
  }
216
235
  await onInterceptedToolCall(normalizedToolCall);
@@ -341,10 +360,30 @@ export async function handleToolLoopEventV1(
341
360
  compat.validation,
342
361
  );
343
362
  if (validationTermination) {
363
+ if (validationTermination.soft) {
364
+ const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, validationTermination);
365
+ log.debug("Soft-blocking schema validation loop guard (emitting hint)", {
366
+ tool: normalizedToolCall.function.name,
367
+ fingerprint: validationTermination.fingerprint,
368
+ repeatCount: validationTermination.repeatCount,
369
+ });
370
+ await onToolResult(hintChunk);
371
+ return { intercepted: false, skipConverter: true };
372
+ }
344
373
  return { intercepted: false, skipConverter: true, terminate: validationTermination };
345
374
  }
346
375
  const termination = evaluateToolLoopGuard(toolLoopGuard, normalizedToolCall);
347
376
  if (termination) {
377
+ if (termination.soft) {
378
+ const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination);
379
+ log.debug("Soft-blocking tool loop guard in validation path (emitting hint)", {
380
+ tool: normalizedToolCall.function.name,
381
+ fingerprint: termination.fingerprint,
382
+ repeatCount: termination.repeatCount,
383
+ });
384
+ await onToolResult(hintChunk);
385
+ return { intercepted: false, skipConverter: true };
386
+ }
348
387
  return { intercepted: false, skipConverter: true, terminate: termination };
349
388
  }
350
389
  const reroutedWrite = tryRerouteEditToWrite(
@@ -415,6 +454,16 @@ export async function handleToolLoopEventV1(
415
454
 
416
455
  const termination = evaluateToolLoopGuard(toolLoopGuard, normalizedToolCall);
417
456
  if (termination) {
457
+ if (termination.soft) {
458
+ const hintChunk = createLoopGuardHintChunk(responseMeta, normalizedToolCall, termination);
459
+ log.debug("Soft-blocking tool loop guard (emitting hint)", {
460
+ tool: normalizedToolCall.function.name,
461
+ fingerprint: termination.fingerprint,
462
+ repeatCount: termination.repeatCount,
463
+ });
464
+ await onToolResult(hintChunk);
465
+ return { intercepted: false, skipConverter: true };
466
+ }
418
467
  return { intercepted: false, skipConverter: true, terminate: termination };
419
468
  }
420
469
  await onInterceptedToolCall(normalizedToolCall);
@@ -515,6 +564,11 @@ function evaluateToolLoopGuard(
515
564
  };
516
565
  }
517
566
 
567
+ // First trigger (repeatCount exactly one over threshold): soft block.
568
+ // Emit a hint to the model instead of killing the stream.
569
+ // If the model ignores the hint and retries, subsequent triggers are hard kills.
570
+ const isFirstTrigger = decision.repeatCount === decision.maxRepeat + 1;
571
+
518
572
  return {
519
573
  reason: "loop_guard",
520
574
  message: `Tool loop guard stopped repeated failing calls to "${toolCall.function.name}" `
@@ -525,6 +579,7 @@ function evaluateToolLoopGuard(
525
579
  repeatCount: decision.repeatCount,
526
580
  maxRepeat: decision.maxRepeat,
527
581
  errorClass: decision.errorClass,
582
+ soft: isFirstTrigger,
528
583
  };
529
584
  }
530
585
 
@@ -570,12 +625,15 @@ function evaluateSchemaValidationLoopGuard(
570
625
  return null;
571
626
  }
572
627
 
573
- log.warn("Tool loop guard triggered on schema validation", {
628
+ const isFirstTrigger = decision.repeatCount === decision.maxRepeat + 1;
629
+
630
+ log.debug("Tool loop guard triggered on schema validation", {
574
631
  tool: toolCall.function.name,
575
632
  fingerprint: decision.fingerprint,
576
633
  repeatCount: decision.repeatCount,
577
634
  maxRepeat: decision.maxRepeat,
578
635
  validationSignature,
636
+ soft: isFirstTrigger,
579
637
  });
580
638
  return {
581
639
  reason: "loop_guard",
@@ -588,6 +646,7 @@ function evaluateSchemaValidationLoopGuard(
588
646
  repeatCount: decision.repeatCount,
589
647
  maxRepeat: decision.maxRepeat,
590
648
  errorClass: decision.errorClass,
649
+ soft: isFirstTrigger,
591
650
  };
592
651
  }
593
652
 
@@ -663,6 +722,35 @@ function createNonFatalSchemaValidationHintChunk(
663
722
  };
664
723
  }
665
724
 
725
+ type LoopGuardHintChunk = NonFatalSchemaValidationResultChunk;
726
+
727
+ function createLoopGuardHintChunk(
728
+ meta: { id: string; created: number; model: string },
729
+ toolCall: OpenAiToolCall,
730
+ termination: ToolLoopGuardTermination,
731
+ ): LoopGuardHintChunk {
732
+ const content =
733
+ `Tool "${toolCall.function.name}" has been temporarily blocked after `
734
+ + `${termination.repeatCount} repeated ${termination.errorClass} failures. `
735
+ + "Do not retry this tool. Use a different approach to complete the task.";
736
+ return {
737
+ id: meta.id,
738
+ object: "chat.completion.chunk",
739
+ created: meta.created,
740
+ model: meta.model,
741
+ choices: [
742
+ {
743
+ index: 0,
744
+ delta: {
745
+ role: "assistant",
746
+ content,
747
+ },
748
+ finish_reason: null,
749
+ },
750
+ ],
751
+ };
752
+ }
753
+
666
754
  function safeArgTypeSummary(event: StreamJsonToolCallEvent): Record<string, string> {
667
755
  try {
668
756
  let raw: unknown;