@oh-my-pi/pi-coding-agent 6.1.0 → 6.7.0

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.
Files changed (93) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/docs/sdk.md +1 -1
  3. package/package.json +5 -5
  4. package/scripts/generate-template.ts +6 -6
  5. package/src/cli/args.ts +3 -0
  6. package/src/core/agent-session.ts +39 -0
  7. package/src/core/bash-executor.ts +3 -3
  8. package/src/core/cursor/exec-bridge.ts +95 -88
  9. package/src/core/custom-commands/bundled/review/index.ts +142 -145
  10. package/src/core/custom-commands/bundled/wt/index.ts +68 -66
  11. package/src/core/custom-commands/loader.ts +4 -6
  12. package/src/core/custom-tools/index.ts +2 -2
  13. package/src/core/custom-tools/loader.ts +66 -61
  14. package/src/core/custom-tools/types.ts +4 -4
  15. package/src/core/custom-tools/wrapper.ts +61 -25
  16. package/src/core/event-bus.ts +19 -47
  17. package/src/core/extensions/index.ts +8 -4
  18. package/src/core/extensions/loader.ts +160 -120
  19. package/src/core/extensions/types.ts +4 -4
  20. package/src/core/extensions/wrapper.ts +149 -100
  21. package/src/core/hooks/index.ts +1 -1
  22. package/src/core/hooks/tool-wrapper.ts +96 -70
  23. package/src/core/hooks/types.ts +1 -2
  24. package/src/core/index.ts +1 -0
  25. package/src/core/mcp/index.ts +6 -2
  26. package/src/core/mcp/json-rpc.ts +88 -0
  27. package/src/core/mcp/loader.ts +22 -4
  28. package/src/core/mcp/manager.ts +202 -48
  29. package/src/core/mcp/tool-bridge.ts +143 -55
  30. package/src/core/mcp/tool-cache.ts +122 -0
  31. package/src/core/python-executor.ts +3 -9
  32. package/src/core/sdk.ts +33 -32
  33. package/src/core/session-manager.ts +30 -0
  34. package/src/core/settings-manager.ts +34 -1
  35. package/src/core/ssh/ssh-executor.ts +6 -84
  36. package/src/core/streaming-output.ts +107 -53
  37. package/src/core/tools/ask.ts +92 -93
  38. package/src/core/tools/bash.ts +103 -94
  39. package/src/core/tools/calculator.ts +41 -26
  40. package/src/core/tools/complete.ts +76 -66
  41. package/src/core/tools/context.ts +25 -25
  42. package/src/core/tools/exa/index.ts +1 -1
  43. package/src/core/tools/exa/mcp-client.ts +56 -101
  44. package/src/core/tools/find.ts +250 -253
  45. package/src/core/tools/git.ts +39 -33
  46. package/src/core/tools/grep.ts +440 -427
  47. package/src/core/tools/index.ts +62 -61
  48. package/src/core/tools/ls.ts +119 -114
  49. package/src/core/tools/lsp/clients/biome-client.ts +5 -7
  50. package/src/core/tools/lsp/clients/index.ts +4 -4
  51. package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
  52. package/src/core/tools/lsp/config.ts +2 -2
  53. package/src/core/tools/lsp/index.ts +824 -639
  54. package/src/core/tools/notebook.ts +121 -119
  55. package/src/core/tools/output.ts +163 -147
  56. package/src/core/tools/patch/applicator.ts +1100 -0
  57. package/src/core/tools/patch/diff.ts +362 -0
  58. package/src/core/tools/patch/fuzzy.ts +647 -0
  59. package/src/core/tools/patch/index.ts +430 -0
  60. package/src/core/tools/patch/normalize.ts +220 -0
  61. package/src/core/tools/patch/normative.ts +49 -0
  62. package/src/core/tools/patch/parser.ts +528 -0
  63. package/src/core/tools/patch/shared.ts +228 -0
  64. package/src/core/tools/patch/types.ts +244 -0
  65. package/src/core/tools/python.ts +139 -136
  66. package/src/core/tools/read.ts +237 -216
  67. package/src/core/tools/render-utils.ts +196 -77
  68. package/src/core/tools/renderers.ts +1 -1
  69. package/src/core/tools/ssh.ts +99 -80
  70. package/src/core/tools/task/executor.ts +11 -7
  71. package/src/core/tools/task/index.ts +352 -343
  72. package/src/core/tools/task/worker.ts +13 -23
  73. package/src/core/tools/todo-write.ts +74 -59
  74. package/src/core/tools/web-fetch.ts +54 -47
  75. package/src/core/tools/web-search/index.ts +27 -16
  76. package/src/core/tools/write.ts +89 -41
  77. package/src/core/ttsr.ts +106 -152
  78. package/src/core/voice.ts +49 -39
  79. package/src/index.ts +16 -12
  80. package/src/lib/worktree/index.ts +1 -9
  81. package/src/modes/interactive/components/diff.ts +15 -8
  82. package/src/modes/interactive/components/settings-defs.ts +24 -0
  83. package/src/modes/interactive/components/tool-execution.ts +34 -6
  84. package/src/modes/interactive/controllers/event-controller.ts +6 -19
  85. package/src/modes/interactive/controllers/input-controller.ts +1 -1
  86. package/src/modes/interactive/utils/ui-helpers.ts +5 -1
  87. package/src/modes/rpc/rpc-mode.ts +99 -81
  88. package/src/prompts/tools/patch.md +76 -0
  89. package/src/prompts/tools/read.md +1 -1
  90. package/src/prompts/tools/{edit.md → replace.md} +1 -0
  91. package/src/utils/shell.ts +0 -40
  92. package/src/core/tools/edit-diff.ts +0 -574
  93. package/src/core/tools/edit.ts +0 -326
