Skip to content
Open
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
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@ All notable changes to the Brainfile VSCode extension will be documented in this
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.10.3] - 2026-02-10

### Added

#### Send to Agent — Cursor
- **Cursor** added as Tier 1 agent: native chat via `workbench.action.chat.open`, detected by app name

### Fixed

#### Last-used agent selection
- When saved "last used" agent is not available in current environment (e.g. Copilot in Cursor), extension now falls back to default agent and persists it instead of leaving invalid selection

## [0.10.1] - 2025-11-26

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/board/__tests__/types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
describe("board/types", () => {
describe("AgentType constants", () => {
it("AGENT_TYPES contains all expected agents", () => {
expect(AGENT_TYPES).toEqual(["copilot", "claude-code", "copy"])
expect(AGENT_TYPES).toEqual(["copilot", "claude-code", "cursor", "copy"])
})

it("AGENT_LABELS has label for each agent type", () => {
Expand Down
20 changes: 19 additions & 1 deletion src/board/agents/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface AgentProvider {
/** Display name shown in UI */
label: string

/** VS Code Codicon name for UI (e.g. "comment-discussion", "terminal", "hubot"). Optional. */
icon?: string

/** VS Code extension ID for detection */
extensionId?: string

Expand Down Expand Up @@ -87,10 +90,11 @@ export interface AgentProvider {
* Currently supported (Tier 1 - reliable native APIs):
* - GitHub Copilot (native VS Code chat API)
* - Claude Code (native VS Code chat API)
* - Cursor (native chat, detected by app name)
*
* Future consideration (Tier 2 - requires more research):
* - Cline, Roo Code, Kilo Code (Cline forks)
* - Continue, Cursor
* - Continue
*/
export const AGENT_PROVIDERS: AgentProvider[] = [
// ============================================================================
Expand All @@ -99,6 +103,7 @@ export const AGENT_PROVIDERS: AgentProvider[] = [
{
id: "copilot",
label: "GitHub Copilot",
icon: "comment-discussion",
extensionId: "github.copilot-chat",
priority: 1,
commands: {
Expand All @@ -109,6 +114,7 @@ export const AGENT_PROVIDERS: AgentProvider[] = [
{
id: "claude-code",
label: "Claude Code",
icon: "hubot",
extensionId: "anthropic.claude-code",
priority: 2,
commands: {
Expand All @@ -117,13 +123,25 @@ export const AGENT_PROVIDERS: AgentProvider[] = [
},
focusDelay: 400,
},
{
id: "cursor",
label: "Cursor",
icon: "terminal",
appNameMatch: "cursor",
priority: 3,
commands: {
openWithPrompt: "workbench.action.chat.open",
newTask: "workbench.action.chat.newChat",
},
},

// ============================================================================
// Fallback - Always available
// ============================================================================
{
id: "copy",
label: "Copy to Clipboard",
icon: "clippy",
priority: 99,
commands: {},
},
Expand Down
1 change: 1 addition & 0 deletions src/board/agents/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export class AgentRegistry {
id: provider.id,
type: provider.id, // Webview uses 'type' field
label: provider.label,
icon: provider.icon,
available: isProviderAvailable(provider),
priority: provider.priority,
}))
Expand Down
2 changes: 2 additions & 0 deletions src/board/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ export interface DetectedAgent {
id: string
type: string // Same as id, for webview compatibility
label: string
/** VS Code Codicon name for UI. Optional. */
icon?: string
available: boolean
priority: number
}
Expand Down
13 changes: 9 additions & 4 deletions src/boardEditorPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -463,14 +463,19 @@ export class BoardEditorPanel {
// Agent detection methods - using AgentRegistry
private _postAvailableAgents() {
const registry = getAgentRegistry()
const agents = registry.getAvailableAgents()
const defaultAgent = registry.getDefaultAgent()
const availableIds = new Set(agents.map((agent) => agent.id))

// Sync last used from workspace state
if (this._lastUsedAgent) {
// Only use saved last-used if that agent is currently available (e.g. Cursor has no Copilot)
if (this._lastUsedAgent && availableIds.has(this._lastUsedAgent)) {
registry.setLastUsed(this._lastUsedAgent)
} else {
this._lastUsedAgent = defaultAgent
registry.setLastUsed(defaultAgent)
this._context.workspaceState.update("brainfile.lastUsedAgent", defaultAgent)
}

const agents = registry.getAvailableAgents()
const defaultAgent = registry.getDefaultAgent()
this._panel.webview.postMessage({
type: "agentsDetected",
agents,
Expand Down
17 changes: 12 additions & 5 deletions src/boardViewProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,21 @@ export class BoardViewProvider implements vscode.WebviewViewProvider {
private postAvailableAgents() {
if (!this._view) return
const registry = getAgentRegistry()
const agents = registry.getAvailableAgents()
const defaultAgent = registry.getDefaultAgent()
const availableIds = new Set(agents.map((agent) => agent.id))

// Sync last used from workspace state
if (this._lastUsedAgent) {
// Only use saved last-used if that agent is currently available (e.g. Cursor has no Copilot)
if (this._lastUsedAgent && availableIds.has(this._lastUsedAgent)) {
registry.setLastUsed(this._lastUsedAgent)
} else {
this._lastUsedAgent = defaultAgent
registry.setLastUsed(defaultAgent)
if (this._context) {
this._context.workspaceState.update("brainfile.lastUsedAgent", defaultAgent)
}
}

const agents = registry.getAvailableAgents()
const defaultAgent = registry.getDefaultAgent()
this._view.webview.postMessage({
type: "agentsDetected",
agents,
Expand Down Expand Up @@ -2111,7 +2118,7 @@ columns:
items.push({ kind: vscode.QuickPickItemKind.Separator, label: "Send to Agent" } as any) // Type assertion due to kind property
availableAgents.forEach((agent) => {
items.push({
label: `$(debug-start) ${agent.label}`,
label: `$(${agent.icon ?? "debug-start"}) ${agent.label}`,
description: agent.type === agentRegistry.getDefaultAgent() ? "Default" : undefined,
action: "send-agent",
agentType: agent.type,
Expand Down
68 changes: 33 additions & 35 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type Task,
} from "@brainfile/core"
import * as vscode from "vscode"
import { buildAgentPrompt } from "./board"
import { buildAgentPrompt, getAgentRegistry } from "./board"
import { BoardEditorPanel, BoardEditorPanelSerializer } from "./boardEditorPanel"
import { BoardViewProvider } from "./boardViewProvider"
import { BrainfileCodeLensProvider } from "./codeLensProvider"
Expand Down Expand Up @@ -488,45 +488,43 @@ function registerCodeLensCommands(context: vscode.ExtensionContext, boardProvide
task: targetTask,
})

// Show quick pick to choose agent
const agents = [
{ label: "$(comment-discussion) Copilot", description: "Send to GitHub Copilot Chat", agent: "copilot" },
{ label: "$(terminal) Cursor", description: "Send to Cursor AI", agent: "cursor" },
{ label: "$(hubot) Claude Code", description: "Send to Claude Code terminal", agent: "claude-code" },
{ label: "$(clippy) Copy Prompt", description: "Copy to clipboard", agent: "copy" },
]
const registry = getAgentRegistry()
const availableAgents = registry.getAvailableAgents()
const defaultAgentId = registry.getDefaultAgent()

const agentItems: vscode.QuickPickItem[] = availableAgents.map((agent) => ({
label: `$(${agent.icon ?? "debug-start"}) ${agent.label}`,
description: agent.id === defaultAgentId ? "Default" : undefined,
detail: agent.id,
}))
agentItems.push({
label: "$(clippy) Copy Prompt",
description: "Copy to clipboard",
detail: "copy",
})

const selected = await vscode.window.showQuickPick(agents, {
const selected = await vscode.window.showQuickPick(agentItems, {
placeHolder: "Choose where to send the task",
})

if (!selected) return

// Send to the selected agent
switch (selected.agent) {
case "copilot":
case "cursor":
try {
await vscode.commands.executeCommand("workbench.action.chat.newChat")
await new Promise((resolve) => setTimeout(resolve, 100))
await vscode.commands.executeCommand("workbench.action.chat.open", prompt)
} catch (_err) {
await vscode.env.clipboard.writeText(prompt)
vscode.window.showInformationMessage("Prompt copied. Paste into chat.")
}
break
if (!selected || !selected.detail) return

case "claude-code": {
const escapedPrompt = prompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")
const terminal = vscode.window.createTerminal("Claude Code")
terminal.show()
terminal.sendText(`claude "${escapedPrompt}"`)
break
}
default:
await vscode.env.clipboard.writeText(prompt)
vscode.window.showInformationMessage("Prompt copied to clipboard.")
break
const agentId = selected.detail

// Claude Code: send to terminal (legacy CodeLens behavior)
if (agentId === "claude-code") {
const escapedPrompt = prompt.replace(/"/g, '\\"').replace(/\n/g, "\\n")
const terminal = vscode.window.createTerminal("Claude Code")
terminal.show()
terminal.sendText(`claude "${escapedPrompt}"`)
return
}

const result = await registry.sendToAgent(agentId, prompt)
if (!result.success) {
vscode.window.showErrorMessage(result.message || "Failed to send to agent")
} else if (result.copiedToClipboard && result.message) {
vscode.window.showInformationMessage(result.message)
}
} catch (error) {
vscode.window.showErrorMessage(`Failed to send to agent: ${error}`)
Expand Down