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
12 changes: 6 additions & 6 deletions .claude/commands/task-run.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
6 changes: 6 additions & 0 deletions .superset/lib/setup/args.sh
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# Setup argument parsing.

FORCE_OVERWRITE_DATA=0
SETUP_LOCAL_MCP=0

setup_print_usage() {
cat <<EOT
Usage: .superset/setup.sh [options]

Options:
-f, --force Reset superset-dev-data/ before seeding local DB
-m, --mcp Add superset-local MCP entry to .mcp.json
-h, --help Show this help message
EOT
}
Expand All @@ -23,6 +25,10 @@ setup_parse_args() {
FORCE_OVERWRITE_DATA=1
shift
;;
-m|--mcp)
SETUP_LOCAL_MCP=1
shift
;;
-h|--help)
setup_print_usage
return 2
Expand Down
7 changes: 7 additions & 0 deletions .superset/lib/setup/main.sh
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ setup_main() {
step_failed "Write .env file"
fi

# Step 10: Setup local MCP in .mcp.json (opt-in)
if [ "$SETUP_LOCAL_MCP" = "1" ]; then
if ! step_setup_local_mcp; then
step_failed "Setup local MCP"
fi
fi

# Print summary and exit with appropriate code
print_summary "Setup"
}
35 changes: 35 additions & 0 deletions .superset/lib/setup/steps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,41 @@ PORTSJSON
return 0
}

step_setup_local_mcp() {
echo "🔌 Setting up local MCP server in .mcp.json..."

local mcp_file=".mcp.json"
if [ ! -f "$mcp_file" ]; then
warn "No .mcp.json found — skipping local MCP setup"
step_skipped "Setup local MCP (no .mcp.json)"
return 0
fi

if ! command -v jq &> /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/..."

Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import {
AGENT_LABELS,
AGENT_TYPES,
type AgentType,
} from "@superset/shared/agent-command";
import { Button } from "@superset/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
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";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use the renderer/ alias instead of a relative path.

All other imports in this file (lines 27–31) use the renderer/ alias. Line 32 uses a fragile relative path instead.

🛠️ Proposed fix
-import { buildAgentCommand } from "../../../../utils/buildAgentCommand";
+import { buildAgentCommand } from "renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildAgentCommand";

As per coding guidelines: "Use alias as defined in tsconfig.json when possible" for apps/desktop/**/*.{ts,tsx}.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { buildAgentCommand } from "../../../../utils/buildAgentCommand";
import { buildAgentCommand } from "renderer/routes/_authenticated/_dashboard/tasks/$taskId/utils/buildAgentCommand";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/tasks/`$taskId/components/PropertiesSidebar/components/OpenInWorkspace/OpenInWorkspace.tsx
at line 32, The import for buildAgentCommand uses a fragile relative path;
replace it with the project alias import to match other imports in this file.
Update the import that references buildAgentCommand to use the renderer/ alias
(same style as the other imports on lines 27–31) so the symbol buildAgentCommand
is imported via the alias rather than "../../../../utils/buildAgentCommand".
Ensure the rest of the file continues to reference buildAgentCommand unchanged.

import { deriveBranchName } from "../../../../utils/deriveBranchName";

interface OpenInWorkspaceProps {
Expand All @@ -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<string | null>(
() => localStorage.getItem("lastOpenedInProjectId"),
);
const [selectedAgent, setSelectedAgent] = useState<AgentType>(() => {
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);
Expand All @@ -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,
});
}

Expand Down Expand Up @@ -168,6 +199,36 @@ export function OpenInWorkspace({ task }: OpenInWorkspaceProps) {
<HiArrowRight className="w-3.5 h-3.5" />
</Button>
</div>
<Select
value={selectedAgent}
onValueChange={(value: AgentType) => {
setSelectedAgent(value);
localStorage.setItem("lastSelectedAgent", value);
}}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue placeholder="Select agent" />
</SelectTrigger>
<SelectContent>
{AGENT_TYPES.map((agent) => {
const icon = getPresetIcon(agent, isDark);
return (
<SelectItem key={agent} value={agent}>
<span className="flex items-center gap-2">
{icon && (
<img
src={icon}
alt=""
className="size-3.5 object-contain"
/>
)}
{AGENT_LABELS[agent]}
</span>
</SelectItem>
);
})}
</SelectContent>
</Select>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {
type AgentType,
buildAgentCommand,
buildClaudeCommand,
type TaskInput,
} from "@superset/shared/agent-command";

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -20,7 +20,7 @@ const tools: ToolDefinition<any>[] = [
listProjects,
listWorkspaces,
navigateToWorkspace,
startClaudeSession,
startAgentSession,
switchWorkspace,
updateWorkspace,
];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof schema> = {
name: "start_claude_session",
export const startAgentSession: ToolDefinition<typeof schema> = {
name: "start_agent_session",
schema,
execute,
};
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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;
Expand Down
1 change: 0 additions & 1 deletion apps/desktop/src/renderer/stores/tabs/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
Loading
Loading