feat(composio): backend-proxied Composio integration end-to-end#501
feat(composio): backend-proxied Composio integration end-to-end#501senamakel merged 6 commits intotinyhumansai:mainfrom
Conversation
- Introduced a new Composio module for backend-proxied access to various OAuth integrations. - Implemented ComposioClient for handling API requests related to toolkits, connections, and actions. - Added RPC operations for listing toolkits, managing connections, and executing actions. - Registered Composio controllers and schemas for integration with the existing system. - Created a debug script for testing Composio OAuth flow against the live backend. - Updated Cargo.lock to version 0.52.3 to reflect the new changes.
- Added ComposioConnectModal for managing Composio toolkit connections, mirroring the user experience of existing modals. - Introduced composioApi for backend communication, including functions for listing toolkits, managing connections, and handling OAuth authorization. - Created toolkitMeta for displaying metadata of Composio toolkits in the Skills grid. - Developed hooks for fetching and managing Composio integrations, ensuring real-time updates on connection status. - Updated Skills page to integrate Composio toolkits, providing a seamless user experience for connecting and managing integrations.
…ger script - Bumped OpenHuman package version in Cargo.lock to 0.52.3. - Introduced a new script, debug-composio-trigger.mjs, for Socket.IO live listening of Composio trigger events, facilitating testing and debugging of webhook interactions.
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 4 minutes and 10 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds end-to-end Composio integration: frontend modal + hooks and RPC client for OAuth/connect/disconnect/polling; backend Composio client, RPC ops, agent tools, types, event-bus trigger handling and Socket.IO wiring; CLI debug scripts and Skills page UX to manage connections. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User as User (browser)
participant Modal as ComposioConnectModal
participant FrontApi as composioApi (frontend RPC)
participant Core as Core RPC / Sidecar
participant Backend as Backend ComposioClient
User->>Modal: Click "Connect"
Modal->>FrontApi: authorize(toolkit)
FrontApi->>Core: callCoreRpc openhuman.composio_authorize
Core->>Backend: ComposioClient.authorize -> obtain connectUrl
Backend-->>Core: {connectUrl, connectionId}
Core-->>FrontApi: authorize response
FrontApi-->>Modal: connectUrl
Modal-->>User: open connectUrl (external window)
Modal->>Core: periodically listConnections (poll)
Core->>Backend: ComposioClient.list_connections
Backend-->>Core: connection list
Core-->>Modal: connections
alt connection becomes connected
Modal->>Modal: transition to connected, show details
else timeout / error
Modal->>Modal: transition to error
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (7)
app/src/components/composio/toolkitMeta.ts (1)
73-86: Use a const arrow forcomposioToolkitMeta.This helper lives under
app/src, so it should follow the app TS convention here.♻️ Suggested change
-export function composioToolkitMeta(slug: string): ComposioToolkitMeta { +export const composioToolkitMeta = (slug: string): ComposioToolkitMeta => { const key = slug.toLowerCase(); const hit = CATALOG[key]; if (hit) return { slug: key, ...hit }; // Fallback: title-case the slug and bucket it under "Other". const name = key.charAt(0).toUpperCase() + key.slice(1); return { slug: key, name, description: `Composio integration for ${name}.`, category: 'Other', icon: '\uD83D\uDD0C', }; -} +};As per coding guidelines "Prefer arrow functions over function declarations".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/composio/toolkitMeta.ts` around lines 73 - 86, Replace the function declaration composioToolkitMeta with an exported const arrow function while preserving its behavior and types: keep the parameter name slug: string, use the same local variables (key, hit, name), reference CATALOG and return the same ComposioToolkitMeta shape (including fallback object with slug, name, description, category, icon). Ensure the exported identifier remains composioToolkitMeta and the function remains typed to return ComposioToolkitMeta.src/openhuman/event_bus/events.rs (1)
292-294: Add Composio cases toall_variants_have_correct_domain.The new
"composio"arm is not covered by the regression test below, so a future remap of these variants would slip through unnoticed.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/event_bus/events.rs` around lines 292 - 294, The test all_variants_have_correct_domain is missing the new Composio variants, so update that test to include ComposioTriggerReceived, ComposioConnectionCreated, and ComposioActionExecuted mapped to the "composio" domain; locate the match/expected mapping inside the test (all_variants_have_correct_domain in events.rs) and add these three variants to the expected set or match arm so the regression test verifies they resolve to "composio".src/openhuman/socket/event_handlers.rs (1)
115-158: Parsecomposio:triggerthrough the shared DTO.This branch redefines the webhook payload shape by hand even though
src/openhuman/composio/types.rsalready hasComposioTriggerEvent. Deserializing once here would keep the transport parser and domain type in sync and make backend shape drift fail loudly instead of publishing partially-empty events.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/socket/event_handlers.rs` around lines 115 - 158, Replace the manual field extraction in the "composio:trigger" branch with deserialization into the shared DTO ComposioTriggerEvent from src/openhuman/composio/types.rs: attempt serde_json::from_value(data.clone()) (or from a specific nested value) to produce a ComposioTriggerEvent, handle the Err by logging a warning and dropping the event, and on Ok map the DTO fields into DomainEvent::ComposioTriggerReceived (preserving payload/metadata_id/metadata_uuid/toolkit/trigger) before calling publish_global; this ensures shape mismatches fail at deserialize time and reuses the canonical type rather than hand-parsing fields.src/openhuman/composio/ops.rs (1)
186-188: Consider using#[allow(unused_imports)]on the re-export instead.The
_unused_markerfunction is a workaround for dead_code warnings. A cleaner approach is to allow unused imports directly on the re-export line.♻️ Cleaner unused warning suppression
// ── Helpers re-exported so callers can pull connection/tool types without // reaching into the nested types module. +#[allow(unused_imports)] pub use super::types::{ComposioConnection as Connection, ComposioToolSchema as ToolSchemaType}; - -// Suppress unused import warnings when only some ops are referenced. -#[allow(dead_code)] -fn _unused_marker(_c: ComposioConnection, _t: ComposioToolSchema) {}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/composio/ops.rs` around lines 186 - 188, The current _unused_marker function (_unused_marker(_c: ComposioConnection, _t: ComposioToolSchema)) is used to silence dead_code warnings; instead remove that function and apply #[allow(unused_imports)] directly to the re-export(s) that bring ComposioConnection and ComposioToolSchema into scope (the pub use / re-export line), so unused import warnings are suppressed at the import site rather than by a dummy function.scripts/debug-composio-login.sh (1)
94-112: Consider adding a trap for temp file cleanup on script abort.If the script is killed between
mktempandrm -f, the temp file may be left behind. This is minor since the file contains only HTTP response data and is in/tmp.♻️ Optional: Add trap for cleanup
set -euo pipefail + +CLEANUP_FILES=() +cleanup() { rm -f "${CLEANUP_FILES[@]}" 2>/dev/null || true; } +trap cleanup EXIT SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"Then in the
callfunction:local tmp tmp="$(mktemp)" + CLEANUP_FILES+=("$tmp")🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/debug-composio-login.sh` around lines 94 - 112, The call function creates a temporary file with mktemp (tmp) but only removes it at the end, so if the script is aborted the temp file can be left behind; update the script to ensure cleanup by installing a trap that removes the temporary file on EXIT/INT/TERM (or within call use a trap on RETURN) so tmp is always removed, referencing the mktemp invocation and the tmp variable used alongside the existing rm -f "$tmp" cleanup in call.scripts/debug-composio-trigger.mjs (1)
154-169: Consider logging the error in debug mode for easier troubleshooting.The empty catch block silently returns
null, which is the intended fallback behavior. However, logging the actual error in debug mode would help diagnose why the binary fallback failed.♻️ Optional: Add debug logging for core binary errors
- } catch { + } catch (err) { + dbg('core binary failed:', err.message); return null; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/debug-composio-trigger.mjs` around lines 154 - 169, In getSessionTokenFromCore, the catch block swallows errors—capture the thrown error (catch (err)) and, when a debug flag is set (e.g. process.env.DEBUG or a new process.env.COMPOSIO_DEBUG), log a concise diagnostic including coreBin and the error message (use processLogger or console.debug) before returning null; keep the fallback behavior unchanged and only emit the log in debug mode so execSync failures (from coreBin / auth get_session_token) are visible during troubleshooting.src/openhuman/composio/tools.rs (1)
339-358: UnnecessaryArcwrapping followed by immediate clone.The code wraps
clientinArc, then immediately dereferences and clones it ((*client).clone()), which defeats the purpose ofArc. SinceComposioClientis alreadyClone(it wrapsArc<IntegrationClient>internally), just clone it directly.♻️ Simplify by removing unnecessary Arc
pub fn all_composio_agent_tools( config: &crate::openhuman::config::IntegrationsConfig, ) -> Vec<Box<dyn Tool>> { - let Some(client) = super::client::build_composio_client(config) else { + let Some(c) = super::client::build_composio_client(config) else { tracing::debug!("[composio] agent tools not registered — disabled or missing credentials"); return Vec::new(); }; - let client = Arc::new(client); - // Each tool gets its own cheap clone of the client handle. - let c = (*client).clone(); let tools: Vec<Box<dyn Tool>> = vec![ Box::new(ComposioListToolkitsTool::new(c.clone())), Box::new(ComposioListConnectionsTool::new(c.clone())), Box::new(ComposioAuthorizeTool::new(c.clone())), Box::new(ComposioListToolsTool::new(c.clone())), Box::new(ComposioExecuteTool::new(c)), ]; tracing::debug!(count = tools.len(), "[composio] agent tools registered"); tools }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/openhuman/composio/tools.rs` around lines 339 - 358, In all_composio_agent_tools: avoid wrapping the returned client in Arc then immediately dereferencing and cloning; instead use the Clone impl on the ComposioClient returned by super::client::build_composio_client(config) directly (e.g., let client = match build_composio_client(...) { Some(c) => c, ... }), then create cheap clones with client.clone() and pass those clones into ComposioListToolkitsTool::new, ComposioListConnectionsTool::new, ComposioAuthorizeTool::new, ComposioListToolsTool::new and ComposioExecuteTool::new; remove the Arc::new(...) and the (*client).clone() temporary to simplify the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/src/components/composio/ComposioConnectModal.tsx`:
- Around line 84-125: startPolling currently uses setInterval which can trigger
overlapping async tick executions (calling listConnections) and race updates
like setPhase and onChanged; change tick handling to prevent overlap by adding
an in-flight guard (e.g., a boolean or ref like isPollingRef used by tick) or
replace the setInterval with a recursive setTimeout that awaits tick before
scheduling the next call; update references to pollTimerRef and stopPolling to
clear the timeout when stopping, keep using pollDeadlineRef, POLL_INTERVAL_MS,
listConnections, deriveComposioState, setPhase and onChanged as before, and
ensure tick returns early when the in-flight flag is set or the deadline has
passed to avoid concurrent polls and races.
- Around line 48-54: The modal currently only treats an incoming connection as
'connected' vs 'idle'; change initialization and resume logic to derive the true
composio state from the incoming connection and resume pending flows instead of
resetting to 'idle': use deriveComposioState(connection) to map the incoming
connection into the Phase enum (e.g., map any pending/authorizing state to
'authorizing' or the appropriate Phase) when setting the initial phase and when
setting activeConnection/connectUrl so the modal will start polling for an
in-flight OAuth handoff rather than calling authorize() anew; update the
initialization of phase, activeConnection, and connectUrl (and any code paths
that currently call authorize() on mount) to prefer resuming the existing
connection when deriveComposioState(connection) indicates a pending/authorizing
state.
In `@src/openhuman/channels/runtime/startup.rs`:
- Around line 47-49: The Composio trigger subscriber is being registered
unconditionally; move the feature-gate check for integrations.enabled &&
integrations.composio.enabled into the register_composio_trigger_subscriber()
function so the function itself becomes a no-op when the feature is disabled,
and remove any duplicate gating at the call sites (e.g., startup.rs and
src/core/jsonrpc.rs) so both entry points call the same guarded function; update
register_composio_trigger_subscriber() to read the integrations config (or
accept it as an argument) and return early when composio is disabled, ensuring
subscriber registration only happens when the feature is enabled.
In `@src/openhuman/composio/client.rs`:
- Around line 161-167: The comment above the reqwest client builder is
misleading: instead of saying it "reuses the shared client's reqwest pool,"
update the comment to state that this code builds a fresh lightweight Client
(creating a new connection pool) for the DELETE call; optionally note that to
actually reuse the shared pool you should clone or use the existing shared
reqwest::Client rather than calling Client::builder().build(). Refer to the
http_client local and the Client::builder() block (the
timeout/connect_timeout/build() sequence) when making this change.
In `@src/openhuman/composio/schemas.rs`:
- Around line 208-259: Handlers handle_authorize, handle_delete_connection and
handle_execute currently trim but allow all-whitespace strings to pass to the
backend; create a helper (e.g., read_required_non_empty for String) that calls
read_required::<String>, trims the result and returns Err when the trimmed
string is empty, then replace calls to read_required::<String>(¶ms,
"toolkit"/"connection_id"/"tool") in those handlers with the new
read_required_non_empty so blank/whitespace-only inputs are rejected locally
before invoking super::ops::* methods; keep read_required and read_optional
unchanged for other types.
---
Nitpick comments:
In `@app/src/components/composio/toolkitMeta.ts`:
- Around line 73-86: Replace the function declaration composioToolkitMeta with
an exported const arrow function while preserving its behavior and types: keep
the parameter name slug: string, use the same local variables (key, hit, name),
reference CATALOG and return the same ComposioToolkitMeta shape (including
fallback object with slug, name, description, category, icon). Ensure the
exported identifier remains composioToolkitMeta and the function remains typed
to return ComposioToolkitMeta.
In `@scripts/debug-composio-login.sh`:
- Around line 94-112: The call function creates a temporary file with mktemp
(tmp) but only removes it at the end, so if the script is aborted the temp file
can be left behind; update the script to ensure cleanup by installing a trap
that removes the temporary file on EXIT/INT/TERM (or within call use a trap on
RETURN) so tmp is always removed, referencing the mktemp invocation and the tmp
variable used alongside the existing rm -f "$tmp" cleanup in call.
In `@scripts/debug-composio-trigger.mjs`:
- Around line 154-169: In getSessionTokenFromCore, the catch block swallows
errors—capture the thrown error (catch (err)) and, when a debug flag is set
(e.g. process.env.DEBUG or a new process.env.COMPOSIO_DEBUG), log a concise
diagnostic including coreBin and the error message (use processLogger or
console.debug) before returning null; keep the fallback behavior unchanged and
only emit the log in debug mode so execSync failures (from coreBin / auth
get_session_token) are visible during troubleshooting.
In `@src/openhuman/composio/ops.rs`:
- Around line 186-188: The current _unused_marker function (_unused_marker(_c:
ComposioConnection, _t: ComposioToolSchema)) is used to silence dead_code
warnings; instead remove that function and apply #[allow(unused_imports)]
directly to the re-export(s) that bring ComposioConnection and
ComposioToolSchema into scope (the pub use / re-export line), so unused import
warnings are suppressed at the import site rather than by a dummy function.
In `@src/openhuman/composio/tools.rs`:
- Around line 339-358: In all_composio_agent_tools: avoid wrapping the returned
client in Arc then immediately dereferencing and cloning; instead use the Clone
impl on the ComposioClient returned by
super::client::build_composio_client(config) directly (e.g., let client = match
build_composio_client(...) { Some(c) => c, ... }), then create cheap clones with
client.clone() and pass those clones into ComposioListToolkitsTool::new,
ComposioListConnectionsTool::new, ComposioAuthorizeTool::new,
ComposioListToolsTool::new and ComposioExecuteTool::new; remove the
Arc::new(...) and the (*client).clone() temporary to simplify the code.
In `@src/openhuman/event_bus/events.rs`:
- Around line 292-294: The test all_variants_have_correct_domain is missing the
new Composio variants, so update that test to include ComposioTriggerReceived,
ComposioConnectionCreated, and ComposioActionExecuted mapped to the "composio"
domain; locate the match/expected mapping inside the test
(all_variants_have_correct_domain in events.rs) and add these three variants to
the expected set or match arm so the regression test verifies they resolve to
"composio".
In `@src/openhuman/socket/event_handlers.rs`:
- Around line 115-158: Replace the manual field extraction in the
"composio:trigger" branch with deserialization into the shared DTO
ComposioTriggerEvent from src/openhuman/composio/types.rs: attempt
serde_json::from_value(data.clone()) (or from a specific nested value) to
produce a ComposioTriggerEvent, handle the Err by logging a warning and dropping
the event, and on Ok map the DTO fields into
DomainEvent::ComposioTriggerReceived (preserving
payload/metadata_id/metadata_uuid/toolkit/trigger) before calling
publish_global; this ensures shape mismatches fail at deserialize time and
reuses the canonical type rather than hand-parsing fields.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8d90e544-39f7-478c-9a9c-1c6fd1d86eff
⛔ Files ignored due to path filters (2)
Cargo.lockis excluded by!**/*.lockapp/src-tauri/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (23)
app/src/components/composio/ComposioConnectModal.tsxapp/src/components/composio/toolkitMeta.tsapp/src/lib/composio/composioApi.tsapp/src/lib/composio/hooks.tsapp/src/lib/composio/types.tsapp/src/pages/Skills.tsxscripts/debug-composio-login.shscripts/debug-composio-trigger.mjssrc/core/all.rssrc/core/jsonrpc.rssrc/openhuman/channels/runtime/startup.rssrc/openhuman/composio/bus.rssrc/openhuman/composio/client.rssrc/openhuman/composio/mod.rssrc/openhuman/composio/ops.rssrc/openhuman/composio/schemas.rssrc/openhuman/composio/tools.rssrc/openhuman/composio/types.rssrc/openhuman/config/schema/tools.rssrc/openhuman/event_bus/events.rssrc/openhuman/mod.rssrc/openhuman/socket/event_handlers.rssrc/openhuman/tools/ops.rs
| crate::openhuman::health::bus::register_health_subscriber(); | ||
| crate::openhuman::skills::bus::register_skill_cleanup_subscriber(); | ||
| crate::openhuman::composio::register_composio_trigger_subscriber(); |
There was a problem hiding this comment.
Honor the Composio feature gate before subscribing.
This path registers the trigger subscriber even when integrations.enabled or integrations.composio.enabled is false, so Composio trigger handling can still come alive with the feature turned off. src/core/jsonrpc.rs now does the same, so the safest fix is to move the gate into register_composio_trigger_subscriber() and share it across both entry points.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/openhuman/channels/runtime/startup.rs` around lines 47 - 49, The Composio
trigger subscriber is being registered unconditionally; move the feature-gate
check for integrations.enabled && integrations.composio.enabled into the
register_composio_trigger_subscriber() function so the function itself becomes a
no-op when the feature is disabled, and remove any duplicate gating at the call
sites (e.g., startup.rs and src/core/jsonrpc.rs) so both entry points call the
same guarded function; update register_composio_trigger_subscriber() to read the
integrations config (or accept it as an argument) and return early when composio
is disabled, ensuring subscriber registration only happens when the feature is
enabled.
- Updated ComposioConnectModal to handle various connection phases more effectively, including 'waiting' for pending connections. - Introduced new refs for managing polling state and in-flight requests to prevent overlapping executions. - Improved error handling during polling, providing clearer feedback on OAuth timeouts. - Added logic to resume polling if the modal opens while an OAuth handoff is in progress. - Refactored connection handling in the modal for better clarity and maintainability.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (3)
scripts/debug-composio-trigger.mjs (1)
65-76: Load.envbefore resolvingTRIGGER_SLUG.Line 76 reads
process.env.TRIGGER_SLUGbefore Lines 98-99 callloadEnv(), so the documented.env/app/.env.localoverride never applies unless the caller exported it in the parent shell.Proposed fix
-const args = process.argv.slice(2); -const flag = (name) => args.includes(name); -const valueOf = (name, fallback) => { - const idx = args.indexOf(name); - if (idx === -1 || idx === args.length - 1) return fallback; - return args[idx + 1]; -}; - -const DEBUG = flag('--debug'); -const TIMEOUT_SECS = parseInt(valueOf('--timeout', '0'), 10); // 0 = forever -const MAX_EVENTS = parseInt(valueOf('--max-events', '0'), 10); // 0 = unlimited -const TRIGGER_SLUG = (valueOf('--trigger', process.env.TRIGGER_SLUG || 'GMAIL_NEW_GMAIL_MESSAGE')).trim(); - function dbg(...a) { if (DEBUG) console.log(' [debug]', ...a); } @@ loadEnv(path.join(ROOT, '.env')); loadEnv(path.join(ROOT, 'app', '.env.local')); + +const args = process.argv.slice(2); +const flag = (name) => args.includes(name); +const valueOf = (name, fallback) => { + const idx = args.indexOf(name); + if (idx === -1 || idx === args.length - 1) return fallback; + return args[idx + 1]; +}; + +const DEBUG = flag('--debug'); +const TIMEOUT_SECS = parseInt(valueOf('--timeout', '0'), 10); // 0 = forever +const MAX_EVENTS = parseInt(valueOf('--max-events', '0'), 10); // 0 = unlimited +const TRIGGER_SLUG = (valueOf('--trigger', process.env.TRIGGER_SLUG || 'GMAIL_NEW_GMAIL_MESSAGE')).trim();Also applies to: 84-99
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/debug-composio-trigger.mjs` around lines 65 - 76, TRIGGER_SLUG (and other environment-dependent constants resolved via valueOf) is being computed before the project's .env is loaded, so process.env overrides from loadEnv() never apply; move the call to loadEnv() (or ensure loadEnv() is invoked) before you compute TRIGGER_SLUG, TIMEOUT_SECS, MAX_EVENTS, DEBUG, and any other variables that read process.env so that valueOf('--trigger', process.env.TRIGGER_SLUG || ...) picks up .env values; update the script to call loadEnv() (or import/execute its initializer) prior to the lines that define flag(), valueOf(), and the constants that depend on process.env.app/src/components/composio/ComposioConnectModal.tsx (1)
38-43: Prefer an arrow component declaration for consistency with repo TS style.This default-exported function can be converted to a
constarrow component to align with project conventions.♻️ Suggested refactor
-export default function ComposioConnectModal({ +const ComposioConnectModal = ({ toolkit, connection, onChanged, onClose, }: ComposioConnectModalProps) { @@ -} +}; + +export default ComposioConnectModal;As per coding guidelines,
**/*.{js,jsx,ts,tsx}: “Prefer arrow functions over function declarations”.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@app/src/components/composio/ComposioConnectModal.tsx` around lines 38 - 43, Convert the default-exported function component ComposioConnectModal to a const arrow component to match repo style: replace the function declaration "export default function ComposioConnectModal({ toolkit, connection, onChanged, onClose, }: ComposioConnectModalProps)" with a const arrow like "const ComposioConnectModal: React.FC<ComposioConnectModalProps> = ({ toolkit, connection, onChanged, onClose }) => { ... }" and keep the default export (either "export default ComposioConnectModal" at the bottom or export inline); ensure TypeScript typing still references ComposioConnectModalProps and that all references to the component remain unchanged.scripts/debug-composio-login.sh (1)
188-188: Build JSON payloads withjq -ninstead of string interpolation.Direct interpolation can produce invalid JSON when values include quotes/backslashes.
♻️ Suggested refactor
- call POST "/agent-integrations/composio/authorize" "{\"toolkit\":\"$TOOLKIT\"}" + AUTH_PAYLOAD="$(jq -cn --arg toolkit "$TOOLKIT" '{toolkit: $toolkit}')" + call POST "/agent-integrations/composio/authorize" "$AUTH_PAYLOAD" @@ - call POST "/agent-integrations/composio/execute" \ - "{\"tool\":\"$EXECUTE_TOOL\",\"arguments\":{}}" + EXEC_PAYLOAD="$(jq -cn --arg tool "$EXECUTE_TOOL" '{tool: $tool, arguments: {}}')" + call POST "/agent-integrations/composio/execute" "$EXEC_PAYLOAD"Also applies to: 263-264
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/debug-composio-login.sh` at line 188, The script builds JSON payloads via string interpolation which can break when values contain quotes or backslashes; update the two places that call the helper 'call' (e.g., the POST to "/agent-integrations/composio/authorize" and the occurrences around lines 263-264) to generate the JSON payload with jq -n (or an equivalent JSON-safe builder) and pass the resulting string to the call command, ensuring values like $TOOLKIT (and the other variables used later) are safely encoded as JSON strings rather than interpolated into raw JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/debug-composio-login.sh`:
- Around line 223-225: The current jq selection that sets STATUS from
CONNECTIONS_JSON uses "id == $cid OR toolkit == $tk" and then head -n1, which
can return a stale toolkit-only record; change the logic to prefer an exact id
match first and only fall back to a toolkit match if no id match exists: when
computing STATUS (the STATUS assignment that reads CONNECTIONS_JSON with jq
using $TOOLKIT and $CONNECTION_ID), implement a two-step selection (try
select(.id == $cid) first, and if that yields nothing then select where .toolkit
equals $tk case-insensitively) or use a jq expression that returns .status from
an id match before considering toolkit matches so head -n1 cannot pick a stale
toolkit-only entry.
In `@scripts/debug-composio-trigger.mjs`:
- Around line 233-261: The Gmail precheck currently hard-exits if no Gmail
connection is found, blocking other toolkit triggers; change it to determine the
target toolkit from the trigger flag/argument (e.g. parse --trigger or a
variable like desiredToolkit), then replace the hard-coded gmail lookup (the
connections.find(...) that assigns gmail) with a lookup for the desiredToolkit
(compare c.toolkit to desiredToolkit case-insensitively), and only call
process.exit(1) when the desiredToolkit is 'gmail' and no connection exists; for
other toolkits, log a warning (use fail/console.warn/info) but do not exit so
the socket listener can still run.
- Around line 58-60: The dynamic import currently passes a filesystem path
(socketIoPath) directly to import(), which breaks on Windows; import
pathToFileURL from 'url' and convert socketIoPath to a file:// URL (e.g.,
pathToFileURL(socketIoPath).href or toString()) before calling await
import(...), update the top-of-file imports alongside fileURLToPath and ensure
any other dynamic imports in this module use pathToFileURL so Node ESM receives
a valid URL.
In `@src/openhuman/composio/ops.rs`:
- Line 11: Remove the erroneous import `use anyhow::Result;` and add a concrete
alias for the module's error result type, e.g. `type OpResult<T> =
std::result::Result<T, String>;`, then update the seven functions that currently
declare return types as `Result<T, String>` to use `OpResult<T>` instead (i.e.,
replace each `Result<..., String>` with `OpResult<...>` for the functions that
return the module-specific string-error Result).
---
Nitpick comments:
In `@app/src/components/composio/ComposioConnectModal.tsx`:
- Around line 38-43: Convert the default-exported function component
ComposioConnectModal to a const arrow component to match repo style: replace the
function declaration "export default function ComposioConnectModal({ toolkit,
connection, onChanged, onClose, }: ComposioConnectModalProps)" with a const
arrow like "const ComposioConnectModal: React.FC<ComposioConnectModalProps> = ({
toolkit, connection, onChanged, onClose }) => { ... }" and keep the default
export (either "export default ComposioConnectModal" at the bottom or export
inline); ensure TypeScript typing still references ComposioConnectModalProps and
that all references to the component remain unchanged.
In `@scripts/debug-composio-login.sh`:
- Line 188: The script builds JSON payloads via string interpolation which can
break when values contain quotes or backslashes; update the two places that call
the helper 'call' (e.g., the POST to "/agent-integrations/composio/authorize"
and the occurrences around lines 263-264) to generate the JSON payload with jq
-n (or an equivalent JSON-safe builder) and pass the resulting string to the
call command, ensuring values like $TOOLKIT (and the other variables used later)
are safely encoded as JSON strings rather than interpolated into raw JSON.
In `@scripts/debug-composio-trigger.mjs`:
- Around line 65-76: TRIGGER_SLUG (and other environment-dependent constants
resolved via valueOf) is being computed before the project's .env is loaded, so
process.env overrides from loadEnv() never apply; move the call to loadEnv() (or
ensure loadEnv() is invoked) before you compute TRIGGER_SLUG, TIMEOUT_SECS,
MAX_EVENTS, DEBUG, and any other variables that read process.env so that
valueOf('--trigger', process.env.TRIGGER_SLUG || ...) picks up .env values;
update the script to call loadEnv() (or import/execute its initializer) prior to
the lines that define flag(), valueOf(), and the constants that depend on
process.env.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a0e60ba8-7b02-4754-959e-ecbf7a677018
📒 Files selected for processing (9)
app/src/components/composio/ComposioConnectModal.tsxscripts/debug-composio-login.shscripts/debug-composio-trigger.mjssrc/openhuman/composio/client.rssrc/openhuman/composio/ops.rssrc/openhuman/composio/schemas.rssrc/openhuman/composio/tools.rssrc/openhuman/event_bus/events.rssrc/openhuman/socket/event_handlers.rs
🚧 Files skipped from review as they are similar to previous changes (4)
- src/openhuman/socket/event_handlers.rs
- src/openhuman/event_bus/events.rs
- src/openhuman/composio/client.rs
- src/openhuman/composio/tools.rs
…fication - Updated debug-composio-login.sh to build JSON payloads using jq for safer handling of toolkit data. - Enhanced connection verification logic in debug-composio-trigger.mjs to prioritize newly created connections and provide warnings for missing toolkits. - Refactored composio operations in ops.rs to return a more explicit error type, improving clarity in error handling across RPC operations.
Summary
openhuman::composioRust domain module with RPC controllers (openhuman.composio_list_toolkits,_list_connections,_authorize,_delete_connection,_list_tools,_execute), backend-proxied through the sharedIntegrationClient— no Composio API key on the client.composio_list_toolkits,composio_list_connections,composio_authorize,composio_list_tools,composio_execute) registered in the tool registry whenintegrations.composio.enabledis set.composio:triggerevents from the backend into the event bus asDomainEvent::ComposioTriggerReceived, with aComposioTriggerSubscriberregistered at startup in bothchannels::runtime::startupandcore::jsonrpcbootstrap.UnifiedSkillCards in the same grid as regular skills, grouped by category. Click →ComposioConnectModal→openhuman.composio_authorize→ opens Composio OAuth URL via@tauri-apps/plugin-opener→ pollscomposio_list_connections→ flips to "Connected" state.scripts/debug-composio-login.sh(curl + jq OAuth flow),scripts/debug-composio-trigger.mjs(Node socket.io listener with catchall + heartbeat + diagnostic checklist).Problem
The core had a monolithic
tools/composio.rsthat called Composio's v2/v3 API directly withx-api-key, bypassing the backend's allowlist, billing/margin, HMAC webhook pipeline, and trigger fan-out. The backend team shipped/agent-integrations/composio/*routes (PR https://github.com/tinyhumansai/backend/pull/625) but nothing in the core or UI consumed them, so users couldn't authorize toolkits, receive trigger events, or see connection state.Solution
Rust core
src/openhuman/composio/domain module following thecronmodule template (mod.rslight,types.rs,client.rs,ops.rs,schemas.rs,bus.rs,tools.rs).ComposioClientwrapsIntegrationClientfor GET/POST and adds a bespokeraw_deletehelper because the shared client doesn't expose DELETE.DomainEventenum extended withComposioTriggerReceived,ComposioConnectionCreated,ComposioActionExecuted;domain()match arm returns"composio".socket::event_handlers::handle_sio_eventparsescomposio:triggerpayloads (toolkit,trigger,metadata.id/uuid,payload) and publishes to the global bus.IntegrationsConfiggains acomposio: IntegrationTogglefield; everything downstream keys off it.core::all::build_registered_controllersandbuild_declared_controller_schemas; namespace description added.Frontend
app/src/lib/composio/{types,composioApi,hooks}.tsmirrorlib/skills/skillsApi.tsshape.useComposioIntegrations()fetches toolkits + connections on mount, polls connections every 5s, derives aconnectionByToolkitMap, detects the "composio disabled" error and short-circuits.ComposioConnectModalportal-based, state machineidle → authorizing → waiting → connected / error, reusesopenUrl()for Tauri-aware OAuth handoff, polls with 5-minute timeout, supports Disconnect.Skills.tsxappends composio items withkind: 'composio'to the unified grid; toolkit metadata (display name, description, category, emoji) lives incomponents/composio/toolkitMeta.tswith a title-cased fallback for unknown slugs so new backend entries render automatically.Debug tooling
scripts/debug-composio-login.sh— pure bash + curl + jq. Walks the OAuth flow:/toolkits→/connections→/authorize→ poll →/tools→ optional/execute.scripts/debug-composio-trigger.mjs— Node script using thesocket.io-clientfrom the app workspace. Resolves JWT from env oropenhuman-core auth get_session_token, verifies the gmail connection is ACTIVE, opens a socket.io client against the backend with the same options the RustSocketManageruses, attaches an always-ononAnycatchall, prints a heartbeat idle log every 15s when no traffic has arrived in 30s+, and on zero events exits with a diagnostic checklist that pinpoints userId mismatch vs silent socket drop vs backend-side drop.Submission Checklist
cargo test --lib openhuman::composio(2 subscriber tests),core::allregistry validation (schemas ↔ handlers paired),event_busdomain match arms.scripts/debug-composio-login.shagainst staging (Gmail OAuth succeeded, connection flipped to ACTIVE) andscripts/debug-composio-trigger.mjs(socket listener confirmed working once a separate backend bug inhandleWebhook.tsv2/v3 payload destructure was fixed — see "Impact" below).//!headers on every new Rust file, JSDoc on every exported TS symbol and every script.Impact
integrations.enabled && integrations.composio.enabled; zero impact if unset. The legacytools/composio.rsdirect-API path is left in place for backwards compatibility and is only wired whenconfig.composio.api_keyis set, so existing users don't regress.IntegrationClientwith Bearer JWT auth.composio_executecharges through the backend's existing pricing pipeline; cost is returned in the response and logged viaComposioActionExecutedevents.src/controllers/agentIntegrations/composio/handleWebhook.tsdestructuresverified.payload.userIdassuming the Composio v2 shape, but v3 webhooks (which Composio is now delivering) put the user identifier under a different key. The emit silently early-returns withdelivered: false. The diagnostic script's catchall + checklist now guides future debuggers straight to this class of issue. A follow-up PR on backend-1 is needed to shape-tolerate the destructure — details in the trigger script's exit message and the session history.Related
handleWebhook.ts+ permanentmatched_sockets=Nlog line.ComposioConnectModal(thelistTools/executeRPCs are already wired incomposioApi.ts).toolkitMeta.tswith SVGs and expand beyond the initial 7 entries.src/openhuman/tools/composio.rsonce all users have migrated off the direct-API path.Summary by CodeRabbit