From 5284b10cd49b1e023c8761d1a7f15a29f45aa737 Mon Sep 17 00:00:00 2001 From: tusharmagar Date: Fri, 3 Apr 2026 14:46:32 +0530 Subject: [PATCH] Enhance Composio connection handling and improve rendering logic - Added a 'hidden' property to ComposioConnectCardData to prevent rendering of duplicate connection cards. - Updated App and ChatSidebar components to skip rendering if the card is marked as hidden. - Refactored ComposioConnectCard to utilize a ref for callback firing, ensuring onConnected is only called once. - Improved instructions for Composio integration to clarify usage and loading of the composio-integration skill. This update streamlines the user experience by avoiding duplicate connection prompts and enhances the overall clarity of integration instructions. --- apps/x/apps/renderer/src/App.tsx | 2 + .../ai-elements/composio-connect-card.tsx | 10 +- .../renderer/src/components/chat-sidebar.tsx | 1 + .../renderer/src/lib/chat-conversation.ts | 5 + .../src/application/assistant/instructions.ts | 36 +---- .../skills/composio-integration/skill.ts | 127 ++++++++++++++++++ .../src/application/assistant/skills/index.ts | 7 + 7 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 apps/x/packages/core/src/application/assistant/skills/composio-integration/skill.ts diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 4e8d4024..7ef34cd1 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -3838,6 +3838,8 @@ function App() { } const composioConnectData = getComposioConnectCardData(item) if (composioConnectData) { + // Skip rendering if this is a duplicate "already connected" card + if (composioConnectData.hidden) return null return ( (alreadyConnected ? "connected" : "idle"); const [errorMessage, setErrorMessage] = useState(null); + const didFireCallback = useRef(alreadyConnected ?? false); // Listen for composio:didConnect events useEffect(() => { @@ -38,7 +39,10 @@ export function ComposioConnectCard({ if (event.success) { setConnectionState("connected"); setErrorMessage(null); - if (!alreadyConnected) onConnected?.(toolkitSlug); + if (!didFireCallback.current) { + didFireCallback.current = true; + onConnected?.(toolkitSlug); + } } else { setConnectionState("error"); setErrorMessage(event.error || "Connection failed"); @@ -46,7 +50,7 @@ export function ComposioConnectCard({ } ); return cleanup; - }, [toolkitSlug, alreadyConnected, onConnected]); + }, [toolkitSlug, onConnected]); const handleConnect = useCallback(async () => { setConnectionState("connecting"); diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index 7747b929..160d8fb1 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -344,6 +344,7 @@ export function ChatSidebar({ } const composioConnectData = getComposioConnectCardData(item) if (composioConnectData) { + if (composioConnectData.hidden) return null return ( { const connectedToolkits = composioAccountsRepo.getConnectedToolkits(); const connectedSection = connectedToolkits.length > 0 ? `**Currently connected:** ${connectedToolkits.map(slug => CURATED_TOOLKITS.find(t => t.slug === slug)?.displayName ?? slug).join(', ')}` - : `**No services connected yet.** Use \`composio-list-toolkits\` to show available integrations, or \`composio-connect-toolkit\` to help the user connect one.`; + : `**No services connected yet.** Load the \`composio-integration\` skill to help the user connect one.`; return ` ## Composio Integrations -You can connect to external services (Gmail, Slack, GitHub, Notion, etc.) via Composio. - ${connectedSection} -**CRITICAL: NEVER say "I can't access [service]" or "I don't have access to [service]" without FIRST trying Composio.** If a user asks about ANY third-party service (LinkedIn, Gmail, GitHub, Slack, etc.), your FIRST action must be to check \`composio-list-toolkits\` or try \`composio-connect-toolkit\`. Never give up before trying. - -**Discovery & Execution Flow:** -1. When the user asks to interact with a service (e.g., "get my LinkedIn profile", "check my email", "list GitHub issues"): - a. Check if the service is connected (via \`composio-list-toolkits\` or the connected list above) - b. If NOT connected, call \`composio-connect-toolkit\` immediately — do NOT ask for confirmation - c. If connected, proceed to search and execute -2. Use \`composio-search-tools\` with SHORT keyword queries (e.g., "list issues", "send email", "get profile") — avoid long sentences. -3. Read the \`inputSchema\` from search results carefully — note which fields are in \`required\`. -4. Call \`composio-execute-tool\` with the tool slug, toolkit slug, AND all required \`arguments\`. For tools with empty \`properties: {}\`, pass \`arguments: {}\`. - -**Important:** -- Use short keyword search queries, NOT full sentences (good: "list issues", bad: "get all open issues for a GitHub repository") -- ALWAYS pass required arguments to composio-execute-tool — read the inputSchema from search results -- **If a tool call fails, fix the arguments and retry IMMEDIATELY — do NOT stop and narrate the error to the user.** -- **Multi-part requests:** When the user asks to "connect X and then do Y", complete BOTH parts. If part 1 (connect) is already done, proceed directly to part 2. -- Confirm with the user before executing tools that send messages, create items, or modify data (NOT for read-only queries or connecting) -- Connecting a toolkit is always safe — just do it when needed, don't ask permission +Load the \`composio-integration\` skill when the user asks to interact with any third-party service. NEVER say "I can't access [service]" without loading the skill and trying Composio first. `; } @@ -67,9 +48,9 @@ You're an insightful, encouraging assistant who combines meticulous clarity with ## What Rowboat Is Rowboat is an agentic assistant for everyday work - emails, meetings, projects, and people. Users give you tasks like "draft a follow-up email," "prep me for this meeting," or "summarize where we are with this project." You figure out what context you need, pull from emails and meetings, and get it done. -**Email Drafting:** When users ask you to **draft** or **compose** emails (e.g., "draft a follow-up to Monica", "write an email to John about the project"), load the \`draft-emails\` skill first. Do NOT load this skill for reading, fetching, or checking emails — use Composio tools for that instead. +**Email Drafting:** When users ask you to **draft** or **compose** emails (e.g., "draft a follow-up to Monica", "write an email to John about the project"), load the \`draft-emails\` skill first. Do NOT load this skill for reading, fetching, or checking emails — use the \`composio-integration\` skill for that instead. -**Live Email/Calendar/Service Queries:** When users ask to **read**, **fetch**, **check**, or **view** emails, calendar events, or any data from a connected service (e.g., "what's my latest email?", "check my inbox", "what meetings do I have today?"), use \`composio-search-tools\` and \`composio-execute-tool\` to query the connected service directly. Do NOT look in local \`gmail_sync/\` or \`calendar_sync/\` folders — use Composio for live data. +**Third-Party Services:** When users ask to interact with any external service (Gmail, GitHub, Slack, LinkedIn, Notion, Google Sheets, Jira, etc.) — reading emails, listing issues, sending messages, fetching profiles — load the \`composio-integration\` skill first. Do NOT look in local \`gmail_sync/\` or \`calendar_sync/\` folders for live data. **Meeting Prep:** When users ask you to prepare for a meeting, prep for a call, or brief them on attendees, load the \`meeting-prep\` skill first. It provides structured guidance for gathering context about attendees from the knowledge base and creating useful meeting briefs. @@ -210,9 +191,9 @@ Always consult this catalog first so you load the right skills before taking act - Never start a response with a heading. Lead with a sentence or two of context first. - Avoid deeply nested bullets. If nesting beyond 2 levels, restructure. -## Tool Priority: Composio First, Then MCP +## Tool Priority -For third-party services (GitHub, Gmail, Slack, etc.), use \`composio-*\` builtin tools first. Only fall back to MCP tools for capabilities Composio doesn't cover (web search, file scraping, audio). See the dynamic "Composio Integrations" section below for full details. +For third-party services (GitHub, Gmail, Slack, etc.), load the \`composio-integration\` skill. For capabilities Composio doesn't cover (web search, file scraping, audio), use MCP tools via the \`mcp-integration\` skill. ## Execution Reminders - Explore existing files and structure before creating new assets. @@ -252,10 +233,7 @@ ${runtimeContextPrompt} - \`web-search\` - Search the web. Returns rich results with full text, highlights, and metadata. The \`category\` parameter defaults to \`general\` (full web search) — only use a specific category like \`news\`, \`company\`, \`research paper\` etc. when the query is clearly about that type. For everyday queries (weather, restaurants, prices, how-to), use \`general\`. - \`app-navigation\` - Control the app UI: open notes, switch views, filter/search the knowledge base, manage saved views. **Load the \`app-navigation\` skill before using this tool.** - \`save-to-memory\` - Save observations about the user to the agent memory system. Use this proactively during conversations. -- \`composio-list-toolkits\` — List available integrations (Gmail, Slack, GitHub, etc.) and their connection status -- \`composio-search-tools\` — Search for tools by use case (e.g., "send email", "create issue"); returns tool slugs and input schemas -- \`composio-execute-tool\` — Execute a Composio tool by slug with parameters from search results -- \`composio-connect-toolkit\` — Connect a service (Gmail, Slack, GitHub, etc.) via OAuth directly from chat +- \`composio-list-toolkits\`, \`composio-search-tools\`, \`composio-execute-tool\`, \`composio-connect-toolkit\` — Composio integration tools. Load the \`composio-integration\` skill for usage guidance. **Prefer these tools whenever possible** — they work instantly with zero friction. For file operations inside \`~/.rowboat/\`, always use these instead of \`executeCommand\`. diff --git a/apps/x/packages/core/src/application/assistant/skills/composio-integration/skill.ts b/apps/x/packages/core/src/application/assistant/skills/composio-integration/skill.ts new file mode 100644 index 00000000..795daeeb --- /dev/null +++ b/apps/x/packages/core/src/application/assistant/skills/composio-integration/skill.ts @@ -0,0 +1,127 @@ +export const skill = String.raw` +# Composio Integration + +**Load this skill** when the user asks to interact with ANY third-party service — email, GitHub, Slack, LinkedIn, Notion, Jira, Google Sheets, calendar, etc. This skill provides the complete workflow for discovering, connecting, and executing Composio tools. + +## Available Tools + +| Tool | Purpose | +|------|---------| +| **composio-list-toolkits** | List all available integrations and their connection status | +| **composio-search-tools** | Search for tools by use case; returns slugs and input schemas | +| **composio-execute-tool** | Execute a tool by slug with parameters | +| **composio-connect-toolkit** | Connect a service via OAuth (opens browser) | + +## Toolkit Slugs (exact values for toolkitSlug parameter) + +| Service | Slug | +|---------|------| +| Gmail | \`gmail\` | +| Google Calendar | \`googlecalendar\` | +| Google Sheets | \`googlesheets\` | +| Google Docs | \`googledocs\` | +| Google Drive | \`googledrive\` | +| Slack | \`slack\` | +| GitHub | \`github\` | +| Notion | \`notion\` | +| Linear | \`linear\` | +| Jira | \`jira\` | +| Asana | \`asana\` | +| Trello | \`trello\` | +| HubSpot | \`hubspot\` | +| Salesforce | \`salesforce\` | +| LinkedIn | \`linkedin\` | +| X (Twitter) | \`twitter\` | +| Reddit | \`reddit\` | +| Dropbox | \`dropbox\` | +| OneDrive | \`onedrive\` | +| Microsoft Outlook | \`microsoft_outlook\` | +| Microsoft Teams | \`microsoft_teams\` | +| Calendly | \`calendly\` | +| Cal.com | \`cal\` | +| Intercom | \`intercom\` | +| Zendesk | \`zendesk\` | +| Airtable | \`airtable\` | + +**IMPORTANT:** Always use these exact slugs. Do NOT guess — e.g., Google Sheets is \`googlesheets\` (no underscore), not \`google_sheets\`. + +## Critical: Check First, Connect Second + +**BEFORE calling composio-connect-toolkit, ALWAYS check if the service is already connected.** The system prompt includes a "Currently connected" list. If the service is there, skip connecting and go straight to search + execute. + +**Flow:** +1. Check if the service is in the "Currently connected" list (in the system prompt above) +2. If **connected** → go directly to step 4 +3. If **NOT connected** → call \`composio-connect-toolkit\` once, wait for user to authenticate, then continue +4. Call \`composio-search-tools\` with SHORT keyword queries +5. Read the \`inputSchema\` from results — note \`required\` fields +6. Call \`composio-execute-tool\` with slug, toolkit, and all required arguments + +**NEVER call composio-connect-toolkit for a service that's already connected.** This creates duplicate connect cards in the UI. + +## Search Query Tips + +Use **short keyword queries**, not full sentences: + +| ✅ Good | ❌ Bad | +|---------|--------| +| "list issues" | "get all open issues for a GitHub repository" | +| "send email" | "send an email to someone using Gmail" | +| "get profile" | "fetch the authenticated user's profile details" | +| "create spreadsheet" | "create a new Google Sheets spreadsheet with data" | + +If the first search returns 0 results, try a different short query (e.g., "issues" instead of "list issues"). + +## Passing Arguments + +**ALWAYS include the \`arguments\` field** when calling \`composio-execute-tool\`, even if the tool has no required parameters. + +- Read the \`inputSchema\` from search results carefully +- Extract user-provided values into the correct fields (e.g., "rowboatlabs/rowboat" → \`owner: "rowboatlabs", repo: "rowboat"\`) +- For tools with empty \`properties: {}\`, pass \`arguments: {}\` +- For tools with required fields, pass all of them + +### Example: GitHub Issues + +User says: "Get me the open issues on rowboatlabs/rowboat" + +1. \`composio-search-tools({ query: "list issues", toolkitSlug: "github" })\` + → finds \`GITHUB_ISSUES_LIST_FOR_REPO\` with required: ["owner", "repo"] +2. \`composio-execute-tool({ toolSlug: "GITHUB_ISSUES_LIST_FOR_REPO", toolkitSlug: "github", arguments: { owner: "rowboatlabs", repo: "rowboat", state: "open", per_page: 100 } })\` + +### Example: Gmail Fetch + +User says: "What's my latest email?" + +1. \`composio-search-tools({ query: "fetch emails", toolkitSlug: "gmail" })\` + → finds \`GMAIL_FETCH_EMAILS\` +2. \`composio-execute-tool({ toolSlug: "GMAIL_FETCH_EMAILS", toolkitSlug: "gmail", arguments: { user_id: "me", max_results: 5 } })\` + +### Example: LinkedIn Profile (no-arg tool) + +User says: "Get my LinkedIn profile" + +1. \`composio-search-tools({ query: "get profile", toolkitSlug: "linkedin" })\` + → finds \`LINKEDIN_GET_MY_INFO\` with properties: {} +2. \`composio-execute-tool({ toolSlug: "LINKEDIN_GET_MY_INFO", toolkitSlug: "linkedin", arguments: {} })\` + +## Error Recovery + +- **If a tool call fails** (missing fields, 500 error): Fix the arguments and retry IMMEDIATELY. Do NOT stop and narrate the error to the user. +- **If search returns 0 results**: Try a different short query. If still 0, the tool may not exist for that service. +- **If a tool requires connection**: Call \`composio-connect-toolkit\` once, then retry after connection. + +## Multi-Part Requests + +When the user says "connect X and then do Y" — complete BOTH parts in one turn: +1. If X is already connected (check the connected list), skip to Y immediately +2. If X needs connecting, connect it, then proceed to Y after authentication + +## Confirmation Rules + +- **Read-only actions** (fetch, list, get, search): Execute without asking +- **Mutating actions** (send email, create issue, post, delete): Show the user what you're about to do and confirm before executing +- **Connecting a toolkit**: Always safe — just do it when needed +`; + +export default skill; diff --git a/apps/x/packages/core/src/application/assistant/skills/index.ts b/apps/x/packages/core/src/application/assistant/skills/index.ts index 44774d6e..ee51ecb3 100644 --- a/apps/x/packages/core/src/application/assistant/skills/index.ts +++ b/apps/x/packages/core/src/application/assistant/skills/index.ts @@ -12,6 +12,7 @@ import backgroundAgentsSkill from "./background-agents/skill.js"; import createPresentationsSkill from "./create-presentations/skill.js"; import appNavigationSkill from "./app-navigation/skill.js"; +import composioIntegrationSkill from "./composio-integration/skill.js"; const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url)); const CATALOG_PREFIX = "src/application/assistant/skills"; @@ -84,6 +85,12 @@ const definitions: SkillDefinition[] = [ summary: "Discovering, executing, and integrating MCP tools. Use this to check what external capabilities are available and execute MCP tools on behalf of users.", content: mcpIntegrationSkill, }, + { + id: "composio-integration", + title: "Composio Integration", + summary: "Interact with third-party services (Gmail, GitHub, Slack, LinkedIn, Notion, Jira, Google Sheets, etc.) via Composio. Search, connect, and execute tools.", + content: composioIntegrationSkill, + }, { id: "deletion-guardrails", title: "Deletion Guardrails",