Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions ts/packages/agents/code/src/codeActionsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,29 @@ export type ChangeColorThemeAction = {
};

export type SplitDirection = "right" | "left" | "up" | "down";
export type EditorPosition = "first" | "last" | "active";

// Split to update the current editor window into a new editor pane to the left, right, above, or below
// ACTION: Split an editor window into multiple panes showing the same file or different files side-by-side.
// This creates a new editor pane (split view) for working with multiple files simultaneously.
// USE THIS for: "split editor", "split the editor with X", "duplicate this editor to the right", "split X"
//
// Examples:
// - "split editor to the right" → splits active editor
// - "split the first editor" → splits leftmost editor
// - "split app.tsx to the left" → finds editor showing app.tsx and splits it
// - "split the last editor down" → splits rightmost editor downward
// - "split the editor with utils.ts" → finds editor showing utils.ts and splits it
export type SplitEditorAction = {
actionName: "splitEditor";
parameters: {
// e.g., "right", "left", "up", "down", only if specified by the user
// Direction to split: "right", "left", "up", "down". Only include if user specifies direction.
direction?: SplitDirection;
// Which editor to split by position. Use "first" for leftmost editor, "last" for rightmost, "active" for current editor, or a number (0-based index).
editorPosition?: EditorPosition | number;
// Which editor to split by file name. Extract the file name or pattern from user request.
// Examples: "app.tsx", "main.py", "utils", "codeActionHandler"
// Use this when user says "split X" or "split the editor with X" where X is a file name.
fileName?: string;
};
};

Expand Down
13 changes: 11 additions & 2 deletions ts/packages/agents/code/src/vscode/editorCodeActionsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,11 +160,20 @@ export type EditorActionFixProblem = {
};
};