package/CHANGELOG.md CHANGED
@@ -2,6 +2,62 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [6.7.0] - 2026-01-19
6
+
7
+ ### Added
8
+
9
+ - Normative patch generation to canonicalize edit tool output with tool call argument rewriting for session history
10
+ - Patch matching fallback variants: trimmed context, collapsed duplicates, single-line reduction, comment-prefix normalization
11
+ - Extended anchor syntax: ellipsis placeholders, `top of file`/`start of file`, `@@ line N`, nested `@@` anchors, space-separated hierarchical contexts
12
+ - Relaxed fuzzy threshold fallback and unique substring acceptance for context matching
13
+ - Added `--no-title` flag to disable automatic session title generation
14
+ - Environment variables for edit tool configuration (OMP_EDIT_VARIANT, OMP_EDIT_FUZZY, OMP_EDIT_FUZZY_THRESHOLD)
15
+ - Configurable fuzzy matching threshold setting (0.85 lenient to 0.98 strict)
16
+ - Apply-patch mode for edit tool (`edit.patchMode` setting) with create, update, delete, and rename operations
17
+ - Added MCP tool caching for faster startup with cached tool definitions
18
+
19
+ ### Changed
20
+
21
+ - Patch applicator now supports normalized input, implicit context lines, and improved indentation adjustment
22
+ - Patch operation schema uses 'op' instead of 'operation' and 'rename' instead of 'moveTo'
23
+ - Fuzzy matching tries comment-prefix normalized matches before unicode normalization
24
+ - Updated patch prompts with clearer anchor selection rules and verbatim context requirements
25
+ - Changed default behavior of read tool to omit line numbers by default
26
+ - Changed default edit tool mode to use apply-patch format instead of oldText/newText
27
+ - Converted tool implementations from factory functions to class-based architecture
28
+ - Refactored edit tool with modular patch architecture (moved from `edit/` to `patch/` module)
29
+ - Enhanced patch parsing: unified diff format, Codex-style patches, nested anchors, multi-file markers
30
+ - Improved fuzzy matching with multiple match tracking, ambiguity detection, and out-of-order hunk processing
31
+ - Better diff rendering: smarter truncation, optional line numbers, trailing newline preservation
32
+ - Improved error messages with hierarchical context display using `>` separator
33
+ - Centralized output sanitization in streaming-output module
34
+ - Enhanced MCP startup with deferred tool loading and cached fallback
35
+
36
+ ### Fixed
37
+
38
+ - Patch application handles repeated context blocks, preserves original indentation on fuzzy match
39
+ - Ambiguous context matching resolves duplicates using adjacent @@ anchor positioning
40
+ - Patch parser handles bare *** terminators, model hallucination markers, line hint ranges
41
+ - Function context matching handles signatures with and without empty parentheses
42
+ - Fixed session title generation to respect OMP_NO_TITLE environment variable
43
+ - Fixed Python module discovery to use import.meta.dir for ES module compatibility
44
+ - Fixed LSP writethrough batching to flush when delete operations complete a batch
45
+ - Fixed line number validation, BOM detection, and trailing newline preservation in patches
46
+ - Fixed hierarchical context matching and space-separated anchor parsing
47
+ - Fixed fuzzy matching to avoid infinite loops when `allowFuzzy` is disabled
48
+ - Fixed tool completion logic to only mark tools as complete when streaming is not aborted or in error state
49
+ - Fixed MCP tool path formatting to correctly display provider information
50
+
51
+ ## [6.2.0] - 2026-01-19
52
+ ### Changed
53
+
54
+ - Improved LSP batching to coalesce formatting and diagnostics for parallel edits
55
+ - Updated edit and write tools to support batched LSP operations
56
+
57
+ ### Fixed
58
+
59
+ - Coalesced LSP formatting/diagnostics for parallel edits so only the final write triggers LSP across touched files
60
+
5
61
  ## [6.1.0] - 2026-01-19
