diff --git a/apps/x/apps/main/src/ipc.ts b/apps/x/apps/main/src/ipc.ts index 0111ccea..e05b57b3 100644 --- a/apps/x/apps/main/src/ipc.ts +++ b/apps/x/apps/main/src/ipc.ts @@ -34,7 +34,6 @@ import { triggerSync as triggerGranolaSync } from '@x/core/dist/knowledge/granol import { ISlackConfigRepo } from '@x/core/dist/slack/repo.js'; import { isOnboardingComplete, markOnboardingComplete } from '@x/core/dist/config/note_creation_config.js'; import * as composioHandler from './composio-handler.js'; -import { setConnectionInitiator } from '@x/core/dist/composio/connection-bridge.js'; import { IAgentScheduleRepo } from '@x/core/dist/agent-schedule/repo.js'; import { IAgentScheduleStateRepo } from '@x/core/dist/agent-schedule/state-repo.js'; import { triggerRun as triggerAgentScheduleRun } from '@x/core/dist/agent-schedule/runner.js'; @@ -377,9 +376,6 @@ export function setupIpcHandlers() { // Forward knowledge commit events to renderer for panel refresh versionHistory.onCommit(() => emitKnowledgeCommitEvent()); - // Wire the connection bridge so builtin tools (in core) can trigger OAuth (in main) - setConnectionInitiator(composioHandler.initiateConnection); - registerIpcHandlers({ 'app:getVersions': async () => { // args is null for this channel (no request payload) diff --git a/apps/x/apps/renderer/src/App.tsx b/apps/x/apps/renderer/src/App.tsx index 0bdc08a8..4e8d4024 100644 --- a/apps/x/apps/renderer/src/App.tsx +++ b/apps/x/apps/renderer/src/App.tsx @@ -69,7 +69,7 @@ import { getWebSearchCardData, getAppActionCardData, getComposioConnectCardData, - getComposioActionCardData, + getToolDisplayName, inferRunTitleFromMessage, isChatMessage, isErrorMessage, @@ -3849,8 +3849,7 @@ function App() { /> ) } - const composioActionData = getComposioActionCardData(item) - const toolTitle = composioActionData ? composioActionData.label : item.name + const toolTitle = getToolDisplayName(item) const errorText = item.status === 'error' ? 'Tool error' : '' const output = normalizeToolOutput(item.result, item.status) const input = normalizeToolInput(item.input) diff --git a/apps/x/apps/renderer/src/components/ai-elements/composio-connect-card.tsx b/apps/x/apps/renderer/src/components/ai-elements/composio-connect-card.tsx index 434ce9fa..e07b9487 100644 --- a/apps/x/apps/renderer/src/components/ai-elements/composio-connect-card.tsx +++ b/apps/x/apps/renderer/src/components/ai-elements/composio-connect-card.tsx @@ -5,7 +5,6 @@ import { CheckCircleIcon, Link2Icon, LoaderIcon, - WrenchIcon, XCircleIcon, } from "lucide-react"; import { Button } from "@/components/ui/button"; @@ -67,18 +66,21 @@ export function ComposioConnectCard({ }, [toolkitSlug]); const isToolRunning = status === "pending" || status === "running"; + const displayName = toolkitDisplayName || toolkitSlug; return (
+ {/* Toolkit initial */}
- + + {displayName.charAt(0).toUpperCase()} +
+ {/* Name & status */}
- - {toolkitDisplayName || toolkitSlug} - + {displayName} {connectionState === "connected" && ( Connected @@ -93,6 +95,7 @@ export function ComposioConnectCard({ )}
+ {/* Action area */} {connectionState === "connected" ? ( ) : connectionState === "connecting" ? ( diff --git a/apps/x/apps/renderer/src/components/chat-sidebar.tsx b/apps/x/apps/renderer/src/components/chat-sidebar.tsx index f5bf3bb6..7747b929 100644 --- a/apps/x/apps/renderer/src/components/chat-sidebar.tsx +++ b/apps/x/apps/renderer/src/components/chat-sidebar.tsx @@ -36,7 +36,7 @@ import { createEmptyChatTabViewState, getWebSearchCardData, getComposioConnectCardData, - getComposioActionCardData, + getToolDisplayName, isChatMessage, isErrorMessage, isToolCall, @@ -355,8 +355,7 @@ export function ChatSidebar({ /> ) } - const composioActionData = getComposioActionCardData(item) - const toolTitle = composioActionData ? composioActionData.label : item.name + const toolTitle = getToolDisplayName(item) const errorText = item.status === 'error' ? 'Tool error' : '' const output = normalizeToolOutput(item.result, item.status) const input = normalizeToolInput(item.input) diff --git a/apps/x/apps/renderer/src/lib/chat-conversation.ts b/apps/x/apps/renderer/src/lib/chat-conversation.ts index 863b23ed..83a2da50 100644 --- a/apps/x/apps/renderer/src/lib/chat-conversation.ts +++ b/apps/x/apps/renderer/src/lib/chat-conversation.ts @@ -278,6 +278,51 @@ export const getComposioConnectCardData = (tool: ToolCall): ComposioConnectCardD } } +// Human-friendly display names for builtin tools +const TOOL_DISPLAY_NAMES: Record = { + 'workspace-readFile': 'Reading file', + 'workspace-writeFile': 'Writing file', + 'workspace-edit': 'Editing file', + 'workspace-readdir': 'Reading directory', + 'workspace-exists': 'Checking path', + 'workspace-stat': 'Getting file info', + 'workspace-glob': 'Finding files', + 'workspace-grep': 'Searching files', + 'workspace-mkdir': 'Creating directory', + 'workspace-rename': 'Renaming', + 'workspace-copy': 'Copying file', + 'workspace-remove': 'Removing', + 'workspace-getRoot': 'Getting workspace root', + 'loadSkill': 'Loading skill', + 'parseFile': 'Parsing file', + 'LLMParse': 'Extracting content', + 'analyzeAgent': 'Analyzing agent', + 'executeCommand': 'Running command', + 'addMcpServer': 'Adding MCP server', + 'listMcpServers': 'Listing MCP servers', + 'listMcpTools': 'Listing MCP tools', + 'executeMcpTool': 'Running MCP tool', + 'web-search': 'Searching the web', + 'save-to-memory': 'Saving to memory', + 'app-navigation': 'Navigating app', + 'composio-list-toolkits': 'Listing integrations', + 'composio-search-tools': 'Searching tools', + 'composio-execute-tool': 'Running tool', + 'composio-connect-toolkit': 'Connecting service', +} + +/** + * Get a human-friendly display name for a tool call. + * For Composio tools, returns a contextual label (e.g., "Found 3 tools for 'send email' in Gmail"). + * For builtin tools, returns a static friendly name (e.g., "Reading file"). + * Falls back to the raw tool name if no mapping exists. + */ +export const getToolDisplayName = (tool: ToolCall): string => { + const composioData = getComposioActionCardData(tool) + if (composioData) return composioData.label + return TOOL_DISPLAY_NAMES[tool.name] || tool.name +} + // Composio action card data (for search, execute, list tools) export type ComposioActionCardData = { actionType: 'search' | 'execute' | 'list' diff --git a/apps/x/packages/core/src/application/lib/builtin-tools.ts b/apps/x/packages/core/src/application/lib/builtin-tools.ts index 3f82afdd..404db713 100644 --- a/apps/x/packages/core/src/application/lib/builtin-tools.ts +++ b/apps/x/packages/core/src/application/lib/builtin-tools.ts @@ -15,7 +15,6 @@ import { WorkDir } from "../../config/config.js"; import { composioAccountsRepo } from "../../composio/repo.js"; import { executeAction as executeComposioAction, isConfigured as isComposioConfigured, searchTools as searchComposioTools } from "../../composio/client.js"; import { CURATED_TOOLKITS, CURATED_TOOLKIT_SLUGS } from "@x/shared/dist/composio.js"; -import { getConnectionInitiator } from "../../composio/connection-bridge.js"; import type { ToolContext } from "./exec-tool.js"; import { generateText } from "ai"; import { createProvider } from "../../models/models.js"; @@ -1292,7 +1291,7 @@ export const BuiltinTools: z.infer = { }, 'composio-connect-toolkit': { - description: 'Connect a Composio service (Gmail, Slack, GitHub, etc.) via OAuth. Opens the user\'s browser for authentication. After authenticating, the user can use tools from that service.', + description: 'Connect a Composio service (Gmail, Slack, GitHub, etc.) via OAuth. Shows a connect card for the user to authenticate.', inputSchema: z.object({ toolkitSlug: z.string().describe('The toolkit slug to connect (e.g., "gmail", "github", "slack", "notion")'), }), @@ -1315,35 +1314,13 @@ export const BuiltinTools: z.infer = { }; } - // Use the connection bridge to trigger OAuth - const initiator = getConnectionInitiator(); - if (!initiator) { - return { - success: false, - error: 'Connection system not available. Please try connecting via Settings > Tools Library instead.', - }; - } - - try { - const result = await initiator(toolkitSlug); - if (result.success) { - const toolkit = CURATED_TOOLKITS.find(t => t.slug === toolkitSlug); - return { - success: true, - message: `Opening browser to authenticate with ${toolkit?.displayName ?? toolkitSlug}. Please complete the authentication in your browser, then let me know when you're done.`, - }; - } - return { - success: false, - error: result.error || 'Failed to initiate connection', - }; - } catch (error) { - const message = error instanceof Error ? error.message : String(error); - return { - success: false, - error: `Connection failed: ${message}`, - }; - } + // Return signal — the UI renders a ComposioConnectCard with a Connect button. + // OAuth only starts when the user clicks that button. + const toolkit = CURATED_TOOLKITS.find(t => t.slug === toolkitSlug); + return { + success: true, + message: `Please connect ${toolkit?.displayName ?? toolkitSlug} to continue.`, + }; }, isAvailable: async () => isComposioConfigured(), }, diff --git a/apps/x/packages/core/src/composio/connection-bridge.ts b/apps/x/packages/core/src/composio/connection-bridge.ts deleted file mode 100644 index cf6c5d6c..00000000 --- a/apps/x/packages/core/src/composio/connection-bridge.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Connection bridge for Composio toolkit OAuth. - * - * Builtin tools run in the core package which cannot import Electron-specific - * code from the main process. This module provides a callback registry so the - * main process can register its `initiateConnection` function at startup, and - * builtin tools can call it at runtime. - */ - -type ConnectionInitiator = (toolkitSlug: string) => Promise<{ - success: boolean; - redirectUrl?: string; - connectedAccountId?: string; - error?: string; -}>; - -let connectionInitiator: ConnectionInitiator | null = null; - -/** - * Register the connection initiator callback. - * Called once by the main process at startup. - */ -export function setConnectionInitiator(fn: ConnectionInitiator): void { - connectionInitiator = fn; -} - -/** - * Get the registered connection initiator. - * Returns null if not yet registered (app not fully initialized). - */ -export function getConnectionInitiator(): ConnectionInitiator | null { - return connectionInitiator; -}