// Action to move the cursor in a file to a specified position.
// ACTION: Move the cursor to a specific position within a file (for navigation or editing preparation).
// This moves the cursor position, NOT split/duplicate the editor view.
// USE THIS for: "go to file X", "jump to line 50", "go to function foo", "move cursor to X"
// DO NOT USE for: "split editor", "split X", "duplicate editor" (use splitEditor action instead)
//
// Examples:
// - "go to line 50" → { target: { type: "onLine", line: 50 } }
// - "jump to function main" → { target: { type: "insideFunction", name: "main" } }
// - "go to app.tsx" → { target: { type: "inFile", filePath: "app.tsx" } }
// - "move cursor to the end of file" → { target: { type: "atEndOfFile" } }
export type EditorActionMoveCursor = {
actionName: "moveCursorInFile";
parameters: {
//Target position for the cursor. Supports symbolic locations, line-based positions, or file-relative positions.
// Target position for the cursor. Supports symbolic locations, line-based positions, or file-relative positions.
target: CursorTarget;
// Optional file where the cursor should be moved. Defaults to the active editor if not provided.
file?: FileTarget;
Expand Down
166 changes: 157 additions & 9 deletions ts/packages/coda/src/handleVSCodeActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -298,38 +298,186 @@ export async function handleBaseEditorActions(
}

case "splitEditor": {
if (actionData && actionData.direction) {
switch (actionData.direction) {
console.log(
`[splitEditor] Starting with actionData:`,
JSON.stringify(actionData),
);
// Find the target editor to split
let targetEditor: vscode.TextEditor | undefined;
const editorPosition = actionData?.editorPosition;
const fileName = actionData?.fileName;
console.log(
`[splitEditor] editorPosition=${editorPosition}, fileName=${fileName}`,
);

if (fileName || editorPosition !== undefined) {
// Find target editor by fileName or editorPosition
// Use visibleTextEditors to get all currently visible editors
const allEditors = vscode.window.visibleTextEditors;
console.log(
`[splitEditor] Found ${allEditors.length} visible editors:`,
allEditors.map((e) => e.document.fileName),
);

if (fileName) {
// Search by file name (case-insensitive, partial match)
const pattern = fileName.toLowerCase();
console.log(
`[splitEditor] Searching for pattern: ${pattern}`,
);

// First try visible editors
targetEditor = allEditors.find((editor) =>
editor.document.fileName
.toLowerCase()
.includes(pattern),
);

// If not found in visible editors, search all open tabs
if (!targetEditor) {
console.log(
`[splitEditor] Not found in visible editors, searching all tabs...`,
);
for (const tabGroup of vscode.window.tabGroups.all) {
for (const tab of tabGroup.tabs) {
const input = tab.input as any;
if (input?.uri) {
const filePath =
input.uri.fsPath || input.uri.path;
if (
filePath.toLowerCase().includes(pattern)
) {
console.log(
`[splitEditor] Found tab with matching file: ${filePath}`,
);
// Open the document to make it an editor
const document =
await vscode.workspace.openTextDocument(
input.uri,
);
targetEditor =
await vscode.window.showTextDocument(
document,
{
viewColumn:
tabGroup.viewColumn,
preserveFocus: false,
},
);
break;
}
}
}
if (targetEditor) break;
}
}

if (!targetEditor) {
console.log(
`[splitEditor] No editor or tab found with pattern: ${pattern}`,
);
actionResult.handled = false;
actionResult.message = `No editor found with file: ${fileName}`;
break;
}
console.log(
`[splitEditor] Found target editor: ${targetEditor.document.fileName}`,
);
} else if (editorPosition !== undefined) {
// Search by position
if (typeof editorPosition === "number") {
targetEditor = allEditors[editorPosition];
if (!targetEditor) {
actionResult.handled = false;
actionResult.message = `No editor at position: ${editorPosition}`;
break;
}
} else if (editorPosition === "first") {
// Sort by viewColumn to get leftmost editor
const sortedEditors = [...allEditors].sort(
(a, b) => (a.viewColumn || 0) - (b.viewColumn || 0),
);
targetEditor = sortedEditors[0];
} else if (editorPosition === "last") {
// Sort by viewColumn to get rightmost editor
const sortedEditors = [...allEditors].sort(
(a, b) => (a.viewColumn || 0) - (b.viewColumn || 0),
);
targetEditor = sortedEditors[sortedEditors.length - 1];
} else if (editorPosition === "active") {
targetEditor = vscode.window.activeTextEditor;
}

if (!targetEditor) {
actionResult.handled = false;
actionResult.message = `No editor found at position: ${editorPosition}`;
break;
}
}

// Focus the target editor temporarily (only if it's not already active)
if (targetEditor !== vscode.window.activeTextEditor) {
console.log(
`[splitEditor] Focusing target editor: ${targetEditor!.document.fileName}`,
);
await vscode.window.showTextDocument(
targetEditor!.document,
{
viewColumn:
targetEditor!.viewColumn ??
vscode.ViewColumn.One,
preserveFocus: false,
},
);
}
}

// Execute the split command
const direction = actionData?.direction;
if (direction) {
switch (direction) {
case "right": {
vscode.commands.executeCommand(
await vscode.commands.executeCommand(
"workbench.action.splitEditorRight",
);
break;
}
case "left": {
vscode.commands.executeCommand(
await vscode.commands.executeCommand(
"workbench.action.splitEditorLeft",
);
break;
}
case "up": {
vscode.commands.executeCommand(
await vscode.commands.executeCommand(
"workbench.action.splitEditorUp",
);
break;
}
case "down": {
vscode.commands.executeCommand(
await vscode.commands.executeCommand(
"workbench.action.splitEditorDown",
);
break;
}
}
actionResult.message = `Split editor ${actionData.direction}`;
} else {
vscode.commands.executeCommand("workbench.action.splitEditor");
actionResult.message = "Split editor";
await vscode.commands.executeCommand(
"workbench.action.splitEditor",
);
}

// Build result message
const targetInfo = fileName
? ` (${fileName})`
: editorPosition !== undefined
? ` (${editorPosition})`
: "";
actionResult.message =
`Split editor${targetInfo} ${direction || ""}`.trim();
console.log(
`[splitEditor] Completed successfully: ${actionResult.message}`,
);
break;
}

Expand Down
28 changes: 26 additions & 2 deletions ts/packages/commandExecutor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,18 +86,40 @@ The server is configured in `.mcp.json`:

#### execute_command

Execute user commands such as playing music, managing lists, or working with calendars.
Execute user commands including music playback, list management, calendar operations, and VSCode automation.

**Parameters:**

- `request` (string): The natural language command to execute

**Examples:**

**Music & Media:**

- "play sweet emotion by aerosmith"
- "play bohemian rhapsody by queen"

**Lists & Tasks:**

- "add jelly beans to my grocery list"
- "what's on my shopping list"

**Calendar:**

- "schedule a meeting for tomorrow at 2pm"

**VSCode Automation:**

- "switch to monokai theme"
- "change theme to dark+"
- "open the explorer view"
- "create a new folder called components"
- "open file app.ts"
- "split editor to the right"
- "toggle zen mode"
- "open integrated terminal"
- "show output panel"

#### ping (debug mode)

Test server connectivity.
Expand All @@ -115,7 +137,9 @@ Command Executor MCP Server
TypeAgent Dispatcher (WebSocket)
TypeAgent Agents (Music, Lists, Calendar, etc.)
├─ TypeAgent Agents (Music, Lists, Calendar, etc.)
└─ Coda VSCode Extension (via WebSocket on port 8082)
└─ VSCode APIs (theme, editor, files, terminal, etc.)
```

The MCP server:
Expand Down
Loading
Loading