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
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const ConversationList = ({
useEffect(() => {
if (fetcher.data && fetcher.state === "idle") {
setIsLoading(false);
const newConversations = fetcher.data.conversations.filter(
const newConversations = (fetcher.data.conversations ?? []).filter(
(c) => !loadedConversationIds.current.has(c.id),
);
newConversations.forEach((c) => loadedConversationIds.current.add(c.id));
Expand Down
84 changes: 67 additions & 17 deletions apps/webapp/app/routes/home.agent.connect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
type ActionFunctionArgs,
type LoaderFunctionArgs,
} from "@remix-run/node";
import { useFetcher, useLoaderData } from "@remix-run/react";
import { Link, useFetcher, useLoaderData } from "@remix-run/react";
import { Mail, Clock, Check, Star, MessageCircle } from "lucide-react";
import { PageHeader } from "~/components/common/page-header";
import {
Expand Down Expand Up @@ -66,6 +66,18 @@ export async function loader({ request }: LoaderFunctionArgs) {
hasSlack = !!slackAccount;
}

// Fetch active MCP sources to detect connected AI providers
const activeMcpSources = workspaceId
? await prisma.mCPSession.findMany({
where: { deleted: null, workspaceId },
select: { source: true },
distinct: ["source"],
})
: [];
const connectedProviders = activeMcpSources
.map((s) => s.source?.toLowerCase() ?? "")
.filter(Boolean);

return json({
whatsappOptin,
imessageOptin,
Expand All @@ -75,6 +87,7 @@ export async function loader({ request }: LoaderFunctionArgs) {
slack: hasSlack,
email: hasEmail,
},
connectedProviders,
});
}

Expand Down Expand Up @@ -140,6 +153,34 @@ export async function action({ request }: ActionFunctionArgs) {
return json({ error: "Invalid intent" }, { status: 400 });
}

const PROVIDER_SOURCE_MAP: Record<string, string> = {
claude_code: "claude-code",
claude: "claude",
cursor: "cursor",
codex: "codex",
vscode: "vscode",
windsurf: "windsurf",
zed: "zed",
kilo_code: "kilo-code",
vscode_insiders: "vscode-insiders",
amp: "amp",
augment_code: "augment-code",
roo_code: "roo-code",
opencode: "opencode",
qwen_coder: "qwen",
copilot_cli: "copilot-cli",
copilot_coding_agent: "copilot-agent",
warp: "warp",
rovo_dev: "rovo-dev",
cline: "cline",
kiro: "kiro",
trae: "trae",
perplexity: "perplexity",
qodo_gen: "qodo-gen",
crush: "crush",
factory: "factory",
};

// Direct communication channels
const DIRECT_CHANNELS = [
{
Expand Down Expand Up @@ -214,6 +255,12 @@ function DirectChannelCard({
Default
</Badge>
)}
{isConnected && (
<Badge className="rounded bg-green-100 text-xs text-green-800">
<Check size={10} className="mr-1" />
Connected
</Badge>
)}
{isWaitlist && !isConnected && isOptedIn && (
<Badge className="rounded bg-green-100 text-xs text-green-800">
<Check size={10} />
Expand All @@ -226,7 +273,7 @@ function DirectChannelCard({
Waitlist
</Badge>
)}
{channel.status === "available" && !isDefault && (
{channel.status === "available" && !isConnected && !isDefault && (
<Badge className="rounded bg-green-100 text-xs text-green-800">
Available
</Badge>
Expand Down Expand Up @@ -265,19 +312,15 @@ function DirectChannelCard({
</Button>
)}
{channel.id === "slack" && (
<Button
variant="secondary"
className="w-full rounded"
onClick={(e) => {
e.stopPropagation();
window.open(
"https://docs.getcore.me/access-core/channels/slack",
"_blank",
);
}}
<Link
to="/home/integration/slack"
className="w-full"
onClick={(e) => e.stopPropagation()}
>
View Docs
</Button>
<Button variant="secondary" className="w-full rounded">
Connect
</Button>
</Link>
)}
{isConnected && !isDefault && onSetDefault && (
<Button
Expand Down Expand Up @@ -348,8 +391,13 @@ function EmailModal({
}

export default function Connect() {
const { whatsappOptin, imessageOptin, defaultChannel, connectedChannels } =
useLoaderData<typeof loader>();
const {
whatsappOptin,
imessageOptin,
defaultChannel,
connectedChannels,
connectedProviders,
} = useLoaderData<typeof loader>();
const [isEmailModalOpen, setIsEmailModalOpen] = useState(false);
const fetcher = useFetcher<{ success: boolean }>();
const imessageFetcher = useFetcher<{ success: boolean }>();
Expand Down Expand Up @@ -461,7 +509,9 @@ export default function Connect() {
<ProviderCard
key={provider.id}
provider={provider}
isConnected={false}
isConnected={connectedProviders.includes(
PROVIDER_SOURCE_MAP[provider.id]?.toLowerCase() ?? "",
)}
/>
))}
</div>
Expand Down
45 changes: 45 additions & 0 deletions apps/webapp/app/routes/home.integration.$slug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { PageHeader } from "~/components/common/page-header";
import { prisma } from "~/db.server";
import { scheduler, unschedule } from "~/services/oauth/scheduler";
import { Plus } from "lucide-react";
import { IntegrationRunner } from "~/services/integrations/integration-runner";
import { isBillingEnabled, isPaidPlan } from "~/config/billing.server";

export async function loader({ request, params }: LoaderFunctionArgs) {
Expand Down Expand Up @@ -56,12 +57,28 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
isPaidPlan(subscription?.planType || "FREE") ||
user.admin;

let tools: Array<{ name: string; description: string }> = [];
try {
const rawTools = await IntegrationRunner.getTools({
integrationDefinition: integration as any,
});
tools = rawTools
.filter((t: any) => t.name)
.map((t: any) => ({
name: t.name,
description: t.description ?? "",
}));
} catch {
tools = [];
}

return json({
integration,
integrationAccounts,
activeAccounts,
userId: user.id,
isAutoReadAvailable,
tools,
});
}

Expand Down Expand Up @@ -111,13 +128,15 @@ interface IntegrationDetailProps {
integrationAccounts: any;
activeAccounts: any[];
isAutoReadAvailable: boolean;
tools: Array<{ name: string; description: string }>;
}

export function IntegrationDetail({
integration,
integrationAccounts,
activeAccounts,
isAutoReadAvailable,
tools,
}: IntegrationDetailProps) {
const hasActiveAccounts = activeAccounts && activeAccounts.length > 0;

Expand Down Expand Up @@ -244,6 +263,30 @@ export function IntegrationDetail({
supportsAutoActivity={hasAutoActivity}
/>

{/* Tools Section */}
{tools && tools.length > 0 && (
<div className="mt-6 space-y-3">
<h3 className="text-lg font-medium">
Tools ({tools.length})
</h3>
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2">
{tools.map((tool) => (
<div
key={tool.name}
className="bg-background-3 rounded-lg p-3"
>
<p className="text-sm font-medium">{tool.name}</p>
{tool.description && (
<p className="text-muted-foreground mt-0.5 text-xs">
{tool.description}
</p>
)}
</div>
))}
</div>
</div>
)}

{/* MCP Authentication Section */}
<MCPAuthSection
integration={integration}
Expand All @@ -264,6 +307,7 @@ export default function IntegrationDetailWrapper() {
integrationAccounts,
activeAccounts,
isAutoReadAvailable,
tools,
} = useLoaderData<typeof loader>();

return (
Expand All @@ -272,6 +316,7 @@ export default function IntegrationDetailWrapper() {
integrationAccounts={integrationAccounts}
activeAccounts={activeAccounts}
isAutoReadAvailable={isAutoReadAvailable}
tools={tools}
/>
);
}