From e83f379143e6c0e87ac7922243381dd30fb9b4b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9B=B7=E7=94=B5=E8=8A=BD=E8=A1=A3?= Date: Tue, 30 Jun 2026 21:58:48 -0400 Subject: [PATCH] Cache Composio tools and load saved settings in the UI Tools are now cached per-org with a 5-minute TTL to avoid hitting the Composio API on every agent run. The ComposioPanel loads the currently saved API key status and pre-checks enabled toolkits from the snapshot, so users see their existing configuration when opening Settings > Tools. --- .../services/composio/composio-tool-bridge.ts | 18 ++++++++- .../src/main/services/snapshot-service.ts | 1 + .../src/renderer/src/pages/SettingsPage.tsx | 2 +- .../src/pages/settings/ComposioPanel.tsx | 37 ++++++++++++++++--- .../src/shared/snapshot-domain.ts | 8 ++++ 5 files changed, 59 insertions(+), 7 deletions(-) diff --git a/AgentCompany-New/src/main/services/composio/composio-tool-bridge.ts b/AgentCompany-New/src/main/services/composio/composio-tool-bridge.ts index b734d98a..a3842522 100644 --- a/AgentCompany-New/src/main/services/composio/composio-tool-bridge.ts +++ b/AgentCompany-New/src/main/services/composio/composio-tool-bridge.ts @@ -3,6 +3,15 @@ import type { ToolDefinition, ToolCall } from "../provider-tool-turn"; import { ComposioClient, type ComposioToolSchema } from "./composio-client"; const COMPOSIO_TOOL_PREFIX = "composio_"; +const CACHE_TTL_MS = 5 * 60 * 1000; + +interface CacheEntry { + tools: ToolDefinition[]; + fetchedAt: number; + toolkitsKey: string; +} + +const toolCache = new Map(); export function isComposioTool(toolName: string): boolean { return toolName.startsWith(COMPOSIO_TOOL_PREFIX); @@ -34,9 +43,16 @@ export async function loadComposioTools(db: DatabaseSync, orgId: string): Promis if (!apiKey) return []; const toolkits = getEnabledToolkits(db, orgId); if (toolkits.length === 0) return []; + const toolkitsKey = toolkits.sort().join(","); + const cached = toolCache.get(orgId); + if (cached && cached.toolkitsKey === toolkitsKey && Date.now() - cached.fetchedAt < CACHE_TTL_MS) { + return cached.tools; + } const client = new ComposioClient(apiKey); const schemas = await client.getTools(toolkits); - return schemas.map(composioSchemaToToolDefinition); + const tools = schemas.map(composioSchemaToToolDefinition); + toolCache.set(orgId, { tools, fetchedAt: Date.now(), toolkitsKey }); + return tools; } export async function executeComposioTool( diff --git a/AgentCompany-New/src/main/services/snapshot-service.ts b/AgentCompany-New/src/main/services/snapshot-service.ts index 3f848413..94997cbc 100644 --- a/AgentCompany-New/src/main/services/snapshot-service.ts +++ b/AgentCompany-New/src/main/services/snapshot-service.ts @@ -162,6 +162,7 @@ export class SnapshotService { .map((rule) => ({ ...rule, enabled: toBool(rule.enabled) })), meetings: list(this.db, "meetings", "scheduled_at desc"), sprints: list(this.db, "sprints", "start_date desc"), + composioSettings: list(this.db, "composio_settings", "org_id asc, key asc"), }; } diff --git a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx index d829bfe5..69ed4f18 100644 --- a/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx +++ b/AgentCompany-New/src/renderer/src/pages/SettingsPage.tsx @@ -76,7 +76,7 @@ export function SettingsPage({ snapshot, actions }: PageProps) { actions={actions} /> )} - {tab === "tools" && } + {tab === "tools" && } {tab === "skills" && ( { + const row = settings.find((s) => s.orgId === orgId && s.key === "toolkits"); + if (!row?.value) return new Set(); + try { + const parsed = JSON.parse(row.value); + return new Set(Array.isArray(parsed) ? parsed.filter((v): v is string => typeof v === "string") : []); + } catch { + return new Set(); + } +} + +function hasSavedKey(settings: ComposioSettingRecord[], orgId: string): boolean { + return settings.some((s) => s.orgId === orgId && s.key === "api_key" && s.value.length > 0); +} + export function ComposioPanel({ orgId, + settings, actions, }: { orgId: string; + settings: ComposioSettingRecord[]; actions: SnapshotActions; }) { + const savedToolkits = useMemo(() => parseSavedToolkits(settings, orgId), [settings, orgId]); + const keyConfigured = useMemo(() => hasSavedKey(settings, orgId), [settings, orgId]); + const [apiKey, setApiKey] = useState(""); - const [selected, setSelected] = useState>(new Set()); + const [selected, setSelected] = useState>(savedToolkits); const [saved, setSaved] = useState(false); const toggle = (id: string) => { @@ -47,12 +68,17 @@ export function ComposioPanel({ const handleSave = async (e: FormEvent) => { e.preventDefault(); - if (!apiKey.trim()) return; + const key = apiKey.trim(); + if (!key && !keyConfigured) return; + const row = settings.find((s) => s.orgId === orgId && s.key === "api_key"); + const effectiveKey = key || row?.value || ""; + if (!effectiveKey) return; await actions.run(() => window.agentCompany.saveComposioSettings({ orgId, - apiKey: apiKey.trim(), + apiKey: effectiveKey, toolkits: [...selected], })); + setApiKey(""); setSaved(true); setTimeout(() => setSaved(false), 2000); }; @@ -71,6 +97,7 @@ export function ComposioPanel({ className="input" value={apiKey} onChange={(e) => setApiKey(e.target.value)} + placeholder={keyConfigured ? "••••••••" : undefined} autoComplete="off" style={{ width: "100%", maxWidth: 400 }} /> @@ -109,7 +136,7 @@ export function ComposioPanel({
- {saved && Saved} diff --git a/AgentCompany-New/src/shared/snapshot-domain.ts b/AgentCompany-New/src/shared/snapshot-domain.ts index 0c24859d..dbd35d70 100644 --- a/AgentCompany-New/src/shared/snapshot-domain.ts +++ b/AgentCompany-New/src/shared/snapshot-domain.ts @@ -138,4 +138,12 @@ export interface AppSnapshot { automationRules: AutomationRuleRecord[]; meetings: MeetingRecord[]; sprints: SprintRecord[]; + composioSettings: ComposioSettingRecord[]; +} + +export interface ComposioSettingRecord { + orgId: string; + key: string; + value: string; + updatedAt: string; }