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
1 change: 1 addition & 0 deletions cloudflare/composio-broker/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ describe("connected-apps broker boundaries", () => {
expect(fetchCalls.some((call) =>
call.url.includes("/tool_router/session/trs_multi/toolkits?")
&& !call.url.includes("toolkits=")
&& call.url.includes("is_connected=true")
&& call.url.includes("cursor=toolkits-page-2")
)).toBe(true);

Expand Down
4 changes: 3 additions & 1 deletion cloudflare/composio-broker/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,9 @@ async function listSessionToolkits(
const seenCursors = new Set<string>();
let cursor: string | undefined;
for (let page = 0; page < MAX_CONNECTED_ACCOUNT_PAGES; page += 1) {
const params = new URLSearchParams({ limit: "50" });
// Avoid walking the full marketplace just to render the Connected tab.
// Composio supports a server-side connected-only filter on this route.
const params = new URLSearchParams({ limit: "50", is_connected: "true" });
if (cursor) params.set("cursor", cursor);
const response = await composioRequest(
env,
Expand Down
1 change: 1 addition & 0 deletions server/composio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ describe.sequential("Composio Sessions", () => {
expect(inventoryCalls[1]?.query).toContain("cursor=accounts-page-2");
const toolkitCalls = calls.slice(callCount).filter((call) => call.path.endsWith("/toolkits"));
expect(toolkitCalls).toHaveLength(2);
expect(toolkitCalls[0]?.query).toContain("is_connected=true");
expect(toolkitCalls[1]?.query).toContain("cursor=toolkits-page-2");
});

Expand Down
8 changes: 7 additions & 1 deletion server/composio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,10 @@ async function listSessionToolkits(
const seenCursors = new Set<string>();
let cursor: string | undefined;
for (let page = 0; page < MAX_CONNECTED_ACCOUNT_PAGES; page += 1) {
const params = new URLSearchParams({ limit: "50" });
// The unfiltered endpoint contains the entire Composio marketplace and is
// cursor-paginated in 50-item pages. The Connected tab only needs the
// user's connected toolkits, so avoid scanning hundreds of unrelated apps.
const params = new URLSearchParams({ limit: "50", is_connected: "true" });
if (cursor) params.set("cursor", cursor);
const response = await fetch(
`${apiBase()}/tool_router/session/${encodeURIComponent(sessionId)}/toolkits?${params}`,
Expand Down Expand Up @@ -673,6 +676,8 @@ export interface ToolkitCard {
label: string;
blurb: string;
logo: string | null;
/** Toolkits such as public search need no user authorization. */
noAuth?: boolean;
/** used for the client-side favicon fallback when logo is null/broken */
domain: string | null;
}
Expand Down Expand Up @@ -735,6 +740,7 @@ export async function listToolkits(cfg: AppConfig): Promise<{ cards: ToolkitCard
label: t.name ?? t.slug ?? "",
blurb: (t.meta?.description ?? t.description ?? "").slice(0, 90),
logo: t.meta?.logo ?? t.logo ?? null,
noAuth: t.no_auth === true,
domain: null,
}));
toolkitCache = { at: Date.now(), cards };
Expand Down
10 changes: 9 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { Sidebar } from "@/components/Sidebar";
import { ChatView } from "@/components/ChatView";
import { GroupView } from "@/components/GroupView";
import { SettingsPanel } from "@/components/SettingsPanel";
import { PluginsPanel } from "@/components/PluginsPanel";
import { PluginsPanel, preloadConnectedApps } from "@/components/PluginsPanel";
import { ComputerPanel } from "@/components/ComputerPanel";
import { InspectorPanel } from "@/components/InspectorPanel";
import { SettingsModal } from "@/components/SettingsModal";
Expand Down Expand Up @@ -75,6 +75,14 @@ function Shell() {
window.ogb?.setUnreadCount?.(unreadCount);
}, [unreadCount]);

// Warm connected-account state as soon as the local server is available.
// The modal then opens with the correct Connect/Add account buttons and
// quietly revalidates instead of rediscovering every account from scratch.
useEffect(() => {
if (!state.connected) return;
void preloadConnectedApps().catch(() => {});
}, [state.connected]);

// Picking a conversation closes the drawer: on a phone the chat is what you
// asked for, and leaving the list up would hide it. Watching activeView too
// catches re-selecting the bot that is already current from another view —
Expand Down
33 changes: 33 additions & 0 deletions src/components/PluginsPanel.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { describe, expect, it } from "vitest";

import {
connectedInventoryCopy,
connectorActionLabel,
disconnectAccountConfirmation,
mergeCompleteConnectorStatus,
mergeCurrentConnectorStatus,
requiresAccountAlias,
type ConnectorStatus,
} from "./PluginsPanel";

Expand Down Expand Up @@ -112,4 +115,34 @@ describe("connected-app status races", () => {
"Disconnect “ca_personal” from GitHub? Only this GitHub account will be revoked.",
);
});

it("recognizes the existing-account alias guard and ignores unrelated errors", () => {
expect(requiresAccountAlias("Add an account alias so the existing connection is not replaced")).toBe(true);
expect(requiresAccountAlias("Authorization expired")).toBe(false);
});

it("never presents unloaded account state as disconnected", () => {
expect(connectedInventoryCopy("loading").title).toBe("Checking connected apps…");
expect(connectorActionLabel("loading", {
busy: false,
included: false,
canContinue: false,
hasAccounts: false,
failed: false,
})).toBe("Checking…");
expect(connectorActionLabel("ready", {
busy: false,
included: false,
canContinue: false,
hasAccounts: true,
failed: false,
})).toBe("Add account");
expect(connectorActionLabel("error", {
busy: false,
included: false,
canContinue: false,
hasAccounts: false,
failed: false,
})).toBe("Unavailable");
});
});
Loading
Loading