6
62
 
7
63
  ### Added
package/docs/sdk.md CHANGED
@@ -942,7 +942,7 @@ createTools // Create all tools from ToolSession
942
942
  type ToolSession // Session context for tool creation
943
943
 
944
944
  // Individual tool factories
945
- createReadTool, createBashTool, createEditTool, createWriteTool
945
+ createReadTool, createBashTool, EditTool, createWriteTool
946
946
  createGrepTool, createFindTool, createLsTool, createGitTool
947
947
 
948
948
  // Types
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "6.1.0",
3
+ "version": "6.7.0",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "ompConfig": {
@@ -40,10 +40,10 @@
40
40
  "prepublishOnly": "bun run generate-template && bun run clean && bun run build"
41
41
  },
42
42
  "dependencies": {
43
- "@oh-my-pi/pi-agent-core": "6.1.0",
44
- "@oh-my-pi/pi-ai": "6.1.0",
45
- "@oh-my-pi/pi-git-tool": "6.1.0",
46
- "@oh-my-pi/pi-tui": "6.1.0",
43
+ "@oh-my-pi/pi-agent-core": "6.7.0",
44
+ "@oh-my-pi/pi-ai": "6.7.0",
45
+ "@oh-my-pi/pi-git-tool": "6.7.0",
46
+ "@oh-my-pi/pi-tui": "6.7.0",
47
47
  "@openai/agents": "^0.3.7",
48
48
  "@sinclair/typebox": "^0.34.46",
49
49
  "ajv": "^8.17.1",
@@ -13,15 +13,15 @@ const js = await Bun.file(`${dir}template.js`).text();
13
13
 
14
14
  // Minify CSS
15
15
  const minifiedCss = css
16
- .replace(/\/\*[\s\S]*?\*\//g, "")
17
- .replace(/\s+/g, " ")
18
- .replace(/\s*([{}:;,])\s*/g, "$1")
19
- .trim();
16
+ .replace(/\/\*[\s\S]*?\*\//g, "")
17
+ .replace(/\s+/g, " ")
18
+ .replace(/\s*([{}:;,])\s*/g, "$1")
19
+ .trim();
20
20
 
21
21
  // Inline everything
22
22
  const template = html
23
- .replace("<template-css/>", `<style>${minifiedCss}</style>`)
24
- .replace("<template-js/>", `<script>${js}</script>`);
23
+ .replace("<template-css/>", `<style>${minifiedCss}</style>`)
24
+ .replace("<template-js/>", `<script>${js}</script>`);
25
25
 
26
26
  // Write generated file
27
27
  const output = `// Auto-generated by scripts/generate-template.ts - DO NOT EDIT
package/src/cli/args.ts CHANGED
@@ -40,6 +40,7 @@ export interface Args {
40
40
  noSkills?: boolean;
41
41
  skills?: string[];
42
42
  listModels?: string | true;
43
+ noTitle?: boolean;
43
44
  messages: string[];
44
45
  fileArgs: string[];
45
46
  /** Unknown flags (potentially extension flags) - map of flag name to value */
@@ -143,6 +144,8 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
143
144
  result.noExtensions = true;
144
145
  } else if (arg === "--no-skills") {
145
146
  result.noSkills = true;
147
+ } else if (arg === "--no-title") {
148
+ result.noTitle = true;
146
149
  } else if (arg === "--skills" && i + 1 < args.length) {
147
150
  // Comma-separated glob patterns for skill filtering
148
151
  result.skills = args[++i].split(",").map((s) => s.trim());
@@ -432,6 +432,18 @@ export class AgentSession {
432
432
  if (event.message.role === "assistant") {
433
433
  this._lastAssistantMessage = event.message;
434
434
  }
435
+
436
+ if (event.message.role === "toolResult") {
437
+ const { $normative, toolCallId } = event.message as {
438
+ toolName?: string;
439
+ toolCallId?: string;
440
+ details?: unknown;
441
+ $normative?: Record<string, unknown>;
442
+ };
443
+ if ($normative && toolCallId) {
444
+ await this._rewriteToolCallArgs(toolCallId, $normative);
445
+ }
446
+ }
435
447
  }
436
448
 
437
449
  // Check auto-retry and auto-compaction after agent completes
@@ -513,6 +525,33 @@ export class AgentSession {
513
525
  return undefined;
514
526
  }
515
527
 
528
+ /** Rewrite tool call arguments in agent state and persisted session history. */
529
+ private async _rewriteToolCallArgs(toolCallId: string, args: Record<string, unknown>): Promise<void> {
530
+ let updated = false;
531
+ const messages = this.agent.state.messages;
532
+ for (let i = messages.length - 1; i >= 0; i--) {
533
+ const msg = messages[i];
534
+ if (msg.role !== "assistant") continue;
535
+ const assistantMsg = msg as AssistantMessage;
536
+ if (!Array.isArray(assistantMsg.content)) continue;
537
+ for (const block of assistantMsg.content) {
538
+ if (typeof block !== "object" || block === null) continue;
539
+ if (!("type" in block) || (block as { type?: string }).type !== "toolCall") continue;
540
+ const toolCall = block as { id?: string; arguments?: Record<string, unknown> };
541
+ if (toolCall.id === toolCallId) {
542
+ toolCall.arguments = args;
543
+ updated = true;
544
+ break;
545
+ }
546
+ }
547
+ if (updated) break;
548
+ }
549
+
550
+ if (updated) {
551
+ await this.sessionManager.rewriteAssistantToolCallArgs(toolCallId, args);
552
+ }
553
+ }
554
+
516
555
  /** Emit extension events based on agent events */
517
556
  private async _emitExtensionEvent(event: AgentEvent): Promise<void> {
518
557
  if (!this._extensionRunner) return;
@@ -9,7 +9,7 @@
9
9
  import type { Subprocess } from "bun";
10
10
  import { getShellConfig, killProcessTree } from "../utils/shell";
11
11
  import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
12
- import { createOutputSink, pumpStream } from "./streaming-output";
12
+ import { OutputSink, pumpStream } from "./streaming-output";
13
13
  import type { BashOperations } from "./tools/bash";
14
14
  import { DEFAULT_MAX_BYTES } from "./tools/truncate";
15
15
  import { ScopeSignal } from "./utils";
@@ -85,7 +85,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
85
85
  killProcessTree(child.pid);
86
86
  });
87
87
 
88
- const sink = createOutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
88
+ const sink = new OutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
89
89
 
90
90
  const writer = sink.getWriter();
91
91
  try {
@@ -128,7 +128,7 @@ export async function executeBashWithOperations(
128
128
  operations: BashOperations,
129
129
  options?: BashExecutorOptions,
130
130
  ): Promise<BashResult> {
131
- const sink = createOutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
131
+ const sink = new OutputSink(DEFAULT_MAX_BYTES, DEFAULT_MAX_BYTES * 2, options?.onChunk);
132
132
  const writer = sink.getWriter();
133
133
 
134
134
  // Create a ReadableStream from the callback-based operations.exec
@@ -7,7 +7,7 @@ import type {
7
7
  AgentToolResult,
8
8
  AgentToolUpdateCallback,
9
9
  } from "@oh-my-pi/pi-agent-core";
10
- import type { CursorExecHandlers, CursorMcpCall, ToolResultMessage } from "@oh-my-pi/pi-ai";
10
+ import type { CursorMcpCall, CursorExecHandlers as ICursorExecHandlers, ToolResultMessage } from "@oh-my-pi/pi-ai";
11
11
  import { resolveToCwd } from "../tools/path-utils";
12
12
 
13
13
  interface CursorExecBridgeOptions {
@@ -143,92 +143,99 @@ function formatMcpToolErrorMessage(toolName: string, availableTools: string[]):
143
143
  return `MCP tool "${toolName}" not found. Available tools: ${list}`;
144
144
  }
145
145
 
146
- export function createCursorExecHandlers(options: CursorExecBridgeOptions): CursorExecHandlers {
147
- return {
148
- read: async (args) => {
149
- const toolCallId = decodeToolCallId(args.toolCallId);
150
- const toolResultMessage = await executeTool(options, "read", toolCallId, { path: args.path });
151
- return toolResultMessage;
152
- },
153
- ls: async (args) => {
154
- const toolCallId = decodeToolCallId(args.toolCallId);
155
- const toolResultMessage = await executeTool(options, "ls", toolCallId, { path: args.path });
156
- return toolResultMessage;
157
- },
158
- grep: async (args) => {
159
- const toolCallId = decodeToolCallId(args.toolCallId);
160
- const toolResultMessage = await executeTool(options, "grep", toolCallId, {
161
- pattern: args.pattern,
162
- path: args.path || undefined,
163
- glob: args.glob || undefined,
164
- outputMode: args.outputMode || undefined,
165
- context: args.context ?? args.contextBefore ?? args.contextAfter ?? undefined,
166
- ignoreCase: args.caseInsensitive || undefined,
167
- type: args.type || undefined,
168
- headLimit: args.headLimit ?? undefined,
169
- multiline: args.multiline || undefined,
170
- });
171
- return toolResultMessage;
172
- },
173
- write: async (args) => {
174
- const toolCallId = decodeToolCallId(args.toolCallId);
175
- const content = args.fileText ?? new TextDecoder().decode(args.fileBytes ?? new Uint8Array());
176
- const toolResultMessage = await executeTool(options, "write", toolCallId, {
177
- path: args.path,
178
- content,
179
- });
180
- return toolResultMessage;
181
- },
182
- delete: async (args) => {
183
- const toolCallId = decodeToolCallId(args.toolCallId);
184
- const toolResultMessage = await executeDelete(options, args.path, toolCallId);
185
- return toolResultMessage;
186
- },
187
- shell: async (args) => {
188
- const toolCallId = decodeToolCallId(args.toolCallId);
189
- const timeoutSeconds =
190
- args.timeout && args.timeout > 0
191
- ? args.timeout > 1000
192
- ? Math.ceil(args.timeout / 1000)
193
- : args.timeout
194
- : undefined;
195
- const toolResultMessage = await executeTool(options, "bash", toolCallId, {
196
- command: args.command,
197
- workdir: args.workingDirectory || undefined,
198
- timeout: timeoutSeconds,
199
- });
200
- return toolResultMessage;
201
- },
202
- diagnostics: async (args) => {
203
- const toolCallId = decodeToolCallId(args.toolCallId);
204
- const toolResultMessage = await executeTool(options, "lsp", toolCallId, {
205
- action: "diagnostics",
206
- file: args.path,
207
- });
208
- return toolResultMessage;
209
- },
210
- mcp: async (call: CursorMcpCall) => {
211
- const toolName = call.toolName || call.name;
212
- const toolCallId = decodeToolCallId(call.toolCallId);
213
- const tool = options.tools.get(toolName);
214
- if (!tool) {
215
- const availableTools = Array.from(options.tools.keys()).filter((name) => name.startsWith("mcp_"));
216
- const message = formatMcpToolErrorMessage(toolName, availableTools);
217
- const toolResult: ToolResultMessage = {
218
- role: "toolResult",
219
- toolCallId,
220
- toolName,
221
- content: [{ type: "text", text: message }],
222
- details: {},
223
- isError: true,
224
- timestamp: Date.now(),
225
- };
226
- return toolResult;
227
- }
146
+ export class CursorExecHandlers implements ICursorExecHandlers {
147
+ constructor(private options: CursorExecBridgeOptions) {}
228
148
 
229
- const args = Object.keys(call.args ?? {}).length > 0 ? call.args : decodeMcpArgs(call.rawArgs ?? {});
230
- const toolResultMessage = await executeTool(options, toolName, toolCallId, args);
231
- return toolResultMessage;
232
- },
233
- };
149
+ async read(args: Parameters<NonNullable<ICursorExecHandlers["read"]>>[0]) {
150
+ const toolCallId = decodeToolCallId(args.toolCallId);
151
+ const toolResultMessage = await executeTool(this.options, "read", toolCallId, { path: args.path });
152
+ return toolResultMessage;
153
+ }
154
+
155
+ async ls(args: Parameters<NonNullable<ICursorExecHandlers["ls"]>>[0]) {
156
+ const toolCallId = decodeToolCallId(args.toolCallId);
157
+ const toolResultMessage = await executeTool(this.options, "ls", toolCallId, { path: args.path });
158
+ return toolResultMessage;
159
+ }
160
+
161
+ async grep(args: Parameters<NonNullable<ICursorExecHandlers["grep"]>>[0]) {
162
+ const toolCallId = decodeToolCallId(args.toolCallId);
163
+ const toolResultMessage = await executeTool(this.options, "grep", toolCallId, {
164
+ pattern: args.pattern,
165
+ path: args.path || undefined,
166
+ glob: args.glob || undefined,
167
+ outputMode: args.outputMode || undefined,
168
+ context: args.context ?? args.contextBefore ?? args.contextAfter ?? undefined,
169
+ ignoreCase: args.caseInsensitive || undefined,
170
+ type: args.type || undefined,
171
+ headLimit: args.headLimit ?? undefined,
172
+ multiline: args.multiline || undefined,
173
+ });
174
+ return toolResultMessage;
175
+ }
176
+
177
+ async write(args: Parameters<NonNullable<ICursorExecHandlers["write"]>>[0]) {
178
+ const toolCallId = decodeToolCallId(args.toolCallId);
179
+ const content = args.fileText ?? new TextDecoder().decode(args.fileBytes ?? new Uint8Array());
180
+ const toolResultMessage = await executeTool(this.options, "write", toolCallId, {
181
+ path: args.path,
182
+ content,
183
+ });
184
+ return toolResultMessage;
185
+ }
186
+
187
+ async delete(args: Parameters<NonNullable<ICursorExecHandlers["delete"]>>[0]) {
188
+ const toolCallId = decodeToolCallId(args.toolCallId);
189
+ const toolResultMessage = await executeDelete(this.options, args.path, toolCallId);
190
+ return toolResultMessage;
191
+ }
192
+
193
+ async shell(args: Parameters<NonNullable<ICursorExecHandlers["shell"]>>[0]) {
194
+ const toolCallId = decodeToolCallId(args.toolCallId);
195
+ const timeoutSeconds =
196
+ args.timeout && args.timeout > 0
197
+ ? args.timeout > 1000
198
+ ? Math.ceil(args.timeout / 1000)
199
+ : args.timeout
200
+ : undefined;
201
+ const toolResultMessage = await executeTool(this.options, "bash", toolCallId, {
202
+ command: args.command,
203
+ workdir: args.workingDirectory || undefined,
204
+ timeout: timeoutSeconds,
205
+ });
206
+ return toolResultMessage;
207
+ }
208
+
209
+ async diagnostics(args: Parameters<NonNullable<ICursorExecHandlers["diagnostics"]>>[0]) {
210
+ const toolCallId = decodeToolCallId(args.toolCallId);
211
+ const toolResultMessage = await executeTool(this.options, "lsp", toolCallId, {
212
+ action: "diagnostics",
213
+ file: args.path,
214
+ });
215
+ return toolResultMessage;
216
+ }
217
+
218
+ async mcp(call: CursorMcpCall) {
219
+ const toolName = call.toolName || call.name;
220
+ const toolCallId = decodeToolCallId(call.toolCallId);
221
+ const tool = this.options.tools.get(toolName);
222
+ if (!tool) {
223
+ const availableTools = Array.from(this.options.tools.keys()).filter((name) => name.startsWith("mcp_"));
224
+ const message = formatMcpToolErrorMessage(toolName, availableTools);
225
+ const toolResult: ToolResultMessage = {
226
+ role: "toolResult",
227
+ toolCallId,
228
+ toolName,
229
+ content: [{ type: "text", text: message }],
230
+ details: {},
231
+ isError: true,
232
+ timestamp: Date.now(),
233
+ };
234
+ return toolResult;
235
+ }
236
+
237
+ const args = Object.keys(call.args ?? {}).length > 0 ? call.args : decodeMcpArgs(call.rawArgs ?? {});
238
+ const toolResultMessage = await executeTool(this.options, toolName, toolCallId, args);
239
+ return toolResultMessage;
240
+ }
234
241
  }