@rama_nigg/open-cursor 2.4.4 → 2.4.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.
@@ -520,6 +520,15 @@ function resolveCursorAgentBinary(deps = {}) {
520
520
  log.warn("cursor-agent not found at known paths, falling back to PATH", { checkedPaths: knownPaths });
521
521
  return "cursor-agent";
522
522
  }
523
+ function formatShellCommandForPlatform(command, platform = process.platform) {
524
+ if (platform !== "win32") {
525
+ return command;
526
+ }
527
+ if (command.startsWith('"') && command.endsWith('"')) {
528
+ return command;
529
+ }
530
+ return `"${command}"`;
531
+ }
523
532
  var log;
524
533
  var init_binary = __esm(() => {
525
534
  init_logger();
@@ -569,7 +578,7 @@ async function pollForAuthFile(timeoutMs = AUTH_POLL_TIMEOUT, intervalMs = AUTH_
569
578
  async function startCursorOAuth() {
570
579
  return new Promise((resolve2, reject) => {
571
580
  log2.info("Starting cursor-cli login process");
572
- const proc = spawn(resolveCursorAgentBinary(), ["login"], {
581
+ const proc = spawn(formatShellCommandForPlatform(resolveCursorAgentBinary()), ["login"], {
573
582
  stdio: ["pipe", "pipe", "pipe"],
574
583
  shell: process.platform === "win32"
575
584
  });
@@ -763,29 +772,6 @@ class LineBuffer {
763
772
  }
764
773
  }
765
774
 
766
- // src/streaming/types.ts
767
- var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
768
- if (event.type === "thinking") {
769
- return true;
770
- }
771
- return event.type === "assistant" && hasThinkingContent(event);
772
- }, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
773
- if (event.type === "thinking") {
774
- return event.text ?? "";
775
- }
776
- return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
777
- }, inferToolName = (event) => {
778
- const [key] = Object.keys(event.tool_call ?? {});
779
- if (!key) {
780
- return "";
781
- }
782
- if (key.endsWith("ToolCall")) {
783
- const base = key.slice(0, -"ToolCall".length);
784
- return base.charAt(0).toLowerCase() + base.slice(1);
785
- }
786
- return key;
787
- };
788
-
789
775
  // src/streaming/delta-tracker.ts
790
776
  class DeltaTracker {
791
777
  lastText = "";
@@ -823,14 +809,70 @@ class DeltaTracker {
823
809
  }
824
810
  }
825
811
 
812
+ class MixedDeltaTracker {
813
+ emittedText = "";
814
+ emittedThinking = "";
815
+ nextText(value) {
816
+ const delta = this.diff(this.emittedText, value);
817
+ if (delta) {
818
+ this.emittedText += delta;
819
+ }
820
+ return delta;
821
+ }
822
+ nextThinking(value) {
823
+ const delta = this.diff(this.emittedThinking, value);
824
+ if (delta) {
825
+ this.emittedThinking += delta;
826
+ }
827
+ return delta;
828
+ }
829
+ reset() {
830
+ this.emittedText = "";
831
+ this.emittedThinking = "";
832
+ }
833
+ diff(emitted, current) {
834
+ if (!emitted) {
835
+ return current;
836
+ }
837
+ if (current.startsWith(emitted)) {
838
+ return current.slice(emitted.length);
839
+ }
840
+ if (emitted.startsWith(current)) {
841
+ return "";
842
+ }
843
+ return current;
844
+ }
845
+ }
846
+
847
+ // src/streaming/types.ts
848
+ var hasTextContent = (event) => event.message.content.some((content) => content.type === "text"), hasThinkingContent = (event) => event.message.content.some((content) => content.type === "thinking"), isAssistantText = (event) => event.type === "assistant" && hasTextContent(event), isThinking = (event) => {
849
+ if (event.type === "thinking") {
850
+ return true;
851
+ }
852
+ return event.type === "assistant" && hasThinkingContent(event);
853
+ }, isToolCall = (event) => event.type === "tool_call", isResult = (event) => event.type === "result", extractText = (event) => event.message.content.filter((content) => content.type === "text").map((content) => content.text).join(""), extractThinking = (event) => {
854
+ if (event.type === "thinking") {
855
+ return event.text ?? "";
856
+ }
857
+ return event.message.content.filter((content) => content.type === "thinking").map((content) => content.thinking).join("");
858
+ }, inferToolName = (event) => {
859
+ const [key] = Object.keys(event.tool_call ?? {});
860
+ if (!key) {
861
+ return "";
862
+ }
863
+ if (key.endsWith("ToolCall")) {
864
+ const base = key.slice(0, -"ToolCall".length);
865
+ return base.charAt(0).toLowerCase() + base.slice(1);
866
+ }
867
+ return key;
868
+ };
869
+
826
870
  // src/streaming/openai-sse.ts
827
871
  class StreamToSseConverter {
828
872
  id;
829
873
  created;
830
874
  model;
831
- tracker = new DeltaTracker;
832
- sawAssistantPartials = false;
833
- sawThinkingPartials = false;
875
+ tracker = new MixedDeltaTracker;
834
876
  constructor(model, options) {
835
877
  this.model = model;
836
878
  this.id = options?.id ?? `cursor-acp-${Date.now()}`;
@@ -838,35 +880,17 @@ class StreamToSseConverter {
838
880
  }
839
881
  handleEvent(event) {
840
882
  if (isAssistantText(event)) {
841
- const isPartial = typeof event.timestamp_ms === "number";
842
- if (isPartial) {
843
- const text = extractText(event);
844
- if (text) {
845
- this.sawAssistantPartials = true;
846
- return [this.chunkWith({ content: text })];
847
- }
848
- return [];
849
- }
850
- if (this.sawAssistantPartials) {
883
+ const text = extractText(event);
884
+ if (!text)
851
885
  return [];
852
- }
853
- const delta = this.tracker.nextText(extractText(event));
886
+ const delta = this.tracker.nextText(text);
854
887
  return delta ? [this.chunkWith({ content: delta })] : [];
855
888
  }
856
889
  if (isThinking(event)) {
857
- const isPartial = typeof event.timestamp_ms === "number";
858
- if (isPartial) {
859
- const text = extractThinking(event);
860
- if (text) {
861
- this.sawThinkingPartials = true;
862
- return [this.chunkWith({ reasoning_content: text })];
863
- }
864
- return [];
865
- }
866
- if (this.sawThinkingPartials) {
890
+ const text = extractThinking(event);
891
+ if (!text)
867
892
  return [];
868
- }
869
- const delta = this.tracker.nextThinking(extractThinking(event));
893
+ const delta = this.tracker.nextThinking(text);
870
894
  return delta ? [this.chunkWith({ reasoning_content: delta })] : [];
871
895
  }
872
896
  if (isToolCall(event)) {
@@ -1042,27 +1066,6 @@ var init_perf = __esm(() => {
1042
1066
  });
1043
1067
 
1044
1068
  // src/proxy/prompt-builder.ts
1045
- import { appendFileSync as appendFileSync2, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "node:fs";
1046
- import { homedir as homedir4 } from "node:os";
1047
- import { join as join5 } from "node:path";
1048
- function ensureLogDir2() {
1049
- try {
1050
- if (!existsSync4(DEBUG_LOG_DIR)) {
1051
- mkdirSync2(DEBUG_LOG_DIR, { recursive: true });
1052
- }
1053
- } catch {}
1054
- }
1055
- function debugLogToFile(message, data) {
1056
- try {
1057
- ensureLogDir2();
1058
- const timestamp = new Date().toISOString();
1059
- const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
1060
- `;
1061
- appendFileSync2(DEBUG_LOG_FILE, logLine);
1062
- } catch (err) {
1063
- log5.debug(message, data);
1064
- }
1065
- }
1066
1069
  function buildPromptFromMessages(messages, tools, subagentNames = []) {
1067
1070
  const messageSummary = messages.map((m, i) => {
1068
1071
  const role = m?.role ?? "?";
@@ -1076,7 +1079,7 @@ function buildPromptFromMessages(messages, tools, subagentNames = []) {
1076
1079
  const assistantWithToolCalls = messages.filter((m) => m?.role === "assistant" && Array.isArray(m?.tool_calls) && m.tool_calls.length > 0);
1077
1080
  const assistantEmpty = messages.filter((m) => m?.role === "assistant" && (!m?.tool_calls || m.tool_calls.length === 0) && (!m?.content || m.content === "" || m.content === null));
1078
1081
  const toolResults = messages.filter((m) => m?.role === "tool");
1079
- debugLogToFile("buildPromptFromMessages", {
1082
+ log5.debug("buildPromptFromMessages", {
1080
1083
  totalMessages: messages.length,
1081
1084
  totalTools: tools.length,
1082
1085
  messageSummary,
@@ -1171,22 +1174,20 @@ ${toolDescs}`);
1171
1174
  const finalPrompt = lines.join(`
1172
1175
 
1173
1176
  `);
1174
- debugLogToFile("buildPromptFromMessages: final prompt", {
1177
+ log5.debug("buildPromptFromMessages: final prompt", {
1175
1178
  lineCount: lines.length,
1176
1179
  promptLength: finalPrompt.length,
1177
1180
  promptPreview: finalPrompt.slice(0, 500),
1178
1181
  hasToolResultFormat: finalPrompt.includes("TOOL_RESULT"),
1179
1182
  hasAssistantToolCallFormat: finalPrompt.includes("tool_call(id:"),
1180
- hasCompletionSignal: finalPrompt.includes("Based on the tool results")
1183
+ hasCompletionSignal: finalPrompt.includes("The above tool calls have been executed")
1181
1184
  });
1182
1185
  return finalPrompt;
1183
1186
  }
1184
- var log5, DEBUG_LOG_DIR, DEBUG_LOG_FILE;
1187
+ var log5;
1185
1188
  var init_prompt_builder = __esm(() => {
1186
1189
  init_logger();
1187
1190
  log5 = createLogger("proxy:prompt-builder");
1188
- DEBUG_LOG_DIR = join5(homedir4(), ".config", "opencode", "logs");
1189
- DEBUG_LOG_FILE = join5(DEBUG_LOG_DIR, "tool-loop-debug.log");
1190
1191
  });
1191
1192
 
1192
1193
  // src/proxy/tool-loop.ts
@@ -11975,7 +11976,7 @@ function buildMcpToolDefinitions(tools) {
11975
11976
  function namespaceMcpTool(serverName, toolName) {
11976
11977
  const sanitizedServer = serverName.replace(/[^a-zA-Z0-9]/g, "_");
11977
11978
  const sanitizedTool = toolName.replace(/[^a-zA-Z0-9]/g, "_");
11978
- return `mcp__${sanitizedServer}__${sanitizedTool}`;
11979
+ return `${MCP_TOOL_PREFIX}${sanitizedServer}__${sanitizedTool}`;
11979
11980
  }
11980
11981
  function mcpSchemaToZod(inputSchema, z2) {
11981
11982
  if (!inputSchema || typeof inputSchema !== "object") {
@@ -12017,7 +12018,7 @@ function mcpSchemaToZod(inputSchema, z2) {
12017
12018
  }
12018
12019
  return shape;
12019
12020
  }
12020
- var log13;
12021
+ var log13, MCP_TOOL_PREFIX = "mcp__";
12021
12022
  var init_tool_bridge = __esm(() => {
12022
12023
  init_logger();
12023
12024
  log13 = createLogger("mcp:tool-bridge");
@@ -14022,9 +14023,6 @@ function resolveEditArguments(args) {
14022
14023
  newString = fallbackContent;
14023
14024
  }
14024
14025
  }
14025
- if (oldString === undefined && newString !== undefined) {
14026
- oldString = "";
14027
- }
14028
14026
  return {
14029
14027
  path: path2,
14030
14028
  old_string: oldString,
@@ -14431,7 +14429,10 @@ function applyToolSchemaCompat(toolCall, toolSchemaMap) {
14431
14429
  const toolSpecificArgs = normalizeToolSpecificArgs(toolCall.function.name, normalizedArgs);
14432
14430
  const schema = toolSchemaMap.get(toolCall.function.name);
14433
14431
  const sanitization = sanitizeArgumentsForSchema(toolSpecificArgs, schema);
14434
- const validation = validateToolArguments(toolCall.function.name, sanitization.args, schema, sanitization.unexpected);
14432
+ const validation = validateToolArguments(toolCall.function.name, sanitization.args, schema, sanitization.unexpected, {
14433
+ originalArgs: parsedArgs,
14434
+ writeSchema: toolSchemaMap.get("write")
14435
+ });
14435
14436
  const normalizedToolCall = {
14436
14437
  ...toolCall,
14437
14438
  function: {
@@ -14442,12 +14443,59 @@ function applyToolSchemaCompat(toolCall, toolSchemaMap) {
14442
14443
  return {
14443
14444
  toolCall: normalizedToolCall,
14444
14445
  normalizedArgs: sanitization.args,
14446
+ originalArgs: parsedArgs,
14445
14447
  originalArgKeys,
14446
14448
  normalizedArgKeys: Object.keys(sanitization.args),
14447
14449
  collisionKeys,
14448
14450
  validation
14449
14451
  };
14450
14452
  }
14453
+ function isFullFileShapedEditValidationFailure(toolName, args, validation, originalArgs, writeSchema) {
14454
+ if (toolName.toLowerCase() !== "edit" || validation.ok) {
14455
+ return false;
14456
+ }
14457
+ return buildEditFullFileHint(args, validation.missing, validation.typeErrors, {
14458
+ originalArgs,
14459
+ writeSchema
14460
+ }) !== null;
14461
+ }
14462
+ function buildWriteArguments(filePath, content, writeSchema) {
14463
+ if (!isRecord3(writeSchema)) {
14464
+ return { path: filePath, content };
14465
+ }
14466
+ const required = Array.isArray(writeSchema.required) ? writeSchema.required.filter((value) => typeof value === "string") : [];
14467
+ if (required.includes("filePath")) {
14468
+ return { filePath, content };
14469
+ }
14470
+ return { path: filePath, content };
14471
+ }
14472
+ function tryRerouteEditToWrite(toolCall, compat, allowedToolNames, toolSchemaMap) {
14473
+ if (toolCall.function.name.toLowerCase() !== "edit") {
14474
+ return null;
14475
+ }
14476
+ if (!allowedToolNames.has("write") || !toolSchemaMap.has("write")) {
14477
+ return null;
14478
+ }
14479
+ const writeSchema = toolSchemaMap.get("write");
14480
+ if (!isFullFileShapedEditValidationFailure(toolCall.function.name, compat.normalizedArgs, compat.validation, compat.originalArgs, writeSchema)) {
14481
+ return null;
14482
+ }
14483
+ const filePath = typeof compat.normalizedArgs.path === "string" && compat.normalizedArgs.path.length > 0 ? compat.normalizedArgs.path : typeof compat.normalizedArgs.filePath === "string" && compat.normalizedArgs.filePath.length > 0 ? compat.normalizedArgs.filePath : null;
14484
+ if (!filePath) {
14485
+ return null;
14486
+ }
14487
+ const content = typeof compat.normalizedArgs.new_string === "string" ? compat.normalizedArgs.new_string : typeof compat.normalizedArgs.newString === "string" ? compat.normalizedArgs.newString : typeof compat.normalizedArgs.content === "string" ? compat.normalizedArgs.content : null;
14488
+ if (content === null) {
14489
+ return null;
14490
+ }
14491
+ return {
14492
+ ...toolCall,
14493
+ function: {
14494
+ name: "write",
14495
+ arguments: JSON.stringify(buildWriteArguments(filePath, content, writeSchema))
14496
+ }
14497
+ };
14498
+ }
14451
14499
  function parseArguments(rawArguments) {
14452
14500
  try {
14453
14501
  const parsed = JSON.parse(rawArguments);
@@ -14630,7 +14678,7 @@ function sanitizeArgumentsForSchema(args, schema) {
14630
14678
  }
14631
14679
  return { args: sanitized, unexpected };
14632
14680
  }
14633
- function validateToolArguments(toolName, args, schema, unexpected) {
14681
+ function validateToolArguments(toolName, args, schema, unexpected, context = {}) {
14634
14682
  if (!isRecord3(schema)) {
14635
14683
  return {
14636
14684
  hasSchema: false,
@@ -14666,10 +14714,59 @@ function validateToolArguments(toolName, args, schema, unexpected) {
14666
14714
  missing,
14667
14715
  unexpected,
14668
14716
  typeErrors,
14669
- repairHint: ok ? undefined : buildRepairHint(toolName, missing, unexpected, typeErrors)
14717
+ repairHint: ok ? undefined : buildRepairHint(toolName, args, missing, unexpected, typeErrors, context)
14670
14718
  };
14671
14719
  }
14672
- function buildRepairHint(toolName, missing, unexpected, typeErrors) {
14720
+ function hadOldStringPropertyInPayload(args) {
14721
+ for (const key of Object.keys(args)) {
14722
+ const token = key.toLowerCase().replace(/[^a-z0-9]/g, "");
14723
+ if (token === "oldstring") {
14724
+ return true;
14725
+ }
14726
+ }
14727
+ return false;
14728
+ }
14729
+ function hasEditFilePath(args) {
14730
+ const pathValue = args.path ?? args.filePath;
14731
+ return typeof pathValue === "string" && pathValue.trim().length > 0;
14732
+ }
14733
+ function hasEditBody(args) {
14734
+ const body = args.new_string ?? args.newString ?? args.content;
14735
+ return typeof body === "string" && body.length > 0;
14736
+ }
14737
+ function writeToolExample(writeSchema) {
14738
+ if (!isRecord3(writeSchema)) {
14739
+ return "write with path and content";
14740
+ }
14741
+ const required = Array.isArray(writeSchema.required) ? writeSchema.required.filter((value) => typeof value === "string") : [];
14742
+ if (required.includes("filePath")) {
14743
+ return "write with filePath and content";
14744
+ }
14745
+ return "write with path and content";
14746
+ }
14747
+ function buildEditFullFileHint(args, missing, typeErrors, context) {
14748
+ if (typeErrors.length > 0) {
14749
+ return null;
14750
+ }
14751
+ const missingOldStringOnly = (missing.includes("old_string") || missing.includes("oldString")) && missing.every((key) => key === "old_string" || key === "oldString");
14752
+ if (!missingOldStringOnly) {
14753
+ return null;
14754
+ }
14755
+ const originalArgs = context.originalArgs ?? {};
14756
+ if (hadOldStringPropertyInPayload(originalArgs)) {
14757
+ return null;
14758
+ }
14759
+ if (!hasEditFilePath(args) || !hasEditBody(args)) {
14760
+ return null;
14761
+ }
14762
+ const example = writeToolExample(context.writeSchema);
14763
+ return `For a full file body, use ${example} instead of edit without old_string`;
14764
+ }
14765
+ function buildRepairHint(toolName, args, missing, unexpected, typeErrors, context = {}) {
14766
+ const fullFileHint = toolName.toLowerCase() === "edit" ? buildEditFullFileHint(args, missing, typeErrors, context) : null;
14767
+ if (fullFileHint) {
14768
+ return fullFileHint;
14769
+ }
14673
14770
  const hints = [];
14674
14771
  if (missing.length > 0) {
14675
14772
  hints.push(`missing required: ${missing.join(", ")}`);
@@ -14680,7 +14777,7 @@ function buildRepairHint(toolName, missing, unexpected, typeErrors) {
14680
14777
  if (typeErrors.length > 0) {
14681
14778
  hints.push(`fix type errors: ${typeErrors.join("; ")}`);
14682
14779
  }
14683
- if (toolName.toLowerCase() === "edit" && (missing.includes("old_string") || missing.includes("new_string"))) {
14780
+ if (toolName.toLowerCase() === "edit" && (missing.includes("old_string") || missing.includes("oldString") || missing.includes("new_string") || missing.includes("newString"))) {
14684
14781
  hints.push("edit requires path, old_string, and new_string");
14685
14782
  }
14686
14783
  return hints.join(" | ");
@@ -14867,6 +14964,14 @@ async function handleToolLoopEventLegacy(options) {
14867
14964
  }
14868
14965
  return { intercepted: false, skipConverter: true, terminate: validationTermination };
14869
14966
  }
14967
+ const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat, allowedToolNames, toolSchemaMap);
14968
+ if (reroutedWrite) {
14969
+ log18.debug("Rerouting malformed edit call to write (legacy)", {
14970
+ missing: compat.validation.missing
14971
+ });
14972
+ await onInterceptedToolCall(reroutedWrite);
14973
+ return { intercepted: true, skipConverter: true };
14974
+ }
14870
14975
  if (shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
14871
14976
  const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
14872
14977
  log18.debug("Emitting non-fatal schema validation hint in legacy and skipping malformed tool execution", {
@@ -15018,6 +15123,18 @@ async function handleToolLoopEventV1(options) {
15018
15123
  terminate: createSchemaValidationTermination(normalizedToolCall, compat.validation)
15019
15124
  };
15020
15125
  }
15126
+ const reroutedWrite = tryRerouteEditToWrite(normalizedToolCall, compat, allowedToolNames, toolSchemaMap);
15127
+ if (reroutedWrite) {
15128
+ log18.debug("Rerouting malformed edit call to write", {
15129
+ missing: compat.validation.missing,
15130
+ typeErrors: compat.validation.typeErrors
15131
+ });
15132
+ await onInterceptedToolCall(reroutedWrite);
15133
+ return {
15134
+ intercepted: true,
15135
+ skipConverter: true
15136
+ };
15137
+ }
15021
15138
  if (schemaValidationFailureMode === "pass_through" && shouldEmitNonFatalSchemaValidationHint(normalizedToolCall, compat.validation)) {
15022
15139
  const hintChunk = createNonFatalSchemaValidationHintChunk(responseMeta, normalizedToolCall, compat.validation);
15023
15140
  log18.debug("Emitting non-fatal schema validation hint and skipping malformed tool execution", {
@@ -15228,8 +15345,9 @@ function shouldTerminateOnSchemaValidation(toolCall, validation) {
15228
15345
  }
15229
15346
  function createNonFatalSchemaValidationHintChunk(meta, toolCall, validation) {
15230
15347
  const termination = createSchemaValidationTermination(toolCall, validation);
15231
- const hint = termination.repairHint || "Use write for full-file replacement, or provide path, old_string, and new_string for edit.";
15232
- const content = `Skipped malformed tool call "${toolCall.function.name}": ${termination.message} ${hint}`.trim();
15348
+ const fallbackHint = "Use write for full-file replacement, or provide path, old_string, and new_string for edit.";
15349
+ const suffix = termination.repairHint ? "" : ` ${fallbackHint}`;
15350
+ const content = `Skipped malformed tool call "${toolCall.function.name}": ${termination.message}${suffix}`.trim();
15233
15351
  return {
15234
15352
  id: meta.id,
15235
15353
  object: "chat.completion.chunk",
@@ -15870,25 +15988,13 @@ __export(exports_plugin, {
15870
15988
  CursorPlugin: () => CursorPlugin
15871
15989
  });
15872
15990
  import { tool as tool2 } from "@opencode-ai/plugin";
15873
- import { appendFileSync as appendFileSync3, existsSync as existsSync5, realpathSync } from "fs";
15991
+ import { realpathSync } from "fs";
15874
15992
  import { mkdir } from "fs/promises";
15875
- import { homedir as homedir5 } from "os";
15876
- import { isAbsolute, join as join6, relative, resolve as resolve2 } from "path";
15877
- function ensureDebugLogDir() {
15878
- try {
15879
- if (!existsSync5(DEBUG_LOG_DIR2)) {
15880
- mkdir(DEBUG_LOG_DIR2, { recursive: true }).catch(() => {});
15881
- }
15882
- } catch {}
15883
- }
15884
- function debugLogToFile2(message, data) {
15885
- try {
15886
- ensureDebugLogDir();
15887
- const timestamp = new Date().toISOString();
15888
- const logLine = `[${timestamp}] ${message}: ${JSON.stringify(data, null, 2)}
15889
- `;
15890
- appendFileSync3(DEBUG_LOG_FILE2, logLine);
15891
- } catch {}
15993
+ import { homedir as homedir4 } from "os";
15994
+ import { isAbsolute, join as join5, relative, resolve as resolve2 } from "path";
15995
+ function getMcpToolDefinitionName(mcpToolDefs, index) {
15996
+ const name = mcpToolDefs[index]?.function?.name;
15997
+ return typeof name === "string" && name.length > 0 ? name : undefined;
15892
15998
  }
15893
15999
  function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDefs, mcpToolSummaries, subagentNames = []) {
15894
16000
  const parts = [];
@@ -15898,27 +16004,27 @@ function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDef
15898
16004
  parts.push(`Available OpenCode tools (use via tool calls): ${names}. Original skill ids mapped as: ${mapping}. Aliases include oc_skill_* and oc_superskill_* when applicable.`);
15899
16005
  }
15900
16006
  if (mcpToolSummaries && mcpToolSummaries.length > 0) {
16007
+ const summariesWithCallNames = mcpToolSummaries.map((summary, index) => ({
16008
+ ...summary,
16009
+ callName: summary.callName ?? getMcpToolDefinitionName(mcpToolDefs, index) ?? namespaceMcpTool(summary.serverName, summary.toolName)
16010
+ }));
15901
16011
  const servers = new Map;
15902
- for (const s of mcpToolSummaries) {
16012
+ for (const s of summariesWithCallNames) {
15903
16013
  const list = servers.get(s.serverName) ?? [];
15904
16014
  list.push(s);
15905
16015
  servers.set(s.serverName, list);
15906
16016
  }
15907
16017
  const lines = [
15908
- "MCP TOOLS — Use via Shell with the `mcptool` CLI.",
15909
- "Syntax: mcptool call <server> <tool> [json-args]",
16018
+ `MCP TOOLS — Use via direct tool calls (\`${MCP_TOOL_PREFIX}<server>__<tool>\`).`,
16019
+ "These tools are exposed as first-class tool calls (e.g. mcp__filesystem__read_file).",
15910
16020
  ""
15911
16021
  ];
15912
16022
  for (const [server2, tools] of servers) {
15913
16023
  lines.push(`Server: ${server2}`);
15914
16024
  for (const t of tools) {
15915
16025
  const paramHint = t.params?.length ? ` (params: ${t.params.join(", ")})` : "";
15916
- lines.push(` - ${t.toolName}${paramHint}${t.description ? " " + t.description : ""}`);
15917
- }
15918
- if (tools.length > 0) {
15919
- const ex = tools[0];
15920
- const exArgs = ex.params?.length ? ` '{"${ex.params[0]}":"..."}'` : "";
15921
- lines.push(` Example: mcptool call ${server2} ${ex.toolName}${exArgs}`);
16026
+ const sourceHint = t.callName === t.toolName ? "" : ` (server: ${t.serverName}; tool: ${t.toolName})`;
16027
+ lines.push(` - ${t.callName}${paramHint}${t.description ? " — " + t.description : ""}${sourceHint}`);
15922
16028
  }
15923
16029
  lines.push("");
15924
16030
  }
@@ -15933,8 +16039,8 @@ function buildAvailableToolsSystemMessage(lastToolNames, lastToolMap, mcpToolDef
15933
16039
  `) : null;
15934
16040
  }
15935
16041
  async function ensurePluginDirectory() {
15936
- const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join6(homedir5(), ".config");
15937
- const pluginDir = join6(configHome, "opencode", "plugin");
16042
+ const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join5(homedir4(), ".config");
16043
+ const pluginDir = join5(configHome, "opencode", "plugin");
15938
16044
  try {
15939
16045
  await mkdir(pluginDir, { recursive: true });
15940
16046
  log20.debug("Plugin directory ensured", { path: pluginDir });
@@ -15951,8 +16057,8 @@ function getGlobalKey() {
15951
16057
  return "__opencode_cursor_proxy_server__";
15952
16058
  }
15953
16059
  function getOpenCodeConfigPrefix() {
15954
- const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join6(homedir5(), ".config");
15955
- return join6(configHome, "opencode");
16060
+ const configHome = process.env.XDG_CONFIG_HOME ? resolve2(process.env.XDG_CONFIG_HOME) : join5(homedir4(), ".config");
16061
+ return join5(configHome, "opencode");
15956
16062
  }
15957
16063
  function canonicalizePathForCompare(pathValue) {
15958
16064
  const resolvedPath = resolve2(pathValue);
@@ -16029,7 +16135,7 @@ function resolveWorkspaceDirectory(worktree, directory) {
16029
16135
  if (isAcceptableWorkspace(cwd, configPrefix)) {
16030
16136
  return cwd;
16031
16137
  }
16032
- const home = resolveCandidate(homedir5());
16138
+ const home = resolveCandidate(homedir4());
16033
16139
  if (home && !isRootPath(home)) {
16034
16140
  return home;
16035
16141
  }
@@ -16110,6 +16216,7 @@ function extractCompletionFromStream(output) {
16110
16216
  let usage;
16111
16217
  let sawAssistantPartials = false;
16112
16218
  let sawThinkingPartials = false;
16219
+ const tracker = new MixedDeltaTracker;
16113
16220
  for (const line of lines) {
16114
16221
  const event = parseStreamJsonLine(line);
16115
16222
  if (!event) {
@@ -16121,8 +16228,8 @@ function extractCompletionFromStream(output) {
16121
16228
  continue;
16122
16229
  const isPartial = typeof event.timestamp_ms === "number";
16123
16230
  if (isPartial) {
16124
- assistantText += text;
16125
16231
  sawAssistantPartials = true;
16232
+ assistantText += tracker.nextText(text);
16126
16233
  } else if (!sawAssistantPartials) {
16127
16234
  assistantText = text;
16128
16235
  }
@@ -16132,8 +16239,8 @@ function extractCompletionFromStream(output) {
16132
16239
  if (thinking) {
16133
16240
  const isPartial = typeof event.timestamp_ms === "number";
16134
16241
  if (isPartial) {
16135
- reasoningText += thinking;
16136
16242
  sawThinkingPartials = true;
16243
+ reasoningText += tracker.nextThinking(thinking);
16137
16244
  } else if (!sawThinkingPartials) {
16138
16245
  reasoningText = thinking;
16139
16246
  }
@@ -16330,7 +16437,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
16330
16437
  const messages = Array.isArray(body?.messages) ? body.messages : [];
16331
16438
  const stream = body?.stream === true;
16332
16439
  const tools = Array.isArray(body?.tools) ? body.tools : [];
16333
- debugLogToFile2("raw_request_body", {
16440
+ log20.debug("raw request body", {
16334
16441
  model: body?.model,
16335
16442
  cursorModel: body?.cursorModel,
16336
16443
  stream,
@@ -16820,7 +16927,7 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
16820
16927
  if (FORCE_TOOL_MODE) {
16821
16928
  cmd.push("--force");
16822
16929
  }
16823
- const child = spawn3(cmd[0], cmd.slice(1), {
16930
+ const child = spawn3(formatShellCommandForPlatform(cmd[0]), cmd.slice(1), {
16824
16931
  stdio: ["pipe", "pipe", "pipe"],
16825
16932
  shell: process.platform === "win32"
16826
16933
  });
@@ -16967,22 +17074,18 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
16967
17074
  child.kill();
16968
17075
  } catch {}
16969
17076
  };
16970
- child.stdout.on("data", async (chunk) => {
16971
- if (streamTerminated || res.writableEnded) {
16972
- return;
16973
- }
16974
- if (!firstTokenReceived) {
16975
- perf.mark("first-token");
16976
- firstTokenReceived = true;
16977
- }
16978
- for (const line of lineBuffer.push(chunk)) {
16979
- if (streamTerminated || res.writableEnded) {
17077
+ const chunkQueue = [];
17078
+ let draining = false;
17079
+ let childClosed = false;
17080
+ let childCloseHandled = false;
17081
+ let childExitCode = null;
17082
+ const processLines = async (lines) => {
17083
+ for (const line of lines) {
17084
+ if (streamTerminated || res.writableEnded)
16980
17085
  break;
16981
- }
16982
17086
  const event = parseStreamJsonLine(line);
16983
- if (!event) {
17087
+ if (!event)
16984
17088
  continue;
16985
- }
16986
17089
  if (isResult(event)) {
16987
17090
  usage = extractOpenAiUsageFromResult(event) ?? usage;
16988
17091
  }
@@ -17031,148 +17134,100 @@ async function ensureCursorProxyServer(workspaceDirectory, toolRouter) {
17031
17134
  }
17032
17135
  break;
17033
17136
  }
17034
- if (result.intercepted) {
17137
+ if (result.intercepted)
17035
17138
  break;
17036
- }
17037
- if (result.skipConverter) {
17139
+ if (result.skipConverter)
17038
17140
  continue;
17039
- }
17040
17141
  }
17041
- if (streamTerminated || res.writableEnded) {
17142
+ if (streamTerminated || res.writableEnded)
17042
17143
  break;
17043
- }
17044
17144
  for (const sse of converter.handleEvent(event)) {
17045
17145
  res.write(sse);
17046
17146
  }
17047
17147
  }
17048
- });
17049
- child.on("close", async (code) => {
17050
- if (streamTerminated || res.writableEnded) {
17148
+ };
17149
+ const drainQueue = async () => {
17150
+ if (draining)
17051
17151
  return;
17052
- }
17053
- for (const line of lineBuffer.flush()) {
17054
- if (streamTerminated || res.writableEnded) {
17055
- break;
17056
- }
17057
- const event = parseStreamJsonLine(line);
17058
- if (!event) {
17059
- continue;
17060
- }
17061
- if (isResult(event)) {
17062
- usage = extractOpenAiUsageFromResult(event) ?? usage;
17063
- }
17064
- if (event.type === "tool_call") {
17065
- const result = await handleToolLoopEventWithFallback({
17066
- event,
17067
- boundary: boundaryContext.getBoundary(),
17068
- boundaryMode: boundaryContext.getBoundary().mode,
17069
- autoFallbackToLegacy: ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK,
17070
- toolLoopMode: TOOL_LOOP_MODE,
17071
- allowedToolNames,
17072
- toolSchemaMap,
17073
- toolLoopGuard,
17074
- toolMapper,
17075
- toolSessionId,
17076
- shouldEmitToolUpdates: SHOULD_EMIT_TOOL_UPDATES,
17077
- proxyExecuteToolCalls: PROXY_EXECUTE_TOOL_CALLS,
17078
- suppressConverterToolEvents: SUPPRESS_CONVERTER_TOOL_EVENTS,
17079
- toolRouter,
17080
- responseMeta: { id, created, model },
17081
- passThroughTracker,
17082
- onToolUpdate: (update) => {
17083
- res.write(formatToolUpdateEvent(update));
17084
- },
17085
- onToolResult: (toolResult) => {
17086
- res.write(`data: ${JSON.stringify(toolResult)}
17087
-
17088
- `);
17089
- },
17090
- onInterceptedToolCall: (toolCall) => {
17091
- emitToolCallAndTerminate(toolCall);
17092
- },
17093
- onFallbackToLegacy: (error) => {
17094
- boundaryContext.activateLegacyFallback("handleToolLoopEvent.close", error);
17095
- }
17096
- });
17097
- if (result.terminate) {
17098
- if (!result.terminate.silent) {
17099
- emitTerminalAssistantErrorAndTerminate(result.terminate.message);
17100
- } else {
17101
- streamTerminated = true;
17102
- try {
17103
- child.kill();
17104
- } catch {}
17105
- }
17106
- break;
17107
- }
17108
- if (result.intercepted) {
17152
+ draining = true;
17153
+ try {
17154
+ while (chunkQueue.length > 0) {
17155
+ if (streamTerminated || res.writableEnded)
17109
17156
  break;
17157
+ const chunk = chunkQueue.shift();
17158
+ if (!firstTokenReceived) {
17159
+ perf.mark("first-token");
17160
+ firstTokenReceived = true;
17110
17161
  }
17111
- if (result.skipConverter) {
17112
- continue;
17113
- }
17162
+ await processLines(lineBuffer.push(chunk));
17114
17163
  }
17115
- if (streamTerminated || res.writableEnded) {
17116
- break;
17117
- }
17118
- for (const sse of converter.handleEvent(event)) {
17119
- res.write(sse);
17120
- }
17121
- }
17122
- if (streamTerminated || res.writableEnded) {
17123
- return;
17124
- }
17125
- perf.mark("request:done");
17126
- perf.summarize();
17127
- const stderrText = Buffer.concat(stderrChunks).toString().trim();
17128
- log20.debug("cursor-agent completed (node stream)", {
17129
- code,
17130
- stderrChars: stderrText.length
17131
- });
17132
- if (code !== 0) {
17133
- const errSource = stderrText || `cursor-agent exited with code ${String(code ?? "unknown")} and no output`;
17134
- const parsed = parseAgentError(errSource);
17135
- const msg = formatErrorForUser(parsed);
17136
- const errChunk = createChatCompletionChunk(id, created, model, msg, true);
17137
- res.write(`data: ${JSON.stringify(errChunk)}
17164
+ if (childClosed && !childCloseHandled && !streamTerminated && !res.writableEnded) {
17165
+ childCloseHandled = true;
17166
+ await processLines(lineBuffer.flush());
17167
+ if (streamTerminated || res.writableEnded)
17168
+ return;
17169
+ perf.mark("request:done");
17170
+ perf.summarize();
17171
+ const stderrText = Buffer.concat(stderrChunks).toString().trim();
17172
+ log20.debug("cursor-agent completed (node stream)", {
17173
+ code: childExitCode,
17174
+ stderrChars: stderrText.length
17175
+ });
17176
+ if (childExitCode !== 0) {
17177
+ const errSource = stderrText || `cursor-agent exited with code ${String(childExitCode ?? "unknown")} and no output`;
17178
+ const parsed = parseAgentError(errSource);
17179
+ const msg = formatErrorForUser(parsed);
17180
+ const errChunk = createChatCompletionChunk(id, created, model, msg, true);
17181
+ res.write(`data: ${JSON.stringify(errChunk)}
17138
17182
 
17139
17183
  `);
17140
- res.write(formatSseDone());
17141
- streamTerminated = true;
17142
- res.end();
17143
- return;
17144
- }
17145
- const passThroughSummary = passThroughTracker.getSummary();
17146
- if (passThroughSummary.hasActivity) {
17147
- await toastService.showPassThroughSummary(passThroughSummary.tools);
17148
- }
17149
- if (passThroughSummary.errors.length > 0) {
17150
- await toastService.showErrorSummary(passThroughSummary.errors);
17151
- }
17152
- const doneChunk = {
17153
- id,
17154
- object: "chat.completion.chunk",
17155
- created,
17156
- model,
17157
- choices: [
17158
- {
17159
- index: 0,
17160
- delta: {},
17161
- finish_reason: "stop"
17184
+ res.write(formatSseDone());
17185
+ streamTerminated = true;
17186
+ res.end();
17187
+ return;
17162
17188
  }
17163
- ]
17164
- };
17165
- res.write(`data: ${JSON.stringify(doneChunk)}
17189
+ const passThroughSummary = passThroughTracker.getSummary();
17190
+ if (passThroughSummary.hasActivity) {
17191
+ await toastService.showPassThroughSummary(passThroughSummary.tools);
17192
+ }
17193
+ if (passThroughSummary.errors.length > 0) {
17194
+ await toastService.showErrorSummary(passThroughSummary.errors);
17195
+ }
17196
+ const doneChunk = {
17197
+ id,
17198
+ object: "chat.completion.chunk",
17199
+ created,
17200
+ model,
17201
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
17202
+ };
17203
+ res.write(`data: ${JSON.stringify(doneChunk)}
17166
17204
 
17167
17205
  `);
17168
- if (usage) {
17169
- const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
17170
- res.write(`data: ${JSON.stringify(usageChunk)}
17206
+ if (usage) {
17207
+ const usageChunk = createChatCompletionUsageChunk(id, created, model, usage);
17208
+ res.write(`data: ${JSON.stringify(usageChunk)}
17171
17209
 
17172
17210
  `);
17211
+ }
17212
+ res.write(formatSseDone());
17213
+ streamTerminated = true;
17214
+ res.end();
17215
+ }
17216
+ } finally {
17217
+ draining = false;
17218
+ if (!streamTerminated && !res.writableEnded && (chunkQueue.length > 0 || childClosed && !childCloseHandled)) {
17219
+ drainQueue();
17220
+ }
17173
17221
  }
17174
- res.write(formatSseDone());
17175
- res.end();
17222
+ };
17223
+ child.stdout.on("data", (chunk) => {
17224
+ chunkQueue.push(Buffer.from(chunk));
17225
+ drainQueue();
17226
+ });
17227
+ child.on("close", (code) => {
17228
+ childClosed = true;
17229
+ childExitCode = code;
17230
+ drainQueue();
17176
17231
  });
17177
17232
  }
17178
17233
  } catch (error) {
@@ -17373,7 +17428,7 @@ function buildToolHookEntries(registry, fallbackBaseDir) {
17373
17428
  }
17374
17429
  return entries;
17375
17430
  }
17376
- var log20, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp", CURSOR_PROVIDER_PREFIX, CURSOR_PROXY_HOST = "127.0.0.1", CURSOR_PROXY_DEFAULT_PORT = 32124, CURSOR_PROXY_DEFAULT_BASE_URL, REUSE_EXISTING_PROXY, SESSION_WORKSPACE_CACHE_LIMIT = 200, FORCE_TOOL_MODE, EMIT_TOOL_UPDATES, FORWARD_TOOL_CALLS, TOOL_LOOP_MODE_RAW, TOOL_LOOP_MODE, TOOL_LOOP_MODE_VALID, PROVIDER_BOUNDARY_MODE_RAW, PROVIDER_BOUNDARY_MODE, PROVIDER_BOUNDARY_MODE_VALID, LEGACY_PROVIDER_BOUNDARY, PROVIDER_BOUNDARY, ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK, TOOL_LOOP_MAX_REPEAT_RAW, TOOL_LOOP_MAX_REPEAT, TOOL_LOOP_MAX_REPEAT_VALID, PROXY_EXECUTE_TOOL_CALLS, SUPPRESS_CONVERTER_TOOL_EVENTS, SHOULD_EMIT_TOOL_UPDATES, NATIVE_TOOL_HOOK_EXCLUSIONS, CursorPlugin = async ({ $, directory, worktree, client: client3, serverUrl }) => {
17431
+ var log20, CURSOR_PROVIDER_ID2 = "cursor-acp", CURSOR_PROVIDER_PREFIX, CURSOR_PROXY_HOST = "127.0.0.1", CURSOR_PROXY_DEFAULT_PORT = 32124, CURSOR_PROXY_DEFAULT_BASE_URL, REUSE_EXISTING_PROXY, SESSION_WORKSPACE_CACHE_LIMIT = 200, FORCE_TOOL_MODE, EMIT_TOOL_UPDATES, FORWARD_TOOL_CALLS, TOOL_LOOP_MODE_RAW, TOOL_LOOP_MODE, TOOL_LOOP_MODE_VALID, PROVIDER_BOUNDARY_MODE_RAW, PROVIDER_BOUNDARY_MODE, PROVIDER_BOUNDARY_MODE_VALID, LEGACY_PROVIDER_BOUNDARY, PROVIDER_BOUNDARY, ENABLE_PROVIDER_BOUNDARY_AUTOFALLBACK, TOOL_LOOP_MAX_REPEAT_RAW, TOOL_LOOP_MAX_REPEAT, TOOL_LOOP_MAX_REPEAT_VALID, PROXY_EXECUTE_TOOL_CALLS, SUPPRESS_CONVERTER_TOOL_EVENTS, SHOULD_EMIT_TOOL_UPDATES, NATIVE_TOOL_HOOK_EXCLUSIONS, CursorPlugin = async ({ $, directory, worktree, client: client3, serverUrl }) => {
17377
17432
  const workspaceDirectory = resolveWorkspaceDirectory(worktree, directory);
17378
17433
  log20.debug("Plugin initializing", {
17379
17434
  directory,
@@ -17429,6 +17484,7 @@ var log20, DEBUG_LOG_DIR2, DEBUG_LOG_FILE2, CURSOR_PROVIDER_ID2 = "cursor-acp",
17429
17484
  mcpToolSummaries = tools.map((t) => ({
17430
17485
  serverName: t.serverName,
17431
17486
  toolName: t.name,
17487
+ callName: namespaceMcpTool(t.serverName, t.name),
17432
17488
  description: t.description,
17433
17489
  params: t.inputSchema ? Object.keys(t.inputSchema.properties ?? {}) : undefined
17434
17490
  }));
@@ -17646,8 +17702,6 @@ var init_plugin = __esm(() => {
17646
17702
  init_tool_loop_guard();
17647
17703
  init_binary();
17648
17704
  log20 = createLogger("plugin");
17649
- DEBUG_LOG_DIR2 = join6(homedir5(), ".config", "opencode", "logs");
17650
- DEBUG_LOG_FILE2 = join6(DEBUG_LOG_DIR2, "tool-loop-debug.log");
17651
17705
  CURSOR_PROVIDER_PREFIX = `${CURSOR_PROVIDER_ID2}/`;
17652
17706
  CURSOR_PROXY_DEFAULT_BASE_URL = `http://${CURSOR_PROXY_HOST}:${CURSOR_PROXY_DEFAULT_PORT}/v1`;
17653
17707
  REUSE_EXISTING_PROXY = process.env.CURSOR_ACP_REUSE_EXISTING_PROXY !== "false";