diff --git a/.claude/commands/task-run.md b/.claude/commands/task-run.md index a324030d651..d4816d61c2a 100644 --- a/.claude/commands/task-run.md +++ b/.claude/commands/task-run.md @@ -1,9 +1,9 @@ --- -description: Create a task, workspace, and start a Claude Code session to work on it -allowed-tools: mcp__superset__create_task, mcp__superset__list_members, mcp__superset__list_devices, mcp__superset__list_projects, mcp__superset__create_workspace, mcp__superset__start_claude_session, Bash(git config user.email) +description: Create a task, workspace, and start an AI agent session to work on it +allowed-tools: mcp__superset__create_task, mcp__superset__list_members, mcp__superset__list_devices, mcp__superset__list_projects, mcp__superset__create_workspace, mcp__superset__start_agent_session, Bash(git config user.email) --- -Create a new task in Superset, spin up a workspace, and start a Claude Code session to work on it. +Create a new task in Superset, spin up a workspace, and start an AI agent session to work on it. ## Input @@ -44,15 +44,15 @@ Run these in parallel: - Default to `feat/...` if the type is ambiguous - Create the workspace using `mcp__superset__create_workspace` -### 3. Start Claude Code session +### 3. Start AI agent session -- Start a Claude Code session using `mcp__superset__start_claude_session` with the created task ID and workspace ID +- Start an AI agent session using `mcp__superset__start_agent_session` with the created task ID and workspace ID ## Output Confirm with a summary: - Task: title, priority, slug - Workspace: name, branch -- Claude Code: running +- Agent session: running $ARGUMENTS diff --git a/.superset/lib/setup/args.sh b/.superset/lib/setup/args.sh index f1c780dd76f..c37d106f08b 100644 --- a/.superset/lib/setup/args.sh +++ b/.superset/lib/setup/args.sh @@ -1,6 +1,7 @@ # Setup argument parsing. FORCE_OVERWRITE_DATA=0 +SETUP_LOCAL_MCP=0 setup_print_usage() { cat < /dev/null; then + error "jq not available" + return 1 + fi + + local api_port="${API_PORT:-$((${SUPERSET_PORT_BASE:-3000} + 1))}" + local local_url="http://localhost:${api_port}/api/agent/mcp" + + # Add or update superset-local entry + local tmp_file="${mcp_file}.tmp.$$" + if ! jq --arg url "$local_url" '.mcpServers["superset-local"] = {"type": "http", "url": $url}' "$mcp_file" > "$tmp_file"; then + error "Failed to set local MCP entry" + rm -f "$tmp_file" + return 1 + fi + if ! mv "$tmp_file" "$mcp_file"; then + error "Failed to write $mcp_file" + rm -f "$tmp_file" + return 1 + fi + success "Local MCP set to $local_url" + + return 0 +} + step_seed_auth_token() { echo "🔑 Seeding auth token into superset-dev-data/..." diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx index 4dd4c521c88..074173d084d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx @@ -1,3 +1,8 @@ +import { + AGENT_LABELS, + AGENT_TYPES, + type AgentType, +} from "@superset/shared/agent-command"; import { Button } from "@superset/ui/button"; import { DropdownMenu, @@ -5,15 +10,27 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@superset/ui/dropdown-menu"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; import { toast } from "@superset/ui/sonner"; import { useEffect, useState } from "react"; import { HiArrowRight, HiChevronDown } from "react-icons/hi2"; +import { + getPresetIcon, + useIsDarkTheme, +} from "renderer/assets/app-icons/preset-icons"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { useCreateWorkspace } from "renderer/react-query/workspaces"; import { ProjectThumbnail } from "renderer/screens/main/components/WorkspaceSidebar/ProjectSection/ProjectThumbnail"; +import { useTabsStore } from "renderer/stores/tabs/store"; import { useWorkspaceInitStore } from "renderer/stores/workspace-init"; import type { TaskWithStatus } from "../../../../../components/TasksView/hooks/useTasksTable"; -import { buildClaudeCommand } from "../../../../utils/buildClaudeCommand"; +import { buildAgentCommand } from "../../../../utils/buildAgentCommand"; import { deriveBranchName } from "../../../../utils/deriveBranchName"; interface OpenInWorkspaceProps { @@ -24,17 +41,24 @@ export function OpenInWorkspace({ task }: OpenInWorkspaceProps) { const { data: recentProjects = [] } = electronTrpc.projects.getRecents.useQuery(); const createWorkspace = useCreateWorkspace(); + const addTab = useTabsStore((s) => s.addTab); + const setTabAutoTitle = useTabsStore((s) => s.setTabAutoTitle); + const isDark = useIsDarkTheme(); const [selectedProjectId, setSelectedProjectId] = useState( () => localStorage.getItem("lastOpenedInProjectId"), ); + const [selectedAgent, setSelectedAgent] = useState(() => { + const stored = localStorage.getItem("lastSelectedAgent"); + return stored && (AGENT_TYPES as readonly string[]).includes(stored) + ? (stored as AgentType) + : "claude"; + }); - // Default to the first recent project const effectiveProjectId = selectedProjectId ?? recentProjects[0]?.id ?? null; const selectedProject = recentProjects.find( (p) => p.id === effectiveProjectId, ); - // Sync default once projects load useEffect(() => { if (!selectedProjectId && recentProjects.length > 0) { setSelectedProjectId(recentProjects[0].id); @@ -60,27 +84,34 @@ export function OpenInWorkspace({ task }: OpenInWorkspaceProps) { branchName, }); - if (!result.wasExisting) { - const command = buildClaudeCommand({ - task: { - id: task.id, - slug: task.slug, - title: task.title, - description: task.description, - priority: task.priority, - statusName: task.status.name, - labels: task.labels, - }, - randomId: window.crypto.randomUUID(), - }); + const command = buildAgentCommand({ + task: { + id: task.id, + slug: task.slug, + title: task.title, + description: task.description, + priority: task.priority, + statusName: task.status.name, + labels: task.labels, + }, + randomId: window.crypto.randomUUID(), + agent: selectedAgent, + }); + if (result.wasExisting) { + const { tabId } = addTab(result.workspace.id, { + initialCommands: [command], + }); + setTabAutoTitle(tabId, "Agent"); + } else { const store = useWorkspaceInitStore.getState(); const pending = store.pendingTerminalSetups[result.workspace.id]; store.addPendingTerminalSetup({ workspaceId: result.workspace.id, projectId: result.projectId, - initialCommands: [...(pending?.initialCommands ?? []), command], + initialCommands: pending?.initialCommands ?? null, defaultPresets: pending?.defaultPresets, + agentCommand: command, }); } @@ -168,6 +199,36 @@ export function OpenInWorkspace({ task }: OpenInWorkspaceProps) { + ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildAgentCommand.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildAgentCommand.ts new file mode 100644 index 00000000000..f1fb8129898 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildAgentCommand.ts @@ -0,0 +1,6 @@ +export { + type AgentType, + buildAgentCommand, + buildClaudeCommand, + type TaskInput, +} from "@superset/shared/agent-command"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildClaudeCommand.ts b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildClaudeCommand.ts deleted file mode 100644 index 4eea275f03b..00000000000 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildClaudeCommand.ts +++ /dev/null @@ -1 +0,0 @@ -export { buildClaudeCommand } from "@superset/shared/claude-command"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/index.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/index.ts index 85f08e8d7f1..61d097834c4 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/index.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/index.ts @@ -5,7 +5,7 @@ import { getWorkspaceDetails } from "./get-workspace-details"; import { listProjects } from "./list-projects"; import { listWorkspaces } from "./list-workspaces"; import { navigateToWorkspace } from "./navigate-to-workspace"; -import { startClaudeSession } from "./start-claude-session"; +import { startAgentSession } from "./start-agent-session"; import { switchWorkspace } from "./switch-workspace"; import type { CommandResult, ToolContext, ToolDefinition } from "./types"; import { updateWorkspace } from "./update-workspace"; @@ -20,7 +20,7 @@ const tools: ToolDefinition[] = [ listProjects, listWorkspaces, navigateToWorkspace, - startClaudeSession, + startAgentSession, switchWorkspace, updateWorkspace, ]; diff --git a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-agent-session.ts similarity index 93% rename from apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts rename to apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-agent-session.ts index 8a63cad3a76..c5dfd5135cd 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-claude-session.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/components/AgentHooks/hooks/useCommandWatcher/tools/start-agent-session.ts @@ -76,13 +76,13 @@ async function execute( error: error instanceof Error ? error.message - : "Failed to start Claude session", + : "Failed to start agent session", }; } } -export const startClaudeSession: ToolDefinition = { - name: "start_claude_session", +export const startAgentSession: ToolDefinition = { + name: "start_agent_session", schema, execute, }; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx index 65cbe43fe9e..bc8825965e6 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/terminal/components/TerminalSettings/components/PresetsSection.tsx @@ -1,4 +1,9 @@ import type { ExecutionMode, TerminalPreset } from "@superset/local-db"; +import { + AGENT_PRESET_COMMANDS, + AGENT_PRESET_DESCRIPTIONS, + AGENT_TYPES, +} from "@superset/shared/agent-command"; import { Button } from "@superset/ui/button"; import { Label } from "@superset/ui/label"; import { Tooltip, TooltipContent, TooltipTrigger } from "@superset/ui/tooltip"; @@ -29,64 +34,15 @@ interface PresetTemplate { }; } -const PRESET_TEMPLATES: PresetTemplate[] = [ - { - name: "codex", - preset: { - name: "codex", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: [ - 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', - ], - }, - }, - { - name: "claude", - preset: { - name: "claude", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: ["claude --dangerously-skip-permissions"], - }, - }, - { - name: "gemini", - preset: { - name: "gemini", - description: "Danger mode: All permissions auto-approved", - cwd: "", - commands: ["gemini --yolo"], - }, - }, - { - name: "cursor-agent", - preset: { - name: "cursor-agent", - description: "Cursor AI agent for terminal-based coding assistance", - cwd: "", - commands: ["cursor-agent"], - }, - }, - { - name: "opencode", - preset: { - name: "opencode", - description: "OpenCode: Open-source AI coding agent", - cwd: "", - commands: ["opencode"], - }, - }, - { - name: "copilot", - preset: { - name: "copilot", - description: "GitHub Copilot: AI-powered coding in your terminal", - cwd: "", - commands: ["copilot"], - }, +const PRESET_TEMPLATES: PresetTemplate[] = AGENT_TYPES.map((agent) => ({ + name: agent, + preset: { + name: agent, + description: AGENT_PRESET_DESCRIPTIONS[agent], + cwd: "", + commands: AGENT_PRESET_COMMANDS[agent], }, -]; +})); interface PresetsSectionProps { showPresets: boolean; diff --git a/apps/desktop/src/renderer/stores/tabs/utils.test.ts b/apps/desktop/src/renderer/stores/tabs/utils.test.ts index 29daadda60d..790582fcf02 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils.test.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import type { MosaicNode } from "react-mosaic-component"; -import type { FileStatus } from "shared/changes-types"; import type { Tab } from "./types"; import { buildMultiPaneLayout, diff --git a/apps/desktop/src/renderer/stores/tabs/utils.ts b/apps/desktop/src/renderer/stores/tabs/utils.ts index b3d851c5f75..17d3c20ce59 100644 --- a/apps/desktop/src/renderer/stores/tabs/utils.ts +++ b/apps/desktop/src/renderer/stores/tabs/utils.ts @@ -1,5 +1,9 @@ import type { MosaicBranch, MosaicNode } from "react-mosaic-component"; -import { type ChangeCategory, type FileStatus, isNewFile } from "shared/changes-types"; +import { + type ChangeCategory, + type FileStatus, + isNewFile, +} from "shared/changes-types"; import { hasRenderedPreview, isImageFile } from "shared/file-types"; import type { BrowserPaneState, diff --git a/apps/docs/content/docs/mcp.mdx b/apps/docs/content/docs/mcp.mdx index d83d38468df..066243f417b 100644 --- a/apps/docs/content/docs/mcp.mdx +++ b/apps/docs/content/docs/mcp.mdx @@ -15,7 +15,7 @@ Superset provides an [MCP (Model Context Protocol)](https://modelcontextprotocol | **Workspaces** | Create, update, switch, delete, list, navigate workspaces | | **Devices** | List devices, projects, and app context | | **Organization** | List members and task statuses | -| **AI Sessions** | Start autonomous Claude sessions and subagents | +| **AI Sessions** | Start autonomous AI agent sessions (Claude, Codex, Gemini, OpenCode, Copilot, Cursor Agent) and subagents | ## Setup @@ -43,27 +43,27 @@ Run `opencode mcp add` and follow the interactive prompts: ```ansi title="terminal" opencode mcp add -┌ Add MCP server -│ -◇ Location -│ Current project -│ -◇ Enter MCP server name -│ superset -│ -◇ Select MCP server type -│ Remote -│ -◇ Enter MCP server URL -│ https://api.superset.sh/api/agent/mcp -│ -◇ Does this server require OAuth authentication? -│ Yes -│ -◇ Do you have a pre-registered client ID? -│ No -│ -└ MCP server added successfully +[2m┌[0m Add MCP server +[2m│[0m +[2m◇[0m Location +[2m│[0m [36mCurrent project[0m +[2m│[0m +[2m◇[0m Enter MCP server name +[2m│[0m [36msuperset[0m +[2m│[0m +[2m◇[0m Select MCP server type +[2m│[0m [36mRemote[0m +[2m│[0m +[2m◇[0m Enter MCP server URL +[2m│[0m [36mhttps://api.superset.sh/api/agent/mcp[0m +[2m│[0m +[2m◇[0m Does this server require OAuth authentication? +[2m│[0m [36mYes[0m +[2m│[0m +[2m◇[0m Do you have a pre-registered client ID? +[2m│[0m [36mNo[0m +[2m│[0m +[2m└[0m [32mMCP server added successfully[0m ``` @@ -240,7 +240,7 @@ API keys grant full access to your organization. Keep them secret and never comm | Tool | Description | |------|-------------| -| `start_claude_session` | Start an autonomous Claude Code session for a task. Launches Claude with the task context in the specified workspace. When `paneId` is provided, adds a new terminal pane to the tab containing that pane (subagent behavior) instead of initializing the workspace. | +| `start_agent_session` | Start an autonomous AI agent session for a task. Supports multiple agents: Claude, Codex, Gemini, OpenCode, Copilot, and Cursor Agent (defaults to Claude). Launches the agent with task context in the specified workspace. When `paneId` is provided, adds a new terminal pane to the tab containing that pane (subagent behavior) instead of initializing the workspace. | ## Example Usage @@ -251,4 +251,5 @@ Once connected, you can ask your AI agent to: - "Create a new workspace for the auth feature" - "Show me who's online in my team" - "Start a Claude session on my MacBook to work on the auth task" +- "Start a Codex session to fix the failing tests" - "Spin up a subagent to fix the failing tests" diff --git a/packages/mcp/src/tools/devices/get-workspace-details/get-workspace-details.ts b/packages/mcp/src/tools/devices/get-workspace-details/get-workspace-details.ts index d9e521ebcc1..866abdf26ff 100644 --- a/packages/mcp/src/tools/devices/get-workspace-details/get-workspace-details.ts +++ b/packages/mcp/src/tools/devices/get-workspace-details/get-workspace-details.ts @@ -7,7 +7,7 @@ export function register(server: McpServer) { "get_workspace_details", { description: - "Get detailed information about a workspace on a device, including its tabs and panes. Use this to discover pane IDs needed for start_claude_session's paneId parameter. The target device must belong to the current user.", + "Get detailed information about a workspace on a device, including its tabs and panes. Use this to discover pane IDs needed for start_agent_session's paneId parameter. The target device must belong to the current user.", inputSchema: { deviceId: z.string().describe("Target device ID"), workspaceId: z.string().describe("Workspace ID to get details for"), diff --git a/packages/mcp/src/tools/devices/start-agent-session/index.ts b/packages/mcp/src/tools/devices/start-agent-session/index.ts new file mode 100644 index 00000000000..a0be3cad15d --- /dev/null +++ b/packages/mcp/src/tools/devices/start-agent-session/index.ts @@ -0,0 +1 @@ +export { register } from "./start-agent-session"; diff --git a/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts b/packages/mcp/src/tools/devices/start-agent-session/start-agent-session.ts similarity index 71% rename from packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts rename to packages/mcp/src/tools/devices/start-agent-session/start-agent-session.ts index c42db1c70cf..4459c6c5bea 100644 --- a/packages/mcp/src/tools/devices/start-claude-session/start-claude-session.ts +++ b/packages/mcp/src/tools/devices/start-agent-session/start-agent-session.ts @@ -1,7 +1,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { db } from "@superset/db/client"; import { taskStatuses, tasks } from "@superset/db/schema"; -import { buildClaudeCommand } from "@superset/shared/claude-command"; +import { AGENT_TYPES, buildAgentCommand } from "@superset/shared/agent-command"; import { and, eq, isNull } from "drizzle-orm"; import { alias } from "drizzle-orm/pg-core"; import { z } from "zod"; @@ -44,13 +44,21 @@ function validateArgs(args: Record): { taskId: string; workspaceId: string; paneId?: string; + agent?: string; } | null { const deviceId = args.deviceId as string; const taskId = args.taskId as string; const workspaceId = args.workspaceId as string; const paneId = args.paneId as string | undefined; + const agent = args.agent as string | undefined; if (!deviceId || !taskId || !workspaceId) return null; - return { deviceId, taskId, workspaceId, ...(paneId ? { paneId } : {}) }; + return { + deviceId, + taskId, + workspaceId, + ...(paneId ? { paneId } : {}), + ...(agent ? { agent } : {}), + }; } const ERROR_ARGS_REQUIRED = { @@ -70,10 +78,10 @@ const ERROR_TASK_NOT_FOUND = { export function register(server: McpServer) { server.registerTool( - "start_claude_session", + "start_agent_session", { description: - "Start an autonomous Claude Code session for a task in an existing workspace. Launches Claude with the task context in the specified workspace. When paneId is provided, adds a new terminal pane to the tab containing that pane (subagent behavior) instead of initializing the workspace. The target device must belong to the current user.", + "Start an autonomous AI agent session for a task in an existing workspace. Launches the specified agent (defaults to Claude) with the task context in the specified workspace. When paneId is provided, adds a new terminal pane to the tab containing that pane (subagent behavior) instead of initializing the workspace. The target device must belong to the current user.", inputSchema: { deviceId: z.string().describe("Target device ID"), taskId: z.string().describe("Task ID to work on"), @@ -88,6 +96,12 @@ export function register(server: McpServer) { .describe( "Optional pane ID. When provided, adds a new pane to the tab containing this pane instead of initializing the workspace.", ), + agent: z + .enum(AGENT_TYPES) + .optional() + .describe( + 'AI agent to use: "claude", "codex", "gemini", "opencode", "copilot", or "cursor-agent". Defaults to "claude".', + ), }, }, async (args, extra) => { @@ -95,6 +109,9 @@ export function register(server: McpServer) { const validated = validateArgs(args); if (!validated) return ERROR_ARGS_REQUIRED; + const agent = + (validated.agent as (typeof AGENT_TYPES)[number]) ?? "claude"; + const task = await fetchTask({ taskId: validated.taskId, organizationId: ctx.organizationId, @@ -104,9 +121,13 @@ export function register(server: McpServer) { return executeOnDevice({ ctx, deviceId: validated.deviceId, - tool: "start_claude_session", + tool: "start_agent_session", params: { - command: buildClaudeCommand({ task, randomId: crypto.randomUUID() }), + command: buildAgentCommand({ + task, + randomId: crypto.randomUUID(), + agent, + }), name: task.slug, workspaceId: validated.workspaceId, ...(validated.paneId ? { paneId: validated.paneId } : {}), diff --git a/packages/mcp/src/tools/devices/start-claude-session/index.ts b/packages/mcp/src/tools/devices/start-claude-session/index.ts deleted file mode 100644 index 4a758bd261c..00000000000 --- a/packages/mcp/src/tools/devices/start-claude-session/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { register } from "./start-claude-session"; diff --git a/packages/mcp/src/tools/index.ts b/packages/mcp/src/tools/index.ts index 2f5e4bde332..443c8625a8f 100644 --- a/packages/mcp/src/tools/index.ts +++ b/packages/mcp/src/tools/index.ts @@ -7,7 +7,7 @@ import { register as listDevices } from "./devices/list-devices"; import { register as listProjects } from "./devices/list-projects"; import { register as listWorkspaces } from "./devices/list-workspaces"; import { register as navigateToWorkspace } from "./devices/navigate-to-workspace"; -import { register as startClaudeSession } from "./devices/start-claude-session"; +import { register as startAgentSession } from "./devices/start-agent-session"; import { register as switchWorkspace } from "./devices/switch-workspace"; import { register as updateWorkspace } from "./devices/update-workspace"; import { register as listMembers } from "./organizations/list-members"; @@ -36,7 +36,7 @@ const allTools = [ switchWorkspace, deleteWorkspace, updateWorkspace, - startClaudeSession, + startAgentSession, ]; export function registerTools(server: McpServer) { diff --git a/packages/shared/package.json b/packages/shared/package.json index 909835f3934..878937fb036 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -20,6 +20,10 @@ "types": "./src/auth/index.ts", "default": "./src/auth/index.ts" }, + "./agent-command": { + "types": "./src/agent-command.ts", + "default": "./src/agent-command.ts" + }, "./claude-command": { "types": "./src/claude-command.ts", "default": "./src/claude-command.ts" diff --git a/packages/shared/src/agent-command.ts b/packages/shared/src/agent-command.ts new file mode 100644 index 00000000000..6e90133451e --- /dev/null +++ b/packages/shared/src/agent-command.ts @@ -0,0 +1,143 @@ +export const AGENT_TYPES = [ + "claude", + "codex", + "gemini", + "opencode", + "copilot", + "cursor-agent", +] as const; + +export type AgentType = (typeof AGENT_TYPES)[number]; + +export const AGENT_LABELS: Record = { + claude: "Claude", + codex: "Codex", + gemini: "Gemini", + opencode: "OpenCode", + copilot: "Copilot", + "cursor-agent": "Cursor Agent", +}; + +export const AGENT_PRESET_COMMANDS: Record = { + claude: ["claude --dangerously-skip-permissions"], + codex: [ + 'codex -c model_reasoning_effort="high" --ask-for-approval never --sandbox danger-full-access -c model_reasoning_summary="detailed" -c model_supports_reasoning_summaries=true', + ], + gemini: ["gemini --yolo"], + opencode: ["opencode"], + copilot: ["copilot"], + "cursor-agent": ["cursor-agent"], +}; + +export const AGENT_PRESET_DESCRIPTIONS: Record = { + claude: "Danger mode: All permissions auto-approved", + codex: "Danger mode: All permissions auto-approved", + gemini: "Danger mode: All permissions auto-approved", + opencode: "OpenCode: Open-source AI coding agent", + copilot: "GitHub Copilot: AI-powered coding in your terminal", + "cursor-agent": "Cursor AI agent for terminal-based coding assistance", +}; + +export interface TaskInput { + id: string; + slug: string; + title: string; + description: string | null; + priority: string; + statusName: string | null; + labels: string[] | null; +} + +function buildPrompt(task: TaskInput): string { + const metadata = [ + `Priority: ${task.priority}`, + task.statusName && `Status: ${task.statusName}`, + task.labels?.length && `Labels: ${task.labels.join(", ")}`, + ] + .filter(Boolean) + .join("\n"); + + return `You are working on task "${task.title}" (${task.slug}). + +${metadata} + +## Task Description + +${task.description || "No description provided."} + +## Instructions + +You are running fully autonomously. Do not ask questions or wait for user feedback — make all decisions independently based on the codebase and task description. + +1. Explore the codebase to understand the relevant code and architecture +2. Create a detailed execution plan for this task including: + - Purpose and scope of the changes + - Key assumptions + - Concrete implementation steps with specific files to modify + - How to validate the changes work correctly +3. Implement the plan +4. Verify your changes work correctly (run relevant tests, typecheck, lint) +5. When done, use the Superset MCP \`update_task\` tool to update task "${task.id}" with a summary of what was done`; +} + +function buildHeredoc( + prompt: string, + delimiter: string, + command: string, + suffix?: string, +): string { + const closing = suffix ? `)" ${suffix}` : ')"'; + return [ + `${command} "$(cat <<'${delimiter}'`, + prompt, + delimiter, + closing, + ].join("\n"); +} + +const AGENT_COMMANDS: Record< + AgentType, + (prompt: string, delimiter: string) => string +> = { + claude: (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "claude --dangerously-skip-permissions"), + codex: (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "codex --full-auto"), + gemini: (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "gemini --yolo"), + opencode: (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "opencode --prompt"), + copilot: (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "copilot -i", "--yolo"), + "cursor-agent": (prompt, delimiter) => + buildHeredoc(prompt, delimiter, "cursor-agent --yolo"), +}; + +export function buildAgentCommand({ + task, + randomId, + agent = "claude", +}: { + task: TaskInput; + randomId: string; + agent?: AgentType; +}): string { + const prompt = buildPrompt(task); + let delimiter = `SUPERSET_PROMPT_${randomId.replaceAll("-", "")}`; + while (prompt.includes(delimiter)) { + delimiter = `${delimiter}_X`; + } + const builder = AGENT_COMMANDS[agent]; + return builder(prompt, delimiter); +} + +/** @deprecated Use `buildAgentCommand` instead */ +export function buildClaudeCommand({ + task, + randomId, +}: { + task: TaskInput; + randomId: string; +}): string { + return buildAgentCommand({ task, randomId, agent: "claude" }); +} diff --git a/packages/shared/src/claude-command.ts b/packages/shared/src/claude-command.ts index f409be6becb..1eb61781c32 100644 --- a/packages/shared/src/claude-command.ts +++ b/packages/shared/src/claude-command.ts @@ -1,56 +1,11 @@ -interface TaskInput { - id: string; - slug: string; - title: string; - description: string | null; - priority: string; - statusName: string | null; - labels: string[] | null; -} - -export function buildClaudeCommand({ - task, - randomId, -}: { - task: TaskInput; - randomId: string; -}): string { - const metadata = [ - `Priority: ${task.priority}`, - task.statusName && `Status: ${task.statusName}`, - task.labels?.length && `Labels: ${task.labels.join(", ")}`, - ] - .filter(Boolean) - .join("\n"); - - const prompt = `You are working on task "${task.title}" (${task.slug}). - -${metadata} - -## Task Description - -${task.description || "No description provided."} - -## Instructions - -You are running fully autonomously. Do not ask questions or wait for user feedback — make all decisions independently based on the codebase and task description. - -1. Explore the codebase to understand the relevant code and architecture -2. Create a detailed execution plan for this task including: - - Purpose and scope of the changes - - Key assumptions - - Concrete implementation steps with specific files to modify - - How to validate the changes work correctly -3. Implement the plan -4. Verify your changes work correctly (run relevant tests, typecheck, lint) -5. When done, use the Superset MCP \`update_task\` tool to update task "${task.id}" with a summary of what was done`; - - const delimiter = `SUPERSET_PROMPT_${randomId.replaceAll("-", "")}`; - - return [ - `claude --dangerously-skip-permissions "$(cat <<'${delimiter}'`, - prompt, - delimiter, - ')"', - ].join("\n"); -} +/** @deprecated Use `@superset/shared/agent-command` instead */ +export { + AGENT_LABELS, + AGENT_PRESET_COMMANDS, + AGENT_PRESET_DESCRIPTIONS, + AGENT_TYPES, + type AgentType, + buildAgentCommand, + buildClaudeCommand, + type TaskInput, +} from "./agent-command";