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;
-}